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