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