]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Nodes.pm
ui: resource mappings: remove border and add resize handle
[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
87c3e931 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.",
657 type => 'string', format => 'pve-configid-list',
658 },
659 cf => {
660 description => "The RRD consolidation function",
661 type => 'string',
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",
702 type => 'string',
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 => {
d0289a19 942 description => "Restricted to users on realm 'pam'",
7d020b42 943 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
aff192e6
DM
944 },
945 description => "Creates a VNC Shell proxy.",
946 parameters => {
6110ed03 947 additionalProperties => 0,
aff192e6
DM
948 properties => {
949 node => get_standard_option('pve-node'),
d03d7e1e
TM
950 cmd => {
951 type => 'string',
952 description => "Run specific command or default to login.",
dab7a849 953 enum => [keys %$shell_cmd_map],
d03d7e1e
TM
954 optional => 1,
955 default => 'login',
956 },
f24be7e7
TL
957 'cmd-opts' => {
958 type => 'string',
959 description => "Add parameters to a command. Encoded as null terminated strings.",
960 requires => 'cmd',
961 optional => 1,
962 default => '',
963 },
8dfca17e
DM
964 websocket => {
965 optional => 1,
966 type => 'boolean',
967 description => "use websocket instead of standard vnc.",
968 },
b8ac8b0c
DC
969 width => {
970 optional => 1,
971 description => "sets the width of the console in pixels.",
972 type => 'integer',
973 minimum => 16,
974 maximum => 4096,
975 },
976 height => {
977 optional => 1,
978 description => "sets the height of the console in pixels.",
979 type => 'integer',
980 minimum => 16,
981 maximum => 2160,
982 },
aff192e6
DM
983 },
984 },
f9d26e09 985 returns => {
6110ed03 986 additionalProperties => 0,
aff192e6
DM
987 properties => {
988 user => { type => 'string' },
989 ticket => { type => 'string' },
990 cert => { type => 'string' },
991 port => { type => 'integer' },
992 upid => { type => 'string' },
993 },
994 },
995 code => sub {
996 my ($param) = @_;
997
998 my $rpcenv = PVE::RPCEnvironment::get();
d553e535 999 my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
d0289a19 1000
f9d26e09 1001 raise_perm_exc("realm != pam") if $realm ne 'pam';
0c8e509e
FE
1002
1003 if (defined($param->{cmd}) && $param->{cmd} eq 'upgrade' && $user ne 'root@pam') {
1004 raise_perm_exc('user != root@pam');
1005 }
3a76893d 1006
aff192e6
DM
1007 my $node = $param->{node};
1008
57ebda08 1009 my $authpath = "/nodes/$node";
57ebda08
DM
1010 my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath);
1011
aff192e6
DM
1012 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
1013 if !$sslcert;
1014
677bee7c 1015 my ($port, $remcmd) = $get_vnc_connection_info->($node);
d4a25f0b 1016
f24be7e7 1017 my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'});
aff192e6 1018
f9d26e09 1019 my $timeout = 10;
aff192e6 1020
677bee7c
TL
1021 my $cmd = ['/usr/bin/vncterm',
1022 '-rfbport', $port,
1023 '-timeout', $timeout,
1024 '-authpath', $authpath,
1025 '-perm', 'Sys.Console',
1026 ];
b8ac8b0c 1027
677bee7c
TL
1028 push @$cmd, '-width', $param->{width} if $param->{width};
1029 push @$cmd, '-height', $param->{height} if $param->{height};
b8ac8b0c 1030
8dfca17e 1031 if ($param->{websocket}) {
f9d26e09 1032 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
8dfca17e
DM
1033 push @$cmd, '-notls', '-listen', 'localhost';
1034 }
1035
1036 push @$cmd, '-c', @$remcmd, @$shcmd;
aff192e6
DM
1037
1038 my $realcmd = sub {
1039 my $upid = shift;
1040
1041 syslog ('info', "starting vnc proxy $upid\n");
1042
6d394492 1043 my $cmdstr = join (' ', @$cmd);
aff192e6
DM
1044 syslog ('info', "launch command: $cmdstr");
1045
f9d26e09 1046 eval {
6d394492 1047 foreach my $k (keys %ENV) {
8dfca17e 1048 next if $k eq 'PVE_VNC_TICKET';
b0d4b407 1049 next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME' || $k eq 'LANG' || $k eq 'LANGUAGE';
6d394492
DM
1050 delete $ENV{$k};
1051 }
1052 $ENV{PWD} = '/';
1053
b0d4b407 1054 PVE::Tools::run_command($cmd, errmsg => "vncterm failed", keeplocale => 1);
6d394492
DM
1055 };
1056 if (my $err = $@) {
1057 syslog ('err', $err);
aff192e6
DM
1058 }
1059
1060 return;
1061 };
1062
1063 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
f9d26e09 1064
6806a0f8 1065 PVE::Tools::wait_for_vnc_port($port);
aff192e6
DM
1066
1067 return {
1068 user => $user,
1069 ticket => $ticket,
f9d26e09
TL
1070 port => $port,
1071 upid => $upid,
1072 cert => $sslcert,
aff192e6
DM
1073 };
1074 }});
1075
4b168c27
DC
1076__PACKAGE__->register_method ({
1077 name => 'termproxy',
1078 path => 'termproxy',
1079 method => 'POST',
1080 protected => 1,
1081 permissions => {
1082 description => "Restricted to users on realm 'pam'",
1083 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1084 },
1085 description => "Creates a VNC Shell proxy.",
1086 parameters => {
1087 additionalProperties => 0,
1088 properties => {
1089 node => get_standard_option('pve-node'),
d03d7e1e
TM
1090 cmd => {
1091 type => 'string',
1092 description => "Run specific command or default to login.",
dab7a849 1093 enum => [keys %$shell_cmd_map],
d03d7e1e
TM
1094 optional => 1,
1095 default => 'login',
1096 },
f24be7e7
TL
1097 'cmd-opts' => {
1098 type => 'string',
1099 description => "Add parameters to a command. Encoded as null terminated strings.",
1100 requires => 'cmd',
1101 optional => 1,
1102 default => '',
1103 },
4b168c27
DC
1104 },
1105 },
1106 returns => {
1107 additionalProperties => 0,
1108 properties => {
1109 user => { type => 'string' },
1110 ticket => { type => 'string' },
1111 port => { type => 'integer' },
1112 upid => { type => 'string' },
1113 },
1114 },
1115 code => sub {
1116 my ($param) = @_;
1117
1118 my $rpcenv = PVE::RPCEnvironment::get();
4b168c27 1119 my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
f24be7e7 1120 raise_perm_exc("realm $realm != pam") if $realm ne 'pam';
4b168c27
DC
1121
1122 my $node = $param->{node};
4b168c27 1123 my $authpath = "/nodes/$node";
4b168c27
DC
1124 my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath);
1125
677bee7c 1126 my ($port, $remcmd) = $get_vnc_connection_info->($node);
4b168c27 1127
f24be7e7 1128 my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'});
4b168c27
DC
1129
1130 my $realcmd = sub {
1131 my $upid = shift;
1132
1133 syslog ('info', "starting termproxy $upid\n");
1134
f24be7e7
TL
1135 my $cmd = [
1136 '/usr/bin/termproxy',
1137 $port,
677bee7c
TL
1138 '--path', $authpath,
1139 '--perm', 'Sys.Console',
1140 '--'
1141 ];
d03d7e1e 1142 push @$cmd, @$remcmd, @$shcmd;
4b168c27
DC
1143
1144 PVE::Tools::run_command($cmd);
1145 };
4b168c27
DC
1146 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
1147
1148 PVE::Tools::wait_for_vnc_port($port);
1149
1150 return {
1151 user => $user,
1152 ticket => $ticket,
1153 port => $port,
1154 upid => $upid,
1155 };
1156 }});
1157
8dfca17e
DM
1158__PACKAGE__->register_method({
1159 name => 'vncwebsocket',
1160 path => 'vncwebsocket',
1161 method => 'GET',
f9d26e09 1162 permissions => {
8dfca17e
DM
1163 description => "Restricted to users on realm 'pam'. You also need to pass a valid ticket (vncticket).",
1164 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1165 },
0e68b116 1166 description => "Opens a websocket for VNC traffic.",
8dfca17e 1167 parameters => {
6110ed03 1168 additionalProperties => 0,
8dfca17e
DM
1169 properties => {
1170 node => get_standard_option('pve-node'),
1171 vncticket => {
1172 description => "Ticket from previous call to vncproxy.",
1173 type => 'string',
1174 maxLength => 512,
1175 },
1176 port => {
1177 description => "Port number returned by previous vncproxy call.",
1178 type => 'integer',
1179 minimum => 5900,
1180 maximum => 5999,
1181 },
1182 },
1183 },
1184 returns => {
1185 type => "object",
1186 properties => {
1187 port => { type => 'string' },
1188 },
1189 },
1190 code => sub {
1191 my ($param) = @_;
1192
1193 my $rpcenv = PVE::RPCEnvironment::get();
1194
1195 my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
1196
f9d26e09 1197 raise_perm_exc("realm != pam") if $realm ne 'pam';
8dfca17e
DM
1198
1199 my $authpath = "/nodes/$param->{node}";
1200
1201 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $user, $authpath);
1202
1203 my $port = $param->{port};
f9d26e09 1204
8dfca17e
DM
1205 return { port => $port };
1206 }});
1207
2d802f8c 1208__PACKAGE__->register_method ({
f9d26e09
TL
1209 name => 'spiceshell',
1210 path => 'spiceshell',
2d802f8c
DM
1211 method => 'POST',
1212 protected => 1,
1213 proxyto => 'node',
1214 permissions => {
1215 description => "Restricted to users on realm 'pam'",
1216 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1217 },
7774df78 1218 description => "Creates a SPICE shell.",
2d802f8c 1219 parameters => {
6110ed03 1220 additionalProperties => 0,
2d802f8c
DM
1221 properties => {
1222 node => get_standard_option('pve-node'),
7774df78 1223 proxy => get_standard_option('spice-proxy', { optional => 1 }),
d03d7e1e
TM
1224 cmd => {
1225 type => 'string',
1226 description => "Run specific command or default to login.",
dab7a849 1227 enum => [keys %$shell_cmd_map],
d03d7e1e
TM
1228 optional => 1,
1229 default => 'login',
1230 },
f24be7e7
TL
1231 'cmd-opts' => {
1232 type => 'string',
1233 description => "Add parameters to a command. Encoded as null terminated strings.",
1234 requires => 'cmd',
1235 optional => 1,
1236 default => '',
1237 },
2d802f8c
DM
1238 },
1239 },
7774df78 1240 returns => get_standard_option('remote-viewer-config'),
2d802f8c
DM
1241 code => sub {
1242 my ($param) = @_;
1243
1244 my $rpcenv = PVE::RPCEnvironment::get();
1245 my $authuser = $rpcenv->get_user();
1246
1247 my ($user, undef, $realm) = PVE::AccessControl::verify_username($authuser);
1248
f9d26e09 1249 raise_perm_exc("realm != pam") if $realm ne 'pam';
b270a6b4
FE
1250
1251 if (defined($param->{cmd}) && $param->{cmd} eq 'upgrade' && $user ne 'root@pam') {
1252 raise_perm_exc('user != root@pam');
1253 }
2d802f8c
DM
1254
1255 my $node = $param->{node};
1256 my $proxy = $param->{proxy};
2d802f8c
DM
1257
1258 my $authpath = "/nodes/$node";
b289829f 1259 my $permissions = 'Sys.Console';
b270a6b4 1260
f24be7e7 1261 my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'});
2d802f8c 1262
b289829f 1263 my $title = "Shell on '$node'";
2d802f8c 1264
b289829f 1265 return PVE::API2Tools::run_spiceterm($authpath, $permissions, 0, $node, $proxy, $title, $shcmd);
2d802f8c
DM
1266 }});
1267
aff192e6 1268__PACKAGE__->register_method({
f9d26e09
TL
1269 name => 'dns',
1270 path => 'dns',
aff192e6
DM
1271 method => 'GET',
1272 permissions => {
7d020b42 1273 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
aff192e6
DM
1274 },
1275 description => "Read DNS settings.",
1276 proxyto => 'node',
1277 parameters => {
6110ed03 1278 additionalProperties => 0,
aff192e6
DM
1279 properties => {
1280 node => get_standard_option('pve-node'),
1281 },
1282 },
1283 returns => {
1284 type => "object",
6110ed03 1285 additionalProperties => 0,
aff192e6
DM
1286 properties => {
1287 search => {
1288 description => "Search domain for host-name lookup.",
1289 type => 'string',
1290 optional => 1,
1291 },
1292 dns1 => {
1293 description => 'First name server IP address.',
1294 type => 'string',
1295 optional => 1,
f9d26e09 1296 },
aff192e6
DM
1297 dns2 => {
1298 description => 'Second name server IP address.',
1299 type => 'string',
1300 optional => 1,
f9d26e09 1301 },
aff192e6
DM
1302 dns3 => {
1303 description => 'Third name server IP address.',
1304 type => 'string',
1305 optional => 1,
f9d26e09 1306 },
aff192e6
DM
1307 },
1308 },
1309 code => sub {
1310 my ($param) = @_;
1311
1312 my $res = PVE::INotify::read_file('resolvconf');
1313
1314 return $res;
1315 }});
1316
1317__PACKAGE__->register_method({
f9d26e09
TL
1318 name => 'update_dns',
1319 path => 'dns',
aff192e6
DM
1320 method => 'PUT',
1321 description => "Write DNS settings.",
d0289a19
DM
1322 permissions => {
1323 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
1324 },
aff192e6
DM
1325 proxyto => 'node',
1326 protected => 1,
1327 parameters => {
6110ed03 1328 additionalProperties => 0,
aff192e6
DM
1329 properties => {
1330 node => get_standard_option('pve-node'),
1331 search => {
1332 description => "Search domain for host-name lookup.",
1333 type => 'string',
1334 },
1335 dns1 => {
1336 description => 'First name server IP address.',
7a9486a7 1337 type => 'string', format => 'ip',
aff192e6 1338 optional => 1,
f9d26e09 1339 },
aff192e6
DM
1340 dns2 => {
1341 description => 'Second name server IP address.',
7a9486a7 1342 type => 'string', format => 'ip',
aff192e6 1343 optional => 1,
f9d26e09 1344 },
aff192e6
DM
1345 dns3 => {
1346 description => 'Third name server IP address.',
7a9486a7 1347 type => 'string', format => 'ip',
aff192e6 1348 optional => 1,
f9d26e09 1349 },
aff192e6
DM
1350 },
1351 },
1352 returns => { type => "null" },
1353 code => sub {
1354 my ($param) = @_;
1355
1356 PVE::INotify::update_file('resolvconf', $param);
1357
79d62026 1358 return;
aff192e6
DM
1359 }});
1360
1361__PACKAGE__->register_method({
f9d26e09
TL
1362 name => 'time',
1363 path => 'time',
aff192e6
DM
1364 method => 'GET',
1365 permissions => {
7d020b42
DM
1366 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1367 },
aff192e6
DM
1368 description => "Read server time and time zone settings.",
1369 proxyto => 'node',
1370 parameters => {
1371 additionalProperties => 0,
1372 properties => {
1373 node => get_standard_option('pve-node'),
1374 },
1375 },
1376 returns => {
1377 type => "object",
6110ed03 1378 additionalProperties => 0,
aff192e6
DM
1379 properties => {
1380 timezone => {
1381 description => "Time zone",
1382 type => 'string',
1383 },
1384 time => {
1385 description => "Seconds since 1970-01-01 00:00:00 UTC.",
1386 type => 'integer',
1387 minimum => 1297163644,
bed5fdfc 1388 renderer => 'timestamp',
aff192e6
DM
1389 },
1390 localtime => {
1391 description => "Seconds since 1970-01-01 00:00:00 (local time)",
1392 type => 'integer',
1393 minimum => 1297163644,
bed5fdfc 1394 renderer => 'timestamp_gmt',
aff192e6
DM
1395 },
1396 },
1397 },
1398 code => sub {
1399 my ($param) = @_;
1400
1401 my $ctime = time();
1402 my $ltime = timegm_nocheck(localtime($ctime));
1403 my $res = {
1404 timezone => PVE::INotify::read_file('timezone'),
bed5fdfc 1405 time => $ctime,
aff192e6
DM
1406 localtime => $ltime,
1407 };
1408
1409 return $res;
1410 }});
1411
1412__PACKAGE__->register_method({
f9d26e09
TL
1413 name => 'set_timezone',
1414 path => 'time',
aff192e6
DM
1415 method => 'PUT',
1416 description => "Set time zone.",
d0289a19
DM
1417 permissions => {
1418 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
1419 },
aff192e6
DM
1420 proxyto => 'node',
1421 protected => 1,
1422 parameters => {
6110ed03 1423 additionalProperties => 0,
aff192e6
DM
1424 properties => {
1425 node => get_standard_option('pve-node'),
1426 timezone => {
1427 description => "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
1428 type => 'string',
1429 },
1430 },
1431 },
1432 returns => { type => "null" },
1433 code => sub {
1434 my ($param) = @_;
1435
1436 PVE::INotify::write_file('timezone', $param->{timezone});
1437
79d62026 1438 return;
aff192e6
DM
1439 }});
1440
c9164975 1441__PACKAGE__->register_method({
f9d26e09
TL
1442 name => 'aplinfo',
1443 path => 'aplinfo',
c9164975
DM
1444 method => 'GET',
1445 permissions => {
1446 user => 'all',
1447 },
1448 description => "Get list of appliances.",
1449 proxyto => 'node',
1450 parameters => {
6110ed03 1451 additionalProperties => 0,
c9164975
DM
1452 properties => {
1453 node => get_standard_option('pve-node'),
1454 },
1455 },
1456 returns => {
1457 type => 'array',
1458 items => {
1459 type => "object",
1460 properties => {},
1461 },
1462 },
1463 code => sub {
1464 my ($param) = @_;
1465
c9164975
DM
1466 my $list = PVE::APLInfo::load_data();
1467
0d213945
TL
1468 my $res = [];
1469 for my $appliance (values %{$list->{all}}) {
1470 next if $appliance->{'package'} eq 'pve-web-news';
1471 push @$res, $appliance;
c9164975
DM
1472 }
1473
1474 return $res;
1475 }});
1476
0532bd28 1477__PACKAGE__->register_method({
f9d26e09
TL
1478 name => 'apl_download',
1479 path => 'aplinfo',
0532bd28
DM
1480 method => 'POST',
1481 permissions => {
1482 check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
1483 },
1484 description => "Download appliance templates.",
1485 proxyto => 'node',
1486 protected => 1,
1487 parameters => {
6110ed03 1488 additionalProperties => 0,
0532bd28
DM
1489 properties => {
1490 node => get_standard_option('pve-node'),
82282acf 1491 storage => get_standard_option('pve-storage-id', {
62180d0f 1492 description => "The storage where the template will be stored",
82282acf
WL
1493 completion => \&PVE::Storage::complete_storage_enabled,
1494 }),
6110ed03
TL
1495 template => {
1496 type => 'string',
1497 description => "The template which will downloaded",
1498 maxLength => 255,
1499 completion => \&complete_templet_repo,
82282acf 1500 },
0532bd28
DM
1501 },
1502 },
1503 returns => { type => "string" },
1504 code => sub {
1505 my ($param) = @_;
1506
1507 my $rpcenv = PVE::RPCEnvironment::get();
0532bd28 1508 my $user = $rpcenv->get_user();
0532bd28 1509
aee25c2e 1510 my $node = $param->{node};
0532bd28 1511 my $template = $param->{template};
0532bd28 1512
aee25c2e
TL
1513 my $list = PVE::APLInfo::load_data();
1514 my $appliance = $list->{all}->{$template};
1515 raise_param_exc({ template => "no such template"}) if !$appliance;
0532bd28 1516
bbcfdc08 1517 my $cfg = PVE::Storage::config();
0532bd28
DM
1518 my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node);
1519
aee25c2e
TL
1520 die "unknown template type '$appliance->{type}'\n"
1521 if !($appliance->{type} eq 'openvz' || $appliance->{type} eq 'lxc');
0532bd28 1522
f9d26e09 1523 die "storage '$param->{storage}' does not support templates\n"
0532bd28
DM
1524 if !$scfg->{content}->{vztmpl};
1525
0532bd28 1526 my $tmpldir = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
0532bd28 1527
fac5d57e 1528 my $worker = sub {
aee25c2e
TL
1529 my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
1530
1531 PVE::Tools::download_file_from_url("$tmpldir/$template", $appliance->{location}, {
1532 hash_required => 1,
1533 sha512sum => $appliance->{sha512sum},
1534 md5sum => $appliance->{md5sum},
1535 http_proxy => $dccfg->{http_proxy},
1536 });
fac5d57e 1537 };
0532bd28 1538
fac5d57e 1539 my $upid = $rpcenv->fork_worker('download', $template, $user, $worker);
0532bd28 1540
fac5d57e 1541 return $upid;
0532bd28
DM
1542 }});
1543
17711ff8
LS
1544__PACKAGE__->register_method({
1545 name => 'query_url_metadata',
1546 path => 'query-url-metadata',
1547 method => 'GET',
1548 description => "Query metadata of an URL: file size, file name and mime type.",
1549 proxyto => 'node',
1550 permissions => {
1551 check => ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]],
1552 },
1553 parameters => {
1554 additionalProperties => 0,
1555 properties => {
1556 node => get_standard_option('pve-node'),
1557 url => {
1558 description => "The URL to query the metadata from.",
1559 type => 'string',
1560 pattern => 'https?://.*',
1561 },
1562 'verify-certificates' => {
1563 description => "If false, no SSL/TLS certificates will be verified.",
1564 type => 'boolean',
1565 optional => 1,
1566 default => 1,
1567 }
1568 },
1569 },
1570 returns => {
1571 type => "object",
1572 properties => {
1573 filename => {
1574 type => 'string',
1575 optional => 1,
1576 },
1577 size => {
1578 type => 'integer',
1579 renderer => 'bytes',
1580 optional => 1,
1581 },
1582 mimetype => {
1583 type => 'string',
1584 optional => 1,
1585 },
1586 },
1587 },
1588 code => sub {
1589 my ($param) = @_;
1590
1591 my $url = $param->{url};
1592
1593 my $ua = LWP::UserAgent->new();
dfde1eec 1594 $ua->agent("Proxmox VE");
17711ff8
LS
1595
1596 my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
1597 if ($dccfg->{http_proxy}) {
1598 $ua->proxy('http', $dccfg->{http_proxy});
1599 }
1600
1601 my $verify = $param->{'verify-certificates'} // 1;
1602 if (!$verify) {
1603 $ua->ssl_opts(
1604 verify_hostname => 0,
1605 SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
1606 );
1607 }
1608
1609 my $req = HTTP::Request->new(HEAD => $url);
1610 my $res = $ua->request($req);
1611
1612 die "invalid server response: '" . $res->status_line() . "'\n" if ($res->code() != 200);
1613
1614 my $size = $res->header("Content-Length");
1615 my $disposition = $res->header("Content-Disposition");
1616 my $type = $res->header("Content-Type");
1617
1618 my $filename;
1619
1620 if ($disposition && ($disposition =~ m/filename="([^"]*)"/ || $disposition =~ m/filename=([^;]*)/)) {
1621 $filename = $1;
1622 } elsif ($url =~ m!^[^?]+/([^?/]*)(?:\?.*)?$!) {
1623 $filename = $1;
1624 }
1625
1626 # Content-Type: text/html; charset=utf-8
1627 if ($type && $type =~ m/^([^;]+);/) {
1628 $type = $1;
1629 }
1630
1631 my $ret = {};
1632 $ret->{filename} = $filename if $filename;
1633 $ret->{size} = $size + 0 if $size;
1634 $ret->{mimetype} = $type if $type;
1635
1636 return $ret;
1637 }});
1638
34ada77a
EK
1639__PACKAGE__->register_method({
1640 name => 'report',
1641 path => 'report',
1642 method => 'GET',
1643 permissions => {
1644 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1645 },
6bbdf981 1646 protected => 1,
34ada77a
EK
1647 description => "Gather various systems information about a node",
1648 proxyto => 'node',
1649 parameters => {
6110ed03 1650 additionalProperties => 0,
34ada77a
EK
1651 properties => {
1652 node => get_standard_option('pve-node'),
1653 },
1654 },
1655 returns => {
cdb5a209 1656 type => 'string',
34ada77a
EK
1657 },
1658 code => sub {
cdb5a209 1659 return PVE::Report::generate();
34ada77a 1660 }});
0532bd28 1661
b72cdbb7
TL
1662# returns a list of VMIDs, those can be filtered by
1663# * current parent node
1664# * vmid whitelist
1665# * guest is a template (default: skip)
1666# * guest is HA manged (default: skip)
1667my $get_filtered_vmlist = sub {
1668 my ($nodename, $vmfilter, $templates, $ha_managed) = @_;
b92400b6 1669
b92400b6
DM
1670 my $vmlist = PVE::Cluster::get_vmlist();
1671
dbfcac27 1672 my $vms_allowed;
2a498506 1673 if (defined($vmfilter)) {
dbfcac27 1674 $vms_allowed = { map { $_ => 1 } PVE::Tools::split_list($vmfilter) };
2a498506
DC
1675 }
1676
b72cdbb7 1677 my $res = {};
b92400b6 1678 foreach my $vmid (keys %{$vmlist->{ids}}) {
dbfcac27 1679 next if defined($vms_allowed) && !$vms_allowed->{$vmid};
b92400b6 1680
b72cdbb7
TL
1681 my $d = $vmlist->{ids}->{$vmid};
1682 next if $nodename && $d->{node} ne $nodename;
b92400b6 1683
b72cdbb7
TL
1684 eval {
1685 my $class;
2c27e4b7 1686 if ($d->{type} eq 'lxc') {
b72cdbb7 1687 $class = 'PVE::LXC::Config';
2c27e4b7 1688 } elsif ($d->{type} eq 'qemu') {
b72cdbb7 1689 $class = 'PVE::QemuConfig';
b92400b6 1690 } else {
dbfcac27 1691 die "unknown virtual guest type '$d->{type}'\n";
b92400b6
DM
1692 }
1693
b72cdbb7
TL
1694 my $conf = $class->load_config($vmid);
1695 return if !$templates && $class->is_template($conf);
1696 return if !$ha_managed && PVE::HA::Config::vm_is_ha_managed($vmid);
04b2004b 1697
b2bb6d77 1698 $res->{$vmid}->{conf} = $conf;
b72cdbb7 1699 $res->{$vmid}->{type} = $d->{type};
1c8dc310 1700 $res->{$vmid}->{class} = $class;
b92400b6
DM
1701 };
1702 warn $@ if $@;
1703 }
1704
b72cdbb7
TL
1705 return $res;
1706};
1707
1708# return all VMs which should get started/stopped on power up/down
1709my $get_start_stop_list = sub {
1710 my ($nodename, $autostart, $vmfilter) = @_;
1711
ae9d10ca
TL
1712 # do not skip HA vms on force or if a specific VMID set is wanted
1713 my $include_ha_managed = defined($vmfilter) ? 1 : 0;
1714
dbfcac27 1715 my $vmlist = $get_filtered_vmlist->($nodename, $vmfilter, undef, $include_ha_managed);
b72cdbb7
TL
1716
1717 my $resList = {};
1718 foreach my $vmid (keys %$vmlist) {
b2bb6d77 1719 my $conf = $vmlist->{$vmid}->{conf};
b72cdbb7
TL
1720 next if $autostart && !$conf->{onboot};
1721
dbfcac27
TL
1722 my $startup = $conf->{startup} ? PVE::JSONSchema::pve_parse_startup_order($conf->{startup}) : {};
1723 my $order = $startup->{order} = $startup->{order} // LONG_MAX;
b72cdbb7 1724
dbfcac27
TL
1725 $resList->{$order}->{$vmid} = $startup;
1726 $resList->{$order}->{$vmid}->{type} = $vmlist->{$vmid}->{type};
b72cdbb7
TL
1727 }
1728
b92400b6
DM
1729 return $resList;
1730};
1731
1c8dc310
TL
1732my $remove_locks_on_startup = sub {
1733 my ($nodename) = @_;
1734
1735 my $vmlist = &$get_filtered_vmlist($nodename, undef, undef, 1);
1736
1737 foreach my $vmid (keys %$vmlist) {
1738 my $conf = $vmlist->{$vmid}->{conf};
23b54109 1739 my $class = $vmlist->{$vmid}->{class};
1c8dc310
TL
1740
1741 eval {
1742 if ($class->has_lock($conf, 'backup')) {
1743 $class->remove_lock($vmid, 'backup');
1744 my $msg = "removed left over backup lock from '$vmid'!";
1745 warn "$msg\n"; # prints to task log
1746 syslog('warning', $msg);
1747 }
1748 }; warn $@ if $@;
1749 }
1750};
1751
b92400b6 1752__PACKAGE__->register_method ({
f9d26e09
TL
1753 name => 'startall',
1754 path => 'startall',
b92400b6
DM
1755 method => 'POST',
1756 protected => 1,
17e3b3b2 1757 permissions => {
6aec4565
FE
1758 description => "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/<ID>' for "
1759 ."each ID passed via the 'vms' parameter.",
1760 user => 'all',
17e3b3b2 1761 },
9c8a09a7 1762 proxyto => 'node',
e1b57809 1763 description => "Start all VMs and containers located on this node (by default only those with onboot=1).",
b92400b6 1764 parameters => {
6110ed03 1765 additionalProperties => 0,
b92400b6
DM
1766 properties => {
1767 node => get_standard_option('pve-node'),
c09c7160
AD
1768 force => {
1769 optional => 1,
1770 type => 'boolean',
e1b57809
TL
1771 default => 'off',
1772 description => "Issue start command even if virtual guest have 'onboot' not set or set to off.",
c09c7160 1773 },
2a498506 1774 vms => {
a740deff 1775 description => "Only consider guests from this comma separated list of VMIDs.",
2a498506
DC
1776 type => 'string', format => 'pve-vmid-list',
1777 optional => 1,
1778 },
b92400b6
DM
1779 },
1780 },
1781 returns => {
1782 type => 'string',
1783 },
1784 code => sub {
1785 my ($param) = @_;
1786
1787 my $rpcenv = PVE::RPCEnvironment::get();
1788 my $authuser = $rpcenv->get_user();
1789
6aec4565
FE
1790 if (!$rpcenv->check($authuser, "/", [ 'VM.PowerMgmt' ], 1)) {
1791 my @vms = PVE::Tools::split_list($param->{vms});
1792 if (scalar(@vms) > 0) {
1793 $rpcenv->check($authuser, "/vms/$_", [ 'VM.PowerMgmt' ]) for @vms;
1794 } else {
1795 raise_perm_exc("/, VM.PowerMgmt");
1796 }
1797 }
1798
b92400b6
DM
1799 my $nodename = $param->{node};
1800 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
1801
c09c7160
AD
1802 my $force = $param->{force};
1803
b92400b6 1804 my $code = sub {
b92400b6
DM
1805 $rpcenv->{type} = 'priv'; # to start tasks in background
1806
3947d0a0
DM
1807 if (!PVE::Cluster::check_cfs_quorum(1)) {
1808 print "waiting for quorum ...\n";
1809 do {
1810 sleep(1);
1811 } while (!PVE::Cluster::check_cfs_quorum(1));
1812 print "got quorum\n";
b92400b6 1813 }
1c8dc310
TL
1814
1815 eval { # remove backup locks, but avoid running into a scheduled backup job
1816 PVE::Tools::lock_file('/var/run/vzdump.lock', 10, $remove_locks_on_startup, $nodename);
f77faab2
TL
1817 };
1818 warn $@ if $@;
1c8dc310 1819
c09c7160 1820 my $autostart = $force ? undef : 1;
f77faab2 1821 my $startList = $get_start_stop_list->($nodename, $autostart, $param->{vms});
3f12bcfb
DM
1822
1823 # Note: use numeric sorting with <=>
f77faab2 1824 for my $order (sort {$a <=> $b} keys %$startList) {
b92400b6
DM
1825 my $vmlist = $startList->{$order};
1826
f77faab2 1827 for my $vmid (sort {$a <=> $b} keys %$vmlist) {
b92400b6
DM
1828 my $d = $vmlist->{$vmid};
1829
1830 PVE::Cluster::check_cfs_quorum(); # abort when we loose quorum
b72cdbb7 1831
b92400b6
DM
1832 eval {
1833 my $default_delay = 0;
1834 my $upid;
1835
2c27e4b7
DM
1836 if ($d->{type} eq 'lxc') {
1837 return if PVE::LXC::check_running($vmid);
1838 print STDERR "Starting CT $vmid\n";
1b5e56f2 1839 $upid = PVE::API2::LXC::Status->vm_start({node => $nodename, vmid => $vmid });
2c27e4b7 1840 } elsif ($d->{type} eq 'qemu') {
d6c49392 1841 $default_delay = 3; # to reduce load
b92400b6
DM
1842 return if PVE::QemuServer::check_running($vmid, 1);
1843 print STDERR "Starting VM $vmid\n";
1844 $upid = PVE::API2::Qemu->vm_start({node => $nodename, vmid => $vmid });
1845 } else {
1846 die "unknown VM type '$d->{type}'\n";
1847 }
1848
f77faab2
TL
1849 my $task = PVE::Tools::upid_decode($upid);
1850 while (PVE::ProcFSTools::check_process_running($task->{pid})) {
b92400b6
DM
1851 sleep(1);
1852 }
1853
1854 my $status = PVE::Tools::upid_read_status($upid);
0a7de337 1855 if (!PVE::Tools::upid_status_is_error($status)) {
b92400b6
DM
1856 # use default delay to reduce load
1857 my $delay = defined($d->{up}) ? int($d->{up}) : $default_delay;
1858 if ($delay > 0) {
1859 print STDERR "Waiting for $delay seconds (startup delay)\n" if $d->{up};
1860 for (my $i = 0; $i < $delay; $i++) {
1861 sleep(1);
1862 }
1863 }
1864 } else {
b27ebcad
TL
1865 my $rendered_type = $d->{type} eq 'lxc' ? 'CT' : 'VM';
1866 print STDERR "Starting $rendered_type $vmid failed: $status\n";
b92400b6
DM
1867 }
1868 };
1869 warn $@ if $@;
1870 }
1871 }
1872 return;
1873 };
1874
1875 return $rpcenv->fork_worker('startall', undef, $authuser, $code);
1876 }});
1877
1878my $create_stop_worker = sub {
11543293 1879 my ($nodename, $type, $vmid, $timeout, $force_stop) = @_;
b92400b6 1880
2c27e4b7
DM
1881 if ($type eq 'lxc') {
1882 return if !PVE::LXC::check_running($vmid);
2c27e4b7 1883 print STDERR "Stopping CT $vmid (timeout = $timeout seconds)\n";
79d62026 1884 return PVE::API2::LXC::Status->vm_shutdown(
11543293 1885 { node => $nodename, vmid => $vmid, timeout => $timeout, forceStop => $force_stop }
79d62026 1886 );
2c27e4b7 1887 } elsif ($type eq 'qemu') {
b92400b6 1888 return if !PVE::QemuServer::check_running($vmid, 1);
88ba9a1d 1889 print STDERR "Stopping VM $vmid (timeout = $timeout seconds)\n";
79d62026 1890 return PVE::API2::Qemu->vm_shutdown(
11543293 1891 { node => $nodename, vmid => $vmid, timeout => $timeout, forceStop => $force_stop }
79d62026 1892 );
b92400b6
DM
1893 } else {
1894 die "unknown VM type '$type'\n";
1895 }
b92400b6
DM
1896};
1897
1898__PACKAGE__->register_method ({
f9d26e09
TL
1899 name => 'stopall',
1900 path => 'stopall',
b92400b6
DM
1901 method => 'POST',
1902 protected => 1,
17e3b3b2 1903 permissions => {
6aec4565
FE
1904 description => "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/<ID>' for "
1905 ."each ID passed via the 'vms' parameter.",
1906 user => 'all',
17e3b3b2 1907 },
9c8a09a7 1908 proxyto => 'node',
b92400b6
DM
1909 description => "Stop all VMs and Containers.",
1910 parameters => {
6110ed03 1911 additionalProperties => 0,
b92400b6
DM
1912 properties => {
1913 node => get_standard_option('pve-node'),
2a498506
DC
1914 vms => {
1915 description => "Only consider Guests with these IDs.",
1916 type => 'string', format => 'pve-vmid-list',
1917 optional => 1,
1918 },
11543293
TL
1919 'force-stop' => {
1920 description => 'Force a hard-stop after the timeout.',
1921 type => 'boolean',
1922 default => 1,
1923 optional => 1,
1924 },
8f063fbc 1925 'timeout' => {
853ce1ea
TL
1926 description => 'Timeout for each guest shutdown task. Depending on `force-stop`,'
1927 .' the shutdown gets then simply aborted or a hard-stop is forced.',
8f063fbc
TL
1928 type => 'integer',
1929 optional => 1,
1930 default => 180,
853ce1ea
TL
1931 minimum => 0,
1932 maximum => 2 * 3600, # mostly arbitrary, but we do not want to high timeouts
8f063fbc 1933 },
b92400b6
DM
1934 },
1935 },
1936 returns => {
1937 type => 'string',
1938 },
1939 code => sub {
1940 my ($param) = @_;
1941
1942 my $rpcenv = PVE::RPCEnvironment::get();
1943 my $authuser = $rpcenv->get_user();
1944
6aec4565
FE
1945 if (!$rpcenv->check($authuser, "/", [ 'VM.PowerMgmt' ], 1)) {
1946 my @vms = PVE::Tools::split_list($param->{vms});
1947 if (scalar(@vms) > 0) {
1948 $rpcenv->check($authuser, "/vms/$_", [ 'VM.PowerMgmt' ]) for @vms;
1949 } else {
1950 raise_perm_exc("/, VM.PowerMgmt");
1951 }
1952 }
1953
b92400b6
DM
1954 my $nodename = $param->{node};
1955 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
1956
1957 my $code = sub {
1958
1959 $rpcenv->{type} = 'priv'; # to start tasks in background
1960
f77faab2 1961 my $stopList = $get_start_stop_list->($nodename, undef, $param->{vms});
b92400b6
DM
1962
1963 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
89ceb802
TL
1964 my $datacenterconfig = cfs_read_file('datacenter.cfg');
1965 # if not set by user spawn max cpu count number of workers
1966 my $maxWorkers = $datacenterconfig->{max_workers} || $cpuinfo->{cpus};
b92400b6 1967
f77faab2 1968 for my $order (sort {$b <=> $a} keys %$stopList) {
b92400b6
DM
1969 my $vmlist = $stopList->{$order};
1970 my $workers = {};
37bec895
DM
1971
1972 my $finish_worker = sub {
1973 my $pid = shift;
f77faab2 1974 my $worker = delete $workers->{$pid} || return;
37bec895 1975
f77faab2 1976 syslog('info', "end task $worker->{upid}");
37bec895
DM
1977 };
1978
f77faab2 1979 for my $vmid (sort {$b <=> $a} keys %$vmlist) {
b92400b6 1980 my $d = $vmlist->{$vmid};
8f063fbc 1981 my $timeout = int($d->{down} // $param->{timeout} // 180);
11543293
TL
1982 my $upid = eval {
1983 $create_stop_worker->(
1984 $nodename, $d->{type}, $vmid, $timeout, $param->{'force-stop'} // 1)
1985 };
b92400b6 1986 warn $@ if $@;
37bec895
DM
1987 next if !$upid;
1988
f77faab2
TL
1989 my $task = PVE::Tools::upid_decode($upid, 1);
1990 next if !$task;
37bec895 1991
f77faab2 1992 my $pid = $task->{pid};
37bec895
DM
1993
1994 $workers->{$pid} = { type => $d->{type}, upid => $upid, vmid => $vmid };
b92400b6
DM
1995 while (scalar(keys %$workers) >= $maxWorkers) {
1996 foreach my $p (keys %$workers) {
1997 if (!PVE::ProcFSTools::check_process_running($p)) {
f77faab2 1998 $finish_worker->($p);
b92400b6
DM
1999 }
2000 }
2001 sleep(1);
2002 }
2003 }
2004 while (scalar(keys %$workers)) {
f77faab2 2005 for my $p (keys %$workers) {
b92400b6 2006 if (!PVE::ProcFSTools::check_process_running($p)) {
f77faab2 2007 $finish_worker->($p);
b92400b6
DM
2008 }
2009 }
2010 sleep(1);
2011 }
2012 }
37bec895
DM
2013
2014 syslog('info', "all VMs and CTs stopped");
2015
b92400b6
DM
2016 return;
2017 };
2018
2019 return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
b92400b6
DM
2020 }});
2021
9c8a09a7 2022my $create_migrate_worker = sub {
fc6b77a1 2023 my ($nodename, $type, $vmid, $target, $with_local_disks) = @_;
9c8a09a7
AD
2024
2025 my $upid;
2c27e4b7
DM
2026 if ($type eq 'lxc') {
2027 my $online = PVE::LXC::check_running($vmid) ? 1 : 0;
2028 print STDERR "Migrating CT $vmid\n";
f77faab2
TL
2029 $upid = PVE::API2::LXC->migrate_vm(
2030 { node => $nodename, vmid => $vmid, target => $target, restart => $online });
2c27e4b7 2031 } elsif ($type eq 'qemu') {
0b54f653
TL
2032 print STDERR "Check VM $vmid: ";
2033 *STDERR->flush();
9c8a09a7 2034 my $online = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
f77faab2
TL
2035 my $preconditions = PVE::API2::Qemu->migrate_vm_precondition(
2036 {node => $nodename, vmid => $vmid, target => $target});
0b54f653
TL
2037 my $invalidConditions = '';
2038 if ($online && !$with_local_disks && scalar @{$preconditions->{local_disks}}) {
f77faab2
TL
2039 $invalidConditions .= "\n Has local disks: ";
2040 $invalidConditions .= join(', ', map { $_->{volid} } @{$preconditions->{local_disks}});
d8d17271
TM
2041 }
2042
2043 if (@{$preconditions->{local_resources}}) {
f77faab2
TL
2044 $invalidConditions .= "\n Has local resources: ";
2045 $invalidConditions .= join(', ', @{$preconditions->{local_resources}});
d8d17271
TM
2046 }
2047
0b54f653
TL
2048 if ($invalidConditions && $invalidConditions ne '') {
2049 print STDERR "skip VM $vmid - precondition check failed:";
2050 die "$invalidConditions\n";
2051 }
2052 print STDERR "precondition check passed\n";
9c8a09a7 2053 print STDERR "Migrating VM $vmid\n";
71fd3de9
TL
2054
2055 my $params = {
6f3d18dd
TL
2056 node => $nodename,
2057 vmid => $vmid,
2058 target => $target,
2059 online => $online,
71fd3de9 2060 };
13411f99 2061 $params->{'with-local-disks'} = $with_local_disks if defined($with_local_disks);
71fd3de9
TL
2062
2063 $upid = PVE::API2::Qemu->migrate_vm($params);
9c8a09a7
AD
2064 } else {
2065 die "unknown VM type '$type'\n";
2066 }
2067
f77faab2 2068 my $task = PVE::Tools::upid_decode($upid);
9c8a09a7 2069
f77faab2 2070 return $task->{pid};
9c8a09a7
AD
2071};
2072
2073__PACKAGE__->register_method ({
2074 name => 'migrateall',
2075 path => 'migrateall',
2076 method => 'POST',
2077 proxyto => 'node',
2078 protected => 1,
17e3b3b2 2079 permissions => {
6aec4565
FE
2080 description => "The 'VM.Migrate' permission is required on '/' or on '/vms/<ID>' for each "
2081 ."ID passed via the 'vms' parameter.",
2082 user => 'all',
17e3b3b2 2083 },
9c8a09a7
AD
2084 description => "Migrate all VMs and Containers.",
2085 parameters => {
2086 additionalProperties => 0,
2087 properties => {
2088 node => get_standard_option('pve-node'),
2089 target => get_standard_option('pve-node', { description => "Target node." }),
f77faab2
TL
2090 maxworkers => {
2091 description => "Maximal number of parallel migration job. If not set, uses"
2092 ."'max_workers' from datacenter.cfg. One of both must be set!",
89ceb802 2093 optional => 1,
f77faab2
TL
2094 type => 'integer',
2095 minimum => 1
9c8a09a7 2096 },
2a498506
DC
2097 vms => {
2098 description => "Only consider Guests with these IDs.",
2099 type => 'string', format => 'pve-vmid-list',
2100 optional => 1,
2101 },
fc6b77a1
TM
2102 "with-local-disks" => {
2103 type => 'boolean',
2104 description => "Enable live storage migration for local disk",
2105 optional => 1,
2106 },
9c8a09a7
AD
2107 },
2108 },
2109 returns => {
2110 type => 'string',
2111 },
2112 code => sub {
2113 my ($param) = @_;
2114
2115 my $rpcenv = PVE::RPCEnvironment::get();
2116 my $authuser = $rpcenv->get_user();
2117
6aec4565
FE
2118 if (!$rpcenv->check($authuser, "/", [ 'VM.Migrate' ], 1)) {
2119 my @vms = PVE::Tools::split_list($param->{vms});
2120 if (scalar(@vms) > 0) {
2121 $rpcenv->check($authuser, "/vms/$_", [ 'VM.Migrate' ]) for @vms;
2122 } else {
2123 raise_perm_exc("/, VM.Migrate");
2124 }
2125 }
2126
9c8a09a7
AD
2127 my $nodename = $param->{node};
2128 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
2129
73981e39 2130 my $target = $param->{target};
fc6b77a1 2131 my $with_local_disks = $param->{'with-local-disks'};
73981e39
DC
2132 raise_param_exc({ target => "target is local node."}) if $target eq $nodename;
2133
2134 PVE::Cluster::check_cfs_quorum();
2135
2136 PVE::Cluster::check_node_exists($target);
89ceb802
TL
2137
2138 my $datacenterconfig = cfs_read_file('datacenter.cfg');
2139 # prefer parameter over datacenter cfg settings
2140 my $maxWorkers = $param->{maxworkers} || $datacenterconfig->{max_workers} ||
2141 die "either 'maxworkers' parameter or max_workers in datacenter.cfg must be set!\n";
9c8a09a7
AD
2142
2143 my $code = sub {
9c8a09a7
AD
2144 $rpcenv->{type} = 'priv'; # to start tasks in background
2145
f5c1dde5 2146 my $vmlist = &$get_filtered_vmlist($nodename, $param->{vms}, 1, 1);
49652c46
TL
2147 if (!scalar(keys %$vmlist)) {
2148 warn "no virtual guests matched, nothing to do..\n";
2149 return;
2150 }
9c8a09a7 2151
f5c1dde5 2152 my $workers = {};
49652c46 2153 my $workers_started = 0;
f5c1dde5
TL
2154 foreach my $vmid (sort keys %$vmlist) {
2155 my $d = $vmlist->{$vmid};
2156 my $pid;
fc6b77a1 2157 eval { $pid = &$create_migrate_worker($nodename, $d->{type}, $vmid, $target, $with_local_disks); };
f5c1dde5
TL
2158 warn $@ if $@;
2159 next if !$pid;
9c8a09a7 2160
49652c46 2161 $workers_started++;
f5c1dde5
TL
2162 $workers->{$pid} = 1;
2163 while (scalar(keys %$workers) >= $maxWorkers) {
9c8a09a7
AD
2164 foreach my $p (keys %$workers) {
2165 if (!PVE::ProcFSTools::check_process_running($p)) {
2166 delete $workers->{$p};
2167 }
2168 }
2169 sleep(1);
2170 }
2171 }
f5c1dde5
TL
2172 while (scalar(keys %$workers)) {
2173 foreach my $p (keys %$workers) {
8e4bee65 2174 # FIXME: what about PID re-use ?!?!
f5c1dde5
TL
2175 if (!PVE::ProcFSTools::check_process_running($p)) {
2176 delete $workers->{$p};
2177 }
2178 }
2179 sleep(1);
2180 }
49652c46
TL
2181 if ($workers_started <= 0) {
2182 die "no migrations worker started...\n";
2183 }
2184 print STDERR "All jobs finished, used $workers_started workers in total.\n";
9c8a09a7
AD
2185 return;
2186 };
2187
2188 return $rpcenv->fork_worker('migrateall', undef, $authuser, $code);
2189
2190 }});
2191
820d0458
DC
2192__PACKAGE__->register_method ({
2193 name => 'get_etc_hosts',
2194 path => 'hosts',
2195 method => 'GET',
2196 proxyto => 'node',
2197 protected => 1,
2198 permissions => {
2199 check => ['perm', '/', [ 'Sys.Audit' ]],
2200 },
2201 description => "Get the content of /etc/hosts.",
2202 parameters => {
2203 additionalProperties => 0,
2204 properties => {
2205 node => get_standard_option('pve-node'),
2206 },
2207 },
2208 returns => {
2209 type => 'object',
2210 properties => {
2211 digest => get_standard_option('pve-config-digest'),
2212 data => {
2213 type => 'string',
2214 description => 'The content of /etc/hosts.'
2215 },
2216 },
2217 },
2218 code => sub {
2219 my ($param) = @_;
2220
2221 return PVE::INotify::read_file('etchosts');
2222
2223 }});
2224
2225__PACKAGE__->register_method ({
2226 name => 'write_etc_hosts',
2227 path => 'hosts',
2228 method => 'POST',
2229 proxyto => 'node',
2230 protected => 1,
2231 permissions => {
2232 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
2233 },
2234 description => "Write /etc/hosts.",
2235 parameters => {
2236 additionalProperties => 0,
2237 properties => {
2238 node => get_standard_option('pve-node'),
2239 digest => get_standard_option('pve-config-digest'),
2240 data => {
2241 type => 'string',
2242 description => 'The target content of /etc/hosts.'
2243 },
2244 },
2245 },
2246 returns => {
2247 type => 'null',
2248 },
2249 code => sub {
2250 my ($param) = @_;
2251
79d62026 2252 PVE::Tools::lock_file('/var/lock/pve-etchosts.lck', undef, sub {
820d0458
DC
2253 if ($param->{digest}) {
2254 my $hosts = PVE::INotify::read_file('etchosts');
2255 PVE::Tools::assert_if_modified($hosts->{digest}, $param->{digest});
2256 }
2257 PVE::INotify::write_file('etchosts', $param->{data});
2258 });
2259 die $@ if $@;
2260
79d62026 2261 return;
820d0458
DC
2262 }});
2263
82282acf
WL
2264# bash completion helper
2265
2266sub complete_templet_repo {
2267 my ($cmdname, $pname, $cvalue) = @_;
2268
2269 my $repo = PVE::APLInfo::load_data();
2270 my $res = [];
2271 foreach my $templ (keys %{$repo->{all}}) {
2272 next if $templ !~ m/^$cvalue/;
2273 push @$res, $templ;
2274 }
2275
2276 return $res;
2277}
2278
aff192e6
DM
2279package PVE::API2::Nodes;
2280
2281use strict;
2282use warnings;
2283
2284use PVE::SafeSyslog;
2285use PVE::Cluster;
2286use PVE::RESTHandler;
2287use PVE::RPCEnvironment;
b193e4ac 2288use PVE::API2Tools;
f57cbe5d 2289use PVE::JSONSchema qw(get_standard_option);
aff192e6
DM
2290
2291use base qw(PVE::RESTHandler);
2292
2293__PACKAGE__->register_method ({
f9d26e09 2294 subclass => "PVE::API2::Nodes::Nodeinfo",
aff192e6
DM
2295 path => '{node}',
2296});
2297
2298__PACKAGE__->register_method ({
f9d26e09
TL
2299 name => 'index',
2300 path => '',
aff192e6
DM
2301 method => 'GET',
2302 permissions => { user => 'all' },
2303 description => "Cluster node index.",
2304 parameters => {
6110ed03 2305 additionalProperties => 0,
aff192e6
DM
2306 properties => {},
2307 },
2308 returns => {
2309 type => 'array',
2310 items => {
2311 type => "object",
f57cbe5d
DM
2312 properties => {
2313 node => get_standard_option('pve-node'),
2314 status => {
2315 description => "Node status.",
2316 type => 'string',
2317 enum => ['unknown', 'online', 'offline'],
2318 },
2319 cpu => {
2320 description => "CPU utilization.",
2321 type => 'number',
2322 optional => 1,
2323 renderer => 'fraction_as_percentage',
2324 },
2325 maxcpu => {
2326 description => "Number of available CPUs.",
2327 type => 'integer',
2328 optional => 1,
2329 },
2330 mem => {
2331 description => "Used memory in bytes.",
4a512d7a 2332 type => 'integer',
f57cbe5d
DM
2333 optional => 1,
2334 renderer => 'bytes',
2335 },
2336 maxmem => {
2337 description => "Number of available memory in bytes.",
2338 type => 'integer',
2339 optional => 1,
2340 renderer => 'bytes',
2341 },
2342 level => {
2343 description => "Support level.",
2344 type => 'string',
2345 optional => 1,
2346 },
2347 uptime => {
2348 description => "Node uptime in seconds.",
2349 type => 'integer',
2350 optional => 1,
2351 renderer => 'duration',
2352 },
2353 ssl_fingerprint => {
2354 description => "The SSL fingerprint for the node certificate.",
2355 type => 'string',
2356 optional => 1,
2357 },
2358 },
aff192e6 2359 },
b193e4ac 2360 links => [ { rel => 'child', href => "{node}" } ],
aff192e6
DM
2361 },
2362 code => sub {
2363 my ($param) = @_;
f9d26e09 2364
57d56896
TL
2365 my $rpcenv = PVE::RPCEnvironment::get();
2366 my $authuser = $rpcenv->get_user();
2367
aff192e6
DM
2368 my $clinfo = PVE::Cluster::get_clinfo();
2369 my $res = [];
2370
b193e4ac
DM
2371 my $nodelist = PVE::Cluster::get_nodelist();
2372 my $members = PVE::Cluster::get_members();
aff192e6
DM
2373 my $rrd = PVE::Cluster::rrd_dump();
2374
b193e4ac 2375 foreach my $node (@$nodelist) {
57d56896
TL
2376 my $can_audit = $rpcenv->check($authuser, "/nodes/$node", [ 'Sys.Audit' ], 1);
2377 my $entry = PVE::API2Tools::extract_node_stats($node, $members, $rrd, !$can_audit);
1a664b0f
FG
2378
2379 $entry->{ssl_fingerprint} = eval { PVE::Cluster::get_node_fingerprint($node) };
2380 warn "$@" if $@;
2381
aff192e6
DM
2382 push @$res, $entry;
2383 }
2384
2385 return $res;
2386 }});
2387
23881;