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