]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Nodes.pm
vnc: use SSH command helper
[pve-manager.git] / PVE / API2 / Nodes.pm
CommitLineData
aff192e6
DM
1package PVE::API2::Nodes::Nodeinfo;
2
3use strict;
4use warnings;
5a378425
TL
5
6use Digest::MD5;
7use Digest::SHA;
aff192e6 8use Filesys::Df;
40993593 9use HTTP::Status qw(:constants);
5a378425
TL
10use JSON;
11use POSIX qw(LONG_MAX);
12use Time::Local qw(timegm_nocheck);
13use Socket;
17711ff8 14use IO::Socket::SSL;
5a378425 15
b289829f 16use PVE::API2Tools;
5a378425
TL
17use PVE::APLInfo;
18use PVE::AccessControl;
c9164975 19use PVE::Cluster qw(cfs_read_file);
5a378425 20use PVE::DataCenterConfig;
05b252dc 21use PVE::Exception qw(raise raise_perm_exc raise_param_exc);
4a07fced 22use PVE::Firewall;
7e5cb2f0 23use PVE::HA::Config;
5a378425
TL
24use PVE::HA::Env::PVE2;
25use PVE::INotify;
26use PVE::JSONSchema qw(get_standard_option);
27use PVE::LXC;
3f83a033 28use PVE::NodeConfig;
5a378425 29use PVE::ProcFSTools;
7141ae25 30use PVE::QemuConfig;
b92400b6 31use PVE::QemuServer;
ebb71cb5 32use PVE::RESTEnvironment qw(log_warn);
5a378425
TL
33use PVE::RESTHandler;
34use PVE::RPCEnvironment;
35use PVE::RRD;
36use PVE::Report;
37use PVE::SafeSyslog;
38use PVE::Storage;
81fd95cf 39use PVE::Tools qw(file_get_contents);
5a378425
TL
40use PVE::pvecfg;
41
21299915 42use PVE::API2::APT;
c6c4b278 43use PVE::API2::Capabilities;
38db610a 44use PVE::API2::Ceph;
036475f8 45use PVE::API2::Certificates;
e781363f 46use PVE::API2::Disks;
5a378425
TL
47use PVE::API2::Firewall::Host;
48use PVE::API2::Hardware;
49use PVE::API2::LXC::Status;
50use PVE::API2::LXC;
51use PVE::API2::Network;
52use PVE::API2::NodeConfig;
53use PVE::API2::Qemu::CPU;
5a378425
TL
54use PVE::API2::Qemu;
55use PVE::API2::Replication;
56use PVE::API2::Services;
57use PVE::API2::Storage::Scan;
58use PVE::API2::Storage::Status;
59use PVE::API2::Subscription;
60use PVE::API2::Tasks;
61use PVE::API2::VZDump;
aff192e6 62
4be427a0
AD
63my $have_sdn;
64eval {
bb654699 65 require PVE::API2::Network::SDN::Zones::Status;
4be427a0
AD
66 $have_sdn = 1;
67};
68
aff192e6
DM
69use base qw(PVE::RESTHandler);
70
9018237e
FG
71my $verify_command_item_desc = {
72 description => "An array of objects describing endpoints, methods and arguments.",
73 type => "array",
74 items => {
75 type => "object",
76 properties => {
77 path => {
78 description => "A relative path to an API endpoint on this node.",
79 type => "string",
80 optional => 0,
81 },
82 method => {
83 description => "A method related to the API endpoint (GET, POST etc.).",
84 type => "string",
85 pattern => "(GET|POST|PUT|DELETE)",
86 optional => 0,
87 },
88 args => {
89 description => "A set of parameter names and their values.",
90 type => "object",
91 optional => 1,
92 },
93 },
94 }
95};
96
a1de4410
SS
97PVE::JSONSchema::register_format('pve-command-batch', \&verify_command_batch);
98sub verify_command_batch {
99 my ($value, $noerr) = @_;
100 my $commands = eval { decode_json($value); };
101
79d62026 102 return if $noerr && $@;
a1de4410
SS
103 die "commands param did not contain valid JSON: $@" if $@;
104
79d62026 105 eval { PVE::JSONSchema::validate($commands, $verify_command_item_desc) };
a1de4410 106
79d62026 107 return $commands if !$@;
a1de4410 108
79d62026
TL
109 return if $noerr;
110 die "commands is not a valid array of commands: $@";
a1de4410
SS
111}
112
aff192e6 113__PACKAGE__->register_method ({
f9d26e09 114 subclass => "PVE::API2::Qemu",
aff192e6
DM
115 path => 'qemu',
116});
117
989b7743 118__PACKAGE__->register_method ({
f9d26e09 119 subclass => "PVE::API2::LXC",
989b7743
DM
120 path => 'lxc',
121});
122
38db610a 123__PACKAGE__->register_method ({
f9d26e09 124 subclass => "PVE::API2::Ceph",
38db610a
DM
125 path => 'ceph',
126});
127
bf58f8dd 128__PACKAGE__->register_method ({
f9d26e09 129 subclass => "PVE::API2::VZDump",
bf58f8dd
DM
130 path => 'vzdump',
131});
132
aff192e6 133__PACKAGE__->register_method ({
f9d26e09 134 subclass => "PVE::API2::Services",
aff192e6
DM
135 path => 'services',
136});
137
21ace8d3 138__PACKAGE__->register_method ({
f9d26e09 139 subclass => "PVE::API2::Subscription",
21ace8d3
DM
140 path => 'subscription',
141});
142
aff192e6 143__PACKAGE__->register_method ({
f9d26e09 144 subclass => "PVE::API2::Network",
aff192e6
DM
145 path => 'network',
146});
147
148__PACKAGE__->register_method ({
f9d26e09 149 subclass => "PVE::API2::Tasks",
aff192e6
DM
150 path => 'tasks',
151});
152
153__PACKAGE__->register_method ({
f5b6ccb1 154 subclass => "PVE::API2::Storage::Scan",
aff192e6
DM
155 path => 'scan',
156});
157
523d5f48
TL
158__PACKAGE__->register_method ({
159 subclass => "PVE::API2::Hardware",
160 path => 'hardware',
161});
162
c6c4b278
TL
163__PACKAGE__->register_method ({
164 subclass => "PVE::API2::Capabilities",
165 path => 'capabilities',
166});
523d5f48 167
aff192e6 168__PACKAGE__->register_method ({
f9d26e09 169 subclass => "PVE::API2::Storage::Status",
aff192e6
DM
170 path => 'storage',
171});
172
e781363f
DC
173__PACKAGE__->register_method ({
174 subclass => "PVE::API2::Disks",
175 path => 'disks',
176});
177
21299915 178__PACKAGE__->register_method ({
f9d26e09 179 subclass => "PVE::API2::APT",
21299915
DM
180 path => 'apt',
181});
182
4a07fced 183__PACKAGE__->register_method ({
f9d26e09 184 subclass => "PVE::API2::Firewall::Host",
4a07fced
DM
185 path => 'firewall',
186});
187
892821fd
DM
188__PACKAGE__->register_method ({
189 subclass => "PVE::API2::Replication",
190 path => 'replication',
191});
192
036475f8
FG
193__PACKAGE__->register_method ({
194 subclass => "PVE::API2::Certificates",
195 path => 'certificates',
196});
197
198
c4f78bb7
FG
199__PACKAGE__->register_method ({
200 subclass => "PVE::API2::NodeConfig",
201 path => 'config',
202});
203
4be427a0
AD
204if ($have_sdn) {
205 __PACKAGE__->register_method ({
bb654699
AD
206 subclass => "PVE::API2::Network::SDN::Zones::Status",
207 path => 'sdn/zones',
4be427a0 208 });
48181607
TL
209
210__PACKAGE__->register_method ({
211 name => 'sdnindex',
212 path => 'sdn',
213 method => 'GET',
214 permissions => { user => 'all' },
215 description => "SDN index.",
216 parameters => {
217 additionalProperties => 0,
218 properties => {
219 node => get_standard_option('pve-node'),
220 },
221 },
222 returns => {
223 type => 'array',
224 items => {
225 type => "object",
226 properties => {},
227 },
228 links => [ { rel => 'child', href => "{name}" } ],
229 },
230 code => sub {
231 my ($param) = @_;
232
233 my $result = [
234 { name => 'zones' },
235 ];
236 return $result;
237 }});
4be427a0
AD
238}
239
aff192e6 240__PACKAGE__->register_method ({
f9d26e09
TL
241 name => 'index',
242 path => '',
aff192e6
DM
243 method => 'GET',
244 permissions => { user => 'all' },
245 description => "Node index.",
246 parameters => {
6110ed03 247 additionalProperties => 0,
aff192e6
DM
248 properties => {
249 node => get_standard_option('pve-node'),
250 },
251 },
252 returns => {
253 type => 'array',
254 items => {
255 type => "object",
256 properties => {},
257 },
258 links => [ { rel => 'child', href => "{name}" } ],
259 },
260 code => sub {
261 my ($param) = @_;
f9d26e09 262
aff192e6 263 my $result = [
784d6ee9
TL
264 { name => 'aplinfo' },
265 { name => 'apt' },
c6c4b278 266 { name => 'capabilities' },
38db610a 267 { name => 'ceph' },
784d6ee9
TL
268 { name => 'certificates' },
269 { name => 'config' },
e781363f 270 { name => 'disks' },
784d6ee9
TL
271 { name => 'dns' },
272 { name => 'firewall' },
2f410501 273 { name => 'hardware' },
784d6ee9 274 { name => 'hosts' },
952280b4 275 { name => 'journal' },
784d6ee9 276 { name => 'lxc' },
8b3c353e 277 { name => 'migrateall' },
784d6ee9
TL
278 { name => 'netstat' },
279 { name => 'network' },
280 { name => 'qemu' },
17711ff8 281 { name => 'query-url-metadata' },
784d6ee9 282 { name => 'replication' },
34ada77a 283 { name => 'report' },
aff192e6
DM
284 { name => 'rrd' }, # fixme: remove?
285 { name => 'rrddata' },# fixme: remove?
aff192e6 286 { name => 'scan' },
784d6ee9
TL
287 { name => 'services' },
288 { name => 'spiceshell' },
b92400b6 289 { name => 'startall' },
784d6ee9 290 { name => 'status' },
b92400b6 291 { name => 'stopall' },
784d6ee9
TL
292 { name => 'storage' },
293 { name => 'subscription' },
5f04abc2 294 { name => 'suspendall' },
784d6ee9
TL
295 { name => 'syslog' },
296 { name => 'tasks' },
297 { name => 'termproxy' },
298 { name => 'time' },
299 { name => 'version' },
300 { name => 'vncshell' },
301 { name => 'vzdump' },
302 { name => 'wakeonlan' },
952280b4 303 ];
aff192e6 304
bb654699
AD
305 push @$result, { name => 'sdn' } if $have_sdn;
306
aff192e6
DM
307 return $result;
308 }});
309
8747a9ec 310__PACKAGE__->register_method ({
f9d26e09 311 name => 'version',
8747a9ec
DM
312 path => 'version',
313 method => 'GET',
314 proxyto => 'node',
315 permissions => { user => 'all' },
316 description => "API version details",
317 parameters => {
6110ed03 318 additionalProperties => 0,
8747a9ec
DM
319 properties => {
320 node => get_standard_option('pve-node'),
321 },
322 },
323 returns => {
324 type => "object",
325 properties => {
180a86d3
TL
326 version => {
327 type => 'string',
328 description => 'The current installed pve-manager package version',
329 },
330 release => {
331 type => 'string',
332 description => 'The current installed Proxmox VE Release',
333 },
334 repoid => {
335 type => 'string',
336 description => 'The short git commit hash ID from which this version was build',
337 },
8747a9ec
DM
338 },
339 },
340 code => sub {
341 my ($resp, $param) = @_;
f9d26e09 342
8747a9ec
DM
343 return PVE::pvecfg::version_info();
344 }});
345
20ad4e0e
TL
346my sub get_current_kernel_info {
347 my ($sysname, $nodename, $release, $version, $machine) = POSIX::uname();
348
349 my $kernel_version_string = "$sysname $release $version"; # for legacy compat
350 my $current_kernel = {
351 sysname => $sysname,
352 release => $release,
353 version => $version,
354 machine => $machine,
355 };
356 return ($current_kernel, $kernel_version_string);
357}
358
92759ae1 359my $boot_mode_info_cache;
81fd95cf 360my sub get_boot_mode_info {
92759ae1
TL
361 return $boot_mode_info_cache if defined($boot_mode_info_cache);
362
81fd95cf
TL
363 my $is_efi_booted = -d "/sys/firmware/efi";
364
92759ae1 365 $boot_mode_info_cache = {
81fd95cf
TL
366 mode => $is_efi_booted ? 'efi' : 'legacy-bios',
367 };
368
8113229e
FG
369 my $efi_var = "/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c";
370
371 if ($is_efi_booted && -e $efi_var) {
372 my $efi_var_sec_boot_entry = eval { file_get_contents($efi_var) };
81fd95cf
TL
373 if ($@) {
374 warn "Failed to read secure boot state: $@\n";
375 } else {
376 my @secureboot = unpack("CCCCC", $efi_var_sec_boot_entry);
92759ae1 377 $boot_mode_info_cache->{secureboot} = $secureboot[4] == 1 ? 1 : 0;
81fd95cf
TL
378 }
379 }
92759ae1 380 return $boot_mode_info_cache;
81fd95cf
TL
381}
382
aff192e6 383__PACKAGE__->register_method({
f9d26e09
TL
384 name => 'status',
385 path => 'status',
aff192e6
DM
386 method => 'GET',
387 permissions => {
7d020b42 388 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
aff192e6
DM
389 },
390 description => "Read node status",
391 proxyto => 'node',
392 parameters => {
6110ed03 393 additionalProperties => 0,
aff192e6
DM
394 properties => {
395 node => get_standard_option('pve-node'),
396 },
397 },
398 returns => {
399 type => "object",
4467a5d2 400 additionalProperties => 1,
aff192e6 401 properties => {
4467a5d2
TL
402 # TODO: document remaing ones
403 'boot-info' => {
404 description => "Meta-information about the boot mode.",
405 type => 'object',
406 properties => {
407 mode => {
408 description => 'Through which firmware the system got booted.',
409 type => 'string',
410 enum => [qw(efi legacy-bios)],
411 },
412 secureboot => {
413 description => 'System is booted in secure mode, only applicable for the "efi" mode.',
414 type => 'boolean',
415 optional => 1,
416 },
417 },
418 },
419 'current-kernel' => {
420 description => "The uptime of the system in seconds.",
421 type => 'object',
422 properties => {
423 sysname => {
424 description => 'OS kernel name (e.g., "Linux")',
425 type => 'string',
426 },
427 release => {
428 description => 'OS kernel release (e.g., "6.8.0")',
429 type => 'string',
430 },
431 version => {
432 description => 'OS kernel version with build info',
433 type => 'string',
434 },
435 machine => {
436 description => 'Hardware (architecture) type',
437 type => 'string',
438 },
439 },
440 },
aff192e6
DM
441 },
442 },
443 code => sub {
444 my ($param) = @_;
445
446 my $res = {
447 uptime => 0,
448 idle => 0,
449 };
450
451 my ($uptime, $idle) = PVE::ProcFSTools::read_proc_uptime();
452 $res->{uptime} = $uptime;
f9d26e09 453
aff192e6
DM
454 my ($avg1, $avg5, $avg15) = PVE::ProcFSTools::read_loadavg();
455 $res->{loadavg} = [ $avg1, $avg5, $avg15];
f9d26e09 456
20ad4e0e
TL
457 my ($current_kernel_info, $kversion_string) = get_current_kernel_info();
458 $res->{kversion} = $kversion_string;
459 $res->{'current-kernel'} = $current_kernel_info;
aff192e6 460
81fd95cf
TL
461 $res->{'boot-info'} = get_boot_mode_info();
462
aff192e6
DM
463 $res->{cpuinfo} = PVE::ProcFSTools::read_cpuinfo();
464
465 my $stat = PVE::ProcFSTools::read_proc_stat();
466 $res->{cpu} = $stat->{cpu};
467 $res->{wait} = $stat->{wait};
468
469 my $meminfo = PVE::ProcFSTools::read_meminfo();
470 $res->{memory} = {
471 free => $meminfo->{memfree},
472 total => $meminfo->{memtotal},
473 used => $meminfo->{memused},
474 };
f9d26e09 475
20539e0c
DM
476 $res->{ksm} = {
477 shared => $meminfo->{memshared},
478 };
479
aff192e6
DM
480 $res->{swap} = {
481 free => $meminfo->{swapfree},
482 total => $meminfo->{swaptotal},
483 used => $meminfo->{swapused},
484 };
485
486 $res->{pveversion} = PVE::pvecfg::package() . "/" .
8747a9ec 487 PVE::pvecfg::version_text();
aff192e6
DM
488
489 my $dinfo = df('/', 1); # output is bytes
490
491 $res->{rootfs} = {
492 total => $dinfo->{blocks},
493 avail => $dinfo->{bavail},
494 used => $dinfo->{used},
3d0fcc46 495 free => $dinfo->{blocks} - $dinfo->{used},
aff192e6
DM
496 };
497
498 return $res;
499 }});
500
0455911d
SP
501__PACKAGE__->register_method({
502 name => 'netstat',
503 path => 'netstat',
504 method => 'GET',
505 permissions => {
506 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
507 },
508 description => "Read tap/vm network device interface counters",
509 proxyto => 'node',
510 parameters => {
511 additionalProperties => 0,
512 properties => {
513 node => get_standard_option('pve-node'),
514 },
515 },
516 returns => {
d6e7fa04
TL
517 type => "array",
518 items => {
519 type => "object",
520 properties => {},
521 },
0455911d
SP
522 },
523 code => sub {
524 my ($param) = @_;
525
526 my $res = [ ];
527
528 my $netdev = PVE::ProcFSTools::read_proc_net_dev();
d6e7fa04
TL
529 foreach my $dev (sort keys %$netdev) {
530 next if $dev !~ m/^(?:tap|veth)([1-9]\d*)i(\d+)$/;
531 my ($vmid, $netid) = ($1, $2);
532
533 push @$res, {
534 vmid => $vmid,
535 dev => "net$netid",
536 in => $netdev->{$dev}->{transmit},
537 out => $netdev->{$dev}->{receive},
538 };
0455911d
SP
539 }
540
541 return $res;
542 }});
543
87c3e931
SP
544__PACKAGE__->register_method({
545 name => 'execute',
546 path => 'execute',
40993593 547 method => 'POST',
eb859354 548 description => "Execute multiple commands in order, root only.",
87c3e931 549 proxyto => 'node',
40993593 550 protected => 1, # avoid problems with proxy code
87c3e931
SP
551 parameters => {
552 additionalProperties => 0,
553 properties => {
554 node => get_standard_option('pve-node'),
40993593
DM
555 commands => {
556 description => "JSON encoded array of commands.",
557 type => "string",
9018237e
FG
558 verbose_description => "JSON encoded array of commands, where each command is an object with the following properties:\n"
559 . PVE::RESTHandler::dump_properties($verify_command_item_desc->{items}->{properties}, 'full'),
a1de4410 560 format => "pve-command-batch",
40993593 561 }
87c3e931
SP
562 },
563 },
564 returns => {
565 type => 'array',
72db67a2
SS
566 items => {
567 type => "object",
568 properties => {},
87c3e931
SP
569 },
570 },
571 code => sub {
572 my ($param) = @_;
573 my $res = [];
574
40993593
DM
575 my $rpcenv = PVE::RPCEnvironment::get();
576 my $user = $rpcenv->get_user();
a1de4410 577 # just parse the json again, it should already be validated
40993593
DM
578 my $commands = eval { decode_json($param->{commands}); };
579
58ab77d1 580 foreach my $cmd (@$commands) {
87c3e931 581 eval {
40993593
DM
582 $cmd->{args} //= {};
583
584 my $path = "nodes/$param->{node}/$cmd->{path}";
f9d26e09 585
40993593
DM
586 my $uri_param = {};
587 my ($handler, $info) = PVE::API2->find_handler($cmd->{method}, $path, $uri_param);
588 if (!$handler || !$info) {
589 die "no handler for '$path'\n";
590 }
591
592 foreach my $p (keys %{$cmd->{args}}) {
593 raise_param_exc({ $p => "duplicate parameter" }) if defined($uri_param->{$p});
594 $uri_param->{$p} = $cmd->{args}->{$p};
595 }
596
597 # check access permissions
598 $rpcenv->check_api2_permissions($info->{permissions}, $user, $uri_param);
599
600 push @$res, {
601 status => HTTP_OK,
a51ceeb7 602 data => $handler->handle($info, $uri_param),
40993593 603 };
87c3e931 604 };
40993593
DM
605 if (my $err = $@) {
606 my $resp = { status => HTTP_INTERNAL_SERVER_ERROR };
607 if (ref($err) eq "PVE::Exception") {
608 $resp->{status} = $err->{code} if $err->{code};
609 $resp->{errors} = $err->{errors} if $err->{errors};
610 $resp->{message} = $err->{msg};
611 } else {
612 $resp->{message} = $err;
613 }
614 push @$res, $resp;
615 }
87c3e931
SP
616 }
617
618 return $res;
619 }});
620
621
aff192e6 622__PACKAGE__->register_method({
f9d26e09
TL
623 name => 'node_cmd',
624 path => 'status',
aff192e6
DM
625 method => 'POST',
626 permissions => {
7d020b42 627 check => ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
aff192e6
DM
628 },
629 protected => 1,
630 description => "Reboot or shutdown a node.",
631 proxyto => 'node',
632 parameters => {
6110ed03 633 additionalProperties => 0,
aff192e6
DM
634 properties => {
635 node => get_standard_option('pve-node'),
636 command => {
637 description => "Specify the command.",
638 type => 'string',
639 enum => [qw(reboot shutdown)],
640 },
641 },
642 },
643 returns => { type => "null" },
644 code => sub {
645 my ($param) = @_;
646
647 if ($param->{command} eq 'reboot') {
648 system ("(sleep 2;/sbin/reboot)&");
649 } elsif ($param->{command} eq 'shutdown') {
650 system ("(sleep 2;/sbin/poweroff)&");
651 }
652
79d62026 653 return;
aff192e6
DM
654 }});
655
b3d84542
CE
656__PACKAGE__->register_method({
657 name => 'wakeonlan',
658 path => 'wakeonlan',
659 method => 'POST',
660 permissions => {
661 check => ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
662 },
663 protected => 1,
664 description => "Try to wake a node via 'wake on LAN' network packet.",
665 parameters => {
666 additionalProperties => 0,
667 properties => {
668 node => get_standard_option('pve-node', {
669 description => 'target node for wake on LAN packet',
5aa7b909
CE
670 completion => sub {
671 my $members = PVE::Cluster::get_members();
672 return [ grep { !$members->{$_}->{online} } keys %$members ];
673 }
b3d84542
CE
674 }),
675 },
676 },
0f615ea9
CE
677 returns => {
678 type => 'string',
679 format => 'mac-addr',
ec178804 680 description => 'MAC address used to assemble the WoL magic packet.',
0f615ea9 681 },
b3d84542
CE
682 code => sub {
683 my ($param) = @_;
684
82436996 685 my $node = $param->{node};
869c155c 686 my $local_node = PVE::INotify::nodename();
82436996
TL
687
688 die "'$node' is local node, cannot wake my self!\n"
869c155c 689 if $node eq 'localhost' || $node eq $local_node;
82436996
TL
690
691 PVE::Cluster::check_node_exists($node);
692
693 my $config = PVE::NodeConfig::load_config($node);
3f83a033
CE
694 my $wol_config = PVE::NodeConfig::get_wakeonlan_config($config);
695 my $mac_addr = $wol_config->{mac};
b3d84542 696 if (!defined($mac_addr)) {
82436996 697 die "No wake on LAN MAC address defined for '$node'!\n";
b3d84542
CE
698 }
699
869c155c
CE
700 my $local_config = PVE::NodeConfig::load_config($local_node);
701 my $local_wol_config = PVE::NodeConfig::get_wakeonlan_config($local_config);
a967ff65 702 my $broadcast_addr = $local_wol_config->{'broadcast-address'} // '255.255.255.255';
869c155c 703
b3d84542
CE
704 $mac_addr =~ s/://g;
705 my $packet = chr(0xff) x 6 . pack('H*', $mac_addr) x 16;
706
a967ff65 707 my $addr = gethostbyname($broadcast_addr);
b3d84542
CE
708 my $port = getservbyname('discard', 'udp');
709 my $to = Socket::pack_sockaddr_in($port, $addr);
82436996 710
b3d84542
CE
711 socket(my $sock, Socket::AF_INET, Socket::SOCK_DGRAM, Socket::IPPROTO_UDP)
712 || die "Unable to open socket: $!\n";
713 setsockopt($sock, Socket::SOL_SOCKET, Socket::SO_BROADCAST, 1)
714 || die "Unable to set socket option: $!\n";
715
f2be47a4
TL
716 if (defined(my $bind_iface = $local_wol_config->{'bind-interface'})) {
717 my $bind_iface_raw = pack('Z*', $bind_iface); # Null terminated interface name
869c155c
CE
718 setsockopt($sock, Socket::SOL_SOCKET, Socket::SO_BINDTODEVICE, $bind_iface_raw)
719 || die "Unable to bind socket to interface '$bind_iface': $!\n";
720 }
721
b3d84542
CE
722 send($sock, $packet, 0, $to)
723 || die "Unable to send packet: $!\n";
724
725 close($sock);
726
3f83a033 727 return $wol_config->{mac};
b3d84542 728 }});
aff192e6
DM
729
730__PACKAGE__->register_method({
f9d26e09
TL
731 name => 'rrd',
732 path => 'rrd',
aff192e6
DM
733 method => 'GET',
734 protected => 1, # fixme: can we avoid that?
735 permissions => {
7d020b42 736 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
aff192e6
DM
737 },
738 description => "Read node RRD statistics (returns PNG)",
739 parameters => {
6110ed03 740 additionalProperties => 0,
aff192e6
DM
741 properties => {
742 node => get_standard_option('pve-node'),
743 timeframe => {
744 description => "Specify the time frame you are interested in.",
745 type => 'string',
746 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
747 },
748 ds => {
749 description => "The list of datasources you want to display.",
58ab77d1 750 type => 'string', format => 'pve-configid-list',
aff192e6
DM
751 },
752 cf => {
753 description => "The RRD consolidation function",
58ab77d1 754 type => 'string',
aff192e6
DM
755 enum => [ 'AVERAGE', 'MAX' ],
756 optional => 1,
757 },
758 },
759 },
760 returns => {
761 type => "object",
762 properties => {
763 filename => { type => 'string' },
764 },
765 },
766 code => sub {
767 my ($param) = @_;
768
516a7948 769 return PVE::RRD::create_rrd_graph(
f9d26e09 770 "pve2-node/$param->{node}", $param->{timeframe},
aff192e6
DM
771 $param->{ds}, $param->{cf});
772
773 }});
774
775__PACKAGE__->register_method({
f9d26e09
TL
776 name => 'rrddata',
777 path => 'rrddata',
aff192e6
DM
778 method => 'GET',
779 protected => 1, # fixme: can we avoid that?
780 permissions => {
7d020b42 781 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
aff192e6
DM
782 },
783 description => "Read node RRD statistics",
784 parameters => {
6110ed03 785 additionalProperties => 0,
aff192e6
DM
786 properties => {
787 node => get_standard_option('pve-node'),
788 timeframe => {
789 description => "Specify the time frame you are interested in.",
790 type => 'string',
791 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
792 },
793 cf => {
794 description => "The RRD consolidation function",
58ab77d1 795 type => 'string',
aff192e6
DM
796 enum => [ 'AVERAGE', 'MAX' ],
797 optional => 1,
798 },
799 },
800 },
801 returns => {
802 type => "array",
803 items => {
804 type => "object",
805 properties => {},
806 },
807 },
808 code => sub {
809 my ($param) = @_;
810
516a7948 811 return PVE::RRD::create_rrd_data(
aff192e6
DM
812 "pve2-node/$param->{node}", $param->{timeframe}, $param->{cf});
813 }});
814
815__PACKAGE__->register_method({
f9d26e09
TL
816 name => 'syslog',
817 path => 'syslog',
aff192e6
DM
818 method => 'GET',
819 description => "Read system log",
820 proxyto => 'node',
821 permissions => {
7d020b42 822 check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
aff192e6
DM
823 },
824 protected => 1,
825 parameters => {
6110ed03 826 additionalProperties => 0,
aff192e6
DM
827 properties => {
828 node => get_standard_option('pve-node'),
829 start => {
830 type => 'integer',
831 minimum => 0,
832 optional => 1,
833 },
834 limit => {
835 type => 'integer',
836 minimum => 0,
837 optional => 1,
838 },
01b753b6
TL
839 since => {
840 type=> 'string',
841 pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
842 description => "Display all log since this date-time string.",
843 optional => 1,
844 },
845 until => {
846 type=> 'string',
847 pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
848 description => "Display all log until this date-time string.",
849 optional => 1,
850 },
34142272
DC
851 service => {
852 description => "Service ID",
853 type => 'string',
854 maxLength => 128,
855 optional => 1,
856 },
aff192e6
DM
857 },
858 },
859 returns => {
860 type => 'array',
f9d26e09 861 items => {
aff192e6
DM
862 type => "object",
863 properties => {
864 n => {
865 description=> "Line number",
866 type=> 'integer',
867 },
868 t => {
869 description=> "Line text",
870 type => 'string',
871 }
872 }
873 }
874 },
875 code => sub {
876 my ($param) = @_;
877
aff192e6
DM
878 my $rpcenv = PVE::RPCEnvironment::get();
879 my $user = $rpcenv->get_user();
880 my $node = $param->{node};
34142272
DC
881 my $service;
882
883 if ($param->{service}) {
884 my $service_aliases = {
885 'postfix' => 'postfix@-',
886 };
887
888 $service = $service_aliases->{$param->{service}} // $param->{service};
889 }
aff192e6 890
01b753b6 891 my ($count, $lines) = PVE::Tools::dump_journal($param->{start}, $param->{limit},
34142272 892 $param->{since}, $param->{until}, $service);
ff9c330c
DM
893
894 $rpcenv->set_result_attrib('total', $count);
01b753b6
TL
895
896 return $lines;
ff9c330c
DM
897 }});
898
1d397a83
DC
899__PACKAGE__->register_method({
900 name => 'journal',
901 path => 'journal',
902 method => 'GET',
903 description => "Read Journal",
904 proxyto => 'node',
905 permissions => {
906 check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
907 },
908 protected => 1,
909 parameters => {
910 additionalProperties => 0,
911 properties => {
912 node => get_standard_option('pve-node'),
913 since => {
961399a3
TL
914 type=> 'integer',
915 minimum => 0,
f9b08743 916 description => "Display all log since this UNIX epoch. Conflicts with 'startcursor'.",
1d397a83
DC
917 optional => 1,
918 },
919 until => {
961399a3
TL
920 type=> 'integer',
921 minimum => 0,
f9b08743 922 description => "Display all log until this UNIX epoch. Conflicts with 'endcursor'.",
1d397a83
DC
923 optional => 1,
924 },
925 lastentries => {
a03cb8b8 926 description => "Limit to the last X lines. Conflicts with a range.",
1d397a83 927 type => 'integer',
961399a3 928 minimum => 0,
1d397a83
DC
929 optional => 1,
930 },
931 startcursor => {
f9b08743 932 description => "Start after the given Cursor. Conflicts with 'since'",
1d397a83
DC
933 type => 'string',
934 optional => 1,
935 },
936 endcursor => {
f9b08743 937 description => "End before the given Cursor. Conflicts with 'until'",
1d397a83
DC
938 type => 'string',
939 optional => 1,
940 },
941 },
942 },
943 returns => {
944 type => 'array',
945 items => {
946 type => "string",
947 }
948 },
949 code => sub {
950 my ($param) = @_;
951
952 my $rpcenv = PVE::RPCEnvironment::get();
953 my $user = $rpcenv->get_user();
954
68df9496 955 my $cmd = ["/usr/bin/mini-journalreader", "-j"];
a03cb8b8
TL
956 push @$cmd, '-n', $param->{lastentries} if $param->{lastentries};
957 push @$cmd, '-b', $param->{since} if $param->{since};
958 push @$cmd, '-e', $param->{until} if $param->{until};
68df9496
DC
959 push @$cmd, '-f', PVE::Tools::shellquote($param->{startcursor}) if $param->{startcursor};
960 push @$cmd, '-t', PVE::Tools::shellquote($param->{endcursor}) if $param->{endcursor};
961 push @$cmd, ' | gzip ';
962
963 open(my $fh, "-|", join(' ', @$cmd))
964 or die "could not start mini-journalreader";
965
966 return {
967 download => {
968 fh => $fh,
969 stream => 1,
970 'content-type' => 'application/json',
971 'content-encoding' => 'gzip',
972 },
973 },
1d397a83
DC
974 }});
975
aff192e6
DM
976my $sslcert;
977
dab7a849 978my $shell_cmd_map = {
f24be7e7
TL
979 'login' => {
980 cmd => [ '/bin/login', '-f', 'root' ],
981 },
982 'upgrade' => {
983 cmd => [ '/usr/bin/pveupgrade', '--shell' ],
984 },
985 'ceph_install' => {
986 cmd => [ '/usr/bin/pveceph', 'install' ],
987 allow_args => 1,
988 },
d03d7e1e
TM
989};
990
991sub get_shell_command {
f24be7e7 992 my ($user, $shellcmd, $args) = @_;
d03d7e1e 993
f24be7e7 994 my $cmd;
d03d7e1e 995 if ($user eq 'root@pam') {
fc1da3b0 996 if (defined($shellcmd) && exists($shell_cmd_map->{$shellcmd})) {
f24be7e7
TL
997 my $def = $shell_cmd_map->{$shellcmd};
998 $cmd = [ @{$def->{cmd}} ]; # clone
999 if (defined($args) && $def->{allow_args}) {
1000 push @$cmd, split("\0", $args);
1001 }
d03d7e1e 1002 } else {
f24be7e7 1003 $cmd = [ '/bin/login', '-f', 'root' ];
d03d7e1e
TM
1004 }
1005 } else {
f24be7e7
TL
1006 # non-root must always login for now, we do not have a superuser role!
1007 $cmd = [ '/bin/login' ];
d03d7e1e 1008 }
f24be7e7 1009 return $cmd;
d03d7e1e
TM
1010}
1011
677bee7c
TL
1012my $get_vnc_connection_info = sub {
1013 my $node = shift;
1014
1015 my $remote_cmd = [];
1016
1017 my ($remip, $family);
1018 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
1019 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
23d1d283
FG
1020 $remote_cmd = PVE::SSHInfo::ssh_info_to_command({ ip => $remip, name => $node }, ('-t'));
1021 push @$remote_cmd, '--';
677bee7c
TL
1022 } else {
1023 $family = PVE::Tools::get_host_address_family($node);
1024 }
1025 my $port = PVE::Tools::next_vnc_port($family);
1026
1027 return ($port, $remote_cmd);
1028};
1029
aff192e6 1030__PACKAGE__->register_method ({
f9d26e09
TL
1031 name => 'vncshell',
1032 path => 'vncshell',
aff192e6
DM
1033 method => 'POST',
1034 protected => 1,
1035 permissions => {
7d020b42 1036 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
aff192e6
DM
1037 },
1038 description => "Creates a VNC Shell proxy.",
1039 parameters => {
6110ed03 1040 additionalProperties => 0,
aff192e6
DM
1041 properties => {
1042 node => get_standard_option('pve-node'),
d03d7e1e
TM
1043 cmd => {
1044 type => 'string',
4fb92ae8 1045 description => "Run specific command or default to login (requires 'root\@pam')",
dab7a849 1046 enum => [keys %$shell_cmd_map],
d03d7e1e
TM
1047 optional => 1,
1048 default => 'login',
1049 },
f24be7e7
TL
1050 'cmd-opts' => {
1051 type => 'string',
1052 description => "Add parameters to a command. Encoded as null terminated strings.",
1053 requires => 'cmd',
1054 optional => 1,
1055 default => '',
1056 },
8dfca17e
DM
1057 websocket => {
1058 optional => 1,
1059 type => 'boolean',
1060 description => "use websocket instead of standard vnc.",
1061 },
b8ac8b0c
DC
1062 width => {
1063 optional => 1,
1064 description => "sets the width of the console in pixels.",
1065 type => 'integer',
1066 minimum => 16,
1067 maximum => 4096,
1068 },
1069 height => {
1070 optional => 1,
1071 description => "sets the height of the console in pixels.",
1072 type => 'integer',
1073 minimum => 16,
1074 maximum => 2160,
1075 },
aff192e6
DM
1076 },
1077 },
f9d26e09 1078 returns => {
6110ed03 1079 additionalProperties => 0,
aff192e6
DM
1080 properties => {
1081 user => { type => 'string' },
1082 ticket => { type => 'string' },
1083 cert => { type => 'string' },
1084 port => { type => 'integer' },
1085 upid => { type => 'string' },
1086 },
1087 },
1088 code => sub {
1089 my ($param) = @_;
1090
1091 my $rpcenv = PVE::RPCEnvironment::get();
d553e535 1092 my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
d0289a19 1093
0c8e509e 1094
4fb92ae8 1095 if (defined($param->{cmd}) && $param->{cmd} ne 'login' && $user ne 'root@pam') {
0c8e509e
FE
1096 raise_perm_exc('user != root@pam');
1097 }
3a76893d 1098
aff192e6
DM
1099 my $node = $param->{node};
1100
57ebda08 1101 my $authpath = "/nodes/$node";
57ebda08
DM
1102 my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath);
1103
aff192e6
DM
1104 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
1105 if !$sslcert;
1106
677bee7c 1107 my ($port, $remcmd) = $get_vnc_connection_info->($node);
d4a25f0b 1108
f24be7e7 1109 my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'});
aff192e6 1110
f9d26e09 1111 my $timeout = 10;
aff192e6 1112
677bee7c
TL
1113 my $cmd = ['/usr/bin/vncterm',
1114 '-rfbport', $port,
1115 '-timeout', $timeout,
1116 '-authpath', $authpath,
1117 '-perm', 'Sys.Console',
1118 ];
b8ac8b0c 1119
677bee7c
TL
1120 push @$cmd, '-width', $param->{width} if $param->{width};
1121 push @$cmd, '-height', $param->{height} if $param->{height};
b8ac8b0c 1122
8dfca17e 1123 if ($param->{websocket}) {
f9d26e09 1124 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
8dfca17e
DM
1125 push @$cmd, '-notls', '-listen', 'localhost';
1126 }
1127
1128 push @$cmd, '-c', @$remcmd, @$shcmd;
aff192e6
DM
1129
1130 my $realcmd = sub {
1131 my $upid = shift;
1132
1133 syslog ('info', "starting vnc proxy $upid\n");
1134
6d394492 1135 my $cmdstr = join (' ', @$cmd);
aff192e6
DM
1136 syslog ('info', "launch command: $cmdstr");
1137
f9d26e09 1138 eval {
6d394492 1139 foreach my $k (keys %ENV) {
8dfca17e 1140 next if $k eq 'PVE_VNC_TICKET';
b0d4b407 1141 next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME' || $k eq 'LANG' || $k eq 'LANGUAGE';
6d394492
DM
1142 delete $ENV{$k};
1143 }
1144 $ENV{PWD} = '/';
1145
b0d4b407 1146 PVE::Tools::run_command($cmd, errmsg => "vncterm failed", keeplocale => 1);
6d394492
DM
1147 };
1148 if (my $err = $@) {
1149 syslog ('err', $err);
aff192e6
DM
1150 }
1151
1152 return;
1153 };
1154
1155 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
f9d26e09 1156
6806a0f8 1157 PVE::Tools::wait_for_vnc_port($port);
aff192e6
DM
1158
1159 return {
1160 user => $user,
1161 ticket => $ticket,
f9d26e09
TL
1162 port => $port,
1163 upid => $upid,
1164 cert => $sslcert,
aff192e6
DM
1165 };
1166 }});
1167
4b168c27
DC
1168__PACKAGE__->register_method ({
1169 name => 'termproxy',
1170 path => 'termproxy',
1171 method => 'POST',
1172 protected => 1,
1173 permissions => {
4b168c27
DC
1174 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1175 },
1176 description => "Creates a VNC Shell proxy.",
1177 parameters => {
1178 additionalProperties => 0,
1179 properties => {
1180 node => get_standard_option('pve-node'),
d03d7e1e
TM
1181 cmd => {
1182 type => 'string',
4fb92ae8 1183 description => "Run specific command or default to login (requires 'root\@pam')",
dab7a849 1184 enum => [keys %$shell_cmd_map],
d03d7e1e
TM
1185 optional => 1,
1186 default => 'login',
1187 },
f24be7e7
TL
1188 'cmd-opts' => {
1189 type => 'string',
1190 description => "Add parameters to a command. Encoded as null terminated strings.",
1191 requires => 'cmd',
1192 optional => 1,
1193 default => '',
1194 },
4b168c27
DC
1195 },
1196 },
1197 returns => {
1198 additionalProperties => 0,
1199 properties => {
1200 user => { type => 'string' },
1201 ticket => { type => 'string' },
1202 port => { type => 'integer' },
1203 upid => { type => 'string' },
1204 },
1205 },
1206 code => sub {
1207 my ($param) = @_;
1208
1209 my $rpcenv = PVE::RPCEnvironment::get();
4b168c27 1210 my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
4b168c27
DC
1211
1212 my $node = $param->{node};
4b168c27 1213 my $authpath = "/nodes/$node";
4b168c27
DC
1214 my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath);
1215
677bee7c 1216 my ($port, $remcmd) = $get_vnc_connection_info->($node);
4b168c27 1217
f24be7e7 1218 my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'});
4b168c27
DC
1219
1220 my $realcmd = sub {
1221 my $upid = shift;
1222
1223 syslog ('info', "starting termproxy $upid\n");
1224
f24be7e7
TL
1225 my $cmd = [
1226 '/usr/bin/termproxy',
1227 $port,
677bee7c
TL
1228 '--path', $authpath,
1229 '--perm', 'Sys.Console',
1230 '--'
1231 ];
d03d7e1e 1232 push @$cmd, @$remcmd, @$shcmd;
4b168c27
DC
1233
1234 PVE::Tools::run_command($cmd);
1235 };
4b168c27
DC
1236 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
1237
1238 PVE::Tools::wait_for_vnc_port($port);
1239
1240 return {
1241 user => $user,
1242 ticket => $ticket,
1243 port => $port,
1244 upid => $upid,
1245 };
1246 }});
1247
8dfca17e
DM
1248__PACKAGE__->register_method({
1249 name => 'vncwebsocket',
1250 path => 'vncwebsocket',
1251 method => 'GET',
f9d26e09 1252 permissions => {
7914f5e7 1253 description => "You also need to pass a valid ticket (vncticket).",
8dfca17e
DM
1254 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1255 },
0e68b116 1256 description => "Opens a websocket for VNC traffic.",
8dfca17e 1257 parameters => {
6110ed03 1258 additionalProperties => 0,
8dfca17e
DM
1259 properties => {
1260 node => get_standard_option('pve-node'),
1261 vncticket => {
1262 description => "Ticket from previous call to vncproxy.",
1263 type => 'string',
1264 maxLength => 512,
1265 },
1266 port => {
1267 description => "Port number returned by previous vncproxy call.",
1268 type => 'integer',
1269 minimum => 5900,
1270 maximum => 5999,
1271 },
1272 },
1273 },
1274 returns => {
1275 type => "object",
1276 properties => {
1277 port => { type => 'string' },
1278 },
1279 },
1280 code => sub {
1281 my ($param) = @_;
1282
1283 my $rpcenv = PVE::RPCEnvironment::get();
1284
1285 my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
1286
8dfca17e
DM
1287 my $authpath = "/nodes/$param->{node}";
1288
1289 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $user, $authpath);
1290
1291 my $port = $param->{port};
f9d26e09 1292
8dfca17e
DM
1293 return { port => $port };
1294 }});
1295
2d802f8c 1296__PACKAGE__->register_method ({
f9d26e09
TL
1297 name => 'spiceshell',
1298 path => 'spiceshell',
2d802f8c
DM
1299 method => 'POST',
1300 protected => 1,
1301 proxyto => 'node',
1302 permissions => {
2d802f8c
DM
1303 check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1304 },
7774df78 1305 description => "Creates a SPICE shell.",
2d802f8c 1306 parameters => {
6110ed03 1307 additionalProperties => 0,
2d802f8c
DM
1308 properties => {
1309 node => get_standard_option('pve-node'),
7774df78 1310 proxy => get_standard_option('spice-proxy', { optional => 1 }),
d03d7e1e
TM
1311 cmd => {
1312 type => 'string',
4fb92ae8 1313 description => "Run specific command or default to login (requires 'root\@pam')",
dab7a849 1314 enum => [keys %$shell_cmd_map],
d03d7e1e
TM
1315 optional => 1,
1316 default => 'login',
1317 },
f24be7e7
TL
1318 'cmd-opts' => {
1319 type => 'string',
1320 description => "Add parameters to a command. Encoded as null terminated strings.",
1321 requires => 'cmd',
1322 optional => 1,
1323 default => '',
1324 },
2d802f8c
DM
1325 },
1326 },
7774df78 1327 returns => get_standard_option('remote-viewer-config'),
2d802f8c
DM
1328 code => sub {
1329 my ($param) = @_;
1330
1331 my $rpcenv = PVE::RPCEnvironment::get();
1332 my $authuser = $rpcenv->get_user();
1333
1334 my ($user, undef, $realm) = PVE::AccessControl::verify_username($authuser);
1335
b270a6b4 1336
4fb92ae8 1337 if (defined($param->{cmd}) && $param->{cmd} ne 'login' && $user ne 'root@pam') {
b270a6b4
FE
1338 raise_perm_exc('user != root@pam');
1339 }
2d802f8c
DM
1340
1341 my $node = $param->{node};
1342 my $proxy = $param->{proxy};
2d802f8c
DM
1343
1344 my $authpath = "/nodes/$node";
b289829f 1345 my $permissions = 'Sys.Console';
b270a6b4 1346
f24be7e7 1347 my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'});
2d802f8c 1348
b289829f 1349 my $title = "Shell on '$node'";
2d802f8c 1350
b289829f 1351 return PVE::API2Tools::run_spiceterm($authpath, $permissions, 0, $node, $proxy, $title, $shcmd);
2d802f8c
DM
1352 }});
1353
aff192e6 1354__PACKAGE__->register_method({
f9d26e09
TL
1355 name => 'dns',
1356 path => 'dns',
aff192e6
DM
1357 method => 'GET',
1358 permissions => {
7d020b42 1359 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
aff192e6
DM
1360 },
1361 description => "Read DNS settings.",
1362 proxyto => 'node',
1363 parameters => {
6110ed03 1364 additionalProperties => 0,
aff192e6
DM
1365 properties => {
1366 node => get_standard_option('pve-node'),
1367 },
1368 },
1369 returns => {
1370 type => "object",
6110ed03 1371 additionalProperties => 0,
aff192e6
DM
1372 properties => {
1373 search => {
1374 description => "Search domain for host-name lookup.",
1375 type => 'string',
1376 optional => 1,
1377 },
1378 dns1 => {
1379 description => 'First name server IP address.',
1380 type => 'string',
1381 optional => 1,
f9d26e09 1382 },
aff192e6
DM
1383 dns2 => {
1384 description => 'Second name server IP address.',
1385 type => 'string',
1386 optional => 1,
f9d26e09 1387 },
aff192e6
DM
1388 dns3 => {
1389 description => 'Third name server IP address.',
1390 type => 'string',
1391 optional => 1,
f9d26e09 1392 },
aff192e6
DM
1393 },
1394 },
1395 code => sub {
1396 my ($param) = @_;
1397
1398 my $res = PVE::INotify::read_file('resolvconf');
1399
1400 return $res;
1401 }});
1402
1403__PACKAGE__->register_method({
f9d26e09
TL
1404 name => 'update_dns',
1405 path => 'dns',
aff192e6
DM
1406 method => 'PUT',
1407 description => "Write DNS settings.",
d0289a19
DM
1408 permissions => {
1409 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
1410 },
aff192e6
DM
1411 proxyto => 'node',
1412 protected => 1,
1413 parameters => {
6110ed03 1414 additionalProperties => 0,
aff192e6
DM
1415 properties => {
1416 node => get_standard_option('pve-node'),
1417 search => {
1418 description => "Search domain for host-name lookup.",
1419 type => 'string',
1420 },
1421 dns1 => {
1422 description => 'First name server IP address.',
7a9486a7 1423 type => 'string', format => 'ip',
aff192e6 1424 optional => 1,
f9d26e09 1425 },
aff192e6
DM
1426 dns2 => {
1427 description => 'Second name server IP address.',
7a9486a7 1428 type => 'string', format => 'ip',
aff192e6 1429 optional => 1,
f9d26e09 1430 },
aff192e6
DM
1431 dns3 => {
1432 description => 'Third name server IP address.',
7a9486a7 1433 type => 'string', format => 'ip',
aff192e6 1434 optional => 1,
f9d26e09 1435 },
aff192e6
DM
1436 },
1437 },
1438 returns => { type => "null" },
1439 code => sub {
1440 my ($param) = @_;
1441
1442 PVE::INotify::update_file('resolvconf', $param);
1443
79d62026 1444 return;
aff192e6
DM
1445 }});
1446
1447__PACKAGE__->register_method({
f9d26e09
TL
1448 name => 'time',
1449 path => 'time',
aff192e6
DM
1450 method => 'GET',
1451 permissions => {
7d020b42
DM
1452 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1453 },
aff192e6
DM
1454 description => "Read server time and time zone settings.",
1455 proxyto => 'node',
1456 parameters => {
58ab77d1 1457 additionalProperties => 0,
aff192e6
DM
1458 properties => {
1459 node => get_standard_option('pve-node'),
1460 },
1461 },
1462 returns => {
1463 type => "object",
6110ed03 1464 additionalProperties => 0,
aff192e6
DM
1465 properties => {
1466 timezone => {
1467 description => "Time zone",
1468 type => 'string',
1469 },
1470 time => {
1471 description => "Seconds since 1970-01-01 00:00:00 UTC.",
1472 type => 'integer',
1473 minimum => 1297163644,
bed5fdfc 1474 renderer => 'timestamp',
aff192e6
DM
1475 },
1476 localtime => {
1477 description => "Seconds since 1970-01-01 00:00:00 (local time)",
1478 type => 'integer',
1479 minimum => 1297163644,
bed5fdfc 1480 renderer => 'timestamp_gmt',
aff192e6 1481 },
58ab77d1 1482 },
aff192e6
DM
1483 },
1484 code => sub {
1485 my ($param) = @_;
1486
1487 my $ctime = time();
1488 my $ltime = timegm_nocheck(localtime($ctime));
1489 my $res = {
1490 timezone => PVE::INotify::read_file('timezone'),
bed5fdfc 1491 time => $ctime,
aff192e6
DM
1492 localtime => $ltime,
1493 };
1494
1495 return $res;
1496 }});
1497
1498__PACKAGE__->register_method({
f9d26e09
TL
1499 name => 'set_timezone',
1500 path => 'time',
aff192e6
DM
1501 method => 'PUT',
1502 description => "Set time zone.",
d0289a19
DM
1503 permissions => {
1504 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
1505 },
aff192e6
DM
1506 proxyto => 'node',
1507 protected => 1,
1508 parameters => {
6110ed03 1509 additionalProperties => 0,
aff192e6
DM
1510 properties => {
1511 node => get_standard_option('pve-node'),
1512 timezone => {
1513 description => "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
1514 type => 'string',
1515 },
1516 },
1517 },
1518 returns => { type => "null" },
1519 code => sub {
1520 my ($param) = @_;
1521
1522 PVE::INotify::write_file('timezone', $param->{timezone});
1523
79d62026 1524 return;
aff192e6
DM
1525 }});
1526
c9164975 1527__PACKAGE__->register_method({
f9d26e09
TL
1528 name => 'aplinfo',
1529 path => 'aplinfo',
c9164975
DM
1530 method => 'GET',
1531 permissions => {
1532 user => 'all',
1533 },
1534 description => "Get list of appliances.",
1535 proxyto => 'node',
1536 parameters => {
6110ed03 1537 additionalProperties => 0,
c9164975
DM
1538 properties => {
1539 node => get_standard_option('pve-node'),
1540 },
1541 },
1542 returns => {
1543 type => 'array',
1544 items => {
1545 type => "object",
1546 properties => {},
1547 },
1548 },
1549 code => sub {
1550 my ($param) = @_;
1551
c9164975
DM
1552 my $list = PVE::APLInfo::load_data();
1553
0d213945
TL
1554 my $res = [];
1555 for my $appliance (values %{$list->{all}}) {
1556 next if $appliance->{'package'} eq 'pve-web-news';
1557 push @$res, $appliance;
c9164975
DM
1558 }
1559
1560 return $res;
1561 }});
1562
0532bd28 1563__PACKAGE__->register_method({
f9d26e09
TL
1564 name => 'apl_download',
1565 path => 'aplinfo',
0532bd28
DM
1566 method => 'POST',
1567 permissions => {
1568 check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
1569 },
1570 description => "Download appliance templates.",
1571 proxyto => 'node',
1572 protected => 1,
1573 parameters => {
6110ed03 1574 additionalProperties => 0,
0532bd28
DM
1575 properties => {
1576 node => get_standard_option('pve-node'),
82282acf 1577 storage => get_standard_option('pve-storage-id', {
62180d0f 1578 description => "The storage where the template will be stored",
82282acf
WL
1579 completion => \&PVE::Storage::complete_storage_enabled,
1580 }),
6110ed03
TL
1581 template => {
1582 type => 'string',
1583 description => "The template which will downloaded",
1584 maxLength => 255,
1585 completion => \&complete_templet_repo,
82282acf 1586 },
0532bd28
DM
1587 },
1588 },
1589 returns => { type => "string" },
1590 code => sub {
1591 my ($param) = @_;
1592
1593 my $rpcenv = PVE::RPCEnvironment::get();
0532bd28 1594 my $user = $rpcenv->get_user();
0532bd28 1595
aee25c2e 1596 my $node = $param->{node};
0532bd28 1597 my $template = $param->{template};
0532bd28 1598
aee25c2e
TL
1599 my $list = PVE::APLInfo::load_data();
1600 my $appliance = $list->{all}->{$template};
1601 raise_param_exc({ template => "no such template"}) if !$appliance;
0532bd28 1602
bbcfdc08 1603 my $cfg = PVE::Storage::config();
0532bd28
DM
1604 my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node);
1605
aee25c2e
TL
1606 die "unknown template type '$appliance->{type}'\n"
1607 if !($appliance->{type} eq 'openvz' || $appliance->{type} eq 'lxc');
0532bd28 1608
f9d26e09 1609 die "storage '$param->{storage}' does not support templates\n"
0532bd28
DM
1610 if !$scfg->{content}->{vztmpl};
1611
0532bd28 1612 my $tmpldir = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
0532bd28 1613
fac5d57e 1614 my $worker = sub {
aee25c2e
TL
1615 my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
1616
1617 PVE::Tools::download_file_from_url("$tmpldir/$template", $appliance->{location}, {
1618 hash_required => 1,
1619 sha512sum => $appliance->{sha512sum},
1620 md5sum => $appliance->{md5sum},
1621 http_proxy => $dccfg->{http_proxy},
1622 });
fac5d57e 1623 };
0532bd28 1624
fac5d57e 1625 my $upid = $rpcenv->fork_worker('download', $template, $user, $worker);
0532bd28 1626
fac5d57e 1627 return $upid;
0532bd28
DM
1628 }});
1629
17711ff8
LS
1630__PACKAGE__->register_method({
1631 name => 'query_url_metadata',
1632 path => 'query-url-metadata',
1633 method => 'GET',
1634 description => "Query metadata of an URL: file size, file name and mime type.",
1635 proxyto => 'node',
1636 permissions => {
6cf1a48d
TL
1637 check => ['or',
1638 ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]],
1639 ['perm', '/nodes/{node}', [ 'Sys.AccessNetwork' ]],
1640 ],
17711ff8
LS
1641 },
1642 parameters => {
1643 additionalProperties => 0,
1644 properties => {
1645 node => get_standard_option('pve-node'),
1646 url => {
1647 description => "The URL to query the metadata from.",
1648 type => 'string',
1649 pattern => 'https?://.*',
1650 },
1651 'verify-certificates' => {
1652 description => "If false, no SSL/TLS certificates will be verified.",
1653 type => 'boolean',
1654 optional => 1,
1655 default => 1,
d61728e2 1656 },
17711ff8
LS
1657 },
1658 },
1659 returns => {
1660 type => "object",
1661 properties => {
1662 filename => {
1663 type => 'string',
1664 optional => 1,
1665 },
1666 size => {
1667 type => 'integer',
1668 renderer => 'bytes',
1669 optional => 1,
1670 },
1671 mimetype => {
1672 type => 'string',
1673 optional => 1,
1674 },
1675 },
1676 },
1677 code => sub {
1678 my ($param) = @_;
1679
1680 my $url = $param->{url};
1681
1682 my $ua = LWP::UserAgent->new();
dfde1eec 1683 $ua->agent("Proxmox VE");
17711ff8
LS
1684
1685 my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
1686 if ($dccfg->{http_proxy}) {
1687 $ua->proxy('http', $dccfg->{http_proxy});
1688 }
1689
1690 my $verify = $param->{'verify-certificates'} // 1;
1691 if (!$verify) {
1692 $ua->ssl_opts(
1693 verify_hostname => 0,
1694 SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
1695 );
1696 }
1697
1698 my $req = HTTP::Request->new(HEAD => $url);
1699 my $res = $ua->request($req);
1700
1701 die "invalid server response: '" . $res->status_line() . "'\n" if ($res->code() != 200);
1702
1703 my $size = $res->header("Content-Length");
1704 my $disposition = $res->header("Content-Disposition");
1705 my $type = $res->header("Content-Type");
1706
1707 my $filename;
1708
1709 if ($disposition && ($disposition =~ m/filename="([^"]*)"/ || $disposition =~ m/filename=([^;]*)/)) {
1710 $filename = $1;
1711 } elsif ($url =~ m!^[^?]+/([^?/]*)(?:\?.*)?$!) {
1712 $filename = $1;
1713 }
1714
1715 # Content-Type: text/html; charset=utf-8
1716 if ($type && $type =~ m/^([^;]+);/) {
1717 $type = $1;
1718 }
1719
1720 my $ret = {};
1721 $ret->{filename} = $filename if $filename;
1722 $ret->{size} = $size + 0 if $size;
1723 $ret->{mimetype} = $type if $type;
1724
1725 return $ret;
1726 }});
1727
34ada77a
EK
1728__PACKAGE__->register_method({
1729 name => 'report',
1730 path => 'report',
1731 method => 'GET',
1732 permissions => {
1733 check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1734 },
6bbdf981 1735 protected => 1,
34ada77a
EK
1736 description => "Gather various systems information about a node",
1737 proxyto => 'node',
1738 parameters => {
6110ed03 1739 additionalProperties => 0,
34ada77a
EK
1740 properties => {
1741 node => get_standard_option('pve-node'),
1742 },
1743 },
1744 returns => {
cdb5a209 1745 type => 'string',
34ada77a
EK
1746 },
1747 code => sub {
cdb5a209 1748 return PVE::Report::generate();
34ada77a 1749 }});
0532bd28 1750
b72cdbb7
TL
1751# returns a list of VMIDs, those can be filtered by
1752# * current parent node
1753# * vmid whitelist
1754# * guest is a template (default: skip)
1755# * guest is HA manged (default: skip)
1756my $get_filtered_vmlist = sub {
1757 my ($nodename, $vmfilter, $templates, $ha_managed) = @_;
b92400b6 1758
b92400b6
DM
1759 my $vmlist = PVE::Cluster::get_vmlist();
1760
dbfcac27 1761 my $vms_allowed;
2a498506 1762 if (defined($vmfilter)) {
dbfcac27 1763 $vms_allowed = { map { $_ => 1 } PVE::Tools::split_list($vmfilter) };
2a498506
DC
1764 }
1765
b72cdbb7 1766 my $res = {};
b92400b6 1767 foreach my $vmid (keys %{$vmlist->{ids}}) {
dbfcac27 1768 next if defined($vms_allowed) && !$vms_allowed->{$vmid};
b92400b6 1769
b72cdbb7
TL
1770 my $d = $vmlist->{ids}->{$vmid};
1771 next if $nodename && $d->{node} ne $nodename;
b92400b6 1772
b72cdbb7
TL
1773 eval {
1774 my $class;
2c27e4b7 1775 if ($d->{type} eq 'lxc') {
b72cdbb7 1776 $class = 'PVE::LXC::Config';
2c27e4b7 1777 } elsif ($d->{type} eq 'qemu') {
b72cdbb7 1778 $class = 'PVE::QemuConfig';
b92400b6 1779 } else {
dbfcac27 1780 die "unknown virtual guest type '$d->{type}'\n";
b92400b6
DM
1781 }
1782
b72cdbb7
TL
1783 my $conf = $class->load_config($vmid);
1784 return if !$templates && $class->is_template($conf);
1785 return if !$ha_managed && PVE::HA::Config::vm_is_ha_managed($vmid);
04b2004b 1786
b2bb6d77 1787 $res->{$vmid}->{conf} = $conf;
b72cdbb7 1788 $res->{$vmid}->{type} = $d->{type};
1c8dc310 1789 $res->{$vmid}->{class} = $class;
b92400b6
DM
1790 };
1791 warn $@ if $@;
1792 }
1793
b72cdbb7
TL
1794 return $res;
1795};
1796
1797# return all VMs which should get started/stopped on power up/down
1798my $get_start_stop_list = sub {
1799 my ($nodename, $autostart, $vmfilter) = @_;
1800
ae9d10ca
TL
1801 # do not skip HA vms on force or if a specific VMID set is wanted
1802 my $include_ha_managed = defined($vmfilter) ? 1 : 0;
1803
dbfcac27 1804 my $vmlist = $get_filtered_vmlist->($nodename, $vmfilter, undef, $include_ha_managed);
b72cdbb7
TL
1805
1806 my $resList = {};
1807 foreach my $vmid (keys %$vmlist) {
b2bb6d77 1808 my $conf = $vmlist->{$vmid}->{conf};
b72cdbb7
TL
1809 next if $autostart && !$conf->{onboot};
1810
dbfcac27
TL
1811 my $startup = $conf->{startup} ? PVE::JSONSchema::pve_parse_startup_order($conf->{startup}) : {};
1812 my $order = $startup->{order} = $startup->{order} // LONG_MAX;
b72cdbb7 1813
dbfcac27
TL
1814 $resList->{$order}->{$vmid} = $startup;
1815 $resList->{$order}->{$vmid}->{type} = $vmlist->{$vmid}->{type};
b72cdbb7
TL
1816 }
1817
b92400b6
DM
1818 return $resList;
1819};
1820
1c8dc310
TL
1821my $remove_locks_on_startup = sub {
1822 my ($nodename) = @_;
1823
1824 my $vmlist = &$get_filtered_vmlist($nodename, undef, undef, 1);
1825
1826 foreach my $vmid (keys %$vmlist) {
1827 my $conf = $vmlist->{$vmid}->{conf};
23b54109 1828 my $class = $vmlist->{$vmid}->{class};
1c8dc310
TL
1829
1830 eval {
1831 if ($class->has_lock($conf, 'backup')) {
1832 $class->remove_lock($vmid, 'backup');
1833 my $msg = "removed left over backup lock from '$vmid'!";
1834 warn "$msg\n"; # prints to task log
1835 syslog('warning', $msg);
1836 }
1837 }; warn $@ if $@;
1838 }
1839};
1840
b92400b6 1841__PACKAGE__->register_method ({
f9d26e09
TL
1842 name => 'startall',
1843 path => 'startall',
b92400b6
DM
1844 method => 'POST',
1845 protected => 1,
17e3b3b2 1846 permissions => {
6aec4565
FE
1847 description => "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/<ID>' for "
1848 ."each ID passed via the 'vms' parameter.",
1849 user => 'all',
17e3b3b2 1850 },
9c8a09a7 1851 proxyto => 'node',
e1b57809 1852 description => "Start all VMs and containers located on this node (by default only those with onboot=1).",
b92400b6 1853 parameters => {
6110ed03 1854 additionalProperties => 0,
b92400b6
DM
1855 properties => {
1856 node => get_standard_option('pve-node'),
c09c7160
AD
1857 force => {
1858 optional => 1,
1859 type => 'boolean',
e1b57809
TL
1860 default => 'off',
1861 description => "Issue start command even if virtual guest have 'onboot' not set or set to off.",
c09c7160 1862 },
2a498506 1863 vms => {
a740deff 1864 description => "Only consider guests from this comma separated list of VMIDs.",
2a498506
DC
1865 type => 'string', format => 'pve-vmid-list',
1866 optional => 1,
1867 },
b92400b6
DM
1868 },
1869 },
1870 returns => {
1871 type => 'string',
1872 },
1873 code => sub {
1874 my ($param) = @_;
1875
1876 my $rpcenv = PVE::RPCEnvironment::get();
1877 my $authuser = $rpcenv->get_user();
1878
6aec4565
FE
1879 if (!$rpcenv->check($authuser, "/", [ 'VM.PowerMgmt' ], 1)) {
1880 my @vms = PVE::Tools::split_list($param->{vms});
1881 if (scalar(@vms) > 0) {
1882 $rpcenv->check($authuser, "/vms/$_", [ 'VM.PowerMgmt' ]) for @vms;
1883 } else {
1884 raise_perm_exc("/, VM.PowerMgmt");
1885 }
1886 }
1887
b92400b6
DM
1888 my $nodename = $param->{node};
1889 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
1890
c09c7160
AD
1891 my $force = $param->{force};
1892
b92400b6 1893 my $code = sub {
b92400b6
DM
1894 $rpcenv->{type} = 'priv'; # to start tasks in background
1895
3947d0a0
DM
1896 if (!PVE::Cluster::check_cfs_quorum(1)) {
1897 print "waiting for quorum ...\n";
1898 do {
1899 sleep(1);
1900 } while (!PVE::Cluster::check_cfs_quorum(1));
1901 print "got quorum\n";
b92400b6 1902 }
1c8dc310
TL
1903
1904 eval { # remove backup locks, but avoid running into a scheduled backup job
1905 PVE::Tools::lock_file('/var/run/vzdump.lock', 10, $remove_locks_on_startup, $nodename);
f77faab2
TL
1906 };
1907 warn $@ if $@;
1c8dc310 1908
c09c7160 1909 my $autostart = $force ? undef : 1;
f77faab2 1910 my $startList = $get_start_stop_list->($nodename, $autostart, $param->{vms});
3f12bcfb
DM
1911
1912 # Note: use numeric sorting with <=>
f77faab2 1913 for my $order (sort {$a <=> $b} keys %$startList) {
b92400b6
DM
1914 my $vmlist = $startList->{$order};
1915
f77faab2 1916 for my $vmid (sort {$a <=> $b} keys %$vmlist) {
b92400b6
DM
1917 my $d = $vmlist->{$vmid};
1918
1919 PVE::Cluster::check_cfs_quorum(); # abort when we loose quorum
b72cdbb7 1920
b92400b6
DM
1921 eval {
1922 my $default_delay = 0;
1923 my $upid;
1924
2c27e4b7
DM
1925 if ($d->{type} eq 'lxc') {
1926 return if PVE::LXC::check_running($vmid);
1927 print STDERR "Starting CT $vmid\n";
1b5e56f2 1928 $upid = PVE::API2::LXC::Status->vm_start({node => $nodename, vmid => $vmid });
2c27e4b7 1929 } elsif ($d->{type} eq 'qemu') {
d6c49392 1930 $default_delay = 3; # to reduce load
b92400b6
DM
1931 return if PVE::QemuServer::check_running($vmid, 1);
1932 print STDERR "Starting VM $vmid\n";
1933 $upid = PVE::API2::Qemu->vm_start({node => $nodename, vmid => $vmid });
1934 } else {
1935 die "unknown VM type '$d->{type}'\n";
1936 }
1937
f77faab2
TL
1938 my $task = PVE::Tools::upid_decode($upid);
1939 while (PVE::ProcFSTools::check_process_running($task->{pid})) {
b92400b6
DM
1940 sleep(1);
1941 }
1942
1943 my $status = PVE::Tools::upid_read_status($upid);
0a7de337 1944 if (!PVE::Tools::upid_status_is_error($status)) {
b92400b6
DM
1945 # use default delay to reduce load
1946 my $delay = defined($d->{up}) ? int($d->{up}) : $default_delay;
1947 if ($delay > 0) {
1948 print STDERR "Waiting for $delay seconds (startup delay)\n" if $d->{up};
1949 for (my $i = 0; $i < $delay; $i++) {
1950 sleep(1);
1951 }
1952 }
1953 } else {
b27ebcad
TL
1954 my $rendered_type = $d->{type} eq 'lxc' ? 'CT' : 'VM';
1955 print STDERR "Starting $rendered_type $vmid failed: $status\n";
b92400b6
DM
1956 }
1957 };
1958 warn $@ if $@;
1959 }
1960 }
1961 return;
1962 };
1963
1964 return $rpcenv->fork_worker('startall', undef, $authuser, $code);
1965 }});
1966
1967my $create_stop_worker = sub {
11543293 1968 my ($nodename, $type, $vmid, $timeout, $force_stop) = @_;
b92400b6 1969
2c27e4b7
DM
1970 if ($type eq 'lxc') {
1971 return if !PVE::LXC::check_running($vmid);
2c27e4b7 1972 print STDERR "Stopping CT $vmid (timeout = $timeout seconds)\n";
79d62026 1973 return PVE::API2::LXC::Status->vm_shutdown(
11543293 1974 { node => $nodename, vmid => $vmid, timeout => $timeout, forceStop => $force_stop }
79d62026 1975 );
2c27e4b7 1976 } elsif ($type eq 'qemu') {
b92400b6 1977 return if !PVE::QemuServer::check_running($vmid, 1);
88ba9a1d 1978 print STDERR "Stopping VM $vmid (timeout = $timeout seconds)\n";
79d62026 1979 return PVE::API2::Qemu->vm_shutdown(
11543293 1980 { node => $nodename, vmid => $vmid, timeout => $timeout, forceStop => $force_stop }
79d62026 1981 );
b92400b6
DM
1982 } else {
1983 die "unknown VM type '$type'\n";
1984 }
b92400b6
DM
1985};
1986
1987__PACKAGE__->register_method ({
f9d26e09
TL
1988 name => 'stopall',
1989 path => 'stopall',
b92400b6
DM
1990 method => 'POST',
1991 protected => 1,
17e3b3b2 1992 permissions => {
6aec4565
FE
1993 description => "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/<ID>' for "
1994 ."each ID passed via the 'vms' parameter.",
1995 user => 'all',
17e3b3b2 1996 },
9c8a09a7 1997 proxyto => 'node',
b92400b6
DM
1998 description => "Stop all VMs and Containers.",
1999 parameters => {
6110ed03 2000 additionalProperties => 0,
b92400b6
DM
2001 properties => {
2002 node => get_standard_option('pve-node'),
2a498506
DC
2003 vms => {
2004 description => "Only consider Guests with these IDs.",
2005 type => 'string', format => 'pve-vmid-list',
2006 optional => 1,
2007 },
11543293
TL
2008 'force-stop' => {
2009 description => 'Force a hard-stop after the timeout.',
2010 type => 'boolean',
2011 default => 1,
2012 optional => 1,
2013 },
8f063fbc 2014 'timeout' => {
853ce1ea
TL
2015 description => 'Timeout for each guest shutdown task. Depending on `force-stop`,'
2016 .' the shutdown gets then simply aborted or a hard-stop is forced.',
8f063fbc
TL
2017 type => 'integer',
2018 optional => 1,
2019 default => 180,
853ce1ea
TL
2020 minimum => 0,
2021 maximum => 2 * 3600, # mostly arbitrary, but we do not want to high timeouts
8f063fbc 2022 },
b92400b6
DM
2023 },
2024 },
2025 returns => {
2026 type => 'string',
2027 },
2028 code => sub {
2029 my ($param) = @_;
2030
2031 my $rpcenv = PVE::RPCEnvironment::get();
2032 my $authuser = $rpcenv->get_user();
2033
6aec4565
FE
2034 if (!$rpcenv->check($authuser, "/", [ 'VM.PowerMgmt' ], 1)) {
2035 my @vms = PVE::Tools::split_list($param->{vms});
2036 if (scalar(@vms) > 0) {
2037 $rpcenv->check($authuser, "/vms/$_", [ 'VM.PowerMgmt' ]) for @vms;
2038 } else {
2039 raise_perm_exc("/, VM.PowerMgmt");
2040 }
2041 }
2042
b92400b6
DM
2043 my $nodename = $param->{node};
2044 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
2045
2046 my $code = sub {
2047
2048 $rpcenv->{type} = 'priv'; # to start tasks in background
2049
f77faab2 2050 my $stopList = $get_start_stop_list->($nodename, undef, $param->{vms});
b92400b6
DM
2051
2052 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
89ceb802
TL
2053 my $datacenterconfig = cfs_read_file('datacenter.cfg');
2054 # if not set by user spawn max cpu count number of workers
2055 my $maxWorkers = $datacenterconfig->{max_workers} || $cpuinfo->{cpus};
b92400b6 2056
f77faab2 2057 for my $order (sort {$b <=> $a} keys %$stopList) {
b92400b6
DM
2058 my $vmlist = $stopList->{$order};
2059 my $workers = {};
37bec895
DM
2060
2061 my $finish_worker = sub {
2062 my $pid = shift;
f77faab2 2063 my $worker = delete $workers->{$pid} || return;
37bec895 2064
f77faab2 2065 syslog('info', "end task $worker->{upid}");
37bec895
DM
2066 };
2067
f77faab2 2068 for my $vmid (sort {$b <=> $a} keys %$vmlist) {
b92400b6 2069 my $d = $vmlist->{$vmid};
8f063fbc 2070 my $timeout = int($d->{down} // $param->{timeout} // 180);
11543293
TL
2071 my $upid = eval {
2072 $create_stop_worker->(
2073 $nodename, $d->{type}, $vmid, $timeout, $param->{'force-stop'} // 1)
2074 };
b92400b6 2075 warn $@ if $@;
37bec895
DM
2076 next if !$upid;
2077
f77faab2
TL
2078 my $task = PVE::Tools::upid_decode($upid, 1);
2079 next if !$task;
37bec895 2080
f77faab2 2081 my $pid = $task->{pid};
37bec895
DM
2082
2083 $workers->{$pid} = { type => $d->{type}, upid => $upid, vmid => $vmid };
b92400b6
DM
2084 while (scalar(keys %$workers) >= $maxWorkers) {
2085 foreach my $p (keys %$workers) {
2086 if (!PVE::ProcFSTools::check_process_running($p)) {
f77faab2 2087 $finish_worker->($p);
b92400b6
DM
2088 }
2089 }
2090 sleep(1);
2091 }
2092 }
2093 while (scalar(keys %$workers)) {
f77faab2 2094 for my $p (keys %$workers) {
b92400b6 2095 if (!PVE::ProcFSTools::check_process_running($p)) {
f77faab2 2096 $finish_worker->($p);
b92400b6
DM
2097 }
2098 }
2099 sleep(1);
2100 }
2101 }
37bec895
DM
2102
2103 syslog('info', "all VMs and CTs stopped");
2104
b92400b6
DM
2105 return;
2106 };
2107
2108 return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
b92400b6
DM
2109 }});
2110
5f04abc2
HL
2111my $create_suspend_worker = sub {
2112 my ($nodename, $vmid) = @_;
25c0052a
TL
2113 if (!PVE::QemuServer::check_running($vmid, 1)) {
2114 print "VM $vmid not running, skipping suspension\n";
2115 return;
2116 }
5f04abc2
HL
2117 print STDERR "Suspending VM $vmid\n";
2118 return PVE::API2::Qemu->vm_suspend(
2119 { node => $nodename, vmid => $vmid, todisk => 1 }
2120 );
2121};
2122
2123__PACKAGE__->register_method ({
2124 name => 'suspendall',
2125 path => 'suspendall',
2126 method => 'POST',
2127 protected => 1,
2128 permissions => {
2129 description => "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/<ID>' for each"
2130 ." ID passed via the 'vms' parameter. Additionally, you need 'VM.Config.Disk' on the"
2131 ." '/vms/{vmid}' path and 'Datastore.AllocateSpace' for the configured state-storage(s)",
2132 user => 'all',
2133 },
2134 proxyto => 'node',
2135 description => "Suspend all VMs.",
2136 parameters => {
2137 additionalProperties => 0,
2138 properties => {
2139 node => get_standard_option('pve-node'),
2140 vms => {
2141 description => "Only consider Guests with these IDs.",
2142 type => 'string', format => 'pve-vmid-list',
2143 optional => 1,
2144 },
2145 },
2146 },
2147 returns => {
2148 type => 'string',
2149 },
2150 code => sub {
2151 my ($param) = @_;
2152
2153 my $rpcenv = PVE::RPCEnvironment::get();
2154 my $authuser = $rpcenv->get_user();
2155
2156 # we cannot really check access to the state-storage here, that's happening per worker.
2157 if (!$rpcenv->check($authuser, "/", [ 'VM.PowerMgmt', 'VM.Config.Disk' ], 1)) {
2158 my @vms = PVE::Tools::split_list($param->{vms});
2159 if (scalar(@vms) > 0) {
2160 $rpcenv->check($authuser, "/vms/$_", [ 'VM.PowerMgmt' ]) for @vms;
2161 } else {
84e1e9d9 2162 raise_perm_exc("/, VM.PowerMgmt && VM.Config.Disk");
5f04abc2
HL
2163 }
2164 }
2165
2166 my $nodename = $param->{node};
2167 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
2168
2169 my $code = sub {
2170
2171 $rpcenv->{type} = 'priv'; # to start tasks in background
2172
84e1e9d9 2173 my $toSuspendList = $get_start_stop_list->($nodename, undef, $param->{vms});
5f04abc2
HL
2174
2175 my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
2176 my $datacenterconfig = cfs_read_file('datacenter.cfg');
2177 # if not set by user spawn max cpu count number of workers
2178 my $maxWorkers = $datacenterconfig->{max_workers} || $cpuinfo->{cpus};
2179
84e1e9d9
TL
2180 for my $order (sort {$b <=> $a} keys %$toSuspendList) {
2181 my $vmlist = $toSuspendList->{$order};
5f04abc2
HL
2182 my $workers = {};
2183
2184 my $finish_worker = sub {
2185 my $pid = shift;
2186 my $worker = delete $workers->{$pid} || return;
2187
2188 syslog('info', "end task $worker->{upid}");
2189 };
2190
2191 for my $vmid (sort {$b <=> $a} keys %$vmlist) {
2192 my $d = $vmlist->{$vmid};
ebb71cb5
TL
2193 if ($d->{type} ne 'qemu') {
2194 log_warn("skipping $vmid, only VMs can be suspended");
5f04abc2
HL
2195 next;
2196 }
2197 my $upid = eval {
2198 $create_suspend_worker->($nodename, $vmid)
2199 };
2200 warn $@ if $@;
2201 next if !$upid;
2202
2203 my $task = PVE::Tools::upid_decode($upid, 1);
2204 next if !$task;
2205
2206 my $pid = $task->{pid};
5f04abc2 2207 $workers->{$pid} = { type => $d->{type}, upid => $upid, vmid => $vmid };
84e1e9d9 2208
5f04abc2 2209 while (scalar(keys %$workers) >= $maxWorkers) {
84e1e9d9 2210 for my $p (keys %$workers) {
5f04abc2
HL
2211 if (!PVE::ProcFSTools::check_process_running($p)) {
2212 $finish_worker->($p);
2213 }
2214 }
2215 sleep(1);
2216 }
2217 }
2218 while (scalar(keys %$workers)) {
2219 for my $p (keys %$workers) {
2220 if (!PVE::ProcFSTools::check_process_running($p)) {
2221 $finish_worker->($p);
2222 }
2223 }
2224 sleep(1);
2225 }
2226 }
2227
2228 syslog('info', "all VMs suspended");
2229
2230 return;
2231 };
2232
2233 return $rpcenv->fork_worker('suspendall', undef, $authuser, $code);
2234 }});
2235
2236
9c8a09a7 2237my $create_migrate_worker = sub {
fc6b77a1 2238 my ($nodename, $type, $vmid, $target, $with_local_disks) = @_;
9c8a09a7
AD
2239
2240 my $upid;
2c27e4b7
DM
2241 if ($type eq 'lxc') {
2242 my $online = PVE::LXC::check_running($vmid) ? 1 : 0;
2243 print STDERR "Migrating CT $vmid\n";
f77faab2
TL
2244 $upid = PVE::API2::LXC->migrate_vm(
2245 { node => $nodename, vmid => $vmid, target => $target, restart => $online });
2c27e4b7 2246 } elsif ($type eq 'qemu') {
0b54f653
TL
2247 print STDERR "Check VM $vmid: ";
2248 *STDERR->flush();
9c8a09a7 2249 my $online = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
f77faab2
TL
2250 my $preconditions = PVE::API2::Qemu->migrate_vm_precondition(
2251 {node => $nodename, vmid => $vmid, target => $target});
0b54f653
TL
2252 my $invalidConditions = '';
2253 if ($online && !$with_local_disks && scalar @{$preconditions->{local_disks}}) {
f77faab2
TL
2254 $invalidConditions .= "\n Has local disks: ";
2255 $invalidConditions .= join(', ', map { $_->{volid} } @{$preconditions->{local_disks}});
d8d17271
TM
2256 }
2257
2258 if (@{$preconditions->{local_resources}}) {
f77faab2
TL
2259 $invalidConditions .= "\n Has local resources: ";
2260 $invalidConditions .= join(', ', @{$preconditions->{local_resources}});
d8d17271
TM
2261 }
2262
0b54f653
TL
2263 if ($invalidConditions && $invalidConditions ne '') {
2264 print STDERR "skip VM $vmid - precondition check failed:";
2265 die "$invalidConditions\n";
2266 }
2267 print STDERR "precondition check passed\n";
9c8a09a7 2268 print STDERR "Migrating VM $vmid\n";
71fd3de9
TL
2269
2270 my $params = {
6f3d18dd
TL
2271 node => $nodename,
2272 vmid => $vmid,
2273 target => $target,
2274 online => $online,
71fd3de9 2275 };
13411f99 2276 $params->{'with-local-disks'} = $with_local_disks if defined($with_local_disks);
71fd3de9
TL
2277
2278 $upid = PVE::API2::Qemu->migrate_vm($params);
9c8a09a7
AD
2279 } else {
2280 die "unknown VM type '$type'\n";
2281 }
2282
f77faab2 2283 my $task = PVE::Tools::upid_decode($upid);
9c8a09a7 2284
f77faab2 2285 return $task->{pid};
9c8a09a7
AD
2286};
2287
2288__PACKAGE__->register_method ({
2289 name => 'migrateall',
2290 path => 'migrateall',
2291 method => 'POST',
2292 proxyto => 'node',
2293 protected => 1,
17e3b3b2 2294 permissions => {
6aec4565
FE
2295 description => "The 'VM.Migrate' permission is required on '/' or on '/vms/<ID>' for each "
2296 ."ID passed via the 'vms' parameter.",
2297 user => 'all',
17e3b3b2 2298 },
9c8a09a7
AD
2299 description => "Migrate all VMs and Containers.",
2300 parameters => {
2301 additionalProperties => 0,
2302 properties => {
2303 node => get_standard_option('pve-node'),
58ab77d1 2304 target => get_standard_option('pve-node', { description => "Target node." }),
f77faab2
TL
2305 maxworkers => {
2306 description => "Maximal number of parallel migration job. If not set, uses"
2307 ."'max_workers' from datacenter.cfg. One of both must be set!",
89ceb802 2308 optional => 1,
f77faab2
TL
2309 type => 'integer',
2310 minimum => 1
58ab77d1 2311 },
2a498506
DC
2312 vms => {
2313 description => "Only consider Guests with these IDs.",
2314 type => 'string', format => 'pve-vmid-list',
2315 optional => 1,
2316 },
fc6b77a1
TM
2317 "with-local-disks" => {
2318 type => 'boolean',
2319 description => "Enable live storage migration for local disk",
2320 optional => 1,
2321 },
9c8a09a7
AD
2322 },
2323 },
2324 returns => {
2325 type => 'string',
2326 },
2327 code => sub {
2328 my ($param) = @_;
2329
2330 my $rpcenv = PVE::RPCEnvironment::get();
2331 my $authuser = $rpcenv->get_user();
2332
6aec4565
FE
2333 if (!$rpcenv->check($authuser, "/", [ 'VM.Migrate' ], 1)) {
2334 my @vms = PVE::Tools::split_list($param->{vms});
2335 if (scalar(@vms) > 0) {
2336 $rpcenv->check($authuser, "/vms/$_", [ 'VM.Migrate' ]) for @vms;
2337 } else {
2338 raise_perm_exc("/, VM.Migrate");
2339 }
2340 }
2341
9c8a09a7
AD
2342 my $nodename = $param->{node};
2343 $nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
2344
73981e39 2345 my $target = $param->{target};
fc6b77a1 2346 my $with_local_disks = $param->{'with-local-disks'};
73981e39
DC
2347 raise_param_exc({ target => "target is local node."}) if $target eq $nodename;
2348
2349 PVE::Cluster::check_cfs_quorum();
2350
2351 PVE::Cluster::check_node_exists($target);
89ceb802
TL
2352
2353 my $datacenterconfig = cfs_read_file('datacenter.cfg');
2354 # prefer parameter over datacenter cfg settings
2355 my $maxWorkers = $param->{maxworkers} || $datacenterconfig->{max_workers} ||
2356 die "either 'maxworkers' parameter or max_workers in datacenter.cfg must be set!\n";
9c8a09a7
AD
2357
2358 my $code = sub {
9c8a09a7
AD
2359 $rpcenv->{type} = 'priv'; # to start tasks in background
2360
f5c1dde5 2361 my $vmlist = &$get_filtered_vmlist($nodename, $param->{vms}, 1, 1);
49652c46
TL
2362 if (!scalar(keys %$vmlist)) {
2363 warn "no virtual guests matched, nothing to do..\n";
2364 return;
2365 }
9c8a09a7 2366
f5c1dde5 2367 my $workers = {};
49652c46 2368 my $workers_started = 0;
f5c1dde5
TL
2369 foreach my $vmid (sort keys %$vmlist) {
2370 my $d = $vmlist->{$vmid};
2371 my $pid;
fc6b77a1 2372 eval { $pid = &$create_migrate_worker($nodename, $d->{type}, $vmid, $target, $with_local_disks); };
f5c1dde5
TL
2373 warn $@ if $@;
2374 next if !$pid;
9c8a09a7 2375
49652c46 2376 $workers_started++;
f5c1dde5
TL
2377 $workers->{$pid} = 1;
2378 while (scalar(keys %$workers) >= $maxWorkers) {
9c8a09a7
AD
2379 foreach my $p (keys %$workers) {
2380 if (!PVE::ProcFSTools::check_process_running($p)) {
2381 delete $workers->{$p};
2382 }
2383 }
2384 sleep(1);
2385 }
2386 }
f5c1dde5
TL
2387 while (scalar(keys %$workers)) {
2388 foreach my $p (keys %$workers) {
8e4bee65 2389 # FIXME: what about PID re-use ?!?!
f5c1dde5
TL
2390 if (!PVE::ProcFSTools::check_process_running($p)) {
2391 delete $workers->{$p};
2392 }
2393 }
2394 sleep(1);
2395 }
49652c46
TL
2396 if ($workers_started <= 0) {
2397 die "no migrations worker started...\n";
2398 }
2399 print STDERR "All jobs finished, used $workers_started workers in total.\n";
9c8a09a7
AD
2400 return;
2401 };
2402
2403 return $rpcenv->fork_worker('migrateall', undef, $authuser, $code);
2404
2405 }});
2406
820d0458
DC
2407__PACKAGE__->register_method ({
2408 name => 'get_etc_hosts',
2409 path => 'hosts',
2410 method => 'GET',
2411 proxyto => 'node',
2412 protected => 1,
2413 permissions => {
2414 check => ['perm', '/', [ 'Sys.Audit' ]],
2415 },
2416 description => "Get the content of /etc/hosts.",
2417 parameters => {
2418 additionalProperties => 0,
2419 properties => {
2420 node => get_standard_option('pve-node'),
2421 },
2422 },
2423 returns => {
2424 type => 'object',
2425 properties => {
2426 digest => get_standard_option('pve-config-digest'),
2427 data => {
2428 type => 'string',
2429 description => 'The content of /etc/hosts.'
2430 },
2431 },
2432 },
2433 code => sub {
2434 my ($param) = @_;
2435
2436 return PVE::INotify::read_file('etchosts');
2437
2438 }});
2439
2440__PACKAGE__->register_method ({
2441 name => 'write_etc_hosts',
2442 path => 'hosts',
2443 method => 'POST',
2444 proxyto => 'node',
2445 protected => 1,
2446 permissions => {
2447 check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
2448 },
2449 description => "Write /etc/hosts.",
2450 parameters => {
2451 additionalProperties => 0,
2452 properties => {
2453 node => get_standard_option('pve-node'),
2454 digest => get_standard_option('pve-config-digest'),
2455 data => {
2456 type => 'string',
2457 description => 'The target content of /etc/hosts.'
2458 },
2459 },
2460 },
2461 returns => {
2462 type => 'null',
2463 },
2464 code => sub {
2465 my ($param) = @_;
2466
79d62026 2467 PVE::Tools::lock_file('/var/lock/pve-etchosts.lck', undef, sub {
820d0458
DC
2468 if ($param->{digest}) {
2469 my $hosts = PVE::INotify::read_file('etchosts');
2470 PVE::Tools::assert_if_modified($hosts->{digest}, $param->{digest});
2471 }
2472 PVE::INotify::write_file('etchosts', $param->{data});
2473 });
2474 die $@ if $@;
2475
79d62026 2476 return;
820d0458
DC
2477 }});
2478
82282acf
WL
2479# bash completion helper
2480
2481sub complete_templet_repo {
2482 my ($cmdname, $pname, $cvalue) = @_;
2483
2484 my $repo = PVE::APLInfo::load_data();
2485 my $res = [];
2486 foreach my $templ (keys %{$repo->{all}}) {
2487 next if $templ !~ m/^$cvalue/;
2488 push @$res, $templ;
2489 }
2490
2491 return $res;
2492}
2493
aff192e6
DM
2494package PVE::API2::Nodes;
2495
2496use strict;
2497use warnings;
2498
2499use PVE::SafeSyslog;
2500use PVE::Cluster;
2501use PVE::RESTHandler;
2502use PVE::RPCEnvironment;
b193e4ac 2503use PVE::API2Tools;
f57cbe5d 2504use PVE::JSONSchema qw(get_standard_option);
aff192e6
DM
2505
2506use base qw(PVE::RESTHandler);
2507
2508__PACKAGE__->register_method ({
f9d26e09 2509 subclass => "PVE::API2::Nodes::Nodeinfo",
aff192e6
DM
2510 path => '{node}',
2511});
2512
2513__PACKAGE__->register_method ({
f9d26e09
TL
2514 name => 'index',
2515 path => '',
aff192e6
DM
2516 method => 'GET',
2517 permissions => { user => 'all' },
2518 description => "Cluster node index.",
2519 parameters => {
6110ed03 2520 additionalProperties => 0,
aff192e6
DM
2521 properties => {},
2522 },
2523 returns => {
2524 type => 'array',
2525 items => {
2526 type => "object",
f57cbe5d
DM
2527 properties => {
2528 node => get_standard_option('pve-node'),
2529 status => {
2530 description => "Node status.",
2531 type => 'string',
2532 enum => ['unknown', 'online', 'offline'],
2533 },
2534 cpu => {
2535 description => "CPU utilization.",
2536 type => 'number',
2537 optional => 1,
2538 renderer => 'fraction_as_percentage',
2539 },
2540 maxcpu => {
2541 description => "Number of available CPUs.",
2542 type => 'integer',
2543 optional => 1,
2544 },
2545 mem => {
2546 description => "Used memory in bytes.",
4a512d7a 2547 type => 'integer',
f57cbe5d
DM
2548 optional => 1,
2549 renderer => 'bytes',
2550 },
2551 maxmem => {
2552 description => "Number of available memory in bytes.",
2553 type => 'integer',
2554 optional => 1,
2555 renderer => 'bytes',
2556 },
2557 level => {
2558 description => "Support level.",
2559 type => 'string',
2560 optional => 1,
2561 },
2562 uptime => {
2563 description => "Node uptime in seconds.",
2564 type => 'integer',
2565 optional => 1,
2566 renderer => 'duration',
2567 },
2568 ssl_fingerprint => {
2569 description => "The SSL fingerprint for the node certificate.",
2570 type => 'string',
2571 optional => 1,
2572 },
2573 },
aff192e6 2574 },
b193e4ac 2575 links => [ { rel => 'child', href => "{node}" } ],
aff192e6
DM
2576 },
2577 code => sub {
2578 my ($param) = @_;
f9d26e09 2579
57d56896
TL
2580 my $rpcenv = PVE::RPCEnvironment::get();
2581 my $authuser = $rpcenv->get_user();
2582
aff192e6
DM
2583 my $clinfo = PVE::Cluster::get_clinfo();
2584 my $res = [];
2585
b193e4ac
DM
2586 my $nodelist = PVE::Cluster::get_nodelist();
2587 my $members = PVE::Cluster::get_members();
aff192e6
DM
2588 my $rrd = PVE::Cluster::rrd_dump();
2589
b193e4ac 2590 foreach my $node (@$nodelist) {
57d56896
TL
2591 my $can_audit = $rpcenv->check($authuser, "/nodes/$node", [ 'Sys.Audit' ], 1);
2592 my $entry = PVE::API2Tools::extract_node_stats($node, $members, $rrd, !$can_audit);
1a664b0f
FG
2593
2594 $entry->{ssl_fingerprint} = eval { PVE::Cluster::get_node_fingerprint($node) };
2595 warn "$@" if $@;
2596
aff192e6
DM
2597 push @$res, $entry;
2598 }
2599
2600 return $res;
2601 }});
2602
26031;