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