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