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