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