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