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