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