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