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
::RPCEnvironment
;
41 use PVE
::API2
::Capabilities
;
43 use PVE
::API2
::Certificates
;
45 use PVE
::API2
::Firewall
::Host
;
46 use PVE
::API2
::Hardware
;
47 use PVE
::API2
::LXC
::Status
;
49 use PVE
::API2
::Network
;
50 use PVE
::API2
::NodeConfig
;
51 use PVE
::API2
::Qemu
::CPU
;
53 use PVE
::API2
::Replication
;
54 use PVE
::API2
::Services
;
55 use PVE
::API2
::Storage
::Scan
;
56 use PVE
::API2
::Storage
::Status
;
57 use PVE
::API2
::Subscription
;
59 use PVE
::API2
::VZDump
;
63 require PVE
::API2
::Network
::SDN
::Zones
::Status
;
67 use base
qw(PVE::RESTHandler);
69 my $verify_command_item_desc = {
70 description
=> "An array of objects describing endpoints, methods and arguments.",
76 description
=> "A relative path to an API endpoint on this node.",
81 description
=> "A method related to the API endpoint (GET, POST etc.).",
83 pattern
=> "(GET|POST|PUT|DELETE)",
87 description
=> "A set of parameter names and their values.",
95 PVE
::JSONSchema
::register_format
('pve-command-batch', \
&verify_command_batch
);
96 sub verify_command_batch
{
97 my ($value, $noerr) = @_;
98 my $commands = eval { decode_json
($value); };
100 return if $noerr && $@;
101 die "commands param did not contain valid JSON: $@" if $@;
103 eval { PVE
::JSONSchema
::validate
($commands, $verify_command_item_desc) };
105 return $commands if !$@;
108 die "commands is not a valid array of commands: $@";
111 __PACKAGE__-
>register_method ({
112 subclass
=> "PVE::API2::Qemu",
116 __PACKAGE__-
>register_method ({
117 subclass
=> "PVE::API2::LXC",
121 __PACKAGE__-
>register_method ({
122 subclass
=> "PVE::API2::Ceph",
126 __PACKAGE__-
>register_method ({
127 subclass
=> "PVE::API2::VZDump",
131 __PACKAGE__-
>register_method ({
132 subclass
=> "PVE::API2::Services",
136 __PACKAGE__-
>register_method ({
137 subclass
=> "PVE::API2::Subscription",
138 path
=> 'subscription',
141 __PACKAGE__-
>register_method ({
142 subclass
=> "PVE::API2::Network",
146 __PACKAGE__-
>register_method ({
147 subclass
=> "PVE::API2::Tasks",
151 __PACKAGE__-
>register_method ({
152 subclass
=> "PVE::API2::Storage::Scan",
156 __PACKAGE__-
>register_method ({
157 subclass
=> "PVE::API2::Hardware",
161 __PACKAGE__-
>register_method ({
162 subclass
=> "PVE::API2::Capabilities",
163 path
=> 'capabilities',
166 __PACKAGE__-
>register_method ({
167 subclass
=> "PVE::API2::Storage::Status",
171 __PACKAGE__-
>register_method ({
172 subclass
=> "PVE::API2::Disks",
176 __PACKAGE__-
>register_method ({
177 subclass
=> "PVE::API2::APT",
181 __PACKAGE__-
>register_method ({
182 subclass
=> "PVE::API2::Firewall::Host",
186 __PACKAGE__-
>register_method ({
187 subclass
=> "PVE::API2::Replication",
188 path
=> 'replication',
191 __PACKAGE__-
>register_method ({
192 subclass
=> "PVE::API2::Certificates",
193 path
=> 'certificates',
197 __PACKAGE__-
>register_method ({
198 subclass
=> "PVE::API2::NodeConfig",
203 __PACKAGE__-
>register_method ({
204 subclass
=> "PVE::API2::Network::SDN::Zones::Status",
208 __PACKAGE__-
>register_method ({
212 permissions
=> { user
=> 'all' },
213 description
=> "SDN index.",
215 additionalProperties
=> 0,
217 node
=> get_standard_option
('pve-node'),
226 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
238 __PACKAGE__-
>register_method ({
242 permissions
=> { user
=> 'all' },
243 description
=> "Node index.",
245 additionalProperties
=> 0,
247 node
=> get_standard_option
('pve-node'),
256 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
262 { name
=> 'aplinfo' },
264 { name
=> 'capabilities' },
266 { name
=> 'certificates' },
267 { name
=> 'config' },
270 { name
=> 'firewall' },
271 { name
=> 'hardware' },
273 { name
=> 'journal' },
275 { name
=> 'migrateall' },
276 { name
=> 'netstat' },
277 { name
=> 'network' },
279 { name
=> 'query-url-metadata' },
280 { name
=> 'replication' },
281 { name
=> 'report' },
282 { name
=> 'rrd' }, # fixme: remove?
283 { name
=> 'rrddata' },# fixme: remove?
285 { name
=> 'services' },
286 { name
=> 'spiceshell' },
287 { name
=> 'startall' },
288 { name
=> 'status' },
289 { name
=> 'stopall' },
290 { name
=> 'storage' },
291 { name
=> 'subscription' },
292 { name
=> 'syslog' },
294 { name
=> 'termproxy' },
296 { name
=> 'version' },
297 { name
=> 'vncshell' },
298 { name
=> 'vzdump' },
299 { name
=> 'wakeonlan' },
302 push @$result, { name
=> 'sdn' } if $have_sdn;
307 __PACKAGE__-
>register_method ({
312 permissions
=> { user
=> 'all' },
313 description
=> "API version details",
315 additionalProperties
=> 0,
317 node
=> get_standard_option
('pve-node'),
325 description
=> 'The current installed pve-manager package version',
329 description
=> 'The current installed Proxmox VE Release',
333 description
=> 'The short git commit hash ID from which this version was build',
338 my ($resp, $param) = @_;
340 return PVE
::pvecfg
::version_info
();
343 __PACKAGE__-
>register_method({
348 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
350 description
=> "Read node status",
353 additionalProperties
=> 0,
355 node
=> get_standard_option
('pve-node'),
372 my ($uptime, $idle) = PVE
::ProcFSTools
::read_proc_uptime
();
373 $res->{uptime
} = $uptime;
375 my ($avg1, $avg5, $avg15) = PVE
::ProcFSTools
::read_loadavg
();
376 $res->{loadavg
} = [ $avg1, $avg5, $avg15];
378 my ($sysname, $nodename, $release, $version, $machine) = POSIX
::uname
();
380 $res->{kversion
} = "$sysname $release $version";
382 $res->{cpuinfo
} = PVE
::ProcFSTools
::read_cpuinfo
();
384 my $stat = PVE
::ProcFSTools
::read_proc_stat
();
385 $res->{cpu
} = $stat->{cpu
};
386 $res->{wait} = $stat->{wait};
388 my $meminfo = PVE
::ProcFSTools
::read_meminfo
();
390 free
=> $meminfo->{memfree
},
391 total
=> $meminfo->{memtotal
},
392 used
=> $meminfo->{memused
},
396 shared
=> $meminfo->{memshared
},
400 free
=> $meminfo->{swapfree
},
401 total
=> $meminfo->{swaptotal
},
402 used
=> $meminfo->{swapused
},
405 $res->{pveversion
} = PVE
::pvecfg
::package() . "/" .
406 PVE
::pvecfg
::version_text
();
408 my $dinfo = df
('/', 1); # output is bytes
411 total
=> $dinfo->{blocks
},
412 avail
=> $dinfo->{bavail
},
413 used
=> $dinfo->{used
},
414 free
=> $dinfo->{blocks
} - $dinfo->{used
},
420 __PACKAGE__-
>register_method({
425 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
427 description
=> "Read tap/vm network device interface counters",
430 additionalProperties
=> 0,
432 node
=> get_standard_option
('pve-node'),
447 my $netdev = PVE
::ProcFSTools
::read_proc_net_dev
();
448 foreach my $dev (sort keys %$netdev) {
449 next if $dev !~ m/^(?:tap|veth)([1-9]\d*)i(\d+)$/;
450 my ($vmid, $netid) = ($1, $2);
455 in => $netdev->{$dev}->{transmit
},
456 out
=> $netdev->{$dev}->{receive
},
463 __PACKAGE__-
>register_method({
468 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
470 description
=> "Execute multiple commands in order.",
472 protected
=> 1, # avoid problems with proxy code
474 additionalProperties
=> 0,
476 node
=> get_standard_option
('pve-node'),
478 description
=> "JSON encoded array of commands.",
480 verbose_description
=> "JSON encoded array of commands, where each command is an object with the following properties:\n"
481 . PVE
::RESTHandler
::dump_properties
($verify_command_item_desc->{items
}->{properties
}, 'full'),
482 format
=> "pve-command-batch",
497 my $rpcenv = PVE
::RPCEnvironment
::get
();
498 my $user = $rpcenv->get_user();
499 # just parse the json again, it should already be validated
500 my $commands = eval { decode_json
($param->{commands
}); };
502 foreach my $cmd (@$commands) {
506 my $path = "nodes/$param->{node}/$cmd->{path}";
509 my ($handler, $info) = PVE
::API2-
>find_handler($cmd->{method}, $path, $uri_param);
510 if (!$handler || !$info) {
511 die "no handler for '$path'\n";
514 foreach my $p (keys %{$cmd->{args
}}) {
515 raise_param_exc
({ $p => "duplicate parameter" }) if defined($uri_param->{$p});
516 $uri_param->{$p} = $cmd->{args
}->{$p};
519 # check access permissions
520 $rpcenv->check_api2_permissions($info->{permissions
}, $user, $uri_param);
524 data
=> $handler->handle($info, $uri_param),
528 my $resp = { status
=> HTTP_INTERNAL_SERVER_ERROR
};
529 if (ref($err) eq "PVE::Exception") {
530 $resp->{status
} = $err->{code
} if $err->{code
};
531 $resp->{errors
} = $err->{errors
} if $err->{errors
};
532 $resp->{message
} = $err->{msg
};
534 $resp->{message
} = $err;
544 __PACKAGE__-
>register_method({
549 check
=> ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
552 description
=> "Reboot or shutdown a node.",
555 additionalProperties
=> 0,
557 node
=> get_standard_option
('pve-node'),
559 description
=> "Specify the command.",
561 enum
=> [qw(reboot shutdown)],
565 returns
=> { type
=> "null" },
569 if ($param->{command
} eq 'reboot') {
570 system ("(sleep 2;/sbin/reboot)&");
571 } elsif ($param->{command
} eq 'shutdown') {
572 system ("(sleep 2;/sbin/poweroff)&");
578 __PACKAGE__-
>register_method({
583 check
=> ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
586 description
=> "Try to wake a node via 'wake on LAN' network packet.",
588 additionalProperties
=> 0,
590 node
=> get_standard_option
('pve-node', {
591 description
=> 'target node for wake on LAN packet',
593 my $members = PVE
::Cluster
::get_members
();
594 return [ grep { !$members->{$_}->{online
} } keys %$members ];
601 format
=> 'mac-addr',
602 description
=> 'MAC address used to assemble the WoL magic packet.',
607 my $node = $param->{node
};
609 die "'$node' is local node, cannot wake my self!\n"
610 if $node eq 'localhost' || $node eq PVE
::INotify
::nodename
();
612 PVE
::Cluster
::check_node_exists
($node);
614 my $config = PVE
::NodeConfig
::load_config
($node);
615 my $mac_addr = $config->{wakeonlan
};
616 if (!defined($mac_addr)) {
617 die "No wake on LAN MAC address defined for '$node'!\n";
621 my $packet = chr(0xff) x
6 . pack('H*', $mac_addr) x
16;
623 my $addr = gethostbyname('255.255.255.255');
624 my $port = getservbyname('discard', 'udp');
625 my $to = Socket
::pack_sockaddr_in
($port, $addr);
627 socket(my $sock, Socket
::AF_INET
, Socket
::SOCK_DGRAM
, Socket
::IPPROTO_UDP
)
628 || die "Unable to open socket: $!\n";
629 setsockopt($sock, Socket
::SOL_SOCKET
, Socket
::SO_BROADCAST
, 1)
630 || die "Unable to set socket option: $!\n";
632 send($sock, $packet, 0, $to)
633 || die "Unable to send packet: $!\n";
637 return $config->{wakeonlan
};
640 __PACKAGE__-
>register_method({
644 protected
=> 1, # fixme: can we avoid that?
646 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
648 description
=> "Read node RRD statistics (returns PNG)",
650 additionalProperties
=> 0,
652 node
=> get_standard_option
('pve-node'),
654 description
=> "Specify the time frame you are interested in.",
656 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
659 description
=> "The list of datasources you want to display.",
660 type
=> 'string', format
=> 'pve-configid-list',
663 description
=> "The RRD consolidation function",
665 enum
=> [ 'AVERAGE', 'MAX' ],
673 filename
=> { type
=> 'string' },
679 return PVE
::RRD
::create_rrd_graph
(
680 "pve2-node/$param->{node}", $param->{timeframe
},
681 $param->{ds
}, $param->{cf
});
685 __PACKAGE__-
>register_method({
689 protected
=> 1, # fixme: can we avoid that?
691 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
693 description
=> "Read node RRD statistics",
695 additionalProperties
=> 0,
697 node
=> get_standard_option
('pve-node'),
699 description
=> "Specify the time frame you are interested in.",
701 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
704 description
=> "The RRD consolidation function",
706 enum
=> [ 'AVERAGE', 'MAX' ],
721 return PVE
::RRD
::create_rrd_data
(
722 "pve2-node/$param->{node}", $param->{timeframe
}, $param->{cf
});
725 __PACKAGE__-
>register_method({
729 description
=> "Read system log",
732 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
736 additionalProperties
=> 0,
738 node
=> get_standard_option
('pve-node'),
751 pattern
=> '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
752 description
=> "Display all log since this date-time string.",
757 pattern
=> '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
758 description
=> "Display all log until this date-time string.",
762 description
=> "Service ID",
775 description
=> "Line number",
779 description
=> "Line text",
788 my $rpcenv = PVE
::RPCEnvironment
::get
();
789 my $user = $rpcenv->get_user();
790 my $node = $param->{node
};
793 if ($param->{service
}) {
794 my $service_aliases = {
795 'postfix' => 'postfix@-',
798 $service = $service_aliases->{$param->{service
}} // $param->{service
};
801 my ($count, $lines) = PVE
::Tools
::dump_journal
($param->{start
}, $param->{limit
},
802 $param->{since
}, $param->{until}, $service);
804 $rpcenv->set_result_attrib('total', $count);
809 __PACKAGE__-
>register_method({
813 description
=> "Read Journal",
816 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
820 additionalProperties
=> 0,
822 node
=> get_standard_option
('pve-node'),
826 description
=> "Display all log since this UNIX epoch. Conflicts with 'startcursor'.",
832 description
=> "Display all log until this UNIX epoch. Conflicts with 'endcursor'.",
836 description
=> "Limit to the last X lines. Conflicts with a range.",
842 description
=> "Start after the given Cursor. Conflicts with 'since'",
847 description
=> "End before the given Cursor. Conflicts with 'until'",
862 my $rpcenv = PVE
::RPCEnvironment
::get
();
863 my $user = $rpcenv->get_user();
865 my $cmd = ["/usr/bin/mini-journalreader", "-j"];
866 push @$cmd, '-n', $param->{lastentries
} if $param->{lastentries
};
867 push @$cmd, '-b', $param->{since
} if $param->{since
};
868 push @$cmd, '-e', $param->{until} if $param->{until};
869 push @$cmd, '-f', PVE
::Tools
::shellquote
($param->{startcursor
}) if $param->{startcursor
};
870 push @$cmd, '-t', PVE
::Tools
::shellquote
($param->{endcursor
}) if $param->{endcursor
};
871 push @$cmd, ' | gzip ';
873 open(my $fh, "-|", join(' ', @$cmd))
874 or die "could not start mini-journalreader";
880 'content-type' => 'application/json',
881 'content-encoding' => 'gzip',
888 my $shell_cmd_map = {
890 cmd
=> [ '/bin/login', '-f', 'root' ],
893 cmd
=> [ '/usr/bin/pveupgrade', '--shell' ],
896 cmd
=> [ '/usr/bin/pveceph', 'install' ],
901 sub get_shell_command
{
902 my ($user, $shellcmd, $args) = @_;
905 if ($user eq 'root@pam') {
906 if (defined($shellcmd) && exists($shell_cmd_map->{$shellcmd})) {
907 my $def = $shell_cmd_map->{$shellcmd};
908 $cmd = [ @{$def->{cmd
}} ]; # clone
909 if (defined($args) && $def->{allow_args
}) {
910 push @$cmd, split("\0", $args);
913 $cmd = [ '/bin/login', '-f', 'root' ];
916 # non-root must always login for now, we do not have a superuser role!
917 $cmd = [ '/bin/login' ];
922 my $get_vnc_connection_info = sub {
927 my ($remip, $family);
928 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
929 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
930 $remote_cmd = ['/usr/bin/ssh', '-e', 'none', '-t', $remip , '--'];
932 $family = PVE
::Tools
::get_host_address_family
($node);
934 my $port = PVE
::Tools
::next_vnc_port
($family);
936 return ($port, $remote_cmd);
939 __PACKAGE__-
>register_method ({
945 description
=> "Restricted to users on realm 'pam'",
946 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
948 description
=> "Creates a VNC Shell proxy.",
950 additionalProperties
=> 0,
952 node
=> get_standard_option
('pve-node'),
955 description
=> "Run specific command or default to login.",
956 enum
=> [keys %$shell_cmd_map],
962 description
=> "Add parameters to a command. Encoded as null terminated strings.",
970 description
=> "use websocket instead of standard vnc.",
974 description
=> "sets the width of the console in pixels.",
981 description
=> "sets the height of the console in pixels.",
989 additionalProperties
=> 0,
991 user
=> { type
=> 'string' },
992 ticket
=> { type
=> 'string' },
993 cert
=> { type
=> 'string' },
994 port
=> { type
=> 'integer' },
995 upid
=> { type
=> 'string' },
1001 my $rpcenv = PVE
::RPCEnvironment
::get
();
1002 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
1004 raise_perm_exc
("realm != pam") if $realm ne 'pam';
1006 if (defined($param->{cmd
}) && $param->{cmd
} eq 'upgrade' && $user ne 'root@pam') {
1007 raise_perm_exc
('user != root@pam');
1010 my $node = $param->{node
};
1012 my $authpath = "/nodes/$node";
1013 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($user, $authpath);
1015 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
1018 my ($port, $remcmd) = $get_vnc_connection_info->($node);
1020 my $shcmd = get_shell_command
($user, $param->{cmd
}, $param->{'cmd-opts'});
1024 my $cmd = ['/usr/bin/vncterm',
1026 '-timeout', $timeout,
1027 '-authpath', $authpath,
1028 '-perm', 'Sys.Console',
1031 push @$cmd, '-width', $param->{width
} if $param->{width
};
1032 push @$cmd, '-height', $param->{height
} if $param->{height
};
1034 if ($param->{websocket
}) {
1035 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
1036 push @$cmd, '-notls', '-listen', 'localhost';
1039 push @$cmd, '-c', @$remcmd, @$shcmd;
1044 syslog
('info', "starting vnc proxy $upid\n");
1046 my $cmdstr = join (' ', @$cmd);
1047 syslog
('info', "launch command: $cmdstr");
1050 foreach my $k (keys %ENV) {
1051 next if $k eq 'PVE_VNC_TICKET';
1052 next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME' || $k eq 'LANG' || $k eq 'LANGUAGE';
1057 PVE
::Tools
::run_command
($cmd, errmsg
=> "vncterm failed", keeplocale
=> 1);
1060 syslog
('err', $err);
1066 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
1068 PVE
::Tools
::wait_for_vnc_port
($port);
1079 __PACKAGE__-
>register_method ({
1080 name
=> 'termproxy',
1081 path
=> 'termproxy',
1085 description
=> "Restricted to users on realm 'pam'",
1086 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1088 description
=> "Creates a VNC Shell proxy.",
1090 additionalProperties
=> 0,
1092 node
=> get_standard_option
('pve-node'),
1095 description
=> "Run specific command or default to login.",
1096 enum
=> [keys %$shell_cmd_map],
1102 description
=> "Add parameters to a command. Encoded as null terminated strings.",
1110 additionalProperties
=> 0,
1112 user
=> { type
=> 'string' },
1113 ticket
=> { type
=> 'string' },
1114 port
=> { type
=> 'integer' },
1115 upid
=> { type
=> 'string' },
1121 my $rpcenv = PVE
::RPCEnvironment
::get
();
1122 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
1123 raise_perm_exc
("realm $realm != pam") if $realm ne 'pam';
1125 my $node = $param->{node
};
1126 my $authpath = "/nodes/$node";
1127 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($user, $authpath);
1129 my ($port, $remcmd) = $get_vnc_connection_info->($node);
1131 my $shcmd = get_shell_command
($user, $param->{cmd
}, $param->{'cmd-opts'});
1136 syslog
('info', "starting termproxy $upid\n");
1139 '/usr/bin/termproxy',
1141 '--path', $authpath,
1142 '--perm', 'Sys.Console',
1145 push @$cmd, @$remcmd, @$shcmd;
1147 PVE
::Tools
::run_command
($cmd);
1149 my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
1151 PVE
::Tools
::wait_for_vnc_port
($port);
1161 __PACKAGE__-
>register_method({
1162 name
=> 'vncwebsocket',
1163 path
=> 'vncwebsocket',
1166 description
=> "Restricted to users on realm 'pam'. You also need to pass a valid ticket (vncticket).",
1167 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1169 description
=> "Opens a websocket for VNC traffic.",
1171 additionalProperties
=> 0,
1173 node
=> get_standard_option
('pve-node'),
1175 description
=> "Ticket from previous call to vncproxy.",
1180 description
=> "Port number returned by previous vncproxy call.",
1190 port
=> { type
=> 'string' },
1196 my $rpcenv = PVE
::RPCEnvironment
::get
();
1198 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($rpcenv->get_user());
1200 raise_perm_exc
("realm != pam") if $realm ne 'pam';
1202 my $authpath = "/nodes/$param->{node}";
1204 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $user, $authpath);
1206 my $port = $param->{port
};
1208 return { port
=> $port };
1211 __PACKAGE__-
>register_method ({
1212 name
=> 'spiceshell',
1213 path
=> 'spiceshell',
1218 description
=> "Restricted to users on realm 'pam'",
1219 check
=> ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
1221 description
=> "Creates a SPICE shell.",
1223 additionalProperties
=> 0,
1225 node
=> get_standard_option
('pve-node'),
1226 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1229 description
=> "Run specific command or default to login.",
1230 enum
=> [keys %$shell_cmd_map],
1236 description
=> "Add parameters to a command. Encoded as null terminated strings.",
1243 returns
=> get_standard_option
('remote-viewer-config'),
1247 my $rpcenv = PVE
::RPCEnvironment
::get
();
1248 my $authuser = $rpcenv->get_user();
1250 my ($user, undef, $realm) = PVE
::AccessControl
::verify_username
($authuser);
1252 raise_perm_exc
("realm != pam") if $realm ne 'pam';
1254 if (defined($param->{cmd
}) && $param->{cmd
} eq 'upgrade' && $user ne 'root@pam') {
1255 raise_perm_exc
('user != root@pam');
1258 my $node = $param->{node
};
1259 my $proxy = $param->{proxy
};
1261 my $authpath = "/nodes/$node";
1262 my $permissions = 'Sys.Console';
1264 my $shcmd = get_shell_command
($user, $param->{cmd
}, $param->{'cmd-opts'});
1266 my $title = "Shell on '$node'";
1268 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, 0, $node, $proxy, $title, $shcmd);
1271 __PACKAGE__-
>register_method({
1276 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1278 description
=> "Read DNS settings.",
1281 additionalProperties
=> 0,
1283 node
=> get_standard_option
('pve-node'),
1288 additionalProperties
=> 0,
1291 description
=> "Search domain for host-name lookup.",
1296 description
=> 'First name server IP address.',
1301 description
=> 'Second name server IP address.',
1306 description
=> 'Third name server IP address.',
1315 my $res = PVE
::INotify
::read_file
('resolvconf');
1320 __PACKAGE__-
>register_method({
1321 name
=> 'update_dns',
1324 description
=> "Write DNS settings.",
1326 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
1331 additionalProperties
=> 0,
1333 node
=> get_standard_option
('pve-node'),
1335 description
=> "Search domain for host-name lookup.",
1339 description
=> 'First name server IP address.',
1340 type
=> 'string', format
=> 'ip',
1344 description
=> 'Second name server IP address.',
1345 type
=> 'string', format
=> 'ip',
1349 description
=> 'Third name server IP address.',
1350 type
=> 'string', format
=> 'ip',
1355 returns
=> { type
=> "null" },
1359 PVE
::INotify
::update_file
('resolvconf', $param);
1364 __PACKAGE__-
>register_method({
1369 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1371 description
=> "Read server time and time zone settings.",
1374 additionalProperties
=> 0,
1376 node
=> get_standard_option
('pve-node'),
1381 additionalProperties
=> 0,
1384 description
=> "Time zone",
1388 description
=> "Seconds since 1970-01-01 00:00:00 UTC.",
1390 minimum
=> 1297163644,
1391 renderer
=> 'timestamp',
1394 description
=> "Seconds since 1970-01-01 00:00:00 (local time)",
1396 minimum
=> 1297163644,
1397 renderer
=> 'timestamp_gmt',
1405 my $ltime = timegm_nocheck
(localtime($ctime));
1407 timezone
=> PVE
::INotify
::read_file
('timezone'),
1409 localtime => $ltime,
1415 __PACKAGE__-
>register_method({
1416 name
=> 'set_timezone',
1419 description
=> "Set time zone.",
1421 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
1426 additionalProperties
=> 0,
1428 node
=> get_standard_option
('pve-node'),
1430 description
=> "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
1435 returns
=> { type
=> "null" },
1439 PVE
::INotify
::write_file
('timezone', $param->{timezone
});
1444 __PACKAGE__-
>register_method({
1451 description
=> "Get list of appliances.",
1454 additionalProperties
=> 0,
1456 node
=> get_standard_option
('pve-node'),
1469 my $list = PVE
::APLInfo
::load_data
();
1472 for my $appliance (values %{$list->{all
}}) {
1473 next if $appliance->{'package'} eq 'pve-web-news';
1474 push @$res, $appliance;
1480 __PACKAGE__-
>register_method({
1481 name
=> 'apl_download',
1485 check
=> ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
1487 description
=> "Download appliance templates.",
1491 additionalProperties
=> 0,
1493 node
=> get_standard_option
('pve-node'),
1494 storage
=> get_standard_option
('pve-storage-id', {
1495 description
=> "The storage where the template will be stored",
1496 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1500 description
=> "The template which will downloaded",
1502 completion
=> \
&complete_templet_repo
,
1506 returns
=> { type
=> "string" },
1510 my $rpcenv = PVE
::RPCEnvironment
::get
();
1511 my $user = $rpcenv->get_user();
1513 my $node = $param->{node
};
1514 my $template = $param->{template
};
1516 my $list = PVE
::APLInfo
::load_data
();
1517 my $appliance = $list->{all
}->{$template};
1518 raise_param_exc
({ template
=> "no such template"}) if !$appliance;
1520 my $cfg = PVE
::Storage
::config
();
1521 my $scfg = PVE
::Storage
::storage_check_enabled
($cfg, $param->{storage
}, $node);
1523 die "unknown template type '$appliance->{type}'\n"
1524 if !($appliance->{type
} eq 'openvz' || $appliance->{type
} eq 'lxc');
1526 die "storage '$param->{storage}' does not support templates\n"
1527 if !$scfg->{content
}->{vztmpl
};
1529 my $tmpldir = PVE
::Storage
::get_vztmpl_dir
($cfg, $param->{storage
});
1532 my $dccfg = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1534 PVE
::Tools
::download_file_from_url
("$tmpldir/$template", $appliance->{location
}, {
1536 sha512sum
=> $appliance->{sha512sum
},
1537 md5sum
=> $appliance->{md5sum
},
1538 http_proxy
=> $dccfg->{http_proxy
},
1542 my $upid = $rpcenv->fork_worker('download', $template, $user, $worker);
1547 __PACKAGE__-
>register_method({
1548 name
=> 'query_url_metadata',
1549 path
=> 'query-url-metadata',
1551 description
=> "Query metadata of an URL: file size, file name and mime type.",
1554 check
=> ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]],
1557 additionalProperties
=> 0,
1559 node
=> get_standard_option
('pve-node'),
1561 description
=> "The URL to query the metadata from.",
1563 pattern
=> 'https?://.*',
1565 'verify-certificates' => {
1566 description
=> "If false, no SSL/TLS certificates will be verified.",
1582 renderer
=> 'bytes',
1594 my $url = $param->{url
};
1596 my $ua = LWP
::UserAgent-
>new();
1597 $ua->agent("Proxmox VE");
1599 my $dccfg = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1600 if ($dccfg->{http_proxy
}) {
1601 $ua->proxy('http', $dccfg->{http_proxy
});
1604 my $verify = $param->{'verify-certificates'} // 1;
1607 verify_hostname
=> 0,
1608 SSL_verify_mode
=> IO
::Socket
::SSL
::SSL_VERIFY_NONE
,
1612 my $req = HTTP
::Request-
>new(HEAD
=> $url);
1613 my $res = $ua->request($req);
1615 die "invalid server response: '" . $res->status_line() . "'\n" if ($res->code() != 200);
1617 my $size = $res->header("Content-Length");
1618 my $disposition = $res->header("Content-Disposition");
1619 my $type = $res->header("Content-Type");
1623 if ($disposition && ($disposition =~ m/filename="([^"]*)"/ || $disposition =~ m/filename=([^;]*)/)) {
1625 } elsif ($url =~ m!^[^?]+/([^?/]*)(?:\?.*)?$!) {
1629 # Content-Type: text/html; charset=utf-8
1630 if ($type && $type =~ m/^([^;]+);/) {
1635 $ret->{filename
} = $filename if $filename;
1636 $ret->{size
} = $size + 0 if $size;
1637 $ret->{mimetype
} = $type if $type;
1642 __PACKAGE__-
>register_method({
1647 check
=> ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
1650 description
=> "Gather various systems information about a node",
1653 additionalProperties
=> 0,
1655 node
=> get_standard_option
('pve-node'),
1662 return PVE
::Report
::generate
();
1665 # returns a list of VMIDs, those can be filtered by
1666 # * current parent node
1668 # * guest is a template (default: skip)
1669 # * guest is HA manged (default: skip)
1670 my $get_filtered_vmlist = sub {
1671 my ($nodename, $vmfilter, $templates, $ha_managed) = @_;
1673 my $vmlist = PVE
::Cluster
::get_vmlist
();
1676 if (defined($vmfilter)) {
1677 $vms_allowed = { map { $_ => 1 } PVE
::Tools
::split_list
($vmfilter) };
1681 foreach my $vmid (keys %{$vmlist->{ids
}}) {
1682 next if defined($vms_allowed) && !$vms_allowed->{$vmid};
1684 my $d = $vmlist->{ids
}->{$vmid};
1685 next if $nodename && $d->{node
} ne $nodename;
1689 if ($d->{type
} eq 'lxc') {
1690 $class = 'PVE::LXC::Config';
1691 } elsif ($d->{type
} eq 'qemu') {
1692 $class = 'PVE::QemuConfig';
1694 die "unknown virtual guest type '$d->{type}'\n";
1697 my $conf = $class->load_config($vmid);
1698 return if !$templates && $class->is_template($conf);
1699 return if !$ha_managed && PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
1701 $res->{$vmid}->{conf
} = $conf;
1702 $res->{$vmid}->{type
} = $d->{type
};
1703 $res->{$vmid}->{class} = $class;
1711 # return all VMs which should get started/stopped on power up/down
1712 my $get_start_stop_list = sub {
1713 my ($nodename, $autostart, $vmfilter) = @_;
1715 # do not skip HA vms on force or if a specific VMID set is wanted
1716 my $include_ha_managed = defined($vmfilter) ?
1 : 0;
1718 my $vmlist = $get_filtered_vmlist->($nodename, $vmfilter, undef, $include_ha_managed);
1721 foreach my $vmid (keys %$vmlist) {
1722 my $conf = $vmlist->{$vmid}->{conf
};
1723 next if $autostart && !$conf->{onboot
};
1725 my $startup = $conf->{startup
} ? PVE
::JSONSchema
::pve_parse_startup_order
($conf->{startup
}) : {};
1726 my $order = $startup->{order
} = $startup->{order
} // LONG_MAX
;
1728 $resList->{$order}->{$vmid} = $startup;
1729 $resList->{$order}->{$vmid}->{type
} = $vmlist->{$vmid}->{type
};
1735 my $remove_locks_on_startup = sub {
1736 my ($nodename) = @_;
1738 my $vmlist = &$get_filtered_vmlist($nodename, undef, undef, 1);
1740 foreach my $vmid (keys %$vmlist) {
1741 my $conf = $vmlist->{$vmid}->{conf
};
1742 my $class = $vmlist->{$vmid}->{class};
1745 if ($class->has_lock($conf, 'backup')) {
1746 $class->remove_lock($vmid, 'backup');
1747 my $msg = "removed left over backup lock from '$vmid'!";
1748 warn "$msg\n"; # prints to task log
1749 syslog
('warning', $msg);
1755 __PACKAGE__-
>register_method ({
1761 description
=> "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/<ID>' for "
1762 ."each ID passed via the 'vms' parameter.",
1766 description
=> "Start all VMs and containers located on this node (by default only those with onboot=1).",
1768 additionalProperties
=> 0,
1770 node
=> get_standard_option
('pve-node'),
1775 description
=> "Issue start command even if virtual guest have 'onboot' not set or set to off.",
1778 description
=> "Only consider guests from this comma separated list of VMIDs.",
1779 type
=> 'string', format
=> 'pve-vmid-list',
1790 my $rpcenv = PVE
::RPCEnvironment
::get
();
1791 my $authuser = $rpcenv->get_user();
1793 if (!$rpcenv->check($authuser, "/", [ 'VM.PowerMgmt' ], 1)) {
1794 my @vms = PVE
::Tools
::split_list
($param->{vms
});
1795 if (scalar(@vms) > 0) {
1796 $rpcenv->check($authuser, "/vms/$_", [ 'VM.PowerMgmt' ]) for @vms;
1798 raise_perm_exc
("/, VM.PowerMgmt");
1802 my $nodename = $param->{node
};
1803 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1805 my $force = $param->{force
};
1808 $rpcenv->{type
} = 'priv'; # to start tasks in background
1810 if (!PVE
::Cluster
::check_cfs_quorum
(1)) {
1811 print "waiting for quorum ...\n";
1814 } while (!PVE
::Cluster
::check_cfs_quorum
(1));
1815 print "got quorum\n";
1818 eval { # remove backup locks, but avoid running into a scheduled backup job
1819 PVE
::Tools
::lock_file
('/var/run/vzdump.lock', 10, $remove_locks_on_startup, $nodename);
1823 my $autostart = $force ?
undef : 1;
1824 my $startList = $get_start_stop_list->($nodename, $autostart, $param->{vms
});
1826 # Note: use numeric sorting with <=>
1827 for my $order (sort {$a <=> $b} keys %$startList) {
1828 my $vmlist = $startList->{$order};
1830 for my $vmid (sort {$a <=> $b} keys %$vmlist) {
1831 my $d = $vmlist->{$vmid};
1833 PVE
::Cluster
::check_cfs_quorum
(); # abort when we loose quorum
1836 my $default_delay = 0;
1839 if ($d->{type
} eq 'lxc') {
1840 return if PVE
::LXC
::check_running
($vmid);
1841 print STDERR
"Starting CT $vmid\n";
1842 $upid = PVE
::API2
::LXC
::Status-
>vm_start({node
=> $nodename, vmid
=> $vmid });
1843 } elsif ($d->{type
} eq 'qemu') {
1844 $default_delay = 3; # to reduce load
1845 return if PVE
::QemuServer
::check_running
($vmid, 1);
1846 print STDERR
"Starting VM $vmid\n";
1847 $upid = PVE
::API2
::Qemu-
>vm_start({node
=> $nodename, vmid
=> $vmid });
1849 die "unknown VM type '$d->{type}'\n";
1852 my $task = PVE
::Tools
::upid_decode
($upid);
1853 while (PVE
::ProcFSTools
::check_process_running
($task->{pid
})) {
1857 my $status = PVE
::Tools
::upid_read_status
($upid);
1858 if (!PVE
::Tools
::upid_status_is_error
($status)) {
1859 # use default delay to reduce load
1860 my $delay = defined($d->{up
}) ?
int($d->{up
}) : $default_delay;
1862 print STDERR
"Waiting for $delay seconds (startup delay)\n" if $d->{up
};
1863 for (my $i = 0; $i < $delay; $i++) {
1868 my $rendered_type = $d->{type
} eq 'lxc' ?
'CT' : 'VM';
1869 print STDERR
"Starting $rendered_type $vmid failed: $status\n";
1878 return $rpcenv->fork_worker('startall', undef, $authuser, $code);
1881 my $create_stop_worker = sub {
1882 my ($nodename, $type, $vmid, $timeout, $force_stop) = @_;
1884 if ($type eq 'lxc') {
1885 return if !PVE
::LXC
::check_running
($vmid);
1886 print STDERR
"Stopping CT $vmid (timeout = $timeout seconds)\n";
1887 return PVE
::API2
::LXC
::Status-
>vm_shutdown(
1888 { node
=> $nodename, vmid
=> $vmid, timeout
=> $timeout, forceStop
=> $force_stop }
1890 } elsif ($type eq 'qemu') {
1891 return if !PVE
::QemuServer
::check_running
($vmid, 1);
1892 print STDERR
"Stopping VM $vmid (timeout = $timeout seconds)\n";
1893 return PVE
::API2
::Qemu-
>vm_shutdown(
1894 { node
=> $nodename, vmid
=> $vmid, timeout
=> $timeout, forceStop
=> $force_stop }
1897 die "unknown VM type '$type'\n";
1901 __PACKAGE__-
>register_method ({
1907 description
=> "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/<ID>' for "
1908 ."each ID passed via the 'vms' parameter.",
1912 description
=> "Stop all VMs and Containers.",
1914 additionalProperties
=> 0,
1916 node
=> get_standard_option
('pve-node'),
1918 description
=> "Only consider Guests with these IDs.",
1919 type
=> 'string', format
=> 'pve-vmid-list',
1923 description
=> 'Force a hard-stop after the timeout.',
1929 description
=> 'Timeout for each guest shutdown task. Depending on `force-stop`,'
1930 .' the shutdown gets then simply aborted or a hard-stop is forced.',
1935 maximum
=> 2 * 3600, # mostly arbitrary, but we do not want to high timeouts
1945 my $rpcenv = PVE
::RPCEnvironment
::get
();
1946 my $authuser = $rpcenv->get_user();
1948 if (!$rpcenv->check($authuser, "/", [ 'VM.PowerMgmt' ], 1)) {
1949 my @vms = PVE
::Tools
::split_list
($param->{vms
});
1950 if (scalar(@vms) > 0) {
1951 $rpcenv->check($authuser, "/vms/$_", [ 'VM.PowerMgmt' ]) for @vms;
1953 raise_perm_exc
("/, VM.PowerMgmt");
1957 my $nodename = $param->{node
};
1958 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
1962 $rpcenv->{type
} = 'priv'; # to start tasks in background
1964 my $stopList = $get_start_stop_list->($nodename, undef, $param->{vms
});
1966 my $cpuinfo = PVE
::ProcFSTools
::read_cpuinfo
();
1967 my $datacenterconfig = cfs_read_file
('datacenter.cfg');
1968 # if not set by user spawn max cpu count number of workers
1969 my $maxWorkers = $datacenterconfig->{max_workers
} || $cpuinfo->{cpus
};
1971 for my $order (sort {$b <=> $a} keys %$stopList) {
1972 my $vmlist = $stopList->{$order};
1975 my $finish_worker = sub {
1977 my $worker = delete $workers->{$pid} || return;
1979 syslog
('info', "end task $worker->{upid}");
1982 for my $vmid (sort {$b <=> $a} keys %$vmlist) {
1983 my $d = $vmlist->{$vmid};
1984 my $timeout = int($d->{down
} // $param->{timeout
} // 180);
1986 $create_stop_worker->(
1987 $nodename, $d->{type
}, $vmid, $timeout, $param->{'force-stop'} // 1)
1992 my $task = PVE
::Tools
::upid_decode
($upid, 1);
1995 my $pid = $task->{pid
};
1997 $workers->{$pid} = { type
=> $d->{type
}, upid
=> $upid, vmid
=> $vmid };
1998 while (scalar(keys %$workers) >= $maxWorkers) {
1999 foreach my $p (keys %$workers) {
2000 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
2001 $finish_worker->($p);
2007 while (scalar(keys %$workers)) {
2008 for my $p (keys %$workers) {
2009 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
2010 $finish_worker->($p);
2017 syslog
('info', "all VMs and CTs stopped");
2022 return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
2025 my $create_migrate_worker = sub {
2026 my ($nodename, $type, $vmid, $target, $with_local_disks) = @_;
2029 if ($type eq 'lxc') {
2030 my $online = PVE
::LXC
::check_running
($vmid) ?
1 : 0;
2031 print STDERR
"Migrating CT $vmid\n";
2032 $upid = PVE
::API2
::LXC-
>migrate_vm(
2033 { node
=> $nodename, vmid
=> $vmid, target
=> $target, restart
=> $online });
2034 } elsif ($type eq 'qemu') {
2035 print STDERR
"Check VM $vmid: ";
2037 my $online = PVE
::QemuServer
::check_running
($vmid, 1) ?
1 : 0;
2038 my $preconditions = PVE
::API2
::Qemu-
>migrate_vm_precondition(
2039 {node
=> $nodename, vmid
=> $vmid, target
=> $target});
2040 my $invalidConditions = '';
2041 if ($online && !$with_local_disks && scalar @{$preconditions->{local_disks
}}) {
2042 $invalidConditions .= "\n Has local disks: ";
2043 $invalidConditions .= join(', ', map { $_->{volid
} } @{$preconditions->{local_disks
}});
2046 if (@{$preconditions->{local_resources
}}) {
2047 $invalidConditions .= "\n Has local resources: ";
2048 $invalidConditions .= join(', ', @{$preconditions->{local_resources
}});
2051 if ($invalidConditions && $invalidConditions ne '') {
2052 print STDERR
"skip VM $vmid - precondition check failed:";
2053 die "$invalidConditions\n";
2055 print STDERR
"precondition check passed\n";
2056 print STDERR
"Migrating VM $vmid\n";
2064 $params->{'with-local-disks'} = $with_local_disks if defined($with_local_disks);
2066 $upid = PVE
::API2
::Qemu-
>migrate_vm($params);
2068 die "unknown VM type '$type'\n";
2071 my $task = PVE
::Tools
::upid_decode
($upid);
2073 return $task->{pid
};
2076 __PACKAGE__-
>register_method ({
2077 name
=> 'migrateall',
2078 path
=> 'migrateall',
2083 description
=> "The 'VM.Migrate' permission is required on '/' or on '/vms/<ID>' for each "
2084 ."ID passed via the 'vms' parameter.",
2087 description
=> "Migrate all VMs and Containers.",
2089 additionalProperties
=> 0,
2091 node
=> get_standard_option
('pve-node'),
2092 target
=> get_standard_option
('pve-node', { description
=> "Target node." }),
2094 description
=> "Maximal number of parallel migration job. If not set, uses"
2095 ."'max_workers' from datacenter.cfg. One of both must be set!",
2101 description
=> "Only consider Guests with these IDs.",
2102 type
=> 'string', format
=> 'pve-vmid-list',
2105 "with-local-disks" => {
2107 description
=> "Enable live storage migration for local disk",
2118 my $rpcenv = PVE
::RPCEnvironment
::get
();
2119 my $authuser = $rpcenv->get_user();
2121 if (!$rpcenv->check($authuser, "/", [ 'VM.Migrate' ], 1)) {
2122 my @vms = PVE
::Tools
::split_list
($param->{vms
});
2123 if (scalar(@vms) > 0) {
2124 $rpcenv->check($authuser, "/vms/$_", [ 'VM.Migrate' ]) for @vms;
2126 raise_perm_exc
("/, VM.Migrate");
2130 my $nodename = $param->{node
};
2131 $nodename = PVE
::INotify
::nodename
() if $nodename eq 'localhost';
2133 my $target = $param->{target
};
2134 my $with_local_disks = $param->{'with-local-disks'};
2135 raise_param_exc
({ target
=> "target is local node."}) if $target eq $nodename;
2137 PVE
::Cluster
::check_cfs_quorum
();
2139 PVE
::Cluster
::check_node_exists
($target);
2141 my $datacenterconfig = cfs_read_file
('datacenter.cfg');
2142 # prefer parameter over datacenter cfg settings
2143 my $maxWorkers = $param->{maxworkers
} || $datacenterconfig->{max_workers
} ||
2144 die "either 'maxworkers' parameter or max_workers in datacenter.cfg must be set!\n";
2147 $rpcenv->{type
} = 'priv'; # to start tasks in background
2149 my $vmlist = &$get_filtered_vmlist($nodename, $param->{vms
}, 1, 1);
2150 if (!scalar(keys %$vmlist)) {
2151 warn "no virtual guests matched, nothing to do..\n";
2156 my $workers_started = 0;
2157 foreach my $vmid (sort keys %$vmlist) {
2158 my $d = $vmlist->{$vmid};
2160 eval { $pid = &$create_migrate_worker($nodename, $d->{type
}, $vmid, $target, $with_local_disks); };
2165 $workers->{$pid} = 1;
2166 while (scalar(keys %$workers) >= $maxWorkers) {
2167 foreach my $p (keys %$workers) {
2168 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
2169 delete $workers->{$p};
2175 while (scalar(keys %$workers)) {
2176 foreach my $p (keys %$workers) {
2177 # FIXME: what about PID re-use ?!?!
2178 if (!PVE
::ProcFSTools
::check_process_running
($p)) {
2179 delete $workers->{$p};
2184 if ($workers_started <= 0) {
2185 die "no migrations worker started...\n";
2187 print STDERR
"All jobs finished, used $workers_started workers in total.\n";
2191 return $rpcenv->fork_worker('migrateall', undef, $authuser, $code);
2195 __PACKAGE__-
>register_method ({
2196 name
=> 'get_etc_hosts',
2202 check
=> ['perm', '/', [ 'Sys.Audit' ]],
2204 description
=> "Get the content of /etc/hosts.",
2206 additionalProperties
=> 0,
2208 node
=> get_standard_option
('pve-node'),
2214 digest
=> get_standard_option
('pve-config-digest'),
2217 description
=> 'The content of /etc/hosts.'
2224 return PVE
::INotify
::read_file
('etchosts');
2228 __PACKAGE__-
>register_method ({
2229 name
=> 'write_etc_hosts',
2235 check
=> ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
2237 description
=> "Write /etc/hosts.",
2239 additionalProperties
=> 0,
2241 node
=> get_standard_option
('pve-node'),
2242 digest
=> get_standard_option
('pve-config-digest'),
2245 description
=> 'The target content of /etc/hosts.'
2255 PVE
::Tools
::lock_file
('/var/lock/pve-etchosts.lck', undef, sub {
2256 if ($param->{digest
}) {
2257 my $hosts = PVE
::INotify
::read_file
('etchosts');
2258 PVE
::Tools
::assert_if_modified
($hosts->{digest
}, $param->{digest
});
2260 PVE
::INotify
::write_file
('etchosts', $param->{data
});
2267 # bash completion helper
2269 sub complete_templet_repo
{
2270 my ($cmdname, $pname, $cvalue) = @_;
2272 my $repo = PVE
::APLInfo
::load_data
();
2274 foreach my $templ (keys %{$repo->{all
}}) {
2275 next if $templ !~ m/^$cvalue/;
2282 package PVE
::API2
::Nodes
;
2287 use PVE
::SafeSyslog
;
2289 use PVE
::RESTHandler
;
2290 use PVE
::RPCEnvironment
;
2292 use PVE
::JSONSchema
qw(get_standard_option);
2294 use base
qw(PVE::RESTHandler);
2296 __PACKAGE__-
>register_method ({
2297 subclass
=> "PVE::API2::Nodes::Nodeinfo",
2301 __PACKAGE__-
>register_method ({
2305 permissions
=> { user
=> 'all' },
2306 description
=> "Cluster node index.",
2308 additionalProperties
=> 0,
2316 node
=> get_standard_option
('pve-node'),
2318 description
=> "Node status.",
2320 enum
=> ['unknown', 'online', 'offline'],
2323 description
=> "CPU utilization.",
2326 renderer
=> 'fraction_as_percentage',
2329 description
=> "Number of available CPUs.",
2334 description
=> "Used memory in bytes.",
2337 renderer
=> 'bytes',
2340 description
=> "Number of available memory in bytes.",
2343 renderer
=> 'bytes',
2346 description
=> "Support level.",
2351 description
=> "Node uptime in seconds.",
2354 renderer
=> 'duration',
2356 ssl_fingerprint
=> {
2357 description
=> "The SSL fingerprint for the node certificate.",
2363 links
=> [ { rel
=> 'child', href
=> "{node}" } ],
2368 my $rpcenv = PVE
::RPCEnvironment
::get
();
2369 my $authuser = $rpcenv->get_user();
2371 my $clinfo = PVE
::Cluster
::get_clinfo
();
2374 my $nodelist = PVE
::Cluster
::get_nodelist
();
2375 my $members = PVE
::Cluster
::get_members
();
2376 my $rrd = PVE
::Cluster
::rrd_dump
();
2378 foreach my $node (@$nodelist) {
2379 my $can_audit = $rpcenv->check($authuser, "/nodes/$node", [ 'Sys.Audit' ], 1);
2380 my $entry = PVE
::API2Tools
::extract_node_stats
($node, $members, $rrd, !$can_audit);
2382 $entry->{ssl_fingerprint
} = eval { PVE
::Cluster
::get_node_fingerprint
($node) };