pve-manager/PVE/API2/Nodes.pm
Thomas Lamprecht 180a86d39a pvecfg: adapt version and release semantic
Wit commit a74ba607d4 we switched over
to using the dpkg-dev provided helpers to set package version,
architecture and such in the buildsystem.

But unlike other repositories we used the version also for giving it
back over the API through the during build generated PVE::pvecfg
module, which wasn't fully updated to the new style.

This patch does that, and also cleans up semantics a bit, the
following two changed:

release is now the Debian release, instead of the "package release"
(i.e., the -X part of a full package version).
version is now simply the full (pve-manager) version, e.g., 6.0-1 or
the currently for testing used 6.0-0+1

This allows to do everything we used this information for even in a
slightly easier way (no  string concat needed anymore), and fits also
with the terminology we often used in our public channels (mailing
lists, forum, website)

Remove some cruft as we touch things.

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2019-05-26 16:11:43 +02:00

2182 lines
52 KiB
Perl

package PVE::API2::Nodes::Nodeinfo;
use strict;
use warnings;
use POSIX qw(LONG_MAX);
use Filesys::Df;
use Time::Local qw(timegm_nocheck);
use HTTP::Status qw(:constants);
use PVE::pvecfg;
use PVE::Tools;
use PVE::API2Tools;
use PVE::ProcFSTools;
use PVE::SafeSyslog;
use PVE::Cluster qw(cfs_read_file);
use PVE::INotify;
use PVE::Exception qw(raise raise_perm_exc raise_param_exc);
use PVE::RESTHandler;
use PVE::RPCEnvironment;
use PVE::JSONSchema qw(get_standard_option);
use PVE::AccessControl;
use PVE::Storage;
use PVE::Firewall;
use PVE::LXC;
use PVE::APLInfo;
use PVE::Report;
use PVE::HA::Env::PVE2;
use PVE::HA::Config;
use PVE::QemuConfig;
use PVE::QemuServer;
use PVE::API2::Subscription;
use PVE::API2::Services;
use PVE::API2::Network;
use PVE::API2::Tasks;
use PVE::API2::Scan;
use PVE::API2::Storage::Status;
use PVE::API2::Qemu;
use PVE::API2::LXC;
use PVE::API2::LXC::Status;
use PVE::API2::VZDump;
use PVE::API2::APT;
use PVE::API2::Ceph;
use PVE::API2::Firewall::Host;
use PVE::API2::Replication;
use PVE::API2::Certificates;
use PVE::API2::NodeConfig;
use PVE::API2::Hardware;
use Digest::MD5;
use Digest::SHA;
use PVE::API2::Disks;
use JSON;
use Socket;
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
subclass => "PVE::API2::Qemu",
path => 'qemu',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::LXC",
path => 'lxc',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Ceph",
path => 'ceph',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::VZDump",
path => 'vzdump',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Services",
path => 'services',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Subscription",
path => 'subscription',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Network",
path => 'network',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Tasks",
path => 'tasks',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Scan",
path => 'scan',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Hardware",
path => 'hardware',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Storage::Status",
path => 'storage',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Disks",
path => 'disks',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::APT",
path => 'apt',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Firewall::Host",
path => 'firewall',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Replication",
path => 'replication',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Certificates",
path => 'certificates',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::NodeConfig",
path => 'config',
});
__PACKAGE__->register_method ({
name => 'index',
path => '',
method => 'GET',
permissions => { user => 'all' },
description => "Node index.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
},
code => sub {
my ($param) = @_;
my $result = [
{ name => 'ceph' },
{ name => 'disks' },
{ name => 'apt' },
{ name => 'version' },
{ name => 'syslog' },
{ name => 'journal' },
{ name => 'status' },
{ name => 'wakeonlan' },
{ name => 'subscription' },
{ name => 'report' },
{ name => 'tasks' },
{ name => 'rrd' }, # fixme: remove?
{ name => 'rrddata' },# fixme: remove?
{ name => 'replication' },
{ name => 'vncshell' },
{ name => 'termproxy' },
{ name => 'spiceshell' },
{ name => 'time' },
{ name => 'dns' },
{ name => 'services' },
{ name => 'scan' },
{ name => 'storage' },
{ name => 'qemu' },
{ name => 'lxc' },
{ name => 'vzdump' },
{ name => 'network' },
{ name => 'aplinfo' },
{ name => 'startall' },
{ name => 'stopall' },
{ name => 'netstat' },
{ name => 'firewall' },
{ name => 'certificates' },
{ name => 'config' },
{ name => 'hosts' },
];
return $result;
}});
__PACKAGE__->register_method ({
name => 'version',
path => 'version',
method => 'GET',
proxyto => 'node',
permissions => { user => 'all' },
description => "API version details",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => "object",
properties => {
version => {
type => 'string',
description => 'The current installed pve-manager package version',
},
release => {
type => 'string',
description => 'The current installed Proxmox VE Release',
},
repoid => {
type => 'string',
description => 'The short git commit hash ID from which this version was build',
},
},
},
code => sub {
my ($resp, $param) = @_;
return PVE::pvecfg::version_info();
}});
__PACKAGE__->register_method({
name => 'status',
path => 'status',
method => 'GET',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
},
description => "Read node status",
proxyto => 'node',
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => "object",
properties => {
},
},
code => sub {
my ($param) = @_;
my $res = {
uptime => 0,
idle => 0,
};
my ($uptime, $idle) = PVE::ProcFSTools::read_proc_uptime();
$res->{uptime} = $uptime;
my ($avg1, $avg5, $avg15) = PVE::ProcFSTools::read_loadavg();
$res->{loadavg} = [ $avg1, $avg5, $avg15];
my ($sysname, $nodename, $release, $version, $machine) = POSIX::uname();
$res->{kversion} = "$sysname $release $version";
$res->{cpuinfo} = PVE::ProcFSTools::read_cpuinfo();
my $stat = PVE::ProcFSTools::read_proc_stat();
$res->{cpu} = $stat->{cpu};
$res->{wait} = $stat->{wait};
my $meminfo = PVE::ProcFSTools::read_meminfo();
$res->{memory} = {
free => $meminfo->{memfree},
total => $meminfo->{memtotal},
used => $meminfo->{memused},
};
$res->{ksm} = {
shared => $meminfo->{memshared},
};
$res->{swap} = {
free => $meminfo->{swapfree},
total => $meminfo->{swaptotal},
used => $meminfo->{swapused},
};
$res->{pveversion} = PVE::pvecfg::package() . "/" .
PVE::pvecfg::version_text();
my $dinfo = df('/', 1); # output is bytes
$res->{rootfs} = {
total => $dinfo->{blocks},
avail => $dinfo->{bavail},
used => $dinfo->{used},
free => $dinfo->{blocks} - $dinfo->{used},
};
return $res;
}});
__PACKAGE__->register_method({
name => 'netstat',
path => 'netstat',
method => 'GET',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
},
description => "Read tap/vm network device interface counters",
proxyto => 'node',
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => "array",
items => {
type => "object",
properties => {},
},
},
code => sub {
my ($param) = @_;
my $res = [ ];
my $netdev = PVE::ProcFSTools::read_proc_net_dev();
foreach my $dev (keys %$netdev) {
next if $dev !~ m/^(?:tap|veth)([1-9]\d*)i(\d+)$/;
my $vmid = $1;
my $netid = $2;
push(
@$res,
{
vmid => $vmid,
dev => "net$netid",
in => $netdev->{$dev}->{transmit},
out => $netdev->{$dev}->{receive},
}
);
}
return $res;
}});
__PACKAGE__->register_method({
name => 'execute',
path => 'execute',
method => 'POST',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
},
description => "Execute multiple commands in order.",
proxyto => 'node',
protected => 1, # avoid problems with proxy code
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
commands => {
description => "JSON encoded array of commands.",
type => "string",
}
},
},
returns => {
type => 'array',
properties => {
},
},
code => sub {
my ($param) = @_;
my $res = [];
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $commands = eval { decode_json($param->{commands}); };
die "commands param did not contain valid JSON: $@" if $@;
die "commands is not an array" if ref($commands) ne "ARRAY";
foreach my $cmd (@$commands) {
eval {
die "$cmd is not a valid command" if (ref($cmd) ne "HASH" || !$cmd->{path} || !$cmd->{method});
$cmd->{args} //= {};
my $path = "nodes/$param->{node}/$cmd->{path}";
my $uri_param = {};
my ($handler, $info) = PVE::API2->find_handler($cmd->{method}, $path, $uri_param);
if (!$handler || !$info) {
die "no handler for '$path'\n";
}
foreach my $p (keys %{$cmd->{args}}) {
raise_param_exc({ $p => "duplicate parameter" }) if defined($uri_param->{$p});
$uri_param->{$p} = $cmd->{args}->{$p};
}
# check access permissions
$rpcenv->check_api2_permissions($info->{permissions}, $user, $uri_param);
push @$res, {
status => HTTP_OK,
data => $handler->handle($info, $uri_param),
};
};
if (my $err = $@) {
my $resp = { status => HTTP_INTERNAL_SERVER_ERROR };
if (ref($err) eq "PVE::Exception") {
$resp->{status} = $err->{code} if $err->{code};
$resp->{errors} = $err->{errors} if $err->{errors};
$resp->{message} = $err->{msg};
} else {
$resp->{message} = $err;
}
push @$res, $resp;
}
}
return $res;
}});
__PACKAGE__->register_method({
name => 'node_cmd',
path => 'status',
method => 'POST',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
},
protected => 1,
description => "Reboot or shutdown a node.",
proxyto => 'node',
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
command => {
description => "Specify the command.",
type => 'string',
enum => [qw(reboot shutdown)],
},
},
},
returns => { type => "null" },
code => sub {
my ($param) = @_;
if ($param->{command} eq 'reboot') {
system ("(sleep 2;/sbin/reboot)&");
} elsif ($param->{command} eq 'shutdown') {
system ("(sleep 2;/sbin/poweroff)&");
}
return undef;
}});
__PACKAGE__->register_method({
name => 'wakeonlan',
path => 'wakeonlan',
method => 'POST',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]],
},
protected => 1,
description => "Try to wake a node via 'wake on LAN' network packet.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node', {
description => 'target node for wake on LAN packet',
completion => sub {
my $members = PVE::Cluster::get_members();
return [ grep { !$members->{$_}->{online} } keys %$members ];
}
}),
},
},
returns => {
type => 'string',
format => 'mac-addr',
description => 'MAC address used to assemble the WoL magic packet.',
},
code => sub {
my ($param) = @_;
my $node = $param->{node};
die "'$node' is local node, cannot wake my self!\n"
if $node eq 'localhost' || $node eq PVE::INotify::nodename();
PVE::Cluster::check_node_exists($node);
my $config = PVE::NodeConfig::load_config($node);
my $mac_addr = $config->{wakeonlan};
if (!defined($mac_addr)) {
die "No wake on LAN MAC address defined for '$node'!\n";
}
$mac_addr =~ s/://g;
my $packet = chr(0xff) x 6 . pack('H*', $mac_addr) x 16;
my $addr = gethostbyname('255.255.255.255');
my $port = getservbyname('discard', 'udp');
my $to = Socket::pack_sockaddr_in($port, $addr);
socket(my $sock, Socket::AF_INET, Socket::SOCK_DGRAM, Socket::IPPROTO_UDP)
|| die "Unable to open socket: $!\n";
setsockopt($sock, Socket::SOL_SOCKET, Socket::SO_BROADCAST, 1)
|| die "Unable to set socket option: $!\n";
send($sock, $packet, 0, $to)
|| die "Unable to send packet: $!\n";
close($sock);
return $config->{wakeonlan};
}});
__PACKAGE__->register_method({
name => 'rrd',
path => 'rrd',
method => 'GET',
protected => 1, # fixme: can we avoid that?
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
},
description => "Read node RRD statistics (returns PNG)",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
timeframe => {
description => "Specify the time frame you are interested in.",
type => 'string',
enum => [ 'hour', 'day', 'week', 'month', 'year' ],
},
ds => {
description => "The list of datasources you want to display.",
type => 'string', format => 'pve-configid-list',
},
cf => {
description => "The RRD consolidation function",
type => 'string',
enum => [ 'AVERAGE', 'MAX' ],
optional => 1,
},
},
},
returns => {
type => "object",
properties => {
filename => { type => 'string' },
},
},
code => sub {
my ($param) = @_;
return PVE::Cluster::create_rrd_graph(
"pve2-node/$param->{node}", $param->{timeframe},
$param->{ds}, $param->{cf});
}});
__PACKAGE__->register_method({
name => 'rrddata',
path => 'rrddata',
method => 'GET',
protected => 1, # fixme: can we avoid that?
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
},
description => "Read node RRD statistics",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
timeframe => {
description => "Specify the time frame you are interested in.",
type => 'string',
enum => [ 'hour', 'day', 'week', 'month', 'year' ],
},
cf => {
description => "The RRD consolidation function",
type => 'string',
enum => [ 'AVERAGE', 'MAX' ],
optional => 1,
},
},
},
returns => {
type => "array",
items => {
type => "object",
properties => {},
},
},
code => sub {
my ($param) = @_;
return PVE::Cluster::create_rrd_data(
"pve2-node/$param->{node}", $param->{timeframe}, $param->{cf});
}});
__PACKAGE__->register_method({
name => 'syslog',
path => 'syslog',
method => 'GET',
description => "Read system log",
proxyto => 'node',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
},
protected => 1,
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
start => {
type => 'integer',
minimum => 0,
optional => 1,
},
limit => {
type => 'integer',
minimum => 0,
optional => 1,
},
since => {
type=> 'string',
pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
description => "Display all log since this date-time string.",
optional => 1,
},
until => {
type=> 'string',
pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$',
description => "Display all log until this date-time string.",
optional => 1,
},
service => {
description => "Service ID",
type => 'string',
maxLength => 128,
optional => 1,
},
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
n => {
description=> "Line number",
type=> 'integer',
},
t => {
description=> "Line text",
type => 'string',
}
}
}
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $node = $param->{node};
my $service;
if ($param->{service}) {
my $service_aliases = {
'postfix' => 'postfix@-',
};
$service = $service_aliases->{$param->{service}} // $param->{service};
}
my ($count, $lines) = PVE::Tools::dump_journal($param->{start}, $param->{limit},
$param->{since}, $param->{until}, $service);
$rpcenv->set_result_attrib('total', $count);
return $lines;
}});
__PACKAGE__->register_method({
name => 'journal',
path => 'journal',
method => 'GET',
description => "Read Journal",
proxyto => 'node',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
},
protected => 1,
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
since => {
type=> 'integer',
minimum => 0,
description => "Display all log since this UNIX epoch. Conflicts with 'startcursor'.",
optional => 1,
},
until => {
type=> 'integer',
minimum => 0,
description => "Display all log until this UNIX epoch. Conflicts with 'endcursor'.",
optional => 1,
},
lastentries => {
description => "Limit to the last X lines. Conflicts with a range.",
type => 'integer',
minimum => 0,
optional => 1,
},
startcursor => {
description => "Start after the given Cursor. Conflicts with 'since'",
type => 'string',
optional => 1,
},
endcursor => {
description => "End before the given Cursor. Conflicts with 'until'",
type => 'string',
optional => 1,
},
},
},
returns => {
type => 'array',
items => {
type => "string",
}
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $cmd = ["/usr/bin/mini-journalreader"];
push @$cmd, '-n', $param->{lastentries} if $param->{lastentries};
push @$cmd, '-b', $param->{since} if $param->{since};
push @$cmd, '-e', $param->{until} if $param->{until};
push @$cmd, '-f', $param->{startcursor} if $param->{startcursor};
push @$cmd, '-t', $param->{endcursor} if $param->{endcursor};
my $lines = [];
my $parser = sub { push @$lines, shift };
PVE::Tools::run_command($cmd, outfunc => $parser);
return $lines;
}});
my $sslcert;
my $shell_cmd_map = {
'login' => [ '/bin/login', '-f', 'root' ],
'upgrade' => [ '/usr/bin/pveupgrade', '--shell' ],
'ceph_install' => [ '/usr/bin/pveceph', 'install' ],
};
sub get_shell_command {
my ($user, $shellcmd) = @_;
if ($user eq 'root@pam') {
if (defined($shellcmd) && exists($shell_cmd_map->{$shellcmd})) {
return $shell_cmd_map->{$shellcmd};
} else {
return [ '/bin/login', '-f', 'root' ];
}
} else {
return [ '/bin/login' ];
}
}
__PACKAGE__->register_method ({
name => 'vncshell',
path => 'vncshell',
method => 'POST',
protected => 1,
permissions => {
description => "Restricted to users on realm 'pam'",
check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
},
description => "Creates a VNC Shell proxy.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
upgrade => {
type => 'boolean',
description => "Deprecated, use the 'cmd' property instead! Run 'apt-get dist-upgrade' instead of normal shell.",
optional => 1,
default => 0,
},
cmd => {
type => 'string',
description => "Run specific command or default to login.",
enum => [keys %$shell_cmd_map],
optional => 1,
default => 'login',
},
websocket => {
optional => 1,
type => 'boolean',
description => "use websocket instead of standard vnc.",
},
width => {
optional => 1,
description => "sets the width of the console in pixels.",
type => 'integer',
minimum => 16,
maximum => 4096,
},
height => {
optional => 1,
description => "sets the height of the console in pixels.",
type => 'integer',
minimum => 16,
maximum => 2160,
},
},
},
returns => {
additionalProperties => 0,
properties => {
user => { type => 'string' },
ticket => { type => 'string' },
cert => { type => 'string' },
port => { type => 'integer' },
upid => { type => 'string' },
},
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
raise_perm_exc("realm != pam") if $realm ne 'pam';
raise_perm_exc('user != root@pam') if $param->{upgrade} && $user ne 'root@pam';
my $node = $param->{node};
my $authpath = "/nodes/$node";
my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath);
$sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
if !$sslcert;
my ($remip, $family);
if ($node ne PVE::INotify::nodename()) {
($remip, $family) = PVE::Cluster::remote_node_ip($node);
} else {
$family = PVE::Tools::get_host_address_family($node);
}
my $port = PVE::Tools::next_vnc_port($family);
# NOTE: vncterm VNC traffic is already TLS encrypted,
# so we select the fastest chipher here (or 'none'?)
my $remcmd = $remip ?
['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
# FIXME: remove with 6.0
if ($param->{upgrade}) {
$param->{cmd} = 'upgrade';
}
my $shcmd = get_shell_command($user, $param->{cmd});
my $timeout = 10;
my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
'-timeout', $timeout, '-authpath', $authpath,
'-perm', 'Sys.Console'];
if ($param->{width}) {
push @$cmd, '-width', $param->{width};
}
if ($param->{height}) {
push @$cmd, '-height', $param->{height};
}
if ($param->{websocket}) {
$ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
push @$cmd, '-notls', '-listen', 'localhost';
}
push @$cmd, '-c', @$remcmd, @$shcmd;
my $realcmd = sub {
my $upid = shift;
syslog ('info', "starting vnc proxy $upid\n");
my $cmdstr = join (' ', @$cmd);
syslog ('info', "launch command: $cmdstr");
eval {
foreach my $k (keys %ENV) {
next if $k eq 'PVE_VNC_TICKET';
next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME' || $k eq 'LANG' || $k eq 'LANGUAGE';
delete $ENV{$k};
}
$ENV{PWD} = '/';
PVE::Tools::run_command($cmd, errmsg => "vncterm failed", keeplocale => 1);
};
if (my $err = $@) {
syslog ('err', $err);
}
return;
};
my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
PVE::Tools::wait_for_vnc_port($port);
return {
user => $user,
ticket => $ticket,
port => $port,
upid => $upid,
cert => $sslcert,
};
}});
__PACKAGE__->register_method ({
name => 'termproxy',
path => 'termproxy',
method => 'POST',
protected => 1,
permissions => {
description => "Restricted to users on realm 'pam'",
check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
},
description => "Creates a VNC Shell proxy.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
upgrade => {
type => 'boolean',
description => "Deprecated, use the 'cmd' property instead! Run 'apt-get dist-upgrade' instead of normal shell.",
optional => 1,
default => 0,
},
cmd => {
type => 'string',
description => "Run specific command or default to login.",
enum => [keys %$shell_cmd_map],
optional => 1,
default => 'login',
},
},
},
returns => {
additionalProperties => 0,
properties => {
user => { type => 'string' },
ticket => { type => 'string' },
port => { type => 'integer' },
upid => { type => 'string' },
},
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
raise_perm_exc("realm != pam") if $realm ne 'pam';
my $node = $param->{node};
my $authpath = "/nodes/$node";
my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath);
my ($remip, $family);
if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
($remip, $family) = PVE::Cluster::remote_node_ip($node);
} else {
$family = PVE::Tools::get_host_address_family($node);
}
my $port = PVE::Tools::next_vnc_port($family);
my $remcmd = $remip ?
['/usr/bin/ssh', '-e', 'none', '-t', $remip , '--'] : [];
# FIXME: remove with 6.0
if ($param->{upgrade}) {
$param->{cmd} = 'upgrade';
}
my $shcmd = get_shell_command($user, $param->{cmd});
my $realcmd = sub {
my $upid = shift;
syslog ('info', "starting termproxy $upid\n");
my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
'--perm', 'Sys.Console', '--'];
push @$cmd, @$remcmd, @$shcmd;
PVE::Tools::run_command($cmd);
};
my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd);
PVE::Tools::wait_for_vnc_port($port);
return {
user => $user,
ticket => $ticket,
port => $port,
upid => $upid,
};
}});
__PACKAGE__->register_method({
name => 'vncwebsocket',
path => 'vncwebsocket',
method => 'GET',
permissions => {
description => "Restricted to users on realm 'pam'. You also need to pass a valid ticket (vncticket).",
check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
},
description => "Opens a weksocket for VNC traffic.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vncticket => {
description => "Ticket from previous call to vncproxy.",
type => 'string',
maxLength => 512,
},
port => {
description => "Port number returned by previous vncproxy call.",
type => 'integer',
minimum => 5900,
maximum => 5999,
},
},
},
returns => {
type => "object",
properties => {
port => { type => 'string' },
},
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user());
raise_perm_exc("realm != pam") if $realm ne 'pam';
my $authpath = "/nodes/$param->{node}";
PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $user, $authpath);
my $port = $param->{port};
return { port => $port };
}});
__PACKAGE__->register_method ({
name => 'spiceshell',
path => 'spiceshell',
method => 'POST',
protected => 1,
proxyto => 'node',
permissions => {
description => "Restricted to users on realm 'pam'",
check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]],
},
description => "Creates a SPICE shell.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
proxy => get_standard_option('spice-proxy', { optional => 1 }),
upgrade => {
type => 'boolean',
description => "Deprecated, use the 'cmd' property instead! Run 'apt-get dist-upgrade' instead of normal shell.",
optional => 1,
default => 0,
},
cmd => {
type => 'string',
description => "Run specific command or default to login.",
enum => [keys %$shell_cmd_map],
optional => 1,
default => 'login',
},
},
},
returns => get_standard_option('remote-viewer-config'),
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my ($user, undef, $realm) = PVE::AccessControl::verify_username($authuser);
raise_perm_exc("realm != pam") if $realm ne 'pam';
raise_perm_exc('user != root@pam') if $param->{upgrade} && $user ne 'root@pam';
my $node = $param->{node};
my $proxy = $param->{proxy};
my $authpath = "/nodes/$node";
my $permissions = 'Sys.Console';
# FIXME: remove with 6.0
if ($param->{upgrade}) {
$param->{cmd} = 'upgrade';
}
my $shcmd = get_shell_command($user, $param->{cmd});
my $title = "Shell on '$node'";
return PVE::API2Tools::run_spiceterm($authpath, $permissions, 0, $node, $proxy, $title, $shcmd);
}});
__PACKAGE__->register_method({
name => 'dns',
path => 'dns',
method => 'GET',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
},
description => "Read DNS settings.",
proxyto => 'node',
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => "object",
additionalProperties => 0,
properties => {
search => {
description => "Search domain for host-name lookup.",
type => 'string',
optional => 1,
},
dns1 => {
description => 'First name server IP address.',
type => 'string',
optional => 1,
},
dns2 => {
description => 'Second name server IP address.',
type => 'string',
optional => 1,
},
dns3 => {
description => 'Third name server IP address.',
type => 'string',
optional => 1,
},
},
},
code => sub {
my ($param) = @_;
my $res = PVE::INotify::read_file('resolvconf');
return $res;
}});
__PACKAGE__->register_method({
name => 'update_dns',
path => 'dns',
method => 'PUT',
description => "Write DNS settings.",
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
},
proxyto => 'node',
protected => 1,
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
search => {
description => "Search domain for host-name lookup.",
type => 'string',
},
dns1 => {
description => 'First name server IP address.',
type => 'string', format => 'ip',
optional => 1,
},
dns2 => {
description => 'Second name server IP address.',
type => 'string', format => 'ip',
optional => 1,
},
dns3 => {
description => 'Third name server IP address.',
type => 'string', format => 'ip',
optional => 1,
},
},
},
returns => { type => "null" },
code => sub {
my ($param) = @_;
PVE::INotify::update_file('resolvconf', $param);
return undef;
}});
__PACKAGE__->register_method({
name => 'time',
path => 'time',
method => 'GET',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
},
description => "Read server time and time zone settings.",
proxyto => 'node',
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => "object",
additionalProperties => 0,
properties => {
timezone => {
description => "Time zone",
type => 'string',
},
time => {
description => "Seconds since 1970-01-01 00:00:00 UTC.",
type => 'integer',
minimum => 1297163644,
renderer => 'timestamp',
},
localtime => {
description => "Seconds since 1970-01-01 00:00:00 (local time)",
type => 'integer',
minimum => 1297163644,
renderer => 'timestamp_gmt',
},
},
},
code => sub {
my ($param) = @_;
my $ctime = time();
my $ltime = timegm_nocheck(localtime($ctime));
my $res = {
timezone => PVE::INotify::read_file('timezone'),
time => $ctime,
localtime => $ltime,
};
return $res;
}});
__PACKAGE__->register_method({
name => 'set_timezone',
path => 'time',
method => 'PUT',
description => "Set time zone.",
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
},
proxyto => 'node',
protected => 1,
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
timezone => {
description => "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.",
type => 'string',
},
},
},
returns => { type => "null" },
code => sub {
my ($param) = @_;
PVE::INotify::write_file('timezone', $param->{timezone});
return undef;
}});
__PACKAGE__->register_method({
name => 'aplinfo',
path => 'aplinfo',
method => 'GET',
permissions => {
user => 'all',
},
description => "Get list of appliances.",
proxyto => 'node',
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {},
},
},
code => sub {
my ($param) = @_;
my $res = [];
my $list = PVE::APLInfo::load_data();
foreach my $template (keys %{$list->{all}}) {
my $pd = $list->{all}->{$template};
next if $pd->{'package'} eq 'pve-web-news';
push @$res, $pd;
}
return $res;
}});
__PACKAGE__->register_method({
name => 'apl_download',
path => 'aplinfo',
method => 'POST',
permissions => {
check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
},
description => "Download appliance templates.",
proxyto => 'node',
protected => 1,
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', {
description => "The storage where the template will be stored",
completion => \&PVE::Storage::complete_storage_enabled,
}),
template => { type => 'string',
description => "The template wich will downloaded",
maxLength => 255,
completion => \&complete_templet_repo,
},
},
},
returns => { type => "string" },
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
my $node = $param->{node};
my $list = PVE::APLInfo::load_data();
my $template = $param->{template};
my $pd = $list->{all}->{$template};
raise_param_exc({ template => "no such template"}) if !$pd;
my $cfg = PVE::Storage::config();
my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node);
die "unknown template type '$pd->{type}'\n"
if !($pd->{type} eq 'openvz' || $pd->{type} eq 'lxc');
die "storage '$param->{storage}' does not support templates\n"
if !$scfg->{content}->{vztmpl};
my $src = $pd->{location};
my $tmpldir = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
my $dest = "$tmpldir/$template";
my $tmpdest = "$tmpldir/${template}.tmp.$$";
my $worker = sub {
my $upid = shift;
print "starting template download from: $src\n";
print "target file: $dest\n";
my $check_hash = sub {
my ($template_info, $filename, $noerr) = @_;
my $digest;
my $expected;
eval {
open(my $fh, '<', $filename) or die "Can't open '$filename': $!";
binmode($fh);
if (defined($template_info->{sha512sum})) {
$expected = $template_info->{sha512sum};
$digest = Digest::SHA->new(512)->addfile($fh)->hexdigest;
} elsif (defined($template_info->{md5sum})) {
#fallback to MD5
$expected = $template_info->{md5sum};
$digest = Digest::MD5->new->addfile($fh)->hexdigest;
} else {
die "no expected checksum defined";
}
close($fh);
};
die "checking hash failed - $@\n" if $@ && !$noerr;
return ($digest, $digest ? lc($digest) eq lc($expected) : 0);
};
eval {
if (-f $dest) {
my ($hash, $correct) = &$check_hash($pd, $dest, 1);
if ($hash && $correct) {
print "file already exists $hash - no need to download\n";
return;
}
}
local %ENV;
my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
if ($dccfg->{http_proxy}) {
$ENV{http_proxy} = $dccfg->{http_proxy};
}
my @cmd = ('/usr/bin/wget', '--progress=dot:mega', '-O', $tmpdest, $src);
if (system (@cmd) != 0) {
die "download failed - $!\n";
}
my ($hash, $correct) = &$check_hash($pd, $tmpdest);
die "could not calculate checksum\n" if !$hash;
if (!$correct) {
my $expected = $pd->{sha512sum} // $pd->{md5sum};
die "wrong checksum: $hash != $expected\n";
}
if (!rename($tmpdest, $dest)) {
die "unable to save file - $!\n";
}
};
my $err = $@;
unlink $tmpdest;
if ($err) {
print "\n";
die $err if $err;
}
print "download finished\n";
};
return $rpcenv->fork_worker('download', undef, $user, $worker);
}});
__PACKAGE__->register_method({
name => 'report',
path => 'report',
method => 'GET',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
},
protected => 1,
description => "Gather various systems information about a node",
proxyto => 'node',
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => 'string',
},
code => sub {
return PVE::Report::generate();
}});
# returns a list of VMIDs, those can be filtered by
# * current parent node
# * vmid whitelist
# * guest is a template (default: skip)
# * guest is HA manged (default: skip)
my $get_filtered_vmlist = sub {
my ($nodename, $vmfilter, $templates, $ha_managed) = @_;
my $vmlist = PVE::Cluster::get_vmlist();
my $vms_allowed = {};
if (defined($vmfilter)) {
foreach my $vmid (PVE::Tools::split_list($vmfilter)) {
$vms_allowed->{$vmid} = 1;
}
}
my $res = {};
foreach my $vmid (keys %{$vmlist->{ids}}) {
next if %$vms_allowed && !$vms_allowed->{$vmid};
my $d = $vmlist->{ids}->{$vmid};
next if $nodename && $d->{node} ne $nodename;
eval {
my $class;
if ($d->{type} eq 'lxc') {
$class = 'PVE::LXC::Config';
} elsif ($d->{type} eq 'qemu') {
$class = 'PVE::QemuConfig';
} else {
die "unknown VM type '$d->{type}'\n";
}
my $conf = $class->load_config($vmid);
return if !$templates && $class->is_template($conf);
return if !$ha_managed && PVE::HA::Config::vm_is_ha_managed($vmid);
$res->{$vmid}->{conf} = $conf;
$res->{$vmid}->{type} = $d->{type};
$res->{$vmid}->{class} = $class;
};
warn $@ if $@;
}
return $res;
};
# return all VMs which should get started/stopped on power up/down
my $get_start_stop_list = sub {
my ($nodename, $autostart, $vmfilter) = @_;
# do not skip HA vms on force or if a specific VMID set is wanted
my $include_ha_managed = defined($vmfilter) ? 1 : 0;
my $vmlist = &$get_filtered_vmlist($nodename, $vmfilter, undef, $include_ha_managed);
my $resList = {};
foreach my $vmid (keys %$vmlist) {
my $conf = $vmlist->{$vmid}->{conf};
next if $autostart && !$conf->{onboot};
my $startup = {};
if ($conf->{startup}) {
$startup = PVE::JSONSchema::pve_parse_startup_order($conf->{startup});
}
$startup->{order} = LONG_MAX if !defined($startup->{order});
$resList->{$startup->{order}}->{$vmid} = $startup;
$resList->{$startup->{order}}->{$vmid}->{type} = $vmlist->{$vmid}->{type};
}
return $resList;
};
my $remove_locks_on_startup = sub {
my ($nodename) = @_;
my $vmlist = &$get_filtered_vmlist($nodename, undef, undef, 1);
foreach my $vmid (keys %$vmlist) {
my $conf = $vmlist->{$vmid}->{conf};
my $class = $vmlist->{$vmid}->{class};
eval {
if ($class->has_lock($conf, 'backup')) {
$class->remove_lock($vmid, 'backup');
my $msg = "removed left over backup lock from '$vmid'!";
warn "$msg\n"; # prints to task log
syslog('warning', $msg);
}
}; warn $@ if $@;
}
};
__PACKAGE__->register_method ({
name => 'startall',
path => 'startall',
method => 'POST',
protected => 1,
permissions => {
check => ['perm', '/', [ 'VM.PowerMgmt' ]],
},
proxyto => 'node',
description => "Start all VMs and containers (when onboot=1).",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
force => {
optional => 1,
type => 'boolean',
description => "force if onboot=0.",
},
vms => {
description => "Only consider Guests with these IDs.",
type => 'string', format => 'pve-vmid-list',
optional => 1,
},
},
},
returns => {
type => 'string',
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $nodename = $param->{node};
$nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
my $force = $param->{force};
my $code = sub {
$rpcenv->{type} = 'priv'; # to start tasks in background
if (!PVE::Cluster::check_cfs_quorum(1)) {
print "waiting for quorum ...\n";
do {
sleep(1);
} while (!PVE::Cluster::check_cfs_quorum(1));
print "got quorum\n";
}
eval { # remove backup locks, but avoid running into a scheduled backup job
PVE::Tools::lock_file('/var/run/vzdump.lock', 10, $remove_locks_on_startup, $nodename);
}; warn $@ if $@;
my $autostart = $force ? undef : 1;
my $startList = &$get_start_stop_list($nodename, $autostart, $param->{vms});
# Note: use numeric sorting with <=>
foreach my $order (sort {$a <=> $b} keys %$startList) {
my $vmlist = $startList->{$order};
foreach my $vmid (sort {$a <=> $b} keys %$vmlist) {
my $d = $vmlist->{$vmid};
PVE::Cluster::check_cfs_quorum(); # abort when we loose quorum
eval {
my $default_delay = 0;
my $upid;
my $typeText = '';
if ($d->{type} eq 'lxc') {
$typeText = 'CT';
return if PVE::LXC::check_running($vmid);
print STDERR "Starting CT $vmid\n";
$upid = PVE::API2::LXC::Status->vm_start({node => $nodename, vmid => $vmid });
} elsif ($d->{type} eq 'qemu') {
$typeText = 'VM';
$default_delay = 3; # to reduce load
return if PVE::QemuServer::check_running($vmid, 1);
print STDERR "Starting VM $vmid\n";
$upid = PVE::API2::Qemu->vm_start({node => $nodename, vmid => $vmid });
} else {
die "unknown VM type '$d->{type}'\n";
}
my $res = PVE::Tools::upid_decode($upid);
while (PVE::ProcFSTools::check_process_running($res->{pid})) {
sleep(1);
}
my $status = PVE::Tools::upid_read_status($upid);
if ($status eq 'OK') {
# use default delay to reduce load
my $delay = defined($d->{up}) ? int($d->{up}) : $default_delay;
if ($delay > 0) {
print STDERR "Waiting for $delay seconds (startup delay)\n" if $d->{up};
for (my $i = 0; $i < $delay; $i++) {
sleep(1);
}
}
} else {
print STDERR "Starting $typeText $vmid failed: $status\n";
}
};
warn $@ if $@;
}
}
return;
};
return $rpcenv->fork_worker('startall', undef, $authuser, $code);
}});
my $create_stop_worker = sub {
my ($nodename, $type, $vmid, $down_timeout) = @_;
my $upid;
if ($type eq 'lxc') {
return if !PVE::LXC::check_running($vmid);
my $timeout = defined($down_timeout) ? int($down_timeout) : 60;
print STDERR "Stopping CT $vmid (timeout = $timeout seconds)\n";
$upid = PVE::API2::LXC::Status->vm_shutdown({node => $nodename, vmid => $vmid,
timeout => $timeout, forceStop => 1 });
} elsif ($type eq 'qemu') {
return if !PVE::QemuServer::check_running($vmid, 1);
my $timeout = defined($down_timeout) ? int($down_timeout) : 60*3;
print STDERR "Stopping VM $vmid (timeout = $timeout seconds)\n";
$upid = PVE::API2::Qemu->vm_shutdown({node => $nodename, vmid => $vmid,
timeout => $timeout, forceStop => 1 });
} else {
die "unknown VM type '$type'\n";
}
return $upid;
};
__PACKAGE__->register_method ({
name => 'stopall',
path => 'stopall',
method => 'POST',
protected => 1,
permissions => {
check => ['perm', '/', [ 'VM.PowerMgmt' ]],
},
proxyto => 'node',
description => "Stop all VMs and Containers.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
vms => {
description => "Only consider Guests with these IDs.",
type => 'string', format => 'pve-vmid-list',
optional => 1,
},
},
},
returns => {
type => 'string',
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $nodename = $param->{node};
$nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
my $code = sub {
$rpcenv->{type} = 'priv'; # to start tasks in background
my $stopList = &$get_start_stop_list($nodename, undef, $param->{vms});
my $cpuinfo = PVE::ProcFSTools::read_cpuinfo();
my $datacenterconfig = cfs_read_file('datacenter.cfg');
# if not set by user spawn max cpu count number of workers
my $maxWorkers = $datacenterconfig->{max_workers} || $cpuinfo->{cpus};
foreach my $order (sort {$b <=> $a} keys %$stopList) {
my $vmlist = $stopList->{$order};
my $workers = {};
my $finish_worker = sub {
my $pid = shift;
my $d = $workers->{$pid};
return if !$d;
delete $workers->{$pid};
syslog('info', "end task $d->{upid}");
};
foreach my $vmid (sort {$b <=> $a} keys %$vmlist) {
my $d = $vmlist->{$vmid};
my $upid;
eval { $upid = &$create_stop_worker($nodename, $d->{type}, $vmid, $d->{down}); };
warn $@ if $@;
next if !$upid;
my $res = PVE::Tools::upid_decode($upid, 1);
next if !$res;
my $pid = $res->{pid};
$workers->{$pid} = { type => $d->{type}, upid => $upid, vmid => $vmid };
while (scalar(keys %$workers) >= $maxWorkers) {
foreach my $p (keys %$workers) {
if (!PVE::ProcFSTools::check_process_running($p)) {
&$finish_worker($p);
}
}
sleep(1);
}
}
while (scalar(keys %$workers)) {
foreach my $p (keys %$workers) {
if (!PVE::ProcFSTools::check_process_running($p)) {
&$finish_worker($p);
}
}
sleep(1);
}
}
syslog('info', "all VMs and CTs stopped");
return;
};
return $rpcenv->fork_worker('stopall', undef, $authuser, $code);
}});
my $create_migrate_worker = sub {
my ($nodename, $type, $vmid, $target) = @_;
my $upid;
if ($type eq 'lxc') {
my $online = PVE::LXC::check_running($vmid) ? 1 : 0;
print STDERR "Migrating CT $vmid\n";
$upid = PVE::API2::LXC->migrate_vm({node => $nodename, vmid => $vmid, target => $target,
restart => $online });
} elsif ($type eq 'qemu') {
my $online = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0;
print STDERR "Migrating VM $vmid\n";
$upid = PVE::API2::Qemu->migrate_vm({node => $nodename, vmid => $vmid, target => $target,
online => $online });
} else {
die "unknown VM type '$type'\n";
}
my $res = PVE::Tools::upid_decode($upid);
return $res->{pid};
};
__PACKAGE__->register_method ({
name => 'migrateall',
path => 'migrateall',
method => 'POST',
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'VM.Migrate' ]],
},
description => "Migrate all VMs and Containers.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
target => get_standard_option('pve-node', { description => "Target node." }),
maxworkers => {
description => "Maximal number of parallel migration job." .
" If not set use 'max_workers' from datacenter.cfg," .
" one of both must be set!",
optional => 1,
type => 'integer',
minimum => 1
},
vms => {
description => "Only consider Guests with these IDs.",
type => 'string', format => 'pve-vmid-list',
optional => 1,
},
},
},
returns => {
type => 'string',
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $nodename = $param->{node};
$nodename = PVE::INotify::nodename() if $nodename eq 'localhost';
my $target = $param->{target};
raise_param_exc({ target => "target is local node."}) if $target eq $nodename;
PVE::Cluster::check_cfs_quorum();
PVE::Cluster::check_node_exists($target);
my $datacenterconfig = cfs_read_file('datacenter.cfg');
# prefer parameter over datacenter cfg settings
my $maxWorkers = $param->{maxworkers} || $datacenterconfig->{max_workers} ||
die "either 'maxworkers' parameter or max_workers in datacenter.cfg must be set!\n";
my $code = sub {
$rpcenv->{type} = 'priv'; # to start tasks in background
my $vmlist = &$get_filtered_vmlist($nodename, $param->{vms}, 1, 1);
my $workers = {};
foreach my $vmid (sort keys %$vmlist) {
my $d = $vmlist->{$vmid};
my $pid;
eval { $pid = &$create_migrate_worker($nodename, $d->{type}, $vmid, $target); };
warn $@ if $@;
next if !$pid;
$workers->{$pid} = 1;
while (scalar(keys %$workers) >= $maxWorkers) {
foreach my $p (keys %$workers) {
if (!PVE::ProcFSTools::check_process_running($p)) {
delete $workers->{$p};
}
}
sleep(1);
}
}
while (scalar(keys %$workers)) {
foreach my $p (keys %$workers) {
if (!PVE::ProcFSTools::check_process_running($p)) {
delete $workers->{$p};
}
}
sleep(1);
}
return;
};
return $rpcenv->fork_worker('migrateall', undef, $authuser, $code);
}});
__PACKAGE__->register_method ({
name => 'get_etc_hosts',
path => 'hosts',
method => 'GET',
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Audit' ]],
},
description => "Get the content of /etc/hosts.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => {
type => 'object',
properties => {
digest => get_standard_option('pve-config-digest'),
data => {
type => 'string',
description => 'The content of /etc/hosts.'
},
},
},
code => sub {
my ($param) = @_;
return PVE::INotify::read_file('etchosts');
}});
__PACKAGE__->register_method ({
name => 'write_etc_hosts',
path => 'hosts',
method => 'POST',
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
},
description => "Write /etc/hosts.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
digest => get_standard_option('pve-config-digest'),
data => {
type => 'string',
description => 'The target content of /etc/hosts.'
},
},
},
returns => {
type => 'null',
},
code => sub {
my ($param) = @_;
PVE::Tools::lock_file('/var/lock/pve-etchosts.lck', undef, sub{
if ($param->{digest}) {
my $hosts = PVE::INotify::read_file('etchosts');
PVE::Tools::assert_if_modified($hosts->{digest}, $param->{digest});
}
PVE::INotify::write_file('etchosts', $param->{data});
});
die $@ if $@;
return undef;
}});
# bash completion helper
sub complete_templet_repo {
my ($cmdname, $pname, $cvalue) = @_;
my $repo = PVE::APLInfo::load_data();
my $res = [];
foreach my $templ (keys %{$repo->{all}}) {
next if $templ !~ m/^$cvalue/;
push @$res, $templ;
}
return $res;
}
package PVE::API2::Nodes;
use strict;
use warnings;
use PVE::SafeSyslog;
use PVE::Cluster;
use PVE::RESTHandler;
use PVE::RPCEnvironment;
use PVE::API2Tools;
use PVE::JSONSchema qw(get_standard_option);
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
subclass => "PVE::API2::Nodes::Nodeinfo",
path => '{node}',
});
__PACKAGE__->register_method ({
name => 'index',
path => '',
method => 'GET',
permissions => { user => 'all' },
description => "Cluster node index.",
parameters => {
additionalProperties => 0,
properties => {},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
node => get_standard_option('pve-node'),
status => {
description => "Node status.",
type => 'string',
enum => ['unknown', 'online', 'offline'],
},
cpu => {
description => "CPU utilization.",
type => 'number',
optional => 1,
renderer => 'fraction_as_percentage',
},
maxcpu => {
description => "Number of available CPUs.",
type => 'integer',
optional => 1,
},
mem => {
description => "Used memory in bytes.",
type => 'string',
optional => 1,
renderer => 'bytes',
},
maxmem => {
description => "Number of available memory in bytes.",
type => 'integer',
optional => 1,
renderer => 'bytes',
},
level => {
description => "Support level.",
type => 'string',
optional => 1,
},
uptime => {
description => "Node uptime in seconds.",
type => 'integer',
optional => 1,
renderer => 'duration',
},
ssl_fingerprint => {
description => "The SSL fingerprint for the node certificate.",
type => 'string',
optional => 1,
},
},
},
links => [ { rel => 'child', href => "{node}" } ],
},
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
my $clinfo = PVE::Cluster::get_clinfo();
my $res = [];
my $nodelist = PVE::Cluster::get_nodelist();
my $members = PVE::Cluster::get_members();
my $rrd = PVE::Cluster::rrd_dump();
foreach my $node (@$nodelist) {
my $can_audit = $rpcenv->check($authuser, "/nodes/$node", [ 'Sys.Audit' ], 1);
my $entry = PVE::API2Tools::extract_node_stats($node, $members, $rrd, !$can_audit);
$entry->{ssl_fingerprint} = PVE::Cluster::get_node_fingerprint($node);
push @$res, $entry;
}
return $res;
}});
1;