1 package PVE
::API2
::Nodes
::Nodeinfo
;
9 use HTTP
::Status
qw(:constants);
11 use POSIX
qw(LONG_MAX);
12 use Time
::Local
qw(timegm_nocheck);
18 use PVE
::AccessControl
;
19 use PVE
::Cluster
qw(cfs_read_file);
20 use PVE
::DataCenterConfig
;
21 use PVE
::Exception
qw(raise raise_perm_exc raise_param_exc);
24 use PVE
::HA
::Env
::PVE2
;
26 use PVE
::JSONSchema
qw(get_standard_option);
32 use PVE
::RESTEnvironment
qw(log_warn);
34 use PVE
::RPCEnvironment
;
39 use PVE
::Tools
qw(file_get_contents);
43 use PVE
::API2
::Capabilities
;
45 use PVE
::API2
::Certificates
;
47 use PVE
::API2
::Firewall
::Host
;
48 use PVE
::API2
::Hardware
;
49 use PVE
::API2
::LXC
::Status
;
51 use PVE
::API2
::Network
;
52 use PVE
::API2
::NodeConfig
;
53 use PVE
::API2
::Qemu
::CPU
;
55 use PVE
::API2
::Replication
;
56 use PVE
::API2
::Services
;
57 use PVE
::API2
::Storage
::Scan
;
58 use PVE
::API2
::Storage
::Status
;
59 use PVE
::API2
::Subscription
;
61 use PVE
::API2
::VZDump
;
65 require PVE
::API2
::Network
::SDN
::Zones
::Status
;
69 use base
qw(PVE::RESTHandler);
71 my $verify_command_item_desc = {
72 description
=> "An array of objects describing endpoints, methods and arguments.",
78 description
=> "A relative path to an API endpoint on this node.",
83 description
=> "A method related to the API endpoint (GET, POST etc.).",
85 pattern
=> "(GET|POST|PUT|DELETE)",
89 description
=> "A set of parameter names and their values.",
97 PVE
::JSONSchema
::register_format
('pve-command-batch', \
&verify_command_batch
);
98 sub verify_command_batch
{
99 my ($value, $noerr) = @_;
100 my $commands = eval { decode_json
($value); };
102 return if $noerr && $@;
103 die "commands param did not contain valid JSON: $@" if $@;
105 eval { PVE
::JSONSchema
::validate
($commands, $verify_command_item_desc) };
107 return $commands if !$@;
110 die "commands is not a valid array of commands: $@";
113 __PACKAGE__-
>register_method ({
114 subclass
=> "PVE::API2::Qemu",
118 __PACKAGE__-
>register_method ({
119 subclass
=> "PVE::API2::LXC",
123 __PACKAGE__-
>register_method ({
124 subclass
=> "PVE::API2::Ceph",
128 __PACKAGE__-
>register_method ({
129 subclass
=> "PVE::API2::VZDump",
133 __PACKAGE__-
>register_method ({
134 subclass
=> "PVE::API2::Services",
138 __PACKAGE__-
>register_method ({
139 subclass
=> "PVE::API2::Subscription",
140 path
=> 'subscription',
143 __PACKAGE__-
>register_method ({
144 subclass
=> "PVE::API2::Network",
148 __PACKAGE__-
>register_method ({
149 subclass
=> "PVE::API2::Tasks",
153 __PACKAGE__-
>register_method ({
154 subclass
=> "PVE::API2::Storage::Scan",
158 __PACKAGE__-
>register_method ({
159 subclass
=> "PVE::API2::Hardware",
163 __PACKAGE__-
>register_method ({
164 subclass
=> "PVE::API2::Capabilities",
165 path
=> 'capabilities',
168 __PACKAGE__-
>register_method ({
169 subclass
=> "PVE::API2::Storage::Status",
173 __PACKAGE__-
>register_method ({
174 subclass
=> "PVE::API2::Disks",
178 __PACKAGE__-
>register_method ({
179 subclass
=> "PVE::API2::APT",
183 __PACKAGE__-
>register_method ({
184 subclass
=> "PVE::API2::Firewall::Host",
188 __PACKAGE__-
>register_method ({
189 subclass
=> "PVE::API2::Replication",
190 path
=> 'replication',
193 __PACKAGE__-
>register_method ({
194 subclass
=> "PVE::API2::Certificates",
195 path
=> 'certificates',
199 __PACKAGE__-
>register_method ({
200 subclass
=> "PVE::API2::NodeConfig",
205 __PACKAGE__-
>register_method ({
206 subclass
=> "PVE::API2::Network::SDN::Zones::Status",
210 __PACKAGE__-
>register_method ({
214 permissions
=> { user
=> 'all' },
215 description
=> "SDN index.",
217 additionalProperties
=> 0,
219 node
=> get_standard_option
('pve-node'),
228 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
240 __PACKAGE__-
>register_method ({
244 permissions
=> { user
=> 'all' },
245 description
=> "Node index.",
247 additionalProperties
=> 0,
249 node
=> get_standard_option
('pve-node'),
258 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
264 { name
=> 'aplinfo' },
266 { name
=> 'capabilities' },
268 { name
=> 'certificates' },
269 { name
=> 'config' },
272 { name
=> 'firewall' },
273 { name
=> 'hardware' },
275 { name
=> 'journal' },
277 { name
=> 'migrateall' },
278 { name
=> 'netstat' },
279 { name
=> 'network' },
281 { name
=> 'query-url-metadata' },
282 { name
=> 'replication' },
283 { name
=> 'report' },
284 { name
=> 'rrd' }, # fixme: remove?
285 { name
=> 'rrddata' },# fixme: remove?
287 { name
=> 'services' },
288 { name
=> 'spiceshell' },
289 { name
=> 'startall' },
290 { name
=> 'status' },
291 { name
=> 'stopall' },
292 { name
=> 'storage' },
293 { name
=> 'subscription' },
294 { name
=> 'suspendall' },
295 { name
=> 'syslog' },
297 { name
=> 'termproxy' },
299 { name
=> 'version' },
300 { name
=> 'vncshell' },
301 { name
=> 'vzdump' },
302 { name
=> 'wakeonlan' },
305 push @$result, { name
=> 'sdn' } if $have_sdn;
310 __PACKAGE__-
>register_method ({
315 permissions
=> { user
=> 'all' },
316 description
=> "API version details",
318 additionalProperties
=> 0,
320 node
=> get_standard_option
('pve-node'),
328 description
=> 'The current installed pve-manager package version',
332 description
=> 'The current installed Proxmox VE Release',
336 description
=> 'The short git commit hash ID from which this version was build',
341 my ($resp, $param) = @_;
343 return PVE
::pvecfg
::version_info
();
346 my sub get_current_kernel_info
{
347 my ($sysname, $nodename, $release, $version, $machine) = POSIX
::uname
();
349 my $kernel_version_string = "$sysname $release $version"; # for legacy compat
350 my $current_kernel = {
356 return ($current_kernel, $kernel_version_string);
359 my $boot_mode_info_cache;
360 my sub get_boot_mode_info
{
361 return $boot_mode_info_cache if defined($boot_mode_info_cache);
363 my $is_efi_booted = -d
"/sys/firmware/efi";
365 $boot_mode_info_cache = {
366 mode
=> $is_efi_booted ?
'efi' : 'legacy-bios',
369 my $efi_var = "/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c";
371 if ($is_efi_booted && -e
$efi_var) {
372 my $efi_var_sec_boot_entry = eval { file_get_contents
($efi_var) };
374 warn "Failed to read secure boot state: $@\n";
376 my @secureboot = unpack("CCCCC", $efi_var_sec_boot_entry);
377 $boot_mode_info_cache->{secureboot
} = $secureboot[4] == 1 ?
1 : 0;
380 return $boot_mode_info_cache;
383 __PACKAGE__-
>register_method({
388 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
390 description
=> "Read node status",
393 additionalProperties
=> 0,
395 node
=> get_standard_option
('pve-node'),
400 additionalProperties
=> 1,
402 # TODO: document remaing ones
404 description
=> "Meta-information about the boot mode.",
408 description
=> 'Through which firmware the system got booted.',
410 enum
=> [qw(efi legacy-bios)],
413 description
=> 'System is booted in secure mode, only applicable for the "efi" mode.',
419 'current-kernel' => {
420 description
=> "The uptime of the system in seconds.",
424 description
=> 'OS kernel name (e.g., "Linux")',
428 description
=> 'OS kernel release (e.g., "6.8.0")',
432 description
=> 'OS kernel version with build info',
436 description
=> 'Hardware (architecture) type',
451 my ($uptime, $idle) = PVE
::ProcFSTools
::read_proc_uptime
();
452 $res->{uptime
} = $uptime;
454 my ($avg1, $avg5, $avg15) = PVE
::ProcFSTools
::read_loadavg
();
455 $res->{loadavg
} = [ $avg1, $avg5, $avg15];
457 my ($current_kernel_info, $kversion_string) = get_current_kernel_info
();
458 $res->{kversion
} = $kversion_string;
459 $res->{'current-kernel'} = $current_kernel_info;
461 $res->{'boot-info'} = get_boot_mode_info
();
463 $res->{cpuinfo
} = PVE
::ProcFSTools
::read_cpuinfo
();
465 my $stat = PVE
::ProcFSTools
::read_proc_stat
();
466 $res->{cpu
} = $stat->{cpu
};
467 $res->{wait} = $stat->{wait};
469 my $meminfo = PVE
::ProcFSTools
::read_meminfo
();
471 free
=> $meminfo->{memfree
},
472 total
=> $meminfo->{memtotal
},
473 used
=> $meminfo->{memused
},
477 shared
=> $meminfo->{memshared
},
481 free
=> $meminfo->{swapfree
},
482 total
=> $meminfo->{swaptotal
},
483 used
=> $meminfo->{swapused
},
486 $res->{pveversion
} = PVE
::pvecfg
::package() . "/" .
487 PVE
::pvecfg
::version_text
();
489 my $dinfo = df
('/', 1); # output is bytes
492 total
=> $dinfo->{blocks
},
493 avail
=> $dinfo->{bavail
},
494 used
=> $dinfo->{used
},
495 free
=> $dinfo->{blocks
} - $dinfo->{used
},
501 __PACKAGE__-
>register_method({
506 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
508 description
=> "Read tap/vm network device interface counters",
511 additionalProperties
=> 0,
513 node
=> get_standard_option
('pve-node'),
528 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
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);
536 in => $netdev->{$dev}->{transmit
},
537 out
=> $netdev->{$dev}->{receive
},
544 __PACKAGE__-
>register_method({
548 description
=> "Execute multiple commands in order, root only.",
550 protected
=> 1, # avoid problems with proxy code
552 additionalProperties
=> 0,
554 node
=> get_standard_option
('pve-node'),
556 description
=> "JSON encoded array of commands.",
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'),
560 format
=> "pve-command-batch",
575 my $rpcenv = PVE
::RPCEnvironment
::get
();
576 my $user = $rpcenv->get_user();
577 # just parse the json again, it should already be validated
578 my $commands = eval { decode_json
($param->{commands
}); };
580 foreach my $cmd (@$commands) {
584 my $path = "nodes/$param->{node}/$cmd->{path}";
587 my ($handler, $info) = PVE
::API2-
>find_handler($cmd->{method}, $path, $uri_param);
588 if (!$handler || !$info) {
589 die "no handler for '$path'\n";
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};
597 # check access permissions
598 $rpcenv->check_api2_permissions($info->{permissions
}, $user, $uri_param);
602 data
=> $handler->handle($info, $uri_param),
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
};
612 $resp->{message
} = $err;
622 __PACKAGE__-
>register_method({
627 check
=> ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
630 description
=> "Reboot or shutdown a node.",
633 additionalProperties
=> 0,
635 node
=> get_standard_option
('pve-node'),
637 description
=> "Specify the command.",
639 enum
=> [qw(reboot shutdown)],
643 returns
=> { type
=> "null" },
647 if ($param->{command
} eq 'reboot') {
648 system ("(sleep 2;/sbin/reboot)&");
649 } elsif ($param->{command
} eq 'shutdown') {
650 system ("(sleep 2;/sbin/poweroff)&");
656 __PACKAGE__-
>register_method({
661 check
=> ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
664 description
=> "Try to wake a node via 'wake on LAN' network packet.",
666 additionalProperties
=> 0,
668 node
=> get_standard_option
('pve-node', {
669 description
=> 'target node for wake on LAN packet',
671 my $members = PVE
::Cluster
::get_members
();
672 return [ grep { !$members->{$_}->{online
} } keys %$members ];
679 format
=> 'mac-addr',
680 description
=> 'MAC address used to assemble the WoL magic packet.',
685 my $node = $param->{node
};
686 my $local_node = PVE
::INotify
::nodename
();
688 die "'$node' is local node, cannot wake my self!\n"
689 if $node eq 'localhost' || $node eq $local_node;
691 PVE
::Cluster
::check_node_exists
($node);
693 my $config = PVE
::NodeConfig
::load_config
($node);
694 my $wol_config = PVE
::NodeConfig
::get_wakeonlan_config
($config);
695 my $mac_addr = $wol_config->{mac
};
696 if (!defined($mac_addr)) {
697 die "No wake on LAN MAC address defined for '$node'!\n";
700 my $local_config = PVE
::NodeConfig
::load_config
($local_node);
701 my $local_wol_config = PVE
::NodeConfig
::get_wakeonlan_config
($local_config);
702 my $broadcast_addr = $local_wol_config->{'broadcast-address'} // '255.255.255.255';
705 my $packet = chr(0xff) x
6 . pack('H*', $mac_addr) x
16;
707 my $addr = gethostbyname($broadcast_addr);
708 my $port = getservbyname('discard', 'udp');
709 my $to = Socket
::pack_sockaddr_in
($port, $addr);
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";
716 if (defined(my $bind_iface = $local_wol_config->{'bind-interface'})) {
717 my $bind_iface_raw = pack('Z*', $bind_iface); # Null terminated interface name
718 setsockopt($sock, Socket
::SOL_SOCKET
, Socket
::SO_BINDTODEVICE
, $bind_iface_raw)
719 || die "Unable to bind socket to interface '$bind_iface': $!\n";
722 send($sock, $packet, 0, $to)
723 || die "Unable to send packet: $!\n";
727 return $wol_config->{mac
};
730 __PACKAGE__-
>register_method({
734 protected
=> 1, # fixme: can we avoid that?
736 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
738 description
=> "Read node RRD statistics (returns PNG)",
740 additionalProperties
=> 0,
742 node
=> get_standard_option
('pve-node'),
744 description
=> "Specify the time frame you are interested in.",
746 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
749 description
=> "The list of datasources you want to display.",
750 type
=> 'string', format
=> 'pve-configid-list',
753 description
=> "The RRD consolidation function",
755 enum
=> [ 'AVERAGE', 'MAX' ],
763 filename
=> { type
=> 'string' },
769 return PVE
::RRD
::create_rrd_graph
(
770 "pve2-node/$param->{node}", $param->{timeframe
},
771 $param->{ds
}, $param->{cf
});
775 __PACKAGE__-
>register_method({
779 protected
=> 1, # fixme: can we avoid that?
781 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
783 description
=> "Read node RRD statistics",
785 additionalProperties
=> 0,
787 node
=> get_standard_option
('pve-node'),
789 description
=> "Specify the time frame you are interested in.",
791 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
794 description
=> "The RRD consolidation function",
796 enum
=> [ 'AVERAGE', 'MAX' ],
811 return PVE
::RRD
::create_rrd_data
(
812 "pve2-node/$param->{node}", $param->{timeframe
}, $param->{cf
});
815 __PACKAGE__-
>register_method({
819 description
=> "Read system log",
822 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
826 additionalProperties
=> 0,
828 node
=> get_standard_option
('pve-node'),
841 pattern
=> '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
842 description
=> "Display all log since this date-time 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.",
852 description
=> "Service ID",
865 description
=> "Line number",
869 description
=> "Line text",
878 my $rpcenv = PVE
::RPCEnvironment
::get
();
879 my $user = $rpcenv->get_user();
880 my $node = $param->{node
};
883 if ($param->{service
}) {
884 my $service_aliases = {
885 'postfix' => 'postfix@-',
888 $service = $service_aliases->{$param->{service
}} // $param->{service
};
891 my ($count, $lines) = PVE
::Tools
::dump_journal
($param->{start
}, $param->{limit
},
892 $param->{since
}, $param->{until}, $service);
894 $rpcenv->set_result_attrib('total', $count);
899 __PACKAGE__-
>register_method({
903 description
=> "Read Journal",
906 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
910 additionalProperties
=> 0,
912 node
=> get_standard_option
('pve-node'),
916 description
=> "Display all log since this UNIX epoch. Conflicts with 'startcursor'.",
922 description
=> "Display all log until this UNIX epoch. Conflicts with 'endcursor'.",
926 description
=> "Limit to the last X lines. Conflicts with a range.",
932 description
=> "Start after the given Cursor. Conflicts with 'since'",
937 description
=> "End before the given Cursor. Conflicts with 'until'",
952 my $rpcenv = PVE
::RPCEnvironment
::get
();
953 my $user = $rpcenv->get_user();
955 my $cmd = ["/usr/bin/mini-journalreader", "-j"];
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};
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 ';
963 open(my $fh, "-|", join(' ', @$cmd))
964 or die "could not start mini-journalreader";
970 'content-type' => 'application/json',
971 'content-encoding' => 'gzip',
978 my $shell_cmd_map = {
980 cmd
=> [ '/bin/login', '-f', 'root' ],
983 cmd
=> [ '/usr/bin/pveupgrade', '--shell' ],
986 cmd
=> [ '/usr/bin/pveceph', 'install' ],
991 sub get_shell_command
{
992 my ($user, $shellcmd, $args) = @_;
995 if ($user eq 'root@pam') {
996 if (defined($shellcmd) && exists($shell_cmd_map->{$shellcmd})) {
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);
1003 $cmd = [ '/bin/login', '-f', 'root' ];
1006 # non-root must always login for now, we do not have a superuser role!
1007 $cmd = [ '/bin/login' ];
1012 my $get_vnc_connection_info = sub {
1015 my $remote_cmd = [];
1017 my ($remip, $family);
1018 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
1019 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
1020 $remote_cmd = PVE
::SSHInfo
::ssh_info_to_command
({ ip
=> $remip, name
=> $node }, ('-t'));
1021 push @$remote_cmd, '--';
1023 $family = PVE
::Tools
::get_host_address_family
($node);
1025 my $port = PVE
::Tools
::next_vnc_port
($family);
1027 return ($port, $remote_cmd);
1030 __PACKAGE__-
>register_method ({
1036 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1038 description
=> "Creates a VNC Shell proxy.",
1040 additionalProperties
=> 0,
1042 node
=> get_standard_option
('pve-node'),
1045 description
=> "Run specific command or default to login (requires 'root\@pam')",
1046 enum
=> [keys %$shell_cmd_map],
1052 description
=> "Add parameters to a command. Encoded as null terminated strings.",
1060 description
=> "use websocket instead of standard vnc.",
1064 description
=> "sets the width of the console in pixels.",
1071 description
=> "sets the height of the console in pixels.",
1079 additionalProperties
=> 0,
1081 user
=> { type
=> 'string' },
1082 ticket
=> { type
=> 'string' },
1083 cert
=> { type
=> 'string' },
1084 port
=> { type
=> 'integer' },
1085 upid
=> { type
=> 'string' },
1091 my $rpcenv = PVE
::RPCEnvironment
::get
();
1092 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
1095 if (defined($param->{cmd
}) && $param->{cmd
} ne 'login' && $user ne 'root@pam') {
1096 raise_perm_exc
('user != root@pam');
1099 my $node = $param->{node
};
1101 my $authpath = "/nodes/$node";
1102 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($user, $authpath);
1104 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1107 my ($port, $remcmd) = $get_vnc_connection_info->($node);
1109 my $shcmd = get_shell_command
($user, $param->{cmd
}, $param->{'cmd-opts'});
1113 my $cmd = ['/usr/bin/vncterm',
1115 '-timeout', $timeout,
1116 '-authpath', $authpath,
1117 '-perm', 'Sys.Console',
1120 push @$cmd, '-width', $param->{width
} if $param->{width
};
1121 push @$cmd, '-height', $param->{height
} if $param->{height
};
1123 if ($param->{websocket
}) {
1124 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1125 push @$cmd, '-notls', '-listen', 'localhost';
1128 push @$cmd, '-c', @$remcmd, @$shcmd;
1133 syslog
('info', "starting vnc proxy $upid\n");
1135 my $cmdstr = join (' ', @$cmd);
1136 syslog
('info', "launch command: $cmdstr");
1139 foreach my $k (keys %ENV) {
1140 next if $k eq 'PVE_VNC_TICKET';
1141 next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME' || $k eq 'LANG' || $k eq 'LANGUAGE';
1146 PVE
::Tools
::run_command
($cmd, errmsg
=> "vncterm failed", keeplocale
=> 1);
1149 syslog
('err', $err);
1155 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
1157 PVE
::Tools
::wait_for_vnc_port
($port);
1168 __PACKAGE__-
>register_method ({
1169 name
=> 'termproxy',
1170 path
=> 'termproxy',
1174 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1176 description
=> "Creates a VNC Shell proxy.",
1178 additionalProperties
=> 0,
1180 node
=> get_standard_option
('pve-node'),
1183 description
=> "Run specific command or default to login (requires 'root\@pam')",
1184 enum
=> [keys %$shell_cmd_map],
1190 description
=> "Add parameters to a command. Encoded as null terminated strings.",
1198 additionalProperties
=> 0,
1200 user
=> { type
=> 'string' },
1201 ticket
=> { type
=> 'string' },
1202 port
=> { type
=> 'integer' },
1203 upid
=> { type
=> 'string' },
1209 my $rpcenv = PVE
::RPCEnvironment
::get
();
1210 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
1212 my $node = $param->{node
};
1213 my $authpath = "/nodes/$node";
1214 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($user, $authpath);
1216 my ($port, $remcmd) = $get_vnc_connection_info->($node);
1218 my $shcmd = get_shell_command
($user, $param->{cmd
}, $param->{'cmd-opts'});
1223 syslog
('info', "starting termproxy $upid\n");
1226 '/usr/bin/termproxy',
1228 '--path', $authpath,
1229 '--perm', 'Sys.Console',
1232 push @$cmd, @$remcmd, @$shcmd;
1234 PVE
::Tools
::run_command
($cmd);
1236 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
1238 PVE
::Tools
::wait_for_vnc_port
($port);
1248 __PACKAGE__-
>register_method({
1249 name
=> 'vncwebsocket',
1250 path
=> 'vncwebsocket',
1253 description
=> "You also need to pass a valid ticket (vncticket).",
1254 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1256 description
=> "Opens a websocket for VNC traffic.",
1258 additionalProperties
=> 0,
1260 node
=> get_standard_option
('pve-node'),
1262 description
=> "Ticket from previous call to vncproxy.",
1267 description
=> "Port number returned by previous vncproxy call.",
1277 port
=> { type
=> 'string' },
1283 my $rpcenv = PVE
::RPCEnvironment
::get
();
1285 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
1287 my $authpath = "/nodes/$param->{node}";
1289 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $user, $authpath);
1291 my $port = $param->{port
};
1293 return { port
=> $port };
1296 __PACKAGE__-
>register_method ({
1297 name
=> 'spiceshell',
1298 path
=> 'spiceshell',
1303 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1305 description
=> "Creates a SPICE shell.",
1307 additionalProperties
=> 0,
1309 node
=> get_standard_option
('pve-node'),
1310 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1313 description
=> "Run specific command or default to login (requires 'root\@pam')",
1314 enum
=> [keys %$shell_cmd_map],
1320 description
=> "Add parameters to a command. Encoded as null terminated strings.",
1327 returns
=> get_standard_option
('remote-viewer-config'),
1331 my $rpcenv = PVE
::RPCEnvironment
::get
();
1332 my $authuser = $rpcenv->get_user();
1334 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($authuser);
1337 if (defined($param->{cmd
}) && $param->{cmd
} ne 'login' && $user ne 'root@pam') {
1338 raise_perm_exc
('user != root@pam');
1341 my $node = $param->{node
};
1342 my $proxy = $param->{proxy
};
1344 my $authpath = "/nodes/$node";
1345 my $permissions = 'Sys.Console';
1347 my $shcmd = get_shell_command
($user, $param->{cmd
}, $param->{'cmd-opts'});
1349 my $title = "Shell on '$node'";
1351 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, 0, $node, $proxy, $title, $shcmd);
1354 __PACKAGE__-
>register_method({
1359 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1361 description
=> "Read DNS settings.",
1364 additionalProperties
=> 0,
1366 node
=> get_standard_option
('pve-node'),
1371 additionalProperties
=> 0,
1374 description
=> "Search domain for host-name lookup.",
1379 description
=> 'First name server IP address.',
1384 description
=> 'Second name server IP address.',
1389 description
=> 'Third name server IP address.',
1398 my $res = PVE
::INotify
::read_file
('resolvconf');
1403 __PACKAGE__-
>register_method({
1404 name
=> 'update_dns',
1407 description
=> "Write DNS settings.",
1409 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
1414 additionalProperties
=> 0,
1416 node
=> get_standard_option
('pve-node'),
1418 description
=> "Search domain for host-name lookup.",
1422 description
=> 'First name server IP address.',
1423 type
=> 'string', format
=> 'ip',
1427 description
=> 'Second name server IP address.',
1428 type
=> 'string', format
=> 'ip',
1432 description
=> 'Third name server IP address.',
1433 type
=> 'string', format
=> 'ip',
1438 returns
=> { type
=> "null" },
1442 PVE
::INotify
::update_file
('resolvconf', $param);
1447 __PACKAGE__-
>register_method({
1452 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1454 description
=> "Read server time and time zone settings.",
1457 additionalProperties
=> 0,
1459 node
=> get_standard_option
('pve-node'),
1464 additionalProperties
=> 0,
1467 description
=> "Time zone",
1471 description
=> "Seconds since 1970-01-01 00:00:00 UTC.",
1473 minimum
=> 1297163644,
1474 renderer
=> 'timestamp',
1477 description
=> "Seconds since 1970-01-01 00:00:00 (local time)",
1479 minimum
=> 1297163644,
1480 renderer
=> 'timestamp_gmt',
1488 my $ltime = timegm_nocheck
(localtime($ctime));
1490 timezone
=> PVE
::INotify
::read_file
('timezone'),
1492 localtime => $ltime,
1498 __PACKAGE__-
>register_method({
1499 name
=> 'set_timezone',
1502 description
=> "Set time zone.",
1504 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
1509 additionalProperties
=> 0,
1511 node
=> get_standard_option
('pve-node'),
1513 description
=> "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
1518 returns
=> { type
=> "null" },
1522 PVE
::INotify
::write_file
('timezone', $param->{timezone
});
1527 __PACKAGE__-
>register_method({
1534 description
=> "Get list of appliances.",
1537 additionalProperties
=> 0,
1539 node
=> get_standard_option
('pve-node'),
1552 my $list = PVE
::APLInfo
::load_data
();
1555 for my $appliance (values %{$list->{all
}}) {
1556 next if $appliance->{'package'} eq 'pve-web-news';
1557 push @$res, $appliance;
1563 __PACKAGE__-
>register_method({
1564 name
=> 'apl_download',
1568 check
=> ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
1570 description
=> "Download appliance templates.",
1574 additionalProperties
=> 0,
1576 node
=> get_standard_option
('pve-node'),
1577 storage
=> get_standard_option
('pve-storage-id', {
1578 description
=> "The storage where the template will be stored",
1579 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1583 description
=> "The template which will downloaded",
1585 completion
=> \
&complete_templet_repo
,
1589 returns
=> { type
=> "string" },
1593 my $rpcenv = PVE
::RPCEnvironment
::get
();
1594 my $user = $rpcenv->get_user();
1596 my $node = $param->{node
};
1597 my $template = $param->{template
};
1599 my $list = PVE
::APLInfo
::load_data
();
1600 my $appliance = $list->{all
}->{$template};
1601 raise_param_exc
({ template
=> "no such template"}) if !$appliance;
1603 my $cfg = PVE
::Storage
::config
();
1604 my $scfg = PVE
::Storage
::storage_check_enabled
($cfg, $param->{storage
}, $node);
1606 die "unknown template type '$appliance->{type}'\n"
1607 if !($appliance->{type
} eq 'openvz' || $appliance->{type
} eq 'lxc');
1609 die "storage '$param->{storage}' does not support templates\n"
1610 if !$scfg->{content
}->{vztmpl
};
1612 my $tmpldir = PVE
::Storage
::get_vztmpl_dir
($cfg, $param->{storage
});
1615 my $dccfg = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1617 PVE
::Tools
::download_file_from_url
("$tmpldir/$template", $appliance->{location
}, {
1619 sha512sum
=> $appliance->{sha512sum
},
1620 md5sum
=> $appliance->{md5sum
},
1621 http_proxy
=> $dccfg->{http_proxy
},
1625 my $upid = $rpcenv->fork_worker('download', $template, $user, $worker);
1630 __PACKAGE__-
>register_method({
1631 name
=> 'query_url_metadata',
1632 path
=> 'query-url-metadata',
1634 description
=> "Query metadata of an URL: file size, file name and mime type.",
1638 ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]],
1639 ['perm', '/nodes/{node}', [ 'Sys.AccessNetwork' ]],
1643 additionalProperties
=> 0,
1645 node
=> get_standard_option
('pve-node'),
1647 description
=> "The URL to query the metadata from.",
1649 pattern
=> 'https?://.*',
1651 'verify-certificates' => {
1652 description
=> "If false, no SSL/TLS certificates will be verified.",
1668 renderer
=> 'bytes',
1680 my $url = $param->{url
};
1682 my $ua = LWP
::UserAgent-
>new();
1683 $ua->agent("Proxmox VE");
1685 my $dccfg = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1686 if ($dccfg->{http_proxy
}) {
1687 $ua->proxy('http', $dccfg->{http_proxy
});
1690 my $verify = $param->{'verify-certificates'} // 1;
1693 verify_hostname
=> 0,
1694 SSL_verify_mode
=> IO
::Socket
::SSL
::SSL_VERIFY_NONE
,
1698 my $req = HTTP
::Request-
>new(HEAD
=> $url);
1699 my $res = $ua->request($req);
1701 die "invalid server response: '" . $res->status_line() . "'\n" if ($res->code() != 200);
1703 my $size = $res->header("Content-Length");
1704 my $disposition = $res->header("Content-Disposition");
1705 my $type = $res->header("Content-Type");
1709 if ($disposition && ($disposition =~ m/filename="([^"]*)"/ || $disposition =~ m/filename=([^;]*)/)) {
1711 } elsif ($url =~ m!^[^?]+/([^?/]*)(?:\?.*)?$!) {
1715 # Content-Type: text/html; charset=utf-8
1716 if ($type && $type =~ m/^([^;]+);/) {
1721 $ret->{filename
} = $filename if $filename;
1722 $ret->{size
} = $size + 0 if $size;
1723 $ret->{mimetype
} = $type if $type;
1728 __PACKAGE__-
>register_method({
1733 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1736 description
=> "Gather various systems information about a node",
1739 additionalProperties
=> 0,
1741 node
=> get_standard_option
('pve-node'),
1748 return PVE
::Report
::generate
();
1751 # returns a list of VMIDs, those can be filtered by
1752 # * current parent node
1754 # * guest is a template (default: skip)
1755 # * guest is HA manged (default: skip)
1756 my $get_filtered_vmlist = sub {
1757 my ($nodename, $vmfilter, $templates, $ha_managed) = @_;
1759 my $vmlist = PVE
::Cluster
::get_vmlist
();
1762 if (defined($vmfilter)) {
1763 $vms_allowed = { map { $_ => 1 } PVE
::Tools
::split_list
($vmfilter) };
1767 foreach my $vmid (keys %{$vmlist->{ids
}}) {
1768 next if defined($vms_allowed) && !$vms_allowed->{$vmid};
1770 my $d = $vmlist->{ids
}->{$vmid};
1771 next if $nodename && $d->{node
} ne $nodename;
1775 if ($d->{type
} eq 'lxc') {
1776 $class = 'PVE::LXC::Config';
1777 } elsif ($d->{type
} eq 'qemu') {
1778 $class = 'PVE::QemuConfig';
1780 die "unknown virtual guest type '$d->{type}'\n";
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);
1787 $res->{$vmid}->{conf
} = $conf;
1788 $res->{$vmid}->{type
} = $d->{type
};
1789 $res->{$vmid}->{class} = $class;
1797 # return all VMs which should get started/stopped on power up/down
1798 my $get_start_stop_list = sub {
1799 my ($nodename, $autostart, $vmfilter) = @_;
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;
1804 my $vmlist = $get_filtered_vmlist->($nodename, $vmfilter, undef, $include_ha_managed);
1807 foreach my $vmid (keys %$vmlist) {
1808 my $conf = $vmlist->{$vmid}->{conf
};
1809 next if $autostart && !$conf->{onboot
};
1811 my $startup = $conf->{startup
} ? PVE
::JSONSchema
::pve_parse_startup_order
($conf->{startup
}) : {};
1812 my $order = $startup->{order
} = $startup->{order
} // LONG_MAX
;
1814 $resList->{$order}->{$vmid} = $startup;
1815 $resList->{$order}->{$vmid}->{type
} = $vmlist->{$vmid}->{type
};
1821 my $remove_locks_on_startup = sub {
1822 my ($nodename) = @_;
1824 my $vmlist = &$get_filtered_vmlist($nodename, undef, undef, 1);
1826 foreach my $vmid (keys %$vmlist) {
1827 my $conf = $vmlist->{$vmid}->{conf
};
1828 my $class = $vmlist->{$vmid}->{class};
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);
1841 __PACKAGE__-
>register_method ({
1847 description
=> "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/<ID>' for "
1848 ."each ID passed via the 'vms' parameter.",
1852 description
=> "Start all VMs and containers located on this node (by default only those with onboot=1).",
1854 additionalProperties
=> 0,
1856 node
=> get_standard_option
('pve-node'),
1861 description
=> "Issue start command even if virtual guest have 'onboot' not set or set to off.",
1864 description
=> "Only consider guests from this comma separated list of VMIDs.",
1865 type
=> 'string', format
=> 'pve-vmid-list',
1876 my $rpcenv = PVE
::RPCEnvironment
::get
();
1877 my $authuser = $rpcenv->get_user();
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;
1884 raise_perm_exc
("/, VM.PowerMgmt");
1888 my $nodename = $param->{node
};
1889 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1891 my $force = $param->{force
};
1894 $rpcenv->{type
} = 'priv'; # to start tasks in background
1896 if (!PVE
::Cluster
::check_cfs_quorum
(1)) {
1897 print "waiting for quorum ...\n";
1900 } while (!PVE
::Cluster
::check_cfs_quorum
(1));
1901 print "got quorum\n";
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);
1909 my $autostart = $force ?
undef : 1;
1910 my $startList = $get_start_stop_list->($nodename, $autostart, $param->{vms
});
1912 # Note: use numeric sorting with <=>
1913 for my $order (sort {$a <=> $b} keys %$startList) {
1914 my $vmlist = $startList->{$order};
1916 for my $vmid (sort {$a <=> $b} keys %$vmlist) {
1917 my $d = $vmlist->{$vmid};
1919 PVE
::Cluster
::check_cfs_quorum
(); # abort when we loose quorum
1922 my $default_delay = 0;
1925 if ($d->{type
} eq 'lxc') {
1926 return if PVE
::LXC
::check_running
($vmid);
1927 print STDERR
"Starting CT $vmid\n";
1928 $upid = PVE
::API2
::LXC
::Status-
>vm_start({node
=> $nodename, vmid
=> $vmid });
1929 } elsif ($d->{type
} eq 'qemu') {
1930 $default_delay = 3; # to reduce load
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 });
1935 die "unknown VM type '$d->{type}'\n";
1938 my $task = PVE
::Tools
::upid_decode
($upid);
1939 while (PVE
::ProcFSTools
::check_process_running
($task->{pid
})) {
1943 my $status = PVE
::Tools
::upid_read_status
($upid);
1944 if (!PVE
::Tools
::upid_status_is_error
($status)) {
1945 # use default delay to reduce load
1946 my $delay = defined($d->{up
}) ?
int($d->{up
}) : $default_delay;
1948 print STDERR
"Waiting for $delay seconds (startup delay)\n" if $d->{up
};
1949 for (my $i = 0; $i < $delay; $i++) {
1954 my $rendered_type = $d->{type
} eq 'lxc' ?
'CT' : 'VM';
1955 print STDERR
"Starting $rendered_type $vmid failed: $status\n";
1964 return $rpcenv->fork_worker('startall', undef, $authuser, $code);
1967 my $create_stop_worker = sub {
1968 my ($nodename, $type, $vmid, $timeout, $force_stop) = @_;
1970 if ($type eq 'lxc') {
1971 return if !PVE
::LXC
::check_running
($vmid);
1972 print STDERR
"Stopping CT $vmid (timeout = $timeout seconds)\n";
1973 return PVE
::API2
::LXC
::Status-
>vm_shutdown(
1974 { node
=> $nodename, vmid
=> $vmid, timeout
=> $timeout, forceStop
=> $force_stop }
1976 } elsif ($type eq 'qemu') {
1977 return if !PVE
::QemuServer
::check_running
($vmid, 1);
1978 print STDERR
"Stopping VM $vmid (timeout = $timeout seconds)\n";
1979 return PVE
::API2
::Qemu-
>vm_shutdown(
1980 { node
=> $nodename, vmid
=> $vmid, timeout
=> $timeout, forceStop
=> $force_stop }
1983 die "unknown VM type '$type'\n";
1987 __PACKAGE__-
>register_method ({
1993 description
=> "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/<ID>' for "
1994 ."each ID passed via the 'vms' parameter.",
1998 description
=> "Stop all VMs and Containers.",
2000 additionalProperties
=> 0,
2002 node
=> get_standard_option
('pve-node'),
2004 description
=> "Only consider Guests with these IDs.",
2005 type
=> 'string', format
=> 'pve-vmid-list',
2009 description
=> 'Force a hard-stop after the timeout.',
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.',
2021 maximum
=> 2 * 3600, # mostly arbitrary, but we do not want to high timeouts
2031 my $rpcenv = PVE
::RPCEnvironment
::get
();
2032 my $authuser = $rpcenv->get_user();
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;
2039 raise_perm_exc
("/, VM.PowerMgmt");
2043 my $nodename = $param->{node
};
2044 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
2048 $rpcenv->{type
} = 'priv'; # to start tasks in background
2050 my $stopList = $get_start_stop_list->($nodename, undef, $param->{vms
});
2052 my $cpuinfo = PVE
::ProcFSTools
::read_cpuinfo
();
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
};
2057 for my $order (sort {$b <=> $a} keys %$stopList) {
2058 my $vmlist = $stopList->{$order};
2061 my $finish_worker = sub {
2063 my $worker = delete $workers->{$pid} || return;
2065 syslog
('info', "end task $worker->{upid}");
2068 for my $vmid (sort {$b <=> $a} keys %$vmlist) {
2069 my $d = $vmlist->{$vmid};
2070 my $timeout = int($d->{down
} // $param->{timeout
} // 180);
2072 $create_stop_worker->(
2073 $nodename, $d->{type
}, $vmid, $timeout, $param->{'force-stop'} // 1)
2078 my $task = PVE
::Tools
::upid_decode
($upid, 1);
2081 my $pid = $task->{pid
};
2083 $workers->{$pid} = { type
=> $d->{type
}, upid
=> $upid, vmid
=> $vmid };
2084 while (scalar(keys %$workers) >= $maxWorkers) {
2085 foreach my $p (keys %$workers) {
2086 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
2087 $finish_worker->($p);
2093 while (scalar(keys %$workers)) {
2094 for my $p (keys %$workers) {
2095 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
2096 $finish_worker->($p);
2103 syslog
('info', "all VMs and CTs stopped");
2108 return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
2111 my $create_suspend_worker = sub {
2112 my ($nodename, $vmid) = @_;
2113 if (!PVE
::QemuServer
::check_running
($vmid, 1)) {
2114 print "VM $vmid not running, skipping suspension\n";
2117 print STDERR
"Suspending VM $vmid\n";
2118 return PVE
::API2
::Qemu-
>vm_suspend(
2119 { node
=> $nodename, vmid
=> $vmid, todisk
=> 1 }
2123 __PACKAGE__-
>register_method ({
2124 name
=> 'suspendall',
2125 path
=> 'suspendall',
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)",
2135 description
=> "Suspend all VMs.",
2137 additionalProperties
=> 0,
2139 node
=> get_standard_option
('pve-node'),
2141 description
=> "Only consider Guests with these IDs.",
2142 type
=> 'string', format
=> 'pve-vmid-list',
2153 my $rpcenv = PVE
::RPCEnvironment
::get
();
2154 my $authuser = $rpcenv->get_user();
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;
2162 raise_perm_exc
("/, VM.PowerMgmt && VM.Config.Disk");
2166 my $nodename = $param->{node
};
2167 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
2171 $rpcenv->{type
} = 'priv'; # to start tasks in background
2173 my $toSuspendList = $get_start_stop_list->($nodename, undef, $param->{vms
});
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
};
2180 for my $order (sort {$b <=> $a} keys %$toSuspendList) {
2181 my $vmlist = $toSuspendList->{$order};
2184 my $finish_worker = sub {
2186 my $worker = delete $workers->{$pid} || return;
2188 syslog
('info', "end task $worker->{upid}");
2191 for my $vmid (sort {$b <=> $a} keys %$vmlist) {
2192 my $d = $vmlist->{$vmid};
2193 if ($d->{type
} ne 'qemu') {
2194 log_warn
("skipping $vmid, only VMs can be suspended");
2198 $create_suspend_worker->($nodename, $vmid)
2203 my $task = PVE
::Tools
::upid_decode
($upid, 1);
2206 my $pid = $task->{pid
};
2207 $workers->{$pid} = { type
=> $d->{type
}, upid
=> $upid, vmid
=> $vmid };
2209 while (scalar(keys %$workers) >= $maxWorkers) {
2210 for my $p (keys %$workers) {
2211 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
2212 $finish_worker->($p);
2218 while (scalar(keys %$workers)) {
2219 for my $p (keys %$workers) {
2220 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
2221 $finish_worker->($p);
2228 syslog
('info', "all VMs suspended");
2233 return $rpcenv->fork_worker('suspendall', undef, $authuser, $code);
2237 my $create_migrate_worker = sub {
2238 my ($nodename, $type, $vmid, $target, $with_local_disks) = @_;
2241 if ($type eq 'lxc') {
2242 my $online = PVE
::LXC
::check_running
($vmid) ?
1 : 0;
2243 print STDERR
"Migrating CT $vmid\n";
2244 $upid = PVE
::API2
::LXC-
>migrate_vm(
2245 { node
=> $nodename, vmid
=> $vmid, target
=> $target, restart
=> $online });
2246 } elsif ($type eq 'qemu') {
2247 print STDERR
"Check VM $vmid: ";
2249 my $online = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2250 my $preconditions = PVE
::API2
::Qemu-
>migrate_vm_precondition(
2251 {node
=> $nodename, vmid
=> $vmid, target
=> $target});
2252 my $invalidConditions = '';
2253 if ($online && !$with_local_disks && scalar @{$preconditions->{local_disks
}}) {
2254 $invalidConditions .= "\n Has local disks: ";
2255 $invalidConditions .= join(', ', map { $_->{volid
} } @{$preconditions->{local_disks
}});
2258 if (@{$preconditions->{local_resources
}}) {
2259 $invalidConditions .= "\n Has local resources: ";
2260 $invalidConditions .= join(', ', @{$preconditions->{local_resources
}});
2263 if ($invalidConditions && $invalidConditions ne '') {
2264 print STDERR
"skip VM $vmid - precondition check failed:";
2265 die "$invalidConditions\n";
2267 print STDERR
"precondition check passed\n";
2268 print STDERR
"Migrating VM $vmid\n";
2276 $params->{'with-local-disks'} = $with_local_disks if defined($with_local_disks);
2278 $upid = PVE
::API2
::Qemu-
>migrate_vm($params);
2280 die "unknown VM type '$type'\n";
2283 my $task = PVE
::Tools
::upid_decode
($upid);
2285 return $task->{pid
};
2288 __PACKAGE__-
>register_method ({
2289 name
=> 'migrateall',
2290 path
=> 'migrateall',
2295 description
=> "The 'VM.Migrate' permission is required on '/' or on '/vms/<ID>' for each "
2296 ."ID passed via the 'vms' parameter.",
2299 description
=> "Migrate all VMs and Containers.",
2301 additionalProperties
=> 0,
2303 node
=> get_standard_option
('pve-node'),
2304 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2306 description
=> "Maximal number of parallel migration job. If not set, uses"
2307 ."'max_workers' from datacenter.cfg. One of both must be set!",
2313 description
=> "Only consider Guests with these IDs.",
2314 type
=> 'string', format
=> 'pve-vmid-list',
2317 "with-local-disks" => {
2319 description
=> "Enable live storage migration for local disk",
2330 my $rpcenv = PVE
::RPCEnvironment
::get
();
2331 my $authuser = $rpcenv->get_user();
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;
2338 raise_perm_exc
("/, VM.Migrate");
2342 my $nodename = $param->{node
};
2343 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
2345 my $target = $param->{target
};
2346 my $with_local_disks = $param->{'with-local-disks'};
2347 raise_param_exc
({ target
=> "target is local node."}) if $target eq $nodename;
2349 PVE
::Cluster
::check_cfs_quorum
();
2351 PVE
::Cluster
::check_node_exists
($target);
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";
2359 $rpcenv->{type
} = 'priv'; # to start tasks in background
2361 my $vmlist = &$get_filtered_vmlist($nodename, $param->{vms
}, 1, 1);
2362 if (!scalar(keys %$vmlist)) {
2363 warn "no virtual guests matched, nothing to do..\n";
2368 my $workers_started = 0;
2369 foreach my $vmid (sort keys %$vmlist) {
2370 my $d = $vmlist->{$vmid};
2372 eval { $pid = &$create_migrate_worker($nodename, $d->{type
}, $vmid, $target, $with_local_disks); };
2377 $workers->{$pid} = 1;
2378 while (scalar(keys %$workers) >= $maxWorkers) {
2379 foreach my $p (keys %$workers) {
2380 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
2381 delete $workers->{$p};
2387 while (scalar(keys %$workers)) {
2388 foreach my $p (keys %$workers) {
2389 # FIXME: what about PID re-use ?!?!
2390 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
2391 delete $workers->{$p};
2396 if ($workers_started <= 0) {
2397 die "no migrations worker started...\n";
2399 print STDERR
"All jobs finished, used $workers_started workers in total.\n";
2403 return $rpcenv->fork_worker('migrateall', undef, $authuser, $code);
2407 __PACKAGE__-
>register_method ({
2408 name
=> 'get_etc_hosts',
2414 check
=> ['perm', '/', [ 'Sys.Audit' ]],
2416 description
=> "Get the content of /etc/hosts.",
2418 additionalProperties
=> 0,
2420 node
=> get_standard_option
('pve-node'),
2426 digest
=> get_standard_option
('pve-config-digest'),
2429 description
=> 'The content of /etc/hosts.'
2436 return PVE
::INotify
::read_file
('etchosts');
2440 __PACKAGE__-
>register_method ({
2441 name
=> 'write_etc_hosts',
2447 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
2449 description
=> "Write /etc/hosts.",
2451 additionalProperties
=> 0,
2453 node
=> get_standard_option
('pve-node'),
2454 digest
=> get_standard_option
('pve-config-digest'),
2457 description
=> 'The target content of /etc/hosts.'
2467 PVE
::Tools
::lock_file
('/var/lock/pve-etchosts.lck', undef, sub {
2468 if ($param->{digest
}) {
2469 my $hosts = PVE
::INotify
::read_file
('etchosts');
2470 PVE
::Tools
::assert_if_modified
($hosts->{digest
}, $param->{digest
});
2472 PVE
::INotify
::write_file
('etchosts', $param->{data
});
2479 # bash completion helper
2481 sub complete_templet_repo
{
2482 my ($cmdname, $pname, $cvalue) = @_;
2484 my $repo = PVE
::APLInfo
::load_data
();
2486 foreach my $templ (keys %{$repo->{all
}}) {
2487 next if $templ !~ m/^$cvalue/;
2494 package PVE
::API2
::Nodes
;
2499 use PVE
::SafeSyslog
;
2501 use PVE
::RESTHandler
;
2502 use PVE
::RPCEnvironment
;
2504 use PVE
::JSONSchema
qw(get_standard_option);
2506 use base
qw(PVE::RESTHandler);
2508 __PACKAGE__-
>register_method ({
2509 subclass
=> "PVE::API2::Nodes::Nodeinfo",
2513 __PACKAGE__-
>register_method ({
2517 permissions
=> { user
=> 'all' },
2518 description
=> "Cluster node index.",
2520 additionalProperties
=> 0,
2528 node
=> get_standard_option
('pve-node'),
2530 description
=> "Node status.",
2532 enum
=> ['unknown', 'online', 'offline'],
2535 description
=> "CPU utilization.",
2538 renderer
=> 'fraction_as_percentage',
2541 description
=> "Number of available CPUs.",
2546 description
=> "Used memory in bytes.",
2549 renderer
=> 'bytes',
2552 description
=> "Number of available memory in bytes.",
2555 renderer
=> 'bytes',
2558 description
=> "Support level.",
2563 description
=> "Node uptime in seconds.",
2566 renderer
=> 'duration',
2568 ssl_fingerprint
=> {
2569 description
=> "The SSL fingerprint for the node certificate.",
2575 links
=> [ { rel
=> 'child', href
=> "{node}" } ],
2580 my $rpcenv = PVE
::RPCEnvironment
::get
();
2581 my $authuser = $rpcenv->get_user();
2583 my $clinfo = PVE
::Cluster
::get_clinfo
();
2586 my $nodelist = PVE
::Cluster
::get_nodelist
();
2587 my $members = PVE
::Cluster
::get_members
();
2588 my $rrd = PVE
::Cluster
::rrd_dump
();
2590 foreach my $node (@$nodelist) {
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);
2594 $entry->{ssl_fingerprint
} = eval { PVE
::Cluster
::get_node_fingerprint
($node) };