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