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