]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Nodes.pm
Use methods from PVE::QemuConfig
[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::Storage::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 JSON;
44
45 use base qw(PVE::RESTHandler);
46
47 __PACKAGE__->register_method ({
48 subclass => "PVE::API2::Qemu",
49 path => 'qemu',
50 });
51
52 __PACKAGE__->register_method ({
53 subclass => "PVE::API2::LXC",
54 path => 'lxc',
55 });
56
57 __PACKAGE__->register_method ({
58 subclass => "PVE::API2::Ceph",
59 path => 'ceph',
60 });
61
62 __PACKAGE__->register_method ({
63 subclass => "PVE::API2::VZDump",
64 path => 'vzdump',
65 });
66
67 __PACKAGE__->register_method ({
68 subclass => "PVE::API2::Services",
69 path => 'services',
70 });
71
72 __PACKAGE__->register_method ({
73 subclass => "PVE::API2::Subscription",
74 path => 'subscription',
75 });
76
77 __PACKAGE__->register_method ({
78 subclass => "PVE::API2::Network",
79 path => 'network',
80 });
81
82 __PACKAGE__->register_method ({
83 subclass => "PVE::API2::Tasks",
84 path => 'tasks',
85 });
86
87 __PACKAGE__->register_method ({
88 subclass => "PVE::API2::Storage::Scan",
89 path => 'scan',
90 });
91
92 __PACKAGE__->register_method ({
93 subclass => "PVE::API2::Storage::Status",
94 path => 'storage',
95 });
96
97 __PACKAGE__->register_method ({
98 subclass => "PVE::API2::APT",
99 path => 'apt',
100 });
101
102 __PACKAGE__->register_method ({
103 subclass => "PVE::API2::Firewall::Host",
104 path => 'firewall',
105 });
106
107 __PACKAGE__->register_method ({
108 name => 'index',
109 path => '',
110 method => 'GET',
111 permissions => { user => 'all' },
112 description => "Node index.",
113 parameters => {
114 additionalProperties => 0,
115 properties => {
116 node => get_standard_option('pve-node'),
117 },
118 },
119 returns => {
120 type => 'array',
121 items => {
122 type => "object",
123 properties => {},
124 },
125 links => [ { rel => 'child', href => "{name}" } ],
126 },
127 code => sub {
128 my ($param) = @_;
129
130 my $result = [
131 { name => 'ceph' },
132 { name => 'apt' },
133 { name => 'version' },
134 { name => 'syslog' },
135 { name => 'status' },
136 { name => 'subscription' },
137 { name => 'report' },
138 { name => 'tasks' },
139 { name => 'rrd' }, # fixme: remove?
140 { name => 'rrddata' },# fixme: remove?
141 { name => 'vncshell' },
142 { name => 'spiceshell' },
143 { name => 'time' },
144 { name => 'dns' },
145 { name => 'services' },
146 { name => 'scan' },
147 { name => 'storage' },
148 { name => 'qemu' },
149 { name => 'lxc' },
150 { name => 'vzdump' },
151 { name => 'network' },
152 { name => 'aplinfo' },
153 { name => 'startall' },
154 { name => 'stopall' },
155 { name => 'netstat' },
156 { name => 'firewall' },
157 ];
158
159 return $result;
160 }});
161
162 __PACKAGE__->register_method ({
163 name => 'version',
164 path => 'version',
165 method => 'GET',
166 proxyto => 'node',
167 permissions => { user => 'all' },
168 description => "API version details",
169 parameters => {
170 additionalProperties => 0,
171 properties => {
172 node => get_standard_option('pve-node'),
173 },
174 },
175 returns => {
176 type => "object",
177 properties => {
178 version => { type => 'string' },
179 release => { type => 'string' },
180 repoid => { type => 'string' },
181 },
182 },
183 code => sub {
184 my ($resp, $param) = @_;
185
186 return PVE::pvecfg::version_info();
187 }});
188
189 __PACKAGE__->register_method({
190 name => 'status',
191 path => 'status',
192 method => 'GET',
193 permissions => {
194 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
195 },
196 description => "Read node status",
197 proxyto => 'node',
198 parameters => {
199 additionalProperties => 0,
200 properties => {
201 node => get_standard_option('pve-node'),
202 },
203 },
204 returns => {
205 type => "object",
206 properties => {
207
208 },
209 },
210 code => sub {
211 my ($param) = @_;
212
213 my $res = {
214 uptime => 0,
215 idle => 0,
216 };
217
218 my ($uptime, $idle) = PVE::ProcFSTools::read_proc_uptime();
219 $res->{uptime} = $uptime;
220
221 my ($avg1, $avg5, $avg15) = PVE::ProcFSTools::read_loadavg();
222 $res->{loadavg} = [ $avg1, $avg5, $avg15];
223
224 my ($sysname, $nodename, $release, $version, $machine) = POSIX::uname();
225
226 $res->{kversion} = "$sysname $release $version";
227
228 $res->{cpuinfo} = PVE::ProcFSTools::read_cpuinfo();
229
230 my $stat = PVE::ProcFSTools::read_proc_stat();
231 $res->{cpu} = $stat->{cpu};
232 $res->{wait} = $stat->{wait};
233
234 my $meminfo = PVE::ProcFSTools::read_meminfo();
235 $res->{memory} = {
236 free => $meminfo->{memfree},
237 total => $meminfo->{memtotal},
238 used => $meminfo->{memused},
239 };
240
241 $res->{ksm} = {
242 shared => $meminfo->{memshared},
243 };
244
245 $res->{swap} = {
246 free => $meminfo->{swapfree},
247 total => $meminfo->{swaptotal},
248 used => $meminfo->{swapused},
249 };
250
251 $res->{pveversion} = PVE::pvecfg::package() . "/" .
252 PVE::pvecfg::version_text();
253
254 my $dinfo = df('/', 1); # output is bytes
255
256 $res->{rootfs} = {
257 total => $dinfo->{blocks},
258 avail => $dinfo->{bavail},
259 used => $dinfo->{used},
260 free => $dinfo->{bavail} - $dinfo->{used},
261 };
262
263 return $res;
264 }});
265
266 __PACKAGE__->register_method({
267 name => 'netstat',
268 path => 'netstat',
269 method => 'GET',
270 permissions => {
271 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
272 },
273 description => "Read tap/vm network device interface counters",
274 proxyto => 'node',
275 parameters => {
276 additionalProperties => 0,
277 properties => {
278 node => get_standard_option('pve-node'),
279 },
280 },
281 returns => {
282 type => "array",
283 items => {
284 type => "object",
285 properties => {},
286 },
287 },
288 code => sub {
289 my ($param) = @_;
290
291 my $res = [ ];
292
293 my $netdev = PVE::ProcFSTools::read_proc_net_dev();
294 foreach my $dev (keys %$netdev) {
295 next if $dev !~ m/^(?:tap|veth)([1-9]\d*)i(\d+)$/;
296 my $vmid = $1;
297 my $netid = $2;
298
299 push(
300 @$res,
301 {
302 vmid => $vmid,
303 dev => "net$netid",
304 in => $netdev->{$dev}->{transmit},
305 out => $netdev->{$dev}->{receive},
306 }
307 );
308 }
309
310 return $res;
311 }});
312
313 __PACKAGE__->register_method({
314 name => 'execute',
315 path => 'execute',
316 method => 'POST',
317 permissions => {
318 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
319 },
320 description => "Execute multiple commands in order.",
321 proxyto => 'node',
322 protected => 1, # avoid problems with proxy code
323 parameters => {
324 additionalProperties => 0,
325 properties => {
326 node => get_standard_option('pve-node'),
327 commands => {
328 description => "JSON encoded array of commands.",
329 type => "string",
330 }
331 },
332 },
333 returns => {
334 type => 'array',
335 properties => {
336
337 },
338 },
339 code => sub {
340 my ($param) = @_;
341 my $res = [];
342
343 my $rpcenv = PVE::RPCEnvironment::get();
344 my $user = $rpcenv->get_user();
345
346 my $commands = eval { decode_json($param->{commands}); };
347
348 die "commands param did not contain valid JSON: $@" if $@;
349 die "commands is not an array" if ref($commands) ne "ARRAY";
350
351 foreach my $cmd (@$commands) {
352 eval {
353 die "$cmd is not a valid command" if (ref($cmd) ne "HASH" || !$cmd->{path} || !$cmd->{method});
354
355 $cmd->{args} //= {};
356
357 my $path = "nodes/$param->{node}/$cmd->{path}";
358
359 my $uri_param = {};
360 my ($handler, $info) = PVE::API2->find_handler($cmd->{method}, $path, $uri_param);
361 if (!$handler || !$info) {
362 die "no handler for '$path'\n";
363 }
364
365 foreach my $p (keys %{$cmd->{args}}) {
366 raise_param_exc({ $p => "duplicate parameter" }) if defined($uri_param->{$p});
367 $uri_param->{$p} = $cmd->{args}->{$p};
368 }
369
370 # check access permissions
371 $rpcenv->check_api2_permissions($info->{permissions}, $user, $uri_param);
372
373 push @$res, {
374 status => HTTP_OK,
375 data => $handler->handle($info, $uri_param),
376 };
377 };
378 if (my $err = $@) {
379 my $resp = { status => HTTP_INTERNAL_SERVER_ERROR };
380 if (ref($err) eq "PVE::Exception") {
381 $resp->{status} = $err->{code} if $err->{code};
382 $resp->{errors} = $err->{errors} if $err->{errors};
383 $resp->{message} = $err->{msg};
384 } else {
385 $resp->{message} = $err;
386 }
387 push @$res, $resp;
388 }
389 }
390
391 return $res;
392 }});
393
394
395 __PACKAGE__->register_method({
396 name => 'node_cmd',
397 path => 'status',
398 method => 'POST',
399 permissions => {
400 check => ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
401 },
402 protected => 1,
403 description => "Reboot or shutdown a node.",
404 proxyto => 'node',
405 parameters => {
406 additionalProperties => 0,
407 properties => {
408 node => get_standard_option('pve-node'),
409 command => {
410 description => "Specify the command.",
411 type => 'string',
412 enum => [qw(reboot shutdown)],
413 },
414 },
415 },
416 returns => { type => "null" },
417 code => sub {
418 my ($param) = @_;
419
420 if ($param->{command} eq 'reboot') {
421 system ("(sleep 2;/sbin/reboot)&");
422 } elsif ($param->{command} eq 'shutdown') {
423 system ("(sleep 2;/sbin/poweroff)&");
424 }
425
426 return undef;
427 }});
428
429
430 __PACKAGE__->register_method({
431 name => 'rrd',
432 path => 'rrd',
433 method => 'GET',
434 protected => 1, # fixme: can we avoid that?
435 permissions => {
436 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
437 },
438 description => "Read node RRD statistics (returns PNG)",
439 parameters => {
440 additionalProperties => 0,
441 properties => {
442 node => get_standard_option('pve-node'),
443 timeframe => {
444 description => "Specify the time frame you are interested in.",
445 type => 'string',
446 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
447 },
448 ds => {
449 description => "The list of datasources you want to display.",
450 type => 'string', format => 'pve-configid-list',
451 },
452 cf => {
453 description => "The RRD consolidation function",
454 type => 'string',
455 enum => [ 'AVERAGE', 'MAX' ],
456 optional => 1,
457 },
458 },
459 },
460 returns => {
461 type => "object",
462 properties => {
463 filename => { type => 'string' },
464 },
465 },
466 code => sub {
467 my ($param) = @_;
468
469 return PVE::Cluster::create_rrd_graph(
470 "pve2-node/$param->{node}", $param->{timeframe},
471 $param->{ds}, $param->{cf});
472
473 }});
474
475 __PACKAGE__->register_method({
476 name => 'rrddata',
477 path => 'rrddata',
478 method => 'GET',
479 protected => 1, # fixme: can we avoid that?
480 permissions => {
481 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
482 },
483 description => "Read node RRD statistics",
484 parameters => {
485 additionalProperties => 0,
486 properties => {
487 node => get_standard_option('pve-node'),
488 timeframe => {
489 description => "Specify the time frame you are interested in.",
490 type => 'string',
491 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
492 },
493 cf => {
494 description => "The RRD consolidation function",
495 type => 'string',
496 enum => [ 'AVERAGE', 'MAX' ],
497 optional => 1,
498 },
499 },
500 },
501 returns => {
502 type => "array",
503 items => {
504 type => "object",
505 properties => {},
506 },
507 },
508 code => sub {
509 my ($param) = @_;
510
511 return PVE::Cluster::create_rrd_data(
512 "pve2-node/$param->{node}", $param->{timeframe}, $param->{cf});
513 }});
514
515 __PACKAGE__->register_method({
516 name => 'syslog',
517 path => 'syslog',
518 method => 'GET',
519 description => "Read system log",
520 proxyto => 'node',
521 permissions => {
522 check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
523 },
524 protected => 1,
525 parameters => {
526 additionalProperties => 0,
527 properties => {
528 node => get_standard_option('pve-node'),
529 start => {
530 type => 'integer',
531 minimum => 0,
532 optional => 1,
533 },
534 limit => {
535 type => 'integer',
536 minimum => 0,
537 optional => 1,
538 },
539 since => {
540 type=> 'string',
541 pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
542 description => "Display all log since this date-time string.",
543 optional => 1,
544 },
545 until => {
546 type=> 'string',
547 pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
548 description => "Display all log until this date-time string.",
549 optional => 1,
550 },
551 },
552 },
553 returns => {
554 type => 'array',
555 items => {
556 type => "object",
557 properties => {
558 n => {
559 description=> "Line number",
560 type=> 'integer',
561 },
562 t => {
563 description=> "Line text",
564 type => 'string',
565 }
566 }
567 }
568 },
569 code => sub {
570 my ($param) = @_;
571
572 my $rpcenv = PVE::RPCEnvironment::get();
573 my $user = $rpcenv->get_user();
574 my $node = $param->{node};
575
576 my ($count, $lines) = PVE::Tools::dump_journal($param->{start}, $param->{limit},
577 $param->{since}, $param->{until});
578
579 $rpcenv->set_result_attrib('total', $count);
580
581 return $lines;
582 }});
583
584 my $sslcert;
585
586 __PACKAGE__->register_method ({
587 name => 'vncshell',
588 path => 'vncshell',
589 method => 'POST',
590 protected => 1,
591 permissions => {
592 description => "Restricted to users on realm 'pam'",
593 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
594 },
595 description => "Creates a VNC Shell proxy.",
596 parameters => {
597 additionalProperties => 0,
598 properties => {
599 node => get_standard_option('pve-node'),
600 upgrade => {
601 type => 'boolean',
602 description => "Run 'apt-get dist-upgrade' instead of normal shell.",
603 optional => 1,
604 default => 0,
605 },
606 websocket => {
607 optional => 1,
608 type => 'boolean',
609 description => "use websocket instead of standard vnc.",
610 },
611 },
612 },
613 returns => {
614 additionalProperties => 0,
615 properties => {
616 user => { type => 'string' },
617 ticket => { type => 'string' },
618 cert => { type => 'string' },
619 port => { type => 'integer' },
620 upid => { type => 'string' },
621 },
622 },
623 code => sub {
624 my ($param) = @_;
625
626 my $rpcenv = PVE::RPCEnvironment::get();
627
628 my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
629
630 raise_perm_exc("realm != pam") if $realm ne 'pam';
631
632 raise_perm_exc('user != root@pam') if $param->{upgrade} && $user ne 'root@pam';
633
634 my $node = $param->{node};
635
636 my $authpath = "/nodes/$node";
637
638 my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath);
639
640 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
641 if !$sslcert;
642
643 my ($remip, $family);
644
645 if ($node ne PVE::INotify::nodename()) {
646 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
647 } else {
648 $family = PVE::Tools::get_host_address_family($node);
649 }
650
651 my $port = PVE::Tools::next_vnc_port($family);
652
653 # NOTE: vncterm VNC traffic is already TLS encrypted,
654 # so we select the fastest chipher here (or 'none'?)
655 my $remcmd = $remip ?
656 ['/usr/bin/ssh', '-t', $remip] : [];
657
658 my $shcmd;
659
660 if ($user eq 'root@pam') {
661 if ($param->{upgrade}) {
662 my $upgradecmd = "pveupgrade --shell";
663 $upgradecmd = PVE::Tools::shellquote($upgradecmd) if $remip;
664 $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
665 } else {
666 $shcmd = [ '/bin/bash', '-l' ];
667 }
668 } else {
669 $shcmd = [ '/bin/login' ];
670 }
671
672 my $timeout = 10;
673
674 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
675 '-timeout', $timeout, '-authpath', $authpath,
676 '-perm', 'Sys.Console'];
677
678 if ($param->{websocket}) {
679 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
680 push @$cmd, '-notls', '-listen', 'localhost';
681 }
682
683 push @$cmd, '-c', @$remcmd, @$shcmd;
684
685 my $realcmd = sub {
686 my $upid = shift;
687
688 syslog ('info', "starting vnc proxy $upid\n");
689
690 my $cmdstr = join (' ', @$cmd);
691 syslog ('info', "launch command: $cmdstr");
692
693 eval {
694 foreach my $k (keys %ENV) {
695 next if $k eq 'PVE_VNC_TICKET';
696 next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME';
697 delete $ENV{$k};
698 }
699 $ENV{PWD} = '/';
700
701 PVE::Tools::run_command($cmd, errmsg => "vncterm failed");
702 };
703 if (my $err = $@) {
704 syslog ('err', $err);
705 }
706
707 return;
708 };
709
710 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
711
712 PVE::Tools::wait_for_vnc_port($port);
713
714 return {
715 user => $user,
716 ticket => $ticket,
717 port => $port,
718 upid => $upid,
719 cert => $sslcert,
720 };
721 }});
722
723 __PACKAGE__->register_method({
724 name => 'vncwebsocket',
725 path => 'vncwebsocket',
726 method => 'GET',
727 permissions => {
728 description => "Restricted to users on realm 'pam'. You also need to pass a valid ticket (vncticket).",
729 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
730 },
731 description => "Opens a weksocket for VNC traffic.",
732 parameters => {
733 additionalProperties => 0,
734 properties => {
735 node => get_standard_option('pve-node'),
736 vncticket => {
737 description => "Ticket from previous call to vncproxy.",
738 type => 'string',
739 maxLength => 512,
740 },
741 port => {
742 description => "Port number returned by previous vncproxy call.",
743 type => 'integer',
744 minimum => 5900,
745 maximum => 5999,
746 },
747 },
748 },
749 returns => {
750 type => "object",
751 properties => {
752 port => { 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 my $authpath = "/nodes/$param->{node}";
765
766 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $user, $authpath);
767
768 my $port = $param->{port};
769
770 return { port => $port };
771 }});
772
773 __PACKAGE__->register_method ({
774 name => 'spiceshell',
775 path => 'spiceshell',
776 method => 'POST',
777 protected => 1,
778 proxyto => 'node',
779 permissions => {
780 description => "Restricted to users on realm 'pam'",
781 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
782 },
783 description => "Creates a SPICE shell.",
784 parameters => {
785 additionalProperties => 0,
786 properties => {
787 node => get_standard_option('pve-node'),
788 proxy => get_standard_option('spice-proxy', { optional => 1 }),
789 upgrade => {
790 type => 'boolean',
791 description => "Run 'apt-get dist-upgrade' instead of normal shell.",
792 optional => 1,
793 default => 0,
794 },
795 },
796 },
797 returns => get_standard_option('remote-viewer-config'),
798 code => sub {
799 my ($param) = @_;
800
801 my $rpcenv = PVE::RPCEnvironment::get();
802 my $authuser = $rpcenv->get_user();
803
804 my ($user, undef, $realm) = PVE::AccessControl::verify_username($authuser);
805
806 raise_perm_exc("realm != pam") if $realm ne 'pam';
807
808 raise_perm_exc('user != root@pam') if $param->{upgrade} && $user ne 'root@pam';
809
810 my $node = $param->{node};
811 my $proxy = $param->{proxy};
812
813 my $authpath = "/nodes/$node";
814 my $permissions = 'Sys.Console';
815
816 my $shcmd;
817
818 if ($user eq 'root@pam') {
819 if ($param->{upgrade}) {
820 my $upgradecmd = "pveupgrade --shell";
821 $shcmd = [ '/bin/bash', '-c', $upgradecmd ];
822 } else {
823 $shcmd = [ '/bin/bash', '-l' ];
824 }
825 } else {
826 $shcmd = [ '/bin/login' ];
827 }
828
829 my $title = "Shell on '$node'";
830
831 return PVE::API2Tools::run_spiceterm($authpath, $permissions, 0, $node, $proxy, $title, $shcmd);
832 }});
833
834 __PACKAGE__->register_method({
835 name => 'dns',
836 path => 'dns',
837 method => 'GET',
838 permissions => {
839 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
840 },
841 description => "Read DNS settings.",
842 proxyto => 'node',
843 parameters => {
844 additionalProperties => 0,
845 properties => {
846 node => get_standard_option('pve-node'),
847 },
848 },
849 returns => {
850 type => "object",
851 additionalProperties => 0,
852 properties => {
853 search => {
854 description => "Search domain for host-name lookup.",
855 type => 'string',
856 optional => 1,
857 },
858 dns1 => {
859 description => 'First name server IP address.',
860 type => 'string',
861 optional => 1,
862 },
863 dns2 => {
864 description => 'Second name server IP address.',
865 type => 'string',
866 optional => 1,
867 },
868 dns3 => {
869 description => 'Third name server IP address.',
870 type => 'string',
871 optional => 1,
872 },
873 },
874 },
875 code => sub {
876 my ($param) = @_;
877
878 my $res = PVE::INotify::read_file('resolvconf');
879
880 return $res;
881 }});
882
883 __PACKAGE__->register_method({
884 name => 'update_dns',
885 path => 'dns',
886 method => 'PUT',
887 description => "Write DNS settings.",
888 permissions => {
889 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
890 },
891 proxyto => 'node',
892 protected => 1,
893 parameters => {
894 additionalProperties => 0,
895 properties => {
896 node => get_standard_option('pve-node'),
897 search => {
898 description => "Search domain for host-name lookup.",
899 type => 'string',
900 },
901 dns1 => {
902 description => 'First name server IP address.',
903 type => 'string', format => 'ip',
904 optional => 1,
905 },
906 dns2 => {
907 description => 'Second name server IP address.',
908 type => 'string', format => 'ip',
909 optional => 1,
910 },
911 dns3 => {
912 description => 'Third name server IP address.',
913 type => 'string', format => 'ip',
914 optional => 1,
915 },
916 },
917 },
918 returns => { type => "null" },
919 code => sub {
920 my ($param) = @_;
921
922 PVE::INotify::update_file('resolvconf', $param);
923
924 return undef;
925 }});
926
927 __PACKAGE__->register_method({
928 name => 'time',
929 path => 'time',
930 method => 'GET',
931 permissions => {
932 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
933 },
934 description => "Read server time and time zone settings.",
935 proxyto => 'node',
936 parameters => {
937 additionalProperties => 0,
938 properties => {
939 node => get_standard_option('pve-node'),
940 },
941 },
942 returns => {
943 type => "object",
944 additionalProperties => 0,
945 properties => {
946 timezone => {
947 description => "Time zone",
948 type => 'string',
949 },
950 time => {
951 description => "Seconds since 1970-01-01 00:00:00 UTC.",
952 type => 'integer',
953 minimum => 1297163644,
954 },
955 localtime => {
956 description => "Seconds since 1970-01-01 00:00:00 (local time)",
957 type => 'integer',
958 minimum => 1297163644,
959 },
960 },
961 },
962 code => sub {
963 my ($param) = @_;
964
965 my $ctime = time();
966 my $ltime = timegm_nocheck(localtime($ctime));
967 my $res = {
968 timezone => PVE::INotify::read_file('timezone'),
969 time => time(),
970 localtime => $ltime,
971 };
972
973 return $res;
974 }});
975
976 __PACKAGE__->register_method({
977 name => 'set_timezone',
978 path => 'time',
979 method => 'PUT',
980 description => "Set time zone.",
981 permissions => {
982 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
983 },
984 proxyto => 'node',
985 protected => 1,
986 parameters => {
987 additionalProperties => 0,
988 properties => {
989 node => get_standard_option('pve-node'),
990 timezone => {
991 description => "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
992 type => 'string',
993 },
994 },
995 },
996 returns => { type => "null" },
997 code => sub {
998 my ($param) = @_;
999
1000 PVE::INotify::write_file('timezone', $param->{timezone});
1001
1002 return undef;
1003 }});
1004
1005 __PACKAGE__->register_method({
1006 name => 'aplinfo',
1007 path => 'aplinfo',
1008 method => 'GET',
1009 permissions => {
1010 user => 'all',
1011 },
1012 description => "Get list of appliances.",
1013 proxyto => 'node',
1014 parameters => {
1015 additionalProperties => 0,
1016 properties => {
1017 node => get_standard_option('pve-node'),
1018 },
1019 },
1020 returns => {
1021 type => 'array',
1022 items => {
1023 type => "object",
1024 properties => {},
1025 },
1026 },
1027 code => sub {
1028 my ($param) = @_;
1029
1030 my $res = [];
1031
1032 my $list = PVE::APLInfo::load_data();
1033
1034 foreach my $template (keys %{$list->{all}}) {
1035 my $pd = $list->{all}->{$template};
1036 next if $pd->{'package'} eq 'pve-web-news';
1037 push @$res, $pd;
1038 }
1039
1040 return $res;
1041 }});
1042
1043 __PACKAGE__->register_method({
1044 name => 'apl_download',
1045 path => 'aplinfo',
1046 method => 'POST',
1047 permissions => {
1048 check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
1049 },
1050 description => "Download appliance templates.",
1051 proxyto => 'node',
1052 protected => 1,
1053 parameters => {
1054 additionalProperties => 0,
1055 properties => {
1056 node => get_standard_option('pve-node'),
1057 storage => get_standard_option('pve-storage-id', {
1058 description => "Only list status for specified storage",
1059 completion => \&PVE::Storage::complete_storage_enabled,
1060 }),
1061 template => { type => 'string',
1062 description => "The template wich will downloaded",
1063 maxLength => 255,
1064 completion => \&complete_templet_repo,
1065 },
1066 },
1067 },
1068 returns => { type => "string" },
1069 code => sub {
1070 my ($param) = @_;
1071
1072 my $rpcenv = PVE::RPCEnvironment::get();
1073
1074 my $user = $rpcenv->get_user();
1075
1076 my $node = $param->{node};
1077
1078 my $list = PVE::APLInfo::load_data();
1079
1080 my $template = $param->{template};
1081 my $pd = $list->{all}->{$template};
1082
1083 raise_param_exc({ template => "no such template"}) if !$pd;
1084
1085 my $cfg = cfs_read_file("storage.cfg");
1086 my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node);
1087
1088 die "cannot download to storage type '$scfg->{type}'"
1089 if !($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs');
1090
1091 die "unknown template type '$pd->{type}'\n"
1092 if !($pd->{type} eq 'openvz' || $pd->{type} eq 'lxc');
1093
1094 die "storage '$param->{storage}' does not support templates\n"
1095 if !$scfg->{content}->{vztmpl};
1096
1097 my $src = $pd->{location};
1098 my $tmpldir = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
1099 my $dest = "$tmpldir/$template";
1100 my $tmpdest = "$tmpldir/${template}.tmp.$$";
1101
1102 my $worker = sub {
1103 my $upid = shift;
1104
1105 print "starting template download from: $src\n";
1106 print "target file: $dest\n";
1107
1108 eval {
1109
1110 if (-f $dest) {
1111 my $md5 = (split (/\s/, `md5sum '$dest'`))[0];
1112
1113 if ($md5 && (lc($md5) eq lc($pd->{md5sum}))) {
1114 print "file already exists $md5 - no need to download\n";
1115 return;
1116 }
1117 }
1118
1119 local %ENV;
1120 my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
1121 if ($dccfg->{http_proxy}) {
1122 $ENV{http_proxy} = $dccfg->{http_proxy};
1123 }
1124
1125 my @cmd = ('/usr/bin/wget', '--progress=dot:mega', '-O', $tmpdest, $src);
1126 if (system (@cmd) != 0) {
1127 die "download failed - $!\n";
1128 }
1129
1130 my $md5 = (split (/\s/, `md5sum '$tmpdest'`))[0];
1131
1132 if (!$md5 || (lc($md5) ne lc($pd->{md5sum}))) {
1133 die "wrong checksum: $md5 != $pd->{md5sum}\n";
1134 }
1135
1136 if (system ('mv', $tmpdest, $dest) != 0) {
1137 die "unable to save file - $!\n";
1138 }
1139 };
1140 my $err = $@;
1141
1142 unlink $tmpdest;
1143
1144 if ($err) {
1145 print "\n";
1146 die $err if $err;
1147 }
1148
1149 print "download finished\n";
1150 };
1151
1152 return $rpcenv->fork_worker('download', undef, $user, $worker);
1153 }});
1154
1155 __PACKAGE__->register_method({
1156 name => 'report',
1157 path => 'report',
1158 method => 'GET',
1159 permissions => {
1160 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1161 },
1162 protected => 1,
1163 description => "Gather various systems information about a node",
1164 proxyto => 'node',
1165 parameters => {
1166 additionalProperties => 0,
1167 properties => {
1168 node => get_standard_option('pve-node'),
1169 },
1170 },
1171 returns => {
1172 type => 'string',
1173 },
1174 code => sub {
1175 return PVE::Report::generate();
1176 }});
1177
1178 my $get_start_stop_list = sub {
1179 my ($nodename, $autostart) = @_;
1180
1181 my $vmlist = PVE::Cluster::get_vmlist();
1182
1183 my $resList = {};
1184 foreach my $vmid (keys %{$vmlist->{ids}}) {
1185 my $d = $vmlist->{ids}->{$vmid};
1186 my $startup;
1187
1188 eval {
1189 return if $d->{node} ne $nodename;
1190
1191 my $bootorder = LONG_MAX;
1192
1193 my $conf;
1194 if ($d->{type} eq 'lxc') {
1195 $conf = PVE::LXC::Config->load_config($vmid);
1196 } elsif ($d->{type} eq 'qemu') {
1197 $conf = PVE::QemuConfig->load_config($vmid);
1198 } else {
1199 die "unknown VM type '$d->{type}'\n";
1200 }
1201
1202 return if $autostart && !$conf->{onboot};
1203
1204 if ($conf->{startup}) {
1205 $startup = PVE::JSONSchema::pve_parse_startup_order($conf->{startup});
1206 $startup->{order} = $bootorder if !defined($startup->{order});
1207 } else {
1208 $startup = { order => $bootorder };
1209 }
1210
1211 # skip ha managed VMs (started by pve-ha-manager)
1212 return if PVE::HA::Config::vm_is_ha_managed($vmid);
1213
1214 $resList->{$startup->{order}}->{$vmid} = $startup;
1215 $resList->{$startup->{order}}->{$vmid}->{type} = $d->{type};
1216 };
1217 warn $@ if $@;
1218 }
1219
1220 return $resList;
1221 };
1222
1223 __PACKAGE__->register_method ({
1224 name => 'startall',
1225 path => 'startall',
1226 method => 'POST',
1227 protected => 1,
1228 proxyto => 'node',
1229 description => "Start all VMs and containers (when onboot=1).",
1230 parameters => {
1231 additionalProperties => 0,
1232 properties => {
1233 node => get_standard_option('pve-node'),
1234 force => {
1235 optional => 1,
1236 type => 'boolean',
1237 description => "force if onboot=0.",
1238 },
1239 },
1240 },
1241 returns => {
1242 type => 'string',
1243 },
1244 code => sub {
1245 my ($param) = @_;
1246
1247 my $rpcenv = PVE::RPCEnvironment::get();
1248 my $authuser = $rpcenv->get_user();
1249
1250 my $nodename = $param->{node};
1251 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
1252
1253 my $force = $param->{force};
1254
1255 my $code = sub {
1256
1257 $rpcenv->{type} = 'priv'; # to start tasks in background
1258
1259 # wait up to 60 seconds for quorum
1260 for (my $i = 60; $i >= 0; $i--) {
1261 last if PVE::Cluster::check_cfs_quorum($i != 0 ? 1 : 0);
1262 sleep(1);
1263 }
1264 my $autostart = $force ? undef : 1;
1265 my $startList = &$get_start_stop_list($nodename, $autostart);
1266
1267 # Note: use numeric sorting with <=>
1268 foreach my $order (sort {$a <=> $b} keys %$startList) {
1269 my $vmlist = $startList->{$order};
1270
1271 foreach my $vmid (sort {$a <=> $b} keys %$vmlist) {
1272 my $d = $vmlist->{$vmid};
1273
1274 PVE::Cluster::check_cfs_quorum(); # abort when we loose quorum
1275
1276 eval {
1277 my $default_delay = 0;
1278 my $upid;
1279
1280 if ($d->{type} eq 'lxc') {
1281 return if PVE::LXC::check_running($vmid);
1282 print STDERR "Starting CT $vmid\n";
1283 $upid = PVE::API2::LXC::Status->vm_start({node => $nodename, vmid => $vmid });
1284 } elsif ($d->{type} eq 'qemu') {
1285 $default_delay = 3; # to redruce load
1286 return if PVE::QemuServer::check_running($vmid, 1);
1287 print STDERR "Starting VM $vmid\n";
1288 $upid = PVE::API2::Qemu->vm_start({node => $nodename, vmid => $vmid });
1289 } else {
1290 die "unknown VM type '$d->{type}'\n";
1291 }
1292
1293 my $res = PVE::Tools::upid_decode($upid);
1294 while (PVE::ProcFSTools::check_process_running($res->{pid})) {
1295 sleep(1);
1296 }
1297
1298 my $status = PVE::Tools::upid_read_status($upid);
1299 if ($status eq 'OK') {
1300 # use default delay to reduce load
1301 my $delay = defined($d->{up}) ? int($d->{up}) : $default_delay;
1302 if ($delay > 0) {
1303 print STDERR "Waiting for $delay seconds (startup delay)\n" if $d->{up};
1304 for (my $i = 0; $i < $delay; $i++) {
1305 sleep(1);
1306 }
1307 }
1308 } else {
1309 if ($d->{type} eq 'lxc') {
1310 print STDERR "Starting CT $vmid failed: $status\n";
1311 } elsif ($d->{type} eq 'qemu') {
1312 print STDERR "Starting VM $vmid failed: status\n";
1313 }
1314 }
1315 };
1316 warn $@ if $@;
1317 }
1318 }
1319 return;
1320 };
1321
1322 return $rpcenv->fork_worker('startall', undef, $authuser, $code);
1323 }});
1324
1325 my $create_stop_worker = sub {
1326 my ($nodename, $type, $vmid, $down_timeout) = @_;
1327
1328 my $upid;
1329 if ($type eq 'lxc') {
1330 return if !PVE::LXC::check_running($vmid);
1331 my $timeout = defined($down_timeout) ? int($down_timeout) : 60;
1332 print STDERR "Stopping CT $vmid (timeout = $timeout seconds)\n";
1333 $upid = PVE::API2::LXC::Status->vm_shutdown({node => $nodename, vmid => $vmid,
1334 timeout => $timeout, forceStop => 1 });
1335 } elsif ($type eq 'qemu') {
1336 return if !PVE::QemuServer::check_running($vmid, 1);
1337 my $timeout = defined($down_timeout) ? int($down_timeout) : 60*3;
1338 print STDERR "Stopping VM $vmid (timeout = $timeout seconds)\n";
1339 $upid = PVE::API2::Qemu->vm_shutdown({node => $nodename, vmid => $vmid,
1340 timeout => $timeout, forceStop => 1 });
1341 } else {
1342 die "unknown VM type '$type'\n";
1343 }
1344
1345 return $upid;
1346 };
1347
1348 __PACKAGE__->register_method ({
1349 name => 'stopall',
1350 path => 'stopall',
1351 method => 'POST',
1352 protected => 1,
1353 proxyto => 'node',
1354 description => "Stop all VMs and Containers.",
1355 parameters => {
1356 additionalProperties => 0,
1357 properties => {
1358 node => get_standard_option('pve-node'),
1359 },
1360 },
1361 returns => {
1362 type => 'string',
1363 },
1364 code => sub {
1365 my ($param) = @_;
1366
1367 my $rpcenv = PVE::RPCEnvironment::get();
1368 my $authuser = $rpcenv->get_user();
1369
1370 my $nodename = $param->{node};
1371 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
1372
1373 my $code = sub {
1374
1375 $rpcenv->{type} = 'priv'; # to start tasks in background
1376
1377 my $stopList = &$get_start_stop_list($nodename);
1378
1379 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
1380 my $datacenterconfig = cfs_read_file('datacenter.cfg');
1381 # if not set by user spawn max cpu count number of workers
1382 my $maxWorkers = $datacenterconfig->{max_workers} || $cpuinfo->{cpus};
1383
1384 foreach my $order (sort {$b <=> $a} keys %$stopList) {
1385 my $vmlist = $stopList->{$order};
1386 my $workers = {};
1387
1388 my $finish_worker = sub {
1389 my $pid = shift;
1390 my $d = $workers->{$pid};
1391 return if !$d;
1392 delete $workers->{$pid};
1393
1394 syslog('info', "end task $d->{upid}");
1395 };
1396
1397 foreach my $vmid (sort {$b <=> $a} keys %$vmlist) {
1398 my $d = $vmlist->{$vmid};
1399 my $upid;
1400 eval { $upid = &$create_stop_worker($nodename, $d->{type}, $vmid, $d->{down}); };
1401 warn $@ if $@;
1402 next if !$upid;
1403
1404 my $res = PVE::Tools::upid_decode($upid, 1);
1405 next if !$res;
1406
1407 my $pid = $res->{pid};
1408
1409 $workers->{$pid} = { type => $d->{type}, upid => $upid, vmid => $vmid };
1410 while (scalar(keys %$workers) >= $maxWorkers) {
1411 foreach my $p (keys %$workers) {
1412 if (!PVE::ProcFSTools::check_process_running($p)) {
1413 &$finish_worker($p);
1414 }
1415 }
1416 sleep(1);
1417 }
1418 }
1419 while (scalar(keys %$workers)) {
1420 foreach my $p (keys %$workers) {
1421 if (!PVE::ProcFSTools::check_process_running($p)) {
1422 &$finish_worker($p);
1423 }
1424 }
1425 sleep(1);
1426 }
1427 }
1428
1429 syslog('info', "all VMs and CTs stopped");
1430
1431 return;
1432 };
1433
1434 return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
1435 }});
1436
1437 my $create_migrate_worker = sub {
1438 my ($nodename, $type, $vmid, $target) = @_;
1439
1440 my $upid;
1441 if ($type eq 'lxc') {
1442 my $online = PVE::LXC::check_running($vmid) ? 1 : 0;
1443 print STDERR "Migrating CT $vmid\n";
1444 $upid = PVE::API2::LXC->migrate_vm({node => $nodename, vmid => $vmid, target => $target,
1445 online => $online });
1446 } elsif ($type eq 'qemu') {
1447 my $online = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
1448 print STDERR "Migrating VM $vmid\n";
1449 $upid = PVE::API2::Qemu->migrate_vm({node => $nodename, vmid => $vmid, target => $target,
1450 online => $online });
1451 } else {
1452 die "unknown VM type '$type'\n";
1453 }
1454
1455 my $res = PVE::Tools::upid_decode($upid);
1456
1457 return $res->{pid};
1458 };
1459
1460 __PACKAGE__->register_method ({
1461 name => 'migrateall',
1462 path => 'migrateall',
1463 method => 'POST',
1464 proxyto => 'node',
1465 protected => 1,
1466 description => "Migrate all VMs and Containers.",
1467 parameters => {
1468 additionalProperties => 0,
1469 properties => {
1470 node => get_standard_option('pve-node'),
1471 target => get_standard_option('pve-node', { description => "Target node." }),
1472 maxworkers => {
1473 description => "Maximal number of parallel migration job." .
1474 " If not set use 'max_workers' from datacenter.cfg," .
1475 " one of both must be set!",
1476 optional => 1,
1477 type => 'integer',
1478 minimum => 1
1479 },
1480 },
1481 },
1482 returns => {
1483 type => 'string',
1484 },
1485 code => sub {
1486 my ($param) = @_;
1487
1488 my $rpcenv = PVE::RPCEnvironment::get();
1489 my $authuser = $rpcenv->get_user();
1490
1491 my $nodename = $param->{node};
1492 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
1493
1494 my $target = $param->{target};
1495
1496 my $datacenterconfig = cfs_read_file('datacenter.cfg');
1497 # prefer parameter over datacenter cfg settings
1498 my $maxWorkers = $param->{maxworkers} || $datacenterconfig->{max_workers} ||
1499 die "either 'maxworkers' parameter or max_workers in datacenter.cfg must be set!\n";
1500
1501 my $code = sub {
1502
1503 $rpcenv->{type} = 'priv'; # to start tasks in background
1504
1505 my $migrateList = &$get_start_stop_list($nodename);
1506
1507 foreach my $order (sort {$b <=> $a} keys %$migrateList) {
1508 my $vmlist = $migrateList->{$order};
1509 my $workers = {};
1510 foreach my $vmid (sort {$b <=> $a} keys %$vmlist) {
1511 my $d = $vmlist->{$vmid};
1512 my $pid;
1513 eval { $pid = &$create_migrate_worker($nodename, $d->{type}, $vmid, $target); };
1514 warn $@ if $@;
1515 next if !$pid;
1516
1517 $workers->{$pid} = 1;
1518 while (scalar(keys %$workers) >= $maxWorkers) {
1519 foreach my $p (keys %$workers) {
1520 if (!PVE::ProcFSTools::check_process_running($p)) {
1521 delete $workers->{$p};
1522 }
1523 }
1524 sleep(1);
1525 }
1526 }
1527 while (scalar(keys %$workers)) {
1528 foreach my $p (keys %$workers) {
1529 if (!PVE::ProcFSTools::check_process_running($p)) {
1530 delete $workers->{$p};
1531 }
1532 }
1533 sleep(1);
1534 }
1535 }
1536 return;
1537 };
1538
1539 return $rpcenv->fork_worker('migrateall', undef, $authuser, $code);
1540
1541 }});
1542
1543 # bash completion helper
1544
1545 sub complete_templet_repo {
1546 my ($cmdname, $pname, $cvalue) = @_;
1547
1548 my $repo = PVE::APLInfo::load_data();
1549 my $res = [];
1550 foreach my $templ (keys %{$repo->{all}}) {
1551 next if $templ !~ m/^$cvalue/;
1552 push @$res, $templ;
1553 }
1554
1555 return $res;
1556 }
1557
1558 package PVE::API2::Nodes;
1559
1560 use strict;
1561 use warnings;
1562
1563 use PVE::SafeSyslog;
1564 use PVE::Cluster;
1565 use PVE::RESTHandler;
1566 use PVE::RPCEnvironment;
1567 use PVE::API2Tools;
1568
1569 use base qw(PVE::RESTHandler);
1570
1571 __PACKAGE__->register_method ({
1572 subclass => "PVE::API2::Nodes::Nodeinfo",
1573 path => '{node}',
1574 });
1575
1576 __PACKAGE__->register_method ({
1577 name => 'index',
1578 path => '',
1579 method => 'GET',
1580 permissions => { user => 'all' },
1581 description => "Cluster node index.",
1582 parameters => {
1583 additionalProperties => 0,
1584 properties => {},
1585 },
1586 returns => {
1587 type => 'array',
1588 items => {
1589 type => "object",
1590 properties => {},
1591 },
1592 links => [ { rel => 'child', href => "{node}" } ],
1593 },
1594 code => sub {
1595 my ($param) = @_;
1596
1597 my $clinfo = PVE::Cluster::get_clinfo();
1598 my $res = [];
1599
1600 my $nodelist = PVE::Cluster::get_nodelist();
1601 my $members = PVE::Cluster::get_members();
1602 my $rrd = PVE::Cluster::rrd_dump();
1603
1604 foreach my $node (@$nodelist) {
1605 my $entry = PVE::API2Tools::extract_node_stats($node, $members, $rrd);
1606 push @$res, $entry;
1607 }
1608
1609 return $res;
1610 }});
1611
1612 1;