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