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