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