]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Nodes.pm
api: node status: cache boot mode info
[pve-manager.git] / PVE / API2 / Nodes.pm
CommitLineData
aff192e6
DM
1package PVE::API2::Nodes::Nodeinfo;
2
3use strict;
4use warnings;
5a378425
TL
5
6use Digest::MD5;
7use Digest::SHA;
aff192e6 8use Filesys::Df;
40993593 9use HTTP::Status qw(:constants);
5a378425
TL
10use JSON;
11use POSIX qw(LONG_MAX);
12use Time::Local qw(timegm_nocheck);
13use Socket;
17711ff8 14use IO::Socket::SSL;
5a378425 15
b289829f 16use PVE::API2Tools;
5a378425
TL
17use PVE::APLInfo;
18use PVE::AccessControl;
c9164975 19use PVE::Cluster qw(cfs_read_file);
5a378425 20use PVE::DataCenterConfig;
05b252dc 21use PVE::Exception qw(raise raise_perm_exc raise_param_exc);
4a07fced 22use PVE::Firewall;
7e5cb2f0 23use PVE::HA::Config;
5a378425
TL
24use PVE::HA::Env::PVE2;
25use PVE::INotify;
26use PVE::JSONSchema qw(get_standard_option);
27use PVE::LXC;
28use PVE::ProcFSTools;
7141ae25 29use PVE::QemuConfig;
b92400b6 30use PVE::QemuServer;
ebb71cb5 31use PVE::RESTEnvironment qw(log_warn);
5a378425
TL
32use PVE::RESTHandler;
33use PVE::RPCEnvironment;
34use PVE::RRD;
35use PVE::Report;
36use PVE::SafeSyslog;
37use PVE::Storage;
81fd95cf 38use PVE::Tools qw(file_get_contents);
5a378425
TL
39use PVE::pvecfg;
40
21299915 41use PVE::API2::APT;
c6c4b278 42use PVE::API2::Capabilities;
38db610a 43use PVE::API2::Ceph;
036475f8 44use PVE::API2::Certificates;
e781363f 45use PVE::API2::Disks;
5a378425
TL
46use PVE::API2::Firewall::Host;
47use PVE::API2::Hardware;
48use PVE::API2::LXC::Status;
49use PVE::API2::LXC;
50use PVE::API2::Network;
51use PVE::API2::NodeConfig;
52use PVE::API2::Qemu::CPU;
5a378425
TL
53use PVE::API2::Qemu;
54use PVE::API2::Replication;
55use PVE::API2::Services;
56use PVE::API2::Storage::Scan;
57use PVE::API2::Storage::Status;
58use PVE::API2::Subscription;
59use PVE::API2::Tasks;
60use PVE::API2::VZDump;
aff192e6 61
4be427a0
AD
62my $have_sdn;
63eval {
bb654699 64 require PVE::API2::Network::SDN::Zones::Status;
4be427a0
AD
65 $have_sdn = 1;
66};
67
aff192e6
DM
68use base qw(PVE::RESTHandler);
69
9018237e
FG
70my $verify_command_item_desc = {
71 description => "An array of objects describing endpoints, methods and arguments.",
72 type => "array",
73 items => {
74 type => "object",
75 properties => {
76 path => {
77 description => "A relative path to an API endpoint on this node.",
78 type => "string",
79 optional => 0,
80 },
81 method => {
82 description => "A method related to the API endpoint (GET, POST etc.).",
83 type => "string",
84 pattern => "(GET|POST|PUT|DELETE)",
85 optional => 0,
86 },
87 args => {
88 description => "A set of parameter names and their values.",
89 type => "object",
90 optional => 1,
91 },
92 },
93 }
94};
95
a1de4410
SS
96PVE::JSONSchema::register_format('pve-command-batch', \&verify_command_batch);
97sub verify_command_batch {
98 my ($value, $noerr) = @_;
99 my $commands = eval { decode_json($value); };
100
79d62026 101 return if $noerr && $@;
a1de4410
SS
102 die "commands param did not contain valid JSON: $@" if $@;
103
79d62026 104 eval { PVE::JSONSchema::validate($commands, $verify_command_item_desc) };
a1de4410 105
79d62026 106 return $commands if !$@;
a1de4410 107
79d62026
TL
108 return if $noerr;
109 die "commands is not a valid array of commands: $@";
a1de4410
SS
110}
111
aff192e6 112__PACKAGE__->register_method ({
f9d26e09 113 subclass => "PVE::API2::Qemu",
aff192e6
DM
114 path => 'qemu',
115});
116
989b7743 117__PACKAGE__->register_method ({
f9d26e09 118 subclass => "PVE::API2::LXC",
989b7743
DM
119 path => 'lxc',
120});
121
38db610a 122__PACKAGE__->register_method ({
f9d26e09 123 subclass => "PVE::API2::Ceph",
38db610a
DM
124 path => 'ceph',
125});
126
bf58f8dd 127__PACKAGE__->register_method ({
f9d26e09 128 subclass => "PVE::API2::VZDump",
bf58f8dd
DM
129 path => 'vzdump',
130});
131
aff192e6 132__PACKAGE__->register_method ({
f9d26e09 133 subclass => "PVE::API2::Services",
aff192e6
DM
134 path => 'services',
135});
136
21ace8d3 137__PACKAGE__->register_method ({
f9d26e09 138 subclass => "PVE::API2::Subscription",
21ace8d3
DM
139 path => 'subscription',
140});
141
aff192e6 142__PACKAGE__->register_method ({
f9d26e09 143 subclass => "PVE::API2::Network",
aff192e6
DM
144 path => 'network',
145});
146
147__PACKAGE__->register_method ({
f9d26e09 148 subclass => "PVE::API2::Tasks",
aff192e6
DM
149 path => 'tasks',
150});
151
152__PACKAGE__->register_method ({
f5b6ccb1 153 subclass => "PVE::API2::Storage::Scan",
aff192e6
DM
154 path => 'scan',
155});
156
523d5f48
TL
157__PACKAGE__->register_method ({
158 subclass => "PVE::API2::Hardware",
159 path => 'hardware',
160});
161
c6c4b278
TL
162__PACKAGE__->register_method ({
163 subclass => "PVE::API2::Capabilities",
164 path => 'capabilities',
165});
523d5f48 166
aff192e6 167__PACKAGE__->register_method ({
f9d26e09 168 subclass => "PVE::API2::Storage::Status",
aff192e6
DM
169 path => 'storage',
170});
171
e781363f
DC
172__PACKAGE__->register_method ({
173 subclass => "PVE::API2::Disks",
174 path => 'disks',
175});
176
21299915 177__PACKAGE__->register_method ({
f9d26e09 178 subclass => "PVE::API2::APT",
21299915
DM
179 path => 'apt',
180});
181
4a07fced 182__PACKAGE__->register_method ({
f9d26e09 183 subclass => "PVE::API2::Firewall::Host",
4a07fced
DM
184 path => 'firewall',
185});
186
892821fd
DM
187__PACKAGE__->register_method ({
188 subclass => "PVE::API2::Replication",
189 path => 'replication',
190});
191
036475f8
FG
192__PACKAGE__->register_method ({
193 subclass => "PVE::API2::Certificates",
194 path => 'certificates',
195});
196
197
c4f78bb7
FG
198__PACKAGE__->register_method ({
199 subclass => "PVE::API2::NodeConfig",
200 path => 'config',
201});
202
4be427a0
AD
203if ($have_sdn) {
204 __PACKAGE__->register_method ({
bb654699
AD
205 subclass => "PVE::API2::Network::SDN::Zones::Status",
206 path => 'sdn/zones',
4be427a0 207 });
48181607
TL
208
209__PACKAGE__->register_method ({
210 name => 'sdnindex',
211 path => 'sdn',
212 method => 'GET',
213 permissions => { user => 'all' },
214 description => "SDN index.",
215 parameters => {
216 additionalProperties => 0,
217 properties => {
218 node => get_standard_option('pve-node'),
219 },
220 },
221 returns => {
222 type => 'array',
223 items => {
224 type => "object",
225 properties => {},
226 },
227 links => [ { rel => 'child', href => "{name}" } ],
228 },
229 code => sub {
230 my ($param) = @_;
231
232 my $result = [
233 { name => 'zones' },
234 ];
235 return $result;
236 }});
4be427a0
AD
237}
238
aff192e6 239__PACKAGE__->register_method ({
f9d26e09
TL
240 name => 'index',
241 path => '',
aff192e6
DM
242 method => 'GET',
243 permissions => { user => 'all' },
244 description => "Node index.",
245 parameters => {
6110ed03 246 additionalProperties => 0,
aff192e6
DM
247 properties => {
248 node => get_standard_option('pve-node'),
249 },
250 },
251 returns => {
252 type => 'array',
253 items => {
254 type => "object",
255 properties => {},
256 },
257 links => [ { rel => 'child', href => "{name}" } ],
258 },
259 code => sub {
260 my ($param) = @_;
f9d26e09 261
aff192e6 262 my $result = [
784d6ee9
TL
263 { name => 'aplinfo' },
264 { name => 'apt' },
c6c4b278 265 { name => 'capabilities' },
38db610a 266 { name => 'ceph' },
784d6ee9
TL
267 { name => 'certificates' },
268 { name => 'config' },
e781363f 269 { name => 'disks' },
784d6ee9
TL
270 { name => 'dns' },
271 { name => 'firewall' },
2f410501 272 { name => 'hardware' },
784d6ee9 273 { name => 'hosts' },
952280b4 274 { name => 'journal' },
784d6ee9 275 { name => 'lxc' },
8b3c353e 276 { name => 'migrateall' },
784d6ee9
TL
277 { name => 'netstat' },
278 { name => 'network' },
279 { name => 'qemu' },
17711ff8 280 { name => 'query-url-metadata' },
784d6ee9 281 { name => 'replication' },
34ada77a 282 { name => 'report' },
aff192e6
DM
283 { name => 'rrd' }, # fixme: remove?
284 { name => 'rrddata' },# fixme: remove?
aff192e6 285 { name => 'scan' },
784d6ee9
TL
286 { name => 'services' },
287 { name => 'spiceshell' },
b92400b6 288 { name => 'startall' },
784d6ee9 289 { name => 'status' },
b92400b6 290 { name => 'stopall' },
784d6ee9
TL
291 { name => 'storage' },
292 { name => 'subscription' },
5f04abc2 293 { name => 'suspendall' },
784d6ee9
TL
294 { name => 'syslog' },
295 { name => 'tasks' },
296 { name => 'termproxy' },
297 { name => 'time' },
298 { name => 'version' },
299 { name => 'vncshell' },
300 { name => 'vzdump' },
301 { name => 'wakeonlan' },
952280b4 302 ];
aff192e6 303
bb654699
AD
304 push @$result, { name => 'sdn' } if $have_sdn;
305
aff192e6
DM
306 return $result;
307 }});
308
8747a9ec 309__PACKAGE__->register_method ({
f9d26e09 310 name => 'version',
8747a9ec
DM
311 path => 'version',
312 method => 'GET',
313 proxyto => 'node',
314 permissions => { user => 'all' },
315 description => "API version details",
316 parameters => {
6110ed03 317 additionalProperties => 0,
8747a9ec
DM
318 properties => {
319 node => get_standard_option('pve-node'),
320 },
321 },
322 returns => {
323 type => "object",
324 properties => {
180a86d3
TL
325 version => {
326 type => 'string',
327 description => 'The current installed pve-manager package version',
328 },
329 release => {
330 type => 'string',
331 description => 'The current installed Proxmox VE Release',
332 },
333 repoid => {
334 type => 'string',
335 description => 'The short git commit hash ID from which this version was build',
336 },
8747a9ec
DM
337 },
338 },
339 code => sub {
340 my ($resp, $param) = @_;
f9d26e09 341
8747a9ec
DM
342 return PVE::pvecfg::version_info();
343 }});
344
20ad4e0e
TL
345my sub get_current_kernel_info {
346 my ($sysname, $nodename, $release, $version, $machine) = POSIX::uname();
347
348 my $kernel_version_string = "$sysname $release $version"; # for legacy compat
349 my $current_kernel = {
350 sysname => $sysname,
351 release => $release,
352 version => $version,
353 machine => $machine,
354 };
355 return ($current_kernel, $kernel_version_string);
356}
357
92759ae1 358my $boot_mode_info_cache;
81fd95cf 359my sub get_boot_mode_info {
92759ae1
TL
360 return $boot_mode_info_cache if defined($boot_mode_info_cache);
361
81fd95cf
TL
362 my $is_efi_booted = -d "/sys/firmware/efi";
363
92759ae1 364 $boot_mode_info_cache = {
81fd95cf
TL
365 mode => $is_efi_booted ? 'efi' : 'legacy-bios',
366 };
367
368 if ($is_efi_booted) {
369 my $efi_var_sec_boot_entry = eval { file_get_contents("/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c") };
370 if ($@) {
371 warn "Failed to read secure boot state: $@\n";
372 } else {
373 my @secureboot = unpack("CCCCC", $efi_var_sec_boot_entry);
92759ae1 374 $boot_mode_info_cache->{secureboot} = $secureboot[4] == 1 ? 1 : 0;
81fd95cf
TL
375 }
376 }
92759ae1 377 return $boot_mode_info_cache;
81fd95cf
TL
378}
379
aff192e6 380__PACKAGE__->register_method({
f9d26e09
TL
381 name => 'status',
382 path => 'status',
aff192e6
DM
383 method => 'GET',
384 permissions => {
7d020b42 385 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
aff192e6
DM
386 },
387 description => "Read node status",
388 proxyto => 'node',
389 parameters => {
6110ed03 390 additionalProperties => 0,
aff192e6
DM
391 properties => {
392 node => get_standard_option('pve-node'),
393 },
394 },
395 returns => {
396 type => "object",
397 properties => {
398
399 },
400 },
401 code => sub {
402 my ($param) = @_;
403
404 my $res = {
405 uptime => 0,
406 idle => 0,
407 };
408
409 my ($uptime, $idle) = PVE::ProcFSTools::read_proc_uptime();
410 $res->{uptime} = $uptime;
f9d26e09 411
aff192e6
DM
412 my ($avg1, $avg5, $avg15) = PVE::ProcFSTools::read_loadavg();
413 $res->{loadavg} = [ $avg1, $avg5, $avg15];
f9d26e09 414
20ad4e0e
TL
415 my ($current_kernel_info, $kversion_string) = get_current_kernel_info();
416 $res->{kversion} = $kversion_string;
417 $res->{'current-kernel'} = $current_kernel_info;
aff192e6 418
81fd95cf
TL
419 $res->{'boot-info'} = get_boot_mode_info();
420
aff192e6
DM
421 $res->{cpuinfo} = PVE::ProcFSTools::read_cpuinfo();
422
423 my $stat = PVE::ProcFSTools::read_proc_stat();
424 $res->{cpu} = $stat->{cpu};
425 $res->{wait} = $stat->{wait};
426
427 my $meminfo = PVE::ProcFSTools::read_meminfo();
428 $res->{memory} = {
429 free => $meminfo->{memfree},
430 total => $meminfo->{memtotal},
431 used => $meminfo->{memused},
432 };
f9d26e09 433
20539e0c
DM
434 $res->{ksm} = {
435 shared => $meminfo->{memshared},
436 };
437
aff192e6
DM
438 $res->{swap} = {
439 free => $meminfo->{swapfree},
440 total => $meminfo->{swaptotal},
441 used => $meminfo->{swapused},
442 };
443
444 $res->{pveversion} = PVE::pvecfg::package() . "/" .
8747a9ec 445 PVE::pvecfg::version_text();
aff192e6
DM
446
447 my $dinfo = df('/', 1); # output is bytes
448
449 $res->{rootfs} = {
450 total => $dinfo->{blocks},
451 avail => $dinfo->{bavail},
452 used => $dinfo->{used},
3d0fcc46 453 free => $dinfo->{blocks} - $dinfo->{used},
aff192e6
DM
454 };
455
456 return $res;
457 }});
458
0455911d
SP
459__PACKAGE__->register_method({
460 name => 'netstat',
461 path => 'netstat',
462 method => 'GET',
463 permissions => {
464 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
465 },
466 description => "Read tap/vm network device interface counters",
467 proxyto => 'node',
468 parameters => {
469 additionalProperties => 0,
470 properties => {
471 node => get_standard_option('pve-node'),
472 },
473 },
474 returns => {
d6e7fa04
TL
475 type => "array",
476 items => {
477 type => "object",
478 properties => {},
479 },
0455911d
SP
480 },
481 code => sub {
482 my ($param) = @_;
483
484 my $res = [ ];
485
486 my $netdev = PVE::ProcFSTools::read_proc_net_dev();
d6e7fa04
TL
487 foreach my $dev (sort keys %$netdev) {
488 next if $dev !~ m/^(?:tap|veth)([1-9]\d*)i(\d+)$/;
489 my ($vmid, $netid) = ($1, $2);
490
491 push @$res, {
492 vmid => $vmid,
493 dev => "net$netid",
494 in => $netdev->{$dev}->{transmit},
495 out => $netdev->{$dev}->{receive},
496 };
0455911d
SP
497 }
498
499 return $res;
500 }});
501
87c3e931
SP
502__PACKAGE__->register_method({
503 name => 'execute',
504 path => 'execute',
40993593 505 method => 'POST',
eb859354 506 description => "Execute multiple commands in order, root only.",
87c3e931 507 proxyto => 'node',
40993593 508 protected => 1, # avoid problems with proxy code
87c3e931
SP
509 parameters => {
510 additionalProperties => 0,
511 properties => {
512 node => get_standard_option('pve-node'),
40993593
DM
513 commands => {
514 description => "JSON encoded array of commands.",
515 type => "string",
9018237e
FG
516 verbose_description => "JSON encoded array of commands, where each command is an object with the following properties:\n"
517 . PVE::RESTHandler::dump_properties($verify_command_item_desc->{items}->{properties}, 'full'),
a1de4410 518 format => "pve-command-batch",
40993593 519 }
87c3e931
SP
520 },
521 },
522 returns => {
523 type => 'array',
72db67a2
SS
524 items => {
525 type => "object",
526 properties => {},
87c3e931
SP
527 },
528 },
529 code => sub {
530 my ($param) = @_;
531 my $res = [];
532
40993593
DM
533 my $rpcenv = PVE::RPCEnvironment::get();
534 my $user = $rpcenv->get_user();
a1de4410 535 # just parse the json again, it should already be validated
40993593
DM
536 my $commands = eval { decode_json($param->{commands}); };
537
58ab77d1 538 foreach my $cmd (@$commands) {
87c3e931 539 eval {
40993593
DM
540 $cmd->{args} //= {};
541
542 my $path = "nodes/$param->{node}/$cmd->{path}";
f9d26e09 543
40993593
DM
544 my $uri_param = {};
545 my ($handler, $info) = PVE::API2->find_handler($cmd->{method}, $path, $uri_param);
546 if (!$handler || !$info) {
547 die "no handler for '$path'\n";
548 }
549
550 foreach my $p (keys %{$cmd->{args}}) {
551 raise_param_exc({ $p => "duplicate parameter" }) if defined($uri_param->{$p});
552 $uri_param->{$p} = $cmd->{args}->{$p};
553 }
554
555 # check access permissions
556 $rpcenv->check_api2_permissions($info->{permissions}, $user, $uri_param);
557
558 push @$res, {
559 status => HTTP_OK,
a51ceeb7 560 data => $handler->handle($info, $uri_param),
40993593 561 };
87c3e931 562 };
40993593
DM
563 if (my $err = $@) {
564 my $resp = { status => HTTP_INTERNAL_SERVER_ERROR };
565 if (ref($err) eq "PVE::Exception") {
566 $resp->{status} = $err->{code} if $err->{code};
567 $resp->{errors} = $err->{errors} if $err->{errors};
568 $resp->{message} = $err->{msg};
569 } else {
570 $resp->{message} = $err;
571 }
572 push @$res, $resp;
573 }
87c3e931
SP
574 }
575
576 return $res;
577 }});
578
579
aff192e6 580__PACKAGE__->register_method({
f9d26e09
TL
581 name => 'node_cmd',
582 path => 'status',
aff192e6
DM
583 method => 'POST',
584 permissions => {
7d020b42 585 check => ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
aff192e6
DM
586 },
587 protected => 1,
588 description => "Reboot or shutdown a node.",
589 proxyto => 'node',
590 parameters => {
6110ed03 591 additionalProperties => 0,
aff192e6
DM
592 properties => {
593 node => get_standard_option('pve-node'),
594 command => {
595 description => "Specify the command.",
596 type => 'string',
597 enum => [qw(reboot shutdown)],
598 },
599 },
600 },
601 returns => { type => "null" },
602 code => sub {
603 my ($param) = @_;
604
605 if ($param->{command} eq 'reboot') {
606 system ("(sleep 2;/sbin/reboot)&");
607 } elsif ($param->{command} eq 'shutdown') {
608 system ("(sleep 2;/sbin/poweroff)&");
609 }
610
79d62026 611 return;
aff192e6
DM
612 }});
613
b3d84542
CE
614__PACKAGE__->register_method({
615 name => 'wakeonlan',
616 path => 'wakeonlan',
617 method => 'POST',
618 permissions => {
619 check => ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
620 },
621 protected => 1,
622 description => "Try to wake a node via 'wake on LAN' network packet.",
623 parameters => {
624 additionalProperties => 0,
625 properties => {
626 node => get_standard_option('pve-node', {
627 description => 'target node for wake on LAN packet',
5aa7b909
CE
628 completion => sub {
629 my $members = PVE::Cluster::get_members();
630 return [ grep { !$members->{$_}->{online} } keys %$members ];
631 }
b3d84542
CE
632 }),
633 },
634 },
0f615ea9
CE
635 returns => {
636 type => 'string',
637 format => 'mac-addr',
ec178804 638 description => 'MAC address used to assemble the WoL magic packet.',
0f615ea9 639 },
b3d84542
CE
640 code => sub {
641 my ($param) = @_;
642
82436996
TL
643 my $node = $param->{node};
644
645 die "'$node' is local node, cannot wake my self!\n"
646 if $node eq 'localhost' || $node eq PVE::INotify::nodename();
647
648 PVE::Cluster::check_node_exists($node);
649
650 my $config = PVE::NodeConfig::load_config($node);
b3d84542
CE
651 my $mac_addr = $config->{wakeonlan};
652 if (!defined($mac_addr)) {
82436996 653 die "No wake on LAN MAC address defined for '$node'!\n";
b3d84542
CE
654 }
655
656 $mac_addr =~ s/://g;
657 my $packet = chr(0xff) x 6 . pack('H*', $mac_addr) x 16;
658
659 my $addr = gethostbyname('255.255.255.255');
660 my $port = getservbyname('discard', 'udp');
661 my $to = Socket::pack_sockaddr_in($port, $addr);
82436996 662
b3d84542
CE
663 socket(my $sock, Socket::AF_INET, Socket::SOCK_DGRAM, Socket::IPPROTO_UDP)
664 || die "Unable to open socket: $!\n";
665 setsockopt($sock, Socket::SOL_SOCKET, Socket::SO_BROADCAST, 1)
666 || die "Unable to set socket option: $!\n";
667
668 send($sock, $packet, 0, $to)
669 || die "Unable to send packet: $!\n";
670
671 close($sock);
672
0f615ea9 673 return $config->{wakeonlan};
b3d84542 674 }});
aff192e6
DM
675
676__PACKAGE__->register_method({
f9d26e09
TL
677 name => 'rrd',
678 path => 'rrd',
aff192e6
DM
679 method => 'GET',
680 protected => 1, # fixme: can we avoid that?
681 permissions => {
7d020b42 682 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
aff192e6
DM
683 },
684 description => "Read node RRD statistics (returns PNG)",
685 parameters => {
6110ed03 686 additionalProperties => 0,
aff192e6
DM
687 properties => {
688 node => get_standard_option('pve-node'),
689 timeframe => {
690 description => "Specify the time frame you are interested in.",
691 type => 'string',
692 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
693 },
694 ds => {
695 description => "The list of datasources you want to display.",
58ab77d1 696 type => 'string', format => 'pve-configid-list',
aff192e6
DM
697 },
698 cf => {
699 description => "The RRD consolidation function",
58ab77d1 700 type => 'string',
aff192e6
DM
701 enum => [ 'AVERAGE', 'MAX' ],
702 optional => 1,
703 },
704 },
705 },
706 returns => {
707 type => "object",
708 properties => {
709 filename => { type => 'string' },
710 },
711 },
712 code => sub {
713 my ($param) = @_;
714
516a7948 715 return PVE::RRD::create_rrd_graph(
f9d26e09 716 "pve2-node/$param->{node}", $param->{timeframe},
aff192e6
DM
717 $param->{ds}, $param->{cf});
718
719 }});
720
721__PACKAGE__->register_method({
f9d26e09
TL
722 name => 'rrddata',
723 path => 'rrddata',
aff192e6
DM
724 method => 'GET',
725 protected => 1, # fixme: can we avoid that?
726 permissions => {
7d020b42 727 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
aff192e6
DM
728 },
729 description => "Read node RRD statistics",
730 parameters => {
6110ed03 731 additionalProperties => 0,
aff192e6
DM
732 properties => {
733 node => get_standard_option('pve-node'),
734 timeframe => {
735 description => "Specify the time frame you are interested in.",
736 type => 'string',
737 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
738 },
739 cf => {
740 description => "The RRD consolidation function",
58ab77d1 741 type => 'string',
aff192e6
DM
742 enum => [ 'AVERAGE', 'MAX' ],
743 optional => 1,
744 },
745 },
746 },
747 returns => {
748 type => "array",
749 items => {
750 type => "object",
751 properties => {},
752 },
753 },
754 code => sub {
755 my ($param) = @_;
756
516a7948 757 return PVE::RRD::create_rrd_data(
aff192e6
DM
758 "pve2-node/$param->{node}", $param->{timeframe}, $param->{cf});
759 }});
760
761__PACKAGE__->register_method({
f9d26e09
TL
762 name => 'syslog',
763 path => 'syslog',
aff192e6
DM
764 method => 'GET',
765 description => "Read system log",
766 proxyto => 'node',
767 permissions => {
7d020b42 768 check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
aff192e6
DM
769 },
770 protected => 1,
771 parameters => {
6110ed03 772 additionalProperties => 0,
aff192e6
DM
773 properties => {
774 node => get_standard_option('pve-node'),
775 start => {
776 type => 'integer',
777 minimum => 0,
778 optional => 1,
779 },
780 limit => {
781 type => 'integer',
782 minimum => 0,
783 optional => 1,
784 },
01b753b6
TL
785 since => {
786 type=> 'string',
787 pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
788 description => "Display all log since this date-time string.",
789 optional => 1,
790 },
791 until => {
792 type=> 'string',
793 pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
794 description => "Display all log until this date-time string.",
795 optional => 1,
796 },
34142272
DC
797 service => {
798 description => "Service ID",
799 type => 'string',
800 maxLength => 128,
801 optional => 1,
802 },
aff192e6
DM
803 },
804 },
805 returns => {
806 type => 'array',
f9d26e09 807 items => {
aff192e6
DM
808 type => "object",
809 properties => {
810 n => {
811 description=> "Line number",
812 type=> 'integer',
813 },
814 t => {
815 description=> "Line text",
816 type => 'string',
817 }
818 }
819 }
820 },
821 code => sub {
822 my ($param) = @_;
823
aff192e6
DM
824 my $rpcenv = PVE::RPCEnvironment::get();
825 my $user = $rpcenv->get_user();
826 my $node = $param->{node};
34142272
DC
827 my $service;
828
829 if ($param->{service}) {
830 my $service_aliases = {
831 'postfix' => 'postfix@-',
832 };
833
834 $service = $service_aliases->{$param->{service}} // $param->{service};
835 }
aff192e6 836
01b753b6 837 my ($count, $lines) = PVE::Tools::dump_journal($param->{start}, $param->{limit},
34142272 838 $param->{since}, $param->{until}, $service);
ff9c330c
DM
839
840 $rpcenv->set_result_attrib('total', $count);
01b753b6
TL
841
842 return $lines;
ff9c330c
DM
843 }});
844
1d397a83
DC
845__PACKAGE__->register_method({
846 name => 'journal',
847 path => 'journal',
848 method => 'GET',
849 description => "Read Journal",
850 proxyto => 'node',
851 permissions => {
852 check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
853 },
854 protected => 1,
855 parameters => {
856 additionalProperties => 0,
857 properties => {
858 node => get_standard_option('pve-node'),
859 since => {
961399a3
TL
860 type=> 'integer',
861 minimum => 0,
f9b08743 862 description => "Display all log since this UNIX epoch. Conflicts with 'startcursor'.",
1d397a83
DC
863 optional => 1,
864 },
865 until => {
961399a3
TL
866 type=> 'integer',
867 minimum => 0,
f9b08743 868 description => "Display all log until this UNIX epoch. Conflicts with 'endcursor'.",
1d397a83
DC
869 optional => 1,
870 },
871 lastentries => {
a03cb8b8 872 description => "Limit to the last X lines. Conflicts with a range.",
1d397a83 873 type => 'integer',
961399a3 874 minimum => 0,
1d397a83
DC
875 optional => 1,
876 },
877 startcursor => {
f9b08743 878 description => "Start after the given Cursor. Conflicts with 'since'",
1d397a83
DC
879 type => 'string',
880 optional => 1,
881 },
882 endcursor => {
f9b08743 883 description => "End before the given Cursor. Conflicts with 'until'",
1d397a83
DC
884 type => 'string',
885 optional => 1,
886 },
887 },
888 },
889 returns => {
890 type => 'array',
891 items => {
892 type => "string",
893 }
894 },
895 code => sub {
896 my ($param) = @_;
897
898 my $rpcenv = PVE::RPCEnvironment::get();
899 my $user = $rpcenv->get_user();
900
68df9496 901 my $cmd = ["/usr/bin/mini-journalreader", "-j"];
a03cb8b8
TL
902 push @$cmd, '-n', $param->{lastentries} if $param->{lastentries};
903 push @$cmd, '-b', $param->{since} if $param->{since};
904 push @$cmd, '-e', $param->{until} if $param->{until};
68df9496
DC
905 push @$cmd, '-f', PVE::Tools::shellquote($param->{startcursor}) if $param->{startcursor};
906 push @$cmd, '-t', PVE::Tools::shellquote($param->{endcursor}) if $param->{endcursor};
907 push @$cmd, ' | gzip ';
908
909 open(my $fh, "-|", join(' ', @$cmd))
910 or die "could not start mini-journalreader";
911
912 return {
913 download => {
914 fh => $fh,
915 stream => 1,
916 'content-type' => 'application/json',
917 'content-encoding' => 'gzip',
918 },
919 },
1d397a83
DC
920 }});
921
aff192e6
DM
922my $sslcert;
923
dab7a849 924my $shell_cmd_map = {
f24be7e7
TL
925 'login' => {
926 cmd => [ '/bin/login', '-f', 'root' ],
927 },
928 'upgrade' => {
929 cmd => [ '/usr/bin/pveupgrade', '--shell' ],
930 },
931 'ceph_install' => {
932 cmd => [ '/usr/bin/pveceph', 'install' ],
933 allow_args => 1,
934 },
d03d7e1e
TM
935};
936
937sub get_shell_command {
f24be7e7 938 my ($user, $shellcmd, $args) = @_;
d03d7e1e 939
f24be7e7 940 my $cmd;
d03d7e1e 941 if ($user eq 'root@pam') {
fc1da3b0 942 if (defined($shellcmd) && exists($shell_cmd_map->{$shellcmd})) {
f24be7e7
TL
943 my $def = $shell_cmd_map->{$shellcmd};
944 $cmd = [ @{$def->{cmd}} ]; # clone
945 if (defined($args) && $def->{allow_args}) {
946 push @$cmd, split("\0", $args);
947 }
d03d7e1e 948 } else {
f24be7e7 949 $cmd = [ '/bin/login', '-f', 'root' ];
d03d7e1e
TM
950 }
951 } else {
f24be7e7
TL
952 # non-root must always login for now, we do not have a superuser role!
953 $cmd = [ '/bin/login' ];
d03d7e1e 954 }
f24be7e7 955 return $cmd;
d03d7e1e
TM
956}
957
677bee7c
TL
958my $get_vnc_connection_info = sub {
959 my $node = shift;
960
961 my $remote_cmd = [];
962
963 my ($remip, $family);
964 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
965 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
966 $remote_cmd = ['/usr/bin/ssh', '-e', 'none', '-t', $remip , '--'];
967 } else {
968 $family = PVE::Tools::get_host_address_family($node);
969 }
970 my $port = PVE::Tools::next_vnc_port($family);
971
972 return ($port, $remote_cmd);
973};
974
aff192e6 975__PACKAGE__->register_method ({
f9d26e09
TL
976 name => 'vncshell',
977 path => 'vncshell',
aff192e6
DM
978 method => 'POST',
979 protected => 1,
980 permissions => {
7d020b42 981 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
aff192e6
DM
982 },
983 description => "Creates a VNC Shell proxy.",
984 parameters => {
6110ed03 985 additionalProperties => 0,
aff192e6
DM
986 properties => {
987 node => get_standard_option('pve-node'),
d03d7e1e
TM
988 cmd => {
989 type => 'string',
4fb92ae8 990 description => "Run specific command or default to login (requires 'root\@pam')",
dab7a849 991 enum => [keys %$shell_cmd_map],
d03d7e1e
TM
992 optional => 1,
993 default => 'login',
994 },
f24be7e7
TL
995 'cmd-opts' => {
996 type => 'string',
997 description => "Add parameters to a command. Encoded as null terminated strings.",
998 requires => 'cmd',
999 optional => 1,
1000 default => '',
1001 },
8dfca17e
DM
1002 websocket => {
1003 optional => 1,
1004 type => 'boolean',
1005 description => "use websocket instead of standard vnc.",
1006 },
b8ac8b0c
DC
1007 width => {
1008 optional => 1,
1009 description => "sets the width of the console in pixels.",
1010 type => 'integer',
1011 minimum => 16,
1012 maximum => 4096,
1013 },
1014 height => {
1015 optional => 1,
1016 description => "sets the height of the console in pixels.",
1017 type => 'integer',
1018 minimum => 16,
1019 maximum => 2160,
1020 },
aff192e6
DM
1021 },
1022 },
f9d26e09 1023 returns => {
6110ed03 1024 additionalProperties => 0,
aff192e6
DM
1025 properties => {
1026 user => { type => 'string' },
1027 ticket => { type => 'string' },
1028 cert => { type => 'string' },
1029 port => { type => 'integer' },
1030 upid => { type => 'string' },
1031 },
1032 },
1033 code => sub {
1034 my ($param) = @_;
1035
1036 my $rpcenv = PVE::RPCEnvironment::get();
d553e535 1037 my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
d0289a19 1038
0c8e509e 1039
4fb92ae8 1040 if (defined($param->{cmd}) && $param->{cmd} ne 'login' && $user ne 'root@pam') {
0c8e509e
FE
1041 raise_perm_exc('user != root@pam');
1042 }
3a76893d 1043
aff192e6
DM
1044 my $node = $param->{node};
1045
57ebda08 1046 my $authpath = "/nodes/$node";
57ebda08
DM
1047 my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath);
1048
aff192e6
DM
1049 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
1050 if !$sslcert;
1051
677bee7c 1052 my ($port, $remcmd) = $get_vnc_connection_info->($node);
d4a25f0b 1053
f24be7e7 1054 my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'});
aff192e6 1055
f9d26e09 1056 my $timeout = 10;
aff192e6 1057
677bee7c
TL
1058 my $cmd = ['/usr/bin/vncterm',
1059 '-rfbport', $port,
1060 '-timeout', $timeout,
1061 '-authpath', $authpath,
1062 '-perm', 'Sys.Console',
1063 ];
b8ac8b0c 1064
677bee7c
TL
1065 push @$cmd, '-width', $param->{width} if $param->{width};
1066 push @$cmd, '-height', $param->{height} if $param->{height};
b8ac8b0c 1067
8dfca17e 1068 if ($param->{websocket}) {
f9d26e09 1069 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
8dfca17e
DM
1070 push @$cmd, '-notls', '-listen', 'localhost';
1071 }
1072
1073 push @$cmd, '-c', @$remcmd, @$shcmd;
aff192e6
DM
1074
1075 my $realcmd = sub {
1076 my $upid = shift;
1077
1078 syslog ('info', "starting vnc proxy $upid\n");
1079
6d394492 1080 my $cmdstr = join (' ', @$cmd);
aff192e6
DM
1081 syslog ('info', "launch command: $cmdstr");
1082
f9d26e09 1083 eval {
6d394492 1084 foreach my $k (keys %ENV) {
8dfca17e 1085 next if $k eq 'PVE_VNC_TICKET';
b0d4b407 1086 next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME' || $k eq 'LANG' || $k eq 'LANGUAGE';
6d394492
DM
1087 delete $ENV{$k};
1088 }
1089 $ENV{PWD} = '/';
1090
b0d4b407 1091 PVE::Tools::run_command($cmd, errmsg => "vncterm failed", keeplocale => 1);
6d394492
DM
1092 };
1093 if (my $err = $@) {
1094 syslog ('err', $err);
aff192e6
DM
1095 }
1096
1097 return;
1098 };
1099
1100 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
f9d26e09 1101
6806a0f8 1102 PVE::Tools::wait_for_vnc_port($port);
aff192e6
DM
1103
1104 return {
1105 user => $user,
1106 ticket => $ticket,
f9d26e09
TL
1107 port => $port,
1108 upid => $upid,
1109 cert => $sslcert,
aff192e6
DM
1110 };
1111 }});
1112
4b168c27
DC
1113__PACKAGE__->register_method ({
1114 name => 'termproxy',
1115 path => 'termproxy',
1116 method => 'POST',
1117 protected => 1,
1118 permissions => {
4b168c27
DC
1119 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1120 },
1121 description => "Creates a VNC Shell proxy.",
1122 parameters => {
1123 additionalProperties => 0,
1124 properties => {
1125 node => get_standard_option('pve-node'),
d03d7e1e
TM
1126 cmd => {
1127 type => 'string',
4fb92ae8 1128 description => "Run specific command or default to login (requires 'root\@pam')",
dab7a849 1129 enum => [keys %$shell_cmd_map],
d03d7e1e
TM
1130 optional => 1,
1131 default => 'login',
1132 },
f24be7e7
TL
1133 'cmd-opts' => {
1134 type => 'string',
1135 description => "Add parameters to a command. Encoded as null terminated strings.",
1136 requires => 'cmd',
1137 optional => 1,
1138 default => '',
1139 },
4b168c27
DC
1140 },
1141 },
1142 returns => {
1143 additionalProperties => 0,
1144 properties => {
1145 user => { type => 'string' },
1146 ticket => { type => 'string' },
1147 port => { type => 'integer' },
1148 upid => { type => 'string' },
1149 },
1150 },
1151 code => sub {
1152 my ($param) = @_;
1153
1154 my $rpcenv = PVE::RPCEnvironment::get();
4b168c27 1155 my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
4b168c27
DC
1156
1157 my $node = $param->{node};
4b168c27 1158 my $authpath = "/nodes/$node";
4b168c27
DC
1159 my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath);
1160
677bee7c 1161 my ($port, $remcmd) = $get_vnc_connection_info->($node);
4b168c27 1162
f24be7e7 1163 my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'});
4b168c27
DC
1164
1165 my $realcmd = sub {
1166 my $upid = shift;
1167
1168 syslog ('info', "starting termproxy $upid\n");
1169
f24be7e7
TL
1170 my $cmd = [
1171 '/usr/bin/termproxy',
1172 $port,
677bee7c
TL
1173 '--path', $authpath,
1174 '--perm', 'Sys.Console',
1175 '--'
1176 ];
d03d7e1e 1177 push @$cmd, @$remcmd, @$shcmd;
4b168c27
DC
1178
1179 PVE::Tools::run_command($cmd);
1180 };
4b168c27
DC
1181 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
1182
1183 PVE::Tools::wait_for_vnc_port($port);
1184
1185 return {
1186 user => $user,
1187 ticket => $ticket,
1188 port => $port,
1189 upid => $upid,
1190 };
1191 }});
1192
8dfca17e
DM
1193__PACKAGE__->register_method({
1194 name => 'vncwebsocket',
1195 path => 'vncwebsocket',
1196 method => 'GET',
f9d26e09 1197 permissions => {
7914f5e7 1198 description => "You also need to pass a valid ticket (vncticket).",
8dfca17e
DM
1199 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1200 },
0e68b116 1201 description => "Opens a websocket for VNC traffic.",
8dfca17e 1202 parameters => {
6110ed03 1203 additionalProperties => 0,
8dfca17e
DM
1204 properties => {
1205 node => get_standard_option('pve-node'),
1206 vncticket => {
1207 description => "Ticket from previous call to vncproxy.",
1208 type => 'string',
1209 maxLength => 512,
1210 },
1211 port => {
1212 description => "Port number returned by previous vncproxy call.",
1213 type => 'integer',
1214 minimum => 5900,
1215 maximum => 5999,
1216 },
1217 },
1218 },
1219 returns => {
1220 type => "object",
1221 properties => {
1222 port => { type => 'string' },
1223 },
1224 },
1225 code => sub {
1226 my ($param) = @_;
1227
1228 my $rpcenv = PVE::RPCEnvironment::get();
1229
1230 my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
1231
8dfca17e
DM
1232 my $authpath = "/nodes/$param->{node}";
1233
1234 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $user, $authpath);
1235
1236 my $port = $param->{port};
f9d26e09 1237
8dfca17e
DM
1238 return { port => $port };
1239 }});
1240
2d802f8c 1241__PACKAGE__->register_method ({
f9d26e09
TL
1242 name => 'spiceshell',
1243 path => 'spiceshell',
2d802f8c
DM
1244 method => 'POST',
1245 protected => 1,
1246 proxyto => 'node',
1247 permissions => {
2d802f8c
DM
1248 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1249 },
7774df78 1250 description => "Creates a SPICE shell.",
2d802f8c 1251 parameters => {
6110ed03 1252 additionalProperties => 0,
2d802f8c
DM
1253 properties => {
1254 node => get_standard_option('pve-node'),
7774df78 1255 proxy => get_standard_option('spice-proxy', { optional => 1 }),
d03d7e1e
TM
1256 cmd => {
1257 type => 'string',
4fb92ae8 1258 description => "Run specific command or default to login (requires 'root\@pam')",
dab7a849 1259 enum => [keys %$shell_cmd_map],
d03d7e1e
TM
1260 optional => 1,
1261 default => 'login',
1262 },
f24be7e7
TL
1263 'cmd-opts' => {
1264 type => 'string',
1265 description => "Add parameters to a command. Encoded as null terminated strings.",
1266 requires => 'cmd',
1267 optional => 1,
1268 default => '',
1269 },
2d802f8c
DM
1270 },
1271 },
7774df78 1272 returns => get_standard_option('remote-viewer-config'),
2d802f8c
DM
1273 code => sub {
1274 my ($param) = @_;
1275
1276 my $rpcenv = PVE::RPCEnvironment::get();
1277 my $authuser = $rpcenv->get_user();
1278
1279 my ($user, undef, $realm) = PVE::AccessControl::verify_username($authuser);
1280
b270a6b4 1281
4fb92ae8 1282 if (defined($param->{cmd}) && $param->{cmd} ne 'login' && $user ne 'root@pam') {
b270a6b4
FE
1283 raise_perm_exc('user != root@pam');
1284 }
2d802f8c
DM
1285
1286 my $node = $param->{node};
1287 my $proxy = $param->{proxy};
2d802f8c
DM
1288
1289 my $authpath = "/nodes/$node";
b289829f 1290 my $permissions = 'Sys.Console';
b270a6b4 1291
f24be7e7 1292 my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'});
2d802f8c 1293
b289829f 1294 my $title = "Shell on '$node'";
2d802f8c 1295
b289829f 1296 return PVE::API2Tools::run_spiceterm($authpath, $permissions, 0, $node, $proxy, $title, $shcmd);
2d802f8c
DM
1297 }});
1298
aff192e6 1299__PACKAGE__->register_method({
f9d26e09
TL
1300 name => 'dns',
1301 path => 'dns',
aff192e6
DM
1302 method => 'GET',
1303 permissions => {
7d020b42 1304 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
aff192e6
DM
1305 },
1306 description => "Read DNS settings.",
1307 proxyto => 'node',
1308 parameters => {
6110ed03 1309 additionalProperties => 0,
aff192e6
DM
1310 properties => {
1311 node => get_standard_option('pve-node'),
1312 },
1313 },
1314 returns => {
1315 type => "object",
6110ed03 1316 additionalProperties => 0,
aff192e6
DM
1317 properties => {
1318 search => {
1319 description => "Search domain for host-name lookup.",
1320 type => 'string',
1321 optional => 1,
1322 },
1323 dns1 => {
1324 description => 'First name server IP address.',
1325 type => 'string',
1326 optional => 1,
f9d26e09 1327 },
aff192e6
DM
1328 dns2 => {
1329 description => 'Second name server IP address.',
1330 type => 'string',
1331 optional => 1,
f9d26e09 1332 },
aff192e6
DM
1333 dns3 => {
1334 description => 'Third name server IP address.',
1335 type => 'string',
1336 optional => 1,
f9d26e09 1337 },
aff192e6
DM
1338 },
1339 },
1340 code => sub {
1341 my ($param) = @_;
1342
1343 my $res = PVE::INotify::read_file('resolvconf');
1344
1345 return $res;
1346 }});
1347
1348__PACKAGE__->register_method({
f9d26e09
TL
1349 name => 'update_dns',
1350 path => 'dns',
aff192e6
DM
1351 method => 'PUT',
1352 description => "Write DNS settings.",
d0289a19
DM
1353 permissions => {
1354 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
1355 },
aff192e6
DM
1356 proxyto => 'node',
1357 protected => 1,
1358 parameters => {
6110ed03 1359 additionalProperties => 0,
aff192e6
DM
1360 properties => {
1361 node => get_standard_option('pve-node'),
1362 search => {
1363 description => "Search domain for host-name lookup.",
1364 type => 'string',
1365 },
1366 dns1 => {
1367 description => 'First name server IP address.',
7a9486a7 1368 type => 'string', format => 'ip',
aff192e6 1369 optional => 1,
f9d26e09 1370 },
aff192e6
DM
1371 dns2 => {
1372 description => 'Second name server IP address.',
7a9486a7 1373 type => 'string', format => 'ip',
aff192e6 1374 optional => 1,
f9d26e09 1375 },
aff192e6
DM
1376 dns3 => {
1377 description => 'Third name server IP address.',
7a9486a7 1378 type => 'string', format => 'ip',
aff192e6 1379 optional => 1,
f9d26e09 1380 },
aff192e6
DM
1381 },
1382 },
1383 returns => { type => "null" },
1384 code => sub {
1385 my ($param) = @_;
1386
1387 PVE::INotify::update_file('resolvconf', $param);
1388
79d62026 1389 return;
aff192e6
DM
1390 }});
1391
1392__PACKAGE__->register_method({
f9d26e09
TL
1393 name => 'time',
1394 path => 'time',
aff192e6
DM
1395 method => 'GET',
1396 permissions => {
7d020b42
DM
1397 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1398 },
aff192e6
DM
1399 description => "Read server time and time zone settings.",
1400 proxyto => 'node',
1401 parameters => {
58ab77d1 1402 additionalProperties => 0,
aff192e6
DM
1403 properties => {
1404 node => get_standard_option('pve-node'),
1405 },
1406 },
1407 returns => {
1408 type => "object",
6110ed03 1409 additionalProperties => 0,
aff192e6
DM
1410 properties => {
1411 timezone => {
1412 description => "Time zone",
1413 type => 'string',
1414 },
1415 time => {
1416 description => "Seconds since 1970-01-01 00:00:00 UTC.",
1417 type => 'integer',
1418 minimum => 1297163644,
bed5fdfc 1419 renderer => 'timestamp',
aff192e6
DM
1420 },
1421 localtime => {
1422 description => "Seconds since 1970-01-01 00:00:00 (local time)",
1423 type => 'integer',
1424 minimum => 1297163644,
bed5fdfc 1425 renderer => 'timestamp_gmt',
aff192e6 1426 },
58ab77d1 1427 },
aff192e6
DM
1428 },
1429 code => sub {
1430 my ($param) = @_;
1431
1432 my $ctime = time();
1433 my $ltime = timegm_nocheck(localtime($ctime));
1434 my $res = {
1435 timezone => PVE::INotify::read_file('timezone'),
bed5fdfc 1436 time => $ctime,
aff192e6
DM
1437 localtime => $ltime,
1438 };
1439
1440 return $res;
1441 }});
1442
1443__PACKAGE__->register_method({
f9d26e09
TL
1444 name => 'set_timezone',
1445 path => 'time',
aff192e6
DM
1446 method => 'PUT',
1447 description => "Set time zone.",
d0289a19
DM
1448 permissions => {
1449 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
1450 },
aff192e6
DM
1451 proxyto => 'node',
1452 protected => 1,
1453 parameters => {
6110ed03 1454 additionalProperties => 0,
aff192e6
DM
1455 properties => {
1456 node => get_standard_option('pve-node'),
1457 timezone => {
1458 description => "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
1459 type => 'string',
1460 },
1461 },
1462 },
1463 returns => { type => "null" },
1464 code => sub {
1465 my ($param) = @_;
1466
1467 PVE::INotify::write_file('timezone', $param->{timezone});
1468
79d62026 1469 return;
aff192e6
DM
1470 }});
1471
c9164975 1472__PACKAGE__->register_method({
f9d26e09
TL
1473 name => 'aplinfo',
1474 path => 'aplinfo',
c9164975
DM
1475 method => 'GET',
1476 permissions => {
1477 user => 'all',
1478 },
1479 description => "Get list of appliances.",
1480 proxyto => 'node',
1481 parameters => {
6110ed03 1482 additionalProperties => 0,
c9164975
DM
1483 properties => {
1484 node => get_standard_option('pve-node'),
1485 },
1486 },
1487 returns => {
1488 type => 'array',
1489 items => {
1490 type => "object",
1491 properties => {},
1492 },
1493 },
1494 code => sub {
1495 my ($param) = @_;
1496
c9164975
DM
1497 my $list = PVE::APLInfo::load_data();
1498
0d213945
TL
1499 my $res = [];
1500 for my $appliance (values %{$list->{all}}) {
1501 next if $appliance->{'package'} eq 'pve-web-news';
1502 push @$res, $appliance;
c9164975
DM
1503 }
1504
1505 return $res;
1506 }});
1507
0532bd28 1508__PACKAGE__->register_method({
f9d26e09
TL
1509 name => 'apl_download',
1510 path => 'aplinfo',
0532bd28
DM
1511 method => 'POST',
1512 permissions => {
1513 check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
1514 },
1515 description => "Download appliance templates.",
1516 proxyto => 'node',
1517 protected => 1,
1518 parameters => {
6110ed03 1519 additionalProperties => 0,
0532bd28
DM
1520 properties => {
1521 node => get_standard_option('pve-node'),
82282acf 1522 storage => get_standard_option('pve-storage-id', {
62180d0f 1523 description => "The storage where the template will be stored",
82282acf
WL
1524 completion => \&PVE::Storage::complete_storage_enabled,
1525 }),
6110ed03
TL
1526 template => {
1527 type => 'string',
1528 description => "The template which will downloaded",
1529 maxLength => 255,
1530 completion => \&complete_templet_repo,
82282acf 1531 },
0532bd28
DM
1532 },
1533 },
1534 returns => { type => "string" },
1535 code => sub {
1536 my ($param) = @_;
1537
1538 my $rpcenv = PVE::RPCEnvironment::get();
0532bd28 1539 my $user = $rpcenv->get_user();
0532bd28 1540
aee25c2e 1541 my $node = $param->{node};
0532bd28 1542 my $template = $param->{template};
0532bd28 1543
aee25c2e
TL
1544 my $list = PVE::APLInfo::load_data();
1545 my $appliance = $list->{all}->{$template};
1546 raise_param_exc({ template => "no such template"}) if !$appliance;
0532bd28 1547
bbcfdc08 1548 my $cfg = PVE::Storage::config();
0532bd28
DM
1549 my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node);
1550
aee25c2e
TL
1551 die "unknown template type '$appliance->{type}'\n"
1552 if !($appliance->{type} eq 'openvz' || $appliance->{type} eq 'lxc');
0532bd28 1553
f9d26e09 1554 die "storage '$param->{storage}' does not support templates\n"
0532bd28
DM
1555 if !$scfg->{content}->{vztmpl};
1556
0532bd28 1557 my $tmpldir = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
0532bd28 1558
fac5d57e 1559 my $worker = sub {
aee25c2e
TL
1560 my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
1561
1562 PVE::Tools::download_file_from_url("$tmpldir/$template", $appliance->{location}, {
1563 hash_required => 1,
1564 sha512sum => $appliance->{sha512sum},
1565 md5sum => $appliance->{md5sum},
1566 http_proxy => $dccfg->{http_proxy},
1567 });
fac5d57e 1568 };
0532bd28 1569
fac5d57e 1570 my $upid = $rpcenv->fork_worker('download', $template, $user, $worker);
0532bd28 1571
fac5d57e 1572 return $upid;
0532bd28
DM
1573 }});
1574
17711ff8
LS
1575__PACKAGE__->register_method({
1576 name => 'query_url_metadata',
1577 path => 'query-url-metadata',
1578 method => 'GET',
1579 description => "Query metadata of an URL: file size, file name and mime type.",
1580 proxyto => 'node',
1581 permissions => {
1582 check => ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]],
1583 },
1584 parameters => {
1585 additionalProperties => 0,
1586 properties => {
1587 node => get_standard_option('pve-node'),
1588 url => {
1589 description => "The URL to query the metadata from.",
1590 type => 'string',
1591 pattern => 'https?://.*',
1592 },
1593 'verify-certificates' => {
1594 description => "If false, no SSL/TLS certificates will be verified.",
1595 type => 'boolean',
1596 optional => 1,
1597 default => 1,
d61728e2 1598 },
17711ff8
LS
1599 },
1600 },
1601 returns => {
1602 type => "object",
1603 properties => {
1604 filename => {
1605 type => 'string',
1606 optional => 1,
1607 },
1608 size => {
1609 type => 'integer',
1610 renderer => 'bytes',
1611 optional => 1,
1612 },
1613 mimetype => {
1614 type => 'string',
1615 optional => 1,
1616 },
1617 },
1618 },
1619 code => sub {
1620 my ($param) = @_;
1621
1622 my $url = $param->{url};
1623
1624 my $ua = LWP::UserAgent->new();
dfde1eec 1625 $ua->agent("Proxmox VE");
17711ff8
LS
1626
1627 my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
1628 if ($dccfg->{http_proxy}) {
1629 $ua->proxy('http', $dccfg->{http_proxy});
1630 }
1631
1632 my $verify = $param->{'verify-certificates'} // 1;
1633 if (!$verify) {
1634 $ua->ssl_opts(
1635 verify_hostname => 0,
1636 SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
1637 );
1638 }
1639
1640 my $req = HTTP::Request->new(HEAD => $url);
1641 my $res = $ua->request($req);
1642
1643 die "invalid server response: '" . $res->status_line() . "'\n" if ($res->code() != 200);
1644
1645 my $size = $res->header("Content-Length");
1646 my $disposition = $res->header("Content-Disposition");
1647 my $type = $res->header("Content-Type");
1648
1649 my $filename;
1650
1651 if ($disposition && ($disposition =~ m/filename="([^"]*)"/ || $disposition =~ m/filename=([^;]*)/)) {
1652 $filename = $1;
1653 } elsif ($url =~ m!^[^?]+/([^?/]*)(?:\?.*)?$!) {
1654 $filename = $1;
1655 }
1656
1657 # Content-Type: text/html; charset=utf-8
1658 if ($type && $type =~ m/^([^;]+);/) {
1659 $type = $1;
1660 }
1661
1662 my $ret = {};
1663 $ret->{filename} = $filename if $filename;
1664 $ret->{size} = $size + 0 if $size;
1665 $ret->{mimetype} = $type if $type;
1666
1667 return $ret;
1668 }});
1669
34ada77a
EK
1670__PACKAGE__->register_method({
1671 name => 'report',
1672 path => 'report',
1673 method => 'GET',
1674 permissions => {
1675 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1676 },
6bbdf981 1677 protected => 1,
34ada77a
EK
1678 description => "Gather various systems information about a node",
1679 proxyto => 'node',
1680 parameters => {
6110ed03 1681 additionalProperties => 0,
34ada77a
EK
1682 properties => {
1683 node => get_standard_option('pve-node'),
1684 },
1685 },
1686 returns => {
cdb5a209 1687 type => 'string',
34ada77a
EK
1688 },
1689 code => sub {
cdb5a209 1690 return PVE::Report::generate();
34ada77a 1691 }});
0532bd28 1692
b72cdbb7
TL
1693# returns a list of VMIDs, those can be filtered by
1694# * current parent node
1695# * vmid whitelist
1696# * guest is a template (default: skip)
1697# * guest is HA manged (default: skip)
1698my $get_filtered_vmlist = sub {
1699 my ($nodename, $vmfilter, $templates, $ha_managed) = @_;
b92400b6 1700
b92400b6
DM
1701 my $vmlist = PVE::Cluster::get_vmlist();
1702
dbfcac27 1703 my $vms_allowed;
2a498506 1704 if (defined($vmfilter)) {
dbfcac27 1705 $vms_allowed = { map { $_ => 1 } PVE::Tools::split_list($vmfilter) };
2a498506
DC
1706 }
1707
b72cdbb7 1708 my $res = {};
b92400b6 1709 foreach my $vmid (keys %{$vmlist->{ids}}) {
dbfcac27 1710 next if defined($vms_allowed) && !$vms_allowed->{$vmid};
b92400b6 1711
b72cdbb7
TL
1712 my $d = $vmlist->{ids}->{$vmid};
1713 next if $nodename && $d->{node} ne $nodename;
b92400b6 1714
b72cdbb7
TL
1715 eval {
1716 my $class;
2c27e4b7 1717 if ($d->{type} eq 'lxc') {
b72cdbb7 1718 $class = 'PVE::LXC::Config';
2c27e4b7 1719 } elsif ($d->{type} eq 'qemu') {
b72cdbb7 1720 $class = 'PVE::QemuConfig';
b92400b6 1721 } else {
dbfcac27 1722 die "unknown virtual guest type '$d->{type}'\n";
b92400b6
DM
1723 }
1724
b72cdbb7
TL
1725 my $conf = $class->load_config($vmid);
1726 return if !$templates && $class->is_template($conf);
1727 return if !$ha_managed && PVE::HA::Config::vm_is_ha_managed($vmid);
04b2004b 1728
b2bb6d77 1729 $res->{$vmid}->{conf} = $conf;
b72cdbb7 1730 $res->{$vmid}->{type} = $d->{type};
1c8dc310 1731 $res->{$vmid}->{class} = $class;
b92400b6
DM
1732 };
1733 warn $@ if $@;
1734 }
1735
b72cdbb7
TL
1736 return $res;
1737};
1738
1739# return all VMs which should get started/stopped on power up/down
1740my $get_start_stop_list = sub {
1741 my ($nodename, $autostart, $vmfilter) = @_;
1742
ae9d10ca
TL
1743 # do not skip HA vms on force or if a specific VMID set is wanted
1744 my $include_ha_managed = defined($vmfilter) ? 1 : 0;
1745
dbfcac27 1746 my $vmlist = $get_filtered_vmlist->($nodename, $vmfilter, undef, $include_ha_managed);
b72cdbb7
TL
1747
1748 my $resList = {};
1749 foreach my $vmid (keys %$vmlist) {
b2bb6d77 1750 my $conf = $vmlist->{$vmid}->{conf};
b72cdbb7
TL
1751 next if $autostart && !$conf->{onboot};
1752
dbfcac27
TL
1753 my $startup = $conf->{startup} ? PVE::JSONSchema::pve_parse_startup_order($conf->{startup}) : {};
1754 my $order = $startup->{order} = $startup->{order} // LONG_MAX;
b72cdbb7 1755
dbfcac27
TL
1756 $resList->{$order}->{$vmid} = $startup;
1757 $resList->{$order}->{$vmid}->{type} = $vmlist->{$vmid}->{type};
b72cdbb7
TL
1758 }
1759
b92400b6
DM
1760 return $resList;
1761};
1762
1c8dc310
TL
1763my $remove_locks_on_startup = sub {
1764 my ($nodename) = @_;
1765
1766 my $vmlist = &$get_filtered_vmlist($nodename, undef, undef, 1);
1767
1768 foreach my $vmid (keys %$vmlist) {
1769 my $conf = $vmlist->{$vmid}->{conf};
23b54109 1770 my $class = $vmlist->{$vmid}->{class};
1c8dc310
TL
1771
1772 eval {
1773 if ($class->has_lock($conf, 'backup')) {
1774 $class->remove_lock($vmid, 'backup');
1775 my $msg = "removed left over backup lock from '$vmid'!";
1776 warn "$msg\n"; # prints to task log
1777 syslog('warning', $msg);
1778 }
1779 }; warn $@ if $@;
1780 }
1781};
1782
b92400b6 1783__PACKAGE__->register_method ({
f9d26e09
TL
1784 name => 'startall',
1785 path => 'startall',
b92400b6
DM
1786 method => 'POST',
1787 protected => 1,
17e3b3b2 1788 permissions => {
6aec4565
FE
1789 description => "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/<ID>' for "
1790 ."each ID passed via the 'vms' parameter.",
1791 user => 'all',
17e3b3b2 1792 },
9c8a09a7 1793 proxyto => 'node',
e1b57809 1794 description => "Start all VMs and containers located on this node (by default only those with onboot=1).",
b92400b6 1795 parameters => {
6110ed03 1796 additionalProperties => 0,
b92400b6
DM
1797 properties => {
1798 node => get_standard_option('pve-node'),
c09c7160
AD
1799 force => {
1800 optional => 1,
1801 type => 'boolean',
e1b57809
TL
1802 default => 'off',
1803 description => "Issue start command even if virtual guest have 'onboot' not set or set to off.",
c09c7160 1804 },
2a498506 1805 vms => {
a740deff 1806 description => "Only consider guests from this comma separated list of VMIDs.",
2a498506
DC
1807 type => 'string', format => 'pve-vmid-list',
1808 optional => 1,
1809 },
b92400b6
DM
1810 },
1811 },
1812 returns => {
1813 type => 'string',
1814 },
1815 code => sub {
1816 my ($param) = @_;
1817
1818 my $rpcenv = PVE::RPCEnvironment::get();
1819 my $authuser = $rpcenv->get_user();
1820
6aec4565
FE
1821 if (!$rpcenv->check($authuser, "/", [ 'VM.PowerMgmt' ], 1)) {
1822 my @vms = PVE::Tools::split_list($param->{vms});
1823 if (scalar(@vms) > 0) {
1824 $rpcenv->check($authuser, "/vms/$_", [ 'VM.PowerMgmt' ]) for @vms;
1825 } else {
1826 raise_perm_exc("/, VM.PowerMgmt");
1827 }
1828 }
1829
b92400b6
DM
1830 my $nodename = $param->{node};
1831 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
1832
c09c7160
AD
1833 my $force = $param->{force};
1834
b92400b6 1835 my $code = sub {
b92400b6
DM
1836 $rpcenv->{type} = 'priv'; # to start tasks in background
1837
3947d0a0
DM
1838 if (!PVE::Cluster::check_cfs_quorum(1)) {
1839 print "waiting for quorum ...\n";
1840 do {
1841 sleep(1);
1842 } while (!PVE::Cluster::check_cfs_quorum(1));
1843 print "got quorum\n";
b92400b6 1844 }
1c8dc310
TL
1845
1846 eval { # remove backup locks, but avoid running into a scheduled backup job
1847 PVE::Tools::lock_file('/var/run/vzdump.lock', 10, $remove_locks_on_startup, $nodename);
f77faab2
TL
1848 };
1849 warn $@ if $@;
1c8dc310 1850
c09c7160 1851 my $autostart = $force ? undef : 1;
f77faab2 1852 my $startList = $get_start_stop_list->($nodename, $autostart, $param->{vms});
3f12bcfb
DM
1853
1854 # Note: use numeric sorting with <=>
f77faab2 1855 for my $order (sort {$a <=> $b} keys %$startList) {
b92400b6
DM
1856 my $vmlist = $startList->{$order};
1857
f77faab2 1858 for my $vmid (sort {$a <=> $b} keys %$vmlist) {
b92400b6
DM
1859 my $d = $vmlist->{$vmid};
1860
1861 PVE::Cluster::check_cfs_quorum(); # abort when we loose quorum
b72cdbb7 1862
b92400b6
DM
1863 eval {
1864 my $default_delay = 0;
1865 my $upid;
1866
2c27e4b7
DM
1867 if ($d->{type} eq 'lxc') {
1868 return if PVE::LXC::check_running($vmid);
1869 print STDERR "Starting CT $vmid\n";
1b5e56f2 1870 $upid = PVE::API2::LXC::Status->vm_start({node => $nodename, vmid => $vmid });
2c27e4b7 1871 } elsif ($d->{type} eq 'qemu') {
d6c49392 1872 $default_delay = 3; # to reduce load
b92400b6
DM
1873 return if PVE::QemuServer::check_running($vmid, 1);
1874 print STDERR "Starting VM $vmid\n";
1875 $upid = PVE::API2::Qemu->vm_start({node => $nodename, vmid => $vmid });
1876 } else {
1877 die "unknown VM type '$d->{type}'\n";
1878 }
1879
f77faab2
TL
1880 my $task = PVE::Tools::upid_decode($upid);
1881 while (PVE::ProcFSTools::check_process_running($task->{pid})) {
b92400b6
DM
1882 sleep(1);
1883 }
1884
1885 my $status = PVE::Tools::upid_read_status($upid);
0a7de337 1886 if (!PVE::Tools::upid_status_is_error($status)) {
b92400b6
DM
1887 # use default delay to reduce load
1888 my $delay = defined($d->{up}) ? int($d->{up}) : $default_delay;
1889 if ($delay > 0) {
1890 print STDERR "Waiting for $delay seconds (startup delay)\n" if $d->{up};
1891 for (my $i = 0; $i < $delay; $i++) {
1892 sleep(1);
1893 }
1894 }
1895 } else {
b27ebcad
TL
1896 my $rendered_type = $d->{type} eq 'lxc' ? 'CT' : 'VM';
1897 print STDERR "Starting $rendered_type $vmid failed: $status\n";
b92400b6
DM
1898 }
1899 };
1900 warn $@ if $@;
1901 }
1902 }
1903 return;
1904 };
1905
1906 return $rpcenv->fork_worker('startall', undef, $authuser, $code);
1907 }});
1908
1909my $create_stop_worker = sub {
11543293 1910 my ($nodename, $type, $vmid, $timeout, $force_stop) = @_;
b92400b6 1911
2c27e4b7
DM
1912 if ($type eq 'lxc') {
1913 return if !PVE::LXC::check_running($vmid);
2c27e4b7 1914 print STDERR "Stopping CT $vmid (timeout = $timeout seconds)\n";
79d62026 1915 return PVE::API2::LXC::Status->vm_shutdown(
11543293 1916 { node => $nodename, vmid => $vmid, timeout => $timeout, forceStop => $force_stop }
79d62026 1917 );
2c27e4b7 1918 } elsif ($type eq 'qemu') {
b92400b6 1919 return if !PVE::QemuServer::check_running($vmid, 1);
88ba9a1d 1920 print STDERR "Stopping VM $vmid (timeout = $timeout seconds)\n";
79d62026 1921 return PVE::API2::Qemu->vm_shutdown(
11543293 1922 { node => $nodename, vmid => $vmid, timeout => $timeout, forceStop => $force_stop }
79d62026 1923 );
b92400b6
DM
1924 } else {
1925 die "unknown VM type '$type'\n";
1926 }
b92400b6
DM
1927};
1928
1929__PACKAGE__->register_method ({
f9d26e09
TL
1930 name => 'stopall',
1931 path => 'stopall',
b92400b6
DM
1932 method => 'POST',
1933 protected => 1,
17e3b3b2 1934 permissions => {
6aec4565
FE
1935 description => "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/<ID>' for "
1936 ."each ID passed via the 'vms' parameter.",
1937 user => 'all',
17e3b3b2 1938 },
9c8a09a7 1939 proxyto => 'node',
b92400b6
DM
1940 description => "Stop all VMs and Containers.",
1941 parameters => {
6110ed03 1942 additionalProperties => 0,
b92400b6
DM
1943 properties => {
1944 node => get_standard_option('pve-node'),
2a498506
DC
1945 vms => {
1946 description => "Only consider Guests with these IDs.",
1947 type => 'string', format => 'pve-vmid-list',
1948 optional => 1,
1949 },
11543293
TL
1950 'force-stop' => {
1951 description => 'Force a hard-stop after the timeout.',
1952 type => 'boolean',
1953 default => 1,
1954 optional => 1,
1955 },
8f063fbc 1956 'timeout' => {
853ce1ea
TL
1957 description => 'Timeout for each guest shutdown task. Depending on `force-stop`,'
1958 .' the shutdown gets then simply aborted or a hard-stop is forced.',
8f063fbc
TL
1959 type => 'integer',
1960 optional => 1,
1961 default => 180,
853ce1ea
TL
1962 minimum => 0,
1963 maximum => 2 * 3600, # mostly arbitrary, but we do not want to high timeouts
8f063fbc 1964 },
b92400b6
DM
1965 },
1966 },
1967 returns => {
1968 type => 'string',
1969 },
1970 code => sub {
1971 my ($param) = @_;
1972
1973 my $rpcenv = PVE::RPCEnvironment::get();
1974 my $authuser = $rpcenv->get_user();
1975
6aec4565
FE
1976 if (!$rpcenv->check($authuser, "/", [ 'VM.PowerMgmt' ], 1)) {
1977 my @vms = PVE::Tools::split_list($param->{vms});
1978 if (scalar(@vms) > 0) {
1979 $rpcenv->check($authuser, "/vms/$_", [ 'VM.PowerMgmt' ]) for @vms;
1980 } else {
1981 raise_perm_exc("/, VM.PowerMgmt");
1982 }
1983 }
1984
b92400b6
DM
1985 my $nodename = $param->{node};
1986 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
1987
1988 my $code = sub {
1989
1990 $rpcenv->{type} = 'priv'; # to start tasks in background
1991
f77faab2 1992 my $stopList = $get_start_stop_list->($nodename, undef, $param->{vms});
b92400b6
DM
1993
1994 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
89ceb802
TL
1995 my $datacenterconfig = cfs_read_file('datacenter.cfg');
1996 # if not set by user spawn max cpu count number of workers
1997 my $maxWorkers = $datacenterconfig->{max_workers} || $cpuinfo->{cpus};
b92400b6 1998
f77faab2 1999 for my $order (sort {$b <=> $a} keys %$stopList) {
b92400b6
DM
2000 my $vmlist = $stopList->{$order};
2001 my $workers = {};
37bec895
DM
2002
2003 my $finish_worker = sub {
2004 my $pid = shift;
f77faab2 2005 my $worker = delete $workers->{$pid} || return;
37bec895 2006
f77faab2 2007 syslog('info', "end task $worker->{upid}");
37bec895
DM
2008 };
2009
f77faab2 2010 for my $vmid (sort {$b <=> $a} keys %$vmlist) {
b92400b6 2011 my $d = $vmlist->{$vmid};
8f063fbc 2012 my $timeout = int($d->{down} // $param->{timeout} // 180);
11543293
TL
2013 my $upid = eval {
2014 $create_stop_worker->(
2015 $nodename, $d->{type}, $vmid, $timeout, $param->{'force-stop'} // 1)
2016 };
b92400b6 2017 warn $@ if $@;
37bec895
DM
2018 next if !$upid;
2019
f77faab2
TL
2020 my $task = PVE::Tools::upid_decode($upid, 1);
2021 next if !$task;
37bec895 2022
f77faab2 2023 my $pid = $task->{pid};
37bec895
DM
2024
2025 $workers->{$pid} = { type => $d->{type}, upid => $upid, vmid => $vmid };
b92400b6
DM
2026 while (scalar(keys %$workers) >= $maxWorkers) {
2027 foreach my $p (keys %$workers) {
2028 if (!PVE::ProcFSTools::check_process_running($p)) {
f77faab2 2029 $finish_worker->($p);
b92400b6
DM
2030 }
2031 }
2032 sleep(1);
2033 }
2034 }
2035 while (scalar(keys %$workers)) {
f77faab2 2036 for my $p (keys %$workers) {
b92400b6 2037 if (!PVE::ProcFSTools::check_process_running($p)) {
f77faab2 2038 $finish_worker->($p);
b92400b6
DM
2039 }
2040 }
2041 sleep(1);
2042 }
2043 }
37bec895
DM
2044
2045 syslog('info', "all VMs and CTs stopped");
2046
b92400b6
DM
2047 return;
2048 };
2049
2050 return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
b92400b6
DM
2051 }});
2052
5f04abc2
HL
2053my $create_suspend_worker = sub {
2054 my ($nodename, $vmid) = @_;
25c0052a
TL
2055 if (!PVE::QemuServer::check_running($vmid, 1)) {
2056 print "VM $vmid not running, skipping suspension\n";
2057 return;
2058 }
5f04abc2
HL
2059 print STDERR "Suspending VM $vmid\n";
2060 return PVE::API2::Qemu->vm_suspend(
2061 { node => $nodename, vmid => $vmid, todisk => 1 }
2062 );
2063};
2064
2065__PACKAGE__->register_method ({
2066 name => 'suspendall',
2067 path => 'suspendall',
2068 method => 'POST',
2069 protected => 1,
2070 permissions => {
2071 description => "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/<ID>' for each"
2072 ." ID passed via the 'vms' parameter. Additionally, you need 'VM.Config.Disk' on the"
2073 ." '/vms/{vmid}' path and 'Datastore.AllocateSpace' for the configured state-storage(s)",
2074 user => 'all',
2075 },
2076 proxyto => 'node',
2077 description => "Suspend all VMs.",
2078 parameters => {
2079 additionalProperties => 0,
2080 properties => {
2081 node => get_standard_option('pve-node'),
2082 vms => {
2083 description => "Only consider Guests with these IDs.",
2084 type => 'string', format => 'pve-vmid-list',
2085 optional => 1,
2086 },
2087 },
2088 },
2089 returns => {
2090 type => 'string',
2091 },
2092 code => sub {
2093 my ($param) = @_;
2094
2095 my $rpcenv = PVE::RPCEnvironment::get();
2096 my $authuser = $rpcenv->get_user();
2097
2098 # we cannot really check access to the state-storage here, that's happening per worker.
2099 if (!$rpcenv->check($authuser, "/", [ 'VM.PowerMgmt', 'VM.Config.Disk' ], 1)) {
2100 my @vms = PVE::Tools::split_list($param->{vms});
2101 if (scalar(@vms) > 0) {
2102 $rpcenv->check($authuser, "/vms/$_", [ 'VM.PowerMgmt' ]) for @vms;
2103 } else {
84e1e9d9 2104 raise_perm_exc("/, VM.PowerMgmt && VM.Config.Disk");
5f04abc2
HL
2105 }
2106 }
2107
2108 my $nodename = $param->{node};
2109 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
2110
2111 my $code = sub {
2112
2113 $rpcenv->{type} = 'priv'; # to start tasks in background
2114
84e1e9d9 2115 my $toSuspendList = $get_start_stop_list->($nodename, undef, $param->{vms});
5f04abc2
HL
2116
2117 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
2118 my $datacenterconfig = cfs_read_file('datacenter.cfg');
2119 # if not set by user spawn max cpu count number of workers
2120 my $maxWorkers = $datacenterconfig->{max_workers} || $cpuinfo->{cpus};
2121
84e1e9d9
TL
2122 for my $order (sort {$b <=> $a} keys %$toSuspendList) {
2123 my $vmlist = $toSuspendList->{$order};
5f04abc2
HL
2124 my $workers = {};
2125
2126 my $finish_worker = sub {
2127 my $pid = shift;
2128 my $worker = delete $workers->{$pid} || return;
2129
2130 syslog('info', "end task $worker->{upid}");
2131 };
2132
2133 for my $vmid (sort {$b <=> $a} keys %$vmlist) {
2134 my $d = $vmlist->{$vmid};
ebb71cb5
TL
2135 if ($d->{type} ne 'qemu') {
2136 log_warn("skipping $vmid, only VMs can be suspended");
5f04abc2
HL
2137 next;
2138 }
2139 my $upid = eval {
2140 $create_suspend_worker->($nodename, $vmid)
2141 };
2142 warn $@ if $@;
2143 next if !$upid;
2144
2145 my $task = PVE::Tools::upid_decode($upid, 1);
2146 next if !$task;
2147
2148 my $pid = $task->{pid};
5f04abc2 2149 $workers->{$pid} = { type => $d->{type}, upid => $upid, vmid => $vmid };
84e1e9d9 2150
5f04abc2 2151 while (scalar(keys %$workers) >= $maxWorkers) {
84e1e9d9 2152 for my $p (keys %$workers) {
5f04abc2
HL
2153 if (!PVE::ProcFSTools::check_process_running($p)) {
2154 $finish_worker->($p);
2155 }
2156 }
2157 sleep(1);
2158 }
2159 }
2160 while (scalar(keys %$workers)) {
2161 for my $p (keys %$workers) {
2162 if (!PVE::ProcFSTools::check_process_running($p)) {
2163 $finish_worker->($p);
2164 }
2165 }
2166 sleep(1);
2167 }
2168 }
2169
2170 syslog('info', "all VMs suspended");
2171
2172 return;
2173 };
2174
2175 return $rpcenv->fork_worker('suspendall', undef, $authuser, $code);
2176 }});
2177
2178
9c8a09a7 2179my $create_migrate_worker = sub {
fc6b77a1 2180 my ($nodename, $type, $vmid, $target, $with_local_disks) = @_;
9c8a09a7
AD
2181
2182 my $upid;
2c27e4b7
DM
2183 if ($type eq 'lxc') {
2184 my $online = PVE::LXC::check_running($vmid) ? 1 : 0;
2185 print STDERR "Migrating CT $vmid\n";
f77faab2
TL
2186 $upid = PVE::API2::LXC->migrate_vm(
2187 { node => $nodename, vmid => $vmid, target => $target, restart => $online });
2c27e4b7 2188 } elsif ($type eq 'qemu') {
0b54f653
TL
2189 print STDERR "Check VM $vmid: ";
2190 *STDERR->flush();
9c8a09a7 2191 my $online = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
f77faab2
TL
2192 my $preconditions = PVE::API2::Qemu->migrate_vm_precondition(
2193 {node => $nodename, vmid => $vmid, target => $target});
0b54f653
TL
2194 my $invalidConditions = '';
2195 if ($online && !$with_local_disks && scalar @{$preconditions->{local_disks}}) {
f77faab2
TL
2196 $invalidConditions .= "\n Has local disks: ";
2197 $invalidConditions .= join(', ', map { $_->{volid} } @{$preconditions->{local_disks}});
d8d17271
TM
2198 }
2199
2200 if (@{$preconditions->{local_resources}}) {
f77faab2
TL
2201 $invalidConditions .= "\n Has local resources: ";
2202 $invalidConditions .= join(', ', @{$preconditions->{local_resources}});
d8d17271
TM
2203 }
2204
0b54f653
TL
2205 if ($invalidConditions && $invalidConditions ne '') {
2206 print STDERR "skip VM $vmid - precondition check failed:";
2207 die "$invalidConditions\n";
2208 }
2209 print STDERR "precondition check passed\n";
9c8a09a7 2210 print STDERR "Migrating VM $vmid\n";
71fd3de9
TL
2211
2212 my $params = {
6f3d18dd
TL
2213 node => $nodename,
2214 vmid => $vmid,
2215 target => $target,
2216 online => $online,
71fd3de9 2217 };
13411f99 2218 $params->{'with-local-disks'} = $with_local_disks if defined($with_local_disks);
71fd3de9
TL
2219
2220 $upid = PVE::API2::Qemu->migrate_vm($params);
9c8a09a7
AD
2221 } else {
2222 die "unknown VM type '$type'\n";
2223 }
2224
f77faab2 2225 my $task = PVE::Tools::upid_decode($upid);
9c8a09a7 2226
f77faab2 2227 return $task->{pid};
9c8a09a7
AD
2228};
2229
2230__PACKAGE__->register_method ({
2231 name => 'migrateall',
2232 path => 'migrateall',
2233 method => 'POST',
2234 proxyto => 'node',
2235 protected => 1,
17e3b3b2 2236 permissions => {
6aec4565
FE
2237 description => "The 'VM.Migrate' permission is required on '/' or on '/vms/<ID>' for each "
2238 ."ID passed via the 'vms' parameter.",
2239 user => 'all',
17e3b3b2 2240 },
9c8a09a7
AD
2241 description => "Migrate all VMs and Containers.",
2242 parameters => {
2243 additionalProperties => 0,
2244 properties => {
2245 node => get_standard_option('pve-node'),
58ab77d1 2246 target => get_standard_option('pve-node', { description => "Target node." }),
f77faab2
TL
2247 maxworkers => {
2248 description => "Maximal number of parallel migration job. If not set, uses"
2249 ."'max_workers' from datacenter.cfg. One of both must be set!",
89ceb802 2250 optional => 1,
f77faab2
TL
2251 type => 'integer',
2252 minimum => 1
58ab77d1 2253 },
2a498506
DC
2254 vms => {
2255 description => "Only consider Guests with these IDs.",
2256 type => 'string', format => 'pve-vmid-list',
2257 optional => 1,
2258 },
fc6b77a1
TM
2259 "with-local-disks" => {
2260 type => 'boolean',
2261 description => "Enable live storage migration for local disk",
2262 optional => 1,
2263 },
9c8a09a7
AD
2264 },
2265 },
2266 returns => {
2267 type => 'string',
2268 },
2269 code => sub {
2270 my ($param) = @_;
2271
2272 my $rpcenv = PVE::RPCEnvironment::get();
2273 my $authuser = $rpcenv->get_user();
2274
6aec4565
FE
2275 if (!$rpcenv->check($authuser, "/", [ 'VM.Migrate' ], 1)) {
2276 my @vms = PVE::Tools::split_list($param->{vms});
2277 if (scalar(@vms) > 0) {
2278 $rpcenv->check($authuser, "/vms/$_", [ 'VM.Migrate' ]) for @vms;
2279 } else {
2280 raise_perm_exc("/, VM.Migrate");
2281 }
2282 }
2283
9c8a09a7
AD
2284 my $nodename = $param->{node};
2285 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
2286
73981e39 2287 my $target = $param->{target};
fc6b77a1 2288 my $with_local_disks = $param->{'with-local-disks'};
73981e39
DC
2289 raise_param_exc({ target => "target is local node."}) if $target eq $nodename;
2290
2291 PVE::Cluster::check_cfs_quorum();
2292
2293 PVE::Cluster::check_node_exists($target);
89ceb802
TL
2294
2295 my $datacenterconfig = cfs_read_file('datacenter.cfg');
2296 # prefer parameter over datacenter cfg settings
2297 my $maxWorkers = $param->{maxworkers} || $datacenterconfig->{max_workers} ||
2298 die "either 'maxworkers' parameter or max_workers in datacenter.cfg must be set!\n";
9c8a09a7
AD
2299
2300 my $code = sub {
9c8a09a7
AD
2301 $rpcenv->{type} = 'priv'; # to start tasks in background
2302
f5c1dde5 2303 my $vmlist = &$get_filtered_vmlist($nodename, $param->{vms}, 1, 1);
49652c46
TL
2304 if (!scalar(keys %$vmlist)) {
2305 warn "no virtual guests matched, nothing to do..\n";
2306 return;
2307 }
9c8a09a7 2308
f5c1dde5 2309 my $workers = {};
49652c46 2310 my $workers_started = 0;
f5c1dde5
TL
2311 foreach my $vmid (sort keys %$vmlist) {
2312 my $d = $vmlist->{$vmid};
2313 my $pid;
fc6b77a1 2314 eval { $pid = &$create_migrate_worker($nodename, $d->{type}, $vmid, $target, $with_local_disks); };
f5c1dde5
TL
2315 warn $@ if $@;
2316 next if !$pid;
9c8a09a7 2317
49652c46 2318 $workers_started++;
f5c1dde5
TL
2319 $workers->{$pid} = 1;
2320 while (scalar(keys %$workers) >= $maxWorkers) {
9c8a09a7
AD
2321 foreach my $p (keys %$workers) {
2322 if (!PVE::ProcFSTools::check_process_running($p)) {
2323 delete $workers->{$p};
2324 }
2325 }
2326 sleep(1);
2327 }
2328 }
f5c1dde5
TL
2329 while (scalar(keys %$workers)) {
2330 foreach my $p (keys %$workers) {
8e4bee65 2331 # FIXME: what about PID re-use ?!?!
f5c1dde5
TL
2332 if (!PVE::ProcFSTools::check_process_running($p)) {
2333 delete $workers->{$p};
2334 }
2335 }
2336 sleep(1);
2337 }
49652c46
TL
2338 if ($workers_started <= 0) {
2339 die "no migrations worker started...\n";
2340 }
2341 print STDERR "All jobs finished, used $workers_started workers in total.\n";
9c8a09a7
AD
2342 return;
2343 };
2344
2345 return $rpcenv->fork_worker('migrateall', undef, $authuser, $code);
2346
2347 }});
2348
820d0458
DC
2349__PACKAGE__->register_method ({
2350 name => 'get_etc_hosts',
2351 path => 'hosts',
2352 method => 'GET',
2353 proxyto => 'node',
2354 protected => 1,
2355 permissions => {
2356 check => ['perm', '/', [ 'Sys.Audit' ]],
2357 },
2358 description => "Get the content of /etc/hosts.",
2359 parameters => {
2360 additionalProperties => 0,
2361 properties => {
2362 node => get_standard_option('pve-node'),
2363 },
2364 },
2365 returns => {
2366 type => 'object',
2367 properties => {
2368 digest => get_standard_option('pve-config-digest'),
2369 data => {
2370 type => 'string',
2371 description => 'The content of /etc/hosts.'
2372 },
2373 },
2374 },
2375 code => sub {
2376 my ($param) = @_;
2377
2378 return PVE::INotify::read_file('etchosts');
2379
2380 }});
2381
2382__PACKAGE__->register_method ({
2383 name => 'write_etc_hosts',
2384 path => 'hosts',
2385 method => 'POST',
2386 proxyto => 'node',
2387 protected => 1,
2388 permissions => {
2389 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
2390 },
2391 description => "Write /etc/hosts.",
2392 parameters => {
2393 additionalProperties => 0,
2394 properties => {
2395 node => get_standard_option('pve-node'),
2396 digest => get_standard_option('pve-config-digest'),
2397 data => {
2398 type => 'string',
2399 description => 'The target content of /etc/hosts.'
2400 },
2401 },
2402 },
2403 returns => {
2404 type => 'null',
2405 },
2406 code => sub {
2407 my ($param) = @_;
2408
79d62026 2409 PVE::Tools::lock_file('/var/lock/pve-etchosts.lck', undef, sub {
820d0458
DC
2410 if ($param->{digest}) {
2411 my $hosts = PVE::INotify::read_file('etchosts');
2412 PVE::Tools::assert_if_modified($hosts->{digest}, $param->{digest});
2413 }
2414 PVE::INotify::write_file('etchosts', $param->{data});
2415 });
2416 die $@ if $@;
2417
79d62026 2418 return;
820d0458
DC
2419 }});
2420
82282acf
WL
2421# bash completion helper
2422
2423sub complete_templet_repo {
2424 my ($cmdname, $pname, $cvalue) = @_;
2425
2426 my $repo = PVE::APLInfo::load_data();
2427 my $res = [];
2428 foreach my $templ (keys %{$repo->{all}}) {
2429 next if $templ !~ m/^$cvalue/;
2430 push @$res, $templ;
2431 }
2432
2433 return $res;
2434}
2435
aff192e6
DM
2436package PVE::API2::Nodes;
2437
2438use strict;
2439use warnings;
2440
2441use PVE::SafeSyslog;
2442use PVE::Cluster;
2443use PVE::RESTHandler;
2444use PVE::RPCEnvironment;
b193e4ac 2445use PVE::API2Tools;
f57cbe5d 2446use PVE::JSONSchema qw(get_standard_option);
aff192e6
DM
2447
2448use base qw(PVE::RESTHandler);
2449
2450__PACKAGE__->register_method ({
f9d26e09 2451 subclass => "PVE::API2::Nodes::Nodeinfo",
aff192e6
DM
2452 path => '{node}',
2453});
2454
2455__PACKAGE__->register_method ({
f9d26e09
TL
2456 name => 'index',
2457 path => '',
aff192e6
DM
2458 method => 'GET',
2459 permissions => { user => 'all' },
2460 description => "Cluster node index.",
2461 parameters => {
6110ed03 2462 additionalProperties => 0,
aff192e6
DM
2463 properties => {},
2464 },
2465 returns => {
2466 type => 'array',
2467 items => {
2468 type => "object",
f57cbe5d
DM
2469 properties => {
2470 node => get_standard_option('pve-node'),
2471 status => {
2472 description => "Node status.",
2473 type => 'string',
2474 enum => ['unknown', 'online', 'offline'],
2475 },
2476 cpu => {
2477 description => "CPU utilization.",
2478 type => 'number',
2479 optional => 1,
2480 renderer => 'fraction_as_percentage',
2481 },
2482 maxcpu => {
2483 description => "Number of available CPUs.",
2484 type => 'integer',
2485 optional => 1,
2486 },
2487 mem => {
2488 description => "Used memory in bytes.",
4a512d7a 2489 type => 'integer',
f57cbe5d
DM
2490 optional => 1,
2491 renderer => 'bytes',
2492 },
2493 maxmem => {
2494 description => "Number of available memory in bytes.",
2495 type => 'integer',
2496 optional => 1,
2497 renderer => 'bytes',
2498 },
2499 level => {
2500 description => "Support level.",
2501 type => 'string',
2502 optional => 1,
2503 },
2504 uptime => {
2505 description => "Node uptime in seconds.",
2506 type => 'integer',
2507 optional => 1,
2508 renderer => 'duration',
2509 },
2510 ssl_fingerprint => {
2511 description => "The SSL fingerprint for the node certificate.",
2512 type => 'string',
2513 optional => 1,
2514 },
2515 },
aff192e6 2516 },
b193e4ac 2517 links => [ { rel => 'child', href => "{node}" } ],
aff192e6
DM
2518 },
2519 code => sub {
2520 my ($param) = @_;
f9d26e09 2521
57d56896
TL
2522 my $rpcenv = PVE::RPCEnvironment::get();
2523 my $authuser = $rpcenv->get_user();
2524
aff192e6
DM
2525 my $clinfo = PVE::Cluster::get_clinfo();
2526 my $res = [];
2527
b193e4ac
DM
2528 my $nodelist = PVE::Cluster::get_nodelist();
2529 my $members = PVE::Cluster::get_members();
aff192e6
DM
2530 my $rrd = PVE::Cluster::rrd_dump();
2531
b193e4ac 2532 foreach my $node (@$nodelist) {
57d56896
TL
2533 my $can_audit = $rpcenv->check($authuser, "/nodes/$node", [ 'Sys.Audit' ], 1);
2534 my $entry = PVE::API2Tools::extract_node_stats($node, $members, $rrd, !$can_audit);
1a664b0f
FG
2535
2536 $entry->{ssl_fingerprint} = eval { PVE::Cluster::get_node_fingerprint($node) };
2537 warn "$@" if $@;
2538
aff192e6
DM
2539 push @$res, $entry;
2540 }
2541
2542 return $res;
2543 }});
2544
25451;