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