auto-format code using perltidy with Proxmox style guide
using the new top-level `make tidy` target, which calls perltidy via our wrapper to enforce the desired style as closely as possible. Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
parent
a750c44c42
commit
138cae897a
88 changed files with 19180 additions and 17920 deletions
130
PVE/API2.pm
130
PVE/API2.pm
|
|
@ -18,119 +18,121 @@ use PVE::API2::Pool;
|
|||
use PVE::API2::AccessControl;
|
||||
use PVE::API2::Storage::Config;
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::Cluster",
|
||||
path => 'cluster',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::Nodes",
|
||||
path => 'nodes',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::Storage::Config",
|
||||
path => 'storage',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::AccessControl",
|
||||
path => 'access',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::Pool",
|
||||
path => 'pools',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
permissions => { user => 'all' },
|
||||
description => "Directory index.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
subdir => { type => 'string' },
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{subdir}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
subdir => { type => 'string' },
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{subdir}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $res = [ { subdir => 'version' } ];
|
||||
my $res = [{ subdir => 'version' }];
|
||||
|
||||
my $ma = PVE::API2->method_attributes();
|
||||
my $ma = PVE::API2->method_attributes();
|
||||
|
||||
foreach my $info (@$ma) {
|
||||
next if !$info->{subclass};
|
||||
foreach my $info (@$ma) {
|
||||
next if !$info->{subclass};
|
||||
|
||||
my $subpath = $info->{match_re}->[0];
|
||||
my $subpath = $info->{match_re}->[0];
|
||||
|
||||
push @$res, { subdir => $subpath };
|
||||
}
|
||||
push @$res, { subdir => $subpath };
|
||||
}
|
||||
|
||||
return $res;
|
||||
}});
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'version',
|
||||
path => 'version',
|
||||
method => 'GET',
|
||||
permissions => { user => 'all' },
|
||||
description => "API version details, including some parts of the global datacenter config.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => "object",
|
||||
properties => {
|
||||
version => {
|
||||
type => 'string',
|
||||
description => 'The full pve-manager package version of this node.',
|
||||
},
|
||||
release => {
|
||||
type => 'string',
|
||||
description => 'The current Proxmox VE point release in `x.y` format.',
|
||||
},
|
||||
repoid => {
|
||||
type => 'string',
|
||||
# length 8 is old (< 8.0) short-id, 16 is new short id, 40 is sha1 and 64 is sha256
|
||||
pattern => '[0-9a-fA-F]{8,64}',
|
||||
description => 'The short git revision from which this version was build.',
|
||||
},
|
||||
console => {
|
||||
type => 'string',
|
||||
enum => ['applet', 'vv', 'html5', 'xtermjs'],
|
||||
optional => 1,
|
||||
description => 'The default console viewer to use.',
|
||||
},
|
||||
},
|
||||
type => "object",
|
||||
properties => {
|
||||
version => {
|
||||
type => 'string',
|
||||
description => 'The full pve-manager package version of this node.',
|
||||
},
|
||||
release => {
|
||||
type => 'string',
|
||||
description => 'The current Proxmox VE point release in `x.y` format.',
|
||||
},
|
||||
repoid => {
|
||||
type => 'string',
|
||||
# length 8 is old (< 8.0) short-id, 16 is new short id, 40 is sha1 and 64 is sha256
|
||||
pattern => '[0-9a-fA-F]{8,64}',
|
||||
description => 'The short git revision from which this version was build.',
|
||||
},
|
||||
console => {
|
||||
type => 'string',
|
||||
enum => ['applet', 'vv', 'html5', 'xtermjs'],
|
||||
optional => 1,
|
||||
description => 'The default console viewer to use.',
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $res = {};
|
||||
my $res = {};
|
||||
|
||||
# TODO remove with next major release
|
||||
my $datacenter_confg = eval { PVE::Cluster::cfs_read_file('datacenter.cfg') } // {};
|
||||
for my $k (qw(console)) {
|
||||
$res->{$k} = $datacenter_confg->{$k} if exists $datacenter_confg->{$k};
|
||||
}
|
||||
# TODO remove with next major release
|
||||
my $datacenter_confg = eval { PVE::Cluster::cfs_read_file('datacenter.cfg') } // {};
|
||||
for my $k (qw(console)) {
|
||||
$res->{$k} = $datacenter_confg->{$k} if exists $datacenter_confg->{$k};
|
||||
}
|
||||
|
||||
my $version_info = PVE::pvecfg::version_info();
|
||||
# force set all version keys independent of their definedness
|
||||
$res->{$_} = $version_info->{$_} for qw(version release repoid);
|
||||
my $version_info = PVE::pvecfg::version_info();
|
||||
# force set all version keys independent of their definedness
|
||||
$res->{$_} = $version_info->{$_} for qw(version release repoid);
|
||||
|
||||
return $res;
|
||||
}});
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
1;
|
||||
|
|
|
|||
479
PVE/API2/ACME.pm
479
PVE/API2/ACME.pm
|
|
@ -17,33 +17,34 @@ use base qw(PVE::RESTHandler);
|
|||
|
||||
my $acme_account_dir = PVE::CertHelpers::acme_account_dir();
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
permissions => { user => 'all' },
|
||||
description => "ACME index.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
return [
|
||||
{ name => 'certificate' },
|
||||
];
|
||||
}});
|
||||
return [
|
||||
{ name => 'certificate' },
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
my $order_certificate = sub {
|
||||
my ($acme, $acme_node_config) = @_;
|
||||
|
|
@ -51,62 +52,62 @@ my $order_certificate = sub {
|
|||
my $plugins = PVE::API2::ACMEPlugin::load_config();
|
||||
|
||||
print "Placing ACME order\n";
|
||||
my ($order_url, $order) = $acme->new_order([ keys %{$acme_node_config->{domains}} ]);
|
||||
my ($order_url, $order) = $acme->new_order([keys %{ $acme_node_config->{domains} }]);
|
||||
print "Order URL: $order_url\n";
|
||||
for my $auth_url (@{$order->{authorizations}}) {
|
||||
print "\nGetting authorization details from '$auth_url'\n";
|
||||
my $auth = $acme->get_authorization($auth_url);
|
||||
for my $auth_url (@{ $order->{authorizations} }) {
|
||||
print "\nGetting authorization details from '$auth_url'\n";
|
||||
my $auth = $acme->get_authorization($auth_url);
|
||||
|
||||
# force lower case, like get_acme_conf does
|
||||
my $domain = lc($auth->{identifier}->{value});
|
||||
if ($auth->{status} eq 'valid') {
|
||||
print "$domain is already validated!\n";
|
||||
} else {
|
||||
print "The validation for $domain is pending!\n";
|
||||
# force lower case, like get_acme_conf does
|
||||
my $domain = lc($auth->{identifier}->{value});
|
||||
if ($auth->{status} eq 'valid') {
|
||||
print "$domain is already validated!\n";
|
||||
} else {
|
||||
print "The validation for $domain is pending!\n";
|
||||
|
||||
my $domain_config = $acme_node_config->{domains}->{$domain};
|
||||
die "no config for domain '$domain'\n" if !$domain_config;
|
||||
my $domain_config = $acme_node_config->{domains}->{$domain};
|
||||
die "no config for domain '$domain'\n" if !$domain_config;
|
||||
|
||||
my $plugin_id = $domain_config->{plugin};
|
||||
my $plugin_id = $domain_config->{plugin};
|
||||
|
||||
my $plugin_cfg = $plugins->{ids}->{$plugin_id};
|
||||
die "plugin '$plugin_id' for domain '$domain' not found!\n"
|
||||
if !$plugin_cfg;
|
||||
my $plugin_cfg = $plugins->{ids}->{$plugin_id};
|
||||
die "plugin '$plugin_id' for domain '$domain' not found!\n"
|
||||
if !$plugin_cfg;
|
||||
|
||||
my $data = {
|
||||
plugin => $plugin_cfg,
|
||||
alias => $domain_config->{alias},
|
||||
};
|
||||
my $data = {
|
||||
plugin => $plugin_cfg,
|
||||
alias => $domain_config->{alias},
|
||||
};
|
||||
|
||||
my $plugin = PVE::ACME::Challenge->lookup($plugin_cfg->{type});
|
||||
$plugin->setup($acme, $auth, $data);
|
||||
my $plugin = PVE::ACME::Challenge->lookup($plugin_cfg->{type});
|
||||
$plugin->setup($acme, $auth, $data);
|
||||
|
||||
print "Triggering validation\n";
|
||||
eval {
|
||||
die "no validation URL returned by plugin '$plugin_id' for domain '$domain'\n"
|
||||
if !defined($data->{url});
|
||||
print "Triggering validation\n";
|
||||
eval {
|
||||
die "no validation URL returned by plugin '$plugin_id' for domain '$domain'\n"
|
||||
if !defined($data->{url});
|
||||
|
||||
$acme->request_challenge_validation($data->{url});
|
||||
print "Sleeping for 5 seconds\n";
|
||||
sleep 5;
|
||||
while (1) {
|
||||
$auth = $acme->get_authorization($auth_url);
|
||||
if ($auth->{status} eq 'pending') {
|
||||
print "Status is still 'pending', trying again in 10 seconds\n";
|
||||
sleep 10;
|
||||
next;
|
||||
} elsif ($auth->{status} eq 'valid') {
|
||||
print "Status is 'valid', domain '$domain' OK!\n";
|
||||
last;
|
||||
}
|
||||
die "validating challenge '$auth_url' failed - status: $auth->{status}\n";
|
||||
}
|
||||
};
|
||||
my $err = $@;
|
||||
eval { $plugin->teardown($acme, $auth, $data) };
|
||||
warn "$@\n" if $@;
|
||||
die $err if $err;
|
||||
}
|
||||
$acme->request_challenge_validation($data->{url});
|
||||
print "Sleeping for 5 seconds\n";
|
||||
sleep 5;
|
||||
while (1) {
|
||||
$auth = $acme->get_authorization($auth_url);
|
||||
if ($auth->{status} eq 'pending') {
|
||||
print "Status is still 'pending', trying again in 10 seconds\n";
|
||||
sleep 10;
|
||||
next;
|
||||
} elsif ($auth->{status} eq 'valid') {
|
||||
print "Status is 'valid', domain '$domain' OK!\n";
|
||||
last;
|
||||
}
|
||||
die "validating challenge '$auth_url' failed - status: $auth->{status}\n";
|
||||
}
|
||||
};
|
||||
my $err = $@;
|
||||
eval { $plugin->teardown($acme, $auth, $data) };
|
||||
warn "$@\n" if $@;
|
||||
die $err if $err;
|
||||
}
|
||||
}
|
||||
print "\nAll domains validated!\n";
|
||||
print "\nCreating CSR\n";
|
||||
|
|
@ -115,39 +116,37 @@ my $order_certificate = sub {
|
|||
my $finalize_error_cnt = 0;
|
||||
print "Checking order status\n";
|
||||
while (1) {
|
||||
$order = $acme->get_order($order_url);
|
||||
if ($order->{status} eq 'pending') {
|
||||
print "still pending, trying to finalize order\n";
|
||||
# FIXME
|
||||
# to be compatible with and without the order ready state we try to
|
||||
# finalize even at the 'pending' state and give up after 5
|
||||
# unsuccessful tries this can be removed when the letsencrypt api
|
||||
# definitely has implemented the 'ready' state
|
||||
eval {
|
||||
$acme->finalize_order($order, PVE::Certificate::pem_to_der($csr));
|
||||
};
|
||||
if (my $err = $@) {
|
||||
die $err if $finalize_error_cnt >= 5;
|
||||
$order = $acme->get_order($order_url);
|
||||
if ($order->{status} eq 'pending') {
|
||||
print "still pending, trying to finalize order\n";
|
||||
# FIXME
|
||||
# to be compatible with and without the order ready state we try to
|
||||
# finalize even at the 'pending' state and give up after 5
|
||||
# unsuccessful tries this can be removed when the letsencrypt api
|
||||
# definitely has implemented the 'ready' state
|
||||
eval { $acme->finalize_order($order, PVE::Certificate::pem_to_der($csr)); };
|
||||
if (my $err = $@) {
|
||||
die $err if $finalize_error_cnt >= 5;
|
||||
|
||||
$finalize_error_cnt++;
|
||||
warn $err;
|
||||
}
|
||||
sleep 5;
|
||||
next;
|
||||
} elsif ($order->{status} eq 'ready') {
|
||||
print "Order is ready, finalizing order\n";
|
||||
$acme->finalize_order($order, PVE::Certificate::pem_to_der($csr));
|
||||
sleep 5;
|
||||
next;
|
||||
} elsif ($order->{status} eq 'processing') {
|
||||
print "still processing, trying again in 30 seconds\n";
|
||||
sleep 30;
|
||||
next;
|
||||
} elsif ($order->{status} eq 'valid') {
|
||||
print "valid!\n";
|
||||
last;
|
||||
}
|
||||
die "order status: $order->{status}\n";
|
||||
$finalize_error_cnt++;
|
||||
warn $err;
|
||||
}
|
||||
sleep 5;
|
||||
next;
|
||||
} elsif ($order->{status} eq 'ready') {
|
||||
print "Order is ready, finalizing order\n";
|
||||
$acme->finalize_order($order, PVE::Certificate::pem_to_der($csr));
|
||||
sleep 5;
|
||||
next;
|
||||
} elsif ($order->{status} eq 'processing') {
|
||||
print "still processing, trying again in 30 seconds\n";
|
||||
sleep 30;
|
||||
next;
|
||||
} elsif ($order->{status} eq 'valid') {
|
||||
print "valid!\n";
|
||||
last;
|
||||
}
|
||||
die "order status: $order->{status}\n";
|
||||
}
|
||||
|
||||
print "\nDownloading certificate\n";
|
||||
|
|
@ -156,227 +155,235 @@ my $order_certificate = sub {
|
|||
return ($cert, $key);
|
||||
};
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'new_certificate',
|
||||
path => 'certificate',
|
||||
method => 'POST',
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
|
||||
},
|
||||
description => "Order a new certificate from ACME-compatible CA.",
|
||||
protected => 1,
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
force => {
|
||||
type => 'boolean',
|
||||
description => 'Overwrite existing custom certificate.',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
force => {
|
||||
type => 'boolean',
|
||||
description => 'Overwrite existing custom certificate.',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'string',
|
||||
type => 'string',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $node = extract_param($param, 'node');
|
||||
my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node);
|
||||
my $node = extract_param($param, 'node');
|
||||
my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node);
|
||||
|
||||
raise_param_exc({'force' => "Custom certificate exists but 'force' is not set."})
|
||||
if !$param->{force} && -e "${cert_prefix}.pem";
|
||||
raise_param_exc({ 'force' => "Custom certificate exists but 'force' is not set." })
|
||||
if !$param->{force} && -e "${cert_prefix}.pem";
|
||||
|
||||
my $node_config = PVE::NodeConfig::load_config($node);
|
||||
my $acme_node_config = PVE::NodeConfig::get_acme_conf($node_config);
|
||||
raise("ACME domain list in node configuration is missing!", 400)
|
||||
if !$acme_node_config || !%{$acme_node_config->{domains}};
|
||||
my $node_config = PVE::NodeConfig::load_config($node);
|
||||
my $acme_node_config = PVE::NodeConfig::get_acme_conf($node_config);
|
||||
raise("ACME domain list in node configuration is missing!", 400)
|
||||
if !$acme_node_config || !%{ $acme_node_config->{domains} };
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $realcmd = sub {
|
||||
STDOUT->autoflush(1);
|
||||
my $account = $acme_node_config->{account};
|
||||
my $account_file = "${acme_account_dir}/${account}";
|
||||
die "ACME account config file '$account' does not exist.\n"
|
||||
if ! -e $account_file;
|
||||
my $realcmd = sub {
|
||||
STDOUT->autoflush(1);
|
||||
my $account = $acme_node_config->{account};
|
||||
my $account_file = "${acme_account_dir}/${account}";
|
||||
die "ACME account config file '$account' does not exist.\n"
|
||||
if !-e $account_file;
|
||||
|
||||
my $acme = PVE::ACME->new($account_file);
|
||||
my $acme = PVE::ACME->new($account_file);
|
||||
|
||||
print "Loading ACME account details\n";
|
||||
$acme->load();
|
||||
print "Loading ACME account details\n";
|
||||
$acme->load();
|
||||
|
||||
my ($cert, $key) = $order_certificate->($acme, $acme_node_config);
|
||||
my ($cert, $key) = $order_certificate->($acme, $acme_node_config);
|
||||
|
||||
my $code = sub {
|
||||
print "Setting pveproxy certificate and key\n";
|
||||
PVE::CertHelpers::set_cert_files($cert, $key, $cert_prefix, $param->{force});
|
||||
my $code = sub {
|
||||
print "Setting pveproxy certificate and key\n";
|
||||
PVE::CertHelpers::set_cert_files($cert, $key, $cert_prefix, $param->{force});
|
||||
|
||||
print "Restarting pveproxy\n";
|
||||
PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']);
|
||||
};
|
||||
PVE::CertHelpers::cert_lock(10, $code);
|
||||
die "$@\n" if $@;
|
||||
};
|
||||
print "Restarting pveproxy\n";
|
||||
PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']);
|
||||
};
|
||||
PVE::CertHelpers::cert_lock(10, $code);
|
||||
die "$@\n" if $@;
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker("acmenewcert", undef, $authuser, $realcmd);
|
||||
}});
|
||||
return $rpcenv->fork_worker("acmenewcert", undef, $authuser, $realcmd);
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'renew_certificate',
|
||||
path => 'certificate',
|
||||
method => 'PUT',
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
|
||||
},
|
||||
description => "Renew existing certificate from CA.",
|
||||
protected => 1,
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
force => {
|
||||
type => 'boolean',
|
||||
description => 'Force renewal even if expiry is more than 30 days away.',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
force => {
|
||||
type => 'boolean',
|
||||
description => 'Force renewal even if expiry is more than 30 days away.',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'string',
|
||||
type => 'string',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $node = extract_param($param, 'node');
|
||||
my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node);
|
||||
my $node = extract_param($param, 'node');
|
||||
my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node);
|
||||
|
||||
raise("No current (custom) certificate found, please order a new certificate!\n")
|
||||
if ! -e "${cert_prefix}.pem";
|
||||
raise("No current (custom) certificate found, please order a new certificate!\n")
|
||||
if !-e "${cert_prefix}.pem";
|
||||
|
||||
my $expires_soon = PVE::Certificate::check_expiry("${cert_prefix}.pem", time() + 30*24*60*60);
|
||||
raise_param_exc({'force' => "Certificate does not expire within the next 30 days, and 'force' is not set."})
|
||||
if !$expires_soon && !$param->{force};
|
||||
my $expires_soon =
|
||||
PVE::Certificate::check_expiry("${cert_prefix}.pem", time() + 30 * 24 * 60 * 60);
|
||||
raise_param_exc(
|
||||
{
|
||||
'force' =>
|
||||
"Certificate does not expire within the next 30 days, and 'force' is not set.",
|
||||
},
|
||||
) if !$expires_soon && !$param->{force};
|
||||
|
||||
my $node_config = PVE::NodeConfig::load_config($node);
|
||||
my $acme_node_config = PVE::NodeConfig::get_acme_conf($node_config);
|
||||
raise("ACME domain list in node configuration is missing!", 400)
|
||||
if !$acme_node_config || !%{$acme_node_config->{domains}};
|
||||
my $node_config = PVE::NodeConfig::load_config($node);
|
||||
my $acme_node_config = PVE::NodeConfig::get_acme_conf($node_config);
|
||||
raise("ACME domain list in node configuration is missing!", 400)
|
||||
if !$acme_node_config || !%{ $acme_node_config->{domains} };
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $old_cert = PVE::Tools::file_get_contents("${cert_prefix}.pem");
|
||||
my $old_cert = PVE::Tools::file_get_contents("${cert_prefix}.pem");
|
||||
|
||||
my $realcmd = sub {
|
||||
STDOUT->autoflush(1);
|
||||
my $account = $acme_node_config->{account};
|
||||
my $account_file = "${acme_account_dir}/${account}";
|
||||
die "ACME account config file '$account' does not exist.\n"
|
||||
if ! -e $account_file;
|
||||
my $realcmd = sub {
|
||||
STDOUT->autoflush(1);
|
||||
my $account = $acme_node_config->{account};
|
||||
my $account_file = "${acme_account_dir}/${account}";
|
||||
die "ACME account config file '$account' does not exist.\n"
|
||||
if !-e $account_file;
|
||||
|
||||
my $acme = PVE::ACME->new($account_file);
|
||||
my $acme = PVE::ACME->new($account_file);
|
||||
|
||||
print "Loading ACME account details\n";
|
||||
$acme->load();
|
||||
print "Loading ACME account details\n";
|
||||
$acme->load();
|
||||
|
||||
my ($cert, $key) = $order_certificate->($acme, $acme_node_config);
|
||||
my ($cert, $key) = $order_certificate->($acme, $acme_node_config);
|
||||
|
||||
my $code = sub {
|
||||
print "Setting pveproxy certificate and key\n";
|
||||
PVE::CertHelpers::set_cert_files($cert, $key, $cert_prefix, 1);
|
||||
my $code = sub {
|
||||
print "Setting pveproxy certificate and key\n";
|
||||
PVE::CertHelpers::set_cert_files($cert, $key, $cert_prefix, 1);
|
||||
|
||||
print "Restarting pveproxy\n";
|
||||
PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']);
|
||||
};
|
||||
PVE::CertHelpers::cert_lock(10, $code);
|
||||
die "$@\n" if $@;
|
||||
print "Restarting pveproxy\n";
|
||||
PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']);
|
||||
};
|
||||
PVE::CertHelpers::cert_lock(10, $code);
|
||||
die "$@\n" if $@;
|
||||
|
||||
print "Revoking old certificate\n";
|
||||
eval { $acme->revoke_certificate($old_cert) };
|
||||
warn "Revoke request to CA failed: $@" if $@;
|
||||
};
|
||||
print "Revoking old certificate\n";
|
||||
eval { $acme->revoke_certificate($old_cert) };
|
||||
warn "Revoke request to CA failed: $@" if $@;
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker("acmerenew", undef, $authuser, $realcmd);
|
||||
}});
|
||||
return $rpcenv->fork_worker("acmerenew", undef, $authuser, $realcmd);
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'revoke_certificate',
|
||||
path => 'certificate',
|
||||
method => 'DELETE',
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
|
||||
},
|
||||
description => "Revoke existing certificate from CA.",
|
||||
protected => 1,
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'string',
|
||||
type => 'string',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $node = extract_param($param, 'node');
|
||||
my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node);
|
||||
my $node = extract_param($param, 'node');
|
||||
my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node);
|
||||
|
||||
my $node_config = PVE::NodeConfig::load_config($node);
|
||||
my $acme_node_config = PVE::NodeConfig::get_acme_conf($node_config);
|
||||
raise("ACME domain list in node configuration is missing!", 400)
|
||||
if !$acme_node_config || !%{$acme_node_config->{domains}};
|
||||
my $node_config = PVE::NodeConfig::load_config($node);
|
||||
my $acme_node_config = PVE::NodeConfig::get_acme_conf($node_config);
|
||||
raise("ACME domain list in node configuration is missing!", 400)
|
||||
if !$acme_node_config || !%{ $acme_node_config->{domains} };
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $cert = PVE::Tools::file_get_contents("${cert_prefix}.pem");
|
||||
my $cert = PVE::Tools::file_get_contents("${cert_prefix}.pem");
|
||||
|
||||
my $realcmd = sub {
|
||||
STDOUT->autoflush(1);
|
||||
my $account = $acme_node_config->{account};
|
||||
my $account_file = "${acme_account_dir}/${account}";
|
||||
die "ACME account config file '$account' does not exist.\n"
|
||||
if ! -e $account_file;
|
||||
my $realcmd = sub {
|
||||
STDOUT->autoflush(1);
|
||||
my $account = $acme_node_config->{account};
|
||||
my $account_file = "${acme_account_dir}/${account}";
|
||||
die "ACME account config file '$account' does not exist.\n"
|
||||
if !-e $account_file;
|
||||
|
||||
my $acme = PVE::ACME->new($account_file);
|
||||
my $acme = PVE::ACME->new($account_file);
|
||||
|
||||
print "Loading ACME account details\n";
|
||||
$acme->load();
|
||||
print "Loading ACME account details\n";
|
||||
$acme->load();
|
||||
|
||||
print "Revoking old certificate\n";
|
||||
eval { $acme->revoke_certificate($cert) };
|
||||
if (my $err = $@) {
|
||||
# is there a better check?
|
||||
die "Revoke request to CA failed: $err" if $err !~ /"Certificate is expired"/;
|
||||
}
|
||||
print "Revoking old certificate\n";
|
||||
eval { $acme->revoke_certificate($cert) };
|
||||
if (my $err = $@) {
|
||||
# is there a better check?
|
||||
die "Revoke request to CA failed: $err" if $err !~ /"Certificate is expired"/;
|
||||
}
|
||||
|
||||
my $code = sub {
|
||||
print "Deleting certificate files\n";
|
||||
unlink "${cert_prefix}.pem";
|
||||
unlink "${cert_prefix}.key";
|
||||
my $code = sub {
|
||||
print "Deleting certificate files\n";
|
||||
unlink "${cert_prefix}.pem";
|
||||
unlink "${cert_prefix}.key";
|
||||
|
||||
print "Restarting pveproxy to revert to self-signed certificates\n";
|
||||
PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']);
|
||||
};
|
||||
print "Restarting pveproxy to revert to self-signed certificates\n";
|
||||
PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']);
|
||||
};
|
||||
|
||||
PVE::CertHelpers::cert_lock(10, $code);
|
||||
die "$@\n" if $@;
|
||||
};
|
||||
PVE::CertHelpers::cert_lock(10, $code);
|
||||
die "$@\n" if $@;
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker("acmerevoke", undef, $authuser, $realcmd);
|
||||
}});
|
||||
return $rpcenv->fork_worker("acmerevoke", undef, $authuser, $realcmd);
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -15,61 +15,61 @@ use PVE::API2::ACMEPlugin;
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::ACMEPlugin",
|
||||
path => 'plugins',
|
||||
});
|
||||
|
||||
my $acme_directories = [
|
||||
{
|
||||
name => 'Let\'s Encrypt V2',
|
||||
url => 'https://acme-v02.api.letsencrypt.org/directory',
|
||||
name => 'Let\'s Encrypt V2',
|
||||
url => 'https://acme-v02.api.letsencrypt.org/directory',
|
||||
},
|
||||
{
|
||||
name => 'Let\'s Encrypt V2 Staging',
|
||||
url => 'https://acme-staging-v02.api.letsencrypt.org/directory',
|
||||
name => 'Let\'s Encrypt V2 Staging',
|
||||
url => 'https://acme-staging-v02.api.letsencrypt.org/directory',
|
||||
},
|
||||
];
|
||||
my $acme_default_directory_url = $acme_directories->[0]->{url};
|
||||
my $account_contact_from_param = sub {
|
||||
my @addresses = PVE::Tools::split_list(extract_param($_[0], 'contact'));
|
||||
return [ map { "mailto:$_" } @addresses ];
|
||||
return [map { "mailto:$_" } @addresses];
|
||||
};
|
||||
my $acme_account_dir = PVE::CertHelpers::acme_account_dir();
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
permissions => { user => 'all' },
|
||||
description => "ACMEAccount index.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
return [
|
||||
{ name => 'account' },
|
||||
{ name => 'tos' },
|
||||
{ name => 'meta' },
|
||||
{ name => 'directories' },
|
||||
{ name => 'plugins' },
|
||||
{ name => 'challenge-schema' },
|
||||
];
|
||||
}});
|
||||
return [
|
||||
{ name => 'account' },
|
||||
{ name => 'tos' },
|
||||
{ name => 'meta' },
|
||||
{ name => 'directories' },
|
||||
{ name => 'plugins' },
|
||||
{ name => 'challenge-schema' },
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'account_index',
|
||||
path => 'account',
|
||||
method => 'GET',
|
||||
|
|
@ -77,112 +77,121 @@ __PACKAGE__->register_method ({
|
|||
description => "ACMEAccount index.",
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $accounts = PVE::CertHelpers::list_acme_accounts();
|
||||
return [ map { { name => $_ } } @$accounts ];
|
||||
}});
|
||||
my $accounts = PVE::CertHelpers::list_acme_accounts();
|
||||
return [map { { name => $_ } } @$accounts];
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'register_account',
|
||||
path => 'account',
|
||||
method => 'POST',
|
||||
description => "Register a new ACME account with CA.",
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
name => get_standard_option('pve-acme-account-name'),
|
||||
contact => get_standard_option('pve-acme-account-contact'),
|
||||
tos_url => {
|
||||
description => 'URL of CA TermsOfService - setting this indicates agreement.',
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
directory => get_standard_option('pve-acme-directory-url', {
|
||||
default => $acme_default_directory_url,
|
||||
optional => 1,
|
||||
}),
|
||||
'eab-kid' => {
|
||||
description => 'Key Identifier for External Account Binding.',
|
||||
type => 'string',
|
||||
requires => 'eab-hmac-key',
|
||||
optional => 1,
|
||||
},
|
||||
'eab-hmac-key' => {
|
||||
description => 'HMAC key for External Account Binding.',
|
||||
type => 'string',
|
||||
requires => 'eab-kid',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
name => get_standard_option('pve-acme-account-name'),
|
||||
contact => get_standard_option('pve-acme-account-contact'),
|
||||
tos_url => {
|
||||
description => 'URL of CA TermsOfService - setting this indicates agreement.',
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
directory => get_standard_option(
|
||||
'pve-acme-directory-url',
|
||||
{
|
||||
default => $acme_default_directory_url,
|
||||
optional => 1,
|
||||
},
|
||||
),
|
||||
'eab-kid' => {
|
||||
description => 'Key Identifier for External Account Binding.',
|
||||
type => 'string',
|
||||
requires => 'eab-hmac-key',
|
||||
optional => 1,
|
||||
},
|
||||
'eab-hmac-key' => {
|
||||
description => 'HMAC key for External Account Binding.',
|
||||
type => 'string',
|
||||
requires => 'eab-kid',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'string',
|
||||
type => 'string',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $account_name = extract_param($param, 'name') // 'default';
|
||||
my $account_file = "${acme_account_dir}/${account_name}";
|
||||
mkdir $acme_account_dir if ! -e $acme_account_dir;
|
||||
my $account_name = extract_param($param, 'name') // 'default';
|
||||
my $account_file = "${acme_account_dir}/${account_name}";
|
||||
mkdir $acme_account_dir if !-e $acme_account_dir;
|
||||
|
||||
my $eab_kid = extract_param($param, 'eab-kid');
|
||||
my $eab_hmac_key = extract_param($param, 'eab-hmac-key');
|
||||
my $eab_kid = extract_param($param, 'eab-kid');
|
||||
my $eab_hmac_key = extract_param($param, 'eab-hmac-key');
|
||||
|
||||
raise_param_exc({'name' => "ACME account config file '${account_name}' already exists."})
|
||||
if -e $account_file;
|
||||
raise_param_exc({
|
||||
'name' => "ACME account config file '${account_name}' already exists." })
|
||||
if -e $account_file;
|
||||
|
||||
my $directory = extract_param($param, 'directory') // $acme_default_directory_url;
|
||||
my $contact = $account_contact_from_param->($param);
|
||||
my $directory = extract_param($param, 'directory') // $acme_default_directory_url;
|
||||
my $contact = $account_contact_from_param->($param);
|
||||
|
||||
my $realcmd = sub {
|
||||
PVE::Cluster::cfs_lock_acme($account_name, 10, sub {
|
||||
die "ACME account config file '${account_name}' already exists.\n"
|
||||
if -e $account_file;
|
||||
my $realcmd = sub {
|
||||
PVE::Cluster::cfs_lock_acme(
|
||||
$account_name,
|
||||
10,
|
||||
sub {
|
||||
die "ACME account config file '${account_name}' already exists.\n"
|
||||
if -e $account_file;
|
||||
|
||||
my $acme = PVE::ACME->new($account_file, $directory);
|
||||
print "Generating ACME account key..\n";
|
||||
$acme->init(4096);
|
||||
print "Registering ACME account..\n";
|
||||
my $acme = PVE::ACME->new($account_file, $directory);
|
||||
print "Generating ACME account key..\n";
|
||||
$acme->init(4096);
|
||||
print "Registering ACME account..\n";
|
||||
|
||||
my %info = (contact => $contact);
|
||||
if (defined($eab_kid)) {
|
||||
$info{eab} = {
|
||||
kid => $eab_kid,
|
||||
hmac_key => $eab_hmac_key
|
||||
};
|
||||
}
|
||||
my %info = (contact => $contact);
|
||||
if (defined($eab_kid)) {
|
||||
$info{eab} = {
|
||||
kid => $eab_kid,
|
||||
hmac_key => $eab_hmac_key,
|
||||
};
|
||||
}
|
||||
|
||||
eval { $acme->new_account($param->{tos_url}, %info); };
|
||||
eval { $acme->new_account($param->{tos_url}, %info); };
|
||||
|
||||
if (my $err = $@) {
|
||||
unlink $account_file;
|
||||
die "Registration failed: $err\n";
|
||||
}
|
||||
print "Registration successful, account URL: '$acme->{location}'\n";
|
||||
});
|
||||
die $@ if $@;
|
||||
};
|
||||
if (my $err = $@) {
|
||||
unlink $account_file;
|
||||
die "Registration failed: $err\n";
|
||||
}
|
||||
print "Registration successful, account URL: '$acme->{location}'\n";
|
||||
},
|
||||
);
|
||||
die $@ if $@;
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker('acmeregister', undef, $authuser, $realcmd);
|
||||
}});
|
||||
return $rpcenv->fork_worker('acmeregister', undef, $authuser, $realcmd);
|
||||
},
|
||||
});
|
||||
|
||||
my $update_account = sub {
|
||||
my ($param, $msg, %info) = @_;
|
||||
|
|
@ -190,325 +199,347 @@ my $update_account = sub {
|
|||
my $account_name = extract_param($param, 'name') // 'default';
|
||||
my $account_file = "${acme_account_dir}/${account_name}";
|
||||
|
||||
raise_param_exc({'name' => "ACME account config file '${account_name}' does not exist."})
|
||||
if ! -e $account_file;
|
||||
|
||||
raise_param_exc({ 'name' => "ACME account config file '${account_name}' does not exist." })
|
||||
if !-e $account_file;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $realcmd = sub {
|
||||
PVE::Cluster::cfs_lock_acme($account_name, 10, sub {
|
||||
die "ACME account config file '${account_name}' does not exist.\n"
|
||||
if ! -e $account_file;
|
||||
PVE::Cluster::cfs_lock_acme(
|
||||
$account_name,
|
||||
10,
|
||||
sub {
|
||||
die "ACME account config file '${account_name}' does not exist.\n"
|
||||
if !-e $account_file;
|
||||
|
||||
my $acme = PVE::ACME->new($account_file);
|
||||
$acme->load();
|
||||
$acme->update_account(%info);
|
||||
if ($info{status} && $info{status} eq 'deactivated') {
|
||||
my $deactivated_name;
|
||||
for my $i (0..100) {
|
||||
my $candidate = "${acme_account_dir}/_deactivated_${account_name}_${i}";
|
||||
if (! -e $candidate) {
|
||||
$deactivated_name = $candidate;
|
||||
last;
|
||||
}
|
||||
}
|
||||
if ($deactivated_name) {
|
||||
print "Renaming account file from '$account_file' to '$deactivated_name'\n";
|
||||
rename($account_file, $deactivated_name) or
|
||||
warn ".. failed - $!\n";
|
||||
} else {
|
||||
warn "No free slot to rename deactivated account file '$account_file', leaving in place\n";
|
||||
}
|
||||
}
|
||||
});
|
||||
die $@ if $@;
|
||||
my $acme = PVE::ACME->new($account_file);
|
||||
$acme->load();
|
||||
$acme->update_account(%info);
|
||||
if ($info{status} && $info{status} eq 'deactivated') {
|
||||
my $deactivated_name;
|
||||
for my $i (0 .. 100) {
|
||||
my $candidate = "${acme_account_dir}/_deactivated_${account_name}_${i}";
|
||||
if (!-e $candidate) {
|
||||
$deactivated_name = $candidate;
|
||||
last;
|
||||
}
|
||||
}
|
||||
if ($deactivated_name) {
|
||||
print
|
||||
"Renaming account file from '$account_file' to '$deactivated_name'\n";
|
||||
rename($account_file, $deactivated_name)
|
||||
or warn ".. failed - $!\n";
|
||||
} else {
|
||||
warn
|
||||
"No free slot to rename deactivated account file '$account_file', leaving in place\n";
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
die $@ if $@;
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker("acme${msg}", undef, $authuser, $realcmd);
|
||||
};
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'update_account',
|
||||
path => 'account/{name}',
|
||||
method => 'PUT',
|
||||
description => "Update existing ACME account information with CA. Note: not specifying any new account information triggers a refresh.",
|
||||
description =>
|
||||
"Update existing ACME account information with CA. Note: not specifying any new account information triggers a refresh.",
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
name => get_standard_option('pve-acme-account-name'),
|
||||
contact => get_standard_option('pve-acme-account-contact', {
|
||||
optional => 1,
|
||||
}),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
name => get_standard_option('pve-acme-account-name'),
|
||||
contact => get_standard_option('pve-acme-account-contact', {
|
||||
optional => 1,
|
||||
}),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'string',
|
||||
type => 'string',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $contact = $account_contact_from_param->($param);
|
||||
if (scalar @$contact) {
|
||||
return $update_account->($param, 'update', contact => $contact);
|
||||
} else {
|
||||
return $update_account->($param, 'refresh');
|
||||
}
|
||||
}});
|
||||
my $contact = $account_contact_from_param->($param);
|
||||
if (scalar @$contact) {
|
||||
return $update_account->($param, 'update', contact => $contact);
|
||||
} else {
|
||||
return $update_account->($param, 'refresh');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'get_account',
|
||||
path => 'account/{name}',
|
||||
method => 'GET',
|
||||
description => "Return existing ACME account information.",
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
name => get_standard_option('pve-acme-account-name'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
name => get_standard_option('pve-acme-account-name'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
account => {
|
||||
type => 'object',
|
||||
optional => 1,
|
||||
renderer => 'yaml',
|
||||
},
|
||||
directory => get_standard_option('pve-acme-directory-url', {
|
||||
optional => 1,
|
||||
}),
|
||||
location => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
tos => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
account => {
|
||||
type => 'object',
|
||||
optional => 1,
|
||||
renderer => 'yaml',
|
||||
},
|
||||
directory => get_standard_option('pve-acme-directory-url', {
|
||||
optional => 1,
|
||||
}),
|
||||
location => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
tos => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $account_name = extract_param($param, 'name') // 'default';
|
||||
my $account_file = "${acme_account_dir}/${account_name}";
|
||||
my $account_name = extract_param($param, 'name') // 'default';
|
||||
my $account_file = "${acme_account_dir}/${account_name}";
|
||||
|
||||
raise_param_exc({'name' => "ACME account config file '${account_name}' does not exist."})
|
||||
if ! -e $account_file;
|
||||
raise_param_exc({
|
||||
'name' => "ACME account config file '${account_name}' does not exist." })
|
||||
if !-e $account_file;
|
||||
|
||||
my $acme = PVE::ACME->new($account_file);
|
||||
$acme->load();
|
||||
my $acme = PVE::ACME->new($account_file);
|
||||
$acme->load();
|
||||
|
||||
my $res = {};
|
||||
$res->{account} = $acme->{account};
|
||||
$res->{directory} = $acme->{directory};
|
||||
$res->{location} = $acme->{location};
|
||||
$res->{tos} = $acme->{tos};
|
||||
my $res = {};
|
||||
$res->{account} = $acme->{account};
|
||||
$res->{directory} = $acme->{directory};
|
||||
$res->{location} = $acme->{location};
|
||||
$res->{tos} = $acme->{tos};
|
||||
|
||||
return $res;
|
||||
}});
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'deactivate_account',
|
||||
path => 'account/{name}',
|
||||
method => 'DELETE',
|
||||
description => "Deactivate existing ACME account at CA.",
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
name => get_standard_option('pve-acme-account-name'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
name => get_standard_option('pve-acme-account-name'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'string',
|
||||
type => 'string',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
return $update_account->($param, 'deactivate', status => 'deactivated');
|
||||
}});
|
||||
return $update_account->($param, 'deactivate', status => 'deactivated');
|
||||
},
|
||||
});
|
||||
|
||||
# TODO: deprecated, remove with pve 9
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'get_tos',
|
||||
path => 'tos',
|
||||
method => 'GET',
|
||||
description => "Retrieve ACME TermsOfService URL from CA. Deprecated, please use /cluster/acme/meta.",
|
||||
description =>
|
||||
"Retrieve ACME TermsOfService URL from CA. Deprecated, please use /cluster/acme/meta.",
|
||||
permissions => { user => 'all' },
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
directory => get_standard_option('pve-acme-directory-url', {
|
||||
default => $acme_default_directory_url,
|
||||
optional => 1,
|
||||
}),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
directory => get_standard_option(
|
||||
'pve-acme-directory-url',
|
||||
{
|
||||
default => $acme_default_directory_url,
|
||||
optional => 1,
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
description => 'ACME TermsOfService URL.',
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
description => 'ACME TermsOfService URL.',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $directory = extract_param($param, 'directory') // $acme_default_directory_url;
|
||||
my $directory = extract_param($param, 'directory') // $acme_default_directory_url;
|
||||
|
||||
my $acme = PVE::ACME->new(undef, $directory);
|
||||
my $meta = $acme->get_meta();
|
||||
my $acme = PVE::ACME->new(undef, $directory);
|
||||
my $meta = $acme->get_meta();
|
||||
|
||||
return $meta ? $meta->{termsOfService} : undef;
|
||||
}});
|
||||
return $meta ? $meta->{termsOfService} : undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'get_meta',
|
||||
path => 'meta',
|
||||
method => 'GET',
|
||||
description => "Retrieve ACME Directory Meta Information",
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Audit']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
directory => get_standard_option('pve-acme-directory-url', {
|
||||
default => $acme_default_directory_url,
|
||||
optional => 1,
|
||||
}),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
directory => get_standard_option(
|
||||
'pve-acme-directory-url',
|
||||
{
|
||||
default => $acme_default_directory_url,
|
||||
optional => 1,
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'object',
|
||||
additionalProperties => 1,
|
||||
properties => {
|
||||
termsOfService => {
|
||||
description => 'ACME TermsOfService URL.',
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
externalAccountRequired => {
|
||||
description => 'EAB Required',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
website => {
|
||||
description => 'URL to more information about the ACME server.',
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
caaIdentities => {
|
||||
description => 'Hostnames referring to the ACME servers.',
|
||||
type => 'array',
|
||||
items => {
|
||||
type => 'string',
|
||||
},
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
type => 'object',
|
||||
additionalProperties => 1,
|
||||
properties => {
|
||||
termsOfService => {
|
||||
description => 'ACME TermsOfService URL.',
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
externalAccountRequired => {
|
||||
description => 'EAB Required',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
website => {
|
||||
description => 'URL to more information about the ACME server.',
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
caaIdentities => {
|
||||
description => 'Hostnames referring to the ACME servers.',
|
||||
type => 'array',
|
||||
items => {
|
||||
type => 'string',
|
||||
},
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $directory = extract_param($param, 'directory') // $acme_default_directory_url;
|
||||
my $directory = extract_param($param, 'directory') // $acme_default_directory_url;
|
||||
|
||||
my $acme = PVE::ACME->new(undef, $directory);
|
||||
my $meta = $acme->get_meta();
|
||||
my $acme = PVE::ACME->new(undef, $directory);
|
||||
my $meta = $acme->get_meta();
|
||||
|
||||
return $meta;
|
||||
}});
|
||||
return $meta;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'get_directories',
|
||||
path => 'directories',
|
||||
method => 'GET',
|
||||
description => "Get named known ACME directory endpoints.",
|
||||
permissions => { user => 'all' },
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
name => {
|
||||
type => 'string',
|
||||
},
|
||||
url => get_standard_option('pve-acme-directory-url'),
|
||||
},
|
||||
},
|
||||
type => 'array',
|
||||
items => {
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
name => {
|
||||
type => 'string',
|
||||
},
|
||||
url => get_standard_option('pve-acme-directory-url'),
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
return $acme_directories;
|
||||
}});
|
||||
return $acme_directories;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'challengeschema',
|
||||
path => 'challenge-schema',
|
||||
method => 'GET',
|
||||
description => "Get schema of ACME challenge types.",
|
||||
permissions => { user => 'all' },
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
},
|
||||
name => {
|
||||
description => 'Human readable name, falls back to id',
|
||||
type => 'string',
|
||||
},
|
||||
type => {
|
||||
type => 'string',
|
||||
},
|
||||
schema => {
|
||||
type => 'object',
|
||||
},
|
||||
},
|
||||
},
|
||||
type => 'array',
|
||||
items => {
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
},
|
||||
name => {
|
||||
description => 'Human readable name, falls back to id',
|
||||
type => 'string',
|
||||
},
|
||||
type => {
|
||||
type => 'string',
|
||||
},
|
||||
schema => {
|
||||
type => 'object',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $plugin_type_enum = PVE::ACME::Challenge->lookup_types();
|
||||
my $plugin_type_enum = PVE::ACME::Challenge->lookup_types();
|
||||
|
||||
my $res = [];
|
||||
my $res = [];
|
||||
|
||||
for my $type (@$plugin_type_enum) {
|
||||
my $plugin = PVE::ACME::Challenge->lookup($type);
|
||||
next if !$plugin->can('get_supported_plugins');
|
||||
for my $type (@$plugin_type_enum) {
|
||||
my $plugin = PVE::ACME::Challenge->lookup($type);
|
||||
next if !$plugin->can('get_supported_plugins');
|
||||
|
||||
my $plugin_type = $plugin->type();
|
||||
my $plugins = $plugin->get_supported_plugins();
|
||||
for my $id (sort keys %$plugins) {
|
||||
my $schema = $plugins->{$id};
|
||||
push @$res, {
|
||||
id => $id,
|
||||
name => $schema->{name} // $id,
|
||||
type => $plugin_type,
|
||||
schema => $schema,
|
||||
};
|
||||
}
|
||||
}
|
||||
my $plugin_type = $plugin->type();
|
||||
my $plugins = $plugin->get_supported_plugins();
|
||||
for my $id (sort keys %$plugins) {
|
||||
my $schema = $plugins->{$id};
|
||||
push @$res,
|
||||
{
|
||||
id => $id,
|
||||
name => $schema->{name} // $id,
|
||||
type => $plugin_type,
|
||||
schema => $schema,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}});
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -17,20 +17,24 @@ use base qw(PVE::RESTHandler);
|
|||
|
||||
my $plugin_config_file = "priv/acme/plugins.cfg";
|
||||
|
||||
cfs_register_file($plugin_config_file,
|
||||
sub { PVE::ACME::Challenge->parse_config(@_); },
|
||||
sub { PVE::ACME::Challenge->write_config(@_); },
|
||||
cfs_register_file(
|
||||
$plugin_config_file,
|
||||
sub { PVE::ACME::Challenge->parse_config(@_); },
|
||||
sub { PVE::ACME::Challenge->write_config(@_); },
|
||||
);
|
||||
|
||||
PVE::ACME::DNSChallenge->register();
|
||||
PVE::ACME::StandAlone->register();
|
||||
PVE::ACME::Challenge->init();
|
||||
|
||||
PVE::JSONSchema::register_standard_option('pve-acme-pluginid', {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
description => 'Unique identifier for ACME plugin instance.',
|
||||
});
|
||||
PVE::JSONSchema::register_standard_option(
|
||||
'pve-acme-pluginid',
|
||||
{
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
description => 'Unique identifier for ACME plugin instance.',
|
||||
},
|
||||
);
|
||||
|
||||
my $plugin_type_enum = PVE::ACME::Challenge->lookup_types();
|
||||
|
||||
|
|
@ -46,50 +50,50 @@ my $modify_cfg_for_api = sub {
|
|||
return $plugin_cfg;
|
||||
};
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
description => "ACME plugin index.",
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
type => {
|
||||
description => "Only list ACME plugins of a specific type",
|
||||
type => 'string',
|
||||
enum => $plugin_type_enum,
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
type => {
|
||||
description => "Only list ACME plugins of a specific type",
|
||||
type => 'string',
|
||||
enum => $plugin_type_enum,
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
plugin => get_standard_option('pve-acme-pluginid'),
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{plugin}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
plugin => get_standard_option('pve-acme-pluginid'),
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{plugin}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $cfg = load_config();
|
||||
my $cfg = load_config();
|
||||
|
||||
my $res = [];
|
||||
foreach my $pluginid (keys %{$cfg->{ids}}) {
|
||||
my $plugin_cfg = $modify_cfg_for_api->($cfg, $pluginid);
|
||||
next if $param->{type} && $param->{type} ne $plugin_cfg->{type};
|
||||
push @$res, $plugin_cfg;
|
||||
}
|
||||
my $res = [];
|
||||
foreach my $pluginid (keys %{ $cfg->{ids} }) {
|
||||
my $plugin_cfg = $modify_cfg_for_api->($cfg, $pluginid);
|
||||
next if $param->{type} && $param->{type} ne $plugin_cfg->{type};
|
||||
push @$res, $plugin_cfg;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method({
|
||||
|
|
@ -98,24 +102,24 @@ __PACKAGE__->register_method({
|
|||
method => 'GET',
|
||||
description => "Get ACME plugin configuration.",
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-acme-pluginid'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-acme-pluginid'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'object',
|
||||
type => 'object',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $cfg = load_config();
|
||||
return $modify_cfg_for_api->($cfg, $param->{id});
|
||||
}
|
||||
my $cfg = load_config();
|
||||
return $modify_cfg_for_api->($cfg, $param->{id});
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method({
|
||||
|
|
@ -124,35 +128,39 @@ __PACKAGE__->register_method({
|
|||
method => 'POST',
|
||||
description => "Add ACME plugin configuration.",
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
protected => 1,
|
||||
parameters => PVE::ACME::Challenge->createSchema(),
|
||||
returns => {
|
||||
type => "null"
|
||||
type => "null",
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $id = extract_param($param, 'id');
|
||||
my $type = extract_param($param, 'type');
|
||||
my $id = extract_param($param, 'id');
|
||||
my $type = extract_param($param, 'type');
|
||||
|
||||
cfs_lock_file($plugin_config_file, undef, sub {
|
||||
my $cfg = load_config();
|
||||
die "ACME plugin ID '$id' already exists\n" if defined($cfg->{ids}->{$id});
|
||||
cfs_lock_file(
|
||||
$plugin_config_file,
|
||||
undef,
|
||||
sub {
|
||||
my $cfg = load_config();
|
||||
die "ACME plugin ID '$id' already exists\n" if defined($cfg->{ids}->{$id});
|
||||
|
||||
my $plugin = PVE::ACME::Challenge->lookup($type);
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
my $plugin = PVE::ACME::Challenge->lookup($type);
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
|
||||
$cfg->{ids}->{$id} = $opts;
|
||||
$cfg->{ids}->{$id}->{type} = $type;
|
||||
$cfg->{ids}->{$id} = $opts;
|
||||
$cfg->{ids}->{$id}->{type} = $type;
|
||||
|
||||
cfs_write_file($plugin_config_file, $cfg);
|
||||
});
|
||||
die "$@" if $@;
|
||||
cfs_write_file($plugin_config_file, $cfg);
|
||||
},
|
||||
);
|
||||
die "$@" if $@;
|
||||
|
||||
return undef;
|
||||
}
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method({
|
||||
|
|
@ -161,51 +169,55 @@ __PACKAGE__->register_method({
|
|||
method => 'PUT',
|
||||
description => "Update ACME plugin configuration.",
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
protected => 1,
|
||||
parameters => PVE::ACME::Challenge->updateSchema(),
|
||||
returns => {
|
||||
type => "null"
|
||||
type => "null",
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $id = extract_param($param, 'id');
|
||||
my $delete = extract_param($param, 'delete');
|
||||
my $digest = extract_param($param, 'digest');
|
||||
my $id = extract_param($param, 'id');
|
||||
my $delete = extract_param($param, 'delete');
|
||||
my $digest = extract_param($param, 'digest');
|
||||
|
||||
cfs_lock_file($plugin_config_file, undef, sub {
|
||||
my $cfg = load_config();
|
||||
PVE::Tools::assert_if_modified($cfg->{digest}, $digest);
|
||||
my $plugin_cfg = $cfg->{ids}->{$id};
|
||||
die "ACME plugin ID '$id' does not exist\n" if !$plugin_cfg;
|
||||
cfs_lock_file(
|
||||
$plugin_config_file,
|
||||
undef,
|
||||
sub {
|
||||
my $cfg = load_config();
|
||||
PVE::Tools::assert_if_modified($cfg->{digest}, $digest);
|
||||
my $plugin_cfg = $cfg->{ids}->{$id};
|
||||
die "ACME plugin ID '$id' does not exist\n" if !$plugin_cfg;
|
||||
|
||||
my $type = $plugin_cfg->{type};
|
||||
my $plugin = PVE::ACME::Challenge->lookup($type);
|
||||
my $type = $plugin_cfg->{type};
|
||||
my $plugin = PVE::ACME::Challenge->lookup($type);
|
||||
|
||||
if (defined($delete)) {
|
||||
my $schema = $plugin->private();
|
||||
my $options = $schema->{options}->{$type};
|
||||
for my $k (PVE::Tools::split_list($delete)) {
|
||||
my $d = $options->{$k} || die "no such option '$k'\n";
|
||||
die "unable to delete required option '$k'\n" if !$d->{optional};
|
||||
if (defined($delete)) {
|
||||
my $schema = $plugin->private();
|
||||
my $options = $schema->{options}->{$type};
|
||||
for my $k (PVE::Tools::split_list($delete)) {
|
||||
my $d = $options->{$k} || die "no such option '$k'\n";
|
||||
die "unable to delete required option '$k'\n" if !$d->{optional};
|
||||
|
||||
delete $cfg->{ids}->{$id}->{$k};
|
||||
}
|
||||
}
|
||||
delete $cfg->{ids}->{$id}->{$k};
|
||||
}
|
||||
}
|
||||
|
||||
my $opts = $plugin->check_config($id, $param, 0, 1);
|
||||
for my $k (sort keys %$opts) {
|
||||
$plugin_cfg->{$k} = $opts->{$k};
|
||||
}
|
||||
my $opts = $plugin->check_config($id, $param, 0, 1);
|
||||
for my $k (sort keys %$opts) {
|
||||
$plugin_cfg->{$k} = $opts->{$k};
|
||||
}
|
||||
|
||||
cfs_write_file($plugin_config_file, $cfg);
|
||||
});
|
||||
die "$@" if $@;
|
||||
cfs_write_file($plugin_config_file, $cfg);
|
||||
},
|
||||
);
|
||||
die "$@" if $@;
|
||||
|
||||
return undef;
|
||||
}
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method({
|
||||
|
|
@ -214,34 +226,38 @@ __PACKAGE__->register_method({
|
|||
method => 'DELETE',
|
||||
description => "Delete ACME plugin configuration.",
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-acme-pluginid'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-acme-pluginid'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => "null"
|
||||
type => "null",
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $id = extract_param($param, 'id');
|
||||
my $id = extract_param($param, 'id');
|
||||
|
||||
cfs_lock_file($plugin_config_file, undef, sub {
|
||||
my $cfg = load_config();
|
||||
cfs_lock_file(
|
||||
$plugin_config_file,
|
||||
undef,
|
||||
sub {
|
||||
my $cfg = load_config();
|
||||
|
||||
delete $cfg->{ids}->{$id};
|
||||
delete $cfg->{ids}->{$id};
|
||||
|
||||
cfs_write_file($plugin_config_file, $cfg);
|
||||
});
|
||||
die "$@" if $@;
|
||||
cfs_write_file($plugin_config_file, $cfg);
|
||||
},
|
||||
);
|
||||
die "$@" if $@;
|
||||
|
||||
return undef;
|
||||
}
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
sub load_config {
|
||||
|
|
|
|||
1206
PVE/API2/APT.pm
1206
PVE/API2/APT.pm
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -10,79 +10,76 @@ use PVE::API2::Qemu::Machine;
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::Qemu::CPU",
|
||||
path => 'qemu/cpu',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::Qemu::Machine",
|
||||
path => 'qemu/machines',
|
||||
});
|
||||
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
permissions => { user => 'all' },
|
||||
description => "Node capabilities index.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $result = [
|
||||
{ name => 'qemu' },
|
||||
];
|
||||
my $result = [
|
||||
{ name => 'qemu' },
|
||||
];
|
||||
|
||||
return $result;
|
||||
}
|
||||
return $result;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'qemu_caps_index',
|
||||
path => 'qemu',
|
||||
method => 'GET',
|
||||
permissions => { user => 'all' },
|
||||
description => "QEMU capabilities index.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $result = [
|
||||
{ name => 'cpu' },
|
||||
{ name => 'machines' },
|
||||
];
|
||||
my $result = [
|
||||
{ name => 'cpu' }, { name => 'machines' },
|
||||
];
|
||||
|
||||
return $result;
|
||||
}
|
||||
return $result;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
846
PVE/API2/Ceph.pm
846
PVE/API2/Ceph.pm
File diff suppressed because it is too large
Load diff
|
|
@ -11,184 +11,185 @@ use PVE::Tools qw(file_get_contents);
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "Directory index.",
|
||||
permissions => { user => 'all' },
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $result = [
|
||||
{ name => 'raw' },
|
||||
{ name => 'db' },
|
||||
{ name => 'value' },
|
||||
];
|
||||
my $result = [
|
||||
{ name => 'raw' }, { name => 'db' }, { name => 'value' },
|
||||
];
|
||||
|
||||
return $result;
|
||||
}});
|
||||
return $result;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'raw',
|
||||
path => 'raw',
|
||||
method => 'GET',
|
||||
proxyto => 'node',
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
|
||||
check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
|
||||
},
|
||||
description => "Get the Ceph configuration file.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => { type => 'string' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
|
||||
my $path = PVE::Ceph::Tools::get_config('pve_ceph_cfgpath');
|
||||
return file_get_contents($path);
|
||||
my $path = PVE::Ceph::Tools::get_config('pve_ceph_cfgpath');
|
||||
return file_get_contents($path);
|
||||
|
||||
}});
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'db',
|
||||
path => 'db',
|
||||
method => 'GET',
|
||||
proxyto => 'node',
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
|
||||
check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
|
||||
},
|
||||
description => "Get the Ceph configuration database.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => 'object',
|
||||
properties => {
|
||||
section => { type => "string", },
|
||||
name => { type => "string", },
|
||||
value => { type => "string", },
|
||||
level => { type => "string", },
|
||||
'can_update_at_runtime' => { type => "boolean", },
|
||||
mask => { type => "string" },
|
||||
},
|
||||
},
|
||||
type => 'array',
|
||||
items => {
|
||||
type => 'object',
|
||||
properties => {
|
||||
section => { type => "string" },
|
||||
name => { type => "string" },
|
||||
value => { type => "string" },
|
||||
level => { type => "string" },
|
||||
'can_update_at_runtime' => { type => "boolean" },
|
||||
mask => { type => "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
|
||||
my $rados = PVE::RADOS->new();
|
||||
my $res = $rados->mon_command( { prefix => 'config dump', format => 'json' });
|
||||
foreach my $entry (@$res) {
|
||||
$entry->{can_update_at_runtime} = $entry->{can_update_at_runtime}? 1 : 0; # JSON::true/false -> 1/0
|
||||
}
|
||||
|
||||
return $res;
|
||||
}});
|
||||
my $rados = PVE::RADOS->new();
|
||||
my $res = $rados->mon_command({ prefix => 'config dump', format => 'json' });
|
||||
foreach my $entry (@$res) {
|
||||
$entry->{can_update_at_runtime} = $entry->{can_update_at_runtime} ? 1 : 0; # JSON::true/false -> 1/0
|
||||
}
|
||||
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
my $SINGLE_CONFIGKEY_RE = qr/[0-9a-z\-_\.]+:[0-9a-zA-Z\-_]+/i;
|
||||
my $CONFIGKEYS_RE = qr/^(:?${SINGLE_CONFIGKEY_RE})(:?[;, ]${SINGLE_CONFIGKEY_RE})*$/;
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'value',
|
||||
path => 'value',
|
||||
method => 'GET',
|
||||
proxyto => 'node',
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Audit' ]],
|
||||
check => ['perm', '/', ['Sys.Audit']],
|
||||
},
|
||||
description => "Get configured values from either the config file or config DB.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
'config-keys' => {
|
||||
type => "string",
|
||||
typetext => "<section>:<config key>[;<section>:<config key>]",
|
||||
pattern => $CONFIGKEYS_RE,
|
||||
description => "List of <section>:<config key> items.",
|
||||
}
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
'config-keys' => {
|
||||
type => "string",
|
||||
typetext => "<section>:<config key>[;<section>:<config key>]",
|
||||
pattern => $CONFIGKEYS_RE,
|
||||
description => "List of <section>:<config key> items.",
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'object',
|
||||
description => "Contains {section}->{key} children with the values",
|
||||
type => 'object',
|
||||
description => "Contains {section}->{key} children with the values",
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
|
||||
# Ceph treats '-' and '_' the same in parameter names, stick with '-'
|
||||
my $normalize = sub {
|
||||
my $t = shift;
|
||||
$t =~ s/_/-/g;
|
||||
return $t;
|
||||
};
|
||||
# Ceph treats '-' and '_' the same in parameter names, stick with '-'
|
||||
my $normalize = sub {
|
||||
my $t = shift;
|
||||
$t =~ s/_/-/g;
|
||||
return $t;
|
||||
};
|
||||
|
||||
my $requested_keys = {};
|
||||
for my $pair (PVE::Tools::split_list($param->{'config-keys'})) {
|
||||
my ($section, $key) = split(":", $pair);
|
||||
$section = $normalize->($section);
|
||||
$key = $normalize->($key);
|
||||
my $requested_keys = {};
|
||||
for my $pair (PVE::Tools::split_list($param->{'config-keys'})) {
|
||||
my ($section, $key) = split(":", $pair);
|
||||
$section = $normalize->($section);
|
||||
$key = $normalize->($key);
|
||||
|
||||
$requested_keys->{$section}->{$key} = 1;
|
||||
}
|
||||
$requested_keys->{$section}->{$key} = 1;
|
||||
}
|
||||
|
||||
my $config = {};
|
||||
my $config = {};
|
||||
|
||||
my $rados = PVE::RADOS->new();
|
||||
my $configdb = $rados->mon_command( { prefix => 'config dump', format => 'json' });
|
||||
for my $s (@{$configdb}) {
|
||||
my ($section, $name, $value) = $s->@{'section', 'name', 'value'};
|
||||
my $n_section = $normalize->($section);
|
||||
my $n_name = $normalize->($name);
|
||||
my $rados = PVE::RADOS->new();
|
||||
my $configdb = $rados->mon_command({ prefix => 'config dump', format => 'json' });
|
||||
for my $s (@{$configdb}) {
|
||||
my ($section, $name, $value) = $s->@{ 'section', 'name', 'value' };
|
||||
my $n_section = $normalize->($section);
|
||||
my $n_name = $normalize->($name);
|
||||
|
||||
$config->{$n_section}->{$n_name} = $value
|
||||
if defined $requested_keys->{$n_section} && $n_name eq $n_name;
|
||||
}
|
||||
$config->{$n_section}->{$n_name} = $value
|
||||
if defined $requested_keys->{$n_section} && $n_name eq $n_name;
|
||||
}
|
||||
|
||||
# read ceph.conf after config db as it has priority if settings are present in both
|
||||
my $config_file = cfs_read_file('ceph.conf'); # cfs_read_file to get it parsed
|
||||
for my $section (keys %{$config_file}) {
|
||||
my $n_section = $normalize->($section);
|
||||
next if !defined $requested_keys->{$n_section};
|
||||
# read ceph.conf after config db as it has priority if settings are present in both
|
||||
my $config_file = cfs_read_file('ceph.conf'); # cfs_read_file to get it parsed
|
||||
for my $section (keys %{$config_file}) {
|
||||
my $n_section = $normalize->($section);
|
||||
next if !defined $requested_keys->{$n_section};
|
||||
|
||||
for my $key (keys %{$config_file->{$section}}) {
|
||||
my $n_key = $normalize->($key);
|
||||
$config->{$n_section}->{$n_key} = $config_file->{$section}->{$key}
|
||||
if $requested_keys->{$n_section}->{$n_key};
|
||||
}
|
||||
}
|
||||
for my $key (keys %{ $config_file->{$section} }) {
|
||||
my $n_key = $normalize->($key);
|
||||
$config->{$n_section}->{$n_key} = $config_file->{$section}->{$key}
|
||||
if $requested_keys->{$n_section}->{$n_key};
|
||||
}
|
||||
}
|
||||
|
||||
return $config;
|
||||
}});
|
||||
return $config;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,65 +15,65 @@ use PVE::API2::Storage::Config;
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
proxyto => 'node',
|
||||
description => "Directory index.",
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
|
||||
check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
|
||||
},
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
name => {
|
||||
description => "The ceph filesystem name.",
|
||||
type => 'string',
|
||||
},
|
||||
metadata_pool => {
|
||||
description => "The name of the metadata pool.",
|
||||
type => 'string',
|
||||
},
|
||||
data_pool => {
|
||||
description => "The name of the data pool.",
|
||||
type => 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
name => {
|
||||
description => "The ceph filesystem name.",
|
||||
type => 'string',
|
||||
},
|
||||
metadata_pool => {
|
||||
description => "The name of the metadata pool.",
|
||||
type => 'string',
|
||||
},
|
||||
data_pool => {
|
||||
description => "The name of the data pool.",
|
||||
type => 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
|
||||
my $rados = PVE::RADOS->new();
|
||||
my $rados = PVE::RADOS->new();
|
||||
|
||||
my $cephfs_list = PVE::Ceph::Tools::ls_fs($rados);
|
||||
my $cephfs_list = PVE::Ceph::Tools::ls_fs($rados);
|
||||
|
||||
my $res = [
|
||||
map {{
|
||||
name => $_->{name},
|
||||
metadata_pool => $_->{metadata_pool},
|
||||
data_pool => $_->{data_pools}->[0],
|
||||
}} @$cephfs_list
|
||||
];
|
||||
my $res = [
|
||||
map { {
|
||||
name => $_->{name},
|
||||
metadata_pool => $_->{metadata_pool},
|
||||
data_pool => $_->{data_pools}->[0],
|
||||
} } @$cephfs_list
|
||||
];
|
||||
|
||||
return $res;
|
||||
}
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'createfs',
|
||||
path => '{name}',
|
||||
method => 'POST',
|
||||
|
|
@ -81,141 +81,143 @@ __PACKAGE__->register_method ({
|
|||
proxyto => 'node',
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
name => {
|
||||
description => "The ceph filesystem name.",
|
||||
type => 'string',
|
||||
default => 'cephfs',
|
||||
optional => 1,
|
||||
pattern => qr|^[^:/\s]+$|,
|
||||
},
|
||||
pg_num => {
|
||||
description => "Number of placement groups for the backing data pool. The metadata pool will use a quarter of this.",
|
||||
type => 'integer',
|
||||
default => 128,
|
||||
optional => 1,
|
||||
minimum => 8,
|
||||
maximum => 32768,
|
||||
},
|
||||
'add-storage' => {
|
||||
description => "Configure the created CephFS as storage for this cluster.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
name => {
|
||||
description => "The ceph filesystem name.",
|
||||
type => 'string',
|
||||
default => 'cephfs',
|
||||
optional => 1,
|
||||
pattern => qr|^[^:/\s]+$|,
|
||||
},
|
||||
pg_num => {
|
||||
description =>
|
||||
"Number of placement groups for the backing data pool. The metadata pool will use a quarter of this.",
|
||||
type => 'integer',
|
||||
default => 128,
|
||||
optional => 1,
|
||||
minimum => 8,
|
||||
maximum => 32768,
|
||||
},
|
||||
'add-storage' => {
|
||||
description => "Configure the created CephFS as storage for this cluster.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'string' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_configured();
|
||||
PVE::Ceph::Tools::check_ceph_configured();
|
||||
|
||||
my $fs_name = $param->{name} // 'cephfs';
|
||||
my $pg_num = $param->{pg_num} // 128;
|
||||
my $fs_name = $param->{name} // 'cephfs';
|
||||
my $pg_num = $param->{pg_num} // 128;
|
||||
|
||||
my $pool_data = "${fs_name}_data";
|
||||
my $pool_metadata = "${fs_name}_metadata";
|
||||
my $pool_data = "${fs_name}_data";
|
||||
my $pool_metadata = "${fs_name}_metadata";
|
||||
|
||||
my $rados = PVE::RADOS->new();
|
||||
my $ls_pools = PVE::Ceph::Tools::ls_pools();
|
||||
my $existing_pools = { map { $_->{poolname} => 1 } @$ls_pools };
|
||||
my $rados = PVE::RADOS->new();
|
||||
my $ls_pools = PVE::Ceph::Tools::ls_pools();
|
||||
my $existing_pools = { map { $_->{poolname} => 1 } @$ls_pools };
|
||||
|
||||
die "ceph pools '$pool_data' and/or '$pool_metadata' already exist\n"
|
||||
if $existing_pools->{$pool_data} || $existing_pools->{$pool_metadata};
|
||||
die "ceph pools '$pool_data' and/or '$pool_metadata' already exist\n"
|
||||
if $existing_pools->{$pool_data} || $existing_pools->{$pool_metadata};
|
||||
|
||||
my $fs = PVE::Ceph::Tools::ls_fs($rados);
|
||||
die "ceph fs '$fs_name' already exists\n"
|
||||
if (grep { $_->{name} eq $fs_name } @$fs);
|
||||
my $fs = PVE::Ceph::Tools::ls_fs($rados);
|
||||
die "ceph fs '$fs_name' already exists\n"
|
||||
if (grep { $_->{name} eq $fs_name } @$fs);
|
||||
|
||||
my $running_mds = PVE::Ceph::Services::get_cluster_mds_state($rados);
|
||||
die "no running Metadata Server (MDS) found!\n" if !scalar(keys %$running_mds);
|
||||
die "no standby Metadata Server (MDS) found!\n"
|
||||
if !grep { $_->{state} eq 'up:standby' } values(%$running_mds);
|
||||
my $running_mds = PVE::Ceph::Services::get_cluster_mds_state($rados);
|
||||
die "no running Metadata Server (MDS) found!\n" if !scalar(keys %$running_mds);
|
||||
die "no standby Metadata Server (MDS) found!\n"
|
||||
if !grep { $_->{state} eq 'up:standby' } values(%$running_mds);
|
||||
|
||||
PVE::Storage::assert_sid_unused($fs_name) if $param->{add_storage};
|
||||
PVE::Storage::assert_sid_unused($fs_name) if $param->{add_storage};
|
||||
|
||||
my $worker = sub {
|
||||
$rados = PVE::RADOS->new();
|
||||
my $worker = sub {
|
||||
$rados = PVE::RADOS->new();
|
||||
|
||||
my $pool_param = {
|
||||
application => 'cephfs',
|
||||
pg_num => $pg_num,
|
||||
};
|
||||
my $pool_param = {
|
||||
application => 'cephfs',
|
||||
pg_num => $pg_num,
|
||||
};
|
||||
|
||||
my @created_pools = ();
|
||||
eval {
|
||||
print "creating data pool '$pool_data'...\n";
|
||||
PVE::Ceph::Tools::create_pool($pool_data, $pool_param, $rados);
|
||||
push @created_pools, $pool_data;
|
||||
my @created_pools = ();
|
||||
eval {
|
||||
print "creating data pool '$pool_data'...\n";
|
||||
PVE::Ceph::Tools::create_pool($pool_data, $pool_param, $rados);
|
||||
push @created_pools, $pool_data;
|
||||
|
||||
print "creating metadata pool '$pool_metadata'...\n";
|
||||
$pool_param->{pg_num} = $pg_num >= 32 ? $pg_num / 4 : 8;
|
||||
PVE::Ceph::Tools::create_pool($pool_metadata, $pool_param, $rados);
|
||||
push @created_pools, $pool_metadata;
|
||||
print "creating metadata pool '$pool_metadata'...\n";
|
||||
$pool_param->{pg_num} = $pg_num >= 32 ? $pg_num / 4 : 8;
|
||||
PVE::Ceph::Tools::create_pool($pool_metadata, $pool_param, $rados);
|
||||
push @created_pools, $pool_metadata;
|
||||
|
||||
print "configuring new CephFS '$fs_name'\n";
|
||||
my $param = {
|
||||
pool_metadata => $pool_metadata,
|
||||
pool_data => $pool_data,
|
||||
};
|
||||
PVE::Ceph::Tools::create_fs($fs_name, $param, $rados);
|
||||
};
|
||||
if (my $err = $@) {
|
||||
$@ = undef;
|
||||
print "configuring new CephFS '$fs_name'\n";
|
||||
my $param = {
|
||||
pool_metadata => $pool_metadata,
|
||||
pool_data => $pool_data,
|
||||
};
|
||||
PVE::Ceph::Tools::create_fs($fs_name, $param, $rados);
|
||||
};
|
||||
if (my $err = $@) {
|
||||
$@ = undef;
|
||||
|
||||
if (@created_pools > 0) {
|
||||
warn "Encountered error after creating at least one pool\n";
|
||||
# our old connection is very likely broken now, recreate
|
||||
$rados = PVE::RADOS->new();
|
||||
foreach my $pool (@created_pools) {
|
||||
warn "cleaning up left over pool '$pool'\n";
|
||||
eval { PVE::Ceph::Tools::destroy_pool($pool, $rados) };
|
||||
warn "$@\n" if $@;
|
||||
}
|
||||
}
|
||||
if (@created_pools > 0) {
|
||||
warn "Encountered error after creating at least one pool\n";
|
||||
# our old connection is very likely broken now, recreate
|
||||
$rados = PVE::RADOS->new();
|
||||
foreach my $pool (@created_pools) {
|
||||
warn "cleaning up left over pool '$pool'\n";
|
||||
eval { PVE::Ceph::Tools::destroy_pool($pool, $rados) };
|
||||
warn "$@\n" if $@;
|
||||
}
|
||||
}
|
||||
|
||||
die "$err\n";
|
||||
}
|
||||
print "Successfully create CephFS '$fs_name'\n";
|
||||
die "$err\n";
|
||||
}
|
||||
print "Successfully create CephFS '$fs_name'\n";
|
||||
|
||||
if ($param->{'add-storage'}) {
|
||||
print "Adding '$fs_name' to storage configuration...\n";
|
||||
if ($param->{'add-storage'}) {
|
||||
print "Adding '$fs_name' to storage configuration...\n";
|
||||
|
||||
my $waittime = 0;
|
||||
while (!PVE::Ceph::Services::is_mds_active($rados, $fs_name)) {
|
||||
if ($waittime >= 10) {
|
||||
die "Need MDS to add storage, but none got active!\n";
|
||||
}
|
||||
my $waittime = 0;
|
||||
while (!PVE::Ceph::Services::is_mds_active($rados, $fs_name)) {
|
||||
if ($waittime >= 10) {
|
||||
die "Need MDS to add storage, but none got active!\n";
|
||||
}
|
||||
|
||||
print "Waiting for an MDS to become active\n";
|
||||
sleep(1);
|
||||
$waittime++;
|
||||
}
|
||||
print "Waiting for an MDS to become active\n";
|
||||
sleep(1);
|
||||
$waittime++;
|
||||
}
|
||||
|
||||
eval {
|
||||
PVE::API2::Storage::Config->create({
|
||||
type => 'cephfs',
|
||||
storage => $fs_name,
|
||||
content => 'backup,iso,vztmpl',
|
||||
'fs-name' => $fs_name,
|
||||
})
|
||||
};
|
||||
die "adding storage for CephFS '$fs_name' failed, check log ".
|
||||
"and add manually!\n$@\n" if $@;
|
||||
}
|
||||
};
|
||||
eval {
|
||||
PVE::API2::Storage::Config->create({
|
||||
type => 'cephfs',
|
||||
storage => $fs_name,
|
||||
content => 'backup,iso,vztmpl',
|
||||
'fs-name' => $fs_name,
|
||||
});
|
||||
};
|
||||
die "adding storage for CephFS '$fs_name' failed, check log "
|
||||
. "and add manually!\n$@\n"
|
||||
if $@;
|
||||
}
|
||||
};
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
|
||||
return $rpcenv->fork_worker('cephfscreate', $fs_name, $user, $worker);
|
||||
}
|
||||
return $rpcenv->fork_worker('cephfscreate', $fs_name, $user, $worker);
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -14,79 +14,80 @@ use PVE::RPCEnvironment;
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "MDS directory index.",
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
|
||||
check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
|
||||
},
|
||||
proxyto => 'node',
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
name => {
|
||||
description => "The name (ID) for the MDS",
|
||||
},
|
||||
addr => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
host => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
state => {
|
||||
type => 'string',
|
||||
description => 'State of the MDS',
|
||||
},
|
||||
standby_replay => {
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
description => 'If true, the standby MDS is polling the active MDS for faster recovery (hot standby).',
|
||||
},
|
||||
rank => {
|
||||
type => 'integer',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
name => {
|
||||
description => "The name (ID) for the MDS",
|
||||
},
|
||||
addr => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
host => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
state => {
|
||||
type => 'string',
|
||||
description => 'State of the MDS',
|
||||
},
|
||||
standby_replay => {
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
description =>
|
||||
'If true, the standby MDS is polling the active MDS for faster recovery (hot standby).',
|
||||
},
|
||||
rank => {
|
||||
type => 'integer',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
|
||||
my $res = [];
|
||||
my $res = [];
|
||||
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
my $rados = PVE::RADOS->new();
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
my $rados = PVE::RADOS->new();
|
||||
|
||||
my $mds_hash = PVE::Ceph::Services::get_services_info("mds", $cfg, $rados);
|
||||
my $mds_hash = PVE::Ceph::Services::get_services_info("mds", $cfg, $rados);
|
||||
|
||||
my $mds_state = PVE::Ceph::Services::get_cluster_mds_state($rados);
|
||||
foreach my $name (keys %$mds_state) {
|
||||
my $d = $mds_state->{$name};
|
||||
# just overwrite, this always provides more info
|
||||
$mds_hash->{$name}->{$_} = $d->{$_} for keys %$d;
|
||||
}
|
||||
my $mds_state = PVE::Ceph::Services::get_cluster_mds_state($rados);
|
||||
foreach my $name (keys %$mds_state) {
|
||||
my $d = $mds_state->{$name};
|
||||
# just overwrite, this always provides more info
|
||||
$mds_hash->{$name}->{$_} = $d->{$_} for keys %$d;
|
||||
}
|
||||
|
||||
return PVE::RESTHandler::hash_to_array($mds_hash, 'name');
|
||||
}
|
||||
return PVE::RESTHandler::hash_to_array($mds_hash, 'name');
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'createmds',
|
||||
path => '{name}',
|
||||
method => 'POST',
|
||||
|
|
@ -94,94 +95,95 @@ __PACKAGE__->register_method ({
|
|||
proxyto => 'node',
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
name => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
default => 'nodename',
|
||||
pattern => PVE::Ceph::Services::SERVICE_REGEX,
|
||||
maxLength => 200,
|
||||
description => "The ID for the mds, when omitted the same as the nodename",
|
||||
},
|
||||
hotstandby => {
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => '0',
|
||||
description => "Determines whether a ceph-mds daemon should poll and replay the log of an active MDS. ".
|
||||
"Faster switch on MDS failure, but needs more idle resources.",
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
name => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
default => 'nodename',
|
||||
pattern => PVE::Ceph::Services::SERVICE_REGEX,
|
||||
maxLength => 200,
|
||||
description => "The ID for the mds, when omitted the same as the nodename",
|
||||
},
|
||||
hotstandby => {
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => '0',
|
||||
description =>
|
||||
"Determines whether a ceph-mds daemon should poll and replay the log of an active MDS. "
|
||||
. "Faster switch on MDS failure, but needs more idle resources.",
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'string' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_installed('ceph_mds');
|
||||
PVE::Ceph::Tools::check_ceph_installed('ceph_mds');
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $nodename = $param->{node};
|
||||
$nodename = INotify::nodename() if $nodename eq 'localhost';
|
||||
my $nodename = $param->{node};
|
||||
$nodename = INotify::nodename() if $nodename eq 'localhost';
|
||||
|
||||
my $mds_id = $param->{name} // $nodename;
|
||||
my $mds_id = $param->{name} // $nodename;
|
||||
|
||||
die "ID of the MDS cannot start with a number!\n" if ($mds_id =~ /^[0-9]/);
|
||||
die "ID of the MDS cannot start with a number!\n" if ($mds_id =~ /^[0-9]/);
|
||||
|
||||
my $worker = sub {
|
||||
my $timeout = PVE::Ceph::Tools::get_config('long_rados_timeout');
|
||||
my $rados = PVE::RADOS->new(timeout => $timeout);
|
||||
my $worker = sub {
|
||||
my $timeout = PVE::Ceph::Tools::get_config('long_rados_timeout');
|
||||
my $rados = PVE::RADOS->new(timeout => $timeout);
|
||||
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
|
||||
my $section = "mds.$mds_id";
|
||||
my $section = "mds.$mds_id";
|
||||
|
||||
if (defined($cfg->{$section})) {
|
||||
die "MDS '$mds_id' already referenced in ceph config, abort!\n"
|
||||
}
|
||||
if (defined($cfg->{$section})) {
|
||||
die "MDS '$mds_id' already referenced in ceph config, abort!\n";
|
||||
}
|
||||
|
||||
if (!defined($cfg->{mds}->{keyring})) {
|
||||
# $id isn't a perl variable but a ceph metavariable
|
||||
my $keyring = '/var/lib/ceph/mds/ceph-$id/keyring';
|
||||
if (!defined($cfg->{mds}->{keyring})) {
|
||||
# $id isn't a perl variable but a ceph metavariable
|
||||
my $keyring = '/var/lib/ceph/mds/ceph-$id/keyring';
|
||||
|
||||
$cfg->{mds}->{keyring} = $keyring;
|
||||
}
|
||||
$cfg->{mds}->{keyring} = $keyring;
|
||||
}
|
||||
|
||||
$cfg->{$section}->{host} = $nodename;
|
||||
$cfg->{$section}->{'mds_standby_for_name'} = 'pve';
|
||||
$cfg->{$section}->{host} = $nodename;
|
||||
$cfg->{$section}->{'mds_standby_for_name'} = 'pve';
|
||||
|
||||
if ($param->{hotstandby}) {
|
||||
$cfg->{$section}->{'mds_standby_replay'} = 'true';
|
||||
}
|
||||
if ($param->{hotstandby}) {
|
||||
$cfg->{$section}->{'mds_standby_replay'} = 'true';
|
||||
}
|
||||
|
||||
cfs_write_file('ceph.conf', $cfg);
|
||||
cfs_write_file('ceph.conf', $cfg);
|
||||
|
||||
eval { PVE::Ceph::Services::create_mds($mds_id, $rados) };
|
||||
if (my $err = $@) {
|
||||
# we abort early if the section is defined, so we know that we
|
||||
# wrote it at this point. Do not auto remove the service, could
|
||||
# do real harm for previously manual setup MDS
|
||||
warn "Encountered error, remove '$section' from ceph.conf\n";
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
delete $cfg->{$section};
|
||||
cfs_write_file('ceph.conf', $cfg);
|
||||
eval { PVE::Ceph::Services::create_mds($mds_id, $rados) };
|
||||
if (my $err = $@) {
|
||||
# we abort early if the section is defined, so we know that we
|
||||
# wrote it at this point. Do not auto remove the service, could
|
||||
# do real harm for previously manual setup MDS
|
||||
warn "Encountered error, remove '$section' from ceph.conf\n";
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
delete $cfg->{$section};
|
||||
cfs_write_file('ceph.conf', $cfg);
|
||||
|
||||
die "$err\n";
|
||||
}
|
||||
};
|
||||
die "$err\n";
|
||||
}
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker('cephcreatemds', "mds.$mds_id", $authuser, $worker);
|
||||
}
|
||||
return $rpcenv->fork_worker('cephcreatemds', "mds.$mds_id", $authuser, $worker);
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'destroymds',
|
||||
path => '{name}',
|
||||
method => 'DELETE',
|
||||
|
|
@ -189,47 +191,47 @@ __PACKAGE__->register_method ({
|
|||
proxyto => 'node',
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
name => {
|
||||
description => 'The name (ID) of the mds',
|
||||
type => 'string',
|
||||
pattern => PVE::Ceph::Services::SERVICE_REGEX,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
name => {
|
||||
description => 'The name (ID) of the mds',
|
||||
type => 'string',
|
||||
pattern => PVE::Ceph::Services::SERVICE_REGEX,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'string' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
|
||||
my $mds_id = $param->{name};
|
||||
my $mds_id = $param->{name};
|
||||
|
||||
my $worker = sub {
|
||||
my $timeout = PVE::Ceph::Tools::get_config('long_rados_timeout');
|
||||
my $rados = PVE::RADOS->new(timeout => $timeout);
|
||||
my $worker = sub {
|
||||
my $timeout = PVE::Ceph::Tools::get_config('long_rados_timeout');
|
||||
my $rados = PVE::RADOS->new(timeout => $timeout);
|
||||
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
|
||||
if (defined($cfg->{"mds.$mds_id"})) {
|
||||
delete $cfg->{"mds.$mds_id"};
|
||||
cfs_write_file('ceph.conf', $cfg);
|
||||
}
|
||||
if (defined($cfg->{"mds.$mds_id"})) {
|
||||
delete $cfg->{"mds.$mds_id"};
|
||||
cfs_write_file('ceph.conf', $cfg);
|
||||
}
|
||||
|
||||
PVE::Ceph::Services::destroy_mds($mds_id, $rados);
|
||||
};
|
||||
PVE::Ceph::Services::destroy_mds($mds_id, $rados);
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker('cephdestroymds', "mds.$mds_id", $authuser, $worker);
|
||||
}
|
||||
return $rpcenv->fork_worker('cephdestroymds', "mds.$mds_id", $authuser, $worker);
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -15,72 +15,72 @@ use PVE::Tools qw(run_command);
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "MGR directory index.",
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
|
||||
check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
|
||||
},
|
||||
proxyto => 'node',
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
name => {
|
||||
description => "The name (ID) for the MGR",
|
||||
},
|
||||
addr => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
host => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
state => {
|
||||
type => 'string',
|
||||
description => 'State of the MGR',
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
name => {
|
||||
description => "The name (ID) for the MGR",
|
||||
},
|
||||
addr => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
host => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
state => {
|
||||
type => 'string',
|
||||
description => 'State of the MGR',
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
|
||||
my $res = [];
|
||||
my $res = [];
|
||||
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
my $rados = PVE::RADOS->new();
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
my $rados = PVE::RADOS->new();
|
||||
|
||||
my $mgr_hash = PVE::Ceph::Services::get_services_info("mgr", $cfg, $rados);
|
||||
my $mgr_hash = PVE::Ceph::Services::get_services_info("mgr", $cfg, $rados);
|
||||
|
||||
my $mgr_dump = $rados->mon_command({ prefix => 'mgr dump' });
|
||||
my $mgr_dump = $rados->mon_command({ prefix => 'mgr dump' });
|
||||
|
||||
my $active_name = $mgr_dump->{active_name};
|
||||
$mgr_hash->{$active_name}->{state} = 'active' if $active_name;
|
||||
my $active_name = $mgr_dump->{active_name};
|
||||
$mgr_hash->{$active_name}->{state} = 'active' if $active_name;
|
||||
|
||||
foreach my $mgr (@{$mgr_dump->{standbys}}) {
|
||||
$mgr_hash->{$mgr->{name}}->{state} = 'standby';
|
||||
}
|
||||
foreach my $mgr (@{ $mgr_dump->{standbys} }) {
|
||||
$mgr_hash->{ $mgr->{name} }->{state} = 'standby';
|
||||
}
|
||||
|
||||
return PVE::RESTHandler::hash_to_array($mgr_hash, 'name');
|
||||
}
|
||||
return PVE::RESTHandler::hash_to_array($mgr_hash, 'name');
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'createmgr',
|
||||
path => '{id}',
|
||||
method => 'POST',
|
||||
|
|
@ -88,47 +88,48 @@ __PACKAGE__->register_method ({
|
|||
proxyto => 'node',
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
id => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
pattern => PVE::Ceph::Services::SERVICE_REGEX,
|
||||
maxLength => 200,
|
||||
description => "The ID for the manager, when omitted the same as the nodename",
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
id => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
pattern => PVE::Ceph::Services::SERVICE_REGEX,
|
||||
maxLength => 200,
|
||||
description => "The ID for the manager, when omitted the same as the nodename",
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'string' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_installed('ceph_mgr');
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::setup_pve_symlinks();
|
||||
PVE::Ceph::Tools::check_ceph_installed('ceph_mgr');
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::setup_pve_symlinks();
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $mgrid = $param->{id} // $param->{node};
|
||||
my $mgrid = $param->{id} // $param->{node};
|
||||
|
||||
my $worker = sub {
|
||||
my $upid = shift;
|
||||
my $worker = sub {
|
||||
my $upid = shift;
|
||||
|
||||
my $rados_timeout = PVE::Ceph::Tools::get_config('long_rados_timeout');
|
||||
my $rados = PVE::RADOS->new(timeout => $rados_timeout);
|
||||
my $rados_timeout = PVE::Ceph::Tools::get_config('long_rados_timeout');
|
||||
my $rados = PVE::RADOS->new(timeout => $rados_timeout);
|
||||
|
||||
PVE::Ceph::Services::create_mgr($mgrid, $rados);
|
||||
};
|
||||
PVE::Ceph::Services::create_mgr($mgrid, $rados);
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker('cephcreatemgr', "mgr.$mgrid", $authuser, $worker);
|
||||
}});
|
||||
return $rpcenv->fork_worker('cephcreatemgr', "mgr.$mgrid", $authuser, $worker);
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'destroymgr',
|
||||
path => '{id}',
|
||||
method => 'DELETE',
|
||||
|
|
@ -136,36 +137,37 @@ __PACKAGE__->register_method ({
|
|||
proxyto => 'node',
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
id => {
|
||||
description => 'The ID of the manager',
|
||||
type => 'string',
|
||||
pattern => PVE::Ceph::Services::SERVICE_REGEX,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
id => {
|
||||
description => 'The ID of the manager',
|
||||
type => 'string',
|
||||
pattern => PVE::Ceph::Services::SERVICE_REGEX,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'string' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
|
||||
my $mgrid = $param->{id};
|
||||
my $mgrid = $param->{id};
|
||||
|
||||
my $worker = sub {
|
||||
my $upid = shift;
|
||||
my $worker = sub {
|
||||
my $upid = shift;
|
||||
|
||||
PVE::Ceph::Services::destroy_mgr($mgrid);
|
||||
};
|
||||
PVE::Ceph::Services::destroy_mgr($mgrid);
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker('cephdestroymgr', "mgr.$mgrid", $authuser, $worker);
|
||||
}});
|
||||
return $rpcenv->fork_worker('cephdestroymgr', "mgr.$mgrid", $authuser, $worker);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,67 +23,73 @@ use base qw(PVE::RESTHandler);
|
|||
my $find_mon_ips = sub {
|
||||
my ($cfg, $rados, $node, $mon_address) = @_;
|
||||
|
||||
my $overwrite_ips = [ PVE::Tools::split_list($mon_address) ];
|
||||
my $overwrite_ips = [PVE::Tools::split_list($mon_address)];
|
||||
$overwrite_ips = PVE::Network::unique_ips($overwrite_ips);
|
||||
|
||||
my $pubnet;
|
||||
if ($rados) {
|
||||
$pubnet = $rados->mon_command({ prefix => "config get" , who => "mon.",
|
||||
key => "public_network", format => 'plain' });
|
||||
# if not defined in the db, the result is empty, it is also always
|
||||
# followed by a newline
|
||||
($pubnet) = $pubnet =~ m/^(\S+)$/;
|
||||
$pubnet = $rados->mon_command({
|
||||
prefix => "config get",
|
||||
who => "mon.",
|
||||
key => "public_network",
|
||||
format => 'plain',
|
||||
});
|
||||
# if not defined in the db, the result is empty, it is also always
|
||||
# followed by a newline
|
||||
($pubnet) = $pubnet =~ m/^(\S+)$/;
|
||||
}
|
||||
$pubnet //= $cfg->{global}->{public_network};
|
||||
|
||||
if (!$pubnet) {
|
||||
if (scalar(@{$overwrite_ips})) {
|
||||
return $overwrite_ips;
|
||||
} else {
|
||||
# don't refactor into '[ PVE::Cluster::remote... ]' as it uses wantarray
|
||||
my $ip = PVE::Cluster::remote_node_ip($node);
|
||||
return [ $ip ];
|
||||
}
|
||||
if (scalar(@{$overwrite_ips})) {
|
||||
return $overwrite_ips;
|
||||
} else {
|
||||
# don't refactor into '[ PVE::Cluster::remote... ]' as it uses wantarray
|
||||
my $ip = PVE::Cluster::remote_node_ip($node);
|
||||
return [$ip];
|
||||
}
|
||||
}
|
||||
|
||||
my $public_nets = [ PVE::Tools::split_list($pubnet) ];
|
||||
my $public_nets = [PVE::Tools::split_list($pubnet)];
|
||||
if (scalar(@{$public_nets}) > 1) {
|
||||
warn "Multiple Ceph public networks detected on $node: $pubnet\n";
|
||||
warn "Networks must be capable of routing to each other.\n";
|
||||
warn "Multiple Ceph public networks detected on $node: $pubnet\n";
|
||||
warn "Networks must be capable of routing to each other.\n";
|
||||
}
|
||||
|
||||
my $res = [];
|
||||
|
||||
if (!scalar(@{$overwrite_ips})) { # auto-select one address for each public network
|
||||
for my $net (@{$public_nets}) {
|
||||
my $allowed_ips = PVE::Network::get_local_ip_from_cidr($net);
|
||||
$allowed_ips = PVE::Network::unique_ips($allowed_ips);
|
||||
for my $net (@{$public_nets}) {
|
||||
my $allowed_ips = PVE::Network::get_local_ip_from_cidr($net);
|
||||
$allowed_ips = PVE::Network::unique_ips($allowed_ips);
|
||||
|
||||
die "No active IP found for the requested ceph public network '$net' on node '$node'\n"
|
||||
if scalar(@$allowed_ips) < 1;
|
||||
die
|
||||
"No active IP found for the requested ceph public network '$net' on node '$node'\n"
|
||||
if scalar(@$allowed_ips) < 1;
|
||||
|
||||
if (scalar(@$allowed_ips) == 1) {
|
||||
push @{$res}, $allowed_ips->[0];
|
||||
} else {
|
||||
die "Multiple IPs for ceph public network '$net' detected on $node:\n".
|
||||
join("\n", @$allowed_ips) ."\nuse 'mon-address' to specify one of them.\n";
|
||||
}
|
||||
}
|
||||
if (scalar(@$allowed_ips) == 1) {
|
||||
push @{$res}, $allowed_ips->[0];
|
||||
} else {
|
||||
die "Multiple IPs for ceph public network '$net' detected on $node:\n"
|
||||
. join("\n", @$allowed_ips)
|
||||
. "\nuse 'mon-address' to specify one of them.\n";
|
||||
}
|
||||
}
|
||||
} else { # check if overwrite IPs are active and in any of the public networks
|
||||
my $allowed_list = [];
|
||||
my $allowed_list = [];
|
||||
|
||||
for my $net (@{$public_nets}) {
|
||||
push @{$allowed_list}, @{PVE::Network::get_local_ip_from_cidr($net)};
|
||||
}
|
||||
for my $net (@{$public_nets}) {
|
||||
push @{$allowed_list}, @{ PVE::Network::get_local_ip_from_cidr($net) };
|
||||
}
|
||||
|
||||
my $allowed_ips = PVE::Network::unique_ips($allowed_list);
|
||||
my $allowed_ips = PVE::Network::unique_ips($allowed_list);
|
||||
|
||||
for my $overwrite_ip (@{$overwrite_ips}) {
|
||||
die "Specified monitor IP '$overwrite_ip' not configured or up on $node!\n"
|
||||
if !grep { $_ eq $overwrite_ip } @{$allowed_ips};
|
||||
for my $overwrite_ip (@{$overwrite_ips}) {
|
||||
die "Specified monitor IP '$overwrite_ip' not configured or up on $node!\n"
|
||||
if !grep { $_ eq $overwrite_ip } @{$allowed_ips};
|
||||
|
||||
push @{$res}, $overwrite_ip;
|
||||
}
|
||||
push @{$res}, $overwrite_ip;
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
|
|
@ -97,17 +103,17 @@ my $ips_from_mon_host = sub {
|
|||
my @hosts = PVE::Tools::split_list($mon_host);
|
||||
|
||||
for my $host (@hosts) {
|
||||
$host =~ s|^\[?v\d+\:||; # remove beginning of vector
|
||||
$host =~ s|/\d+\]?||; # remove end of vector
|
||||
$host =~ s|^\[?v\d+\:||; # remove beginning of vector
|
||||
$host =~ s|/\d+\]?||; # remove end of vector
|
||||
|
||||
($host) = PVE::Tools::parse_host_and_port($host);
|
||||
next if !defined($host);
|
||||
($host) = PVE::Tools::parse_host_and_port($host);
|
||||
next if !defined($host);
|
||||
|
||||
# filter out hostnames
|
||||
my $ip = PVE::JSONSchema::pve_verify_ip($host, 1);
|
||||
next if !defined($ip);
|
||||
# filter out hostnames
|
||||
my $ip = PVE::JSONSchema::pve_verify_ip($host, 1);
|
||||
next if !defined($ip);
|
||||
|
||||
push @{$ips}, $ip;
|
||||
push @{$ips}, $ip;
|
||||
}
|
||||
|
||||
return $ips;
|
||||
|
|
@ -121,26 +127,26 @@ my $assert_mon_prerequisites = sub {
|
|||
my $mon_host_ips = $ips_from_mon_host->($cfg->{global}->{mon_host});
|
||||
|
||||
for my $mon_host_ip (@{$mon_host_ips}) {
|
||||
my $ip = PVE::Network::canonical_ip($mon_host_ip);
|
||||
$used_ips->{$ip} = 1;
|
||||
my $ip = PVE::Network::canonical_ip($mon_host_ip);
|
||||
$used_ips->{$ip} = 1;
|
||||
}
|
||||
|
||||
for my $mon (values %{$monhash}) {
|
||||
next if !defined($mon->{addr});
|
||||
next if !defined($mon->{addr});
|
||||
|
||||
for my $ip ($ips_from_mon_host->($mon->{addr})->@*) {
|
||||
$ip = PVE::Network::canonical_ip($ip);
|
||||
$used_ips->{$ip} = 1;
|
||||
}
|
||||
for my $ip ($ips_from_mon_host->($mon->{addr})->@*) {
|
||||
$ip = PVE::Network::canonical_ip($ip);
|
||||
$used_ips->{$ip} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
for my $monip (@{$monips}) {
|
||||
$monip = PVE::Network::canonical_ip($monip);
|
||||
die "monitor address '$monip' already in use\n" if $used_ips->{$monip};
|
||||
$monip = PVE::Network::canonical_ip($monip);
|
||||
die "monitor address '$monip' already in use\n" if $used_ips->{$monip};
|
||||
}
|
||||
|
||||
if (defined($monhash->{$monid})) {
|
||||
die "monitor '$monid' already exists\n";
|
||||
die "monitor '$monid' already exists\n";
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -148,13 +154,15 @@ my $assert_mon_can_remove = sub {
|
|||
my ($monhash, $monlist, $monid, $mondir) = @_;
|
||||
|
||||
if (
|
||||
!defined($monhash->{$monid} ||
|
||||
grep { defined($_->{name}) && $_->{name} eq $monid } $monlist->@*)
|
||||
!defined(
|
||||
$monhash->{$monid}
|
||||
|| grep { defined($_->{name}) && $_->{name} eq $monid } $monlist->@*
|
||||
)
|
||||
) {
|
||||
die "no such monitor id '$monid'\n"
|
||||
die "no such monitor id '$monid'\n";
|
||||
}
|
||||
|
||||
die "monitor filesystem '$mondir' does not exist on this node\n" if ! -d $mondir;
|
||||
die "monitor filesystem '$mondir' does not exist on this node\n" if !-d $mondir;
|
||||
die "can't remove last monitor\n" if scalar($monlist->@*) <= 1;
|
||||
};
|
||||
|
||||
|
|
@ -178,8 +186,8 @@ my $remove_addr_from_mon_host = sub {
|
|||
|
||||
# ipv6 only without brackets
|
||||
if ($addr =~ m/^\[?(.*?:.*?)\]?$/) {
|
||||
$addr = $1;
|
||||
$monhost =~ s/(^|[ ,;]+)\Q$addr\E(?:[ ,;]+|$)/$1/;
|
||||
$addr = $1;
|
||||
$monhost =~ s/(^|[ ,;]+)\Q$addr\E(?:[ ,;]+|$)/$1/;
|
||||
}
|
||||
|
||||
# remove trailing separators
|
||||
|
|
@ -188,7 +196,7 @@ my $remove_addr_from_mon_host = sub {
|
|||
return $monhost;
|
||||
};
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'listmon',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
|
|
@ -196,73 +204,74 @@ __PACKAGE__->register_method ({
|
|||
proxyto => 'node',
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
|
||||
check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
addr => { type => 'string', optional => 1 },
|
||||
ceph_version => { type => 'string', optional => 1 },
|
||||
ceph_version_short => { type => 'string', optional => 1 },
|
||||
direxists => { type => 'string', optional => 1 },
|
||||
host => { type => 'boolean', optional => 1 },
|
||||
name => { type => 'string' },
|
||||
quorum => { type => 'boolean', optional => 1 },
|
||||
rank => { type => 'integer', optional => 1 },
|
||||
service => { type => 'integer', optional => 1 },
|
||||
state => { type => 'string', optional => 1 },
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
addr => { type => 'string', optional => 1 },
|
||||
ceph_version => { type => 'string', optional => 1 },
|
||||
ceph_version_short => { type => 'string', optional => 1 },
|
||||
direxists => { type => 'string', optional => 1 },
|
||||
host => { type => 'boolean', optional => 1 },
|
||||
name => { type => 'string' },
|
||||
quorum => { type => 'boolean', optional => 1 },
|
||||
rank => { type => 'integer', optional => 1 },
|
||||
service => { type => 'integer', optional => 1 },
|
||||
state => { type => 'string', optional => 1 },
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
|
||||
my $res = [];
|
||||
my $res = [];
|
||||
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
|
||||
my $rados = eval { PVE::RADOS->new() };
|
||||
warn $@ if $@;
|
||||
my $monhash = PVE::Ceph::Services::get_services_info("mon", $cfg, $rados);
|
||||
my $rados = eval { PVE::RADOS->new() };
|
||||
warn $@ if $@;
|
||||
my $monhash = PVE::Ceph::Services::get_services_info("mon", $cfg, $rados);
|
||||
|
||||
if ($rados) {
|
||||
my $monstat = $rados->mon_command({ prefix => 'quorum_status' });
|
||||
if ($rados) {
|
||||
my $monstat = $rados->mon_command({ prefix => 'quorum_status' });
|
||||
|
||||
my $mons = $monstat->{monmap}->{mons};
|
||||
foreach my $d (@$mons) {
|
||||
next if !defined($d->{name});
|
||||
my $name = $d->{name};
|
||||
$monhash->{$name}->{rank} = $d->{rank};
|
||||
$monhash->{$name}->{addr} = $d->{addr};
|
||||
if (grep { $_ eq $d->{rank} } @{$monstat->{quorum}}) {
|
||||
$monhash->{$name}->{quorum} = 1;
|
||||
$monhash->{$name}->{state} = 'running';
|
||||
}
|
||||
}
|
||||
my $mons = $monstat->{monmap}->{mons};
|
||||
foreach my $d (@$mons) {
|
||||
next if !defined($d->{name});
|
||||
my $name = $d->{name};
|
||||
$monhash->{$name}->{rank} = $d->{rank};
|
||||
$monhash->{$name}->{addr} = $d->{addr};
|
||||
if (grep { $_ eq $d->{rank} } @{ $monstat->{quorum} }) {
|
||||
$monhash->{$name}->{quorum} = 1;
|
||||
$monhash->{$name}->{state} = 'running';
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
# we cannot check the status if we do not have a RADOS
|
||||
# object, so set the state to unknown
|
||||
foreach my $monid (sort keys %$monhash) {
|
||||
$monhash->{$monid}->{state} = 'unknown';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
# we cannot check the status if we do not have a RADOS
|
||||
# object, so set the state to unknown
|
||||
foreach my $monid (sort keys %$monhash) {
|
||||
$monhash->{$monid}->{state} = 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
return PVE::RESTHandler::hash_to_array($monhash, 'name');
|
||||
}});
|
||||
return PVE::RESTHandler::hash_to_array($monhash, 'name');
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'createmon',
|
||||
path => '{monid}',
|
||||
method => 'POST',
|
||||
|
|
@ -270,216 +279,230 @@ __PACKAGE__->register_method ({
|
|||
proxyto => 'node',
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
monid => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
pattern => PVE::Ceph::Services::SERVICE_REGEX,
|
||||
maxLength => 200,
|
||||
description => "The ID for the monitor, when omitted the same as the nodename",
|
||||
},
|
||||
'mon-address' => {
|
||||
description => 'Overwrites autodetected monitor IP address(es). ' .
|
||||
'Must be in the public network(s) of Ceph.',
|
||||
type => 'string', format => 'ip-list',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
monid => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
pattern => PVE::Ceph::Services::SERVICE_REGEX,
|
||||
maxLength => 200,
|
||||
description => "The ID for the monitor, when omitted the same as the nodename",
|
||||
},
|
||||
'mon-address' => {
|
||||
description => 'Overwrites autodetected monitor IP address(es). '
|
||||
. 'Must be in the public network(s) of Ceph.',
|
||||
type => 'string',
|
||||
format => 'ip-list',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'string' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_installed('ceph_mon');
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::setup_pve_symlinks();
|
||||
PVE::Ceph::Tools::check_ceph_installed('ceph_mon');
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::setup_pve_symlinks();
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
my $rados = eval { PVE::RADOS->new() }; # try a rados connection, fails for first monitor
|
||||
my $monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados);
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
my $rados = eval { PVE::RADOS->new() }; # try a rados connection, fails for first monitor
|
||||
my $monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados);
|
||||
|
||||
my $is_first_monitor = !(scalar(keys %$monhash) || $cfg->{global}->{mon_host});
|
||||
my $is_first_monitor = !(scalar(keys %$monhash) || $cfg->{global}->{mon_host});
|
||||
|
||||
if (!defined($rados) && !$is_first_monitor) {
|
||||
die "Could not connect to ceph cluster despite configured monitors\n";
|
||||
}
|
||||
if (!defined($rados) && !$is_first_monitor) {
|
||||
die "Could not connect to ceph cluster despite configured monitors\n";
|
||||
}
|
||||
|
||||
my $monid = $param->{monid} // $param->{node};
|
||||
my $monsection = "mon.$monid";
|
||||
my $ips = $find_mon_ips->($cfg, $rados, $param->{node}, $param->{'mon-address'});
|
||||
my $monid = $param->{monid} // $param->{node};
|
||||
my $monsection = "mon.$monid";
|
||||
my $ips = $find_mon_ips->($cfg, $rados, $param->{node}, $param->{'mon-address'});
|
||||
|
||||
$assert_mon_prerequisites->($cfg, $monhash, $monid, $ips);
|
||||
$assert_mon_prerequisites->($cfg, $monhash, $monid, $ips);
|
||||
|
||||
my $worker = sub {
|
||||
my $upid = shift;
|
||||
my $worker = sub {
|
||||
my $upid = shift;
|
||||
|
||||
PVE::Cluster::cfs_lock_file('ceph.conf', undef, sub {
|
||||
# update cfg content and reassert prereqs inside the lock
|
||||
$cfg = cfs_read_file('ceph.conf');
|
||||
# reopen with longer timeout
|
||||
if (defined($rados)) {
|
||||
$rados = PVE::RADOS->new(timeout => PVE::Ceph::Tools::get_config('long_rados_timeout'));
|
||||
}
|
||||
$monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados);
|
||||
$assert_mon_prerequisites->($cfg, $monhash, $monid, $ips);
|
||||
PVE::Cluster::cfs_lock_file(
|
||||
'ceph.conf',
|
||||
undef,
|
||||
sub {
|
||||
# update cfg content and reassert prereqs inside the lock
|
||||
$cfg = cfs_read_file('ceph.conf');
|
||||
# reopen with longer timeout
|
||||
if (defined($rados)) {
|
||||
$rados = PVE::RADOS->new(
|
||||
timeout => PVE::Ceph::Tools::get_config('long_rados_timeout'));
|
||||
}
|
||||
$monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados);
|
||||
$assert_mon_prerequisites->($cfg, $monhash, $monid, $ips);
|
||||
|
||||
my $client_keyring = PVE::Ceph::Tools::get_or_create_admin_keyring();
|
||||
my $mon_keyring = PVE::Ceph::Tools::get_config('pve_mon_key_path');
|
||||
my $client_keyring = PVE::Ceph::Tools::get_or_create_admin_keyring();
|
||||
my $mon_keyring = PVE::Ceph::Tools::get_config('pve_mon_key_path');
|
||||
|
||||
if (! -f $mon_keyring) {
|
||||
print "creating new monitor keyring\n";
|
||||
run_command([
|
||||
'ceph-authtool',
|
||||
'--create-keyring',
|
||||
$mon_keyring,
|
||||
'--gen-key',
|
||||
'-n',
|
||||
'mon.',
|
||||
'--cap',
|
||||
'mon',
|
||||
'allow *',
|
||||
]);
|
||||
run_command([
|
||||
'ceph-authtool',
|
||||
$mon_keyring,
|
||||
'--import-keyring',
|
||||
$client_keyring,
|
||||
]);
|
||||
}
|
||||
if (!-f $mon_keyring) {
|
||||
print "creating new monitor keyring\n";
|
||||
run_command([
|
||||
'ceph-authtool',
|
||||
'--create-keyring',
|
||||
$mon_keyring,
|
||||
'--gen-key',
|
||||
'-n',
|
||||
'mon.',
|
||||
'--cap',
|
||||
'mon',
|
||||
'allow *',
|
||||
]);
|
||||
run_command([
|
||||
'ceph-authtool', $mon_keyring, '--import-keyring', $client_keyring,
|
||||
]);
|
||||
}
|
||||
|
||||
my $ccname = PVE::Ceph::Tools::get_config('ccname');
|
||||
my $mondir = "/var/lib/ceph/mon/$ccname-$monid";
|
||||
-d $mondir && die "monitor filesystem '$mondir' already exist\n";
|
||||
my $ccname = PVE::Ceph::Tools::get_config('ccname');
|
||||
my $mondir = "/var/lib/ceph/mon/$ccname-$monid";
|
||||
-d $mondir && die "monitor filesystem '$mondir' already exist\n";
|
||||
|
||||
my $monmap = "/tmp/monmap";
|
||||
my $monmap = "/tmp/monmap";
|
||||
|
||||
eval {
|
||||
mkdir $mondir;
|
||||
eval {
|
||||
mkdir $mondir;
|
||||
|
||||
run_command(['chown', 'ceph:ceph', $mondir]);
|
||||
run_command(['chown', 'ceph:ceph', $mondir]);
|
||||
|
||||
my $is_first_address = !defined($rados);
|
||||
my $is_first_address = !defined($rados);
|
||||
|
||||
my $monaddrs = [];
|
||||
my $monaddrs = [];
|
||||
|
||||
for my $ip (@{$ips}) {
|
||||
if (Net::IP::ip_is_ipv6($ip)) {
|
||||
$cfg->{global}->{ms_bind_ipv6} = 'true';
|
||||
$cfg->{global}->{ms_bind_ipv4} = 'false' if $is_first_address;
|
||||
} else {
|
||||
$cfg->{global}->{ms_bind_ipv4} = 'true';
|
||||
$cfg->{global}->{ms_bind_ipv6} = 'false' if $is_first_address;
|
||||
}
|
||||
for my $ip (@{$ips}) {
|
||||
if (Net::IP::ip_is_ipv6($ip)) {
|
||||
$cfg->{global}->{ms_bind_ipv6} = 'true';
|
||||
$cfg->{global}->{ms_bind_ipv4} = 'false' if $is_first_address;
|
||||
} else {
|
||||
$cfg->{global}->{ms_bind_ipv4} = 'true';
|
||||
$cfg->{global}->{ms_bind_ipv6} = 'false' if $is_first_address;
|
||||
}
|
||||
|
||||
my $monaddr = Net::IP::ip_is_ipv6($ip) ? "[$ip]" : $ip;
|
||||
push @{$monaddrs}, "v2:$monaddr:3300";
|
||||
push @{$monaddrs}, "v1:$monaddr:6789";
|
||||
my $monaddr = Net::IP::ip_is_ipv6($ip) ? "[$ip]" : $ip;
|
||||
push @{$monaddrs}, "v2:$monaddr:3300";
|
||||
push @{$monaddrs}, "v1:$monaddr:6789";
|
||||
|
||||
$is_first_address = 0;
|
||||
}
|
||||
$is_first_address = 0;
|
||||
}
|
||||
|
||||
my $monmaptool_cmd = [
|
||||
'monmaptool',
|
||||
'--clobber',
|
||||
'--addv',
|
||||
$monid,
|
||||
"[" . join(',', @{$monaddrs}) . "]",
|
||||
'--print',
|
||||
$monmap,
|
||||
];
|
||||
my $monmaptool_cmd = [
|
||||
'monmaptool',
|
||||
'--clobber',
|
||||
'--addv',
|
||||
$monid,
|
||||
"[" . join(',', @{$monaddrs}) . "]",
|
||||
'--print',
|
||||
$monmap,
|
||||
];
|
||||
|
||||
if (defined($rados)) { # we can only have a RADOS object if we have a monitor
|
||||
my $mapdata = $rados->mon_command({ prefix => 'mon getmap', format => 'plain' });
|
||||
file_set_contents($monmap, $mapdata);
|
||||
run_command($monmaptool_cmd);
|
||||
} else { # we need to create a monmap for the first monitor
|
||||
push @{$monmaptool_cmd}, '--create';
|
||||
run_command($monmaptool_cmd);
|
||||
}
|
||||
if (defined($rados)) { # we can only have a RADOS object if we have a monitor
|
||||
my $mapdata =
|
||||
$rados->mon_command({ prefix => 'mon getmap', format => 'plain' });
|
||||
file_set_contents($monmap, $mapdata);
|
||||
run_command($monmaptool_cmd);
|
||||
} else { # we need to create a monmap for the first monitor
|
||||
push @{$monmaptool_cmd}, '--create';
|
||||
run_command($monmaptool_cmd);
|
||||
}
|
||||
|
||||
run_command([
|
||||
'ceph-mon',
|
||||
'--mkfs',
|
||||
'-i',
|
||||
$monid,
|
||||
'--monmap',
|
||||
$monmap,
|
||||
'--keyring',
|
||||
$mon_keyring,
|
||||
]);
|
||||
run_command(['chown', 'ceph:ceph', '-R', $mondir]);
|
||||
};
|
||||
my $err = $@;
|
||||
unlink $monmap;
|
||||
if ($err) {
|
||||
File::Path::remove_tree($mondir);
|
||||
die $err;
|
||||
}
|
||||
run_command([
|
||||
'ceph-mon',
|
||||
'--mkfs',
|
||||
'-i',
|
||||
$monid,
|
||||
'--monmap',
|
||||
$monmap,
|
||||
'--keyring',
|
||||
$mon_keyring,
|
||||
]);
|
||||
run_command(['chown', 'ceph:ceph', '-R', $mondir]);
|
||||
};
|
||||
my $err = $@;
|
||||
unlink $monmap;
|
||||
if ($err) {
|
||||
File::Path::remove_tree($mondir);
|
||||
die $err;
|
||||
}
|
||||
|
||||
# update ceph.conf
|
||||
my $monhost = $cfg->{global}->{mon_host} // "";
|
||||
# add all known monitor ips to mon_host if it does not exist
|
||||
if (!defined($cfg->{global}->{mon_host})) {
|
||||
for my $mon (sort keys %$monhash) {
|
||||
$monhost .= " " . $monhash->{$mon}->{addr};
|
||||
}
|
||||
}
|
||||
$monhost .= " " . join(' ', @{$ips});
|
||||
$cfg->{global}->{mon_host} = $monhost;
|
||||
# The IP is needed in the ceph.conf for the first boot
|
||||
$cfg->{$monsection}->{public_addr} = $ips->[0];
|
||||
# update ceph.conf
|
||||
my $monhost = $cfg->{global}->{mon_host} // "";
|
||||
# add all known monitor ips to mon_host if it does not exist
|
||||
if (!defined($cfg->{global}->{mon_host})) {
|
||||
for my $mon (sort keys %$monhash) {
|
||||
$monhost .= " " . $monhash->{$mon}->{addr};
|
||||
}
|
||||
}
|
||||
$monhost .= " " . join(' ', @{$ips});
|
||||
$cfg->{global}->{mon_host} = $monhost;
|
||||
# The IP is needed in the ceph.conf for the first boot
|
||||
$cfg->{$monsection}->{public_addr} = $ips->[0];
|
||||
|
||||
cfs_write_file('ceph.conf', $cfg);
|
||||
cfs_write_file('ceph.conf', $cfg);
|
||||
|
||||
PVE::Ceph::Services::ceph_service_cmd('start', $monsection);
|
||||
PVE::Ceph::Services::ceph_service_cmd('start', $monsection);
|
||||
|
||||
if ($is_first_monitor) {
|
||||
print "created the first monitor, assume it's safe to disable insecure global"
|
||||
." ID reclaim for new setup\n";
|
||||
eval {
|
||||
run_command(
|
||||
['ceph', 'config', 'set', 'mon', 'auth_allow_insecure_global_id_reclaim', 'false'],
|
||||
errfunc => sub { print STDERR "$_[0]\n" },
|
||||
)
|
||||
};
|
||||
warn "$@" if $@;
|
||||
if ($is_first_monitor) {
|
||||
print
|
||||
"created the first monitor, assume it's safe to disable insecure global"
|
||||
. " ID reclaim for new setup\n";
|
||||
eval {
|
||||
run_command(
|
||||
[
|
||||
'ceph',
|
||||
'config',
|
||||
'set',
|
||||
'mon',
|
||||
'auth_allow_insecure_global_id_reclaim',
|
||||
'false',
|
||||
],
|
||||
errfunc => sub { print STDERR "$_[0]\n" },
|
||||
);
|
||||
};
|
||||
warn "$@" if $@;
|
||||
|
||||
print "Configuring keyring for ceph-crash.service\n";
|
||||
eval {
|
||||
PVE::Ceph::Tools::create_or_update_crash_keyring_file();
|
||||
$cfg->{'client.crash'}->{keyring} = '/etc/pve/ceph/$cluster.$name.keyring';
|
||||
cfs_write_file('ceph.conf', $cfg);
|
||||
};
|
||||
warn "Unable to configure keyring for ceph-crash.service: $@" if $@;
|
||||
}
|
||||
print "Configuring keyring for ceph-crash.service\n";
|
||||
eval {
|
||||
PVE::Ceph::Tools::create_or_update_crash_keyring_file();
|
||||
$cfg->{'client.crash'}->{keyring} =
|
||||
'/etc/pve/ceph/$cluster.$name.keyring';
|
||||
cfs_write_file('ceph.conf', $cfg);
|
||||
};
|
||||
warn "Unable to configure keyring for ceph-crash.service: $@" if $@;
|
||||
}
|
||||
|
||||
eval { PVE::Ceph::Services::ceph_service_cmd('enable', $monsection) };
|
||||
warn "Enable ceph-mon\@${monid}.service failed, do manually: $@\n" if $@;
|
||||
eval { PVE::Ceph::Services::ceph_service_cmd('enable', $monsection) };
|
||||
warn "Enable ceph-mon\@${monid}.service failed, do manually: $@\n" if $@;
|
||||
|
||||
PVE::Ceph::Services::broadcast_ceph_services();
|
||||
});
|
||||
die $@ if $@;
|
||||
# automatically create manager after the first monitor is created
|
||||
if ($is_first_monitor) {
|
||||
PVE::API2::Ceph::MGR->createmgr({
|
||||
node => $param->{node},
|
||||
id => $param->{node}
|
||||
})
|
||||
}
|
||||
};
|
||||
PVE::Ceph::Services::broadcast_ceph_services();
|
||||
},
|
||||
);
|
||||
die $@ if $@;
|
||||
# automatically create manager after the first monitor is created
|
||||
if ($is_first_monitor) {
|
||||
PVE::API2::Ceph::MGR->createmgr({
|
||||
node => $param->{node},
|
||||
id => $param->{node},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker('cephcreatemon', $monsection, $authuser, $worker);
|
||||
}});
|
||||
return $rpcenv->fork_worker('cephcreatemon', $monsection, $authuser, $worker);
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'destroymon',
|
||||
path => '{monid}',
|
||||
method => 'DELETE',
|
||||
|
|
@ -487,122 +510,130 @@ __PACKAGE__->register_method ({
|
|||
proxyto => 'node',
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
monid => {
|
||||
description => 'Monitor ID',
|
||||
type => 'string',
|
||||
pattern => PVE::Ceph::Services::SERVICE_REGEX,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
monid => {
|
||||
description => 'Monitor ID',
|
||||
type => 'string',
|
||||
pattern => PVE::Ceph::Services::SERVICE_REGEX,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'string' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
|
||||
my $monid = $param->{monid};
|
||||
my $monsection = "mon.$monid";
|
||||
my $monid = $param->{monid};
|
||||
my $monsection = "mon.$monid";
|
||||
|
||||
my $rados = PVE::RADOS->new();
|
||||
my $monstat = $rados->mon_command({ prefix => 'quorum_status' });
|
||||
my $monlist = $monstat->{monmap}->{mons};
|
||||
my $monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados);
|
||||
my $rados = PVE::RADOS->new();
|
||||
my $monstat = $rados->mon_command({ prefix => 'quorum_status' });
|
||||
my $monlist = $monstat->{monmap}->{mons};
|
||||
my $monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados);
|
||||
|
||||
my $ccname = PVE::Ceph::Tools::get_config('ccname');
|
||||
my $mondir = "/var/lib/ceph/mon/$ccname-$monid";
|
||||
my $ccname = PVE::Ceph::Tools::get_config('ccname');
|
||||
my $mondir = "/var/lib/ceph/mon/$ccname-$monid";
|
||||
|
||||
$assert_mon_can_remove->($monhash, $monlist, $monid, $mondir);
|
||||
$assert_mon_can_remove->($monhash, $monlist, $monid, $mondir);
|
||||
|
||||
my $worker = sub {
|
||||
my $upid = shift;
|
||||
PVE::Cluster::cfs_lock_file('ceph.conf', undef, sub {
|
||||
# reload info and recheck
|
||||
$cfg = cfs_read_file('ceph.conf');
|
||||
my $worker = sub {
|
||||
my $upid = shift;
|
||||
PVE::Cluster::cfs_lock_file(
|
||||
'ceph.conf',
|
||||
undef,
|
||||
sub {
|
||||
# reload info and recheck
|
||||
$cfg = cfs_read_file('ceph.conf');
|
||||
|
||||
# reopen with longer timeout
|
||||
$rados = PVE::RADOS->new(timeout => PVE::Ceph::Tools::get_config('long_rados_timeout'));
|
||||
$monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados);
|
||||
$monstat = $rados->mon_command({ prefix => 'quorum_status' });
|
||||
$monlist = $monstat->{monmap}->{mons};
|
||||
# reopen with longer timeout
|
||||
$rados = PVE::RADOS->new(
|
||||
timeout => PVE::Ceph::Tools::get_config('long_rados_timeout'));
|
||||
$monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados);
|
||||
$monstat = $rados->mon_command({ prefix => 'quorum_status' });
|
||||
$monlist = $monstat->{monmap}->{mons};
|
||||
|
||||
my $addrs = [];
|
||||
my $addrs = [];
|
||||
|
||||
my $add_addr = sub {
|
||||
my ($addr) = @_;
|
||||
my $add_addr = sub {
|
||||
my ($addr) = @_;
|
||||
|
||||
# extract the ip without port and nonce (if present)
|
||||
($addr) = $addr =~ m|^(.*):\d+(/\d+)?$|;
|
||||
($addr) = $addr =~ m|^\[?(.*?)\]?$|; # remove brackets
|
||||
push @{$addrs}, $addr;
|
||||
};
|
||||
# extract the ip without port and nonce (if present)
|
||||
($addr) = $addr =~ m|^(.*):\d+(/\d+)?$|;
|
||||
($addr) = $addr =~ m|^\[?(.*?)\]?$|; # remove brackets
|
||||
push @{$addrs}, $addr;
|
||||
};
|
||||
|
||||
for my $mon (@$monlist) {
|
||||
if ($mon->{name} eq $monid) {
|
||||
if ($mon->{public_addrs} && $mon->{public_addrs}->{addrvec}) {
|
||||
my $addrvec = $mon->{public_addrs}->{addrvec};
|
||||
for my $addr (@{$addrvec}) {
|
||||
$add_addr->($addr->{addr});
|
||||
}
|
||||
} else {
|
||||
$add_addr->($mon->{public_addr} // $mon->{addr});
|
||||
}
|
||||
last;
|
||||
}
|
||||
}
|
||||
for my $mon (@$monlist) {
|
||||
if ($mon->{name} eq $monid) {
|
||||
if ($mon->{public_addrs} && $mon->{public_addrs}->{addrvec}) {
|
||||
my $addrvec = $mon->{public_addrs}->{addrvec};
|
||||
for my $addr (@{$addrvec}) {
|
||||
$add_addr->($addr->{addr});
|
||||
}
|
||||
} else {
|
||||
$add_addr->($mon->{public_addr} // $mon->{addr});
|
||||
}
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
$assert_mon_can_remove->($monhash, $monlist, $monid, $mondir);
|
||||
$assert_mon_can_remove->($monhash, $monlist, $monid, $mondir);
|
||||
|
||||
# this also stops the service
|
||||
$rados->mon_command({ prefix => "mon remove", name => $monid, format => 'plain' });
|
||||
# this also stops the service
|
||||
$rados->mon_command(
|
||||
{ prefix => "mon remove", name => $monid, format => 'plain' });
|
||||
|
||||
# delete section
|
||||
delete $cfg->{$monsection};
|
||||
# delete section
|
||||
delete $cfg->{$monsection};
|
||||
|
||||
# delete from mon_host
|
||||
if (my $monhost = $cfg->{global}->{mon_host}) {
|
||||
my $mon_host_ips = $ips_from_mon_host->($cfg->{global}->{mon_host});
|
||||
# delete from mon_host
|
||||
if (my $monhost = $cfg->{global}->{mon_host}) {
|
||||
my $mon_host_ips = $ips_from_mon_host->($cfg->{global}->{mon_host});
|
||||
|
||||
for my $addr (@{$addrs}) {
|
||||
$monhost = $remove_addr_from_mon_host->($monhost, $addr);
|
||||
for my $addr (@{$addrs}) {
|
||||
$monhost = $remove_addr_from_mon_host->($monhost, $addr);
|
||||
|
||||
# also remove matching IPs that differ syntactically
|
||||
if (PVE::JSONSchema::pve_verify_ip($addr, 1)) {
|
||||
$addr = PVE::Network::canonical_ip($addr);
|
||||
# also remove matching IPs that differ syntactically
|
||||
if (PVE::JSONSchema::pve_verify_ip($addr, 1)) {
|
||||
$addr = PVE::Network::canonical_ip($addr);
|
||||
|
||||
for my $mon_host_ip (@{$mon_host_ips}) {
|
||||
# match canonical addresses, but remove as present in mon_host
|
||||
if (PVE::Network::canonical_ip($mon_host_ip) eq $addr) {
|
||||
$monhost = $remove_addr_from_mon_host->($monhost, $mon_host_ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$cfg->{global}->{mon_host} = $monhost;
|
||||
}
|
||||
for my $mon_host_ip (@{$mon_host_ips}) {
|
||||
# match canonical addresses, but remove as present in mon_host
|
||||
if (PVE::Network::canonical_ip($mon_host_ip) eq $addr) {
|
||||
$monhost =
|
||||
$remove_addr_from_mon_host->($monhost, $mon_host_ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$cfg->{global}->{mon_host} = $monhost;
|
||||
}
|
||||
|
||||
cfs_write_file('ceph.conf', $cfg);
|
||||
File::Path::remove_tree($mondir);
|
||||
eval { PVE::Ceph::Services::ceph_service_cmd('disable', $monsection) };
|
||||
warn $@ if $@;
|
||||
PVE::Ceph::Services::broadcast_ceph_services();
|
||||
});
|
||||
cfs_write_file('ceph.conf', $cfg);
|
||||
File::Path::remove_tree($mondir);
|
||||
eval { PVE::Ceph::Services::ceph_service_cmd('disable', $monsection) };
|
||||
warn $@ if $@;
|
||||
PVE::Ceph::Services::broadcast_ceph_services();
|
||||
},
|
||||
);
|
||||
|
||||
die $@ if $@;
|
||||
};
|
||||
die $@ if $@;
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker('cephdestroymon', $monsection, $authuser, $worker);
|
||||
}});
|
||||
return $rpcenv->fork_worker('cephdestroymon', $monsection, $authuser, $worker);
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
1677
PVE/API2/Ceph/OSD.pm
1677
PVE/API2/Ceph/OSD.pm
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -5,51 +5,48 @@ use warnings;
|
|||
|
||||
use PVE::API2::ACME;
|
||||
use PVE::Certificate;
|
||||
use PVE::CertHelpers;;
|
||||
use PVE::CertHelpers;
|
||||
use PVE::Exception qw(raise_param_exc);
|
||||
use PVE::JSONSchema qw(get_standard_option);
|
||||
use PVE::Tools qw(extract_param file_get_contents file_set_contents);
|
||||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::ACME",
|
||||
path => 'acme',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
permissions => { user => 'all' },
|
||||
description => "Node index.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
return [
|
||||
{ name => 'acme' },
|
||||
{ name => 'custom' },
|
||||
{ name => 'info' },
|
||||
];
|
||||
return [
|
||||
{ name => 'acme' }, { name => 'custom' }, { name => 'info' },
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'info',
|
||||
path => 'info',
|
||||
method => 'GET',
|
||||
|
|
@ -57,157 +54,159 @@ __PACKAGE__->register_method ({
|
|||
proxyto => 'node',
|
||||
description => "Get information about node's certificates.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => get_standard_option('pve-certificate-info'),
|
||||
type => 'array',
|
||||
items => get_standard_option('pve-certificate-info'),
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $node_path = "/etc/pve/nodes/$param->{node}";
|
||||
my $node_path = "/etc/pve/nodes/$param->{node}";
|
||||
|
||||
my $res = [];
|
||||
my $cert_paths = [
|
||||
'/etc/pve/pve-root-ca.pem',
|
||||
"$node_path/pve-ssl.pem",
|
||||
"$node_path/pveproxy-ssl.pem",
|
||||
];
|
||||
for my $path (@$cert_paths) {
|
||||
eval {
|
||||
my $info = PVE::Certificate::get_certificate_info($path);
|
||||
push @$res, $info if $info;
|
||||
};
|
||||
}
|
||||
return $res;
|
||||
my $res = [];
|
||||
my $cert_paths = [
|
||||
'/etc/pve/pve-root-ca.pem', "$node_path/pve-ssl.pem", "$node_path/pveproxy-ssl.pem",
|
||||
];
|
||||
for my $path (@$cert_paths) {
|
||||
eval {
|
||||
my $info = PVE::Certificate::get_certificate_info($path);
|
||||
push @$res, $info if $info;
|
||||
};
|
||||
}
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'upload_custom_cert',
|
||||
path => 'custom',
|
||||
method => 'POST',
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
|
||||
},
|
||||
description => 'Upload or update custom certificate chain and key.',
|
||||
protected => 1,
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
certificates => {
|
||||
type => 'string',
|
||||
format => 'pem-certificate-chain',
|
||||
description => 'PEM encoded certificate (chain).',
|
||||
},
|
||||
key => {
|
||||
type => 'string',
|
||||
description => 'PEM encoded private key.',
|
||||
format => 'pem-string',
|
||||
optional => 1,
|
||||
},
|
||||
force => {
|
||||
type => 'boolean',
|
||||
description => 'Overwrite existing custom or ACME certificate files.',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
restart => {
|
||||
type => 'boolean',
|
||||
description => 'Restart pveproxy.',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
certificates => {
|
||||
type => 'string',
|
||||
format => 'pem-certificate-chain',
|
||||
description => 'PEM encoded certificate (chain).',
|
||||
},
|
||||
key => {
|
||||
type => 'string',
|
||||
description => 'PEM encoded private key.',
|
||||
format => 'pem-string',
|
||||
optional => 1,
|
||||
},
|
||||
force => {
|
||||
type => 'boolean',
|
||||
description => 'Overwrite existing custom or ACME certificate files.',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
restart => {
|
||||
type => 'boolean',
|
||||
description => 'Restart pveproxy.',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => get_standard_option('pve-certificate-info'),
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $node = extract_param($param, 'node');
|
||||
my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node);
|
||||
my $node = extract_param($param, 'node');
|
||||
my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node);
|
||||
|
||||
my $certs = extract_param($param, 'certificates');
|
||||
$certs = PVE::Certificate::strip_leading_text($certs);
|
||||
my $certs = extract_param($param, 'certificates');
|
||||
$certs = PVE::Certificate::strip_leading_text($certs);
|
||||
|
||||
my $key = extract_param($param, 'key');
|
||||
if ($key) {
|
||||
$key = PVE::Certificate::strip_leading_text($key);
|
||||
} else {
|
||||
raise_param_exc({'key' => "Attempted to upload custom certificate without (existing) key."})
|
||||
if ! -e "${cert_prefix}.key";
|
||||
}
|
||||
my $key = extract_param($param, 'key');
|
||||
if ($key) {
|
||||
$key = PVE::Certificate::strip_leading_text($key);
|
||||
} else {
|
||||
raise_param_exc(
|
||||
{ 'key' => "Attempted to upload custom certificate without (existing) key." })
|
||||
if !-e "${cert_prefix}.key";
|
||||
}
|
||||
|
||||
my $info;
|
||||
my $info;
|
||||
|
||||
my $code = sub {
|
||||
print "Setting custom certificate files\n";
|
||||
$info = PVE::CertHelpers::set_cert_files($certs, $key, $cert_prefix, $param->{force});
|
||||
my $code = sub {
|
||||
print "Setting custom certificate files\n";
|
||||
$info =
|
||||
PVE::CertHelpers::set_cert_files($certs, $key, $cert_prefix, $param->{force});
|
||||
|
||||
if ($param->{restart}) {
|
||||
print "Restarting pveproxy\n";
|
||||
PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']);
|
||||
}
|
||||
};
|
||||
if ($param->{restart}) {
|
||||
print "Restarting pveproxy\n";
|
||||
PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']);
|
||||
}
|
||||
};
|
||||
|
||||
PVE::CertHelpers::cert_lock(10, $code);
|
||||
die "$@\n" if $@;
|
||||
PVE::CertHelpers::cert_lock(10, $code);
|
||||
die "$@\n" if $@;
|
||||
|
||||
return $info;
|
||||
}});
|
||||
return $info;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'remove_custom_cert',
|
||||
path => 'custom',
|
||||
method => 'DELETE',
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
|
||||
},
|
||||
description => 'DELETE custom certificate chain and key.',
|
||||
protected => 1,
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
restart => {
|
||||
type => 'boolean',
|
||||
description => 'Restart pveproxy.',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
restart => {
|
||||
type => 'boolean',
|
||||
description => 'Restart pveproxy.',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'null',
|
||||
type => 'null',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $node = extract_param($param, 'node');
|
||||
my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node);
|
||||
my $node = extract_param($param, 'node');
|
||||
my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node);
|
||||
|
||||
my $code = sub {
|
||||
print "Deleting custom certificate files\n";
|
||||
unlink "${cert_prefix}.pem";
|
||||
unlink "${cert_prefix}.key";
|
||||
my $code = sub {
|
||||
print "Deleting custom certificate files\n";
|
||||
unlink "${cert_prefix}.pem";
|
||||
unlink "${cert_prefix}.key";
|
||||
|
||||
if ($param->{restart}) {
|
||||
print "Restarting pveproxy\n";
|
||||
PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']);
|
||||
}
|
||||
};
|
||||
if ($param->{restart}) {
|
||||
print "Restarting pveproxy\n";
|
||||
PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']);
|
||||
}
|
||||
};
|
||||
|
||||
PVE::CertHelpers::cert_lock(10, $code);
|
||||
die "$@\n" if $@;
|
||||
PVE::CertHelpers::cert_lock(10, $code);
|
||||
die "$@\n" if $@;
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
1250
PVE/API2/Cluster.pm
1250
PVE/API2/Cluster.pm
File diff suppressed because it is too large
Load diff
|
|
@ -25,8 +25,8 @@ sub get_included_vmids {
|
|||
|
||||
my $all_vmids = {};
|
||||
for my $job ($legacy_jobs->@*, grep { $_->{type} eq 'vzdump' } values $jobs->{ids}->%*) {
|
||||
my $job_included_guests = PVE::VZDump::get_included_guests($job);
|
||||
$all_vmids->{$_} = 1 for map { $_->@* } values %{$job_included_guests};
|
||||
my $job_included_guests = PVE::VZDump::get_included_guests($job);
|
||||
$all_vmids->{$_} = 1 for map { $_->@* } values %{$job_included_guests};
|
||||
}
|
||||
|
||||
return $all_vmids;
|
||||
|
|
@ -38,28 +38,29 @@ __PACKAGE__->register_method({
|
|||
method => 'GET',
|
||||
description => "Index for backup info related endpoints",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
description => 'Directory index.',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
subdir => {
|
||||
type => 'string',
|
||||
description => 'API sub-directory endpoint',
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{subdir}" } ],
|
||||
type => 'array',
|
||||
description => 'Directory index.',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
subdir => {
|
||||
type => 'string',
|
||||
description => 'API sub-directory endpoint',
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{subdir}" }],
|
||||
},
|
||||
code => sub {
|
||||
return [
|
||||
{ subdir => 'not-backed-up' },
|
||||
];
|
||||
}});
|
||||
return [
|
||||
{ subdir => 'not-backed-up' },
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method({
|
||||
name => 'get_guests_not_in_backup',
|
||||
|
|
@ -68,72 +69,75 @@ __PACKAGE__->register_method({
|
|||
protected => 1,
|
||||
description => "Shows all guests which are not covered by any backup job.",
|
||||
permissions => {
|
||||
check => ['perm', '/', ['Sys.Audit']],
|
||||
check => ['perm', '/', ['Sys.Audit']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
description => 'Contains the guest objects.',
|
||||
items => {
|
||||
type => 'object',
|
||||
properties => {
|
||||
vmid => {
|
||||
type => 'integer',
|
||||
description => 'VMID of the guest.',
|
||||
},
|
||||
name => {
|
||||
type => 'string',
|
||||
description => 'Name of the guest',
|
||||
optional => 1,
|
||||
},
|
||||
type => {
|
||||
type => 'string',
|
||||
description => 'Type of the guest.',
|
||||
enum => ['qemu', 'lxc'],
|
||||
},
|
||||
},
|
||||
},
|
||||
type => 'array',
|
||||
description => 'Contains the guest objects.',
|
||||
items => {
|
||||
type => 'object',
|
||||
properties => {
|
||||
vmid => {
|
||||
type => 'integer',
|
||||
description => 'VMID of the guest.',
|
||||
},
|
||||
name => {
|
||||
type => 'string',
|
||||
description => 'Name of the guest',
|
||||
optional => 1,
|
||||
},
|
||||
type => {
|
||||
type => 'string',
|
||||
description => 'Type of the guest.',
|
||||
enum => ['qemu', 'lxc'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
|
||||
my $included_vmids = get_included_vmids();
|
||||
my $vmlist = PVE::Cluster::get_vmlist();
|
||||
my $included_vmids = get_included_vmids();
|
||||
my $vmlist = PVE::Cluster::get_vmlist();
|
||||
|
||||
# remove VMIDs to which the user has no permission to not leak infos like the guest name
|
||||
my @allowed_vmids = grep { $rpcenv->check($user, "/vms/$_", [ 'VM.Audit' ], 1) } keys $vmlist->{ids}->%*;
|
||||
# remove VMIDs to which the user has no permission to not leak infos like the guest name
|
||||
my @allowed_vmids =
|
||||
grep { $rpcenv->check($user, "/vms/$_", ['VM.Audit'], 1) } keys $vmlist->{ids}->%*;
|
||||
|
||||
my $result = [];
|
||||
for my $vmid (@allowed_vmids) {
|
||||
next if $included_vmids->{$vmid};
|
||||
my $result = [];
|
||||
for my $vmid (@allowed_vmids) {
|
||||
next if $included_vmids->{$vmid};
|
||||
|
||||
my ($type, $node) = $vmlist->{ids}->{$vmid}->@{'type', 'node'};
|
||||
my ($type, $node) = $vmlist->{ids}->{$vmid}->@{ 'type', 'node' };
|
||||
|
||||
my ($conf, $name);
|
||||
if ($type eq 'qemu') {
|
||||
$conf = PVE::QemuConfig->load_config($vmid, $node);
|
||||
$name = $conf->{name};
|
||||
} elsif ($type eq 'lxc') {
|
||||
$conf = PVE::LXC::Config->load_config($vmid, $node);
|
||||
$name = $conf->{hostname};
|
||||
} else {
|
||||
die "Unexpected error: unknown guest type for VMID $vmid, neither QEMU nor LXC\n";
|
||||
}
|
||||
my ($conf, $name);
|
||||
if ($type eq 'qemu') {
|
||||
$conf = PVE::QemuConfig->load_config($vmid, $node);
|
||||
$name = $conf->{name};
|
||||
} elsif ($type eq 'lxc') {
|
||||
$conf = PVE::LXC::Config->load_config($vmid, $node);
|
||||
$name = $conf->{hostname};
|
||||
} else {
|
||||
die
|
||||
"Unexpected error: unknown guest type for VMID $vmid, neither QEMU nor LXC\n";
|
||||
}
|
||||
|
||||
my $entry = {
|
||||
vmid => int($vmid),
|
||||
type => $type,
|
||||
};
|
||||
$entry->{name} = $name if defined($name);
|
||||
my $entry = {
|
||||
vmid => int($vmid),
|
||||
type => $type,
|
||||
};
|
||||
$entry->{name} = $name if defined($name);
|
||||
|
||||
push @{$result}, $entry;
|
||||
}
|
||||
push @{$result}, $entry;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}});
|
||||
return $result;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
139
PVE/API2/Cluster/BulkActions.pm
Normal file
139
PVE/API2/Cluster/BulkActions.pm
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
package PVE::API2::Cluster::BulkActions;
|
||||
|
||||
use warnings;
|
||||
use strict;
|
||||
|
||||
use Storable qw(dclone);
|
||||
use JSON;
|
||||
|
||||
use PVE::Exception qw(raise_param_exc);
|
||||
use PVE::Tools qw(extract_param);
|
||||
use PVE::JSONSchema qw(get_standard_option);
|
||||
use PVE::RESTHandler;
|
||||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => 'Index for cluster-wide bulk-action API endpoints.',
|
||||
permissions => { user => 'all' },
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => 'object',
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => '{name}' } ],
|
||||
},
|
||||
code => sub {
|
||||
my $result = [
|
||||
{ name => 'migrate' },
|
||||
{ name => 'start' },
|
||||
{ name => 'stop' },
|
||||
];
|
||||
|
||||
return $result;
|
||||
}
|
||||
});
|
||||
|
||||
my $guest_format = {
|
||||
vmid => {
|
||||
defau
|
||||
},
|
||||
};
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
name => 'migrate',
|
||||
path => 'migrate',
|
||||
method => 'POST',
|
||||
description => 'Returns a list of all entities that can be used as notification targets' .
|
||||
' (endpoints and groups).',
|
||||
permissions => {
|
||||
description => "The 'VM.Migrate' permission is required on '/' or on '/vms/<ID>' for each "
|
||||
."ID passed via the 'vms' parameter.",
|
||||
user => 'all',
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
'guests' => {
|
||||
type => 'array',
|
||||
description => '',
|
||||
items => {
|
||||
type => 'string',
|
||||
format => $guest_format,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'string',
|
||||
items => {
|
||||
type => 'object',
|
||||
properties => {
|
||||
name => {
|
||||
description => 'Name of the endpoint/group.',
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
'type' => {
|
||||
description => 'Type of the endpoint or group.',
|
||||
type => 'string',
|
||||
enum => [qw(sendmail gotify group)],
|
||||
},
|
||||
'comment' => {
|
||||
description => 'Comment',
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => '{name}' } ],
|
||||
},
|
||||
code => sub {
|
||||
my $config = PVE::Notify::read_config();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
|
||||
my $targets = eval {
|
||||
my $result = [];
|
||||
|
||||
for my $target (@{$config->get_sendmail_endpoints()}) {
|
||||
push @$result, {
|
||||
name => $target->{name},
|
||||
comment => $target->{comment},
|
||||
type => 'sendmail',
|
||||
};
|
||||
}
|
||||
|
||||
for my $target (@{$config->get_gotify_endpoints()}) {
|
||||
push @$result, {
|
||||
name => $target->{name},
|
||||
comment => $target->{comment},
|
||||
type => 'gotify',
|
||||
};
|
||||
}
|
||||
|
||||
for my $target (@{$config->get_groups()}) {
|
||||
push @$result, {
|
||||
name => $target->{name},
|
||||
comment => $target->{comment},
|
||||
type => 'group',
|
||||
};
|
||||
}
|
||||
|
||||
$result
|
||||
};
|
||||
|
||||
raise_api_error($@) if $@;
|
||||
|
||||
return filter_entities_by_privs($rpcenv, $targets);
|
||||
}
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
@ -17,296 +17,297 @@ use PVE::Tools qw(extract_param);
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'cephindex',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "Cluster ceph index.",
|
||||
permissions => { user => 'all' },
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $result = [
|
||||
{ name => 'metadata' },
|
||||
{ name => 'status' },
|
||||
{ name => 'flags' },
|
||||
];
|
||||
my $result = [
|
||||
{ name => 'metadata' }, { name => 'status' }, { name => 'flags' },
|
||||
];
|
||||
|
||||
return $result;
|
||||
}
|
||||
return $result;
|
||||
},
|
||||
});
|
||||
|
||||
my $metadata_common_props = {
|
||||
hostname => {
|
||||
type => "string",
|
||||
description => "Hostname on which the service is running.",
|
||||
type => "string",
|
||||
description => "Hostname on which the service is running.",
|
||||
},
|
||||
ceph_release => {
|
||||
type => "string",
|
||||
description => "Ceph release codename currently used.",
|
||||
type => "string",
|
||||
description => "Ceph release codename currently used.",
|
||||
},
|
||||
ceph_version => {
|
||||
type => "string",
|
||||
description => "Version info currently used by the service.",
|
||||
type => "string",
|
||||
description => "Version info currently used by the service.",
|
||||
},
|
||||
ceph_version_short => {
|
||||
type => "string",
|
||||
description => "Short version (numerical) info currently used by the service.",
|
||||
type => "string",
|
||||
description => "Short version (numerical) info currently used by the service.",
|
||||
},
|
||||
mem_total_kb => {
|
||||
type => "integer",
|
||||
description => "Memory consumption of the service.",
|
||||
type => "integer",
|
||||
description => "Memory consumption of the service.",
|
||||
},
|
||||
mem_swap_kb => {
|
||||
type => "integer",
|
||||
description => "Memory of the service currently in swap.",
|
||||
type => "integer",
|
||||
description => "Memory of the service currently in swap.",
|
||||
},
|
||||
};
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'metadata',
|
||||
path => 'metadata',
|
||||
method => 'GET',
|
||||
description => "Get ceph metadata.",
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
|
||||
check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
scope => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
default => 'all',
|
||||
enum => ['all', 'versions', ],
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
scope => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
default => 'all',
|
||||
enum => ['all', 'versions'],
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'object',
|
||||
description => "Items for each type of service containing objects for each instance.",
|
||||
properties => {
|
||||
mds => {
|
||||
type => "object",
|
||||
description => "Metadata servers configured in the cluster and their properties.",
|
||||
properties => {
|
||||
"{id}" => {
|
||||
type => "object",
|
||||
description => "Useful properties are listed, but not the full list.",
|
||||
properties => {
|
||||
addr => {
|
||||
type => "string",
|
||||
description => "Bind addresses and ports.",
|
||||
},
|
||||
name => {
|
||||
type => "string",
|
||||
description => "Name of the service instance.",
|
||||
},
|
||||
%{$metadata_common_props},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mgr => {
|
||||
type => "object",
|
||||
description => "Managers configured in the cluster and their properties.",
|
||||
properties => {
|
||||
"{id}" => {
|
||||
type => "object",
|
||||
description => "Useful properties are listed, but not the full list.",
|
||||
properties => {
|
||||
addr => {
|
||||
type => "string",
|
||||
description => "Bind address",
|
||||
},
|
||||
name => {
|
||||
type => "string",
|
||||
description => "Name of the service instance.",
|
||||
},
|
||||
%{$metadata_common_props},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mon => {
|
||||
type => "object",
|
||||
description => "Monitors configured in the cluster and their properties.",
|
||||
properties => {
|
||||
"{id}" => {
|
||||
type => "object",
|
||||
description => "Useful properties are listed, but not the full list.",
|
||||
properties => {
|
||||
addrs => {
|
||||
type => "string",
|
||||
description => "Bind addresses and ports.",
|
||||
},
|
||||
name => {
|
||||
type => "string",
|
||||
description => "Name of the service instance.",
|
||||
},
|
||||
%{$metadata_common_props},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
node => {
|
||||
type => "object",
|
||||
description => "Ceph version installed on the nodes.",
|
||||
properties => {
|
||||
"{node}" => {
|
||||
type => "object",
|
||||
properties => {
|
||||
buildcommit => {
|
||||
type => "string",
|
||||
description => "GIT commit used for the build.",
|
||||
},
|
||||
version => {
|
||||
type => "object",
|
||||
description => "Version info.",
|
||||
properties => {
|
||||
str => {
|
||||
type => "string",
|
||||
description => "Version as single string.",
|
||||
},
|
||||
parts => {
|
||||
type => "array",
|
||||
description => "major, minor & patch",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
osd => {
|
||||
type => "array",
|
||||
description => "OSDs configured in the cluster and their properties.",
|
||||
properties => {
|
||||
"{id}" => {
|
||||
type => "object",
|
||||
description => "Useful properties are listed, but not the full list.",
|
||||
properties => {
|
||||
id => {
|
||||
type => "integer",
|
||||
description => "OSD ID.",
|
||||
},
|
||||
front_addr => {
|
||||
type => "string",
|
||||
description => "Bind addresses and ports for frontend traffic to OSDs.",
|
||||
},
|
||||
back_addr => {
|
||||
type => "string",
|
||||
description => "Bind addresses and ports for backend inter OSD traffic.",
|
||||
},
|
||||
device_id => {
|
||||
type => "string",
|
||||
description => "Devices used by the OSD.",
|
||||
},
|
||||
osd_data => {
|
||||
type => "string",
|
||||
description => "Path to the OSD data directory.",
|
||||
},
|
||||
osd_objectstore => {
|
||||
type => "string",
|
||||
description => "OSD objectstore type.",
|
||||
},
|
||||
%{$metadata_common_props},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type => 'object',
|
||||
description => "Items for each type of service containing objects for each instance.",
|
||||
properties => {
|
||||
mds => {
|
||||
type => "object",
|
||||
description =>
|
||||
"Metadata servers configured in the cluster and their properties.",
|
||||
properties => {
|
||||
"{id}" => {
|
||||
type => "object",
|
||||
description => "Useful properties are listed, but not the full list.",
|
||||
properties => {
|
||||
addr => {
|
||||
type => "string",
|
||||
description => "Bind addresses and ports.",
|
||||
},
|
||||
name => {
|
||||
type => "string",
|
||||
description => "Name of the service instance.",
|
||||
},
|
||||
%{$metadata_common_props},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mgr => {
|
||||
type => "object",
|
||||
description => "Managers configured in the cluster and their properties.",
|
||||
properties => {
|
||||
"{id}" => {
|
||||
type => "object",
|
||||
description => "Useful properties are listed, but not the full list.",
|
||||
properties => {
|
||||
addr => {
|
||||
type => "string",
|
||||
description => "Bind address",
|
||||
},
|
||||
name => {
|
||||
type => "string",
|
||||
description => "Name of the service instance.",
|
||||
},
|
||||
%{$metadata_common_props},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
mon => {
|
||||
type => "object",
|
||||
description => "Monitors configured in the cluster and their properties.",
|
||||
properties => {
|
||||
"{id}" => {
|
||||
type => "object",
|
||||
description => "Useful properties are listed, but not the full list.",
|
||||
properties => {
|
||||
addrs => {
|
||||
type => "string",
|
||||
description => "Bind addresses and ports.",
|
||||
},
|
||||
name => {
|
||||
type => "string",
|
||||
description => "Name of the service instance.",
|
||||
},
|
||||
%{$metadata_common_props},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
node => {
|
||||
type => "object",
|
||||
description => "Ceph version installed on the nodes.",
|
||||
properties => {
|
||||
"{node}" => {
|
||||
type => "object",
|
||||
properties => {
|
||||
buildcommit => {
|
||||
type => "string",
|
||||
description => "GIT commit used for the build.",
|
||||
},
|
||||
version => {
|
||||
type => "object",
|
||||
description => "Version info.",
|
||||
properties => {
|
||||
str => {
|
||||
type => "string",
|
||||
description => "Version as single string.",
|
||||
},
|
||||
parts => {
|
||||
type => "array",
|
||||
description => "major, minor & patch",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
osd => {
|
||||
type => "array",
|
||||
description => "OSDs configured in the cluster and their properties.",
|
||||
properties => {
|
||||
"{id}" => {
|
||||
type => "object",
|
||||
description => "Useful properties are listed, but not the full list.",
|
||||
properties => {
|
||||
id => {
|
||||
type => "integer",
|
||||
description => "OSD ID.",
|
||||
},
|
||||
front_addr => {
|
||||
type => "string",
|
||||
description =>
|
||||
"Bind addresses and ports for frontend traffic to OSDs.",
|
||||
},
|
||||
back_addr => {
|
||||
type => "string",
|
||||
description =>
|
||||
"Bind addresses and ports for backend inter OSD traffic.",
|
||||
},
|
||||
device_id => {
|
||||
type => "string",
|
||||
description => "Devices used by the OSD.",
|
||||
},
|
||||
osd_data => {
|
||||
type => "string",
|
||||
description => "Path to the OSD data directory.",
|
||||
},
|
||||
osd_objectstore => {
|
||||
type => "string",
|
||||
description => "OSD objectstore type.",
|
||||
},
|
||||
%{$metadata_common_props},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $scope = $param->{scope} // 'all';
|
||||
my $scope = $param->{scope} // 'all';
|
||||
|
||||
my $res = {};
|
||||
my $res = {};
|
||||
|
||||
if (defined(my $versions = PVE::Ceph::Services::get_ceph_versions())) {
|
||||
$res->{node} = $versions;
|
||||
}
|
||||
if (defined(my $versions = PVE::Ceph::Services::get_ceph_versions())) {
|
||||
$res->{node} = $versions;
|
||||
}
|
||||
|
||||
return $res if ($scope eq 'versions');
|
||||
return $res if ($scope eq 'versions');
|
||||
|
||||
# only check now, we want to allow calls with scope 'versions' on non-ceph nodes too!
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
my $rados = PVE::RADOS->new();
|
||||
# only check now, we want to allow calls with scope 'versions' on non-ceph nodes too!
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
my $rados = PVE::RADOS->new();
|
||||
|
||||
for my $type ( qw(mon mgr mds) ) {
|
||||
my $typedata = PVE::Ceph::Services::get_cluster_service($type);
|
||||
my $data = {};
|
||||
for my $host (sort keys %$typedata) {
|
||||
for my $service (sort keys %{$typedata->{$host}}) {
|
||||
$data->{"$service\@$host"} = $typedata->{$host}->{$service};
|
||||
}
|
||||
}
|
||||
for my $type (qw(mon mgr mds)) {
|
||||
my $typedata = PVE::Ceph::Services::get_cluster_service($type);
|
||||
my $data = {};
|
||||
for my $host (sort keys %$typedata) {
|
||||
for my $service (sort keys %{ $typedata->{$host} }) {
|
||||
$data->{"$service\@$host"} = $typedata->{$host}->{$service};
|
||||
}
|
||||
}
|
||||
|
||||
# get data from metadata call and merge 'our' data
|
||||
my $services = $rados->mon_command({ prefix => "$type metadata" });
|
||||
for my $service ( @$services ) {
|
||||
my $hostname = $service->{hostname};
|
||||
next if !defined($hostname); # can happen if node is dead
|
||||
# get data from metadata call and merge 'our' data
|
||||
my $services = $rados->mon_command({ prefix => "$type metadata" });
|
||||
for my $service (@$services) {
|
||||
my $hostname = $service->{hostname};
|
||||
next if !defined($hostname); # can happen if node is dead
|
||||
|
||||
my $servicename = $service->{name} // $service->{id};
|
||||
my $id = "$servicename\@$hostname";
|
||||
my $servicename = $service->{name} // $service->{id};
|
||||
my $id = "$servicename\@$hostname";
|
||||
|
||||
if ($data->{$id}) { # copy values over to the metadata hash
|
||||
for my $k (keys %{$data->{$id}}) {
|
||||
$service->{$k} = $data->{$id}->{$k};
|
||||
}
|
||||
}
|
||||
$data->{$id} = $service;
|
||||
}
|
||||
if ($data->{$id}) { # copy values over to the metadata hash
|
||||
for my $k (keys %{ $data->{$id} }) {
|
||||
$service->{$k} = $data->{$id}->{$k};
|
||||
}
|
||||
}
|
||||
$data->{$id} = $service;
|
||||
}
|
||||
|
||||
$res->{$type} = $data;
|
||||
}
|
||||
$res->{$type} = $data;
|
||||
}
|
||||
|
||||
$res->{osd} = $rados->mon_command({ prefix => "osd metadata" });
|
||||
$res->{osd} = $rados->mon_command({ prefix => "osd metadata" });
|
||||
|
||||
return $res;
|
||||
}
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'status',
|
||||
path => 'status',
|
||||
method => 'GET',
|
||||
description => "Get ceph status.",
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
|
||||
check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => { },
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => { type => 'object' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
|
||||
return PVE::Ceph::Tools::ceph_cluster_status();
|
||||
}
|
||||
return PVE::Ceph::Tools::ceph_cluster_status();
|
||||
},
|
||||
});
|
||||
|
||||
my $possible_flags = PVE::Ceph::Tools::get_possible_osd_flags();
|
||||
my $possible_flags_list = [ sort keys %$possible_flags ];
|
||||
my $possible_flags_list = [sort keys %$possible_flags];
|
||||
|
||||
my $get_current_set_flags = sub {
|
||||
my $rados = shift;
|
||||
|
|
@ -318,198 +319,200 @@ my $get_current_set_flags = sub {
|
|||
return { map { $_ => 1 } PVE::Tools::split_list($setflags) };
|
||||
};
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'get_all_flags',
|
||||
path => 'flags',
|
||||
method => 'GET',
|
||||
description => "get the status of all ceph flags",
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Audit' ]],
|
||||
check => ['perm', '/', ['Sys.Audit']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
additionalProperties => 1,
|
||||
properties => {
|
||||
name => {
|
||||
description => "Flag name.",
|
||||
type => 'string', enum => $possible_flags_list,
|
||||
},
|
||||
description => {
|
||||
description => "Flag description.",
|
||||
type => 'string',
|
||||
},
|
||||
value => {
|
||||
description => "Flag value.",
|
||||
type => 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
additionalProperties => 1,
|
||||
properties => {
|
||||
name => {
|
||||
description => "Flag name.",
|
||||
type => 'string',
|
||||
enum => $possible_flags_list,
|
||||
},
|
||||
description => {
|
||||
description => "Flag description.",
|
||||
type => 'string',
|
||||
},
|
||||
value => {
|
||||
description => "Flag value.",
|
||||
type => 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_configured();
|
||||
PVE::Ceph::Tools::check_ceph_configured();
|
||||
|
||||
my $setflags = $get_current_set_flags->();
|
||||
my $setflags = $get_current_set_flags->();
|
||||
|
||||
my $res = [];
|
||||
foreach my $flag (@$possible_flags_list) {
|
||||
my $el = {
|
||||
name => $flag,
|
||||
description => $possible_flags->{$flag}->{description},
|
||||
value => 0,
|
||||
};
|
||||
my $res = [];
|
||||
foreach my $flag (@$possible_flags_list) {
|
||||
my $el = {
|
||||
name => $flag,
|
||||
description => $possible_flags->{$flag}->{description},
|
||||
value => 0,
|
||||
};
|
||||
|
||||
my $realflag = PVE::Ceph::Tools::get_real_flag_name($flag);
|
||||
if ($setflags->{$realflag}) {
|
||||
$el->{value} = 1;
|
||||
}
|
||||
my $realflag = PVE::Ceph::Tools::get_real_flag_name($flag);
|
||||
if ($setflags->{$realflag}) {
|
||||
$el->{value} = 1;
|
||||
}
|
||||
|
||||
push @$res, $el;
|
||||
}
|
||||
push @$res, $el;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'set_flags',
|
||||
path => 'flags',
|
||||
method => 'PUT',
|
||||
description => "Set/Unset multiple ceph flags at once.",
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => $possible_flags,
|
||||
additionalProperties => 0,
|
||||
properties => $possible_flags,
|
||||
},
|
||||
returns => { type => 'string' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
PVE::Ceph::Tools::check_ceph_configured();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
PVE::Ceph::Tools::check_ceph_configured();
|
||||
|
||||
my $worker = sub {
|
||||
my $rados = PVE::RADOS->new(); # (re-)open for forked worker
|
||||
my $worker = sub {
|
||||
my $rados = PVE::RADOS->new(); # (re-)open for forked worker
|
||||
|
||||
my $setflags = $get_current_set_flags->($rados);
|
||||
my $setflags = $get_current_set_flags->($rados);
|
||||
|
||||
my $errors = 0;
|
||||
foreach my $flag (@$possible_flags_list) {
|
||||
next if !defined($param->{$flag});
|
||||
my $val = $param->{$flag};
|
||||
my $realflag = PVE::Ceph::Tools::get_real_flag_name($flag);
|
||||
my $errors = 0;
|
||||
foreach my $flag (@$possible_flags_list) {
|
||||
next if !defined($param->{$flag});
|
||||
my $val = $param->{$flag};
|
||||
my $realflag = PVE::Ceph::Tools::get_real_flag_name($flag);
|
||||
|
||||
next if !$val == !$setflags->{$realflag}; # we do not set/unset flags to the same state
|
||||
next if !$val == !$setflags->{$realflag}; # we do not set/unset flags to the same state
|
||||
|
||||
my $prefix = $val ? 'set' : 'unset';
|
||||
eval {
|
||||
print "$prefix $flag\n";
|
||||
$rados->mon_command({ prefix => "osd $prefix", key => $flag, });
|
||||
};
|
||||
if (my $err = $@) {
|
||||
warn "error with $flag: '$err'\n";
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
my $prefix = $val ? 'set' : 'unset';
|
||||
eval {
|
||||
print "$prefix $flag\n";
|
||||
$rados->mon_command({ prefix => "osd $prefix", key => $flag });
|
||||
};
|
||||
if (my $err = $@) {
|
||||
warn "error with $flag: '$err'\n";
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
die "could not set/unset $errors flags\n";
|
||||
}
|
||||
};
|
||||
if ($errors) {
|
||||
die "could not set/unset $errors flags\n";
|
||||
}
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker('cephsetflags', undef, $user, $worker);
|
||||
}});
|
||||
return $rpcenv->fork_worker('cephsetflags', undef, $user, $worker);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'get_flag',
|
||||
path => 'flags/{flag}',
|
||||
method => 'GET',
|
||||
description => "Get the status of a specific ceph flag.",
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Audit' ]],
|
||||
check => ['perm', '/', ['Sys.Audit']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
flag => {
|
||||
description => "The name of the flag name to get.",
|
||||
type => 'string', enum => $possible_flags_list,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
flag => {
|
||||
description => "The name of the flag name to get.",
|
||||
type => 'string',
|
||||
enum => $possible_flags_list,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'boolean',
|
||||
type => 'boolean',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_configured();
|
||||
PVE::Ceph::Tools::check_ceph_configured();
|
||||
|
||||
my $realflag = PVE::Ceph::Tools::get_real_flag_name($param->{flag});
|
||||
my $realflag = PVE::Ceph::Tools::get_real_flag_name($param->{flag});
|
||||
|
||||
my $setflags = $get_current_set_flags->();
|
||||
if ($setflags->{$realflag}) {
|
||||
return 1;
|
||||
}
|
||||
my $setflags = $get_current_set_flags->();
|
||||
if ($setflags->{$realflag}) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}});
|
||||
return 0;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'update_flag',
|
||||
path => 'flags/{flag}',
|
||||
method => 'PUT',
|
||||
description => "Set or clear (unset) a specific ceph flag",
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
flag => {
|
||||
description => 'The ceph flag to update',
|
||||
type => 'string',
|
||||
enum => $possible_flags_list,
|
||||
},
|
||||
value => {
|
||||
description => 'The new value of the flag',
|
||||
type => 'boolean',
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
flag => {
|
||||
description => 'The ceph flag to update',
|
||||
type => 'string',
|
||||
enum => $possible_flags_list,
|
||||
},
|
||||
value => {
|
||||
description => 'The new value of the flag',
|
||||
type => 'boolean',
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_configured();
|
||||
PVE::Ceph::Tools::check_ceph_configured();
|
||||
|
||||
my $cmd = $param->{value} ? 'set' : 'unset';
|
||||
my $cmd = $param->{value} ? 'set' : 'unset';
|
||||
|
||||
my $rados = PVE::RADOS->new();
|
||||
$rados->mon_command({
|
||||
prefix => "osd $cmd",
|
||||
key => $param->{flag},
|
||||
});
|
||||
|
||||
return undef;
|
||||
}});
|
||||
my $rados = PVE::RADOS->new();
|
||||
$rados->mon_command({
|
||||
prefix => "osd $cmd",
|
||||
key => $param->{flag},
|
||||
});
|
||||
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use PVE::API2::Jobs::RealmSync;
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::Jobs::RealmSync",
|
||||
path => 'realm-sync',
|
||||
});
|
||||
|
|
@ -22,29 +22,29 @@ __PACKAGE__->register_method({
|
|||
description => "Index for jobs related endpoints.",
|
||||
permissions => { user => 'all' },
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
description => 'Directory index.',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
subdir => {
|
||||
type => 'string',
|
||||
description => 'API sub-directory endpoint',
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{subdir}" } ],
|
||||
type => 'array',
|
||||
description => 'Directory index.',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
subdir => {
|
||||
type => 'string',
|
||||
description => 'API sub-directory endpoint',
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{subdir}" }],
|
||||
},
|
||||
code => sub {
|
||||
return [
|
||||
{ subdir => 'schedule-analyze' },
|
||||
{ subdir => 'realm-sync' },
|
||||
];
|
||||
}});
|
||||
return [
|
||||
{ subdir => 'schedule-analyze' }, { subdir => 'realm-sync' },
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method({
|
||||
name => 'schedule-analyze',
|
||||
|
|
@ -53,67 +53,72 @@ __PACKAGE__->register_method({
|
|||
description => "Returns a list of future schedule runtimes.",
|
||||
permissions => { user => 'all' },
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
schedule => {
|
||||
description => "Job schedule. The format is a subset of `systemd` calendar events.",
|
||||
type => 'string', format => 'pve-calendar-event',
|
||||
maxLength => 128,
|
||||
},
|
||||
starttime => {
|
||||
description => "UNIX timestamp to start the calculation from. Defaults to the current time.",
|
||||
optional => 1,
|
||||
type => 'integer',
|
||||
},
|
||||
iterations => {
|
||||
description => "Number of event-iteration to simulate and return.",
|
||||
optional => 1,
|
||||
type => 'integer',
|
||||
minimum => 1,
|
||||
maximum => 100,
|
||||
default => 10,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
schedule => {
|
||||
description =>
|
||||
"Job schedule. The format is a subset of `systemd` calendar events.",
|
||||
type => 'string',
|
||||
format => 'pve-calendar-event',
|
||||
maxLength => 128,
|
||||
},
|
||||
starttime => {
|
||||
description =>
|
||||
"UNIX timestamp to start the calculation from. Defaults to the current time.",
|
||||
optional => 1,
|
||||
type => 'integer',
|
||||
},
|
||||
iterations => {
|
||||
description => "Number of event-iteration to simulate and return.",
|
||||
optional => 1,
|
||||
type => 'integer',
|
||||
minimum => 1,
|
||||
maximum => 100,
|
||||
default => 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
description => 'An array of the next <iterations> events since <starttime>.',
|
||||
items => {
|
||||
type => 'object',
|
||||
properties => {
|
||||
timestamp => {
|
||||
type => 'integer',
|
||||
description => 'UNIX timestamp for the run.',
|
||||
},
|
||||
utc => {
|
||||
type => 'string',
|
||||
description => "UTC timestamp for the run.",
|
||||
},
|
||||
},
|
||||
},
|
||||
type => 'array',
|
||||
description => 'An array of the next <iterations> events since <starttime>.',
|
||||
items => {
|
||||
type => 'object',
|
||||
properties => {
|
||||
timestamp => {
|
||||
type => 'integer',
|
||||
description => 'UNIX timestamp for the run.',
|
||||
},
|
||||
utc => {
|
||||
type => 'string',
|
||||
description => "UTC timestamp for the run.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $starttime = $param->{starttime} // time();
|
||||
my $iterations = $param->{iterations} // 10;
|
||||
my $schedule = $param->{schedule};
|
||||
my $starttime = $param->{starttime} // time();
|
||||
my $iterations = $param->{iterations} // 10;
|
||||
my $schedule = $param->{schedule};
|
||||
|
||||
my $result = [];
|
||||
my $result = [];
|
||||
|
||||
my $event = PVE::CalendarEvent::parse_calendar_event($schedule);
|
||||
my $event = PVE::CalendarEvent::parse_calendar_event($schedule);
|
||||
|
||||
for (my $count = 0; $count < $iterations; $count++) {
|
||||
my $next = PVE::CalendarEvent::compute_next_event($event, $starttime);
|
||||
last if !defined($next);
|
||||
push @$result, {
|
||||
timestamp => $next,
|
||||
utc => scalar(gmtime($next)),
|
||||
};
|
||||
$starttime = $next;
|
||||
}
|
||||
for (my $count = 0; $count < $iterations; $count++) {
|
||||
my $next = PVE::CalendarEvent::compute_next_event($event, $starttime);
|
||||
last if !defined($next);
|
||||
push @$result,
|
||||
{
|
||||
timestamp => $next,
|
||||
utc => scalar(gmtime($next)),
|
||||
};
|
||||
$starttime = $next;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}});
|
||||
return $result;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -9,50 +9,49 @@ use PVE::API2::Cluster::Mapping::USB;
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::Cluster::Mapping::Dir",
|
||||
path => 'dir',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::Cluster::Mapping::PCI",
|
||||
path => 'pci',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::Cluster::Mapping::USB",
|
||||
path => 'usb',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "List resource types.",
|
||||
permissions => {
|
||||
user => 'all',
|
||||
user => 'all',
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $result = [
|
||||
{ name => 'dir' },
|
||||
{ name => 'pci' },
|
||||
{ name => 'usb' },
|
||||
];
|
||||
my $result = [
|
||||
{ name => 'dir' }, { name => 'pci' }, { name => 'usb' },
|
||||
];
|
||||
|
||||
return $result;
|
||||
}});
|
||||
return $result;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -14,295 +14,314 @@ use PVE::Tools qw(extract_param);
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
# only proxy if we give the 'check-node' parameter
|
||||
proxyto_callback => sub {
|
||||
my ($rpcenv, $proxyto, $param) = @_;
|
||||
return $param->{'check-node'} // 'localhost';
|
||||
my ($rpcenv, $proxyto, $param) = @_;
|
||||
return $param->{'check-node'} // 'localhost';
|
||||
},
|
||||
description => "List directory mapping",
|
||||
permissions => {
|
||||
description => "Only lists entries where you have 'Mapping.Modify', 'Mapping.Use' or"
|
||||
." 'Mapping.Audit' permissions on '/mapping/dir/<id>'.",
|
||||
user => 'all',
|
||||
description => "Only lists entries where you have 'Mapping.Modify', 'Mapping.Use' or"
|
||||
. " 'Mapping.Audit' permissions on '/mapping/dir/<id>'.",
|
||||
user => 'all',
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
'check-node' => get_standard_option('pve-node', {
|
||||
description => "If given, checks the configurations on the given node for"
|
||||
." correctness, and adds relevant diagnostics for the directory to the response.",
|
||||
optional => 1,
|
||||
}),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
'check-node' => get_standard_option(
|
||||
'pve-node',
|
||||
{
|
||||
description => "If given, checks the configurations on the given node for"
|
||||
. " correctness, and adds relevant diagnostics for the directory to the response.",
|
||||
optional => 1,
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
description => "The logical ID of the mapping."
|
||||
},
|
||||
map => {
|
||||
type => 'array',
|
||||
description => "The entries of the mapping.",
|
||||
items => {
|
||||
type => 'string',
|
||||
description => "A mapping for a node.",
|
||||
},
|
||||
},
|
||||
description => {
|
||||
type => 'string',
|
||||
description => "A description of the logical mapping.",
|
||||
},
|
||||
checks => {
|
||||
type => "array",
|
||||
optional => 1,
|
||||
description => "A list of checks, only present if 'check-node' is set.",
|
||||
items => {
|
||||
type => 'object',
|
||||
properties => {
|
||||
severity => {
|
||||
type => "string",
|
||||
enum => ['warning', 'error'],
|
||||
description => "The severity of the error",
|
||||
},
|
||||
message => {
|
||||
type => "string",
|
||||
description => "The message of the error",
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{id}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
description => "The logical ID of the mapping.",
|
||||
},
|
||||
map => {
|
||||
type => 'array',
|
||||
description => "The entries of the mapping.",
|
||||
items => {
|
||||
type => 'string',
|
||||
description => "A mapping for a node.",
|
||||
},
|
||||
},
|
||||
description => {
|
||||
type => 'string',
|
||||
description => "A description of the logical mapping.",
|
||||
},
|
||||
checks => {
|
||||
type => "array",
|
||||
optional => 1,
|
||||
description => "A list of checks, only present if 'check-node' is set.",
|
||||
items => {
|
||||
type => 'object',
|
||||
properties => {
|
||||
severity => {
|
||||
type => "string",
|
||||
enum => ['warning', 'error'],
|
||||
description => "The severity of the error",
|
||||
},
|
||||
message => {
|
||||
type => "string",
|
||||
description => "The message of the error",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{id}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $check_node = $param->{'check-node'};
|
||||
my $local_node = PVE::INotify::nodename();
|
||||
my $check_node = $param->{'check-node'};
|
||||
my $local_node = PVE::INotify::nodename();
|
||||
|
||||
die "wrong node to check - $check_node != $local_node\n"
|
||||
if defined($check_node) && $check_node ne 'localhost' && $check_node ne $local_node;
|
||||
die "wrong node to check - $check_node != $local_node\n"
|
||||
if defined($check_node) && $check_node ne 'localhost' && $check_node ne $local_node;
|
||||
|
||||
my $cfg = PVE::Mapping::Dir::config();
|
||||
my $cfg = PVE::Mapping::Dir::config();
|
||||
|
||||
my $can_see_mapping_privs = ['Mapping.Modify', 'Mapping.Use', 'Mapping.Audit'];
|
||||
my $can_see_mapping_privs = ['Mapping.Modify', 'Mapping.Use', 'Mapping.Audit'];
|
||||
|
||||
my $res = [];
|
||||
for my $id (keys $cfg->{ids}->%*) {
|
||||
next if !$rpcenv->check_any($authuser, "/mapping/dir/$id", $can_see_mapping_privs, 1);
|
||||
next if !$cfg->{ids}->{$id};
|
||||
my $res = [];
|
||||
for my $id (keys $cfg->{ids}->%*) {
|
||||
next
|
||||
if !$rpcenv->check_any($authuser, "/mapping/dir/$id", $can_see_mapping_privs, 1);
|
||||
next if !$cfg->{ids}->{$id};
|
||||
|
||||
my $entry = dclone($cfg->{ids}->{$id});
|
||||
$entry->{id} = $id;
|
||||
$entry->{digest} = $cfg->{digest};
|
||||
my $entry = dclone($cfg->{ids}->{$id});
|
||||
$entry->{id} = $id;
|
||||
$entry->{digest} = $cfg->{digest};
|
||||
|
||||
if (defined($check_node)) {
|
||||
$entry->{checks} = [];
|
||||
if (my $mappings = PVE::Mapping::Dir::get_node_mapping($cfg, $id, $check_node)) {
|
||||
if (!scalar($mappings->@*)) {
|
||||
push $entry->{checks}->@*, {
|
||||
severity => 'warning',
|
||||
message => "No mapping for node $check_node.",
|
||||
};
|
||||
}
|
||||
for my $mapping ($mappings->@*) {
|
||||
eval { PVE::Mapping::Dir::assert_valid($mapping) };
|
||||
if (my $err = $@) {
|
||||
push $entry->{checks}->@*, {
|
||||
severity => 'error',
|
||||
message => "Invalid configuration: $err",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defined($check_node)) {
|
||||
$entry->{checks} = [];
|
||||
if (
|
||||
my $mappings = PVE::Mapping::Dir::get_node_mapping($cfg, $id, $check_node)
|
||||
) {
|
||||
if (!scalar($mappings->@*)) {
|
||||
push $entry->{checks}->@*,
|
||||
{
|
||||
severity => 'warning',
|
||||
message => "No mapping for node $check_node.",
|
||||
};
|
||||
}
|
||||
for my $mapping ($mappings->@*) {
|
||||
eval { PVE::Mapping::Dir::assert_valid($mapping) };
|
||||
if (my $err = $@) {
|
||||
push $entry->{checks}->@*,
|
||||
{
|
||||
severity => 'error',
|
||||
message => "Invalid configuration: $err",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
push @$res, $entry;
|
||||
}
|
||||
push @$res, $entry;
|
||||
}
|
||||
|
||||
return $res;
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'get',
|
||||
protected => 1,
|
||||
path => '{id}',
|
||||
method => 'GET',
|
||||
description => "Get directory mapping.",
|
||||
permissions => {
|
||||
check =>['or',
|
||||
['perm', '/mapping/dir/{id}', ['Mapping.Use']],
|
||||
['perm', '/mapping/dir/{id}', ['Mapping.Modify']],
|
||||
['perm', '/mapping/dir/{id}', ['Mapping.Audit']],
|
||||
],
|
||||
check => [
|
||||
'or',
|
||||
['perm', '/mapping/dir/{id}', ['Mapping.Use']],
|
||||
['perm', '/mapping/dir/{id}', ['Mapping.Modify']],
|
||||
['perm', '/mapping/dir/{id}', ['Mapping.Audit']],
|
||||
],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
}
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'object' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $cfg = PVE::Mapping::Dir::config();
|
||||
my $id = $param->{id};
|
||||
my $cfg = PVE::Mapping::Dir::config();
|
||||
my $id = $param->{id};
|
||||
|
||||
my $entry = $cfg->{ids}->{$id};
|
||||
die "mapping '$param->{id}' not found\n" if !defined($entry);
|
||||
my $entry = $cfg->{ids}->{$id};
|
||||
die "mapping '$param->{id}' not found\n" if !defined($entry);
|
||||
|
||||
my $data = dclone($entry);
|
||||
my $data = dclone($entry);
|
||||
|
||||
$data->{digest} = $cfg->{digest};
|
||||
$data->{digest} = $cfg->{digest};
|
||||
|
||||
return $data;
|
||||
}});
|
||||
return $data;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'create',
|
||||
protected => 1,
|
||||
path => '',
|
||||
method => 'POST',
|
||||
description => "Create a new directory mapping.",
|
||||
permissions => {
|
||||
check => ['perm', '/mapping/dir', ['Mapping.Modify']],
|
||||
check => ['perm', '/mapping/dir', ['Mapping.Modify']],
|
||||
},
|
||||
parameters => PVE::Mapping::Dir->createSchema(1),
|
||||
returns => {
|
||||
type => 'null',
|
||||
type => 'null',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $id = extract_param($param, 'id');
|
||||
my $id = extract_param($param, 'id');
|
||||
|
||||
my $plugin = PVE::Mapping::Dir->lookup('dir');
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
my $plugin = PVE::Mapping::Dir->lookup('dir');
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
|
||||
my $map_list = $opts->{map};
|
||||
PVE::Mapping::Dir::assert_valid_map_list($map_list);
|
||||
my $map_list = $opts->{map};
|
||||
PVE::Mapping::Dir::assert_valid_map_list($map_list);
|
||||
|
||||
PVE::Mapping::Dir::lock_dir_config(sub {
|
||||
my $cfg = PVE::Mapping::Dir::config();
|
||||
PVE::Mapping::Dir::lock_dir_config(
|
||||
sub {
|
||||
my $cfg = PVE::Mapping::Dir::config();
|
||||
|
||||
die "dir ID '$id' already defined\n" if defined($cfg->{ids}->{$id});
|
||||
die "dir ID '$id' already defined\n" if defined($cfg->{ids}->{$id});
|
||||
|
||||
$cfg->{ids}->{$id} = $opts;
|
||||
$cfg->{ids}->{$id} = $opts;
|
||||
|
||||
PVE::Mapping::Dir::write_dir_config($cfg);
|
||||
PVE::Mapping::Dir::write_dir_config($cfg);
|
||||
|
||||
}, "create directory mapping failed");
|
||||
},
|
||||
"create directory mapping failed",
|
||||
);
|
||||
|
||||
return;
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'update',
|
||||
protected => 1,
|
||||
path => '{id}',
|
||||
method => 'PUT',
|
||||
description => "Update a directory mapping.",
|
||||
permissions => {
|
||||
check => ['perm', '/mapping/dir/{id}', ['Mapping.Modify']],
|
||||
check => ['perm', '/mapping/dir/{id}', ['Mapping.Modify']],
|
||||
},
|
||||
parameters => PVE::Mapping::Dir->updateSchema(),
|
||||
returns => {
|
||||
type => 'null',
|
||||
type => 'null',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $digest = extract_param($param, 'digest');
|
||||
my $delete = extract_param($param, 'delete');
|
||||
my $id = extract_param($param, 'id');
|
||||
my $digest = extract_param($param, 'digest');
|
||||
my $delete = extract_param($param, 'delete');
|
||||
my $id = extract_param($param, 'id');
|
||||
|
||||
if ($delete) {
|
||||
$delete = [ PVE::Tools::split_list($delete) ];
|
||||
}
|
||||
if ($delete) {
|
||||
$delete = [PVE::Tools::split_list($delete)];
|
||||
}
|
||||
|
||||
PVE::Mapping::Dir::lock_dir_config(sub {
|
||||
my $cfg = PVE::Mapping::Dir::config();
|
||||
PVE::Mapping::Dir::lock_dir_config(
|
||||
sub {
|
||||
my $cfg = PVE::Mapping::Dir::config();
|
||||
|
||||
PVE::Tools::assert_if_modified($cfg->{digest}, $digest) if defined($digest);
|
||||
PVE::Tools::assert_if_modified($cfg->{digest}, $digest) if defined($digest);
|
||||
|
||||
die "dir ID '$id' does not exist\n" if !defined($cfg->{ids}->{$id});
|
||||
die "dir ID '$id' does not exist\n" if !defined($cfg->{ids}->{$id});
|
||||
|
||||
my $plugin = PVE::Mapping::Dir->lookup('dir');
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
my $plugin = PVE::Mapping::Dir->lookup('dir');
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
|
||||
my $map_list = $opts->{map};
|
||||
PVE::Mapping::Dir::assert_valid_map_list($map_list);
|
||||
my $map_list = $opts->{map};
|
||||
PVE::Mapping::Dir::assert_valid_map_list($map_list);
|
||||
|
||||
my $data = $cfg->{ids}->{$id};
|
||||
my $data = $cfg->{ids}->{$id};
|
||||
|
||||
my $options = $plugin->private()->{options}->{dir};
|
||||
PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete);
|
||||
my $options = $plugin->private()->{options}->{dir};
|
||||
PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete);
|
||||
|
||||
$data->{$_} = $opts->{$_} for keys $opts->%*;
|
||||
$data->{$_} = $opts->{$_} for keys $opts->%*;
|
||||
|
||||
PVE::Mapping::Dir::write_dir_config($cfg);
|
||||
PVE::Mapping::Dir::write_dir_config($cfg);
|
||||
|
||||
}, "update directory mapping failed");
|
||||
},
|
||||
"update directory mapping failed",
|
||||
);
|
||||
|
||||
return;
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'delete',
|
||||
protected => 1,
|
||||
path => '{id}',
|
||||
method => 'DELETE',
|
||||
description => "Remove directory mapping.",
|
||||
permissions => {
|
||||
check => [ 'perm', '/mapping/dir', ['Mapping.Modify']],
|
||||
check => ['perm', '/mapping/dir', ['Mapping.Modify']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
}
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $id = $param->{id};
|
||||
my $id = $param->{id};
|
||||
|
||||
PVE::Mapping::Dir::lock_dir_config(sub {
|
||||
my $cfg = PVE::Mapping::Dir::config();
|
||||
PVE::Mapping::Dir::lock_dir_config(
|
||||
sub {
|
||||
my $cfg = PVE::Mapping::Dir::config();
|
||||
|
||||
if ($cfg->{ids}->{$id}) {
|
||||
delete $cfg->{ids}->{$id};
|
||||
}
|
||||
if ($cfg->{ids}->{$id}) {
|
||||
delete $cfg->{ids}->{$id};
|
||||
}
|
||||
|
||||
PVE::Mapping::Dir::write_dir_config($cfg);
|
||||
PVE::Mapping::Dir::write_dir_config($cfg);
|
||||
|
||||
}, "delete dir mapping failed");
|
||||
},
|
||||
"delete dir mapping failed",
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -11,289 +11,308 @@ use PVE::Tools qw(extract_param);
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
# only proxy if we give the 'check-node' parameter
|
||||
proxyto_callback => sub {
|
||||
my ($rpcenv, $proxyto, $param) = @_;
|
||||
return $param->{'check-node'} // 'localhost';
|
||||
my ($rpcenv, $proxyto, $param) = @_;
|
||||
return $param->{'check-node'} // 'localhost';
|
||||
},
|
||||
description => "List PCI Hardware Mapping",
|
||||
permissions => {
|
||||
description => "Only lists entries where you have 'Mapping.Modify', 'Mapping.Use' or".
|
||||
" 'Mapping.Audit' permissions on '/mapping/pci/<id>'.",
|
||||
user => 'all',
|
||||
description => "Only lists entries where you have 'Mapping.Modify', 'Mapping.Use' or"
|
||||
. " 'Mapping.Audit' permissions on '/mapping/pci/<id>'.",
|
||||
user => 'all',
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
'check-node' => get_standard_option('pve-node', {
|
||||
description => "If given, checks the configurations on the given node for ".
|
||||
"correctness, and adds relevant diagnostics for the devices to the response.",
|
||||
optional => 1,
|
||||
}),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
'check-node' => get_standard_option(
|
||||
'pve-node',
|
||||
{
|
||||
description => "If given, checks the configurations on the given node for "
|
||||
. "correctness, and adds relevant diagnostics for the devices to the response.",
|
||||
optional => 1,
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
description => "The logical ID of the mapping."
|
||||
},
|
||||
map => {
|
||||
type => 'array',
|
||||
description => "The entries of the mapping.",
|
||||
items => {
|
||||
type => 'string',
|
||||
description => "A mapping for a node.",
|
||||
},
|
||||
},
|
||||
description => {
|
||||
type => 'string',
|
||||
description => "A description of the logical mapping.",
|
||||
},
|
||||
checks => {
|
||||
type => "array",
|
||||
optional => 1,
|
||||
description => "A list of checks, only present if 'check_node' is set.",
|
||||
items => {
|
||||
type => 'object',
|
||||
properties => {
|
||||
severity => {
|
||||
type => "string",
|
||||
enum => ['warning', 'error'],
|
||||
description => "The severity of the error",
|
||||
},
|
||||
message => {
|
||||
type => "string",
|
||||
description => "The message of the error",
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{id}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
description => "The logical ID of the mapping.",
|
||||
},
|
||||
map => {
|
||||
type => 'array',
|
||||
description => "The entries of the mapping.",
|
||||
items => {
|
||||
type => 'string',
|
||||
description => "A mapping for a node.",
|
||||
},
|
||||
},
|
||||
description => {
|
||||
type => 'string',
|
||||
description => "A description of the logical mapping.",
|
||||
},
|
||||
checks => {
|
||||
type => "array",
|
||||
optional => 1,
|
||||
description => "A list of checks, only present if 'check_node' is set.",
|
||||
items => {
|
||||
type => 'object',
|
||||
properties => {
|
||||
severity => {
|
||||
type => "string",
|
||||
enum => ['warning', 'error'],
|
||||
description => "The severity of the error",
|
||||
},
|
||||
message => {
|
||||
type => "string",
|
||||
description => "The message of the error",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{id}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $check_node = $param->{'check-node'};
|
||||
my $local_node = PVE::INotify::nodename();
|
||||
my $check_node = $param->{'check-node'};
|
||||
my $local_node = PVE::INotify::nodename();
|
||||
|
||||
die "wrong node to check - $check_node != $local_node\n"
|
||||
if defined($check_node) && $check_node ne 'localhost' && $check_node ne $local_node;
|
||||
die "wrong node to check - $check_node != $local_node\n"
|
||||
if defined($check_node) && $check_node ne 'localhost' && $check_node ne $local_node;
|
||||
|
||||
my $cfg = PVE::Mapping::PCI::config();
|
||||
my $cfg = PVE::Mapping::PCI::config();
|
||||
|
||||
my $can_see_mapping_privs = ['Mapping.Modify', 'Mapping.Use', 'Mapping.Audit'];
|
||||
my $can_see_mapping_privs = ['Mapping.Modify', 'Mapping.Use', 'Mapping.Audit'];
|
||||
|
||||
my $res = [];
|
||||
for my $id (keys $cfg->{ids}->%*) {
|
||||
next if !$rpcenv->check_any($authuser, "/mapping/pci/$id", $can_see_mapping_privs, 1);
|
||||
next if !$cfg->{ids}->{$id};
|
||||
my $res = [];
|
||||
for my $id (keys $cfg->{ids}->%*) {
|
||||
next
|
||||
if !$rpcenv->check_any($authuser, "/mapping/pci/$id", $can_see_mapping_privs, 1);
|
||||
next if !$cfg->{ids}->{$id};
|
||||
|
||||
my $entry = dclone($cfg->{ids}->{$id});
|
||||
$entry->{id} = $id;
|
||||
$entry->{digest} = $cfg->{digest};
|
||||
my $entry = dclone($cfg->{ids}->{$id});
|
||||
$entry->{id} = $id;
|
||||
$entry->{digest} = $cfg->{digest};
|
||||
|
||||
if (defined($check_node)) {
|
||||
$entry->{checks} = [];
|
||||
if (my $mappings = PVE::Mapping::PCI::get_node_mapping($cfg, $id, $check_node)) {
|
||||
if (!scalar($mappings->@*)) {
|
||||
push $entry->{checks}->@*, {
|
||||
severity => 'warning',
|
||||
message => "No mapping for node $check_node.",
|
||||
};
|
||||
}
|
||||
for my $mapping ($mappings->@*) {
|
||||
eval { PVE::Mapping::PCI::assert_valid($id, $mapping, $entry) };
|
||||
if (my $err = $@) {
|
||||
push $entry->{checks}->@*, {
|
||||
severity => 'error',
|
||||
message => "Invalid configuration: $err",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defined($check_node)) {
|
||||
$entry->{checks} = [];
|
||||
if (
|
||||
my $mappings = PVE::Mapping::PCI::get_node_mapping($cfg, $id, $check_node)
|
||||
) {
|
||||
if (!scalar($mappings->@*)) {
|
||||
push $entry->{checks}->@*,
|
||||
{
|
||||
severity => 'warning',
|
||||
message => "No mapping for node $check_node.",
|
||||
};
|
||||
}
|
||||
for my $mapping ($mappings->@*) {
|
||||
eval { PVE::Mapping::PCI::assert_valid($id, $mapping, $entry) };
|
||||
if (my $err = $@) {
|
||||
push $entry->{checks}->@*,
|
||||
{
|
||||
severity => 'error',
|
||||
message => "Invalid configuration: $err",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
push @$res, $entry;
|
||||
}
|
||||
push @$res, $entry;
|
||||
}
|
||||
|
||||
return $res;
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'get',
|
||||
protected => 1,
|
||||
path => '{id}',
|
||||
method => 'GET',
|
||||
description => "Get PCI Mapping.",
|
||||
permissions => {
|
||||
check =>['or',
|
||||
['perm', '/mapping/pci/{id}', ['Mapping.Use']],
|
||||
['perm', '/mapping/pci/{id}', ['Mapping.Modify']],
|
||||
['perm', '/mapping/pci/{id}', ['Mapping.Audit']],
|
||||
],
|
||||
check => [
|
||||
'or',
|
||||
['perm', '/mapping/pci/{id}', ['Mapping.Use']],
|
||||
['perm', '/mapping/pci/{id}', ['Mapping.Modify']],
|
||||
['perm', '/mapping/pci/{id}', ['Mapping.Audit']],
|
||||
],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
}
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'object' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $cfg = PVE::Mapping::PCI::config();
|
||||
my $id = $param->{id};
|
||||
my $cfg = PVE::Mapping::PCI::config();
|
||||
my $id = $param->{id};
|
||||
|
||||
my $entry = $cfg->{ids}->{$id};
|
||||
die "mapping '$param->{id}' not found\n" if !defined($entry);
|
||||
my $entry = $cfg->{ids}->{$id};
|
||||
die "mapping '$param->{id}' not found\n" if !defined($entry);
|
||||
|
||||
my $data = dclone($entry);
|
||||
my $data = dclone($entry);
|
||||
|
||||
$data->{digest} = $cfg->{digest};
|
||||
$data->{digest} = $cfg->{digest};
|
||||
|
||||
return $data;
|
||||
}});
|
||||
return $data;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'create',
|
||||
protected => 1,
|
||||
path => '',
|
||||
method => 'POST',
|
||||
description => "Create a new hardware mapping.",
|
||||
permissions => {
|
||||
check => ['perm', '/mapping/pci', ['Mapping.Modify']],
|
||||
check => ['perm', '/mapping/pci', ['Mapping.Modify']],
|
||||
},
|
||||
parameters => PVE::Mapping::PCI->createSchema(1),
|
||||
returns => {
|
||||
type => 'null',
|
||||
type => 'null',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $id = extract_param($param, 'id');
|
||||
my $id = extract_param($param, 'id');
|
||||
|
||||
my $plugin = PVE::Mapping::PCI->lookup('pci');
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
my $plugin = PVE::Mapping::PCI->lookup('pci');
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
|
||||
PVE::Mapping::PCI::lock_pci_config(sub {
|
||||
my $cfg = PVE::Mapping::PCI::config();
|
||||
PVE::Mapping::PCI::lock_pci_config(
|
||||
sub {
|
||||
my $cfg = PVE::Mapping::PCI::config();
|
||||
|
||||
die "pci ID '$id' already defined\n" if defined($cfg->{ids}->{$id});
|
||||
die "pci ID '$id' already defined\n" if defined($cfg->{ids}->{$id});
|
||||
|
||||
$cfg->{ids}->{$id} = $opts;
|
||||
$cfg->{ids}->{$id} = $opts;
|
||||
|
||||
PVE::Mapping::PCI::write_pci_config($cfg);
|
||||
PVE::Mapping::PCI::write_pci_config($cfg);
|
||||
|
||||
}, "create hardware mapping failed");
|
||||
},
|
||||
"create hardware mapping failed",
|
||||
);
|
||||
|
||||
return;
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'update',
|
||||
protected => 1,
|
||||
path => '{id}',
|
||||
method => 'PUT',
|
||||
description => "Update a hardware mapping.",
|
||||
permissions => {
|
||||
check => ['perm', '/mapping/pci/{id}', ['Mapping.Modify']],
|
||||
check => ['perm', '/mapping/pci/{id}', ['Mapping.Modify']],
|
||||
},
|
||||
parameters => PVE::Mapping::PCI->updateSchema(),
|
||||
returns => {
|
||||
type => 'null',
|
||||
type => 'null',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $digest = extract_param($param, 'digest');
|
||||
my $delete = extract_param($param, 'delete');
|
||||
my $id = extract_param($param, 'id');
|
||||
my $digest = extract_param($param, 'digest');
|
||||
my $delete = extract_param($param, 'delete');
|
||||
my $id = extract_param($param, 'id');
|
||||
|
||||
if ($delete) {
|
||||
$delete = [ PVE::Tools::split_list($delete) ];
|
||||
}
|
||||
if ($delete) {
|
||||
$delete = [PVE::Tools::split_list($delete)];
|
||||
}
|
||||
|
||||
PVE::Mapping::PCI::lock_pci_config(sub {
|
||||
my $cfg = PVE::Mapping::PCI::config();
|
||||
PVE::Mapping::PCI::lock_pci_config(
|
||||
sub {
|
||||
my $cfg = PVE::Mapping::PCI::config();
|
||||
|
||||
PVE::Tools::assert_if_modified($cfg->{digest}, $digest) if defined($digest);
|
||||
PVE::Tools::assert_if_modified($cfg->{digest}, $digest) if defined($digest);
|
||||
|
||||
die "pci ID '$id' does not exist\n" if !defined($cfg->{ids}->{$id});
|
||||
die "pci ID '$id' does not exist\n" if !defined($cfg->{ids}->{$id});
|
||||
|
||||
my $plugin = PVE::Mapping::PCI->lookup('pci');
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
my $plugin = PVE::Mapping::PCI->lookup('pci');
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
|
||||
my $data = $cfg->{ids}->{$id};
|
||||
my $data = $cfg->{ids}->{$id};
|
||||
|
||||
my $options = $plugin->private()->{options}->{pci};
|
||||
PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete);
|
||||
my $options = $plugin->private()->{options}->{pci};
|
||||
PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete);
|
||||
|
||||
$data->{$_} = $opts->{$_} for keys $opts->%*;
|
||||
$data->{$_} = $opts->{$_} for keys $opts->%*;
|
||||
|
||||
PVE::Mapping::PCI::write_pci_config($cfg);
|
||||
PVE::Mapping::PCI::write_pci_config($cfg);
|
||||
|
||||
}, "update hardware mapping failed");
|
||||
},
|
||||
"update hardware mapping failed",
|
||||
);
|
||||
|
||||
return;
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'delete',
|
||||
protected => 1,
|
||||
path => '{id}',
|
||||
method => 'DELETE',
|
||||
description => "Remove Hardware Mapping.",
|
||||
permissions => {
|
||||
check => [ 'perm', '/mapping/pci', ['Mapping.Modify']],
|
||||
check => ['perm', '/mapping/pci', ['Mapping.Modify']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
}
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $id = $param->{id};
|
||||
my $id = $param->{id};
|
||||
|
||||
PVE::Mapping::PCI::lock_pci_config(sub {
|
||||
my $cfg = PVE::Mapping::PCI::config();
|
||||
PVE::Mapping::PCI::lock_pci_config(
|
||||
sub {
|
||||
my $cfg = PVE::Mapping::PCI::config();
|
||||
|
||||
if ($cfg->{ids}->{$id}) {
|
||||
delete $cfg->{ids}->{$id};
|
||||
}
|
||||
if ($cfg->{ids}->{$id}) {
|
||||
delete $cfg->{ids}->{$id};
|
||||
}
|
||||
|
||||
PVE::Mapping::PCI::write_pci_config($cfg);
|
||||
PVE::Mapping::PCI::write_pci_config($cfg);
|
||||
|
||||
}, "delete pci mapping failed");
|
||||
},
|
||||
"delete pci mapping failed",
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -11,287 +11,301 @@ use PVE::Tools qw(extract_param);
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
# only proxy if we give the 'check-node' parameter
|
||||
proxyto_callback => sub {
|
||||
my ($rpcenv, $proxyto, $param) = @_;
|
||||
return $param->{'check-node'} // 'localhost';
|
||||
my ($rpcenv, $proxyto, $param) = @_;
|
||||
return $param->{'check-node'} // 'localhost';
|
||||
},
|
||||
description => "List USB Hardware Mappings",
|
||||
permissions => {
|
||||
description => "Only lists entries where you have 'Mapping.Modify', 'Mapping.Use' or".
|
||||
" 'Mapping.Audit' permissions on '/mapping/usb/<id>'.",
|
||||
user => 'all',
|
||||
description => "Only lists entries where you have 'Mapping.Modify', 'Mapping.Use' or"
|
||||
. " 'Mapping.Audit' permissions on '/mapping/usb/<id>'.",
|
||||
user => 'all',
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
'check-node' => get_standard_option('pve-node', {
|
||||
description => "If given, checks the configurations on the given node for ".
|
||||
"correctness, and adds relevant errors to the devices.",
|
||||
optional => 1,
|
||||
}),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
'check-node' => get_standard_option(
|
||||
'pve-node',
|
||||
{
|
||||
description => "If given, checks the configurations on the given node for "
|
||||
. "correctness, and adds relevant errors to the devices.",
|
||||
optional => 1,
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
description => "The logical ID of the mapping."
|
||||
},
|
||||
map => {
|
||||
type => 'array',
|
||||
description => "The entries of the mapping.",
|
||||
items => {
|
||||
type => 'string',
|
||||
description => "A mapping for a node.",
|
||||
},
|
||||
},
|
||||
description => {
|
||||
type => 'string',
|
||||
description => "A description of the logical mapping.",
|
||||
},
|
||||
error => {
|
||||
description => "A list of errors when 'check_node' is given.",
|
||||
items => {
|
||||
type => 'object',
|
||||
properties => {
|
||||
severity => {
|
||||
type => "string",
|
||||
description => "The severity of the error",
|
||||
},
|
||||
message => {
|
||||
type => "string",
|
||||
description => "The message of the error",
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{id}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
description => "The logical ID of the mapping.",
|
||||
},
|
||||
map => {
|
||||
type => 'array',
|
||||
description => "The entries of the mapping.",
|
||||
items => {
|
||||
type => 'string',
|
||||
description => "A mapping for a node.",
|
||||
},
|
||||
},
|
||||
description => {
|
||||
type => 'string',
|
||||
description => "A description of the logical mapping.",
|
||||
},
|
||||
error => {
|
||||
description => "A list of errors when 'check_node' is given.",
|
||||
items => {
|
||||
type => 'object',
|
||||
properties => {
|
||||
severity => {
|
||||
type => "string",
|
||||
description => "The severity of the error",
|
||||
},
|
||||
message => {
|
||||
type => "string",
|
||||
description => "The message of the error",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{id}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $node = $param->{'check-node'};
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $node = $param->{'check-node'};
|
||||
|
||||
die "Wrong node to check\n"
|
||||
if defined($node) && $node ne 'localhost' && $node ne PVE::INotify::nodename();
|
||||
die "Wrong node to check\n"
|
||||
if defined($node) && $node ne 'localhost' && $node ne PVE::INotify::nodename();
|
||||
|
||||
my $cfg = PVE::Mapping::USB::config();
|
||||
my $cfg = PVE::Mapping::USB::config();
|
||||
|
||||
my $res = [];
|
||||
my $res = [];
|
||||
|
||||
my $privs = ['Mapping.Modify', 'Mapping.Use', 'Mapping.Audit'];
|
||||
my $privs = ['Mapping.Modify', 'Mapping.Use', 'Mapping.Audit'];
|
||||
|
||||
for my $id (keys $cfg->{ids}->%*) {
|
||||
next if !$rpcenv->check_full($authuser, "/mapping/usb/$id", $privs, 1, 1);
|
||||
next if !$cfg->{ids}->{$id};
|
||||
for my $id (keys $cfg->{ids}->%*) {
|
||||
next if !$rpcenv->check_full($authuser, "/mapping/usb/$id", $privs, 1, 1);
|
||||
next if !$cfg->{ids}->{$id};
|
||||
|
||||
my $entry = dclone($cfg->{ids}->{$id});
|
||||
$entry->{id} = $id;
|
||||
$entry->{digest} = $cfg->{digest};
|
||||
my $entry = dclone($cfg->{ids}->{$id});
|
||||
$entry->{id} = $id;
|
||||
$entry->{digest} = $cfg->{digest};
|
||||
|
||||
if (defined($node)) {
|
||||
$entry->{errors} = [];
|
||||
if (my $mappings = PVE::Mapping::USB::get_node_mapping($cfg, $id, $node)) {
|
||||
if (!scalar($mappings->@*)) {
|
||||
push $entry->{errors}->@*, {
|
||||
severity => 'warning',
|
||||
message => "No mapping for node $node.",
|
||||
};
|
||||
}
|
||||
for my $mapping ($mappings->@*) {
|
||||
eval {
|
||||
PVE::Mapping::USB::assert_valid($id, $mapping);
|
||||
};
|
||||
if (my $err = $@) {
|
||||
push $entry->{errors}->@*, {
|
||||
severity => 'error',
|
||||
message => "Invalid configuration: $err",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (defined($node)) {
|
||||
$entry->{errors} = [];
|
||||
if (my $mappings = PVE::Mapping::USB::get_node_mapping($cfg, $id, $node)) {
|
||||
if (!scalar($mappings->@*)) {
|
||||
push $entry->{errors}->@*,
|
||||
{
|
||||
severity => 'warning',
|
||||
message => "No mapping for node $node.",
|
||||
};
|
||||
}
|
||||
for my $mapping ($mappings->@*) {
|
||||
eval { PVE::Mapping::USB::assert_valid($id, $mapping); };
|
||||
if (my $err = $@) {
|
||||
push $entry->{errors}->@*,
|
||||
{
|
||||
severity => 'error',
|
||||
message => "Invalid configuration: $err",
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
push @$res, $entry;
|
||||
}
|
||||
push @$res, $entry;
|
||||
}
|
||||
|
||||
return $res;
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'get',
|
||||
protected => 1,
|
||||
path => '{id}',
|
||||
method => 'GET',
|
||||
description => "Get USB Mapping.",
|
||||
permissions => {
|
||||
check =>['or',
|
||||
['perm', '/mapping/usb/{id}', ['Mapping.Audit']],
|
||||
['perm', '/mapping/usb/{id}', ['Mapping.Use']],
|
||||
['perm', '/mapping/usb/{id}', ['Mapping.Modify']],
|
||||
],
|
||||
check => [
|
||||
'or',
|
||||
['perm', '/mapping/usb/{id}', ['Mapping.Audit']],
|
||||
['perm', '/mapping/usb/{id}', ['Mapping.Use']],
|
||||
['perm', '/mapping/usb/{id}', ['Mapping.Modify']],
|
||||
],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
}
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'object' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $cfg = PVE::Mapping::USB::config();
|
||||
my $id = $param->{id};
|
||||
my $cfg = PVE::Mapping::USB::config();
|
||||
my $id = $param->{id};
|
||||
|
||||
my $entry = $cfg->{ids}->{$id};
|
||||
die "mapping '$param->{id}' not found\n" if !defined($entry);
|
||||
my $entry = $cfg->{ids}->{$id};
|
||||
die "mapping '$param->{id}' not found\n" if !defined($entry);
|
||||
|
||||
my $data = dclone($entry);
|
||||
my $data = dclone($entry);
|
||||
|
||||
$data->{digest} = $cfg->{digest};
|
||||
$data->{digest} = $cfg->{digest};
|
||||
|
||||
return $data;
|
||||
}});
|
||||
return $data;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'create',
|
||||
protected => 1,
|
||||
path => '',
|
||||
method => 'POST',
|
||||
description => "Create a new hardware mapping.",
|
||||
permissions => {
|
||||
check => ['perm', '/mapping/usb', ['Mapping.Modify']],
|
||||
check => ['perm', '/mapping/usb', ['Mapping.Modify']],
|
||||
},
|
||||
parameters => PVE::Mapping::USB->createSchema(1),
|
||||
returns => {
|
||||
type => 'null',
|
||||
type => 'null',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $id = extract_param($param, 'id');
|
||||
my $id = extract_param($param, 'id');
|
||||
|
||||
my $plugin = PVE::Mapping::USB->lookup('usb');
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
my $plugin = PVE::Mapping::USB->lookup('usb');
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
|
||||
PVE::Mapping::USB::lock_usb_config(sub {
|
||||
my $cfg = PVE::Mapping::USB::config();
|
||||
PVE::Mapping::USB::lock_usb_config(
|
||||
sub {
|
||||
my $cfg = PVE::Mapping::USB::config();
|
||||
|
||||
die "usb ID '$id' already defined\n" if defined($cfg->{ids}->{$id});
|
||||
die "usb ID '$id' already defined\n" if defined($cfg->{ids}->{$id});
|
||||
|
||||
$cfg->{ids}->{$id} = $opts;
|
||||
$cfg->{ids}->{$id} = $opts;
|
||||
|
||||
PVE::Mapping::USB::write_usb_config($cfg);
|
||||
PVE::Mapping::USB::write_usb_config($cfg);
|
||||
|
||||
}, "create hardware mapping failed");
|
||||
},
|
||||
"create hardware mapping failed",
|
||||
);
|
||||
|
||||
return;
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'update',
|
||||
protected => 1,
|
||||
path => '{id}',
|
||||
method => 'PUT',
|
||||
description => "Update a hardware mapping.",
|
||||
permissions => {
|
||||
check => ['perm', '/mapping/usb/{id}', ['Mapping.Modify']],
|
||||
check => ['perm', '/mapping/usb/{id}', ['Mapping.Modify']],
|
||||
},
|
||||
parameters => PVE::Mapping::USB->updateSchema(),
|
||||
returns => {
|
||||
type => 'null',
|
||||
type => 'null',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $digest = extract_param($param, 'digest');
|
||||
my $delete = extract_param($param, 'delete');
|
||||
my $id = extract_param($param, 'id');
|
||||
my $digest = extract_param($param, 'digest');
|
||||
my $delete = extract_param($param, 'delete');
|
||||
my $id = extract_param($param, 'id');
|
||||
|
||||
if ($delete) {
|
||||
$delete = [ PVE::Tools::split_list($delete) ];
|
||||
}
|
||||
if ($delete) {
|
||||
$delete = [PVE::Tools::split_list($delete)];
|
||||
}
|
||||
|
||||
PVE::Mapping::USB::lock_usb_config(sub {
|
||||
my $cfg = PVE::Mapping::USB::config();
|
||||
PVE::Mapping::USB::lock_usb_config(
|
||||
sub {
|
||||
my $cfg = PVE::Mapping::USB::config();
|
||||
|
||||
PVE::Tools::assert_if_modified($cfg->{digest}, $digest) if defined($digest);
|
||||
PVE::Tools::assert_if_modified($cfg->{digest}, $digest) if defined($digest);
|
||||
|
||||
die "usb ID '$id' does not exist\n" if !defined($cfg->{ids}->{$id});
|
||||
die "usb ID '$id' does not exist\n" if !defined($cfg->{ids}->{$id});
|
||||
|
||||
my $plugin = PVE::Mapping::USB->lookup('usb');
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
my $plugin = PVE::Mapping::USB->lookup('usb');
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
|
||||
my $data = $cfg->{ids}->{$id};
|
||||
my $data = $cfg->{ids}->{$id};
|
||||
|
||||
my $options = $plugin->private()->{options}->{usb};
|
||||
PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete);
|
||||
my $options = $plugin->private()->{options}->{usb};
|
||||
PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete);
|
||||
|
||||
$data->{$_} = $opts->{$_} for keys $opts->%*;
|
||||
$data->{$_} = $opts->{$_} for keys $opts->%*;
|
||||
|
||||
PVE::Mapping::USB::write_usb_config($cfg);
|
||||
PVE::Mapping::USB::write_usb_config($cfg);
|
||||
|
||||
}, "update hardware mapping failed");
|
||||
},
|
||||
"update hardware mapping failed",
|
||||
);
|
||||
|
||||
return;
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'delete',
|
||||
protected => 1,
|
||||
path => '{id}',
|
||||
method => 'DELETE',
|
||||
description => "Remove Hardware Mapping.",
|
||||
permissions => {
|
||||
check => [ 'perm', '/mapping/usb', ['Mapping.Modify']],
|
||||
check => ['perm', '/mapping/usb', ['Mapping.Modify']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
}
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $id = $param->{id};
|
||||
my $id = $param->{id};
|
||||
|
||||
PVE::Mapping::USB::lock_usb_config(sub {
|
||||
my $cfg = PVE::Mapping::USB::config();
|
||||
PVE::Mapping::USB::lock_usb_config(
|
||||
sub {
|
||||
my $cfg = PVE::Mapping::USB::config();
|
||||
|
||||
if ($cfg->{ids}->{$id}) {
|
||||
delete $cfg->{ids}->{$id};
|
||||
}
|
||||
if ($cfg->{ids}->{$id}) {
|
||||
delete $cfg->{ids}->{$id};
|
||||
}
|
||||
|
||||
PVE::Mapping::USB::write_usb_config($cfg);
|
||||
PVE::Mapping::USB::write_usb_config($cfg);
|
||||
|
||||
}, "delete usb mapping failed");
|
||||
},
|
||||
"delete usb mapping failed",
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -16,458 +16,474 @@ use PVE::RESTHandler;
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "Metrics index.",
|
||||
permissions => { user => 'all' },
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $result = [
|
||||
{ name => 'server' },
|
||||
];
|
||||
my $result = [
|
||||
{ name => 'server' },
|
||||
];
|
||||
|
||||
return $result;
|
||||
}
|
||||
return $result;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'server_index',
|
||||
path => 'server',
|
||||
method => 'GET',
|
||||
description => "List configured metric servers.",
|
||||
permissions => {
|
||||
check => ['perm', '/', ['Sys.Audit']],
|
||||
check => ['perm', '/', ['Sys.Audit']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
id => {
|
||||
description => "The ID of the entry.",
|
||||
type => 'string'
|
||||
},
|
||||
disable => {
|
||||
description => "Flag to disable the plugin.",
|
||||
type => 'boolean',
|
||||
},
|
||||
type => {
|
||||
description => "Plugin type.",
|
||||
type => 'string',
|
||||
},
|
||||
server => {
|
||||
description => "Server dns name or IP address",
|
||||
type => 'string',
|
||||
},
|
||||
port => {
|
||||
description => "Server network port",
|
||||
type => 'integer',
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{id}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
id => {
|
||||
description => "The ID of the entry.",
|
||||
type => 'string',
|
||||
},
|
||||
disable => {
|
||||
description => "Flag to disable the plugin.",
|
||||
type => 'boolean',
|
||||
},
|
||||
type => {
|
||||
description => "Plugin type.",
|
||||
type => 'string',
|
||||
},
|
||||
server => {
|
||||
description => "Server dns name or IP address",
|
||||
type => 'string',
|
||||
},
|
||||
port => {
|
||||
description => "Server network port",
|
||||
type => 'integer',
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{id}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $res = [];
|
||||
my $status_cfg = PVE::Cluster::cfs_read_file('status.cfg');
|
||||
my $res = [];
|
||||
my $status_cfg = PVE::Cluster::cfs_read_file('status.cfg');
|
||||
|
||||
for my $id (sort keys %{$status_cfg->{ids}}) {
|
||||
my $plugin_config = $status_cfg->{ids}->{$id};
|
||||
push @$res, {
|
||||
id => $id,
|
||||
disable => $plugin_config->{disable} // 0,
|
||||
type => $plugin_config->{type},
|
||||
server => $plugin_config->{server},
|
||||
port => $plugin_config->{port},
|
||||
};
|
||||
}
|
||||
for my $id (sort keys %{ $status_cfg->{ids} }) {
|
||||
my $plugin_config = $status_cfg->{ids}->{$id};
|
||||
push @$res,
|
||||
{
|
||||
id => $id,
|
||||
disable => $plugin_config->{disable} // 0,
|
||||
type => $plugin_config->{type},
|
||||
server => $plugin_config->{server},
|
||||
port => $plugin_config->{port},
|
||||
};
|
||||
}
|
||||
|
||||
return $res;
|
||||
}});
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'read',
|
||||
path => 'server/{id}',
|
||||
method => 'GET',
|
||||
description => "Read metric server configuration.",
|
||||
permissions => {
|
||||
check => ['perm', '/', ['Sys.Audit']],
|
||||
check => ['perm', '/', ['Sys.Audit']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'object' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $status_cfg = PVE::Cluster::cfs_read_file('status.cfg');
|
||||
my $id = $param->{id};
|
||||
my $status_cfg = PVE::Cluster::cfs_read_file('status.cfg');
|
||||
my $id = $param->{id};
|
||||
|
||||
if (!defined($status_cfg->{ids}->{$id})) {
|
||||
die "status server entry '$id' does not exist\n";
|
||||
}
|
||||
if (!defined($status_cfg->{ids}->{$id})) {
|
||||
die "status server entry '$id' does not exist\n";
|
||||
}
|
||||
|
||||
return $status_cfg->{ids}->{$id};
|
||||
}});
|
||||
return $status_cfg->{ids}->{$id};
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'create',
|
||||
path => 'server/{id}',
|
||||
protected => 1,
|
||||
method => 'POST',
|
||||
description => "Create a new external metric server config",
|
||||
permissions => {
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
parameters => PVE::Status::Plugin->createSchema(),
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $type = extract_param($param, 'type');
|
||||
my $plugin = PVE::Status::Plugin->lookup($type);
|
||||
my $id = extract_param($param, 'id');
|
||||
my $type = extract_param($param, 'type');
|
||||
my $plugin = PVE::Status::Plugin->lookup($type);
|
||||
my $id = extract_param($param, 'id');
|
||||
|
||||
my $sensitive_params = extract_sensitive_params($param, ['token'], []);
|
||||
my $sensitive_params = extract_sensitive_params($param, ['token'], []);
|
||||
|
||||
PVE::Cluster::cfs_lock_file('status.cfg', undef, sub {
|
||||
my $cfg = PVE::Cluster::cfs_read_file('status.cfg');
|
||||
PVE::Cluster::cfs_lock_file(
|
||||
'status.cfg',
|
||||
undef,
|
||||
sub {
|
||||
my $cfg = PVE::Cluster::cfs_read_file('status.cfg');
|
||||
|
||||
die "Metric server '$id' already exists\n"
|
||||
if $cfg->{ids}->{$id};
|
||||
die "Metric server '$id' already exists\n"
|
||||
if $cfg->{ids}->{$id};
|
||||
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
|
||||
$cfg->{ids}->{$id} = $opts;
|
||||
$cfg->{ids}->{$id} = $opts;
|
||||
|
||||
$plugin->on_add_hook($id, $opts, $sensitive_params);
|
||||
$plugin->on_add_hook($id, $opts, $sensitive_params);
|
||||
|
||||
eval {
|
||||
$plugin->test_connection($opts, $id);
|
||||
};
|
||||
eval { $plugin->test_connection($opts, $id); };
|
||||
|
||||
if (my $err = $@) {
|
||||
eval { $plugin->on_delete_hook($id, $opts) };
|
||||
warn "$@\n" if $@;
|
||||
die $err;
|
||||
}
|
||||
if (my $err = $@) {
|
||||
eval { $plugin->on_delete_hook($id, $opts) };
|
||||
warn "$@\n" if $@;
|
||||
die $err;
|
||||
}
|
||||
|
||||
PVE::Cluster::cfs_write_file('status.cfg', $cfg);
|
||||
});
|
||||
die $@ if $@;
|
||||
PVE::Cluster::cfs_write_file('status.cfg', $cfg);
|
||||
},
|
||||
);
|
||||
die $@ if $@;
|
||||
|
||||
return;
|
||||
}});
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'update',
|
||||
protected => 1,
|
||||
path => 'server/{id}',
|
||||
method => 'PUT',
|
||||
description => "Update metric server configuration.",
|
||||
permissions => {
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
parameters => PVE::Status::Plugin->updateSchema(),
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $id = extract_param($param, 'id');
|
||||
my $digest = extract_param($param, 'digest');
|
||||
my $delete = extract_param($param, 'delete');
|
||||
my $id = extract_param($param, 'id');
|
||||
my $digest = extract_param($param, 'digest');
|
||||
my $delete = extract_param($param, 'delete');
|
||||
|
||||
if ($delete) {
|
||||
$delete = [PVE::Tools::split_list($delete)];
|
||||
}
|
||||
if ($delete) {
|
||||
$delete = [PVE::Tools::split_list($delete)];
|
||||
}
|
||||
|
||||
my $sensitive_params = extract_sensitive_params($param, ['token'], $delete);
|
||||
my $sensitive_params = extract_sensitive_params($param, ['token'], $delete);
|
||||
|
||||
PVE::Cluster::cfs_lock_file('status.cfg', undef, sub {
|
||||
my $cfg = PVE::Cluster::cfs_read_file('status.cfg');
|
||||
PVE::Cluster::cfs_lock_file(
|
||||
'status.cfg',
|
||||
undef,
|
||||
sub {
|
||||
my $cfg = PVE::Cluster::cfs_read_file('status.cfg');
|
||||
|
||||
PVE::SectionConfig::assert_if_modified($cfg, $digest);
|
||||
PVE::SectionConfig::assert_if_modified($cfg, $digest);
|
||||
|
||||
my $data = $cfg->{ids}->{$id};
|
||||
die "no such server '$id'\n" if !$data;
|
||||
my $data = $cfg->{ids}->{$id};
|
||||
die "no such server '$id'\n" if !$data;
|
||||
|
||||
my $plugin = PVE::Status::Plugin->lookup($data->{type});
|
||||
my $opts = $plugin->check_config($id, $param, 0, 1);
|
||||
my $plugin = PVE::Status::Plugin->lookup($data->{type});
|
||||
my $opts = $plugin->check_config($id, $param, 0, 1);
|
||||
|
||||
for my $k (keys %$opts) {
|
||||
$data->{$k} = $opts->{$k};
|
||||
}
|
||||
for my $k (keys %$opts) {
|
||||
$data->{$k} = $opts->{$k};
|
||||
}
|
||||
|
||||
if ($delete) {
|
||||
my $options = $plugin->private()->{options}->{$data->{type}};
|
||||
for my $k (@$delete) {
|
||||
my $d = $options->{$k} || die "no such option '$k'\n";
|
||||
die "unable to delete required option '$k'\n" if !$d->{optional};
|
||||
die "unable to delete fixed option '$k'\n" if $d->{fixed};
|
||||
die "cannot set and delete property '$k' at the same time!\n"
|
||||
if defined($opts->{$k});
|
||||
if ($delete) {
|
||||
my $options = $plugin->private()->{options}->{ $data->{type} };
|
||||
for my $k (@$delete) {
|
||||
my $d = $options->{$k} || die "no such option '$k'\n";
|
||||
die "unable to delete required option '$k'\n" if !$d->{optional};
|
||||
die "unable to delete fixed option '$k'\n" if $d->{fixed};
|
||||
die "cannot set and delete property '$k' at the same time!\n"
|
||||
if defined($opts->{$k});
|
||||
|
||||
delete $data->{$k};
|
||||
}
|
||||
}
|
||||
delete $data->{$k};
|
||||
}
|
||||
}
|
||||
|
||||
$plugin->on_update_hook($id, $data, $sensitive_params);
|
||||
$plugin->on_update_hook($id, $data, $sensitive_params);
|
||||
|
||||
$plugin->test_connection($data, $id);
|
||||
$plugin->test_connection($data, $id);
|
||||
|
||||
PVE::Cluster::cfs_write_file('status.cfg', $cfg);
|
||||
});
|
||||
die $@ if $@;
|
||||
PVE::Cluster::cfs_write_file('status.cfg', $cfg);
|
||||
},
|
||||
);
|
||||
die $@ if $@;
|
||||
|
||||
return;
|
||||
}});
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'delete',
|
||||
protected => 1,
|
||||
path => 'server/{id}',
|
||||
method => 'DELETE',
|
||||
description => "Remove Metric server.",
|
||||
permissions => {
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
}
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Cluster::cfs_lock_file('status.cfg', undef, sub {
|
||||
my $cfg = PVE::Cluster::cfs_read_file('status.cfg');
|
||||
PVE::Cluster::cfs_lock_file(
|
||||
'status.cfg',
|
||||
undef,
|
||||
sub {
|
||||
my $cfg = PVE::Cluster::cfs_read_file('status.cfg');
|
||||
|
||||
my $id = $param->{id};
|
||||
my $id = $param->{id};
|
||||
|
||||
my $plugin_cfg = $cfg->{ids}->{$id};
|
||||
my $plugin_cfg = $cfg->{ids}->{$id};
|
||||
|
||||
my $plugin = PVE::Status::Plugin->lookup($plugin_cfg->{type});
|
||||
my $plugin = PVE::Status::Plugin->lookup($plugin_cfg->{type});
|
||||
|
||||
$plugin->on_delete_hook($id, $plugin_cfg);
|
||||
$plugin->on_delete_hook($id, $plugin_cfg);
|
||||
|
||||
delete $cfg->{ids}->{$id};
|
||||
PVE::Cluster::cfs_write_file('status.cfg', $cfg);
|
||||
});
|
||||
die $@ if $@;
|
||||
delete $cfg->{ids}->{$id};
|
||||
PVE::Cluster::cfs_write_file('status.cfg', $cfg);
|
||||
},
|
||||
);
|
||||
die $@ if $@;
|
||||
|
||||
return;
|
||||
}});
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'export',
|
||||
path => 'export',
|
||||
method => 'GET',
|
||||
protected => 1,
|
||||
description => "Retrieve metrics of the cluster.",
|
||||
permissions => {
|
||||
check => ['perm', '/', ['Sys.Audit']],
|
||||
check => ['perm', '/', ['Sys.Audit']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
'local-only' => {
|
||||
type => 'boolean',
|
||||
description =>
|
||||
'Only return metrics for the current node instead of the whole cluster',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
'start-time' => {
|
||||
type => 'integer',
|
||||
description => 'Only include metrics with a timestamp > start-time.',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
'history' => {
|
||||
type => 'boolean',
|
||||
description => 'Also return historic values.'
|
||||
. ' Returns full available metric history unless `start-time` is also set',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
'local-only' => {
|
||||
type => 'boolean',
|
||||
description =>
|
||||
'Only return metrics for the current node instead of the whole cluster',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
'start-time' => {
|
||||
type => 'integer',
|
||||
description => 'Only include metrics with a timestamp > start-time.',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
'history' => {
|
||||
type => 'boolean',
|
||||
description => 'Also return historic values.'
|
||||
. ' Returns full available metric history unless `start-time` is also set',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
data => {
|
||||
type => 'array',
|
||||
description => 'Array of system metrics. Metrics are sorted by their timestamp.',
|
||||
items => {
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
timestamp => {
|
||||
type => 'integer',
|
||||
description => 'Time at which this metric was observed',
|
||||
},
|
||||
id => {
|
||||
type => 'string',
|
||||
description => "Unique identifier for this metric object,"
|
||||
. " for instance 'node/<nodename>' or"
|
||||
. " 'qemu/<vmid>'."
|
||||
},
|
||||
metric => {
|
||||
type => 'string',
|
||||
description => "Name of the metric.",
|
||||
},
|
||||
value => {
|
||||
type => 'number',
|
||||
description => 'Metric value.',
|
||||
},
|
||||
type => {
|
||||
type => 'string',
|
||||
description => 'Type of the metric.',
|
||||
enum => [qw(gauge counter derive)],
|
||||
}
|
||||
}
|
||||
},
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
data => {
|
||||
type => 'array',
|
||||
description =>
|
||||
'Array of system metrics. Metrics are sorted by their timestamp.',
|
||||
items => {
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
timestamp => {
|
||||
type => 'integer',
|
||||
description => 'Time at which this metric was observed',
|
||||
},
|
||||
id => {
|
||||
type => 'string',
|
||||
description => "Unique identifier for this metric object,"
|
||||
. " for instance 'node/<nodename>' or"
|
||||
. " 'qemu/<vmid>'.",
|
||||
},
|
||||
metric => {
|
||||
type => 'string',
|
||||
description => "Name of the metric.",
|
||||
},
|
||||
value => {
|
||||
type => 'number',
|
||||
description => 'Metric value.',
|
||||
},
|
||||
type => {
|
||||
type => 'string',
|
||||
description => 'Type of the metric.',
|
||||
enum => [qw(gauge counter derive)],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my $local_only = $param->{'local-only'} // 0;
|
||||
my $start = $param->{'start-time'};
|
||||
my $history = $param->{'history'} // 0;
|
||||
my ($param) = @_;
|
||||
my $local_only = $param->{'local-only'} // 0;
|
||||
my $start = $param->{'start-time'};
|
||||
my $history = $param->{'history'} // 0;
|
||||
|
||||
my $now = time();
|
||||
my $now = time();
|
||||
|
||||
my $generations;
|
||||
if ($history) {
|
||||
# Assuming update loop time of pvestatd of 10 seconds.
|
||||
if (defined($start)) {
|
||||
my $delta = $now - $start;
|
||||
$generations = int($delta / 10);
|
||||
} else {
|
||||
$generations = PVE::PullMetric::max_generations();
|
||||
}
|
||||
my $generations;
|
||||
if ($history) {
|
||||
# Assuming update loop time of pvestatd of 10 seconds.
|
||||
if (defined($start)) {
|
||||
my $delta = $now - $start;
|
||||
$generations = int($delta / 10);
|
||||
} else {
|
||||
$generations = PVE::PullMetric::max_generations();
|
||||
}
|
||||
|
||||
} else {
|
||||
$generations = 0;
|
||||
};
|
||||
} else {
|
||||
$generations = 0;
|
||||
}
|
||||
|
||||
my @metrics = @{PVE::PullMetric::get_local_metrics($generations)};
|
||||
my @metrics = @{ PVE::PullMetric::get_local_metrics($generations) };
|
||||
|
||||
if (defined($start)) {
|
||||
@metrics = grep {
|
||||
$_->{timestamp} > ($start)
|
||||
} @metrics;
|
||||
}
|
||||
if (defined($start)) {
|
||||
@metrics = grep {
|
||||
$_->{timestamp} > ($start)
|
||||
} @metrics;
|
||||
}
|
||||
|
||||
my $nodename = PVE::INotify::nodename();
|
||||
my $nodename = PVE::INotify::nodename();
|
||||
|
||||
# Fan out to cluster members
|
||||
# Do NOT remove this check
|
||||
if (!$local_only) {
|
||||
my $members = PVE::Cluster::get_members();
|
||||
# Fan out to cluster members
|
||||
# Do NOT remove this check
|
||||
if (!$local_only) {
|
||||
my $members = PVE::Cluster::get_members();
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my ($user, undef) = PVE::AccessControl::split_tokenid($authuser, 1);
|
||||
my ($user, undef) = PVE::AccessControl::split_tokenid($authuser, 1);
|
||||
|
||||
my $ticket;
|
||||
if ($user) {
|
||||
# Theoretically, we might now bypass token privilege separation, since
|
||||
# we use the regular user instead of the token, but
|
||||
# since we already passed the permission check for this handler,
|
||||
# this should be fine.
|
||||
$ticket = PVE::AccessControl::assemble_ticket($user);
|
||||
} else {
|
||||
$ticket = PVE::AccessControl::assemble_ticket($authuser);
|
||||
}
|
||||
my $ticket;
|
||||
if ($user) {
|
||||
# Theoretically, we might now bypass token privilege separation, since
|
||||
# we use the regular user instead of the token, but
|
||||
# since we already passed the permission check for this handler,
|
||||
# this should be fine.
|
||||
$ticket = PVE::AccessControl::assemble_ticket($user);
|
||||
} else {
|
||||
$ticket = PVE::AccessControl::assemble_ticket($authuser);
|
||||
}
|
||||
|
||||
for my $name (keys %$members) {
|
||||
if ($name eq $nodename) {
|
||||
# Skip own node, for that one we already have the metrics
|
||||
next;
|
||||
}
|
||||
for my $name (keys %$members) {
|
||||
if ($name eq $nodename) {
|
||||
# Skip own node, for that one we already have the metrics
|
||||
next;
|
||||
}
|
||||
|
||||
if (!$members->{$name}->{online}) {
|
||||
next;
|
||||
}
|
||||
if (!$members->{$name}->{online}) {
|
||||
next;
|
||||
}
|
||||
|
||||
my $status = eval {
|
||||
my $fingerprint = PVE::Cluster::get_node_fingerprint($name);
|
||||
my $ip = scalar(PVE::Cluster::remote_node_ip($name));
|
||||
my $status = eval {
|
||||
my $fingerprint = PVE::Cluster::get_node_fingerprint($name);
|
||||
my $ip = scalar(PVE::Cluster::remote_node_ip($name));
|
||||
|
||||
my $conn_args = {
|
||||
protocol => 'https',
|
||||
host => $ip,
|
||||
port => 8006,
|
||||
ticket => $ticket,
|
||||
timeout => 5,
|
||||
};
|
||||
my $conn_args = {
|
||||
protocol => 'https',
|
||||
host => $ip,
|
||||
port => 8006,
|
||||
ticket => $ticket,
|
||||
timeout => 5,
|
||||
};
|
||||
|
||||
$conn_args->{cached_fingerprints} = { $fingerprint => 1 };
|
||||
$conn_args->{cached_fingerprints} = { $fingerprint => 1 };
|
||||
|
||||
my $api_client = PVE::APIClient::LWP->new(%$conn_args);
|
||||
my $api_client = PVE::APIClient::LWP->new(%$conn_args);
|
||||
|
||||
my $params = {
|
||||
# Do NOT remove 'local-only' - potential for request recursion!
|
||||
'local-only' => 1,
|
||||
history => $history,
|
||||
};
|
||||
$params->{'start-time'} = $start if defined($start);
|
||||
my $params = {
|
||||
# Do NOT remove 'local-only' - potential for request recursion!
|
||||
'local-only' => 1,
|
||||
history => $history,
|
||||
};
|
||||
$params->{'start-time'} = $start if defined($start);
|
||||
|
||||
$api_client->get('/cluster/metrics/export', $params);
|
||||
};
|
||||
$api_client->get('/cluster/metrics/export', $params);
|
||||
};
|
||||
|
||||
if ($@) {
|
||||
syslog('warning', "could not fetch metrics from $name: $@");
|
||||
} else {
|
||||
push @metrics, $status->{data}->@*;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($@) {
|
||||
syslog('warning', "could not fetch metrics from $name: $@");
|
||||
} else {
|
||||
push @metrics, $status->{data}->@*;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my @sorted = sort {$a->{timestamp} <=> $b->{timestamp}} @metrics;
|
||||
my @sorted = sort { $a->{timestamp} <=> $b->{timestamp} } @metrics;
|
||||
|
||||
return {
|
||||
data => \@sorted,
|
||||
};
|
||||
return {
|
||||
data => \@sorted,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -16,54 +16,52 @@ use PVE::API2::HA::Status;
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
subclass => "PVE::API2::HA::Resources",
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::HA::Resources",
|
||||
path => 'resources',
|
||||
});
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
subclass => "PVE::API2::HA::Groups",
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::HA::Groups",
|
||||
path => 'groups',
|
||||
});
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
subclass => "PVE::API2::HA::Status",
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::HA::Status",
|
||||
path => 'status',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "Directory index.",
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Audit' ]],
|
||||
check => ['perm', '/', ['Sys.Audit']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
id => { type => 'string' },
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{id}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
id => { type => 'string' },
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{id}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $res = [
|
||||
{ id => 'status' },
|
||||
{ id => 'resources' },
|
||||
{ id => 'groups' },
|
||||
];
|
||||
|
||||
return $res;
|
||||
}});
|
||||
my $res = [
|
||||
{ id => 'status' }, { id => 'resources' }, { id => 'groups' },
|
||||
];
|
||||
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -11,47 +11,46 @@ use PVE::API2::Hardware::USB;
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::Hardware::PCI",
|
||||
path => 'pci',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
subclass => "PVE::API2::Hardware::USB",
|
||||
path => 'usb',
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "Index of hardware types",
|
||||
permissions => {
|
||||
user => 'all',
|
||||
user => 'all',
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => { type => { type => 'string'} },
|
||||
},
|
||||
links => [ { rel => 'child', href => "{type}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => { type => { type => 'string' } },
|
||||
},
|
||||
links => [{ rel => 'child', href => "{type}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $res = [
|
||||
{ type => 'pci' },
|
||||
{ type => 'usb' },
|
||||
];
|
||||
my $res = [
|
||||
{ type => 'pci' }, { type => 'usb' },
|
||||
];
|
||||
|
||||
return $res;
|
||||
}
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use base qw(PVE::RESTHandler);
|
|||
|
||||
my $default_class_blacklist = "05;06;0b";
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'pci_scan',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
|
|
@ -20,154 +20,159 @@ __PACKAGE__->register_method ({
|
|||
protected => 1,
|
||||
proxyto => "node",
|
||||
permissions => {
|
||||
check => ['perm', '/', ['Sys.Audit', 'Sys.Modify'], any => 1],
|
||||
check => ['perm', '/', ['Sys.Audit', 'Sys.Modify'], any => 1],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
'pci-class-blacklist' => {
|
||||
type => 'string',
|
||||
format => 'string-list',
|
||||
default => $default_class_blacklist,
|
||||
optional => 1,
|
||||
description => "A list of blacklisted PCI classes, which will not be returned."
|
||||
." Following are filtered by default: Memory Controller (05), Bridge (06) and"
|
||||
." Processor (0b).",
|
||||
},
|
||||
verbose => {
|
||||
type => 'boolean',
|
||||
default => 1,
|
||||
optional => 1,
|
||||
description => "If disabled, does only print the PCI IDs. Otherwise, additional"
|
||||
." information like vendor and device will be returned.",
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
'pci-class-blacklist' => {
|
||||
type => 'string',
|
||||
format => 'string-list',
|
||||
default => $default_class_blacklist,
|
||||
optional => 1,
|
||||
description => "A list of blacklisted PCI classes, which will not be returned."
|
||||
. " Following are filtered by default: Memory Controller (05), Bridge (06) and"
|
||||
. " Processor (0b).",
|
||||
},
|
||||
verbose => {
|
||||
type => 'boolean',
|
||||
default => 1,
|
||||
optional => 1,
|
||||
description => "If disabled, does only print the PCI IDs. Otherwise, additional"
|
||||
. " information like vendor and device will be returned.",
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
links => [ { rel => 'child', href => "{id}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
description => "The PCI ID.",
|
||||
},
|
||||
class => {
|
||||
type => 'string',
|
||||
description => 'The PCI Class of the device.',
|
||||
},
|
||||
vendor => {
|
||||
type => 'string',
|
||||
description => 'The Vendor ID.',
|
||||
},
|
||||
vendor_name => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
device => {
|
||||
type => 'string',
|
||||
description => 'The Device ID.',
|
||||
},
|
||||
device_name => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
subsystem_vendor => {
|
||||
type => 'string',
|
||||
description => 'The Subsystem Vendor ID.',
|
||||
optional => 1,
|
||||
},
|
||||
subsystem_vendor_name => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
subsystem_device => {
|
||||
type => 'string',
|
||||
description => 'The Subsystem Device ID.',
|
||||
optional => 1,
|
||||
},
|
||||
subsystem_device_name => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
iommugroup => {
|
||||
type => 'integer',
|
||||
description => "The IOMMU group in which the device is in. If no IOMMU group is"
|
||||
." detected, it is set to -1.",
|
||||
},
|
||||
mdev => {
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
description => "If set, marks that the device is capable of creating mediated"
|
||||
." devices.",
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{id}" }],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
id => {
|
||||
type => 'string',
|
||||
description => "The PCI ID.",
|
||||
},
|
||||
class => {
|
||||
type => 'string',
|
||||
description => 'The PCI Class of the device.',
|
||||
},
|
||||
vendor => {
|
||||
type => 'string',
|
||||
description => 'The Vendor ID.',
|
||||
},
|
||||
vendor_name => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
device => {
|
||||
type => 'string',
|
||||
description => 'The Device ID.',
|
||||
},
|
||||
device_name => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
subsystem_vendor => {
|
||||
type => 'string',
|
||||
description => 'The Subsystem Vendor ID.',
|
||||
optional => 1,
|
||||
},
|
||||
subsystem_vendor_name => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
subsystem_device => {
|
||||
type => 'string',
|
||||
description => 'The Subsystem Device ID.',
|
||||
optional => 1,
|
||||
},
|
||||
subsystem_device_name => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
iommugroup => {
|
||||
type => 'integer',
|
||||
description =>
|
||||
"The IOMMU group in which the device is in. If no IOMMU group is"
|
||||
. " detected, it is set to -1.",
|
||||
},
|
||||
mdev => {
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
description =>
|
||||
"If set, marks that the device is capable of creating mediated"
|
||||
. " devices.",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $blacklist = $param->{'pci-class-blacklist'} // $default_class_blacklist;
|
||||
my $class_regex = join('|', PVE::Tools::split_list($blacklist));
|
||||
my $blacklist = $param->{'pci-class-blacklist'} // $default_class_blacklist;
|
||||
my $class_regex = join('|', PVE::Tools::split_list($blacklist));
|
||||
|
||||
my $filter;
|
||||
my $filter;
|
||||
|
||||
if ($class_regex ne '') {
|
||||
$filter = sub {
|
||||
my ($pcidevice) = @_;
|
||||
if ($class_regex ne '') {
|
||||
$filter = sub {
|
||||
my ($pcidevice) = @_;
|
||||
|
||||
if ($pcidevice->{class} =~ m/^0x(?:$class_regex)/) {
|
||||
return 0;
|
||||
}
|
||||
if ($pcidevice->{class} =~ m/^0x(?:$class_regex)/) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
};
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
}
|
||||
|
||||
my $verbose = $param->{verbose} // 1;
|
||||
my $verbose = $param->{verbose} // 1;
|
||||
|
||||
return PVE::SysFSTools::lspci($filter, $verbose);
|
||||
}});
|
||||
return PVE::SysFSTools::lspci($filter, $verbose);
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'pci_index',
|
||||
path => '{pci-id-or-mapping}',
|
||||
method => 'GET',
|
||||
description => "Index of available pci methods",
|
||||
permissions => {
|
||||
user => 'all',
|
||||
user => 'all',
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
'pci-id-or-mapping' => {
|
||||
type => 'string',
|
||||
pattern => '(?:(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F])|([a-zA-Z][a-zA-Z0-9_-]+)',
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
'pci-id-or-mapping' => {
|
||||
type => 'string',
|
||||
pattern =>
|
||||
'(?:(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F])|([a-zA-Z][a-zA-Z0-9_-]+)',
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => { method => { type => 'string'} },
|
||||
},
|
||||
links => [ { rel => 'child', href => "{method}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => { method => { type => 'string' } },
|
||||
},
|
||||
links => [{ rel => 'child', href => "{method}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $res = [
|
||||
{ method => 'mdev' },
|
||||
];
|
||||
my $res = [
|
||||
{ method => 'mdev' },
|
||||
];
|
||||
|
||||
return $res;
|
||||
}});
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'mdevscan',
|
||||
path => '{pci-id-or-mapping}/mdev',
|
||||
method => 'GET',
|
||||
|
|
@ -175,69 +180,73 @@ __PACKAGE__->register_method ({
|
|||
protected => 1,
|
||||
proxyto => "node",
|
||||
permissions => {
|
||||
check => ['perm', '/', ['Sys.Audit', 'Sys.Modify'], any => 1],
|
||||
check => ['perm', '/', ['Sys.Audit', 'Sys.Modify'], any => 1],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
'pci-id-or-mapping' => {
|
||||
type => 'string',
|
||||
pattern => '(?:(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F])|([a-zA-Z][a-zA-Z0-9_-]+)',
|
||||
description => "The PCI ID or mapping to list the mdev types for."
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
'pci-id-or-mapping' => {
|
||||
type => 'string',
|
||||
pattern =>
|
||||
'(?:(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F])|([a-zA-Z][a-zA-Z0-9_-]+)',
|
||||
description => "The PCI ID or mapping to list the mdev types for.",
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
type => {
|
||||
type => 'string',
|
||||
description => "The name of the mdev type.",
|
||||
},
|
||||
available => {
|
||||
type => 'integer',
|
||||
description => "The number of still available instances of this type.",
|
||||
},
|
||||
description => {
|
||||
type => 'string',
|
||||
description => "Additional description of the type."
|
||||
},
|
||||
name => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
description => 'A human readable name for the type.',
|
||||
},
|
||||
},
|
||||
},
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
type => {
|
||||
type => 'string',
|
||||
description => "The name of the mdev type.",
|
||||
},
|
||||
available => {
|
||||
type => 'integer',
|
||||
description => "The number of still available instances of this type.",
|
||||
},
|
||||
description => {
|
||||
type => 'string',
|
||||
description => "Additional description of the type.",
|
||||
},
|
||||
name => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
description => 'A human readable name for the type.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
if ($param->{'pci-id-or-mapping'} =~ m/^(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$/) {
|
||||
return PVE::SysFSTools::get_mdev_types($param->{'pci-id-or-mapping'}); # PCI ID
|
||||
} else {
|
||||
my $mapping = $param->{'pci-id-or-mapping'};
|
||||
if ($param->{'pci-id-or-mapping'} =~
|
||||
m/^(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$/
|
||||
) {
|
||||
return PVE::SysFSTools::get_mdev_types($param->{'pci-id-or-mapping'}); # PCI ID
|
||||
} else {
|
||||
my $mapping = $param->{'pci-id-or-mapping'};
|
||||
|
||||
my $types = {};
|
||||
my $devices = PVE::Mapping::PCI::find_on_current_node($mapping);
|
||||
for my $device ($devices->@*) {
|
||||
my $id = $device->{path};
|
||||
next if $id =~ m/;/; # mdev not supported for multifunction devices
|
||||
my $types = {};
|
||||
my $devices = PVE::Mapping::PCI::find_on_current_node($mapping);
|
||||
for my $device ($devices->@*) {
|
||||
my $id = $device->{path};
|
||||
next if $id =~ m/;/; # mdev not supported for multifunction devices
|
||||
|
||||
my $device_types = PVE::SysFSTools::get_mdev_types($id);
|
||||
my $device_types = PVE::SysFSTools::get_mdev_types($id);
|
||||
|
||||
for my $type_definition ($device_types->@*) {
|
||||
my $type = $type_definition->{type};
|
||||
if (!defined($types->{$type})) {
|
||||
$types->{$type} = $type_definition;
|
||||
}
|
||||
}
|
||||
}
|
||||
for my $type_definition ($device_types->@*) {
|
||||
my $type = $type_definition->{type};
|
||||
if (!defined($types->{$type})) {
|
||||
$types->{$type} = $type_definition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [sort { $a->{type} cmp $b->{type} } values($types->%*)];
|
||||
}
|
||||
return [sort { $a->{type} cmp $b->{type} } values($types->%*)];
|
||||
}
|
||||
|
||||
}});
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,37 +17,37 @@ __PACKAGE__->register_method({
|
|||
protected => 1,
|
||||
proxyto => "node",
|
||||
permissions => {
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
busnum => { type => 'integer'},
|
||||
class => { type => 'integer'},
|
||||
devnum => { type => 'integer'},
|
||||
level => { type => 'integer'},
|
||||
manufacturer => { type => 'string', optional => 1 },
|
||||
port => { type => 'integer'},
|
||||
prodid => { type => 'string'},
|
||||
product => { type => 'string', optional => 1 },
|
||||
serial => { type => 'string', optional => 1 },
|
||||
speed => { type => 'string'},
|
||||
usbpath => { type => 'string', optional => 1},
|
||||
vendid => { type => 'string'},
|
||||
},
|
||||
},
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
busnum => { type => 'integer' },
|
||||
class => { type => 'integer' },
|
||||
devnum => { type => 'integer' },
|
||||
level => { type => 'integer' },
|
||||
manufacturer => { type => 'string', optional => 1 },
|
||||
port => { type => 'integer' },
|
||||
prodid => { type => 'string' },
|
||||
product => { type => 'string', optional => 1 },
|
||||
serial => { type => 'string', optional => 1 },
|
||||
speed => { type => 'string' },
|
||||
usbpath => { type => 'string', optional => 1 },
|
||||
vendid => { type => 'string' },
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
return PVE::SysFSTools::scan_usb();
|
||||
}
|
||||
return PVE::SysFSTools::scan_usb();
|
||||
},
|
||||
});
|
||||
|
|
|
|||
1126
PVE/API2/Network.pm
1126
PVE/API2/Network.pm
File diff suppressed because it is too large
Load diff
|
|
@ -10,21 +10,23 @@ use PVE::Tools qw(extract_param);
|
|||
use base qw(PVE::RESTHandler);
|
||||
|
||||
my $node_config_schema = PVE::NodeConfig::get_nodeconfig_schema();
|
||||
my $node_config_keys = [ sort keys %$node_config_schema ];
|
||||
my $node_config_keys = [sort keys %$node_config_schema];
|
||||
my $node_config_return_properties = {
|
||||
digest => {
|
||||
type => 'string',
|
||||
description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
|
||||
maxLength => 40,
|
||||
optional => 1,
|
||||
type => 'string',
|
||||
description =>
|
||||
'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
|
||||
maxLength => 40,
|
||||
optional => 1,
|
||||
},
|
||||
%$node_config_schema,
|
||||
};
|
||||
my $node_config_properties = {
|
||||
delete => {
|
||||
type => 'string', format => 'pve-configid-list',
|
||||
description => "A list of settings you want to delete.",
|
||||
optional => 1,
|
||||
type => 'string',
|
||||
format => 'pve-configid-list',
|
||||
description => "A list of settings you want to delete.",
|
||||
optional => 1,
|
||||
},
|
||||
node => get_standard_option('pve-node'),
|
||||
%$node_config_return_properties,
|
||||
|
|
@ -36,38 +38,39 @@ __PACKAGE__->register_method({
|
|||
method => 'GET',
|
||||
description => "Get node configuration options.",
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Audit' ]],
|
||||
check => ['perm', '/', ['Sys.Audit']],
|
||||
},
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
property => {
|
||||
type => 'string',
|
||||
description => 'Return only a specific property from the node configuration.',
|
||||
enum => $node_config_keys,
|
||||
optional => 1,
|
||||
default => 'all',
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
property => {
|
||||
type => 'string',
|
||||
description => 'Return only a specific property from the node configuration.',
|
||||
enum => $node_config_keys,
|
||||
optional => 1,
|
||||
default => 'all',
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => "object",
|
||||
properties => $node_config_return_properties,
|
||||
type => "object",
|
||||
properties => $node_config_return_properties,
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $config = PVE::NodeConfig::load_config($param->{node});
|
||||
my $config = PVE::NodeConfig::load_config($param->{node});
|
||||
|
||||
if (defined (my $prop = $param->{property})) {
|
||||
return {} if !exists $config->{$prop};
|
||||
return { $prop => $config->{$prop} };
|
||||
}
|
||||
if (defined(my $prop = $param->{property})) {
|
||||
return {} if !exists $config->{$prop};
|
||||
return { $prop => $config->{$prop} };
|
||||
}
|
||||
|
||||
return $config;
|
||||
}});
|
||||
return $config;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method({
|
||||
name => 'set_options',
|
||||
|
|
@ -75,43 +78,44 @@ __PACKAGE__->register_method({
|
|||
method => 'PUT',
|
||||
description => "Set node configuration options.",
|
||||
permissions => {
|
||||
check => ['perm', '/', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/', ['Sys.Modify']],
|
||||
},
|
||||
protected => 1,
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => $node_config_properties,
|
||||
additionalProperties => 0,
|
||||
properties => $node_config_properties,
|
||||
},
|
||||
returns => { type => "null" },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $delete = extract_param($param, 'delete');
|
||||
my $node = extract_param($param, 'node');
|
||||
my $digest = extract_param($param, 'digest');
|
||||
my $delete = extract_param($param, 'delete');
|
||||
my $node = extract_param($param, 'node');
|
||||
my $digest = extract_param($param, 'digest');
|
||||
|
||||
my $code = sub {
|
||||
my $conf = PVE::NodeConfig::load_config($node);
|
||||
my $code = sub {
|
||||
my $conf = PVE::NodeConfig::load_config($node);
|
||||
|
||||
PVE::Tools::assert_if_modified($digest, $conf->{digest});
|
||||
PVE::Tools::assert_if_modified($digest, $conf->{digest});
|
||||
|
||||
foreach my $opt (sort keys %$param) {
|
||||
$conf->{$opt} = $param->{$opt};
|
||||
}
|
||||
foreach my $opt (sort keys %$param) {
|
||||
$conf->{$opt} = $param->{$opt};
|
||||
}
|
||||
|
||||
foreach my $opt (PVE::Tools::split_list($delete)) {
|
||||
delete $conf->{$opt};
|
||||
};
|
||||
foreach my $opt (PVE::Tools::split_list($delete)) {
|
||||
delete $conf->{$opt};
|
||||
}
|
||||
|
||||
PVE::NodeConfig::verify_conf($conf);
|
||||
PVE::NodeConfig::write_config($node, $conf);
|
||||
};
|
||||
PVE::NodeConfig::verify_conf($conf);
|
||||
PVE::NodeConfig::write_config($node, $conf);
|
||||
};
|
||||
|
||||
PVE::NodeConfig::lock_config($node, $code);
|
||||
die $@ if $@;
|
||||
PVE::NodeConfig::lock_config($node, $code);
|
||||
die $@ if $@;
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
3556
PVE/API2/Nodes.pm
3556
PVE/API2/Nodes.pm
File diff suppressed because it is too large
Load diff
746
PVE/API2/Pool.pm
746
PVE/API2/Pool.pm
|
|
@ -16,487 +16,521 @@ use PVE::RESTHandler;
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "List pools or get pool configuration.",
|
||||
permissions => {
|
||||
description => "List all pools where you have Pool.Audit permissions on /pool/<pool>, or the pool specific with {poolid}",
|
||||
user => 'all',
|
||||
description =>
|
||||
"List all pools where you have Pool.Audit permissions on /pool/<pool>, or the pool specific with {poolid}",
|
||||
user => 'all',
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
poolid => {
|
||||
type => 'string',
|
||||
format => 'pve-poolid',
|
||||
optional => 1,
|
||||
},
|
||||
type => {
|
||||
type => 'string',
|
||||
enum => [ 'qemu', 'lxc', 'storage' ],
|
||||
optional => 1,
|
||||
requires => 'poolid',
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
poolid => {
|
||||
type => 'string',
|
||||
format => 'pve-poolid',
|
||||
optional => 1,
|
||||
},
|
||||
type => {
|
||||
type => 'string',
|
||||
enum => ['qemu', 'lxc', 'storage'],
|
||||
optional => 1,
|
||||
requires => 'poolid',
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
poolid => { type => 'string' },
|
||||
comment => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
members => {
|
||||
type => 'array',
|
||||
optional => 1,
|
||||
items => {
|
||||
type => "object",
|
||||
additionalProperties => 1,
|
||||
properties => {
|
||||
type => {
|
||||
type => 'string',
|
||||
enum => [ 'qemu', 'lxc', 'openvz', 'storage' ],
|
||||
},
|
||||
id => {
|
||||
type => 'string',
|
||||
},
|
||||
node => {
|
||||
type => 'string',
|
||||
},
|
||||
vmid => {
|
||||
type => 'integer',
|
||||
optional => 1,
|
||||
},
|
||||
storage => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{poolid}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
poolid => { type => 'string' },
|
||||
comment => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
members => {
|
||||
type => 'array',
|
||||
optional => 1,
|
||||
items => {
|
||||
type => "object",
|
||||
additionalProperties => 1,
|
||||
properties => {
|
||||
type => {
|
||||
type => 'string',
|
||||
enum => ['qemu', 'lxc', 'openvz', 'storage'],
|
||||
},
|
||||
id => {
|
||||
type => 'string',
|
||||
},
|
||||
node => {
|
||||
type => 'string',
|
||||
},
|
||||
vmid => {
|
||||
type => 'integer',
|
||||
optional => 1,
|
||||
},
|
||||
storage => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{poolid}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $usercfg = $rpcenv->{user_cfg};
|
||||
my $usercfg = $rpcenv->{user_cfg};
|
||||
|
||||
my $res = [];
|
||||
if (my $poolid = $param->{poolid}) {
|
||||
$rpcenv->check($authuser, "/pool/$poolid", [ 'Pool.Audit' ], 1);
|
||||
my $res = [];
|
||||
if (my $poolid = $param->{poolid}) {
|
||||
$rpcenv->check($authuser, "/pool/$poolid", ['Pool.Audit'], 1);
|
||||
|
||||
my $vmlist = PVE::Cluster::get_vmlist() || {};
|
||||
my $idlist = $vmlist->{ids} || {};
|
||||
my $vmlist = PVE::Cluster::get_vmlist() || {};
|
||||
my $idlist = $vmlist->{ids} || {};
|
||||
|
||||
my $rrd = PVE::Cluster::rrd_dump();
|
||||
my $rrd = PVE::Cluster::rrd_dump();
|
||||
|
||||
my $pool_config = $usercfg->{pools}->{$poolid};
|
||||
my $pool_config = $usercfg->{pools}->{$poolid};
|
||||
|
||||
die "pool '$poolid' does not exist\n" if !$pool_config;
|
||||
die "pool '$poolid' does not exist\n" if !$pool_config;
|
||||
|
||||
my $members = [];
|
||||
for my $vmid (sort keys %{$pool_config->{vms}}) {
|
||||
my $vmdata = $idlist->{$vmid};
|
||||
next if !$vmdata || defined($param->{type}) && $param->{type} ne $vmdata->{type};
|
||||
my $entry = PVE::API2Tools::extract_vm_stats($vmid, $vmdata, $rrd);
|
||||
push @$members, $entry;
|
||||
}
|
||||
my $members = [];
|
||||
for my $vmid (sort keys %{ $pool_config->{vms} }) {
|
||||
my $vmdata = $idlist->{$vmid};
|
||||
next
|
||||
if !$vmdata || defined($param->{type}) && $param->{type} ne $vmdata->{type};
|
||||
my $entry = PVE::API2Tools::extract_vm_stats($vmid, $vmdata, $rrd);
|
||||
push @$members, $entry;
|
||||
}
|
||||
|
||||
my $nodename = PVE::INotify::nodename();
|
||||
my $cfg = PVE::Storage::config();
|
||||
if (!defined($param->{type}) || $param->{type} eq 'storage') {
|
||||
for my $storeid (sort keys %{$pool_config->{storage}}) {
|
||||
my $scfg = PVE::Storage::storage_config ($cfg, $storeid, 1);
|
||||
next if !$scfg;
|
||||
my $nodename = PVE::INotify::nodename();
|
||||
my $cfg = PVE::Storage::config();
|
||||
if (!defined($param->{type}) || $param->{type} eq 'storage') {
|
||||
for my $storeid (sort keys %{ $pool_config->{storage} }) {
|
||||
my $scfg = PVE::Storage::storage_config($cfg, $storeid, 1);
|
||||
next if !$scfg;
|
||||
|
||||
my $storage_node = $nodename; # prefer local node
|
||||
if ($scfg->{nodes} && !$scfg->{nodes}->{$storage_node}) {
|
||||
for my $node (sort keys(%{$scfg->{nodes}})) {
|
||||
$storage_node = $node;
|
||||
last;
|
||||
}
|
||||
}
|
||||
my $storage_node = $nodename; # prefer local node
|
||||
if ($scfg->{nodes} && !$scfg->{nodes}->{$storage_node}) {
|
||||
for my $node (sort keys(%{ $scfg->{nodes} })) {
|
||||
$storage_node = $node;
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
my $entry = PVE::API2Tools::extract_storage_stats($storeid, $scfg, $storage_node, $rrd);
|
||||
push @$members, $entry;
|
||||
}
|
||||
}
|
||||
my $entry =
|
||||
PVE::API2Tools::extract_storage_stats($storeid, $scfg, $storage_node, $rrd);
|
||||
push @$members, $entry;
|
||||
}
|
||||
}
|
||||
|
||||
my $pool_info = {
|
||||
members => $members,
|
||||
};
|
||||
$pool_info->{comment} = $pool_config->{comment} if defined($pool_config->{comment});
|
||||
$pool_info->{poolid} = $poolid;
|
||||
my $pool_info = {
|
||||
members => $members,
|
||||
};
|
||||
$pool_info->{comment} = $pool_config->{comment} if defined($pool_config->{comment});
|
||||
$pool_info->{poolid} = $poolid;
|
||||
|
||||
push @$res, $pool_info;
|
||||
} else {
|
||||
for my $pool (sort keys %{$usercfg->{pools}}) {
|
||||
next if !$rpcenv->check($authuser, "/pool/$pool", [ 'Pool.Audit' ], 1);
|
||||
push @$res, $pool_info;
|
||||
} else {
|
||||
for my $pool (sort keys %{ $usercfg->{pools} }) {
|
||||
next if !$rpcenv->check($authuser, "/pool/$pool", ['Pool.Audit'], 1);
|
||||
|
||||
my $entry = { poolid => $pool };
|
||||
my $pool_config = $usercfg->{pools}->{$pool};
|
||||
$entry->{comment} = $pool_config->{comment} if defined($pool_config->{comment});
|
||||
push @$res, $entry;
|
||||
}
|
||||
}
|
||||
my $entry = { poolid => $pool };
|
||||
my $pool_config = $usercfg->{pools}->{$pool};
|
||||
$entry->{comment} = $pool_config->{comment} if defined($pool_config->{comment});
|
||||
push @$res, $entry;
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}});
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'create_pool',
|
||||
protected => 1,
|
||||
path => '',
|
||||
method => 'POST',
|
||||
permissions => {
|
||||
check => ['perm', '/pool/{poolid}', ['Pool.Allocate']],
|
||||
check => ['perm', '/pool/{poolid}', ['Pool.Allocate']],
|
||||
},
|
||||
description => "Create new pool.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
poolid => {
|
||||
type => 'string',
|
||||
format => 'pve-poolid',
|
||||
},
|
||||
comment => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
poolid => {
|
||||
type => 'string',
|
||||
format => 'pve-poolid',
|
||||
},
|
||||
comment => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::AccessControl::lock_user_config(sub {
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
my $pool = $param->{poolid};
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
my $pool = $param->{poolid};
|
||||
|
||||
die "pool '$pool' already exists\n" if $usercfg->{pools}->{$pool};
|
||||
if ($pool =~ m!^(.*)/[^/]+$!) {
|
||||
my $parent = $1;
|
||||
die "parent '$parent' of pool '$pool' does not exist\n"
|
||||
if !defined($usercfg->{pools}->{$parent});
|
||||
}
|
||||
die "pool '$pool' already exists\n" if $usercfg->{pools}->{$pool};
|
||||
if ($pool =~ m!^(.*)/[^/]+$!) {
|
||||
my $parent = $1;
|
||||
die "parent '$parent' of pool '$pool' does not exist\n"
|
||||
if !defined($usercfg->{pools}->{$parent});
|
||||
}
|
||||
|
||||
$usercfg->{pools}->{$pool} = {
|
||||
vms => {},
|
||||
storage => {},
|
||||
};
|
||||
$usercfg->{pools}->{$pool} = {
|
||||
vms => {},
|
||||
storage => {},
|
||||
};
|
||||
|
||||
$usercfg->{pools}->{$pool}->{comment} = $param->{comment} if $param->{comment};
|
||||
$usercfg->{pools}->{$pool}->{comment} = $param->{comment} if $param->{comment};
|
||||
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
}, "create pool failed");
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
},
|
||||
"create pool failed",
|
||||
);
|
||||
|
||||
return;
|
||||
}});
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'update_pool_deprecated',
|
||||
protected => 1,
|
||||
path => '{poolid}',
|
||||
method => 'PUT',
|
||||
permissions => {
|
||||
description => "You also need the right to modify permissions on any object you add/delete.",
|
||||
check => ['perm', '/pool/{poolid}', ['Pool.Allocate']],
|
||||
description =>
|
||||
"You also need the right to modify permissions on any object you add/delete.",
|
||||
check => ['perm', '/pool/{poolid}', ['Pool.Allocate']],
|
||||
},
|
||||
description => "Update pool data (deprecated, no support for nested pools - use 'PUT /pools/?poolid={poolid}' instead).",
|
||||
description =>
|
||||
"Update pool data (deprecated, no support for nested pools - use 'PUT /pools/?poolid={poolid}' instead).",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
poolid => { type => 'string', format => 'pve-poolid' },
|
||||
comment => { type => 'string', optional => 1 },
|
||||
vms => {
|
||||
description => 'List of guest VMIDs to add or remove from this pool.',
|
||||
type => 'string', format => 'pve-vmid-list',
|
||||
optional => 1,
|
||||
},
|
||||
storage => {
|
||||
description => 'List of storage IDs to add or remove from this pool.',
|
||||
type => 'string', format => 'pve-storage-id-list',
|
||||
optional => 1,
|
||||
},
|
||||
'allow-move' => {
|
||||
description => 'Allow adding a guest even if already in another pool.'
|
||||
.' The guest will be removed from its current pool and added to this one.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
delete => {
|
||||
description => 'Remove the passed VMIDs and/or storage IDs instead of adding them.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
poolid => { type => 'string', format => 'pve-poolid' },
|
||||
comment => { type => 'string', optional => 1 },
|
||||
vms => {
|
||||
description => 'List of guest VMIDs to add or remove from this pool.',
|
||||
type => 'string',
|
||||
format => 'pve-vmid-list',
|
||||
optional => 1,
|
||||
},
|
||||
storage => {
|
||||
description => 'List of storage IDs to add or remove from this pool.',
|
||||
type => 'string',
|
||||
format => 'pve-storage-id-list',
|
||||
optional => 1,
|
||||
},
|
||||
'allow-move' => {
|
||||
description => 'Allow adding a guest even if already in another pool.'
|
||||
. ' The guest will be removed from its current pool and added to this one.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
delete => {
|
||||
description =>
|
||||
'Remove the passed VMIDs and/or storage IDs instead of adding them.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
return __PACKAGE__->update_pool($param);
|
||||
}});
|
||||
return __PACKAGE__->update_pool($param);
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'update_pool',
|
||||
protected => 1,
|
||||
path => '',
|
||||
method => 'PUT',
|
||||
permissions => {
|
||||
description => "You also need the right to modify permissions on any object you add/delete.",
|
||||
check => ['perm', '/pool/{poolid}', ['Pool.Allocate']],
|
||||
description =>
|
||||
"You also need the right to modify permissions on any object you add/delete.",
|
||||
check => ['perm', '/pool/{poolid}', ['Pool.Allocate']],
|
||||
},
|
||||
description => "Update pool.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
poolid => { type => 'string', format => 'pve-poolid' },
|
||||
comment => { type => 'string', optional => 1 },
|
||||
vms => {
|
||||
description => 'List of guest VMIDs to add or remove from this pool.',
|
||||
type => 'string', format => 'pve-vmid-list',
|
||||
optional => 1,
|
||||
},
|
||||
storage => {
|
||||
description => 'List of storage IDs to add or remove from this pool.',
|
||||
type => 'string', format => 'pve-storage-id-list',
|
||||
optional => 1,
|
||||
},
|
||||
'allow-move' => {
|
||||
description => 'Allow adding a guest even if already in another pool.'
|
||||
.' The guest will be removed from its current pool and added to this one.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
delete => {
|
||||
description => 'Remove the passed VMIDs and/or storage IDs instead of adding them.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
poolid => { type => 'string', format => 'pve-poolid' },
|
||||
comment => { type => 'string', optional => 1 },
|
||||
vms => {
|
||||
description => 'List of guest VMIDs to add or remove from this pool.',
|
||||
type => 'string',
|
||||
format => 'pve-vmid-list',
|
||||
optional => 1,
|
||||
},
|
||||
storage => {
|
||||
description => 'List of storage IDs to add or remove from this pool.',
|
||||
type => 'string',
|
||||
format => 'pve-storage-id-list',
|
||||
optional => 1,
|
||||
},
|
||||
'allow-move' => {
|
||||
description => 'Allow adding a guest even if already in another pool.'
|
||||
. ' The guest will be removed from its current pool and added to this one.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
delete => {
|
||||
description =>
|
||||
'Remove the passed VMIDs and/or storage IDs instead of adding them.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
PVE::AccessControl::lock_user_config(sub {
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
my $pool = $param->{poolid};
|
||||
my $pool_config = $usercfg->{pools}->{$pool};
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
my $pool = $param->{poolid};
|
||||
my $pool_config = $usercfg->{pools}->{$pool};
|
||||
|
||||
die "pool '$pool' does not exist\n" if !$pool_config;
|
||||
die "pool '$pool' does not exist\n" if !$pool_config;
|
||||
|
||||
$pool_config->{comment} = $param->{comment} if defined($param->{comment});
|
||||
$pool_config->{comment} = $param->{comment} if defined($param->{comment});
|
||||
|
||||
if (defined($param->{vms})) {
|
||||
for my $vmid (PVE::Tools::split_list($param->{vms})) {
|
||||
$rpcenv->check_perm_modify($authuser, "/vms/$vmid");
|
||||
if (defined($param->{vms})) {
|
||||
for my $vmid (PVE::Tools::split_list($param->{vms})) {
|
||||
$rpcenv->check_perm_modify($authuser, "/vms/$vmid");
|
||||
|
||||
if ($param->{delete}) {
|
||||
die "VM $vmid is not a pool member\n" if !$pool_config->{vms}->{$vmid};
|
||||
delete $pool_config->{vms}->{$vmid};
|
||||
delete $usercfg->{vms}->{$vmid};
|
||||
} else {
|
||||
die "VM $vmid is already a pool member\n" if $pool_config->{vms}->{$vmid};
|
||||
if (defined(my $existing_pool = $usercfg->{vms}->{$vmid})) {
|
||||
die "VM $vmid belongs already to pool '$existing_pool' and 'allow-move' is not set\n"
|
||||
if !$param->{'allow-move'};
|
||||
if ($param->{delete}) {
|
||||
die "VM $vmid is not a pool member\n"
|
||||
if !$pool_config->{vms}->{$vmid};
|
||||
delete $pool_config->{vms}->{$vmid};
|
||||
delete $usercfg->{vms}->{$vmid};
|
||||
} else {
|
||||
die "VM $vmid is already a pool member\n"
|
||||
if $pool_config->{vms}->{$vmid};
|
||||
if (defined(my $existing_pool = $usercfg->{vms}->{$vmid})) {
|
||||
die
|
||||
"VM $vmid belongs already to pool '$existing_pool' and 'allow-move' is not set\n"
|
||||
if !$param->{'allow-move'};
|
||||
|
||||
$rpcenv->check($authuser, "/pool/$existing_pool", ['Pool.Allocate']);
|
||||
delete $usercfg->{pools}->{$existing_pool}->{vms}->{$vmid};
|
||||
}
|
||||
$pool_config->{vms}->{$vmid} = 1;
|
||||
$usercfg->{vms}->{$vmid} = $pool;
|
||||
}
|
||||
}
|
||||
}
|
||||
$rpcenv->check(
|
||||
$authuser, "/pool/$existing_pool", ['Pool.Allocate'],
|
||||
);
|
||||
delete $usercfg->{pools}->{$existing_pool}->{vms}->{$vmid};
|
||||
}
|
||||
$pool_config->{vms}->{$vmid} = 1;
|
||||
$usercfg->{vms}->{$vmid} = $pool;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (defined($param->{storage})) {
|
||||
for my $storeid (PVE::Tools::split_list($param->{storage})) {
|
||||
$rpcenv->check_perm_modify($authuser, "/storage/$storeid");
|
||||
if (defined($param->{storage})) {
|
||||
for my $storeid (PVE::Tools::split_list($param->{storage})) {
|
||||
$rpcenv->check_perm_modify($authuser, "/storage/$storeid");
|
||||
|
||||
if ($param->{delete}) {
|
||||
die "Storage '$storeid' is not a pool member\n"
|
||||
if !$pool_config->{storage}->{$storeid};
|
||||
delete $pool_config->{storage}->{$storeid};
|
||||
} else {
|
||||
die "Storage '$storeid' is already a pool member\n"
|
||||
if $pool_config->{storage}->{$storeid};
|
||||
if ($param->{delete}) {
|
||||
die "Storage '$storeid' is not a pool member\n"
|
||||
if !$pool_config->{storage}->{$storeid};
|
||||
delete $pool_config->{storage}->{$storeid};
|
||||
} else {
|
||||
die "Storage '$storeid' is already a pool member\n"
|
||||
if $pool_config->{storage}->{$storeid};
|
||||
|
||||
$pool_config->{storage}->{$storeid} = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
$pool_config->{storage}->{$storeid} = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
}, "update pools failed");
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
},
|
||||
"update pools failed",
|
||||
);
|
||||
|
||||
return;
|
||||
}});
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'read_pool',
|
||||
path => '{poolid}',
|
||||
method => 'GET',
|
||||
permissions => {
|
||||
check => ['perm', '/pool/{poolid}', ['Pool.Audit']],
|
||||
check => ['perm', '/pool/{poolid}', ['Pool.Audit']],
|
||||
},
|
||||
description => "Get pool configuration (deprecated, no support for nested pools, use 'GET /pools/?poolid={poolid}').",
|
||||
description =>
|
||||
"Get pool configuration (deprecated, no support for nested pools, use 'GET /pools/?poolid={poolid}').",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
poolid => {
|
||||
type => 'string',
|
||||
format => 'pve-poolid',
|
||||
},
|
||||
type => {
|
||||
type => 'string',
|
||||
enum => [ 'qemu', 'lxc', 'storage' ],
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
poolid => {
|
||||
type => 'string',
|
||||
format => 'pve-poolid',
|
||||
},
|
||||
type => {
|
||||
type => 'string',
|
||||
enum => ['qemu', 'lxc', 'storage'],
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => "object",
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
comment => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
members => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
additionalProperties => 1,
|
||||
properties => {
|
||||
type => {
|
||||
type => 'string',
|
||||
enum => [ 'qemu', 'lxc', 'openvz', 'storage' ],
|
||||
},
|
||||
id => {
|
||||
type => 'string',
|
||||
},
|
||||
node => {
|
||||
type => 'string',
|
||||
},
|
||||
vmid => {
|
||||
type => 'integer',
|
||||
optional => 1,
|
||||
},
|
||||
storage => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type => "object",
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
comment => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
members => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
additionalProperties => 1,
|
||||
properties => {
|
||||
type => {
|
||||
type => 'string',
|
||||
enum => ['qemu', 'lxc', 'openvz', 'storage'],
|
||||
},
|
||||
id => {
|
||||
type => 'string',
|
||||
},
|
||||
node => {
|
||||
type => 'string',
|
||||
},
|
||||
vmid => {
|
||||
type => 'integer',
|
||||
optional => 1,
|
||||
},
|
||||
storage => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $pool_info = __PACKAGE__->index($param);
|
||||
return $pool_info->[0];
|
||||
}});
|
||||
my $pool_info = __PACKAGE__->index($param);
|
||||
return $pool_info->[0];
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'delete_pool_deprecated',
|
||||
protected => 1,
|
||||
path => '{poolid}',
|
||||
method => 'DELETE',
|
||||
permissions => {
|
||||
description => "You can only delete empty pools (no members).",
|
||||
check => ['perm', '/pool/{poolid}', ['Pool.Allocate']],
|
||||
description => "You can only delete empty pools (no members).",
|
||||
check => ['perm', '/pool/{poolid}', ['Pool.Allocate']],
|
||||
},
|
||||
description => "Delete pool (deprecated, no support for nested pools, use 'DELETE /pools/?poolid={poolid}').",
|
||||
description =>
|
||||
"Delete pool (deprecated, no support for nested pools, use 'DELETE /pools/?poolid={poolid}').",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
poolid => { type => 'string', format => 'pve-poolid' },
|
||||
}
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
poolid => { type => 'string', format => 'pve-poolid' },
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
return __PACKAGE__->delete_pool($param);
|
||||
}});
|
||||
return __PACKAGE__->delete_pool($param);
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'delete_pool',
|
||||
protected => 1,
|
||||
path => '',
|
||||
method => 'DELETE',
|
||||
permissions => {
|
||||
description => "You can only delete empty pools (no members).",
|
||||
check => ['perm', '/pool/{poolid}', ['Pool.Allocate']],
|
||||
description => "You can only delete empty pools (no members).",
|
||||
check => ['perm', '/pool/{poolid}', ['Pool.Allocate']],
|
||||
},
|
||||
description => "Delete pool.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
poolid => { type => 'string', format => 'pve-poolid' },
|
||||
}
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
poolid => { type => 'string', format => 'pve-poolid' },
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
PVE::AccessControl::lock_user_config(sub {
|
||||
my $vmlist = PVE::Cluster::get_vmlist() || {};
|
||||
my $idlist = $vmlist->{ids} || {};
|
||||
PVE::AccessControl::lock_user_config(
|
||||
sub {
|
||||
my $vmlist = PVE::Cluster::get_vmlist() || {};
|
||||
my $idlist = $vmlist->{ids} || {};
|
||||
|
||||
my $storecfg = PVE::Storage::config();
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
my $storecfg = PVE::Storage::config();
|
||||
my $usercfg = cfs_read_file("user.cfg");
|
||||
|
||||
my $pool = $param->{poolid};
|
||||
my $pool = $param->{poolid};
|
||||
|
||||
my $pool_config = $usercfg->{pools}->{$pool};
|
||||
die "pool '$pool' does not exist\n" if !$pool_config;
|
||||
for my $subpool (sort keys %{$pool_config->{pools}}) {
|
||||
die "pool '$pool' is not empty (contains pool '$subpool')\n";
|
||||
}
|
||||
my $pool_config = $usercfg->{pools}->{$pool};
|
||||
die "pool '$pool' does not exist\n" if !$pool_config;
|
||||
for my $subpool (sort keys %{ $pool_config->{pools} }) {
|
||||
die "pool '$pool' is not empty (contains pool '$subpool')\n";
|
||||
}
|
||||
|
||||
for my $vmid (sort keys %{$pool_config->{vms}}) {
|
||||
next if !$idlist->{$vmid}; # ignore destroyed guests
|
||||
die "pool '$pool' is not empty (contains VM $vmid)\n";
|
||||
}
|
||||
for my $vmid (sort keys %{ $pool_config->{vms} }) {
|
||||
next if !$idlist->{$vmid}; # ignore destroyed guests
|
||||
die "pool '$pool' is not empty (contains VM $vmid)\n";
|
||||
}
|
||||
|
||||
for my $storeid (sort keys %{$pool_config->{storage}}) {
|
||||
next if !PVE::Storage::storage_config ($storecfg, $storeid, 1);
|
||||
die "pool '$pool' is not empty (contains storage '$storeid')\n";
|
||||
}
|
||||
for my $storeid (sort keys %{ $pool_config->{storage} }) {
|
||||
next if !PVE::Storage::storage_config($storecfg, $storeid, 1);
|
||||
die "pool '$pool' is not empty (contains storage '$storeid')\n";
|
||||
}
|
||||
|
||||
delete ($usercfg->{pools}->{$pool});
|
||||
PVE::AccessControl::delete_pool_acl($pool, $usercfg);
|
||||
delete($usercfg->{pools}->{$pool});
|
||||
PVE::AccessControl::delete_pool_acl($pool, $usercfg);
|
||||
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
}, "delete pool failed");
|
||||
cfs_write_file("user.cfg", $usercfg);
|
||||
},
|
||||
"delete pool failed",
|
||||
);
|
||||
|
||||
return;
|
||||
}});
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -27,11 +27,11 @@ our $lookup_guest_class = sub {
|
|||
my ($vmtype) = @_;
|
||||
|
||||
if ($vmtype eq 'qemu') {
|
||||
return 'PVE::QemuConfig';
|
||||
return 'PVE::QemuConfig';
|
||||
} elsif ($vmtype eq 'lxc') {
|
||||
return 'PVE::LXC::Config';
|
||||
return 'PVE::LXC::Config';
|
||||
} else {
|
||||
die "unknown guest type '$vmtype' - internal error";
|
||||
die "unknown guest type '$vmtype' - internal error";
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -42,38 +42,37 @@ sub run_single_job {
|
|||
my $local_node = PVE::INotify::nodename();
|
||||
|
||||
my $code = sub {
|
||||
$now //= time();
|
||||
$now //= time();
|
||||
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
|
||||
my $jobcfg = $cfg->{ids}->{$jobid};
|
||||
die "no such job '$jobid'\n" if !$jobcfg;
|
||||
my $jobcfg = $cfg->{ids}->{$jobid};
|
||||
die "no such job '$jobid'\n" if !$jobcfg;
|
||||
|
||||
die "internal error - not implemented" if $jobcfg->{type} ne 'local';
|
||||
die "internal error - not implemented" if $jobcfg->{type} ne 'local';
|
||||
|
||||
die "job '$jobid' is disabled\n" if $jobcfg->{disable};
|
||||
die "job '$jobid' is disabled\n" if $jobcfg->{disable};
|
||||
|
||||
my $vms = PVE::Cluster::get_vmlist();
|
||||
my $vmid = $jobcfg->{guest};
|
||||
my $vms = PVE::Cluster::get_vmlist();
|
||||
my $vmid = $jobcfg->{guest};
|
||||
|
||||
die "no such guest '$vmid'\n" if !$vms->{ids}->{$vmid};
|
||||
die "no such guest '$vmid'\n" if !$vms->{ids}->{$vmid};
|
||||
|
||||
die "guest '$vmid' is not on local node\n"
|
||||
if $vms->{ids}->{$vmid}->{node} ne $local_node;
|
||||
die "guest '$vmid' is not on local node\n"
|
||||
if $vms->{ids}->{$vmid}->{node} ne $local_node;
|
||||
|
||||
die "unable to sync to local node\n" if $jobcfg->{target} eq $local_node;
|
||||
die "unable to sync to local node\n" if $jobcfg->{target} eq $local_node;
|
||||
|
||||
my $vmtype = $vms->{ids}->{$vmid}->{type};
|
||||
my $vmtype = $vms->{ids}->{$vmid}->{type};
|
||||
|
||||
my $guest_class = $lookup_guest_class->($vmtype);
|
||||
PVE::Replication::run_replication($guest_class, $jobcfg, $now, $now, $logfunc);
|
||||
my $guest_class = $lookup_guest_class->($vmtype);
|
||||
PVE::Replication::run_replication($guest_class, $jobcfg, $now, $now, $logfunc);
|
||||
};
|
||||
|
||||
my $res = PVE::Tools::lock_file($pvesr_lock_path, 60, $code);
|
||||
die $@ if $@;
|
||||
}
|
||||
|
||||
|
||||
# TODO: below two should probably part of the general job framework/plugin system
|
||||
my sub _should_mail_at_failcount {
|
||||
my ($fail_count) = @_;
|
||||
|
|
@ -87,11 +86,10 @@ my sub _should_mail_at_failcount {
|
|||
# failing job is re-tried every half hour, try to send one mail after 1, 2, 4, 8, etc. days
|
||||
my $i = 1;
|
||||
while ($i * 48 < $fail_count) {
|
||||
$i = $i * 2;
|
||||
$i = $i * 2;
|
||||
}
|
||||
return $i * 48 == $fail_count;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
my sub _handle_job_err {
|
||||
my ($job, $err, $mail) = @_;
|
||||
|
|
@ -121,18 +119,16 @@ my sub _handle_job_err {
|
|||
$template_data->{"error"} = $err;
|
||||
|
||||
my $metadata_fields = {
|
||||
type => "replication",
|
||||
"job-id" => $job->{id},
|
||||
# Hostname (without domain part)
|
||||
hostname => PVE::INotify::nodename(),
|
||||
type => "replication",
|
||||
"job-id" => $job->{id},
|
||||
# Hostname (without domain part)
|
||||
hostname => PVE::INotify::nodename(),
|
||||
};
|
||||
|
||||
eval {
|
||||
PVE::Notify::error(
|
||||
"replication",
|
||||
$template_data,
|
||||
$metadata_fields
|
||||
);
|
||||
PVE::Notify::error(
|
||||
"replication", $template_data, $metadata_fields,
|
||||
);
|
||||
|
||||
};
|
||||
warn ": $@" if $@;
|
||||
|
|
@ -145,22 +141,24 @@ sub run_jobs {
|
|||
my $iteration = $now // time();
|
||||
|
||||
my $code = sub {
|
||||
my $start_time = $now // time();
|
||||
my $start_time = $now // time();
|
||||
|
||||
PVE::ReplicationState::purge_old_states();
|
||||
PVE::ReplicationState::purge_old_states();
|
||||
|
||||
while (my $jobcfg = PVE::ReplicationState::get_next_job($iteration, $start_time)) {
|
||||
my $guest_class = $lookup_guest_class->($jobcfg->{vmtype});
|
||||
while (my $jobcfg = PVE::ReplicationState::get_next_job($iteration, $start_time)) {
|
||||
my $guest_class = $lookup_guest_class->($jobcfg->{vmtype});
|
||||
|
||||
eval {
|
||||
PVE::Replication::run_replication($guest_class, $jobcfg, $iteration, $start_time, $logfunc, $verbose);
|
||||
};
|
||||
if (my $err = $@) {
|
||||
_handle_job_err($jobcfg, $err, $mail);
|
||||
}
|
||||
eval {
|
||||
PVE::Replication::run_replication(
|
||||
$guest_class, $jobcfg, $iteration, $start_time, $logfunc, $verbose,
|
||||
);
|
||||
};
|
||||
if (my $err = $@) {
|
||||
_handle_job_err($jobcfg, $err, $mail);
|
||||
}
|
||||
|
||||
$start_time = $now // time();
|
||||
}
|
||||
$start_time = $now // time();
|
||||
}
|
||||
};
|
||||
|
||||
my $res = PVE::Tools::lock_file($pvesr_lock_path, 60, $code);
|
||||
|
|
@ -177,215 +175,224 @@ my $extract_job_status = sub {
|
|||
$data->{id} = $jobid;
|
||||
|
||||
foreach my $k (qw(last_sync last_try fail_count error duration)) {
|
||||
$data->{$k} = $state->{$k} if defined($state->{$k});
|
||||
$data->{$k} = $state->{$k} if defined($state->{$k});
|
||||
}
|
||||
|
||||
if ($state->{pid} && $state->{ptime}) {
|
||||
if (PVE::ProcFSTools::check_process_running($state->{pid}, $state->{ptime})) {
|
||||
$data->{pid} = $state->{pid};
|
||||
}
|
||||
if (PVE::ProcFSTools::check_process_running($state->{pid}, $state->{ptime})) {
|
||||
$data->{pid} = $state->{pid};
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
};
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'status',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "List status of all replication jobs on this node.",
|
||||
permissions => {
|
||||
description => "Requires the VM.Audit permission on /vms/<vmid>.",
|
||||
user => 'all',
|
||||
description => "Requires the VM.Audit permission on /vms/<vmid>.",
|
||||
user => 'all',
|
||||
},
|
||||
protected => 1,
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
guest => get_standard_option('pve-vmid', {
|
||||
optional => 1,
|
||||
description => "Only list replication jobs for this guest.",
|
||||
}),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
guest => get_standard_option(
|
||||
'pve-vmid',
|
||||
{
|
||||
optional => 1,
|
||||
description => "Only list replication jobs for this guest.",
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
id => { type => 'string' },
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{id}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
id => { type => 'string' },
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{id}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $jobs = PVE::ReplicationState::job_status(1);
|
||||
my $jobs = PVE::ReplicationState::job_status(1);
|
||||
|
||||
my $res = [];
|
||||
foreach my $id (sort keys %$jobs) {
|
||||
my $data = $extract_job_status->($jobs->{$id}, $id);
|
||||
my $guest = $data->{guest};
|
||||
next if defined($param->{guest}) && $guest != $param->{guest};
|
||||
next if !$rpcenv->check($authuser, "/vms/$guest", [ 'VM.Audit' ]);
|
||||
push @$res, $data;
|
||||
}
|
||||
my $res = [];
|
||||
foreach my $id (sort keys %$jobs) {
|
||||
my $data = $extract_job_status->($jobs->{$id}, $id);
|
||||
my $guest = $data->{guest};
|
||||
next if defined($param->{guest}) && $guest != $param->{guest};
|
||||
next if !$rpcenv->check($authuser, "/vms/$guest", ['VM.Audit']);
|
||||
push @$res, $data;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}});
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '{id}',
|
||||
method => 'GET',
|
||||
permissions => { user => 'all' },
|
||||
description => "Directory index.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
return [
|
||||
{ name => 'schedule_now' },
|
||||
{ name => 'log' },
|
||||
{ name => 'status' },
|
||||
];
|
||||
}});
|
||||
return [
|
||||
{ name => 'schedule_now' }, { name => 'log' }, { name => 'status' },
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'job_status',
|
||||
path => '{id}/status',
|
||||
method => 'GET',
|
||||
description => "Get replication job status.",
|
||||
permissions => {
|
||||
description => "Requires the VM.Audit permission on /vms/<vmid>.",
|
||||
user => 'all',
|
||||
description => "Requires the VM.Audit permission on /vms/<vmid>.",
|
||||
user => 'all',
|
||||
},
|
||||
protected => 1,
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $jobs = PVE::ReplicationState::job_status(1);
|
||||
my $jobid = $param->{id};
|
||||
my $jobcfg = $jobs->{$jobid};
|
||||
my $jobs = PVE::ReplicationState::job_status(1);
|
||||
my $jobid = $param->{id};
|
||||
my $jobcfg = $jobs->{$jobid};
|
||||
|
||||
die "no such replication job '$jobid'\n" if !defined($jobcfg);
|
||||
die "no such replication job '$jobid'\n" if !defined($jobcfg);
|
||||
|
||||
my $data = $extract_job_status->($jobcfg, $jobid);
|
||||
my $guest = $data->{guest};
|
||||
my $data = $extract_job_status->($jobcfg, $jobid);
|
||||
my $guest = $data->{guest};
|
||||
|
||||
raise_perm_exc() if !$rpcenv->check($authuser, "/vms/$guest", [ 'VM.Audit' ]);
|
||||
raise_perm_exc() if !$rpcenv->check($authuser, "/vms/$guest", ['VM.Audit']);
|
||||
|
||||
return $data;
|
||||
}});
|
||||
return $data;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method({
|
||||
name => 'read_job_log',
|
||||
path => '{id}/log',
|
||||
method => 'GET',
|
||||
permissions => {
|
||||
description => "Requires the VM.Audit permission on /vms/<vmid>, or 'Sys.Audit' on '/nodes/<node>'",
|
||||
user => 'all',
|
||||
description =>
|
||||
"Requires the VM.Audit permission on /vms/<vmid>, or 'Sys.Audit' on '/nodes/<node>'",
|
||||
user => 'all',
|
||||
},
|
||||
protected => 1,
|
||||
description => "Read replication job log.",
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
node => get_standard_option('pve-node'),
|
||||
start => {
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
optional => 1,
|
||||
},
|
||||
limit => {
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
node => get_standard_option('pve-node'),
|
||||
start => {
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
optional => 1,
|
||||
},
|
||||
limit => {
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
n => {
|
||||
description=> "Line number",
|
||||
type=> 'integer',
|
||||
},
|
||||
t => {
|
||||
description=> "Line text",
|
||||
type => 'string',
|
||||
}
|
||||
}
|
||||
}
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
n => {
|
||||
description => "Line number",
|
||||
type => 'integer',
|
||||
},
|
||||
t => {
|
||||
description => "Line text",
|
||||
type => 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $jobid = $param->{id};
|
||||
my $filename = PVE::ReplicationState::job_logfile_name($jobid);
|
||||
my $jobid = $param->{id};
|
||||
my $filename = PVE::ReplicationState::job_logfile_name($jobid);
|
||||
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
my $data = $cfg->{ids}->{$jobid};
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
my $data = $cfg->{ids}->{$jobid};
|
||||
|
||||
die "no such replication job '$jobid'\n" if !defined($data);
|
||||
die "no such replication job '$jobid'\n" if !defined($data);
|
||||
|
||||
my $node = $param->{node};
|
||||
my $node = $param->{node};
|
||||
|
||||
my $vmid = $data->{guest};
|
||||
raise_perm_exc() if (!($rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ]) ||
|
||||
$rpcenv->check($authuser, "/nodes/$node", [ 'Sys.Audit' ])));
|
||||
my $vmid = $data->{guest};
|
||||
raise_perm_exc()
|
||||
if (!(
|
||||
$rpcenv->check($authuser, "/vms/$vmid", ['VM.Audit'])
|
||||
|| $rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'])
|
||||
));
|
||||
|
||||
my ($count, $lines) = PVE::Tools::dump_logfile($filename, $param->{start}, $param->{limit});
|
||||
my ($count, $lines) =
|
||||
PVE::Tools::dump_logfile($filename, $param->{start}, $param->{limit});
|
||||
|
||||
$rpcenv->set_result_attrib('total', $count);
|
||||
$rpcenv->set_result_attrib('total', $count);
|
||||
|
||||
return $lines;
|
||||
}});
|
||||
return $lines;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'schedule_now',
|
||||
path => '{id}/schedule_now',
|
||||
method => 'POST',
|
||||
|
|
@ -393,30 +400,31 @@ __PACKAGE__->register_method ({
|
|||
proxyto => 'node',
|
||||
protected => 1,
|
||||
permissions => {
|
||||
check => ['perm', '/storage', ['Datastore.Allocate']],
|
||||
check => ['perm', '/storage', ['Datastore.Allocate']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'string',
|
||||
type => 'string',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $jobid = $param->{id};
|
||||
my $jobid = $param->{id};
|
||||
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
my $jobcfg = $cfg->{ids}->{$jobid};
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
my $jobcfg = $cfg->{ids}->{$jobid};
|
||||
|
||||
die "no such replication job '$jobid'\n" if !defined($jobcfg);
|
||||
die "no such replication job '$jobid'\n" if !defined($jobcfg);
|
||||
|
||||
PVE::ReplicationState::schedule_job_now($jobcfg);
|
||||
PVE::ReplicationState::schedule_job_now($jobcfg);
|
||||
|
||||
}});
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -14,268 +14,276 @@ use PVE::RESTHandler;
|
|||
|
||||
use base qw(PVE::RESTHandler);
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
description => "List replication jobs.",
|
||||
permissions => {
|
||||
description => "Will only return replication jobs for which the calling user has"
|
||||
. " VM.Audit permission on /vms/<vmid>.",
|
||||
user => 'all',
|
||||
description => "Will only return replication jobs for which the calling user has"
|
||||
. " VM.Audit permission on /vms/<vmid>.",
|
||||
user => 'all',
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{id}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{id}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
|
||||
my $res = [];
|
||||
foreach my $id (sort keys %{$cfg->{ids}}) {
|
||||
my $d = $cfg->{ids}->{$id};
|
||||
my $vmid = $d->{guest};
|
||||
next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
|
||||
$d->{id} = $id;
|
||||
push @$res, $d;
|
||||
}
|
||||
my $res = [];
|
||||
foreach my $id (sort keys %{ $cfg->{ids} }) {
|
||||
my $d = $cfg->{ids}->{$id};
|
||||
my $vmid = $d->{guest};
|
||||
next if !$rpcenv->check($authuser, "/vms/$vmid", ['VM.Audit'], 1);
|
||||
$d->{id} = $id;
|
||||
push @$res, $d;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}});
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'read',
|
||||
path => '{id}',
|
||||
method => 'GET',
|
||||
description => "Read replication job configuration.",
|
||||
permissions => {
|
||||
description => "Requires the VM.Audit permission on /vms/<vmid>.",
|
||||
user => 'all',
|
||||
description => "Requires the VM.Audit permission on /vms/<vmid>.",
|
||||
user => 'all',
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
},
|
||||
},
|
||||
returns => { type => 'object' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
|
||||
my $data = $cfg->{ids}->{$param->{id}};
|
||||
my $data = $cfg->{ids}->{ $param->{id} };
|
||||
|
||||
die "no such replication job '$param->{id}'\n" if !defined($data);
|
||||
die "no such replication job '$param->{id}'\n" if !defined($data);
|
||||
|
||||
my $vmid = $data->{guest};
|
||||
my $vmid = $data->{guest};
|
||||
|
||||
raise_perm_exc() if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ]);
|
||||
raise_perm_exc() if !$rpcenv->check($authuser, "/vms/$vmid", ['VM.Audit']);
|
||||
|
||||
$data->{id} = $param->{id};
|
||||
$data->{id} = $param->{id};
|
||||
|
||||
$data->{digest} = $cfg->{digest};
|
||||
$data->{digest} = $cfg->{digest};
|
||||
|
||||
return $data;
|
||||
}});
|
||||
return $data;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'create',
|
||||
path => '',
|
||||
protected => 1,
|
||||
method => 'POST',
|
||||
description => "Create a new replication job",
|
||||
permissions => {
|
||||
check => ['perm', '/storage', ['Datastore.Allocate']],
|
||||
check => ['perm', '/storage', ['Datastore.Allocate']],
|
||||
},
|
||||
parameters => PVE::ReplicationConfig->createSchema(),
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $type = extract_param($param, 'type');
|
||||
my $plugin = PVE::ReplicationConfig->lookup($type);
|
||||
my $id = extract_param($param, 'id');
|
||||
my $type = extract_param($param, 'type');
|
||||
my $plugin = PVE::ReplicationConfig->lookup($type);
|
||||
my $id = extract_param($param, 'id');
|
||||
|
||||
# extract guest ID from job ID
|
||||
my ($guest) = PVE::ReplicationConfig::parse_replication_job_id($id);
|
||||
# extract guest ID from job ID
|
||||
my ($guest) = PVE::ReplicationConfig::parse_replication_job_id($id);
|
||||
|
||||
my $nodelist = PVE::Cluster::get_members();
|
||||
my $vmlist = PVE::Cluster::get_vmlist();
|
||||
my $nodelist = PVE::Cluster::get_members();
|
||||
my $vmlist = PVE::Cluster::get_vmlist();
|
||||
|
||||
my $guest_info = $vmlist->{ids}->{$guest};
|
||||
my $guest_info = $vmlist->{ids}->{$guest};
|
||||
|
||||
die "Guest '$guest' does not exist.\n"
|
||||
if !defined($guest_info);
|
||||
die "Target '$param->{target}' does not exist.\n"
|
||||
if !defined($nodelist->{$param->{target}});
|
||||
die "Guest '$guest' does not exist.\n"
|
||||
if !defined($guest_info);
|
||||
die "Target '$param->{target}' does not exist.\n"
|
||||
if !defined($nodelist->{ $param->{target} });
|
||||
|
||||
my $source = $guest_info->{node};
|
||||
die "Source '$param->{source}' does not match current node of guest '$guest' ($source)\n"
|
||||
if defined($param->{source}) && $param->{source} ne $source;
|
||||
my $source = $guest_info->{node};
|
||||
die
|
||||
"Source '$param->{source}' does not match current node of guest '$guest' ($source)\n"
|
||||
if defined($param->{source}) && $param->{source} ne $source;
|
||||
|
||||
$param->{source} //= $source;
|
||||
$param->{source} //= $source;
|
||||
|
||||
die "Source and target must not be identical\n"
|
||||
if $param->{target} eq $source;
|
||||
die "Source and target must not be identical\n"
|
||||
if $param->{target} eq $source;
|
||||
|
||||
my $guest_class = $PVE::API2::Replication::lookup_guest_class->($guest_info->{type});
|
||||
my $guest_conf = $guest_class->load_config($guest, $source);
|
||||
my $rep_volumes = $guest_class->get_replicatable_volumes(PVE::Storage::config(), $guest, $guest_conf, 0, 0);
|
||||
die "No replicatable volumes found\n" if !%$rep_volumes;
|
||||
my $guest_class = $PVE::API2::Replication::lookup_guest_class->($guest_info->{type});
|
||||
my $guest_conf = $guest_class->load_config($guest, $source);
|
||||
my $rep_volumes = $guest_class->get_replicatable_volumes(
|
||||
PVE::Storage::config(), $guest, $guest_conf, 0, 0,
|
||||
);
|
||||
die "No replicatable volumes found\n" if !%$rep_volumes;
|
||||
|
||||
my $code = sub {
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
my $code = sub {
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
|
||||
die "replication job '$id' already exists\n"
|
||||
if $cfg->{ids}->{$id};
|
||||
die "replication job '$id' already exists\n"
|
||||
if $cfg->{ids}->{$id};
|
||||
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
my $opts = $plugin->check_config($id, $param, 1, 1);
|
||||
|
||||
$opts->{guest} = $guest;
|
||||
$opts->{guest} = $guest;
|
||||
|
||||
$cfg->{ids}->{$id} = $opts;
|
||||
$cfg->{ids}->{$id} = $opts;
|
||||
|
||||
$cfg->write();
|
||||
};
|
||||
$cfg->write();
|
||||
};
|
||||
|
||||
PVE::ReplicationConfig::lock($code);
|
||||
PVE::ReplicationConfig::lock($code);
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'update',
|
||||
protected => 1,
|
||||
path => '{id}',
|
||||
method => 'PUT',
|
||||
description => "Update replication job configuration.",
|
||||
permissions => {
|
||||
check => ['perm', '/storage', ['Datastore.Allocate']],
|
||||
check => ['perm', '/storage', ['Datastore.Allocate']],
|
||||
},
|
||||
parameters => PVE::ReplicationConfig->updateSchema(),
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $id = extract_param($param, 'id');
|
||||
my $digest = extract_param($param, 'digest');
|
||||
my $delete = extract_param($param, 'delete');
|
||||
my $id = extract_param($param, 'id');
|
||||
my $digest = extract_param($param, 'digest');
|
||||
my $delete = extract_param($param, 'delete');
|
||||
|
||||
my $code = sub {
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
my $code = sub {
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
|
||||
PVE::SectionConfig::assert_if_modified($cfg, $digest);
|
||||
PVE::SectionConfig::assert_if_modified($cfg, $digest);
|
||||
|
||||
my $data = $cfg->{ids}->{$id};
|
||||
die "no such job '$id'\n" if !$data;
|
||||
my $data = $cfg->{ids}->{$id};
|
||||
die "no such job '$id'\n" if !$data;
|
||||
|
||||
my $plugin = PVE::ReplicationConfig->lookup($data->{type});
|
||||
my $opts = $plugin->check_config($id, $param, 0, 1);
|
||||
my $plugin = PVE::ReplicationConfig->lookup($data->{type});
|
||||
my $opts = $plugin->check_config($id, $param, 0, 1);
|
||||
|
||||
foreach my $k (keys %$opts) {
|
||||
$data->{$k} = $opts->{$k};
|
||||
}
|
||||
foreach my $k (keys %$opts) {
|
||||
$data->{$k} = $opts->{$k};
|
||||
}
|
||||
|
||||
if ($delete) {
|
||||
my $options = $plugin->private()->{options}->{$data->{type}};
|
||||
foreach my $k (PVE::Tools::split_list($delete)) {
|
||||
my $d = $options->{$k} ||
|
||||
die "no such option '$k'\n";
|
||||
die "unable to delete required option '$k'\n"
|
||||
if !$d->{optional};
|
||||
die "unable to delete fixed option '$k'\n"
|
||||
if $d->{fixed};
|
||||
delete $data->{$k};
|
||||
}
|
||||
}
|
||||
if ($delete) {
|
||||
my $options = $plugin->private()->{options}->{ $data->{type} };
|
||||
foreach my $k (PVE::Tools::split_list($delete)) {
|
||||
my $d = $options->{$k}
|
||||
|| die "no such option '$k'\n";
|
||||
die "unable to delete required option '$k'\n"
|
||||
if !$d->{optional};
|
||||
die "unable to delete fixed option '$k'\n"
|
||||
if $d->{fixed};
|
||||
delete $data->{$k};
|
||||
}
|
||||
}
|
||||
|
||||
$cfg->write();
|
||||
};
|
||||
$cfg->write();
|
||||
};
|
||||
|
||||
PVE::ReplicationConfig::lock($code);
|
||||
PVE::ReplicationConfig::lock($code);
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'delete',
|
||||
protected => 1,
|
||||
path => '{id}',
|
||||
method => 'DELETE',
|
||||
description => "Mark replication job for removal.",
|
||||
permissions => {
|
||||
check => ['perm', '/storage', ['Datastore.Allocate']],
|
||||
check => ['perm', '/storage', ['Datastore.Allocate']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
keep => {
|
||||
description => "Keep replicated data at target (do not remove).",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
force => {
|
||||
description => "Will remove the jobconfig entry, but will not cleanup.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
}
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
keep => {
|
||||
description => "Keep replicated data at target (do not remove).",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
force => {
|
||||
description => "Will remove the jobconfig entry, but will not cleanup.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
|
||||
my $code = sub {
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
my $code = sub {
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
|
||||
my $id = $param->{id};
|
||||
if ($param->{force}) {
|
||||
raise_param_exc({ 'keep' => "conflicts with parameter 'force'" }) if $param->{keep};
|
||||
delete $cfg->{ids}->{$id};
|
||||
} else {
|
||||
my $jobcfg = $cfg->{ids}->{$id};
|
||||
die "no such job '$id'\n" if !$jobcfg;
|
||||
my $id = $param->{id};
|
||||
if ($param->{force}) {
|
||||
raise_param_exc({ 'keep' => "conflicts with parameter 'force'" })
|
||||
if $param->{keep};
|
||||
delete $cfg->{ids}->{$id};
|
||||
} else {
|
||||
my $jobcfg = $cfg->{ids}->{$id};
|
||||
die "no such job '$id'\n" if !$jobcfg;
|
||||
|
||||
if (!$param->{keep} && $jobcfg->{type} eq 'local') {
|
||||
# remove local snapshots and remote volumes
|
||||
$jobcfg->{remove_job} = 'full';
|
||||
} else {
|
||||
# only remove local snapshots
|
||||
$jobcfg->{remove_job} = 'local';
|
||||
}
|
||||
if (!$param->{keep} && $jobcfg->{type} eq 'local') {
|
||||
# remove local snapshots and remote volumes
|
||||
$jobcfg->{remove_job} = 'full';
|
||||
} else {
|
||||
# only remove local snapshots
|
||||
$jobcfg->{remove_job} = 'local';
|
||||
}
|
||||
|
||||
warn "Replication job removal is a background task and will take some time.\n"
|
||||
if $rpcenv->{type} eq 'cli';
|
||||
}
|
||||
$cfg->write();
|
||||
};
|
||||
warn "Replication job removal is a background task and will take some time.\n"
|
||||
if $rpcenv->{type} eq 'cli';
|
||||
}
|
||||
$cfg->write();
|
||||
};
|
||||
|
||||
PVE::ReplicationConfig::lock($code);
|
||||
PVE::ReplicationConfig::lock($code);
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ my $essential_services = {
|
|||
# manage subinstances, of which the default is called "-".
|
||||
# This is where we look for the daemon status
|
||||
my $unit_extra_names = {
|
||||
postfix => 'postfix@-'
|
||||
postfix => 'postfix@-',
|
||||
};
|
||||
|
||||
my $get_full_service_state = sub {
|
||||
|
|
@ -56,10 +56,10 @@ my $get_full_service_state = sub {
|
|||
my $res;
|
||||
|
||||
my $parser = sub {
|
||||
my $line = shift;
|
||||
if ($line =~ m/^([^=\s]+)=(.*)$/) {
|
||||
$res->{$1} = $2;
|
||||
}
|
||||
my $line = shift;
|
||||
if ($line =~ m/^([^=\s]+)=(.*)$/) {
|
||||
$res->{$1} = $2;
|
||||
}
|
||||
};
|
||||
|
||||
PVE::Tools::run_command(['systemctl', 'show', $service], outfunc => $parser);
|
||||
|
|
@ -75,11 +75,11 @@ sub get_service_list {
|
|||
|
||||
my $list = {};
|
||||
foreach my $name (@$service_name_list) {
|
||||
my $ss = eval { $get_full_service_state->($name) };
|
||||
warn $@ if $@;
|
||||
next if !$ss;
|
||||
next if !defined($ss->{Description});
|
||||
$list->{$name} = { name => $name, desc => $ss->{Description} };
|
||||
my $ss = eval { $get_full_service_state->($name) };
|
||||
warn $@ if $@;
|
||||
next if !$ss;
|
||||
next if !defined($ss->{Description});
|
||||
$list->{$name} = { name => $name, desc => $ss->{Description} };
|
||||
}
|
||||
|
||||
$static_service_list = $list;
|
||||
|
|
@ -87,7 +87,6 @@ sub get_service_list {
|
|||
return $static_service_list;
|
||||
}
|
||||
|
||||
|
||||
my $service_prop_desc = {
|
||||
description => "Service ID",
|
||||
type => 'string',
|
||||
|
|
@ -99,10 +98,11 @@ my $service_cmd = sub {
|
|||
|
||||
my $initd_cmd;
|
||||
|
||||
die "unknown service command '$cmd'\n" if $cmd !~ m/^(start|stop|restart|reload|try-reload-or-restart)$/;
|
||||
die "unknown service command '$cmd'\n"
|
||||
if $cmd !~ m/^(start|stop|restart|reload|try-reload-or-restart)$/;
|
||||
|
||||
if ($essential_services->{$service} && $cmd eq 'stop') {
|
||||
die "invalid service cmd '$service $cmd': refusing to stop essential service!\n";
|
||||
die "invalid service cmd '$service $cmd': refusing to stop essential service!\n";
|
||||
}
|
||||
|
||||
PVE::Tools::run_command(['systemctl', $cmd, $service]);
|
||||
|
|
@ -115,67 +115,69 @@ my $service_state = sub {
|
|||
|
||||
my $ss = eval { $get_full_service_state->($service) };
|
||||
if (my $err = $@) {
|
||||
return $res;
|
||||
return $res;
|
||||
}
|
||||
my $state = $ss->{SubState} || 'unknown';
|
||||
if ($state eq 'dead' && $ss->{Type} && $ss->{Type} eq 'oneshot' && $ss->{Result}) {
|
||||
$res->{state} = $ss->{Result};
|
||||
$res->{state} = $ss->{Result};
|
||||
} else {
|
||||
$res->{state} = $ss->{SubState} || 'unknown';
|
||||
$res->{state} = $ss->{SubState} || 'unknown';
|
||||
}
|
||||
|
||||
if ($ss->{LoadState} eq 'not-found') {
|
||||
$res->{'unit-state'} = 'not-found'; # not installed
|
||||
$res->{'unit-state'} = 'not-found'; # not installed
|
||||
} else {
|
||||
$res->{'unit-state'} = $ss->{UnitFileState} || 'unknown';
|
||||
$res->{'unit-state'} = $ss->{UnitFileState} || 'unknown';
|
||||
}
|
||||
$res->{'active-state'} = $ss->{ActiveState} || 'unknown';
|
||||
|
||||
return $res;
|
||||
};
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Audit']],
|
||||
},
|
||||
description => "Service list.",
|
||||
proxyto => 'node',
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{service}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{service}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $service_list = get_service_list();
|
||||
my $service_list = get_service_list();
|
||||
|
||||
my $res = [];
|
||||
for my $id (sort keys %{$service_list}) {
|
||||
my $state = $service_state->($id);
|
||||
push @$res, {
|
||||
service => $id,
|
||||
name => $service_list->{$id}->{name},
|
||||
desc => $service_list->{$id}->{desc},
|
||||
%$state,
|
||||
};
|
||||
}
|
||||
my $res = [];
|
||||
for my $id (sort keys %{$service_list}) {
|
||||
my $state = $service_state->($id);
|
||||
push @$res,
|
||||
{
|
||||
service => $id,
|
||||
name => $service_list->{$id}->{name},
|
||||
desc => $service_list->{$id}->{desc},
|
||||
%$state,
|
||||
};
|
||||
}
|
||||
|
||||
return $res;
|
||||
}});
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method({
|
||||
name => 'srvcmdidx',
|
||||
|
|
@ -183,226 +185,232 @@ __PACKAGE__->register_method({
|
|||
method => 'GET',
|
||||
description => "Directory index",
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Audit']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
service => $service_prop_desc,
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
service => $service_prop_desc,
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
subdir => { type => 'string' },
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{subdir}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
subdir => { type => 'string' },
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{subdir}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $res = [
|
||||
{ subdir => 'state' },
|
||||
{ subdir => 'start' },
|
||||
{ subdir => 'stop' },
|
||||
{ subdir => 'restart' },
|
||||
{ subdir => 'reload' },
|
||||
];
|
||||
my $res = [
|
||||
{ subdir => 'state' },
|
||||
{ subdir => 'start' },
|
||||
{ subdir => 'stop' },
|
||||
{ subdir => 'restart' },
|
||||
{ subdir => 'reload' },
|
||||
];
|
||||
|
||||
return $res;
|
||||
}});
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'service_state',
|
||||
path => '{service}/state',
|
||||
method => 'GET',
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Audit']],
|
||||
},
|
||||
description => "Read service properties",
|
||||
proxyto => 'node',
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
service => $service_prop_desc,
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
service => $service_prop_desc,
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $id = $param->{service};
|
||||
my $id = $param->{service};
|
||||
|
||||
my $service_list = get_service_list();
|
||||
my $service_list = get_service_list();
|
||||
|
||||
my $si = $service_list->{$id};
|
||||
my $si = $service_list->{$id};
|
||||
|
||||
my $state = $service_state->($id);
|
||||
my $state = $service_state->($id);
|
||||
|
||||
return {
|
||||
service => $param->{service},
|
||||
name => $si->{name},
|
||||
desc => $si->{desc},
|
||||
%$state,
|
||||
};
|
||||
}});
|
||||
return {
|
||||
service => $param->{service},
|
||||
name => $si->{name},
|
||||
desc => $si->{desc},
|
||||
%$state,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'service_start',
|
||||
path => '{service}/start',
|
||||
method => 'POST',
|
||||
description => "Start service.",
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
|
||||
},
|
||||
proxyto => 'node',
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
service => $service_prop_desc,
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
service => $service_prop_desc,
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'string',
|
||||
type => 'string',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
|
||||
my $user = $rpcenv->get_user();
|
||||
my $user = $rpcenv->get_user();
|
||||
|
||||
my $realcmd = sub {
|
||||
my $upid = shift;
|
||||
my $realcmd = sub {
|
||||
my $upid = shift;
|
||||
|
||||
syslog('info', "starting service $param->{service}: $upid\n");
|
||||
syslog('info', "starting service $param->{service}: $upid\n");
|
||||
|
||||
$service_cmd->($param->{service}, 'start');
|
||||
$service_cmd->($param->{service}, 'start');
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker('srvstart', $param->{service}, $user, $realcmd);
|
||||
}});
|
||||
return $rpcenv->fork_worker('srvstart', $param->{service}, $user, $realcmd);
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'service_stop',
|
||||
path => '{service}/stop',
|
||||
method => 'POST',
|
||||
description => "Stop service.",
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
|
||||
},
|
||||
proxyto => 'node',
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
service => $service_prop_desc,
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
service => $service_prop_desc,
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'string',
|
||||
type => 'string',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
|
||||
my $user = $rpcenv->get_user();
|
||||
my $user = $rpcenv->get_user();
|
||||
|
||||
my $realcmd = sub {
|
||||
my $upid = shift;
|
||||
my $realcmd = sub {
|
||||
my $upid = shift;
|
||||
|
||||
syslog('info', "stopping service $param->{service}: $upid\n");
|
||||
syslog('info', "stopping service $param->{service}: $upid\n");
|
||||
|
||||
$service_cmd->($param->{service}, 'stop');
|
||||
$service_cmd->($param->{service}, 'stop');
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker('srvstop', $param->{service}, $user, $realcmd);
|
||||
}});
|
||||
return $rpcenv->fork_worker('srvstop', $param->{service}, $user, $realcmd);
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'service_restart',
|
||||
path => '{service}/restart',
|
||||
method => 'POST',
|
||||
description => "Hard restart service. Use reload if you want to reduce interruptions.",
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
|
||||
},
|
||||
proxyto => 'node',
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
service => $service_prop_desc,
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
service => $service_prop_desc,
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'string',
|
||||
type => 'string',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
|
||||
my $realcmd = sub {
|
||||
my $upid = shift;
|
||||
syslog('info', "re-starting service $param->{service}: $upid\n");
|
||||
my $realcmd = sub {
|
||||
my $upid = shift;
|
||||
syslog('info', "re-starting service $param->{service}: $upid\n");
|
||||
|
||||
$service_cmd->($param->{service}, 'restart');
|
||||
};
|
||||
$service_cmd->($param->{service}, 'restart');
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker('srvrestart', $param->{service}, $user, $realcmd);
|
||||
}});
|
||||
return $rpcenv->fork_worker('srvrestart', $param->{service}, $user, $realcmd);
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'service_reload',
|
||||
path => '{service}/reload',
|
||||
method => 'POST',
|
||||
description => "Reload service. Falls back to restart if service cannot be reloaded.",
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
|
||||
},
|
||||
proxyto => 'node',
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
service => $service_prop_desc,
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
service => $service_prop_desc,
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'string',
|
||||
type => 'string',
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
|
||||
my $realcmd = sub {
|
||||
my $upid = shift;
|
||||
syslog('info', "reloading service $param->{service}: $upid\n");
|
||||
my $realcmd = sub {
|
||||
my $upid = shift;
|
||||
syslog('info', "reloading service $param->{service}: $upid\n");
|
||||
|
||||
$service_cmd->($param->{service}, 'try-reload-or-restart');
|
||||
$service_cmd->($param->{service}, 'try-reload-or-restart');
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
return $rpcenv->fork_worker('srvreload', $param->{service}, $user, $realcmd);
|
||||
}});
|
||||
return $rpcenv->fork_worker('srvreload', $param->{service}, $user, $realcmd);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ sub parse_key {
|
|||
my ($key, $noerr) = @_;
|
||||
|
||||
if ($key =~ m/^${subscription_pattern}$/) {
|
||||
return wantarray ? ($1, $2) : $1; # number of sockets, level
|
||||
return wantarray ? ($1, $2) : $1; # number of sockets, level
|
||||
}
|
||||
return undef if $noerr;
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ sub check_key {
|
|||
|
||||
my ($sockets, $level) = parse_key($key);
|
||||
if ($sockets < $req_sockets) {
|
||||
die "wrong number of sockets ($sockets < $req_sockets)\n";
|
||||
die "wrong number of sockets ($sockets < $req_sockets)\n";
|
||||
}
|
||||
return ($sockets, $level);
|
||||
}
|
||||
|
|
@ -67,11 +67,11 @@ sub read_etc_subscription {
|
|||
my ($sockets, $level);
|
||||
eval { ($sockets, $level) = check_key($info->{key}, $req_sockets); };
|
||||
if (my $err = $@) {
|
||||
chomp $err;
|
||||
$info->{status} = 'invalid';
|
||||
$info->{message} = $err;
|
||||
chomp $err;
|
||||
$info->{status} = 'invalid';
|
||||
$info->{message} = $err;
|
||||
} else {
|
||||
$info->{level} = $level;
|
||||
$info->{level} = $level;
|
||||
}
|
||||
|
||||
return $info;
|
||||
|
|
@ -83,7 +83,7 @@ my sub cache_is_valid {
|
|||
return if !$info || $info->{status} ne 'active';
|
||||
|
||||
my $checked_info = Proxmox::RS::Subscription::check_age($info, 1);
|
||||
return $checked_info->{status} eq 'active'
|
||||
return $checked_info->{status} eq 'active';
|
||||
}
|
||||
|
||||
sub write_etc_subscription {
|
||||
|
|
@ -92,25 +92,32 @@ sub write_etc_subscription {
|
|||
my $server_id = PVE::API2Tools::get_hwaddress();
|
||||
mkdir "/etc/apt/auth.conf.d";
|
||||
Proxmox::RS::Subscription::write_subscription(
|
||||
$filename, "/etc/apt/auth.conf.d/pve.conf", "enterprise.proxmox.com/debian/pve", $info);
|
||||
$filename,
|
||||
"/etc/apt/auth.conf.d/pve.conf",
|
||||
"enterprise.proxmox.com/debian/pve",
|
||||
$info,
|
||||
);
|
||||
|
||||
if (!(defined($info->{key}) && defined($info->{serverid}))) {
|
||||
unlink "/etc/apt/auth.conf.d/ceph.conf" or $!{ENOENT} or die "failed to remove apt auth ceph.conf - $!";
|
||||
unlink "/etc/apt/auth.conf.d/ceph.conf"
|
||||
or $!{ENOENT}
|
||||
or die "failed to remove apt auth ceph.conf - $!";
|
||||
} else {
|
||||
my $supported_ceph_releases = PVE::Ceph::Releases::get_available_ceph_release_codenames(1);
|
||||
my $ceph_auth = '';
|
||||
for my $ceph_release ($supported_ceph_releases->@*) {
|
||||
$ceph_auth .= "machine enterprise.proxmox.com/debian/ceph-${ceph_release}"
|
||||
." login $info->{key} password $info->{serverid}\n"
|
||||
}
|
||||
# add a generic one to handle the case where a new ceph release was made available while
|
||||
# the subscription info was not yet updated, and as per APT_AUTH.CONF(5) start-with matches.
|
||||
$ceph_auth .= "machine enterprise.proxmox.com/debian/ceph login $info->{key} password $info->{serverid}\n";
|
||||
PVE::Tools::file_set_contents("/etc/apt/auth.conf.d/ceph.conf", $ceph_auth, 0600);
|
||||
my $supported_ceph_releases = PVE::Ceph::Releases::get_available_ceph_release_codenames(1);
|
||||
my $ceph_auth = '';
|
||||
for my $ceph_release ($supported_ceph_releases->@*) {
|
||||
$ceph_auth .= "machine enterprise.proxmox.com/debian/ceph-${ceph_release}"
|
||||
. " login $info->{key} password $info->{serverid}\n";
|
||||
}
|
||||
# add a generic one to handle the case where a new ceph release was made available while
|
||||
# the subscription info was not yet updated, and as per APT_AUTH.CONF(5) start-with matches.
|
||||
$ceph_auth .=
|
||||
"machine enterprise.proxmox.com/debian/ceph login $info->{key} password $info->{serverid}\n";
|
||||
PVE::Tools::file_set_contents("/etc/apt/auth.conf.d/ceph.conf", $ceph_auth, 0600);
|
||||
}
|
||||
}
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'get',
|
||||
path => '',
|
||||
method => 'GET',
|
||||
|
|
@ -118,239 +125,247 @@ __PACKAGE__->register_method ({
|
|||
proxyto => 'node',
|
||||
permissions => { user => 'all' },
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
status => {
|
||||
type => 'string',
|
||||
enum => [qw(new notfound active invalid expired suspended)],
|
||||
description => "The current subscription status.",
|
||||
},
|
||||
checktime => {
|
||||
type => 'integer',
|
||||
description => 'Timestamp of the last check done.',
|
||||
optional => 1,
|
||||
},
|
||||
key => {
|
||||
type => 'string',
|
||||
description => 'The subscription key, if set and permitted to access.',
|
||||
optional => 1,
|
||||
},
|
||||
level => {
|
||||
type => 'string',
|
||||
description => 'A short code for the subscription level.',
|
||||
optional => 1,
|
||||
},
|
||||
message => {
|
||||
type => 'string',
|
||||
description => 'A more human readable status message.',
|
||||
optional => 1,
|
||||
},
|
||||
nextduedate => {
|
||||
type => 'string',
|
||||
description => 'Next due date of the set subscription.',
|
||||
optional => 1,
|
||||
},
|
||||
productname => {
|
||||
type => 'string',
|
||||
description => 'Human readable productname of the set subscription.',
|
||||
optional => 1,
|
||||
},
|
||||
regdate => {
|
||||
type => 'string',
|
||||
description => 'Register date of the set subscription.',
|
||||
optional => 1,
|
||||
},
|
||||
serverid => {
|
||||
type => 'string',
|
||||
description => 'The server ID, if permitted to access.',
|
||||
optional => 1,
|
||||
},
|
||||
signature => {
|
||||
type => 'string',
|
||||
description => 'Signature for offline keys',
|
||||
optional => 1,
|
||||
},
|
||||
sockets => {
|
||||
type => 'integer',
|
||||
description => 'The number of sockets for this host.',
|
||||
optional => 1,
|
||||
},
|
||||
url => {
|
||||
type => 'string',
|
||||
description => 'URL to the web shop.',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
status => {
|
||||
type => 'string',
|
||||
enum => [qw(new notfound active invalid expired suspended)],
|
||||
description => "The current subscription status.",
|
||||
},
|
||||
checktime => {
|
||||
type => 'integer',
|
||||
description => 'Timestamp of the last check done.',
|
||||
optional => 1,
|
||||
},
|
||||
key => {
|
||||
type => 'string',
|
||||
description => 'The subscription key, if set and permitted to access.',
|
||||
optional => 1,
|
||||
},
|
||||
level => {
|
||||
type => 'string',
|
||||
description => 'A short code for the subscription level.',
|
||||
optional => 1,
|
||||
},
|
||||
message => {
|
||||
type => 'string',
|
||||
description => 'A more human readable status message.',
|
||||
optional => 1,
|
||||
},
|
||||
nextduedate => {
|
||||
type => 'string',
|
||||
description => 'Next due date of the set subscription.',
|
||||
optional => 1,
|
||||
},
|
||||
productname => {
|
||||
type => 'string',
|
||||
description => 'Human readable productname of the set subscription.',
|
||||
optional => 1,
|
||||
},
|
||||
regdate => {
|
||||
type => 'string',
|
||||
description => 'Register date of the set subscription.',
|
||||
optional => 1,
|
||||
},
|
||||
serverid => {
|
||||
type => 'string',
|
||||
description => 'The server ID, if permitted to access.',
|
||||
optional => 1,
|
||||
},
|
||||
signature => {
|
||||
type => 'string',
|
||||
description => 'Signature for offline keys',
|
||||
optional => 1,
|
||||
},
|
||||
sockets => {
|
||||
type => 'integer',
|
||||
description => 'The number of sockets for this host.',
|
||||
optional => 1,
|
||||
},
|
||||
url => {
|
||||
type => 'string',
|
||||
description => 'URL to the web shop.',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $node = $param->{node};
|
||||
my $node = $param->{node};
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $has_permission = $rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'], 1);
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $has_permission = $rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'], 1);
|
||||
|
||||
my $server_id = PVE::API2Tools::get_hwaddress();
|
||||
my $url = "https://www.proxmox.com/en/proxmox-virtual-environment/pricing";
|
||||
my $server_id = PVE::API2Tools::get_hwaddress();
|
||||
my $url = "https://www.proxmox.com/en/proxmox-virtual-environment/pricing";
|
||||
|
||||
my $info = read_etc_subscription();
|
||||
if (!$info) {
|
||||
my $no_subscription_info = {
|
||||
status => "notfound",
|
||||
message => "There is no subscription key",
|
||||
url => $url,
|
||||
};
|
||||
$no_subscription_info->{serverid} = $server_id if $has_permission;
|
||||
return $no_subscription_info;
|
||||
}
|
||||
my $info = read_etc_subscription();
|
||||
if (!$info) {
|
||||
my $no_subscription_info = {
|
||||
status => "notfound",
|
||||
message => "There is no subscription key",
|
||||
url => $url,
|
||||
};
|
||||
$no_subscription_info->{serverid} = $server_id if $has_permission;
|
||||
return $no_subscription_info;
|
||||
}
|
||||
|
||||
if (!$has_permission) {
|
||||
return {
|
||||
status => $info->{status},
|
||||
message => $info->{message},
|
||||
url => $url,
|
||||
}
|
||||
}
|
||||
if (!$has_permission) {
|
||||
return {
|
||||
status => $info->{status},
|
||||
message => $info->{message},
|
||||
url => $url,
|
||||
};
|
||||
}
|
||||
|
||||
$info->{serverid} = $server_id;
|
||||
$info->{sockets} = get_sockets();
|
||||
$info->{url} = $url;
|
||||
$info->{serverid} = $server_id;
|
||||
$info->{sockets} = get_sockets();
|
||||
$info->{url} = $url;
|
||||
|
||||
return $info
|
||||
}});
|
||||
return $info;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'update',
|
||||
path => '',
|
||||
method => 'POST',
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
|
||||
},
|
||||
description => "Update subscription info.",
|
||||
proxyto => 'node',
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
force => {
|
||||
description => "Always connect to server, even if local cache is still valid.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0
|
||||
}
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
force => {
|
||||
description => "Always connect to server, even if local cache is still valid.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null'},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $info = read_etc_subscription();
|
||||
return undef if !$info;
|
||||
my $info = read_etc_subscription();
|
||||
return undef if !$info;
|
||||
|
||||
my $server_id = PVE::API2Tools::get_hwaddress();
|
||||
my $key = $info->{key};
|
||||
my $server_id = PVE::API2Tools::get_hwaddress();
|
||||
my $key = $info->{key};
|
||||
|
||||
die "Updating offline key not possible - please remove and re-add subscription key to switch to online key.\n"
|
||||
if $info->{signature};
|
||||
die
|
||||
"Updating offline key not possible - please remove and re-add subscription key to switch to online key.\n"
|
||||
if $info->{signature};
|
||||
|
||||
return undef if !$param->{force} && cache_is_valid($info); # key has been recently checked
|
||||
return undef if !$param->{force} && cache_is_valid($info); # key has been recently checked
|
||||
|
||||
my $req_sockets = get_sockets();
|
||||
check_key($key, $req_sockets);
|
||||
my $req_sockets = get_sockets();
|
||||
check_key($key, $req_sockets);
|
||||
|
||||
my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
|
||||
my $proxy = $dccfg->{http_proxy};
|
||||
my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
|
||||
my $proxy = $dccfg->{http_proxy};
|
||||
|
||||
$info = Proxmox::RS::Subscription::check_subscription($key, $server_id, "", "Proxmox VE", $proxy);
|
||||
$info = Proxmox::RS::Subscription::check_subscription(
|
||||
$key, $server_id, "", "Proxmox VE", $proxy,
|
||||
);
|
||||
|
||||
write_etc_subscription($info);
|
||||
write_etc_subscription($info);
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'set',
|
||||
path => '',
|
||||
method => 'PUT',
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
|
||||
},
|
||||
description => "Set subscription key.",
|
||||
proxyto => 'node',
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
key => {
|
||||
description => "Proxmox VE subscription key",
|
||||
type => 'string',
|
||||
pattern => "\\s*${subscription_pattern}\\s*",
|
||||
maxLength => 32,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
key => {
|
||||
description => "Proxmox VE subscription key",
|
||||
type => 'string',
|
||||
pattern => "\\s*${subscription_pattern}\\s*",
|
||||
maxLength => 32,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null'},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $key = PVE::Tools::trim($param->{key});
|
||||
my $key = PVE::Tools::trim($param->{key});
|
||||
|
||||
my $new_info = {
|
||||
status => 'New',
|
||||
key => $key,
|
||||
checktime => time(),
|
||||
};
|
||||
my $new_info = {
|
||||
status => 'New',
|
||||
key => $key,
|
||||
checktime => time(),
|
||||
};
|
||||
|
||||
my $req_sockets = get_sockets();
|
||||
my $server_id = PVE::API2Tools::get_hwaddress();
|
||||
my $req_sockets = get_sockets();
|
||||
my $server_id = PVE::API2Tools::get_hwaddress();
|
||||
|
||||
check_key($key, $req_sockets);
|
||||
check_key($key, $req_sockets);
|
||||
|
||||
write_etc_subscription($new_info);
|
||||
write_etc_subscription($new_info);
|
||||
|
||||
my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
|
||||
my $proxy = $dccfg->{http_proxy};
|
||||
my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
|
||||
my $proxy = $dccfg->{http_proxy};
|
||||
|
||||
my $checked_info = Proxmox::RS::Subscription::check_subscription(
|
||||
$key, $server_id, "", "Proxmox VE", $proxy);
|
||||
my $checked_info = Proxmox::RS::Subscription::check_subscription(
|
||||
$key, $server_id, "", "Proxmox VE", $proxy,
|
||||
);
|
||||
|
||||
write_etc_subscription($checked_info);
|
||||
write_etc_subscription($checked_info);
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'delete',
|
||||
path => '',
|
||||
method => 'DELETE',
|
||||
permissions => {
|
||||
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
|
||||
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
|
||||
},
|
||||
description => "Delete subscription key of this node.",
|
||||
proxyto => 'node',
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
},
|
||||
},
|
||||
returns => { type => 'null'},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my $subscription_file = '/etc/subscription';
|
||||
return if ! -e $subscription_file;
|
||||
unlink($subscription_file) or die "cannot delete subscription key: $!";
|
||||
return undef;
|
||||
}});
|
||||
my $subscription_file = '/etc/subscription';
|
||||
return if !-e $subscription_file;
|
||||
unlink($subscription_file) or die "cannot delete subscription key: $!";
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ my $convert_token_task = sub {
|
|||
my ($task) = @_;
|
||||
|
||||
if (PVE::AccessControl::pve_verify_tokenid($task->{user}, 1)) {
|
||||
($task->{user}, $task->{tokenid}) = PVE::AccessControl::split_tokenid($task->{user});
|
||||
($task->{user}, $task->{tokenid}) = PVE::AccessControl::split_tokenid($task->{user});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -28,11 +28,11 @@ my $check_task_user = sub {
|
|||
my ($task, $user) = @_;
|
||||
|
||||
if ($task->{tokenid}) {
|
||||
my $fulltoken = PVE::AccessControl::join_tokenid($task->{user}, $task->{tokenid});
|
||||
# token only sees token tasks, user sees user + token tasks
|
||||
return $user eq $fulltoken || $user eq $task->{user};
|
||||
my $fulltoken = PVE::AccessControl::join_tokenid($task->{user}, $task->{tokenid});
|
||||
# token only sees token tasks, user sees user + token tasks
|
||||
return $user eq $fulltoken || $user eq $task->{user};
|
||||
} else {
|
||||
return $user eq $task->{user};
|
||||
return $user eq $task->{user};
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -41,211 +41,216 @@ __PACKAGE__->register_method({
|
|||
path => '',
|
||||
method => 'GET',
|
||||
permissions => {
|
||||
description => "List task associated with the current user, or all task the user has 'Sys.Audit' permissions on /nodes/<node> (the <node> the task runs on).",
|
||||
user => 'all'
|
||||
description =>
|
||||
"List task associated with the current user, or all task the user has 'Sys.Audit' permissions on /nodes/<node> (the <node> the task runs on).",
|
||||
user => 'all',
|
||||
},
|
||||
description => "Read task list for one node (finished tasks).",
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
start => {
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
default => 0,
|
||||
optional => 1,
|
||||
description => "List tasks beginning from this offset.",
|
||||
},
|
||||
limit => {
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
default => 50,
|
||||
optional => 1,
|
||||
description => "Only list this amount of tasks.",
|
||||
},
|
||||
userfilter => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
description => "Only list tasks from this user.",
|
||||
},
|
||||
typefilter => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
description => 'Only list tasks of this type (e.g., vzstart, vzdump).',
|
||||
},
|
||||
vmid => get_standard_option('pve-vmid', {
|
||||
description => "Only list tasks for this VM.",
|
||||
optional => 1,
|
||||
}),
|
||||
errors => {
|
||||
type => 'boolean',
|
||||
default => 0,
|
||||
optional => 1,
|
||||
description => 'Only list tasks with a status of ERROR.',
|
||||
},
|
||||
source => {
|
||||
type => 'string',
|
||||
enum => ['archive', 'active', 'all'],
|
||||
default => 'archive',
|
||||
optional => 1,
|
||||
description => 'List archived, active or all tasks.',
|
||||
},
|
||||
since => {
|
||||
type => 'integer',
|
||||
description => "Only list tasks since this UNIX epoch.",
|
||||
optional => 1,
|
||||
},
|
||||
until => {
|
||||
type => 'integer',
|
||||
description => "Only list tasks until this UNIX epoch.",
|
||||
optional => 1,
|
||||
},
|
||||
statusfilter => {
|
||||
type => 'string',
|
||||
format => 'pve-task-status-type-list',
|
||||
optional => 1,
|
||||
description => 'List of Task States that should be returned.',
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
start => {
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
default => 0,
|
||||
optional => 1,
|
||||
description => "List tasks beginning from this offset.",
|
||||
},
|
||||
limit => {
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
default => 50,
|
||||
optional => 1,
|
||||
description => "Only list this amount of tasks.",
|
||||
},
|
||||
userfilter => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
description => "Only list tasks from this user.",
|
||||
},
|
||||
typefilter => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
description => 'Only list tasks of this type (e.g., vzstart, vzdump).',
|
||||
},
|
||||
vmid => get_standard_option(
|
||||
'pve-vmid',
|
||||
{
|
||||
description => "Only list tasks for this VM.",
|
||||
optional => 1,
|
||||
},
|
||||
),
|
||||
errors => {
|
||||
type => 'boolean',
|
||||
default => 0,
|
||||
optional => 1,
|
||||
description => 'Only list tasks with a status of ERROR.',
|
||||
},
|
||||
source => {
|
||||
type => 'string',
|
||||
enum => ['archive', 'active', 'all'],
|
||||
default => 'archive',
|
||||
optional => 1,
|
||||
description => 'List archived, active or all tasks.',
|
||||
},
|
||||
since => {
|
||||
type => 'integer',
|
||||
description => "Only list tasks since this UNIX epoch.",
|
||||
optional => 1,
|
||||
},
|
||||
until => {
|
||||
type => 'integer',
|
||||
description => "Only list tasks until this UNIX epoch.",
|
||||
optional => 1,
|
||||
},
|
||||
statusfilter => {
|
||||
type => 'string',
|
||||
format => 'pve-task-status-type-list',
|
||||
optional => 1,
|
||||
description => 'List of Task States that should be returned.',
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
upid => { type => 'string', title => 'UPID', },
|
||||
node => { type => 'string', title => 'Node', },
|
||||
pid => { type => 'integer', title => 'PID', },
|
||||
pstart => { type => 'integer', },
|
||||
starttime => { type => 'integer', title => 'Starttime', },
|
||||
type => { type => 'string', title => 'Type', },
|
||||
id => { type => 'string', title => 'ID', },
|
||||
user => { type => 'string', title => 'User', },
|
||||
endtime => { type => 'integer', optional => 1, title => 'Endtime', },
|
||||
status => { type => 'string', optional => 1, title => 'Status', },
|
||||
},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{upid}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
upid => { type => 'string', title => 'UPID' },
|
||||
node => { type => 'string', title => 'Node' },
|
||||
pid => { type => 'integer', title => 'PID' },
|
||||
pstart => { type => 'integer' },
|
||||
starttime => { type => 'integer', title => 'Starttime' },
|
||||
type => { type => 'string', title => 'Type' },
|
||||
id => { type => 'string', title => 'ID' },
|
||||
user => { type => 'string', title => 'User' },
|
||||
endtime => { type => 'integer', optional => 1, title => 'Endtime' },
|
||||
status => { type => 'string', optional => 1, title => 'Status' },
|
||||
},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{upid}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
|
||||
my $res = [];
|
||||
my $res = [];
|
||||
|
||||
my $filename = "/var/log/pve/tasks/index";
|
||||
my $filename = "/var/log/pve/tasks/index";
|
||||
|
||||
my $node = $param->{node};
|
||||
my $start = $param->{start} // 0;
|
||||
my $limit = $param->{limit} // 50;
|
||||
my $userfilter = $param->{userfilter};
|
||||
my $typefilter = $param->{typefilter};
|
||||
my $errors = $param->{errors} // 0;
|
||||
my $source = $param->{source} // 'archive';
|
||||
my $since = $param->{since};
|
||||
my $until = $param->{until};
|
||||
my $statusfilter = {
|
||||
ok => 1,
|
||||
warning => 1,
|
||||
error => 1,
|
||||
unknown => 1,
|
||||
};
|
||||
my $node = $param->{node};
|
||||
my $start = $param->{start} // 0;
|
||||
my $limit = $param->{limit} // 50;
|
||||
my $userfilter = $param->{userfilter};
|
||||
my $typefilter = $param->{typefilter};
|
||||
my $errors = $param->{errors} // 0;
|
||||
my $source = $param->{source} // 'archive';
|
||||
my $since = $param->{since};
|
||||
my $until = $param->{until};
|
||||
my $statusfilter = {
|
||||
ok => 1,
|
||||
warning => 1,
|
||||
error => 1,
|
||||
unknown => 1,
|
||||
};
|
||||
|
||||
if (defined($param->{statusfilter}) && !$errors) {
|
||||
$statusfilter = {
|
||||
ok => 0,
|
||||
warning => 0,
|
||||
error => 0,
|
||||
unknown => 0,
|
||||
};
|
||||
for my $filter (PVE::Tools::split_list($param->{statusfilter})) {
|
||||
$statusfilter->{lc($filter)} = 1 ;
|
||||
}
|
||||
} elsif ($errors) {
|
||||
$statusfilter->{ok} = 0;
|
||||
}
|
||||
if (defined($param->{statusfilter}) && !$errors) {
|
||||
$statusfilter = {
|
||||
ok => 0,
|
||||
warning => 0,
|
||||
error => 0,
|
||||
unknown => 0,
|
||||
};
|
||||
for my $filter (PVE::Tools::split_list($param->{statusfilter})) {
|
||||
$statusfilter->{ lc($filter) } = 1;
|
||||
}
|
||||
} elsif ($errors) {
|
||||
$statusfilter->{ok} = 0;
|
||||
}
|
||||
|
||||
my $count = 0;
|
||||
my $line;
|
||||
my $count = 0;
|
||||
my $line;
|
||||
|
||||
my $auditor = $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ], 1);
|
||||
my $auditor = $rpcenv->check($user, "/nodes/$node", ['Sys.Audit'], 1);
|
||||
|
||||
my $filter_task = sub {
|
||||
my $task = shift;
|
||||
my $filter_task = sub {
|
||||
my $task = shift;
|
||||
|
||||
return 1 if $userfilter && $task->{user} !~ m/\Q$userfilter\E/i;
|
||||
return 1 if !($auditor || $check_task_user->($task, $user));
|
||||
return 1 if $userfilter && $task->{user} !~ m/\Q$userfilter\E/i;
|
||||
return 1 if !($auditor || $check_task_user->($task, $user));
|
||||
|
||||
return 1 if $typefilter && $task->{type} ne $typefilter;
|
||||
return 1 if $typefilter && $task->{type} ne $typefilter;
|
||||
|
||||
return 1 if $param->{vmid} && (!$task->{id} || $task->{id} ne $param->{vmid});
|
||||
return 1 if $param->{vmid} && (!$task->{id} || $task->{id} ne $param->{vmid});
|
||||
|
||||
return 1 if defined($since) && $task->{starttime} < $since;
|
||||
return 1 if defined($until) && $task->{starttime} > $until;
|
||||
return 1 if defined($since) && $task->{starttime} < $since;
|
||||
return 1 if defined($until) && $task->{starttime} > $until;
|
||||
|
||||
my $type = PVE::Tools::upid_normalize_status_type($task->{status});
|
||||
return 1 if !$statusfilter->{$type};
|
||||
my $type = PVE::Tools::upid_normalize_status_type($task->{status});
|
||||
return 1 if !$statusfilter->{$type};
|
||||
|
||||
return 1 if $count++ < $start;
|
||||
return 1 if $limit <= 0;
|
||||
return 1 if $count++ < $start;
|
||||
return 1 if $limit <= 0;
|
||||
|
||||
return 0;
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
|
||||
my $parse_line = sub {
|
||||
if ($line =~ m/^(\S+)(\s([0-9A-Za-z]{8})(\s(\S.*))?)?$/) {
|
||||
my $upid = $1;
|
||||
my $endtime = $3;
|
||||
my $status = $5;
|
||||
if ((my $task = PVE::Tools::upid_decode($upid, 1))) {
|
||||
my $parse_line = sub {
|
||||
if ($line =~ m/^(\S+)(\s([0-9A-Za-z]{8})(\s(\S.*))?)?$/) {
|
||||
my $upid = $1;
|
||||
my $endtime = $3;
|
||||
my $status = $5;
|
||||
if ((my $task = PVE::Tools::upid_decode($upid, 1))) {
|
||||
|
||||
$task->{upid} = $upid;
|
||||
$task->{endtime} = hex($endtime) if $endtime;
|
||||
$task->{status} = $status if $status;
|
||||
$task->{upid} = $upid;
|
||||
$task->{endtime} = hex($endtime) if $endtime;
|
||||
$task->{status} = $status if $status;
|
||||
|
||||
$convert_token_task->($task);
|
||||
if (!$filter_task->($task)) {
|
||||
push @$res, $task;
|
||||
$limit--;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
$convert_token_task->($task);
|
||||
if (!$filter_task->($task)) {
|
||||
push @$res, $task;
|
||||
$limit--;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if ($source eq 'active' || $source eq 'all') {
|
||||
my $recent_tasks = PVE::INotify::read_file('active');
|
||||
for my $task (@$recent_tasks) {
|
||||
next if $task->{saved}; # archived task, already in index(.1)
|
||||
if (!$filter_task->($task)) {
|
||||
$task->{status} = 'RUNNING' if !$task->{status}; # otherwise it would be archived
|
||||
push @$res, $task;
|
||||
$limit--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($source eq 'active' || $source eq 'all') {
|
||||
my $recent_tasks = PVE::INotify::read_file('active');
|
||||
for my $task (@$recent_tasks) {
|
||||
next if $task->{saved}; # archived task, already in index(.1)
|
||||
if (!$filter_task->($task)) {
|
||||
$task->{status} = 'RUNNING' if !$task->{status}; # otherwise it would be archived
|
||||
push @$res, $task;
|
||||
$limit--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($source ne 'active') {
|
||||
if (my $bw = File::ReadBackwards->new($filename)) {
|
||||
while (defined ($line = $bw->readline)) {
|
||||
&$parse_line();
|
||||
}
|
||||
$bw->close();
|
||||
}
|
||||
if (my $bw = File::ReadBackwards->new("$filename.1")) {
|
||||
while (defined ($line = $bw->readline)) {
|
||||
&$parse_line();
|
||||
}
|
||||
$bw->close();
|
||||
}
|
||||
}
|
||||
if ($source ne 'active') {
|
||||
if (my $bw = File::ReadBackwards->new($filename)) {
|
||||
while (defined($line = $bw->readline)) {
|
||||
&$parse_line();
|
||||
}
|
||||
$bw->close();
|
||||
}
|
||||
if (my $bw = File::ReadBackwards->new("$filename.1")) {
|
||||
while (defined($line = $bw->readline)) {
|
||||
&$parse_line();
|
||||
}
|
||||
$bw->close();
|
||||
}
|
||||
}
|
||||
|
||||
$rpcenv->set_result_attrib('total', $count);
|
||||
$rpcenv->set_result_attrib('total', $count);
|
||||
|
||||
return $res;
|
||||
}});
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method({
|
||||
name => 'upid_index',
|
||||
|
|
@ -254,28 +259,28 @@ __PACKAGE__->register_method({
|
|||
description => '', # index helper
|
||||
permissions => { user => 'all' },
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
upid => { type => 'string' },
|
||||
}
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
upid => { type => 'string' },
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [ { rel => 'child', href => "{name}" } ],
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
links => [{ rel => 'child', href => "{name}" }],
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
return [
|
||||
{ name => 'log' },
|
||||
{ name => 'status' }
|
||||
];
|
||||
}});
|
||||
return [
|
||||
{ name => 'log' }, { name => 'status' },
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method({
|
||||
name => 'stop_task',
|
||||
|
|
@ -283,153 +288,159 @@ __PACKAGE__->register_method({
|
|||
method => 'DELETE',
|
||||
description => 'Stop a task.',
|
||||
permissions => {
|
||||
description => "The user needs 'Sys.Modify' permissions on '/nodes/<node>' if they aren't the owner of the task.",
|
||||
user => 'all',
|
||||
description =>
|
||||
"The user needs 'Sys.Modify' permissions on '/nodes/<node>' if they aren't the owner of the task.",
|
||||
user => 'all',
|
||||
},
|
||||
protected => 1,
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
upid => { type => 'string' },
|
||||
}
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
upid => { type => 'string' },
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
|
||||
raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
|
||||
raise_param_exc({ upid => "no such task" }) if ! -f $filename;
|
||||
my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
|
||||
raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
|
||||
raise_param_exc({ upid => "no such task" }) if !-f $filename;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
my $node = $param->{node};
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
my $node = $param->{node};
|
||||
|
||||
$convert_token_task->($task);
|
||||
$convert_token_task->($task);
|
||||
|
||||
if (!$check_task_user->($task, $user)) {
|
||||
$rpcenv->check($user, "/nodes/$node", [ 'Sys.Modify' ]);
|
||||
}
|
||||
if (!$check_task_user->($task, $user)) {
|
||||
$rpcenv->check($user, "/nodes/$node", ['Sys.Modify']);
|
||||
}
|
||||
|
||||
PVE::RPCEnvironment->check_worker($param->{upid}, 1);
|
||||
PVE::RPCEnvironment->check_worker($param->{upid}, 1);
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method({
|
||||
name => 'read_task_log',
|
||||
path => '{upid}/log',
|
||||
method => 'GET',
|
||||
permissions => {
|
||||
description => "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if they aren't the owner of the task.",
|
||||
user => 'all',
|
||||
description =>
|
||||
"The user needs 'Sys.Audit' permissions on '/nodes/<node>' if they aren't the owner of the task.",
|
||||
user => 'all',
|
||||
},
|
||||
protected => 1,
|
||||
download_allowed => 1,
|
||||
description => "Read task log.",
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
upid => {
|
||||
type => 'string',
|
||||
description => "The task's unique ID.",
|
||||
},
|
||||
start => {
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
default => 0,
|
||||
optional => 1,
|
||||
description => "Start at this line when reading the tasklog",
|
||||
},
|
||||
limit => {
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
default => 50,
|
||||
optional => 1,
|
||||
description => "The amount of lines to read from the tasklog.",
|
||||
},
|
||||
download => {
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
description => "Whether the tasklog file should be downloaded. This parameter can't be used in conjunction with other parameters",
|
||||
}
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
upid => {
|
||||
type => 'string',
|
||||
description => "The task's unique ID.",
|
||||
},
|
||||
start => {
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
default => 0,
|
||||
optional => 1,
|
||||
description => "Start at this line when reading the tasklog",
|
||||
},
|
||||
limit => {
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
default => 50,
|
||||
optional => 1,
|
||||
description => "The amount of lines to read from the tasklog.",
|
||||
},
|
||||
download => {
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
description =>
|
||||
"Whether the tasklog file should be downloaded. This parameter can't be used in conjunction with other parameters",
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
n => {
|
||||
description=> "Line number",
|
||||
type=> 'integer',
|
||||
},
|
||||
t => {
|
||||
description=> "Line text",
|
||||
type => 'string',
|
||||
}
|
||||
}
|
||||
}
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {
|
||||
n => {
|
||||
description => "Line number",
|
||||
type => 'integer',
|
||||
},
|
||||
t => {
|
||||
description => "Line text",
|
||||
type => 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
|
||||
raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
|
||||
my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
|
||||
raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
my $node = $param->{node};
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
my $node = $param->{node};
|
||||
|
||||
$convert_token_task->($task);
|
||||
$convert_token_task->($task);
|
||||
|
||||
if (!$check_task_user->($task, $user)) {
|
||||
$rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
|
||||
}
|
||||
if (!$check_task_user->($task, $user)) {
|
||||
$rpcenv->check($user, "/nodes/$node", ['Sys.Audit']);
|
||||
}
|
||||
|
||||
if ($param->{download}) {
|
||||
if (defined($param->{start}) || defined($param->{limit})) {
|
||||
die "'download' cannot be used together with 'start' or 'limit' parameters\n";
|
||||
}
|
||||
# 1024 is a practical cutoff for the size distribution of our log files.
|
||||
my $use_compression = ( -s $filename ) > 1024;
|
||||
if ($param->{download}) {
|
||||
if (defined($param->{start}) || defined($param->{limit})) {
|
||||
die "'download' cannot be used together with 'start' or 'limit' parameters\n";
|
||||
}
|
||||
# 1024 is a practical cutoff for the size distribution of our log files.
|
||||
my $use_compression = (-s $filename) > 1024;
|
||||
|
||||
my $fh;
|
||||
if ($use_compression) {
|
||||
open($fh, "-|", "/usr/bin/gzip", "-c", "$filename")
|
||||
or die "Could not create compressed file stream for file '$filename' - $!\n";
|
||||
} else {
|
||||
open($fh, '<', $filename) or die "Could not open file '$filename' - $!\n";
|
||||
}
|
||||
my $fh;
|
||||
if ($use_compression) {
|
||||
open($fh, "-|", "/usr/bin/gzip", "-c", "$filename")
|
||||
or die "Could not create compressed file stream for file '$filename' - $!\n";
|
||||
} else {
|
||||
open($fh, '<', $filename) or die "Could not open file '$filename' - $!\n";
|
||||
}
|
||||
|
||||
my $task_time = strftime('%FT%TZ', gmtime($task->{starttime}));
|
||||
my $download_name = 'task-'.$task->{node}.'-'.$task->{type}.'-'.$task_time.'.log';
|
||||
my $task_time = strftime('%FT%TZ', gmtime($task->{starttime}));
|
||||
my $download_name =
|
||||
'task-' . $task->{node} . '-' . $task->{type} . '-' . $task_time . '.log';
|
||||
|
||||
return {
|
||||
download => {
|
||||
fh => $fh,
|
||||
stream => 1,
|
||||
'content-encoding' => $use_compression ? 'gzip' : undef,
|
||||
'content-type' => "text/plain",
|
||||
'content-disposition' => "attachment; filename=\"".$download_name."\"",
|
||||
},
|
||||
},
|
||||
} else {
|
||||
my $start = $param->{start} // 0;
|
||||
my $limit = $param->{limit} // 50;
|
||||
return {
|
||||
download => {
|
||||
fh => $fh,
|
||||
stream => 1,
|
||||
'content-encoding' => $use_compression ? 'gzip' : undef,
|
||||
'content-type' => "text/plain",
|
||||
'content-disposition' => "attachment; filename=\"" . $download_name . "\"",
|
||||
},
|
||||
},
|
||||
;
|
||||
} else {
|
||||
my $start = $param->{start} // 0;
|
||||
my $limit = $param->{limit} // 50;
|
||||
|
||||
my ($count, $lines) = PVE::Tools::dump_logfile($filename, $start, $limit);
|
||||
my ($count, $lines) = PVE::Tools::dump_logfile($filename, $start, $limit);
|
||||
|
||||
$rpcenv->set_result_attrib('total', $count);
|
||||
|
||||
return $lines;
|
||||
}
|
||||
}});
|
||||
$rpcenv->set_result_attrib('total', $count);
|
||||
|
||||
return $lines;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
my $exit_status_cache = {};
|
||||
|
||||
|
|
@ -438,92 +449,94 @@ __PACKAGE__->register_method({
|
|||
path => '{upid}/status',
|
||||
method => 'GET',
|
||||
permissions => {
|
||||
description => "The user needs 'Sys.Audit' permissions on '/nodes/<node>' if they are not the owner of the task.",
|
||||
user => 'all',
|
||||
description =>
|
||||
"The user needs 'Sys.Audit' permissions on '/nodes/<node>' if they are not the owner of the task.",
|
||||
user => 'all',
|
||||
},
|
||||
protected => 1,
|
||||
description => "Read task status.",
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
upid => {
|
||||
type => 'string',
|
||||
description => "The task's unique ID.",
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
upid => {
|
||||
type => 'string',
|
||||
description => "The task's unique ID.",
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => "object",
|
||||
properties => {
|
||||
pid => {
|
||||
type => 'integer'
|
||||
},
|
||||
status => {
|
||||
type => 'string', enum => ['running', 'stopped'],
|
||||
},
|
||||
type => {
|
||||
type => 'string',
|
||||
},
|
||||
id => {
|
||||
type => 'string',
|
||||
},
|
||||
user => {
|
||||
type => 'string',
|
||||
},
|
||||
exitstatus => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
upid => {
|
||||
type => 'string',
|
||||
},
|
||||
starttime => {
|
||||
type => 'integer',
|
||||
},
|
||||
pstart => {
|
||||
type => 'integer',
|
||||
},
|
||||
node => {
|
||||
type => 'string',
|
||||
},
|
||||
},
|
||||
type => "object",
|
||||
properties => {
|
||||
pid => {
|
||||
type => 'integer',
|
||||
},
|
||||
status => {
|
||||
type => 'string',
|
||||
enum => ['running', 'stopped'],
|
||||
},
|
||||
type => {
|
||||
type => 'string',
|
||||
},
|
||||
id => {
|
||||
type => 'string',
|
||||
},
|
||||
user => {
|
||||
type => 'string',
|
||||
},
|
||||
exitstatus => {
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
upid => {
|
||||
type => 'string',
|
||||
},
|
||||
starttime => {
|
||||
type => 'integer',
|
||||
},
|
||||
pstart => {
|
||||
type => 'integer',
|
||||
},
|
||||
node => {
|
||||
type => 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
|
||||
raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
|
||||
raise_param_exc({ upid => "no such task" }) if ! -f $filename;
|
||||
my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1);
|
||||
raise_param_exc({ upid => "unable to parse worker upid" }) if !$task;
|
||||
raise_param_exc({ upid => "no such task" }) if !-f $filename;
|
||||
|
||||
my $lines = [];
|
||||
my $lines = [];
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
my $node = $param->{node};
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
my $node = $param->{node};
|
||||
|
||||
$convert_token_task->($task);
|
||||
$convert_token_task->($task);
|
||||
|
||||
if (!$check_task_user->($task, $user)) {
|
||||
$rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
|
||||
}
|
||||
if (!$check_task_user->($task, $user)) {
|
||||
$rpcenv->check($user, "/nodes/$node", ['Sys.Audit']);
|
||||
}
|
||||
|
||||
my $pstart = PVE::ProcFSTools::read_proc_starttime($task->{pid});
|
||||
$task->{status} = ($pstart && ($pstart == $task->{pstart})) ?
|
||||
'running' : 'stopped';
|
||||
my $pstart = PVE::ProcFSTools::read_proc_starttime($task->{pid});
|
||||
$task->{status} = ($pstart && ($pstart == $task->{pstart})) ? 'running' : 'stopped';
|
||||
|
||||
$task->{upid} = $param->{upid}; # include upid
|
||||
$task->{upid} = $param->{upid}; # include upid
|
||||
|
||||
if ($task->{status} eq 'stopped') {
|
||||
if (!defined($exit_status_cache->{$task->{upid}})) {
|
||||
$exit_status_cache->{$task->{upid}} =
|
||||
PVE::Tools::upid_read_status($task->{upid});
|
||||
}
|
||||
$task->{exitstatus} = $exit_status_cache->{$task->{upid}};
|
||||
}
|
||||
if ($task->{status} eq 'stopped') {
|
||||
if (!defined($exit_status_cache->{ $task->{upid} })) {
|
||||
$exit_status_cache->{ $task->{upid} } =
|
||||
PVE::Tools::upid_read_status($task->{upid});
|
||||
}
|
||||
$task->{exitstatus} = $exit_status_cache->{ $task->{upid} };
|
||||
}
|
||||
|
||||
return $task;
|
||||
}});
|
||||
return $task;
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -28,282 +28,287 @@ my sub assert_param_permission_vzdump {
|
|||
PVE::API2::Backup::assert_param_permission_common($rpcenv, $user, $param);
|
||||
|
||||
if (defined($param->{maxfiles}) || defined($param->{'prune-backups'})) {
|
||||
if (my $storeid = PVE::VZDump::get_storage_param($param)) {
|
||||
$rpcenv->check($user, "/storage/$storeid", [ 'Datastore.Allocate' ]);
|
||||
}
|
||||
if (my $storeid = PVE::VZDump::get_storage_param($param)) {
|
||||
$rpcenv->check($user, "/storage/$storeid", ['Datastore.Allocate']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'vzdump',
|
||||
path => '',
|
||||
method => 'POST',
|
||||
description => "Create backup.",
|
||||
permissions => {
|
||||
description => "The user needs 'VM.Backup' permissions on any VM, and "
|
||||
."'Datastore.AllocateSpace' on the backup storage (and fleecing storage when fleecing "
|
||||
."is used). The 'tmpdir', 'dumpdir', 'script' and 'job-id' parameters are restricted "
|
||||
."to the 'root\@pam' user. The 'maxfiles' and 'prune-backups' settings require "
|
||||
."'Datastore.Allocate' on the backup storage. The 'bwlimit', 'performance' and "
|
||||
."'ionice' parameters require 'Sys.Modify' on '/'.",
|
||||
user => 'all',
|
||||
description => "The user needs 'VM.Backup' permissions on any VM, and "
|
||||
. "'Datastore.AllocateSpace' on the backup storage (and fleecing storage when fleecing "
|
||||
. "is used). The 'tmpdir', 'dumpdir', 'script' and 'job-id' parameters are restricted "
|
||||
. "to the 'root\@pam' user. The 'maxfiles' and 'prune-backups' settings require "
|
||||
. "'Datastore.Allocate' on the backup storage. The 'bwlimit', 'performance' and "
|
||||
. "'ionice' parameters require 'Sys.Modify' on '/'.",
|
||||
user => 'all',
|
||||
},
|
||||
protected => 1,
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => PVE::VZDump::Common::json_config_properties({
|
||||
'job-id' => get_standard_option('pve-backup-jobid', {
|
||||
description => "The ID of the backup job. If set, the 'backup-job' metadata field"
|
||||
. " of the backup notification will be set to this value. Only root\@pam"
|
||||
. " can set this parameter.",
|
||||
optional => 1,
|
||||
}),
|
||||
stdout => {
|
||||
type => 'boolean',
|
||||
description => "Write tar to stdout, not to a file.",
|
||||
optional => 1,
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => PVE::VZDump::Common::json_config_properties({
|
||||
'job-id' => get_standard_option(
|
||||
'pve-backup-jobid',
|
||||
{
|
||||
description =>
|
||||
"The ID of the backup job. If set, the 'backup-job' metadata field"
|
||||
. " of the backup notification will be set to this value. Only root\@pam"
|
||||
. " can set this parameter.",
|
||||
optional => 1,
|
||||
},
|
||||
),
|
||||
stdout => {
|
||||
type => 'boolean',
|
||||
description => "Write tar to stdout, not to a file.",
|
||||
optional => 1,
|
||||
},
|
||||
}),
|
||||
},
|
||||
returns => { type => 'string' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
|
||||
my $user = $rpcenv->get_user();
|
||||
my $user = $rpcenv->get_user();
|
||||
|
||||
my $nodename = PVE::INotify::nodename();
|
||||
my $nodename = PVE::INotify::nodename();
|
||||
|
||||
if ($rpcenv->{type} ne 'cli') {
|
||||
raise_param_exc({ node => "option is only allowed on the command line interface."})
|
||||
if $param->{node} && $param->{node} ne $nodename;
|
||||
if ($rpcenv->{type} ne 'cli') {
|
||||
raise_param_exc({ node => "option is only allowed on the command line interface." })
|
||||
if $param->{node} && $param->{node} ne $nodename;
|
||||
|
||||
raise_param_exc({ stdout => "option is only allowed on the command line interface."})
|
||||
if $param->{stdout};
|
||||
}
|
||||
raise_param_exc({
|
||||
stdout => "option is only allowed on the command line interface." })
|
||||
if $param->{stdout};
|
||||
}
|
||||
|
||||
assert_param_permission_vzdump($rpcenv, $user, $param);
|
||||
assert_param_permission_vzdump($rpcenv, $user, $param);
|
||||
|
||||
PVE::VZDump::verify_vzdump_parameters($param, 1);
|
||||
PVE::VZDump::verify_vzdump_parameters($param, 1);
|
||||
|
||||
# silent exit if we run on wrong node
|
||||
return 'OK' if $param->{node} && $param->{node} ne $nodename;
|
||||
# silent exit if we run on wrong node
|
||||
return 'OK' if $param->{node} && $param->{node} ne $nodename;
|
||||
|
||||
my $cmdline = PVE::VZDump::Common::command_line($param);
|
||||
my $cmdline = PVE::VZDump::Common::command_line($param);
|
||||
|
||||
my $vmids_per_node = PVE::VZDump::get_included_guests($param);
|
||||
my $vmids_per_node = PVE::VZDump::get_included_guests($param);
|
||||
|
||||
my $local_vmids = delete $vmids_per_node->{$nodename} // [];
|
||||
my $local_vmids = delete $vmids_per_node->{$nodename} // [];
|
||||
|
||||
# include IDs for deleted guests, and visibly fail later
|
||||
my $orphaned_vmids = delete $vmids_per_node->{''} // [];
|
||||
push @{$local_vmids}, @{$orphaned_vmids};
|
||||
# include IDs for deleted guests, and visibly fail later
|
||||
my $orphaned_vmids = delete $vmids_per_node->{''} // [];
|
||||
push @{$local_vmids}, @{$orphaned_vmids};
|
||||
|
||||
my $skiplist = [ map { @$_ } values $vmids_per_node->%* ];
|
||||
my $skiplist = [map { @$_ } values $vmids_per_node->%*];
|
||||
|
||||
if($param->{stop}){
|
||||
PVE::VZDump::stop_running_backups();
|
||||
return 'OK' if !scalar(@{$local_vmids});
|
||||
}
|
||||
if ($param->{stop}) {
|
||||
PVE::VZDump::stop_running_backups();
|
||||
return 'OK' if !scalar(@{$local_vmids});
|
||||
}
|
||||
|
||||
# silent exit if specified VMs run on other nodes
|
||||
return "OK" if !scalar(@{$local_vmids}) && !$param->{all};
|
||||
# silent exit if specified VMs run on other nodes
|
||||
return "OK" if !scalar(@{$local_vmids}) && !$param->{all};
|
||||
|
||||
PVE::VZDump::parse_mailto_exclude_path($param);
|
||||
PVE::VZDump::parse_mailto_exclude_path($param);
|
||||
|
||||
die "you can only backup a single VM with option --stdout\n"
|
||||
if $param->{stdout} && scalar(@{$local_vmids}) != 1;
|
||||
die "you can only backup a single VM with option --stdout\n"
|
||||
if $param->{stdout} && scalar(@{$local_vmids}) != 1;
|
||||
|
||||
if (my $storeid = PVE::VZDump::get_storage_param($param)) {
|
||||
$rpcenv->check($user, "/storage/$storeid", [ 'Datastore.AllocateSpace' ]);
|
||||
}
|
||||
if (my $storeid = PVE::VZDump::get_storage_param($param)) {
|
||||
$rpcenv->check($user, "/storage/$storeid", ['Datastore.AllocateSpace']);
|
||||
}
|
||||
|
||||
my $worker = sub {
|
||||
my $upid = shift;
|
||||
my $worker = sub {
|
||||
my $upid = shift;
|
||||
|
||||
$SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
|
||||
die "interrupted by signal\n";
|
||||
};
|
||||
$SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub {
|
||||
die "interrupted by signal\n";
|
||||
};
|
||||
|
||||
$param->{vmids} = $local_vmids;
|
||||
my $vzdump = PVE::VZDump->new($cmdline, $param, $skiplist);
|
||||
$param->{vmids} = $local_vmids;
|
||||
my $vzdump = PVE::VZDump->new($cmdline, $param, $skiplist);
|
||||
|
||||
my $LOCK_FH = eval {
|
||||
$vzdump->getlock($upid); # only one process allowed
|
||||
};
|
||||
if (my $err = $@) {
|
||||
$vzdump->send_notification([], 0, $err);
|
||||
exit(-1);
|
||||
}
|
||||
my $LOCK_FH = eval {
|
||||
$vzdump->getlock($upid); # only one process allowed
|
||||
};
|
||||
if (my $err = $@) {
|
||||
$vzdump->send_notification([], 0, $err);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (defined($param->{ionice})) {
|
||||
if ($param->{ionice} > 7) {
|
||||
PVE::VZDump::run_command(undef, "ionice -c3 -p $$");
|
||||
} else {
|
||||
PVE::VZDump::run_command(undef, "ionice -c2 -n$param->{ionice} -p $$");
|
||||
}
|
||||
}
|
||||
$vzdump->exec_backup($rpcenv, $user);
|
||||
if (defined($param->{ionice})) {
|
||||
if ($param->{ionice} > 7) {
|
||||
PVE::VZDump::run_command(undef, "ionice -c3 -p $$");
|
||||
} else {
|
||||
PVE::VZDump::run_command(undef, "ionice -c2 -n$param->{ionice} -p $$");
|
||||
}
|
||||
}
|
||||
$vzdump->exec_backup($rpcenv, $user);
|
||||
|
||||
close($LOCK_FH);
|
||||
};
|
||||
close($LOCK_FH);
|
||||
};
|
||||
|
||||
open STDOUT, '>/dev/null' if $param->{quiet} && !$param->{stdout};
|
||||
open STDERR, '>/dev/null' if $param->{quiet};
|
||||
open STDOUT, '>/dev/null' if $param->{quiet} && !$param->{stdout};
|
||||
open STDERR, '>/dev/null' if $param->{quiet};
|
||||
|
||||
if ($rpcenv->{type} eq 'cli') {
|
||||
if ($param->{stdout}) {
|
||||
if ($rpcenv->{type} eq 'cli') {
|
||||
if ($param->{stdout}) {
|
||||
|
||||
open my $saved_stdout, ">&STDOUT"
|
||||
|| die "can't dup STDOUT: $!\n";
|
||||
open my $saved_stdout, ">&STDOUT"
|
||||
|| die "can't dup STDOUT: $!\n";
|
||||
|
||||
open STDOUT, '>&STDERR' ||
|
||||
die "unable to redirect STDOUT: $!\n";
|
||||
open STDOUT, '>&STDERR'
|
||||
|| die "unable to redirect STDOUT: $!\n";
|
||||
|
||||
$param->{stdout} = $saved_stdout;
|
||||
}
|
||||
}
|
||||
$param->{stdout} = $saved_stdout;
|
||||
}
|
||||
}
|
||||
|
||||
my $taskid;
|
||||
$taskid = $local_vmids->[0] if scalar(@{$local_vmids}) == 1;
|
||||
my $taskid;
|
||||
$taskid = $local_vmids->[0] if scalar(@{$local_vmids}) == 1;
|
||||
|
||||
return $rpcenv->fork_worker('vzdump', $taskid, $user, $worker);
|
||||
}});
|
||||
return $rpcenv->fork_worker('vzdump', $taskid, $user, $worker);
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'defaults',
|
||||
path => 'defaults',
|
||||
method => 'GET',
|
||||
description => "Get the currently configured vzdump defaults.",
|
||||
permissions => {
|
||||
description => "The user needs 'Datastore.Audit' or 'Datastore.AllocateSpace' " .
|
||||
"permissions for the specified storage (or default storage if none specified). Some " .
|
||||
"properties are only returned when the user has 'Sys.Audit' permissions for the node.",
|
||||
user => 'all',
|
||||
description => "The user needs 'Datastore.Audit' or 'Datastore.AllocateSpace' "
|
||||
. "permissions for the specified storage (or default storage if none specified). Some "
|
||||
. "properties are only returned when the user has 'Sys.Audit' permissions for the node.",
|
||||
user => 'all',
|
||||
},
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
storage => get_standard_option('pve-storage-id', { optional => 1 }),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
storage => get_standard_option('pve-storage-id', { optional => 1 }),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
properties => PVE::VZDump::Common::json_config_properties(),
|
||||
type => 'object',
|
||||
additionalProperties => 0,
|
||||
properties => PVE::VZDump::Common::json_config_properties(),
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $node = extract_param($param, 'node');
|
||||
my $storage = extract_param($param, 'storage');
|
||||
my $node = extract_param($param, 'node');
|
||||
my $storage = extract_param($param, 'storage');
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $res = PVE::VZDump::read_vzdump_defaults();
|
||||
my $res = PVE::VZDump::read_vzdump_defaults();
|
||||
|
||||
$res->{storage} = $storage if defined($storage);
|
||||
$res->{storage} = $storage if defined($storage);
|
||||
|
||||
if (!defined($res->{dumpdir}) && !defined($res->{storage})) {
|
||||
$res->{storage} = 'local';
|
||||
}
|
||||
if (!defined($res->{dumpdir}) && !defined($res->{storage})) {
|
||||
$res->{storage} = 'local';
|
||||
}
|
||||
|
||||
if (defined($res->{storage})) {
|
||||
$rpcenv->check_any(
|
||||
$authuser,
|
||||
"/storage/$res->{storage}",
|
||||
['Datastore.Audit', 'Datastore.AllocateSpace'],
|
||||
);
|
||||
if (defined($res->{storage})) {
|
||||
$rpcenv->check_any(
|
||||
$authuser,
|
||||
"/storage/$res->{storage}",
|
||||
['Datastore.Audit', 'Datastore.AllocateSpace'],
|
||||
);
|
||||
|
||||
my $info = PVE::VZDump::storage_info($res->{storage});
|
||||
for my $key (qw(dumpdir prune-backups)) {
|
||||
$res->{$key} = $info->{$key} if defined($info->{$key});
|
||||
}
|
||||
}
|
||||
my $info = PVE::VZDump::storage_info($res->{storage});
|
||||
for my $key (qw(dumpdir prune-backups)) {
|
||||
$res->{$key} = $info->{$key} if defined($info->{$key});
|
||||
}
|
||||
}
|
||||
|
||||
if (defined($res->{'prune-backups'})) {
|
||||
$res->{'prune-backups'} = PVE::JSONSchema::print_property_string(
|
||||
$res->{'prune-backups'},
|
||||
'prune-backups',
|
||||
);
|
||||
}
|
||||
if (defined($res->{'prune-backups'})) {
|
||||
$res->{'prune-backups'} = PVE::JSONSchema::print_property_string(
|
||||
$res->{'prune-backups'}, 'prune-backups',
|
||||
);
|
||||
}
|
||||
|
||||
$res->{mailto} = join(",", @{$res->{mailto}})
|
||||
if defined($res->{mailto});
|
||||
$res->{mailto} = join(",", @{ $res->{mailto} })
|
||||
if defined($res->{mailto});
|
||||
|
||||
$res->{'exclude-path'} = join(",", @{$res->{'exclude-path'}})
|
||||
if defined($res->{'exclude-path'});
|
||||
$res->{'exclude-path'} = join(",", @{ $res->{'exclude-path'} })
|
||||
if defined($res->{'exclude-path'});
|
||||
|
||||
# normal backup users don't need to know these
|
||||
if (!$rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'], 1)) {
|
||||
delete $res->{mailto};
|
||||
delete $res->{tmpdir};
|
||||
delete $res->{dumpdir};
|
||||
delete $res->{script};
|
||||
delete $res->{ionice};
|
||||
}
|
||||
# normal backup users don't need to know these
|
||||
if (!$rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'], 1)) {
|
||||
delete $res->{mailto};
|
||||
delete $res->{tmpdir};
|
||||
delete $res->{dumpdir};
|
||||
delete $res->{script};
|
||||
delete $res->{ionice};
|
||||
}
|
||||
|
||||
my $pool = $res->{pool};
|
||||
if (defined($pool) &&
|
||||
!$rpcenv->check($authuser, "/pool/$pool", ['Pool.Audit'], 1)) {
|
||||
delete $res->{pool};
|
||||
}
|
||||
my $pool = $res->{pool};
|
||||
if (
|
||||
defined($pool)
|
||||
&& !$rpcenv->check($authuser, "/pool/$pool", ['Pool.Audit'], 1)
|
||||
) {
|
||||
delete $res->{pool};
|
||||
}
|
||||
|
||||
return $res;
|
||||
}});
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'extractconfig',
|
||||
path => 'extractconfig',
|
||||
method => 'GET',
|
||||
description => "Extract configuration from vzdump backup archive.",
|
||||
permissions => {
|
||||
description => "The user needs 'VM.Backup' permissions on the backed up guest ID, and 'Datastore.AllocateSpace' on the backup storage.",
|
||||
user => 'all',
|
||||
description =>
|
||||
"The user needs 'VM.Backup' permissions on the backed up guest ID, and 'Datastore.AllocateSpace' on the backup storage.",
|
||||
user => 'all',
|
||||
},
|
||||
protected => 1,
|
||||
proxyto => 'node',
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
volume => {
|
||||
description => "Volume identifier",
|
||||
type => 'string',
|
||||
completion => \&PVE::Storage::complete_volume,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
volume => {
|
||||
description => "Volume identifier",
|
||||
type => 'string',
|
||||
completion => \&PVE::Storage::complete_volume,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'string' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $volume = extract_param($param, 'volume');
|
||||
my $volume = extract_param($param, 'volume');
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $storage_cfg = PVE::Storage::config();
|
||||
PVE::Storage::check_volume_access(
|
||||
$rpcenv,
|
||||
$authuser,
|
||||
$storage_cfg,
|
||||
undef,
|
||||
$volume,
|
||||
'backup',
|
||||
);
|
||||
my $storage_cfg = PVE::Storage::config();
|
||||
PVE::Storage::check_volume_access(
|
||||
$rpcenv, $authuser, $storage_cfg, undef, $volume, 'backup',
|
||||
);
|
||||
|
||||
if (PVE::Storage::parse_volume_id($volume, 1)) {
|
||||
my (undef, undef, $ownervm) = PVE::Storage::parse_volname($storage_cfg, $volume);
|
||||
$rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']);
|
||||
}
|
||||
if (PVE::Storage::parse_volume_id($volume, 1)) {
|
||||
my (undef, undef, $ownervm) = PVE::Storage::parse_volname($storage_cfg, $volume);
|
||||
$rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']);
|
||||
}
|
||||
|
||||
return PVE::Storage::extract_vzdump_config($storage_cfg, $volume);
|
||||
}});
|
||||
return PVE::Storage::extract_vzdump_config($storage_cfg, $volume);
|
||||
},
|
||||
});
|
||||
|
||||
1;
|
||||
|
|
|
|||
231
PVE/API2Tools.pm
231
PVE/API2Tools.pm
|
|
@ -25,16 +25,18 @@ sub get_hwaddress {
|
|||
my $fn = '/etc/ssh/ssh_host_rsa_key.pub';
|
||||
my $st = stat($fn);
|
||||
|
||||
if (defined($hwaddress)
|
||||
&& $hwaddress_st->{mtime} == $st->mtime
|
||||
&& $hwaddress_st->{ino} == $st->ino
|
||||
&& $hwaddress_st->{dev} == $st->dev) {
|
||||
return $hwaddress;
|
||||
if (
|
||||
defined($hwaddress)
|
||||
&& $hwaddress_st->{mtime} == $st->mtime
|
||||
&& $hwaddress_st->{ino} == $st->ino
|
||||
&& $hwaddress_st->{dev} == $st->dev
|
||||
) {
|
||||
return $hwaddress;
|
||||
}
|
||||
|
||||
my $sshkey = PVE::Tools::file_get_contents($fn);
|
||||
$hwaddress = uc(md5_hex($sshkey));
|
||||
$hwaddress_st->@{'mtime', 'ino', 'dev'} = ($st->mtime, $st->ino, $st->dev);
|
||||
$hwaddress_st->@{ 'mtime', 'ino', 'dev' } = ($st->mtime, $st->ino, $st->dev);
|
||||
|
||||
return $hwaddress;
|
||||
}
|
||||
|
|
@ -43,35 +45,40 @@ sub extract_node_stats {
|
|||
my ($node, $members, $rrd, $exclude_stats) = @_;
|
||||
|
||||
my $entry = {
|
||||
id => "node/$node",
|
||||
node => $node,
|
||||
type => "node",
|
||||
status => 'unknown',
|
||||
id => "node/$node",
|
||||
node => $node,
|
||||
type => "node",
|
||||
status => 'unknown',
|
||||
};
|
||||
|
||||
if (my $d = $rrd->{"pve2-node/$node"}) {
|
||||
|
||||
if (!$members || # no cluster
|
||||
($members->{$node} && $members->{$node}->{online})) {
|
||||
if (!$exclude_stats) {
|
||||
$entry->{uptime} = ($d->[0] || 0) + 0;
|
||||
$entry->{cpu} = ($d->[5] || 0) + 0;
|
||||
$entry->{mem} = ($d->[8] || 0) + 0;
|
||||
$entry->{disk} = ($d->[12] || 0) + 0;
|
||||
}
|
||||
$entry->{status} = 'online';
|
||||
}
|
||||
$entry->{level} = $d->[1];
|
||||
if (!$exclude_stats) {
|
||||
$entry->{maxcpu} = ($d->[4] || 0) + 0;
|
||||
$entry->{maxmem} = ($d->[7] || 0) + 0;
|
||||
$entry->{maxdisk} = ($d->[11] || 0) + 0;
|
||||
}
|
||||
if (
|
||||
!$members || # no cluster
|
||||
($members->{$node} && $members->{$node}->{online})
|
||||
) {
|
||||
if (!$exclude_stats) {
|
||||
$entry->{uptime} = ($d->[0] || 0) + 0;
|
||||
$entry->{cpu} = ($d->[5] || 0) + 0;
|
||||
$entry->{mem} = ($d->[8] || 0) + 0;
|
||||
$entry->{disk} = ($d->[12] || 0) + 0;
|
||||
}
|
||||
$entry->{status} = 'online';
|
||||
}
|
||||
$entry->{level} = $d->[1];
|
||||
if (!$exclude_stats) {
|
||||
$entry->{maxcpu} = ($d->[4] || 0) + 0;
|
||||
$entry->{maxmem} = ($d->[7] || 0) + 0;
|
||||
$entry->{maxdisk} = ($d->[11] || 0) + 0;
|
||||
}
|
||||
}
|
||||
|
||||
if ($members && $members->{$node} &&
|
||||
!$members->{$node}->{online}) {
|
||||
$entry->{status} = 'offline';
|
||||
if (
|
||||
$members
|
||||
&& $members->{$node}
|
||||
&& !$members->{$node}->{online}
|
||||
) {
|
||||
$entry->{status} = 'offline';
|
||||
}
|
||||
|
||||
return $entry;
|
||||
|
|
@ -81,49 +88,49 @@ sub extract_vm_stats {
|
|||
my ($vmid, $data, $rrd) = @_;
|
||||
|
||||
my $entry = {
|
||||
id => "$data->{type}/$vmid",
|
||||
vmid => $vmid + 0,
|
||||
node => $data->{node},
|
||||
type => $data->{type},
|
||||
status => 'unknown',
|
||||
id => "$data->{type}/$vmid",
|
||||
vmid => $vmid + 0,
|
||||
node => $data->{node},
|
||||
type => $data->{type},
|
||||
status => 'unknown',
|
||||
};
|
||||
|
||||
my $d;
|
||||
|
||||
if ($d = $rrd->{"pve2-vm/$vmid"}) {
|
||||
|
||||
$entry->{uptime} = ($d->[0] || 0) + 0;
|
||||
$entry->{name} = $d->[1];
|
||||
$entry->{status} = $entry->{uptime} ? 'running' : 'stopped';
|
||||
$entry->{maxcpu} = ($d->[3] || 0) + 0;
|
||||
$entry->{cpu} = ($d->[4] || 0) + 0;
|
||||
$entry->{maxmem} = ($d->[5] || 0) + 0;
|
||||
$entry->{mem} = ($d->[6] || 0) + 0;
|
||||
$entry->{maxdisk} = ($d->[7] || 0) + 0;
|
||||
$entry->{disk} = ($d->[8] || 0) + 0;
|
||||
$entry->{netin} = ($d->[9] || 0) + 0;
|
||||
$entry->{netout} = ($d->[10] || 0) + 0;
|
||||
$entry->{diskread} = ($d->[11] || 0) + 0;
|
||||
$entry->{diskwrite} = ($d->[12] || 0) + 0;
|
||||
$entry->{uptime} = ($d->[0] || 0) + 0;
|
||||
$entry->{name} = $d->[1];
|
||||
$entry->{status} = $entry->{uptime} ? 'running' : 'stopped';
|
||||
$entry->{maxcpu} = ($d->[3] || 0) + 0;
|
||||
$entry->{cpu} = ($d->[4] || 0) + 0;
|
||||
$entry->{maxmem} = ($d->[5] || 0) + 0;
|
||||
$entry->{mem} = ($d->[6] || 0) + 0;
|
||||
$entry->{maxdisk} = ($d->[7] || 0) + 0;
|
||||
$entry->{disk} = ($d->[8] || 0) + 0;
|
||||
$entry->{netin} = ($d->[9] || 0) + 0;
|
||||
$entry->{netout} = ($d->[10] || 0) + 0;
|
||||
$entry->{diskread} = ($d->[11] || 0) + 0;
|
||||
$entry->{diskwrite} = ($d->[12] || 0) + 0;
|
||||
|
||||
} elsif ($d = $rrd->{"pve2.3-vm/$vmid"}) {
|
||||
|
||||
$entry->{uptime} = ($d->[0] || 0) + 0;
|
||||
$entry->{name} = $d->[1];
|
||||
$entry->{status} = $d->[2];
|
||||
$entry->{template} = $d->[3] + 0;
|
||||
$entry->{uptime} = ($d->[0] || 0) + 0;
|
||||
$entry->{name} = $d->[1];
|
||||
$entry->{status} = $d->[2];
|
||||
$entry->{template} = $d->[3] + 0;
|
||||
|
||||
$entry->{maxcpu} = ($d->[5] || 0) + 0;
|
||||
$entry->{cpu} = ($d->[6] || 0) + 0;
|
||||
$entry->{maxmem} = ($d->[7] || 0) + 0;
|
||||
$entry->{mem} = ($d->[8] || 0) + 0;
|
||||
$entry->{maxdisk} = ($d->[9] || 0) + 0;
|
||||
$entry->{disk} = ($d->[10] || 0) + 0;
|
||||
$entry->{netin} = ($d->[11] || 0) + 0;
|
||||
$entry->{netout} = ($d->[12] || 0) + 0;
|
||||
$entry->{diskread} = ($d->[13] || 0) + 0;
|
||||
$entry->{diskwrite} = ($d->[14] || 0) + 0;
|
||||
};
|
||||
$entry->{maxcpu} = ($d->[5] || 0) + 0;
|
||||
$entry->{cpu} = ($d->[6] || 0) + 0;
|
||||
$entry->{maxmem} = ($d->[7] || 0) + 0;
|
||||
$entry->{mem} = ($d->[8] || 0) + 0;
|
||||
$entry->{maxdisk} = ($d->[9] || 0) + 0;
|
||||
$entry->{disk} = ($d->[10] || 0) + 0;
|
||||
$entry->{netin} = ($d->[11] || 0) + 0;
|
||||
$entry->{netout} = ($d->[12] || 0) + 0;
|
||||
$entry->{diskread} = ($d->[13] || 0) + 0;
|
||||
$entry->{diskwrite} = ($d->[14] || 0) + 0;
|
||||
}
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
|
@ -134,20 +141,20 @@ sub extract_storage_stats {
|
|||
my $content = PVE::Storage::Plugin::content_hash_to_string($scfg->{content});
|
||||
|
||||
my $entry = {
|
||||
id => "storage/$node/$storeid",
|
||||
storage => $storeid,
|
||||
node => $node,
|
||||
type => 'storage',
|
||||
plugintype => $scfg->{type},
|
||||
status => 'unknown',
|
||||
shared => $scfg->{shared} || 0,
|
||||
content => $content,
|
||||
id => "storage/$node/$storeid",
|
||||
storage => $storeid,
|
||||
node => $node,
|
||||
type => 'storage',
|
||||
plugintype => $scfg->{type},
|
||||
status => 'unknown',
|
||||
shared => $scfg->{shared} || 0,
|
||||
content => $content,
|
||||
};
|
||||
|
||||
if (my $d = $rrd->{"pve2-storage/$node/$storeid"}) {
|
||||
$entry->{maxdisk} = ($d->[1] || 0) + 0;
|
||||
$entry->{disk} = ($d->[2] || 0) + 0;
|
||||
$entry->{status} = 'available';
|
||||
$entry->{maxdisk} = ($d->[1] || 0) + 0;
|
||||
$entry->{disk} = ($d->[2] || 0) + 0;
|
||||
$entry->{status} = 'available';
|
||||
}
|
||||
|
||||
return $entry;
|
||||
|
|
@ -165,7 +172,7 @@ sub parse_http_proxy {
|
|||
my ($username, $password);
|
||||
|
||||
if (defined(my $p_auth = $uri->userinfo())) {
|
||||
($username, $password) = map URI::Escape::uri_unescape($_), split(":", $p_auth, 2);
|
||||
($username, $password) = map URI::Escape::uri_unescape($_), split(":", $p_auth, 2);
|
||||
}
|
||||
|
||||
return ("$host:$port", $username, $password);
|
||||
|
|
@ -183,13 +190,23 @@ sub run_spiceterm {
|
|||
my $port = PVE::Tools::next_spice_port($family);
|
||||
|
||||
my ($ticket, undef, $remote_viewer_config) =
|
||||
PVE::AccessControl::remote_viewer_config($authuser, $vmid, $node, $proxy, $title, $port);
|
||||
PVE::AccessControl::remote_viewer_config($authuser, $vmid, $node, $proxy, $title, $port);
|
||||
|
||||
my $timeout = 40;
|
||||
|
||||
my $cmd = ['/usr/bin/spiceterm', '--port', $port, '--addr', 'localhost',
|
||||
'--timeout', $timeout, '--authpath', $authpath,
|
||||
'--permissions', $permissions];
|
||||
my $cmd = [
|
||||
'/usr/bin/spiceterm',
|
||||
'--port',
|
||||
$port,
|
||||
'--addr',
|
||||
'localhost',
|
||||
'--timeout',
|
||||
$timeout,
|
||||
'--authpath',
|
||||
$authpath,
|
||||
'--permissions',
|
||||
$permissions,
|
||||
];
|
||||
|
||||
my $dcconf = PVE::Cluster::cfs_read_file('datacenter.cfg');
|
||||
push @$cmd, '--keymap', $dcconf->{keyboard} if $dcconf->{keyboard};
|
||||
|
|
@ -197,34 +214,40 @@ sub run_spiceterm {
|
|||
push @$cmd, '--', @$shcmd;
|
||||
|
||||
my $realcmd = sub {
|
||||
my $upid = shift;
|
||||
my $upid = shift;
|
||||
|
||||
syslog ('info', "starting spiceterm $upid - $title\n");
|
||||
syslog('info', "starting spiceterm $upid - $title\n");
|
||||
|
||||
my $cmdstr = join (' ', @$cmd);
|
||||
syslog ('info', "launch command: $cmdstr");
|
||||
my $cmdstr = join(' ', @$cmd);
|
||||
syslog('info', "launch command: $cmdstr");
|
||||
|
||||
eval {
|
||||
foreach my $k (keys %ENV) {
|
||||
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} = '/';
|
||||
$ENV{SPICE_TICKET} = $ticket;
|
||||
eval {
|
||||
foreach my $k (keys %ENV) {
|
||||
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} = '/';
|
||||
$ENV{SPICE_TICKET} = $ticket;
|
||||
|
||||
PVE::Tools::run_command($cmd, errmsg => 'spiceterm failed\n', keeplocale => 1);
|
||||
};
|
||||
if (my $err = $@) {
|
||||
syslog ('err', $err);
|
||||
}
|
||||
PVE::Tools::run_command($cmd, errmsg => 'spiceterm failed\n', keeplocale => 1);
|
||||
};
|
||||
if (my $err = $@) {
|
||||
syslog('err', $err);
|
||||
}
|
||||
|
||||
return;
|
||||
return;
|
||||
};
|
||||
|
||||
if ($vmid) {
|
||||
$rpcenv->fork_worker('spiceproxy', $vmid, $authuser, $realcmd);
|
||||
$rpcenv->fork_worker('spiceproxy', $vmid, $authuser, $realcmd);
|
||||
} else {
|
||||
$rpcenv->fork_worker('spiceshell', undef, $authuser, $realcmd);
|
||||
$rpcenv->fork_worker('spiceshell', undef, $authuser, $realcmd);
|
||||
}
|
||||
|
||||
PVE::Tools::wait_for_vnc_port($port);
|
||||
|
|
@ -237,13 +260,13 @@ sub resolve_proxyto {
|
|||
|
||||
my $node;
|
||||
if ($proxyto_callback) {
|
||||
$node = $proxyto_callback->($rpcenv, $proxyto, $uri_param);
|
||||
die "internal error - proxyto_callback returned nothing\n"
|
||||
if !$node;
|
||||
$node = $proxyto_callback->($rpcenv, $proxyto, $uri_param);
|
||||
die "internal error - proxyto_callback returned nothing\n"
|
||||
if !$node;
|
||||
} else {
|
||||
$node = $uri_param->{$proxyto};
|
||||
raise_param_exc({ $proxyto => "proxyto parameter does not exist"})
|
||||
if !$node;
|
||||
$node = $uri_param->{$proxyto};
|
||||
raise_param_exc({ $proxyto => "proxyto parameter does not exist" })
|
||||
if !$node;
|
||||
}
|
||||
return $node;
|
||||
}
|
||||
|
|
@ -260,7 +283,7 @@ sub get_resource_pool_guest_members {
|
|||
|
||||
die "pool '$pool' does not exist\n" if !$data;
|
||||
|
||||
my $pool_members = [ grep { $idlist->{$_} } keys %{$data->{vms}} ];
|
||||
my $pool_members = [grep { $idlist->{$_} } keys %{ $data->{vms} }];
|
||||
|
||||
return $pool_members;
|
||||
}
|
||||
|
|
|
|||
245
PVE/APLInfo.pm
245
PVE/APLInfo.pm
|
|
@ -20,10 +20,10 @@ sub logmsg {
|
|||
|
||||
chomp $msg;
|
||||
|
||||
my $tstr = strftime ("%F %H:%M:%S", localtime);
|
||||
my $tstr = strftime("%F %H:%M:%S", localtime);
|
||||
|
||||
foreach my $line (split (/\n/, $msg)) {
|
||||
print $logfd "$tstr $line\n";
|
||||
foreach my $line (split(/\n/, $msg)) {
|
||||
print $logfd "$tstr $line\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -33,84 +33,89 @@ sub read_aplinfo_from_fh {
|
|||
local $/ = "";
|
||||
|
||||
while (my $rec = <$fh>) {
|
||||
chomp $rec;
|
||||
chomp $rec;
|
||||
|
||||
my $res = {};
|
||||
my $res = {};
|
||||
|
||||
while ($rec) {
|
||||
while ($rec) {
|
||||
|
||||
if ($rec =~ s/^Description:\s*([^\n]*)(\n\s+.*)*$//si) {
|
||||
$res->{headline} = $1;
|
||||
my $long = $2 || '';
|
||||
$long =~ s/\n\s+/ /g;
|
||||
$long =~ s/^\s+//g;
|
||||
$long =~ s/\s+$//g;
|
||||
$res->{description} = $long;
|
||||
} elsif ($rec =~ s/^Version:\s*(.*\S)\s*\n//i) {
|
||||
my $version = $1;
|
||||
if ($version =~ m/^(\d[a-zA-Z0-9\.\+\-\:\~]*)(-(\d+))?$/) {
|
||||
$res->{version} = $version;
|
||||
} else {
|
||||
my $msg = "unable to parse appliance record: version = '$version'\n";
|
||||
$update ? die $msg : warn $msg;
|
||||
}
|
||||
} elsif ($rec =~ s/^Type:\s*(.*\S)\s*\n//i) {
|
||||
my $type = $1;
|
||||
if ($type =~ m/^(openvz|lxc)$/) {
|
||||
$res->{type} = $type;
|
||||
} else {
|
||||
my $msg = "unable to parse appliance record: unknown type '$type'\n";
|
||||
$update ? die $msg : warn $msg;
|
||||
}
|
||||
} elsif ($rec =~ s/^([^:]+):\s*(.*\S)\s*\n//) {
|
||||
$res->{lc $1} = $2;
|
||||
} else {
|
||||
my $msg = "unable to parse appliance record: $rec\n";
|
||||
$update ? die $msg : warn $msg;
|
||||
$res = {};
|
||||
last;
|
||||
}
|
||||
}
|
||||
if ($rec =~ s/^Description:\s*([^\n]*)(\n\s+.*)*$//si) {
|
||||
$res->{headline} = $1;
|
||||
my $long = $2 || '';
|
||||
$long =~ s/\n\s+/ /g;
|
||||
$long =~ s/^\s+//g;
|
||||
$long =~ s/\s+$//g;
|
||||
$res->{description} = $long;
|
||||
} elsif ($rec =~ s/^Version:\s*(.*\S)\s*\n//i) {
|
||||
my $version = $1;
|
||||
if ($version =~ m/^(\d[a-zA-Z0-9\.\+\-\:\~]*)(-(\d+))?$/) {
|
||||
$res->{version} = $version;
|
||||
} else {
|
||||
my $msg = "unable to parse appliance record: version = '$version'\n";
|
||||
$update ? die $msg : warn $msg;
|
||||
}
|
||||
} elsif ($rec =~ s/^Type:\s*(.*\S)\s*\n//i) {
|
||||
my $type = $1;
|
||||
if ($type =~ m/^(openvz|lxc)$/) {
|
||||
$res->{type} = $type;
|
||||
} else {
|
||||
my $msg = "unable to parse appliance record: unknown type '$type'\n";
|
||||
$update ? die $msg : warn $msg;
|
||||
}
|
||||
} elsif ($rec =~ s/^([^:]+):\s*(.*\S)\s*\n//) {
|
||||
$res->{ lc $1 } = $2;
|
||||
} else {
|
||||
my $msg = "unable to parse appliance record: $rec\n";
|
||||
$update ? die $msg : warn $msg;
|
||||
$res = {};
|
||||
last;
|
||||
}
|
||||
}
|
||||
|
||||
if ($res->{'package'} eq 'pve-web-news' && $res->{description}) {
|
||||
$list->{'all'}->{$res->{'package'}} = $res;
|
||||
next;
|
||||
}
|
||||
if ($res->{'package'} eq 'pve-web-news' && $res->{description}) {
|
||||
$list->{'all'}->{ $res->{'package'} } = $res;
|
||||
next;
|
||||
}
|
||||
|
||||
$res->{section} = 'unknown' if !$res->{section};
|
||||
$res->{section} = 'unknown' if !$res->{section};
|
||||
|
||||
if ($res->{'package'} && $res->{type} && $res->{os} && $res->{version} &&
|
||||
$res->{infopage}) {
|
||||
my $template;
|
||||
if ($res->{location}) {
|
||||
$template = $res->{location};
|
||||
$template =~ s|.*/([^/]+$PVE::Storage::VZTMPL_EXT_RE_1)$|$1|;
|
||||
if ($res->{location} !~ m|^([a-zA-Z]+)\://|) {
|
||||
# relative location (no http:// prefix)
|
||||
$res->{location} = "$source/$res->{location}";
|
||||
}
|
||||
} else {
|
||||
my $arch = $res->{architecture} || 'i386';
|
||||
$template = "$res->{os}-$res->{package}_$res->{version}_$arch.tar.gz";
|
||||
$template =~ s/$res->{os}-$res->{os}-/$res->{os}-/;
|
||||
$res->{location} = "$source/$res->{section}/$template";
|
||||
}
|
||||
$res->{source} = $source;
|
||||
$res->{template} = $template;
|
||||
$list->{$res->{section}}->{$template} = $res;
|
||||
$list->{'all'}->{$template} = $res;
|
||||
} else {
|
||||
my $msg = "found incomplete appliance records\n";
|
||||
$update ? die $msg : warn $msg;
|
||||
}
|
||||
if (
|
||||
$res->{'package'}
|
||||
&& $res->{type}
|
||||
&& $res->{os}
|
||||
&& $res->{version}
|
||||
&& $res->{infopage}
|
||||
) {
|
||||
my $template;
|
||||
if ($res->{location}) {
|
||||
$template = $res->{location};
|
||||
$template =~ s|.*/([^/]+$PVE::Storage::VZTMPL_EXT_RE_1)$|$1|;
|
||||
if ($res->{location} !~ m|^([a-zA-Z]+)\://|) {
|
||||
# relative location (no http:// prefix)
|
||||
$res->{location} = "$source/$res->{location}";
|
||||
}
|
||||
} else {
|
||||
my $arch = $res->{architecture} || 'i386';
|
||||
$template = "$res->{os}-$res->{package}_$res->{version}_$arch.tar.gz";
|
||||
$template =~ s/$res->{os}-$res->{os}-/$res->{os}-/;
|
||||
$res->{location} = "$source/$res->{section}/$template";
|
||||
}
|
||||
$res->{source} = $source;
|
||||
$res->{template} = $template;
|
||||
$list->{ $res->{section} }->{$template} = $res;
|
||||
$list->{'all'}->{$template} = $res;
|
||||
} else {
|
||||
my $msg = "found incomplete appliance records\n";
|
||||
$update ? die $msg : warn $msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub read_aplinfo {
|
||||
my ($filename, $list, $source, $update) = @_;
|
||||
|
||||
my $fh = IO::File->new("<$filename") ||
|
||||
die "unable to open file '$filename' - $!\n";
|
||||
my $fh = IO::File->new("<$filename")
|
||||
|| die "unable to open file '$filename' - $!\n";
|
||||
|
||||
eval { read_aplinfo_from_fh($fh, $list, $source, $update); };
|
||||
my $err = $@;
|
||||
|
|
@ -127,15 +132,15 @@ sub url_get {
|
|||
|
||||
my $req = HTTP::Request->new(GET => $url);
|
||||
|
||||
logmsg ($logfh, "start download $url");
|
||||
logmsg($logfh, "start download $url");
|
||||
my $res = $ua->request($req, $file);
|
||||
|
||||
if ($res->is_success) {
|
||||
logmsg ($logfh, "download finished: " . $res->status_line);
|
||||
return 0;
|
||||
logmsg($logfh, "download finished: " . $res->status_line);
|
||||
return 0;
|
||||
}
|
||||
|
||||
logmsg ($logfh, "download failed: " . $res->status_line);
|
||||
logmsg($logfh, "download failed: " . $res->status_line);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -153,35 +158,33 @@ sub download_aplinfo {
|
|||
|
||||
eval {
|
||||
|
||||
if (url_get($ua, $aplsigurl, $sigfn, $logfd) != 0) {
|
||||
die "update failed - no signature file '$sigfn'\n";
|
||||
}
|
||||
if (url_get($ua, $aplsigurl, $sigfn, $logfd) != 0) {
|
||||
die "update failed - no signature file '$sigfn'\n";
|
||||
}
|
||||
|
||||
if (url_get($ua, $aplsrcurl, $tmpgz, $logfd) != 0) {
|
||||
die "update failed - no data file '$aplsrcurl'\n";
|
||||
}
|
||||
if (url_get($ua, $aplsrcurl, $tmpgz, $logfd) != 0) {
|
||||
die "update failed - no data file '$aplsrcurl'\n";
|
||||
}
|
||||
|
||||
eval { run_command(["gunzip", "-f", $tmpgz]) };
|
||||
die "update failed: unable to unpack '$tmpgz'\n" if $@;
|
||||
eval { run_command(["gunzip", "-f", $tmpgz]) };
|
||||
die "update failed: unable to unpack '$tmpgz'\n" if $@;
|
||||
|
||||
# verify signature
|
||||
my $trustedkeyring = "/usr/share/doc/pve-manager/trustedkeys.gpg";
|
||||
my $cmd = "/usr/bin/gpgv -q --keyring $trustedkeyring $sigfn $tmp";
|
||||
# verify signature
|
||||
my $trustedkeyring = "/usr/share/doc/pve-manager/trustedkeys.gpg";
|
||||
my $cmd = "/usr/bin/gpgv -q --keyring $trustedkeyring $sigfn $tmp";
|
||||
|
||||
my $logfunc = sub { logmsg($logfd, "signature verification: $_[0]"); };
|
||||
eval {
|
||||
run_command($cmd, outfunc => $logfunc, errfunc => $logfunc);
|
||||
};
|
||||
die "unable to verify signature - $@\n" if $@;
|
||||
my $logfunc = sub { logmsg($logfd, "signature verification: $_[0]"); };
|
||||
eval { run_command($cmd, outfunc => $logfunc, errfunc => $logfunc); };
|
||||
die "unable to verify signature - $@\n" if $@;
|
||||
|
||||
# test syntax
|
||||
eval { read_aplinfo($tmp, {}, $aplinfo->{url}, 1) };
|
||||
die "update failed: $@" if $@;
|
||||
# test syntax
|
||||
eval { read_aplinfo($tmp, {}, $aplinfo->{url}, 1) };
|
||||
die "update failed: $@" if $@;
|
||||
|
||||
rename($tmp, "$APL_INFO_DIRECTORY/$host") or
|
||||
die "update failed: unable to store data: $!\n";
|
||||
rename($tmp, "$APL_INFO_DIRECTORY/$host")
|
||||
or die "update failed: unable to store data: $!\n";
|
||||
|
||||
logmsg($logfd, "update successful");
|
||||
logmsg($logfd, "update successful");
|
||||
};
|
||||
|
||||
my $err = $@;
|
||||
|
|
@ -195,16 +198,16 @@ sub download_aplinfo {
|
|||
|
||||
sub get_apl_sources {
|
||||
my $sources = [
|
||||
{
|
||||
host => "download.proxmox.com",
|
||||
url => "http://download.proxmox.com/images",
|
||||
file => 'aplinfo-pve-8.dat',
|
||||
},
|
||||
{
|
||||
host => "releases.turnkeylinux.org",
|
||||
url => "https://releases.turnkeylinux.org/pve",
|
||||
file => 'aplinfo.dat',
|
||||
},
|
||||
{
|
||||
host => "download.proxmox.com",
|
||||
url => "http://download.proxmox.com/images",
|
||||
file => 'aplinfo-pve-8.dat',
|
||||
},
|
||||
{
|
||||
host => "releases.turnkeylinux.org",
|
||||
url => "https://releases.turnkeylinux.org/pve",
|
||||
file => 'aplinfo.dat',
|
||||
},
|
||||
];
|
||||
|
||||
return $sources;
|
||||
|
|
@ -215,9 +218,9 @@ sub update {
|
|||
|
||||
my $logfile_size = -s $LOGFILE || 0;
|
||||
if ($logfile_size > 1024 * 256) {
|
||||
rename($LOGFILE, "$LOGFILE.0") or warn "failed to rotate log file $LOGFILE - $!\n";
|
||||
rename($LOGFILE, "$LOGFILE.0") or warn "failed to rotate log file $LOGFILE - $!\n";
|
||||
}
|
||||
my $logfd = IO::File->new (">>$LOGFILE");
|
||||
my $logfd = IO::File->new(">>$LOGFILE");
|
||||
logmsg($logfd, "starting update");
|
||||
|
||||
my $ua = LWP::UserAgent->new;
|
||||
|
|
@ -225,9 +228,9 @@ sub update {
|
|||
$ua->agent("PVE/$release");
|
||||
|
||||
if ($proxy) {
|
||||
$ua->proxy(['http', 'https'], $proxy);
|
||||
$ua->proxy(['http', 'https'], $proxy);
|
||||
} else {
|
||||
$ua->env_proxy;
|
||||
$ua->env_proxy;
|
||||
}
|
||||
|
||||
my $sources = get_apl_sources();
|
||||
|
|
@ -236,13 +239,11 @@ sub update {
|
|||
|
||||
my @dlerr = ();
|
||||
foreach my $info (@$sources) {
|
||||
eval {
|
||||
download_aplinfo($ua, $info, $logfd);
|
||||
};
|
||||
if (my $err = $@) {
|
||||
logmsg ($logfd, $err);
|
||||
push @dlerr, $info->{url};
|
||||
}
|
||||
eval { download_aplinfo($ua, $info, $logfd); };
|
||||
if (my $err = $@) {
|
||||
logmsg($logfd, $err);
|
||||
push @dlerr, $info->{url};
|
||||
}
|
||||
}
|
||||
|
||||
close($logfd);
|
||||
|
|
@ -254,15 +255,15 @@ sub update {
|
|||
|
||||
sub load_data {
|
||||
|
||||
my $sources = get_apl_sources();
|
||||
my $sources = get_apl_sources();
|
||||
|
||||
my $list = {};
|
||||
foreach my $info (@$sources) {
|
||||
eval {
|
||||
my $host = $info->{host};
|
||||
read_aplinfo("$APL_INFO_DIRECTORY/$host", $list, $info->{url});
|
||||
};
|
||||
warn $@ if $@;
|
||||
eval {
|
||||
my $host = $info->{host};
|
||||
read_aplinfo("$APL_INFO_DIRECTORY/$host", $list, $info->{url});
|
||||
};
|
||||
warn $@ if $@;
|
||||
}
|
||||
|
||||
return $list;
|
||||
|
|
|
|||
|
|
@ -4,144 +4,148 @@ use warnings;
|
|||
use strict;
|
||||
|
||||
sub compute_alg1 {
|
||||
my ($vmstatus, $goal, $maxchange, $debug) = @_;
|
||||
my ($vmstatus, $goal, $maxchange, $debug) = @_;
|
||||
|
||||
my $log = sub { print @_ if $debug; };
|
||||
|
||||
my $change_func = sub {
|
||||
my ($res, $idlist, $bytes) = @_;
|
||||
my ($res, $idlist, $bytes) = @_;
|
||||
|
||||
my $rest = $bytes;
|
||||
my $repeat = 1;
|
||||
my $done_hash = {};
|
||||
my $progress = 1;
|
||||
my $rest = $bytes;
|
||||
my $repeat = 1;
|
||||
my $done_hash = {};
|
||||
my $progress = 1;
|
||||
|
||||
while ($rest && $repeat && $progress) {
|
||||
$repeat = 0;
|
||||
$progress = 0;
|
||||
while ($rest && $repeat && $progress) {
|
||||
$repeat = 0;
|
||||
$progress = 0;
|
||||
|
||||
my $shares_total = 0;
|
||||
my $alloc_old = 0;
|
||||
my $shares_total = 0;
|
||||
my $alloc_old = 0;
|
||||
|
||||
foreach my $vmid (@$idlist) {
|
||||
next if defined($done_hash->{$vmid});
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $balloon = defined($res->{$vmid}) ? $res->{$vmid} : $d->{balloon};
|
||||
$alloc_old += $balloon - $d->{balloon_min};
|
||||
$shares_total += $d->{shares} || 1000;
|
||||
}
|
||||
foreach my $vmid (@$idlist) {
|
||||
next if defined($done_hash->{$vmid});
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $balloon = defined($res->{$vmid}) ? $res->{$vmid} : $d->{balloon};
|
||||
$alloc_old += $balloon - $d->{balloon_min};
|
||||
$shares_total += $d->{shares} || 1000;
|
||||
}
|
||||
|
||||
my $changes = 0;
|
||||
my $changes = 0;
|
||||
|
||||
my $alloc_new = $alloc_old + $rest;
|
||||
my $alloc_new = $alloc_old + $rest;
|
||||
|
||||
&$log("shares_total: $shares_total $alloc_new\n");
|
||||
&$log("shares_total: $shares_total $alloc_new\n");
|
||||
|
||||
foreach my $vmid (@$idlist) {
|
||||
next if defined($done_hash->{$vmid});
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $shares = $d->{shares} || 1000;
|
||||
my $desired = $d->{balloon_min} + int(($alloc_new/$shares_total)*$shares);
|
||||
foreach my $vmid (@$idlist) {
|
||||
next if defined($done_hash->{$vmid});
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $shares = $d->{shares} || 1000;
|
||||
my $desired = $d->{balloon_min} + int(($alloc_new / $shares_total) * $shares);
|
||||
|
||||
if ($desired > $d->{maxmem}) {
|
||||
$desired = $d->{maxmem};
|
||||
$repeat = 1;
|
||||
} elsif ($desired < $d->{balloon_min}) {
|
||||
$desired = $d->{balloon_min};
|
||||
$repeat = 1;
|
||||
}
|
||||
if ($desired > $d->{maxmem}) {
|
||||
$desired = $d->{maxmem};
|
||||
$repeat = 1;
|
||||
} elsif ($desired < $d->{balloon_min}) {
|
||||
$desired = $d->{balloon_min};
|
||||
$repeat = 1;
|
||||
}
|
||||
|
||||
my ($new, $balloon);
|
||||
if (($bytes > 0) && ($desired - $d->{balloon}) > 0) { # grow
|
||||
$new = $d->{balloon} + $maxchange;
|
||||
$balloon = $new > $desired ? $desired : $new;
|
||||
} elsif (($desired - $d->{balloon}) < 0) { # shrink
|
||||
$new = $d->{balloon} - $maxchange;
|
||||
$balloon = $new > $desired ? $new : $desired;
|
||||
} else {
|
||||
$done_hash->{$vmid} = 1;
|
||||
next;
|
||||
}
|
||||
my ($new, $balloon);
|
||||
if (($bytes > 0) && ($desired - $d->{balloon}) > 0) { # grow
|
||||
$new = $d->{balloon} + $maxchange;
|
||||
$balloon = $new > $desired ? $desired : $new;
|
||||
} elsif (($desired - $d->{balloon}) < 0) { # shrink
|
||||
$new = $d->{balloon} - $maxchange;
|
||||
$balloon = $new > $desired ? $new : $desired;
|
||||
} else {
|
||||
$done_hash->{$vmid} = 1;
|
||||
next;
|
||||
}
|
||||
|
||||
my $diff = $balloon - $d->{balloon};
|
||||
if ($diff != 0) {
|
||||
my $oldballoon = defined($res->{$vmid}) ? $res->{$vmid} : $d->{balloon};
|
||||
$res->{$vmid} = $balloon;
|
||||
my $change = $balloon - $oldballoon;
|
||||
if ($change != 0) {
|
||||
$changes += $change;
|
||||
my $absdiff = $diff > 0 ? $diff : -$diff;
|
||||
$progress += $absdiff;
|
||||
$repeat = 1;
|
||||
}
|
||||
&$log("change request for $vmid ($balloon, $diff, $desired, $new, $changes, $progress)\n");
|
||||
}
|
||||
}
|
||||
my $diff = $balloon - $d->{balloon};
|
||||
if ($diff != 0) {
|
||||
my $oldballoon = defined($res->{$vmid}) ? $res->{$vmid} : $d->{balloon};
|
||||
$res->{$vmid} = $balloon;
|
||||
my $change = $balloon - $oldballoon;
|
||||
if ($change != 0) {
|
||||
$changes += $change;
|
||||
my $absdiff = $diff > 0 ? $diff : -$diff;
|
||||
$progress += $absdiff;
|
||||
$repeat = 1;
|
||||
}
|
||||
&$log(
|
||||
"change request for $vmid ($balloon, $diff, $desired, $new, $changes, $progress)\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$rest -= $changes;
|
||||
}
|
||||
$rest -= $changes;
|
||||
}
|
||||
|
||||
return $rest;
|
||||
return $rest;
|
||||
};
|
||||
|
||||
|
||||
my $idlist = []; # list of VMs with working balloon river
|
||||
my $idlist1 = []; # list of VMs with memory pressure
|
||||
my $idlist2 = []; # list of VMs with enough free memory
|
||||
|
||||
foreach my $vmid (keys %$vmstatus) {
|
||||
my $d = $vmstatus->{$vmid};
|
||||
next if !$d->{balloon}; # skip if balloon driver not running
|
||||
next if !$d->{balloon_min}; # skip if balloon value not set in config
|
||||
next if $d->{lock} && $d->{lock} eq 'migrate';
|
||||
next if defined($d->{shares}) &&
|
||||
($d->{shares} == 0); # skip if shares set to zero
|
||||
my $d = $vmstatus->{$vmid};
|
||||
next if !$d->{balloon}; # skip if balloon driver not running
|
||||
next if !$d->{balloon_min}; # skip if balloon value not set in config
|
||||
next if $d->{lock} && $d->{lock} eq 'migrate';
|
||||
next
|
||||
if defined($d->{shares})
|
||||
&& ($d->{shares} == 0); # skip if shares set to zero
|
||||
|
||||
push @$idlist, $vmid;
|
||||
push @$idlist, $vmid;
|
||||
|
||||
if ($d->{freemem} &&
|
||||
($d->{freemem} > $d->{balloon_min}*0.25) &&
|
||||
($d->{balloon} >= $d->{balloon_min})) {
|
||||
push @$idlist2, $vmid;
|
||||
&$log("idlist2 $vmid $d->{balloon}, $d->{balloon_min}, $d->{freemem}\n");
|
||||
} else {
|
||||
push @$idlist1, $vmid;
|
||||
&$log("idlist1 $vmid $d->{balloon}, $d->{balloon_min}\n");
|
||||
}
|
||||
if (
|
||||
$d->{freemem}
|
||||
&& ($d->{freemem} > $d->{balloon_min} * 0.25)
|
||||
&& ($d->{balloon} >= $d->{balloon_min})
|
||||
) {
|
||||
push @$idlist2, $vmid;
|
||||
&$log("idlist2 $vmid $d->{balloon}, $d->{balloon_min}, $d->{freemem}\n");
|
||||
} else {
|
||||
push @$idlist1, $vmid;
|
||||
&$log("idlist1 $vmid $d->{balloon}, $d->{balloon_min}\n");
|
||||
}
|
||||
}
|
||||
|
||||
my $res = {};
|
||||
|
||||
if ($goal > 10*1024*1024) {
|
||||
&$log("grow request start $goal\n");
|
||||
# priorize VMs with memory pressure
|
||||
my $rest = &$change_func($res, $idlist1, $goal);
|
||||
if ($rest >= $goal) { # no progress ==> consider all VMs
|
||||
&$log("grow request loop $rest\n");
|
||||
$rest = &$change_func($res, $idlist, $rest);
|
||||
}
|
||||
&$log("grow request end $rest\n");
|
||||
if ($goal > 10 * 1024 * 1024) {
|
||||
&$log("grow request start $goal\n");
|
||||
# priorize VMs with memory pressure
|
||||
my $rest = &$change_func($res, $idlist1, $goal);
|
||||
if ($rest >= $goal) { # no progress ==> consider all VMs
|
||||
&$log("grow request loop $rest\n");
|
||||
$rest = &$change_func($res, $idlist, $rest);
|
||||
}
|
||||
&$log("grow request end $rest\n");
|
||||
|
||||
} elsif ($goal < -10*1024*1024) {
|
||||
&$log("shrink request $goal\n");
|
||||
# priorize VMs with enough free memory
|
||||
my $rest = &$change_func($res, $idlist2, $goal);
|
||||
if ($rest <= $goal) { # no progress ==> consider all VMs
|
||||
&$log("shrink request loop $rest\n");
|
||||
$rest = &$change_func($res, $idlist, $rest);
|
||||
}
|
||||
&$log("shrink request end $rest\n");
|
||||
} else {
|
||||
&$log("do nothing\n");
|
||||
# do nothing - requested change to small
|
||||
} elsif ($goal < -10 * 1024 * 1024) {
|
||||
&$log("shrink request $goal\n");
|
||||
# priorize VMs with enough free memory
|
||||
my $rest = &$change_func($res, $idlist2, $goal);
|
||||
if ($rest <= $goal) { # no progress ==> consider all VMs
|
||||
&$log("shrink request loop $rest\n");
|
||||
$rest = &$change_func($res, $idlist, $rest);
|
||||
}
|
||||
&$log("shrink request end $rest\n");
|
||||
} else {
|
||||
&$log("do nothing\n");
|
||||
# do nothing - requested change to small
|
||||
}
|
||||
|
||||
foreach my $vmid (@$idlist) {
|
||||
next if !$res->{$vmid};
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $diff = int($res->{$vmid} - $d->{balloon});
|
||||
my $absdiff = $diff < 0 ? -$diff : $diff;
|
||||
&$log("BALLOON $vmid to $res->{$vmid} ($diff)\n");
|
||||
next if !$res->{$vmid};
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $diff = int($res->{$vmid} - $d->{balloon});
|
||||
my $absdiff = $diff < 0 ? -$diff : $diff;
|
||||
&$log("BALLOON $vmid to $res->{$vmid} ($diff)\n");
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
|
|
|||
1662
PVE/CLI/pve7to8.pm
1662
PVE/CLI/pve7to8.pm
File diff suppressed because it is too large
Load diff
211
PVE/CLI/pveam.pm
211
PVE/CLI/pveam.pm
|
|
@ -24,181 +24,196 @@ sub setup_environment {
|
|||
PVE::RPCEnvironment->setup_default_cli_env();
|
||||
}
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'update',
|
||||
path => 'update',
|
||||
method => 'PUT',
|
||||
description => "Update Container Template Database.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
additionalProperties => 0,
|
||||
properties => {},
|
||||
},
|
||||
returns => { type => 'null'},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
|
||||
my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
|
||||
my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
|
||||
|
||||
my $res = PVE::APLInfo::update($dccfg->{http_proxy});
|
||||
my $res = PVE::APLInfo::update($dccfg->{http_proxy});
|
||||
|
||||
if ($res) {
|
||||
print STDOUT "update successful\n"
|
||||
} else {
|
||||
print STDERR "update failed - see /var/log/pveam.log for details\n"
|
||||
}
|
||||
if ($res) {
|
||||
print STDOUT "update successful\n";
|
||||
} else {
|
||||
print STDERR "update failed - see /var/log/pveam.log for details\n";
|
||||
}
|
||||
|
||||
return undef;
|
||||
return undef;
|
||||
|
||||
}});
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'available',
|
||||
path => 'available',
|
||||
method => 'GET',
|
||||
description => "List available templates.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
section => {
|
||||
type => 'string',
|
||||
description => "Restrict list to specified section.",
|
||||
enum => ['system', 'mail', 'turnkeylinux'],
|
||||
optional => 1,
|
||||
},
|
||||
}
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
section => {
|
||||
type => 'string',
|
||||
description => "Restrict list to specified section.",
|
||||
enum => ['system', 'mail', 'turnkeylinux'],
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null'},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $list = PVE::APLInfo::load_data();
|
||||
my $list = PVE::APLInfo::load_data();
|
||||
|
||||
foreach my $section (sort keys %$list) {
|
||||
next if $section eq 'all';
|
||||
next if $param->{section} && $section ne $param->{section};
|
||||
foreach my $template (sort keys %{$list->{$section}}) {
|
||||
print sprintf("%-15s %s\n", $section, $template);
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
foreach my $section (sort keys %$list) {
|
||||
next if $section eq 'all';
|
||||
next if $param->{section} && $section ne $param->{section};
|
||||
foreach my $template (sort keys %{ $list->{$section} }) {
|
||||
print sprintf("%-15s %s\n", $section, $template);
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
|
||||
}});
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'index',
|
||||
path => 'index',
|
||||
method => 'GET',
|
||||
description => "Get list of all templates on storage",
|
||||
permissions => {
|
||||
description => "Show all users the template which have permission on that storage.",
|
||||
check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
|
||||
description => "Show all users the template which have permission on that storage.",
|
||||
check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
|
||||
},
|
||||
proxyto => 'node',
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
storage => get_standard_option('pve-storage-id', {
|
||||
description => "Only list templates on specified storage",
|
||||
completion => \&PVE::Storage::complete_storage_enabled,
|
||||
}),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
storage => get_standard_option(
|
||||
'pve-storage-id',
|
||||
{
|
||||
description => "Only list templates on specified storage",
|
||||
completion => \&PVE::Storage::complete_storage_enabled,
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
returns => {
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
type => 'array',
|
||||
items => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $storeid = $param->{storage};
|
||||
my $storeid = $param->{storage};
|
||||
|
||||
my $cfg = PVE::Storage::config();
|
||||
my $cfg = PVE::Storage::config();
|
||||
|
||||
PVE::Storage::storage_check_enabled($cfg, $storeid);
|
||||
PVE::Storage::storage_check_enabled($cfg, $storeid);
|
||||
|
||||
die "Storage does not support templates!\n" if !$cfg->{ids}->{$storeid}->{content}->{vztmpl};
|
||||
die "Storage does not support templates!\n"
|
||||
if !$cfg->{ids}->{$storeid}->{content}->{vztmpl};
|
||||
|
||||
my $vollist = PVE::Storage::volume_list($cfg, $storeid, undef, 'vztmpl');
|
||||
my $vollist = PVE::Storage::volume_list($cfg, $storeid, undef, 'vztmpl');
|
||||
|
||||
my $res = [];
|
||||
foreach my $item (@$vollist) {
|
||||
eval { PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $item->{volid}); };
|
||||
next if $@;
|
||||
push @$res, $item;
|
||||
}
|
||||
my $res = [];
|
||||
foreach my $item (@$vollist) {
|
||||
eval {
|
||||
PVE::Storage::check_volume_access(
|
||||
$rpcenv, $authuser, $cfg, undef, $item->{volid},
|
||||
);
|
||||
};
|
||||
next if $@;
|
||||
push @$res, $item;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}});
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'remove',
|
||||
path => 'remove',
|
||||
method => 'DELETE',
|
||||
description => "Remove a template.",
|
||||
permissions => {
|
||||
description => "Only user who can create templates can remove them.",
|
||||
check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
|
||||
description => "Only user who can create templates can remove them.",
|
||||
check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
|
||||
},
|
||||
proxyto => 'node',
|
||||
protected => 1,
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
template_path => {
|
||||
type => 'string',
|
||||
description => "The template to remove.",
|
||||
maxLength => 255,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
template_path => {
|
||||
type => 'string',
|
||||
description => "The template to remove.",
|
||||
maxLength => 255,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
|
||||
my $authuser = $rpcenv->get_user();
|
||||
my $authuser = $rpcenv->get_user();
|
||||
|
||||
my $template = $param->{template_path};
|
||||
my $template = $param->{template_path};
|
||||
|
||||
my $cfg = PVE::Storage::config();
|
||||
my $cfg = PVE::Storage::config();
|
||||
|
||||
PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $template, 'vztmpl');
|
||||
PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $template, 'vztmpl');
|
||||
|
||||
my $abs_path = PVE::Storage::abs_filesystem_path($cfg, $template);
|
||||
my $abs_path = PVE::Storage::abs_filesystem_path($cfg, $template);
|
||||
|
||||
unlink $abs_path;
|
||||
|
||||
return undef;
|
||||
}});
|
||||
unlink $abs_path;
|
||||
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
my $print_list = sub {
|
||||
my ($list) = @_;
|
||||
|
||||
printf "%-60s %-6s\n",
|
||||
qw(NAME SIZE);
|
||||
printf "%-60s %-6s\n", qw(NAME SIZE);
|
||||
|
||||
foreach my $rec (@$list) {
|
||||
printf "%-60s %-4.2fMB\n", $rec->{volid}, $rec->{size}/(1024*1024);
|
||||
printf "%-60s %-4.2fMB\n", $rec->{volid}, $rec->{size} / (1024 * 1024);
|
||||
}
|
||||
};
|
||||
|
||||
our $cmddef = {
|
||||
update => [ __PACKAGE__, 'update', []],
|
||||
download => [ 'PVE::API2::Nodes::Nodeinfo', 'apl_download', [ 'storage', 'template'], { node => $nodename } ],
|
||||
available => [ __PACKAGE__, 'available', []],
|
||||
list => [ __PACKAGE__, 'index', [ 'storage' ], { node => $nodename }, $print_list ],
|
||||
remove => [ __PACKAGE__, 'remove', [ 'template_path' ], { node => $nodename }]
|
||||
update => [__PACKAGE__, 'update', []],
|
||||
download => [
|
||||
'PVE::API2::Nodes::Nodeinfo',
|
||||
'apl_download',
|
||||
['storage', 'template'],
|
||||
{ node => $nodename },
|
||||
],
|
||||
available => [__PACKAGE__, 'available', []],
|
||||
list => [__PACKAGE__, 'index', ['storage'], { node => $nodename }, $print_list],
|
||||
remove => [__PACKAGE__, 'remove', ['template_path'], { node => $nodename }],
|
||||
};
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -45,69 +45,70 @@ sub setup_environment {
|
|||
PVE::RPCEnvironment->setup_default_cli_env();
|
||||
}
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'purge',
|
||||
path => 'purge',
|
||||
method => 'POST',
|
||||
description => "Destroy ceph related data and configuration files.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
logs => {
|
||||
description => 'Additionally purge Ceph logs, /var/log/ceph.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
crash => {
|
||||
description => 'Additionally purge Ceph crash logs, /var/lib/ceph/crash.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
logs => {
|
||||
description => 'Additionally purge Ceph logs, /var/log/ceph.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
crash => {
|
||||
description => 'Additionally purge Ceph crash logs, /var/lib/ceph/crash.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $message;
|
||||
my $pools = [];
|
||||
my $monstat = {};
|
||||
my $mdsstat = {};
|
||||
my $osdstat = [];
|
||||
my $message;
|
||||
my $pools = [];
|
||||
my $monstat = {};
|
||||
my $mdsstat = {};
|
||||
my $osdstat = [];
|
||||
|
||||
eval {
|
||||
my $rados = PVE::RADOS->new();
|
||||
$pools = PVE::Ceph::Tools::ls_pools(undef, $rados);
|
||||
$monstat = PVE::Ceph::Services::get_services_info('mon', undef, $rados);
|
||||
$mdsstat = PVE::Ceph::Services::get_services_info('mds', undef, $rados);
|
||||
$osdstat = $rados->mon_command({ prefix => 'osd metadata' });
|
||||
};
|
||||
warn "Error gathering ceph info, already purged? Message: $@" if $@;
|
||||
eval {
|
||||
my $rados = PVE::RADOS->new();
|
||||
$pools = PVE::Ceph::Tools::ls_pools(undef, $rados);
|
||||
$monstat = PVE::Ceph::Services::get_services_info('mon', undef, $rados);
|
||||
$mdsstat = PVE::Ceph::Services::get_services_info('mds', undef, $rados);
|
||||
$osdstat = $rados->mon_command({ prefix => 'osd metadata' });
|
||||
};
|
||||
warn "Error gathering ceph info, already purged? Message: $@" if $@;
|
||||
|
||||
my $osd = grep { $_->{hostname} eq $nodename } @$osdstat;
|
||||
my $mds = grep { $mdsstat->{$_}->{host} eq $nodename } keys %$mdsstat;
|
||||
my $mon = grep { $monstat->{$_}->{host} eq $nodename } keys %$monstat;
|
||||
my $osd = grep { $_->{hostname} eq $nodename } @$osdstat;
|
||||
my $mds = grep { $mdsstat->{$_}->{host} eq $nodename } keys %$mdsstat;
|
||||
my $mon = grep { $monstat->{$_}->{host} eq $nodename } keys %$monstat;
|
||||
|
||||
# no pools = no data
|
||||
$message .= "- remove pools, this will !!DESTROY DATA!!\n" if @$pools;
|
||||
$message .= "- remove active OSD on $nodename\n" if $osd;
|
||||
$message .= "- remove active MDS on $nodename\n" if $mds;
|
||||
$message .= "- remove other MONs, $nodename is not the last MON\n"
|
||||
if scalar(keys %$monstat) > 1 && $mon;
|
||||
# no pools = no data
|
||||
$message .= "- remove pools, this will !!DESTROY DATA!!\n" if @$pools;
|
||||
$message .= "- remove active OSD on $nodename\n" if $osd;
|
||||
$message .= "- remove active MDS on $nodename\n" if $mds;
|
||||
$message .= "- remove other MONs, $nodename is not the last MON\n"
|
||||
if scalar(keys %$monstat) > 1 && $mon;
|
||||
|
||||
# display all steps at once
|
||||
die "Unable to purge Ceph!\n\nTo continue:\n$message" if $message;
|
||||
# display all steps at once
|
||||
die "Unable to purge Ceph!\n\nTo continue:\n$message" if $message;
|
||||
|
||||
my $services = PVE::Ceph::Services::get_local_services();
|
||||
$services->{mon} = $monstat if $mon;
|
||||
$services->{crash}->{$nodename} = { direxists => 1 } if $param->{crash};
|
||||
$services->{logs}->{$nodename} = { direxists => 1 } if $param->{logs};
|
||||
my $services = PVE::Ceph::Services::get_local_services();
|
||||
$services->{mon} = $monstat if $mon;
|
||||
$services->{crash}->{$nodename} = { direxists => 1 } if $param->{crash};
|
||||
$services->{logs}->{$nodename} = { direxists => 1 } if $param->{logs};
|
||||
|
||||
PVE::Ceph::Tools::purge_all_ceph_services($services);
|
||||
PVE::Ceph::Tools::purge_all_ceph_files($services);
|
||||
PVE::Ceph::Tools::purge_all_ceph_services($services);
|
||||
PVE::Ceph::Tools::purge_all_ceph_files($services);
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
my sub has_valid_subscription {
|
||||
my $info = eval { Proxmox::RS::Subscription::read_subscription('/etc/subscription') } // {};
|
||||
|
|
@ -118,155 +119,168 @@ my sub has_valid_subscription {
|
|||
my $available_ceph_release_codenames = PVE::Ceph::Releases::get_available_ceph_release_codenames(1);
|
||||
my $default_ceph_version = PVE::Ceph::Releases::get_default_ceph_release_codename();
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'install',
|
||||
path => 'install',
|
||||
method => 'POST',
|
||||
description => "Install ceph related packages.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
version => {
|
||||
type => 'string',
|
||||
enum => $available_ceph_release_codenames,
|
||||
default => $default_ceph_version,
|
||||
description => "Ceph version to install.",
|
||||
optional => 1,
|
||||
},
|
||||
repository => {
|
||||
type => 'string',
|
||||
enum => ['enterprise', 'no-subscription', 'test'],
|
||||
default => 'enterprise',
|
||||
description => "Ceph repository to use.",
|
||||
optional => 1,
|
||||
},
|
||||
'allow-experimental' => {
|
||||
type => 'boolean',
|
||||
default => 0,
|
||||
optional => 1,
|
||||
description => "Allow experimental versions. Use with care!",
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
version => {
|
||||
type => 'string',
|
||||
enum => $available_ceph_release_codenames,
|
||||
default => $default_ceph_version,
|
||||
description => "Ceph version to install.",
|
||||
optional => 1,
|
||||
},
|
||||
repository => {
|
||||
type => 'string',
|
||||
enum => ['enterprise', 'no-subscription', 'test'],
|
||||
default => 'enterprise',
|
||||
description => "Ceph repository to use.",
|
||||
optional => 1,
|
||||
},
|
||||
'allow-experimental' => {
|
||||
type => 'boolean',
|
||||
default => 0,
|
||||
optional => 1,
|
||||
description => "Allow experimental versions. Use with care!",
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $cephver = $param->{version} || $default_ceph_version;
|
||||
my $cephver = $param->{version} || $default_ceph_version;
|
||||
|
||||
my $repo = $param->{'repository'} // 'enterprise';
|
||||
my $enterprise_repo = $repo eq 'enterprise';
|
||||
my $cdn = $enterprise_repo ? 'https://enterprise.proxmox.com' : 'http://download.proxmox.com';
|
||||
my $repo = $param->{'repository'} // 'enterprise';
|
||||
my $enterprise_repo = $repo eq 'enterprise';
|
||||
my $cdn =
|
||||
$enterprise_repo ? 'https://enterprise.proxmox.com' : 'http://download.proxmox.com';
|
||||
|
||||
if (has_valid_subscription()) {
|
||||
warn "\nNOTE: The node has an active subscription but a non-production Ceph repository selected.\n\n"
|
||||
if !$enterprise_repo;
|
||||
} elsif ($enterprise_repo) {
|
||||
warn "\nWARN: Enterprise repository selected, but no active subscription!\n\n";
|
||||
} elsif ($repo eq 'no-subscription') {
|
||||
warn "\nHINT: The no-subscription repository is not the best choice for production setups.\n"
|
||||
."Proxmox recommends using the enterprise repository with a valid subscription.\n";
|
||||
} else {
|
||||
warn "\nWARN: The test repository should only be used for test setups or after consulting"
|
||||
." the official Proxmox support!\n\n"
|
||||
}
|
||||
if (has_valid_subscription()) {
|
||||
warn
|
||||
"\nNOTE: The node has an active subscription but a non-production Ceph repository selected.\n\n"
|
||||
if !$enterprise_repo;
|
||||
} elsif ($enterprise_repo) {
|
||||
warn "\nWARN: Enterprise repository selected, but no active subscription!\n\n";
|
||||
} elsif ($repo eq 'no-subscription') {
|
||||
warn
|
||||
"\nHINT: The no-subscription repository is not the best choice for production setups.\n"
|
||||
. "Proxmox recommends using the enterprise repository with a valid subscription.\n";
|
||||
} else {
|
||||
warn
|
||||
"\nWARN: The test repository should only be used for test setups or after consulting"
|
||||
. " the official Proxmox support!\n\n";
|
||||
}
|
||||
|
||||
my $available_ceph_releases = PVE::Ceph::Releases::get_all_available_ceph_releases();
|
||||
die "unsupported ceph version: $cephver" if !exists($available_ceph_releases->{$cephver});
|
||||
my $available_ceph_releases = PVE::Ceph::Releases::get_all_available_ceph_releases();
|
||||
die "unsupported ceph version: $cephver"
|
||||
if !exists($available_ceph_releases->{$cephver});
|
||||
|
||||
my $repolist = "deb ${cdn}/debian/ceph-${cephver} bookworm $repo\n";
|
||||
my $repolist = "deb ${cdn}/debian/ceph-${cephver} bookworm $repo\n";
|
||||
|
||||
my $rendered_release = $available_ceph_releases->{$cephver}->{release} . ' ' . ucfirst($cephver);
|
||||
if (-t STDOUT && !$param->{version}) {
|
||||
print "This will install Ceph ${rendered_release} - continue (y/N)? ";
|
||||
my $rendered_release =
|
||||
$available_ceph_releases->{$cephver}->{release} . ' ' . ucfirst($cephver);
|
||||
if (-t STDOUT && !$param->{version}) {
|
||||
print "This will install Ceph ${rendered_release} - continue (y/N)? ";
|
||||
|
||||
my $answer = <STDIN>;
|
||||
my $continue = defined($answer) && $answer =~ m/^\s*y(?:es)?\s*$/i;
|
||||
my $answer = <STDIN>;
|
||||
my $continue = defined($answer) && $answer =~ m/^\s*y(?:es)?\s*$/i;
|
||||
|
||||
die "Aborting installation as requested\n" if !$continue;
|
||||
}
|
||||
die "Aborting installation as requested\n" if !$continue;
|
||||
}
|
||||
|
||||
PVE::Tools::file_set_contents("/etc/apt/sources.list.d/ceph.list", $repolist);
|
||||
PVE::Tools::file_set_contents("/etc/apt/sources.list.d/ceph.list", $repolist);
|
||||
|
||||
if ($available_ceph_releases->{$cephver}->{unsupported}) {
|
||||
if ($param->{'allow-experimental'}) {
|
||||
warn "NOTE: installing experimental/tech-preview Ceph release ${rendered_release}!\n";
|
||||
} elsif (-t STDOUT) {
|
||||
print "Ceph ${rendered_release} is currently considered a technology preview for Proxmox VE - continue (y/N)? ";
|
||||
my $answer = <STDIN>;
|
||||
my $continue = defined($answer) && $answer =~ m/^\s*y(?:es)?\s*$/i;
|
||||
if ($available_ceph_releases->{$cephver}->{unsupported}) {
|
||||
if ($param->{'allow-experimental'}) {
|
||||
warn
|
||||
"NOTE: installing experimental/tech-preview Ceph release ${rendered_release}!\n";
|
||||
} elsif (-t STDOUT) {
|
||||
print
|
||||
"Ceph ${rendered_release} is currently considered a technology preview for Proxmox VE - continue (y/N)? ";
|
||||
my $answer = <STDIN>;
|
||||
my $continue = defined($answer) && $answer =~ m/^\s*y(?:es)?\s*$/i;
|
||||
|
||||
die "Aborting installation as requested\n" if !$continue;
|
||||
} else {
|
||||
die "refusing to install tech-preview Ceph release ${rendered_release} without 'allow-experimental' parameter!\n";
|
||||
}
|
||||
}
|
||||
die "Aborting installation as requested\n" if !$continue;
|
||||
} else {
|
||||
die
|
||||
"refusing to install tech-preview Ceph release ${rendered_release} without 'allow-experimental' parameter!\n";
|
||||
}
|
||||
}
|
||||
|
||||
local $ENV{DEBIAN_FRONTEND} = 'noninteractive';
|
||||
print "update available package list\n";
|
||||
eval {
|
||||
run_command(
|
||||
['apt-get', '-q', 'update'],
|
||||
outfunc => sub {},
|
||||
errfunc => sub { print STDERR "$_[0]\n" },
|
||||
)
|
||||
};
|
||||
local $ENV{DEBIAN_FRONTEND} = 'noninteractive';
|
||||
print "update available package list\n";
|
||||
eval {
|
||||
run_command(
|
||||
['apt-get', '-q', 'update'],
|
||||
outfunc => sub { },
|
||||
errfunc => sub { print STDERR "$_[0]\n" },
|
||||
);
|
||||
};
|
||||
|
||||
my @apt_install = qw(apt-get --no-install-recommends -o Dpkg::Options::=--force-confnew install --);
|
||||
my @ceph_packages = qw(
|
||||
ceph
|
||||
ceph-common
|
||||
ceph-fuse
|
||||
ceph-mds
|
||||
ceph-volume
|
||||
gdisk
|
||||
nvme-cli
|
||||
);
|
||||
my @apt_install =
|
||||
qw(apt-get --no-install-recommends -o Dpkg::Options::=--force-confnew install --);
|
||||
my @ceph_packages = qw(
|
||||
ceph
|
||||
ceph-common
|
||||
ceph-fuse
|
||||
ceph-mds
|
||||
ceph-volume
|
||||
gdisk
|
||||
nvme-cli
|
||||
);
|
||||
|
||||
print "start installation\n";
|
||||
print "start installation\n";
|
||||
|
||||
# this flag helps to determine when apt is actually done installing (vs. partial extracting)
|
||||
my $install_flag_fn = PVE::Ceph::Tools::ceph_install_flag_file();
|
||||
open(my $install_flag, '>', $install_flag_fn) or die "could not create install flag - $!\n";
|
||||
close $install_flag;
|
||||
# this flag helps to determine when apt is actually done installing (vs. partial extracting)
|
||||
my $install_flag_fn = PVE::Ceph::Tools::ceph_install_flag_file();
|
||||
open(my $install_flag, '>', $install_flag_fn)
|
||||
or die "could not create install flag - $!\n";
|
||||
close $install_flag;
|
||||
|
||||
if (system(@apt_install, @ceph_packages) != 0) {
|
||||
unlink $install_flag_fn or warn "could not remove Ceph installation flag - $!";
|
||||
die "apt failed during ceph installation ($?)\n";
|
||||
}
|
||||
if (system(@apt_install, @ceph_packages) != 0) {
|
||||
unlink $install_flag_fn or warn "could not remove Ceph installation flag - $!";
|
||||
die "apt failed during ceph installation ($?)\n";
|
||||
}
|
||||
|
||||
print "\ninstalled Ceph ${rendered_release} successfully!\n";
|
||||
# done: drop flag file so that the PVE::Ceph::Tools check returns Ok now.
|
||||
unlink $install_flag_fn or warn "could not remove Ceph installation flag - $!";
|
||||
print "\ninstalled Ceph ${rendered_release} successfully!\n";
|
||||
# done: drop flag file so that the PVE::Ceph::Tools check returns Ok now.
|
||||
unlink $install_flag_fn or warn "could not remove Ceph installation flag - $!";
|
||||
|
||||
print "\nreloading API to load new Ceph RADOS library...\n";
|
||||
run_command([
|
||||
'systemctl', 'try-reload-or-restart', 'pvedaemon.service', 'pveproxy.service'
|
||||
]);
|
||||
print "\nreloading API to load new Ceph RADOS library...\n";
|
||||
run_command([
|
||||
'systemctl', 'try-reload-or-restart', 'pvedaemon.service', 'pveproxy.service',
|
||||
]);
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'status',
|
||||
path => 'status',
|
||||
method => 'GET',
|
||||
description => "Get Ceph Status.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
additionalProperties => 0,
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
|
||||
run_command(
|
||||
['ceph', '-s'],
|
||||
outfunc => sub { print "$_[0]\n" },
|
||||
errfunc => sub { print STDERR "$_[0]\n" },
|
||||
timeout => 15,
|
||||
);
|
||||
return undef;
|
||||
}});
|
||||
run_command(
|
||||
['ceph', '-s'],
|
||||
outfunc => sub { print "$_[0]\n" },
|
||||
errfunc => sub { print STDERR "$_[0]\n" },
|
||||
timeout => 15,
|
||||
);
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
my $get_storages = sub {
|
||||
my ($fs, $is_default) = @_;
|
||||
|
|
@ -276,159 +290,161 @@ my $get_storages = sub {
|
|||
my $storages = $cfg->{ids};
|
||||
my $res = {};
|
||||
foreach my $storeid (keys %$storages) {
|
||||
my $curr = $storages->{$storeid};
|
||||
next if $curr->{type} ne 'cephfs';
|
||||
my $cur_fs = $curr->{'fs-name'};
|
||||
$res->{$storeid} = $storages->{$storeid}
|
||||
if (!defined($cur_fs) && $is_default) || (defined($cur_fs) && $fs eq $cur_fs);
|
||||
my $curr = $storages->{$storeid};
|
||||
next if $curr->{type} ne 'cephfs';
|
||||
my $cur_fs = $curr->{'fs-name'};
|
||||
$res->{$storeid} = $storages->{$storeid}
|
||||
if (!defined($cur_fs) && $is_default) || (defined($cur_fs) && $fs eq $cur_fs);
|
||||
}
|
||||
|
||||
return $res;
|
||||
};
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'destroyfs',
|
||||
path => 'destroyfs',
|
||||
method => 'DELETE',
|
||||
description => "Destroy a Ceph filesystem",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
name => {
|
||||
description => "The ceph filesystem name.",
|
||||
type => 'string',
|
||||
},
|
||||
'remove-storages' => {
|
||||
description => "Remove all pveceph-managed storages configured for this fs.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
'remove-pools' => {
|
||||
description => "Remove data and metadata pools configured for this fs.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
name => {
|
||||
description => "The ceph filesystem name.",
|
||||
type => 'string',
|
||||
},
|
||||
'remove-storages' => {
|
||||
description => "Remove all pveceph-managed storages configured for this fs.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
'remove-pools' => {
|
||||
description => "Remove data and metadata pools configured for this fs.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'string' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
my $rpcenv = PVE::RPCEnvironment::get();
|
||||
my $user = $rpcenv->get_user();
|
||||
|
||||
my $fs_name = $param->{name};
|
||||
my $fs_name = $param->{name};
|
||||
|
||||
my $fs;
|
||||
my $fs_list = PVE::Ceph::Tools::ls_fs();
|
||||
for my $entry (@$fs_list) {
|
||||
next if $entry->{name} ne $fs_name;
|
||||
$fs = $entry;
|
||||
last;
|
||||
}
|
||||
die "no such cephfs '$fs_name'\n" if !$fs;
|
||||
my $fs;
|
||||
my $fs_list = PVE::Ceph::Tools::ls_fs();
|
||||
for my $entry (@$fs_list) {
|
||||
next if $entry->{name} ne $fs_name;
|
||||
$fs = $entry;
|
||||
last;
|
||||
}
|
||||
die "no such cephfs '$fs_name'\n" if !$fs;
|
||||
|
||||
my $worker = sub {
|
||||
my $rados = PVE::RADOS->new();
|
||||
my $worker = sub {
|
||||
my $rados = PVE::RADOS->new();
|
||||
|
||||
if ($param->{'remove-storages'}) {
|
||||
my $defaultfs;
|
||||
my $fs_dump = $rados->mon_command({ prefix => "fs dump" });
|
||||
for my $fs ($fs_dump->{filesystems}->@*) {
|
||||
next if $fs->{id} != $fs_dump->{default_fscid};
|
||||
$defaultfs = $fs->{mdsmap}->{fs_name};
|
||||
}
|
||||
warn "no default fs found, maybe not all relevant storages are removed\n"
|
||||
if !defined($defaultfs);
|
||||
if ($param->{'remove-storages'}) {
|
||||
my $defaultfs;
|
||||
my $fs_dump = $rados->mon_command({ prefix => "fs dump" });
|
||||
for my $fs ($fs_dump->{filesystems}->@*) {
|
||||
next if $fs->{id} != $fs_dump->{default_fscid};
|
||||
$defaultfs = $fs->{mdsmap}->{fs_name};
|
||||
}
|
||||
warn "no default fs found, maybe not all relevant storages are removed\n"
|
||||
if !defined($defaultfs);
|
||||
|
||||
my $storages = $get_storages->($fs_name, $fs_name eq ($defaultfs // ''));
|
||||
for my $storeid (keys %$storages) {
|
||||
my $store = $storages->{$storeid};
|
||||
if (!$store->{disable}) {
|
||||
die "storage '$storeid' is not disabled, make sure to disable ".
|
||||
"and unmount the storage first\n";
|
||||
}
|
||||
}
|
||||
my $storages = $get_storages->($fs_name, $fs_name eq ($defaultfs // ''));
|
||||
for my $storeid (keys %$storages) {
|
||||
my $store = $storages->{$storeid};
|
||||
if (!$store->{disable}) {
|
||||
die "storage '$storeid' is not disabled, make sure to disable "
|
||||
. "and unmount the storage first\n";
|
||||
}
|
||||
}
|
||||
|
||||
my $err;
|
||||
for my $storeid (keys %$storages) {
|
||||
# skip external clusters, not managed by pveceph
|
||||
next if $storages->{$storeid}->{monhost};
|
||||
eval { PVE::API2::Storage::Config->delete({storage => $storeid}) };
|
||||
if ($@) {
|
||||
warn "failed to remove storage '$storeid': $@\n";
|
||||
$err = 1;
|
||||
}
|
||||
}
|
||||
die "failed to remove (some) storages - check log and remove manually!\n"
|
||||
if $err;
|
||||
}
|
||||
my $err;
|
||||
for my $storeid (keys %$storages) {
|
||||
# skip external clusters, not managed by pveceph
|
||||
next if $storages->{$storeid}->{monhost};
|
||||
eval { PVE::API2::Storage::Config->delete({ storage => $storeid }) };
|
||||
if ($@) {
|
||||
warn "failed to remove storage '$storeid': $@\n";
|
||||
$err = 1;
|
||||
}
|
||||
}
|
||||
die "failed to remove (some) storages - check log and remove manually!\n"
|
||||
if $err;
|
||||
}
|
||||
|
||||
PVE::Ceph::Tools::destroy_fs($fs_name, $rados);
|
||||
PVE::Ceph::Tools::destroy_fs($fs_name, $rados);
|
||||
|
||||
if ($param->{'remove-pools'}) {
|
||||
warn "removing metadata pool '$fs->{metadata_pool}'\n";
|
||||
eval { PVE::Ceph::Tools::destroy_pool($fs->{metadata_pool}, $rados) };
|
||||
warn "$@\n" if $@;
|
||||
if ($param->{'remove-pools'}) {
|
||||
warn "removing metadata pool '$fs->{metadata_pool}'\n";
|
||||
eval { PVE::Ceph::Tools::destroy_pool($fs->{metadata_pool}, $rados) };
|
||||
warn "$@\n" if $@;
|
||||
|
||||
foreach my $pool ($fs->{data_pools}->@*) {
|
||||
warn "removing data pool '$pool'\n";
|
||||
eval { PVE::Ceph::Tools::destroy_pool($pool, $rados) };
|
||||
warn "$@\n" if $@;
|
||||
}
|
||||
}
|
||||
foreach my $pool ($fs->{data_pools}->@*) {
|
||||
warn "removing data pool '$pool'\n";
|
||||
eval { PVE::Ceph::Tools::destroy_pool($pool, $rados) };
|
||||
warn "$@\n" if $@;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
return $rpcenv->fork_worker('cephdestroyfs', $fs_name, $user, $worker);
|
||||
}});
|
||||
};
|
||||
return $rpcenv->fork_worker('cephdestroyfs', $fs_name, $user, $worker);
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'osd-details',
|
||||
path => 'osd-details',
|
||||
method => 'GET',
|
||||
description => "Get OSD details.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
osdid => {
|
||||
description => "ID of the OSD",
|
||||
type => 'string',
|
||||
},
|
||||
verbose => {
|
||||
description => "Print verbose information, same as json-pretty output format.",
|
||||
type => 'boolean',
|
||||
default => 0,
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
node => get_standard_option('pve-node'),
|
||||
osdid => {
|
||||
description => "ID of the OSD",
|
||||
type => 'string',
|
||||
},
|
||||
verbose => {
|
||||
description => "Print verbose information, same as json-pretty output format.",
|
||||
type => 'boolean',
|
||||
default => 0,
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'object' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
PVE::Ceph::Tools::check_ceph_inited();
|
||||
|
||||
my $res = PVE::API2::Ceph::OSD->osddetails({
|
||||
osdid => $param->{osdid},
|
||||
node => $param->{node},
|
||||
});
|
||||
my $res = PVE::API2::Ceph::OSD->osddetails({
|
||||
osdid => $param->{osdid},
|
||||
node => $param->{node},
|
||||
});
|
||||
|
||||
for my $dev ($res->{devices}->@*) {
|
||||
$dev->{"lv-info"} = PVE::API2::Ceph::OSD->osdvolume({
|
||||
osdid => $param->{osdid},
|
||||
node => $param->{node},
|
||||
type => $dev->{device},
|
||||
});
|
||||
}
|
||||
$res->{verbose} = 1 if $param->{verbose};
|
||||
return $res;
|
||||
}});
|
||||
for my $dev ($res->{devices}->@*) {
|
||||
$dev->{"lv-info"} = PVE::API2::Ceph::OSD->osdvolume({
|
||||
osdid => $param->{osdid},
|
||||
node => $param->{node},
|
||||
type => $dev->{device},
|
||||
});
|
||||
}
|
||||
$res->{verbose} = 1 if $param->{verbose};
|
||||
return $res;
|
||||
},
|
||||
});
|
||||
|
||||
my $format_osddetails = sub {
|
||||
my ($data, $schema, $options) = @_;
|
||||
|
|
@ -436,91 +452,118 @@ my $format_osddetails = sub {
|
|||
$options->{"output-format"} //= "text";
|
||||
|
||||
if ($data->{verbose}) {
|
||||
$options->{"output-format"} = "json-pretty";
|
||||
delete $data->{verbose};
|
||||
$options->{"output-format"} = "json-pretty";
|
||||
delete $data->{verbose};
|
||||
}
|
||||
|
||||
if ($options->{"output-format"} eq "text") {
|
||||
for my $dev ($data->{devices}->@*) {
|
||||
my ($disk, $type, $device) = $dev->@{'physical_device', 'type', 'device'};
|
||||
my ($lv_size, $lv_ctime) = $dev->{'lv-info'}->@{'lv_size', 'creation_time'};
|
||||
for my $dev ($data->{devices}->@*) {
|
||||
my ($disk, $type, $device) = $dev->@{ 'physical_device', 'type', 'device' };
|
||||
my ($lv_size, $lv_ctime) = $dev->{'lv-info'}->@{ 'lv_size', 'creation_time' };
|
||||
|
||||
$data->{osd}->{$device} = "Disk: $disk, Type: $type, LV Size: $lv_size, LV Creation Time: $lv_ctime";
|
||||
}
|
||||
PVE::CLIFormatter::print_api_result($data->{osd}, $schema, undef, $options);
|
||||
$data->{osd}->{$device} =
|
||||
"Disk: $disk, Type: $type, LV Size: $lv_size, LV Creation Time: $lv_ctime";
|
||||
}
|
||||
PVE::CLIFormatter::print_api_result($data->{osd}, $schema, undef, $options);
|
||||
} else {
|
||||
PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
|
||||
PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
|
||||
}
|
||||
};
|
||||
|
||||
our $cmddef = {
|
||||
init => [ 'PVE::API2::Ceph', 'init', [], { node => $nodename } ],
|
||||
init => ['PVE::API2::Ceph', 'init', [], { node => $nodename }],
|
||||
pool => {
|
||||
ls => [ 'PVE::API2::Ceph::Pool', 'lspools', [], { node => $nodename }, sub {
|
||||
my ($data, $schema, $options) = @_;
|
||||
PVE::CLIFormatter::print_api_result($data, $schema,
|
||||
[
|
||||
'pool_name',
|
||||
'size',
|
||||
'min_size',
|
||||
'pg_num',
|
||||
'pg_num_min',
|
||||
'pg_num_final',
|
||||
'pg_autoscale_mode',
|
||||
'target_size',
|
||||
'target_size_ratio',
|
||||
'crush_rule_name',
|
||||
'percent_used',
|
||||
'bytes_used',
|
||||
],
|
||||
$options);
|
||||
}, $PVE::RESTHandler::standard_output_options],
|
||||
create => [ 'PVE::API2::Ceph::Pool', 'createpool', ['name'], { node => $nodename }],
|
||||
destroy => [ 'PVE::API2::Ceph::Pool', 'destroypool', ['name'], { node => $nodename } ],
|
||||
set => [ 'PVE::API2::Ceph::Pool', 'setpool', ['name'], { node => $nodename } ],
|
||||
get => [ 'PVE::API2::Ceph::Pool', 'getpool', ['name'], { node => $nodename }, sub {
|
||||
my ($data, $schema, $options) = @_;
|
||||
PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
|
||||
}, $PVE::RESTHandler::standard_output_options],
|
||||
ls => [
|
||||
'PVE::API2::Ceph::Pool',
|
||||
'lspools',
|
||||
[],
|
||||
{ node => $nodename },
|
||||
sub {
|
||||
my ($data, $schema, $options) = @_;
|
||||
PVE::CLIFormatter::print_api_result(
|
||||
$data,
|
||||
$schema,
|
||||
[
|
||||
'pool_name',
|
||||
'size',
|
||||
'min_size',
|
||||
'pg_num',
|
||||
'pg_num_min',
|
||||
'pg_num_final',
|
||||
'pg_autoscale_mode',
|
||||
'target_size',
|
||||
'target_size_ratio',
|
||||
'crush_rule_name',
|
||||
'percent_used',
|
||||
'bytes_used',
|
||||
],
|
||||
$options,
|
||||
);
|
||||
},
|
||||
$PVE::RESTHandler::standard_output_options,
|
||||
],
|
||||
create => ['PVE::API2::Ceph::Pool', 'createpool', ['name'], { node => $nodename }],
|
||||
destroy => ['PVE::API2::Ceph::Pool', 'destroypool', ['name'], { node => $nodename }],
|
||||
set => ['PVE::API2::Ceph::Pool', 'setpool', ['name'], { node => $nodename }],
|
||||
get => [
|
||||
'PVE::API2::Ceph::Pool',
|
||||
'getpool',
|
||||
['name'],
|
||||
{ node => $nodename },
|
||||
sub {
|
||||
my ($data, $schema, $options) = @_;
|
||||
PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
|
||||
},
|
||||
$PVE::RESTHandler::standard_output_options,
|
||||
],
|
||||
},
|
||||
lspools => { alias => 'pool ls' },
|
||||
createpool => { alias => 'pool create' },
|
||||
destroypool => { alias => 'pool destroy' },
|
||||
fs => {
|
||||
create => [ 'PVE::API2::Ceph::FS', 'createfs', [], { node => $nodename }],
|
||||
destroy => [ __PACKAGE__, 'destroyfs', ['name'], { node => $nodename }],
|
||||
create => ['PVE::API2::Ceph::FS', 'createfs', [], { node => $nodename }],
|
||||
destroy => [__PACKAGE__, 'destroyfs', ['name'], { node => $nodename }],
|
||||
},
|
||||
osd => {
|
||||
create => [ 'PVE::API2::Ceph::OSD', 'createosd', ['dev'], { node => $nodename }, $upid_exit],
|
||||
destroy => [ 'PVE::API2::Ceph::OSD', 'destroyosd', ['osdid'], { node => $nodename }, $upid_exit],
|
||||
details => [
|
||||
__PACKAGE__, 'osd-details', ['osdid'], { node => $nodename }, $format_osddetails,
|
||||
$PVE::RESTHandler::standard_output_options,
|
||||
],
|
||||
create =>
|
||||
['PVE::API2::Ceph::OSD', 'createosd', ['dev'], { node => $nodename }, $upid_exit],
|
||||
destroy =>
|
||||
['PVE::API2::Ceph::OSD', 'destroyosd', ['osdid'], { node => $nodename }, $upid_exit],
|
||||
details => [
|
||||
__PACKAGE__,
|
||||
'osd-details',
|
||||
['osdid'],
|
||||
{ node => $nodename },
|
||||
$format_osddetails,
|
||||
$PVE::RESTHandler::standard_output_options,
|
||||
],
|
||||
},
|
||||
createosd => { alias => 'osd create' },
|
||||
destroyosd => { alias => 'osd destroy' },
|
||||
mon => {
|
||||
create => [ 'PVE::API2::Ceph::MON', 'createmon', [], { node => $nodename }, $upid_exit],
|
||||
destroy => [ 'PVE::API2::Ceph::MON', 'destroymon', ['monid'], { node => $nodename }, $upid_exit],
|
||||
create => ['PVE::API2::Ceph::MON', 'createmon', [], { node => $nodename }, $upid_exit],
|
||||
destroy =>
|
||||
['PVE::API2::Ceph::MON', 'destroymon', ['monid'], { node => $nodename }, $upid_exit],
|
||||
},
|
||||
createmon => { alias => 'mon create' },
|
||||
destroymon => { alias => 'mon destroy' },
|
||||
mgr => {
|
||||
create => [ 'PVE::API2::Ceph::MGR', 'createmgr', [], { node => $nodename }, $upid_exit],
|
||||
destroy => [ 'PVE::API2::Ceph::MGR', 'destroymgr', ['id'], { node => $nodename }, $upid_exit],
|
||||
create => ['PVE::API2::Ceph::MGR', 'createmgr', [], { node => $nodename }, $upid_exit],
|
||||
destroy =>
|
||||
['PVE::API2::Ceph::MGR', 'destroymgr', ['id'], { node => $nodename }, $upid_exit],
|
||||
},
|
||||
createmgr => { alias => 'mgr create' },
|
||||
destroymgr => { alias => 'mgr destroy' },
|
||||
mds => {
|
||||
create => [ 'PVE::API2::Ceph::MDS', 'createmds', [], { node => $nodename }, $upid_exit],
|
||||
destroy => [ 'PVE::API2::Ceph::MDS', 'destroymds', ['name'], { node => $nodename }, $upid_exit],
|
||||
create => ['PVE::API2::Ceph::MDS', 'createmds', [], { node => $nodename }, $upid_exit],
|
||||
destroy =>
|
||||
['PVE::API2::Ceph::MDS', 'destroymds', ['name'], { node => $nodename }, $upid_exit],
|
||||
},
|
||||
start => [ 'PVE::API2::Ceph', 'start', [], { node => $nodename }, $upid_exit],
|
||||
stop => [ 'PVE::API2::Ceph', 'stop', [], { node => $nodename }, $upid_exit],
|
||||
install => [ __PACKAGE__, 'install', [] ],
|
||||
purge => [ __PACKAGE__, 'purge', [] ],
|
||||
status => [ __PACKAGE__, 'status', []],
|
||||
start => ['PVE::API2::Ceph', 'start', [], { node => $nodename }, $upid_exit],
|
||||
stop => ['PVE::API2::Ceph', 'stop', [], { node => $nodename }, $upid_exit],
|
||||
install => [__PACKAGE__, 'install', []],
|
||||
purge => [__PACKAGE__, 'purge', []],
|
||||
status => [__PACKAGE__, 'status', []],
|
||||
};
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -43,22 +43,35 @@ sub param_mapping {
|
|||
my ($name) = @_;
|
||||
|
||||
my $load_file_and_encode = sub {
|
||||
my ($filename) = @_;
|
||||
my ($filename) = @_;
|
||||
|
||||
return PVE::ACME::Challenge->encode_value('string', 'data', PVE::Tools::file_get_contents($filename));
|
||||
return PVE::ACME::Challenge->encode_value(
|
||||
'string',
|
||||
'data',
|
||||
PVE::Tools::file_get_contents($filename),
|
||||
);
|
||||
};
|
||||
|
||||
my $mapping = {
|
||||
'upload_custom_cert' => [
|
||||
'certificates',
|
||||
'key',
|
||||
],
|
||||
'add_plugin' => [
|
||||
['data', $load_file_and_encode, "File with one key-value pair per line, will be base64url encode for storage in plugin config.", 0],
|
||||
],
|
||||
'update_plugin' => [
|
||||
['data', $load_file_and_encode, "File with one key-value pair per line, will be base64url encode for storage in plugin config.", 0],
|
||||
],
|
||||
'upload_custom_cert' => [
|
||||
'certificates', 'key',
|
||||
],
|
||||
'add_plugin' => [
|
||||
[
|
||||
'data',
|
||||
$load_file_and_encode,
|
||||
"File with one key-value pair per line, will be base64url encode for storage in plugin config.",
|
||||
0,
|
||||
],
|
||||
],
|
||||
'update_plugin' => [
|
||||
[
|
||||
'data',
|
||||
$load_file_and_encode,
|
||||
"File with one key-value pair per line, will be base64url encode for storage in plugin config.",
|
||||
0,
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
return $mapping->{$name};
|
||||
|
|
@ -70,203 +83,297 @@ __PACKAGE__->register_method({
|
|||
method => 'POST',
|
||||
description => "Register a new ACME account with a compatible CA.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
name => get_standard_option('pve-acme-account-name'),
|
||||
contact => get_standard_option('pve-acme-account-contact'),
|
||||
directory => get_standard_option('pve-acme-directory-url', {
|
||||
optional => 1,
|
||||
}),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
name => get_standard_option('pve-acme-account-name'),
|
||||
contact => get_standard_option('pve-acme-account-contact'),
|
||||
directory => get_standard_option('pve-acme-directory-url', {
|
||||
optional => 1,
|
||||
}),
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $custom_directory = 0;
|
||||
if (!$param->{directory}) {
|
||||
my $directories = PVE::API2::ACMEAccount->get_directories({});
|
||||
print "Directory endpoints:\n";
|
||||
my $i = 0;
|
||||
while ($i < @$directories) {
|
||||
print $i, ") ", $directories->[$i]->{name}, " (", $directories->[$i]->{url}, ")\n";
|
||||
$i++;
|
||||
}
|
||||
print $i, ") Custom\n";
|
||||
my $custom_directory = 0;
|
||||
if (!$param->{directory}) {
|
||||
my $directories = PVE::API2::ACMEAccount->get_directories({});
|
||||
print "Directory endpoints:\n";
|
||||
my $i = 0;
|
||||
while ($i < @$directories) {
|
||||
print $i, ") ", $directories->[$i]->{name}, " (", $directories->[$i]->{url},
|
||||
")\n";
|
||||
$i++;
|
||||
}
|
||||
print $i, ") Custom\n";
|
||||
|
||||
my $term = Term::ReadLine->new('pvenode');
|
||||
my $get_dir_selection = sub {
|
||||
my $selection = $term->readline("Enter selection: ");
|
||||
if ($selection =~ /^(\d+)$/) {
|
||||
$selection = $1;
|
||||
if ($selection == $i) {
|
||||
$param->{directory} = $term->readline("Enter custom URL: ");
|
||||
$custom_directory = 1;
|
||||
return;
|
||||
} elsif ($selection < $i && $selection >= 0) {
|
||||
$param->{directory} = $directories->[$selection]->{url};
|
||||
return;
|
||||
}
|
||||
}
|
||||
print "Invalid selection.\n";
|
||||
};
|
||||
my $term = Term::ReadLine->new('pvenode');
|
||||
my $get_dir_selection = sub {
|
||||
my $selection = $term->readline("Enter selection: ");
|
||||
if ($selection =~ /^(\d+)$/) {
|
||||
$selection = $1;
|
||||
if ($selection == $i) {
|
||||
$param->{directory} = $term->readline("Enter custom URL: ");
|
||||
$custom_directory = 1;
|
||||
return;
|
||||
} elsif ($selection < $i && $selection >= 0) {
|
||||
$param->{directory} = $directories->[$selection]->{url};
|
||||
return;
|
||||
}
|
||||
}
|
||||
print "Invalid selection.\n";
|
||||
};
|
||||
|
||||
my $attempts = 0;
|
||||
while (!$param->{directory}) {
|
||||
die "Aborting.\n" if $attempts > 3;
|
||||
$get_dir_selection->();
|
||||
$attempts++;
|
||||
}
|
||||
}
|
||||
print "\nAttempting to fetch Terms of Service from '$param->{directory}'..\n";
|
||||
my $meta = PVE::API2::ACMEAccount->get_meta({ directory => $param->{directory} });
|
||||
if ($meta->{termsOfService}) {
|
||||
my $tos = $meta->{termsOfService};
|
||||
print "Terms of Service: $tos\n";
|
||||
my $term = Term::ReadLine->new('pvenode');
|
||||
my $agreed = $term->readline('Do you agree to the above terms? [y|N]: ');
|
||||
die "Cannot continue without agreeing to ToS, aborting.\n"
|
||||
if ($agreed !~ /^y$/i);
|
||||
my $attempts = 0;
|
||||
while (!$param->{directory}) {
|
||||
die "Aborting.\n" if $attempts > 3;
|
||||
$get_dir_selection->();
|
||||
$attempts++;
|
||||
}
|
||||
}
|
||||
print "\nAttempting to fetch Terms of Service from '$param->{directory}'..\n";
|
||||
my $meta = PVE::API2::ACMEAccount->get_meta({ directory => $param->{directory} });
|
||||
if ($meta->{termsOfService}) {
|
||||
my $tos = $meta->{termsOfService};
|
||||
print "Terms of Service: $tos\n";
|
||||
my $term = Term::ReadLine->new('pvenode');
|
||||
my $agreed = $term->readline('Do you agree to the above terms? [y|N]: ');
|
||||
die "Cannot continue without agreeing to ToS, aborting.\n"
|
||||
if ($agreed !~ /^y$/i);
|
||||
|
||||
$param->{tos_url} = $tos;
|
||||
} else {
|
||||
print "No Terms of Service found, proceeding.\n";
|
||||
}
|
||||
$param->{tos_url} = $tos;
|
||||
} else {
|
||||
print "No Terms of Service found, proceeding.\n";
|
||||
}
|
||||
|
||||
my $eab_enabled = $meta->{externalAccountRequired};
|
||||
if (!$eab_enabled && $custom_directory) {
|
||||
my $term = Term::ReadLine->new('pvenode');
|
||||
my $agreed = $term->readline('Do you want to use external account binding? [y|N]: ');
|
||||
$eab_enabled = ($agreed =~ /^y$/i);
|
||||
} elsif ($eab_enabled) {
|
||||
print "The CA requires external account binding.\n";
|
||||
}
|
||||
if ($eab_enabled) {
|
||||
print "You should have received a key id and a key from your CA.\n";
|
||||
my $term = Term::ReadLine->new('pvenode');
|
||||
my $eab_kid = $term->readline('Enter EAB key id: ');
|
||||
my $eab_hmac_key = $term->readline('Enter EAB key: ');
|
||||
my $eab_enabled = $meta->{externalAccountRequired};
|
||||
if (!$eab_enabled && $custom_directory) {
|
||||
my $term = Term::ReadLine->new('pvenode');
|
||||
my $agreed =
|
||||
$term->readline('Do you want to use external account binding? [y|N]: ');
|
||||
$eab_enabled = ($agreed =~ /^y$/i);
|
||||
} elsif ($eab_enabled) {
|
||||
print "The CA requires external account binding.\n";
|
||||
}
|
||||
if ($eab_enabled) {
|
||||
print "You should have received a key id and a key from your CA.\n";
|
||||
my $term = Term::ReadLine->new('pvenode');
|
||||
my $eab_kid = $term->readline('Enter EAB key id: ');
|
||||
my $eab_hmac_key = $term->readline('Enter EAB key: ');
|
||||
|
||||
$param->{'eab-kid'} = $eab_kid;
|
||||
$param->{'eab-hmac-key'} = $eab_hmac_key;
|
||||
}
|
||||
$param->{'eab-kid'} = $eab_kid;
|
||||
$param->{'eab-hmac-key'} = $eab_hmac_key;
|
||||
}
|
||||
|
||||
print "\nAttempting to register account with '$param->{directory}'..\n";
|
||||
print "\nAttempting to register account with '$param->{directory}'..\n";
|
||||
|
||||
$upid_exit->(PVE::API2::ACMEAccount->register_account($param));
|
||||
}});
|
||||
$upid_exit->(PVE::API2::ACMEAccount->register_account($param));
|
||||
},
|
||||
});
|
||||
|
||||
my $print_cert_info = sub {
|
||||
my ($schema, $cert, $options) = @_;
|
||||
|
||||
my $order = [qw(filename fingerprint subject issuer notbefore notafter public-key-type public-key-bits san)];
|
||||
my $order = [
|
||||
qw(filename fingerprint subject issuer notbefore notafter public-key-type public-key-bits san)
|
||||
];
|
||||
PVE::CLIFormatter::print_api_result(
|
||||
$cert, $schema, $order, { %$options, noheader => 1, sort_key => 0 });
|
||||
$cert,
|
||||
$schema,
|
||||
$order,
|
||||
{ %$options, noheader => 1, sort_key => 0 },
|
||||
);
|
||||
};
|
||||
|
||||
our $cmddef = {
|
||||
config => {
|
||||
get => [ 'PVE::API2::NodeConfig', 'get_config', [], { node => $nodename }, sub {
|
||||
my ($res) = @_;
|
||||
print PVE::NodeConfig::write_node_config($res);
|
||||
}],
|
||||
set => [ 'PVE::API2::NodeConfig', 'set_options', [], { node => $nodename } ],
|
||||
get => [
|
||||
'PVE::API2::NodeConfig',
|
||||
'get_config',
|
||||
[],
|
||||
{ node => $nodename },
|
||||
sub {
|
||||
my ($res) = @_;
|
||||
print PVE::NodeConfig::write_node_config($res);
|
||||
},
|
||||
],
|
||||
set => ['PVE::API2::NodeConfig', 'set_options', [], { node => $nodename }],
|
||||
},
|
||||
|
||||
startall => [ 'PVE::API2::Nodes::Nodeinfo', 'startall', [], { node => $nodename } ],
|
||||
stopall => [ 'PVE::API2::Nodes::Nodeinfo', 'stopall', [], { node => $nodename } ],
|
||||
migrateall => [ 'PVE::API2::Nodes::Nodeinfo', 'migrateall', [ 'target' ], { node => $nodename } ],
|
||||
startall => ['PVE::API2::Nodes::Nodeinfo', 'startall', [], { node => $nodename }],
|
||||
stopall => ['PVE::API2::Nodes::Nodeinfo', 'stopall', [], { node => $nodename }],
|
||||
migrateall =>
|
||||
['PVE::API2::Nodes::Nodeinfo', 'migrateall', ['target'], { node => $nodename }],
|
||||
|
||||
cert => {
|
||||
info => [ 'PVE::API2::Certificates', 'info', [], { node => $nodename }, sub {
|
||||
my ($res, $schema, $options) = @_;
|
||||
info => [
|
||||
'PVE::API2::Certificates',
|
||||
'info',
|
||||
[],
|
||||
{ node => $nodename },
|
||||
sub {
|
||||
my ($res, $schema, $options) = @_;
|
||||
|
||||
if (!$options->{'output-format'} || $options->{'output-format'} eq 'text') {
|
||||
for my $cert (sort { $a->{filename} cmp $b->{filename} } @$res) {
|
||||
$print_cert_info->($schema->{items}, $cert, $options);
|
||||
}
|
||||
} else {
|
||||
PVE::CLIFormatter::print_api_result($res, $schema, undef, $options);
|
||||
}
|
||||
if (!$options->{'output-format'} || $options->{'output-format'} eq 'text') {
|
||||
for my $cert (sort { $a->{filename} cmp $b->{filename} } @$res) {
|
||||
$print_cert_info->($schema->{items}, $cert, $options);
|
||||
}
|
||||
} else {
|
||||
PVE::CLIFormatter::print_api_result($res, $schema, undef, $options);
|
||||
}
|
||||
|
||||
}, $PVE::RESTHandler::standard_output_options],
|
||||
set => [ 'PVE::API2::Certificates', 'upload_custom_cert', ['certificates', 'key'], { node => $nodename }, sub {
|
||||
my ($res, $schema, $options) = @_;
|
||||
$print_cert_info->($schema, $res, $options);
|
||||
}, $PVE::RESTHandler::standard_output_options],
|
||||
delete => [ 'PVE::API2::Certificates', 'remove_custom_cert', ['restart'], { node => $nodename } ],
|
||||
},
|
||||
$PVE::RESTHandler::standard_output_options,
|
||||
],
|
||||
set => [
|
||||
'PVE::API2::Certificates',
|
||||
'upload_custom_cert',
|
||||
['certificates', 'key'],
|
||||
{ node => $nodename },
|
||||
sub {
|
||||
my ($res, $schema, $options) = @_;
|
||||
$print_cert_info->($schema, $res, $options);
|
||||
},
|
||||
$PVE::RESTHandler::standard_output_options,
|
||||
],
|
||||
delete =>
|
||||
['PVE::API2::Certificates', 'remove_custom_cert', ['restart'], { node => $nodename }],
|
||||
},
|
||||
|
||||
task => {
|
||||
list => [ 'PVE::API2::Tasks', 'node_tasks', [], { node => $nodename }, sub {
|
||||
my ($data, $schema, $options) = @_;
|
||||
foreach my $task (@$data) {
|
||||
if (!defined($task->{status})) {
|
||||
$task->{status} = 'UNKNOWN';
|
||||
# RUNNING is set by the API call and needs to be checked explicitly
|
||||
} elsif (PVE::Tools::upid_status_is_error($task->{status}) &&
|
||||
$task->{status} ne 'RUNNING')
|
||||
{
|
||||
$task->{status} = 'ERROR';
|
||||
}
|
||||
}
|
||||
PVE::CLIFormatter::print_api_result($data, $schema, ['upid', 'type', 'id', 'user', 'starttime', 'endtime', 'status' ], $options);
|
||||
}, $PVE::RESTHandler::standard_output_options],
|
||||
status => [ 'PVE::API2::Tasks', 'read_task_status', [ 'upid' ], { node => $nodename }, sub {
|
||||
my ($data, $schema, $options) = @_;
|
||||
PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
|
||||
}, $PVE::RESTHandler::standard_output_options],
|
||||
# set limit to 1000000, so we see the whole log, not only the first 50 lines by default
|
||||
log => [ 'PVE::API2::Tasks', 'read_task_log', [ 'upid' ], { node => $nodename, limit => 1000000 }, sub {
|
||||
my ($data, $resultprops) = @_;
|
||||
foreach my $line (@$data) {
|
||||
print $line->{t} . "\n";
|
||||
}
|
||||
}],
|
||||
list => [
|
||||
'PVE::API2::Tasks',
|
||||
'node_tasks',
|
||||
[],
|
||||
{ node => $nodename },
|
||||
sub {
|
||||
my ($data, $schema, $options) = @_;
|
||||
foreach my $task (@$data) {
|
||||
if (!defined($task->{status})) {
|
||||
$task->{status} = 'UNKNOWN';
|
||||
# RUNNING is set by the API call and needs to be checked explicitly
|
||||
} elsif (
|
||||
PVE::Tools::upid_status_is_error($task->{status})
|
||||
&& $task->{status} ne 'RUNNING'
|
||||
) {
|
||||
$task->{status} = 'ERROR';
|
||||
}
|
||||
}
|
||||
PVE::CLIFormatter::print_api_result(
|
||||
$data,
|
||||
$schema,
|
||||
['upid', 'type', 'id', 'user', 'starttime', 'endtime', 'status'],
|
||||
$options,
|
||||
);
|
||||
},
|
||||
$PVE::RESTHandler::standard_output_options,
|
||||
],
|
||||
status => [
|
||||
'PVE::API2::Tasks',
|
||||
'read_task_status',
|
||||
['upid'],
|
||||
{ node => $nodename },
|
||||
sub {
|
||||
my ($data, $schema, $options) = @_;
|
||||
PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
|
||||
},
|
||||
$PVE::RESTHandler::standard_output_options,
|
||||
],
|
||||
# set limit to 1000000, so we see the whole log, not only the first 50 lines by default
|
||||
log => [
|
||||
'PVE::API2::Tasks',
|
||||
'read_task_log',
|
||||
['upid'],
|
||||
{ node => $nodename, limit => 1000000 },
|
||||
sub {
|
||||
my ($data, $resultprops) = @_;
|
||||
foreach my $line (@$data) {
|
||||
print $line->{t} . "\n";
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
acme => {
|
||||
account => {
|
||||
list => [ 'PVE::API2::ACMEAccount', 'account_index', [], {}, sub {
|
||||
my ($res) = @_;
|
||||
for my $acc (@$res) {
|
||||
print "$acc->{name}\n";
|
||||
}
|
||||
}],
|
||||
register => [ __PACKAGE__, 'acme_register', ['name', 'contact'], {}, $upid_exit ],
|
||||
deactivate => [ 'PVE::API2::ACMEAccount', 'deactivate_account', ['name'], {}, $upid_exit ],
|
||||
info => [ 'PVE::API2::ACMEAccount', 'get_account', ['name'], {}, sub {
|
||||
my ($data, $schema, $options) = @_;
|
||||
PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
|
||||
}, $PVE::RESTHandler::standard_output_options],
|
||||
update => [ 'PVE::API2::ACMEAccount', 'update_account', ['name'], {}, $upid_exit ],
|
||||
},
|
||||
cert => {
|
||||
order => [ 'PVE::API2::ACME', 'new_certificate', [], { node => $nodename }, $upid_exit ],
|
||||
renew => [ 'PVE::API2::ACME', 'renew_certificate', [], { node => $nodename }, $upid_exit ],
|
||||
revoke => [ 'PVE::API2::ACME', 'revoke_certificate', [], { node => $nodename }, $upid_exit ],
|
||||
},
|
||||
plugin => {
|
||||
list => [ 'PVE::API2::ACMEPlugin', 'index', [], {}, sub {
|
||||
my ($data, $schema, $options) = @_;
|
||||
PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
|
||||
}, $PVE::RESTHandler::standard_output_options ],
|
||||
config => [ 'PVE::API2::ACMEPlugin', 'get_plugin_config', ['id'], {}, sub {
|
||||
my ($data, $schema, $options) = @_;
|
||||
PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
|
||||
}, $PVE::RESTHandler::standard_output_options ],
|
||||
add => [ 'PVE::API2::ACMEPlugin', 'add_plugin', ['type', 'id'] ],
|
||||
set => [ 'PVE::API2::ACMEPlugin', 'update_plugin', ['id'] ],
|
||||
remove => [ 'PVE::API2::ACMEPlugin', 'delete_plugin', ['id'] ],
|
||||
},
|
||||
account => {
|
||||
list => [
|
||||
'PVE::API2::ACMEAccount',
|
||||
'account_index',
|
||||
[],
|
||||
{},
|
||||
sub {
|
||||
my ($res) = @_;
|
||||
for my $acc (@$res) {
|
||||
print "$acc->{name}\n";
|
||||
}
|
||||
},
|
||||
],
|
||||
register => [__PACKAGE__, 'acme_register', ['name', 'contact'], {}, $upid_exit],
|
||||
deactivate =>
|
||||
['PVE::API2::ACMEAccount', 'deactivate_account', ['name'], {}, $upid_exit],
|
||||
info => [
|
||||
'PVE::API2::ACMEAccount',
|
||||
'get_account',
|
||||
['name'],
|
||||
{},
|
||||
sub {
|
||||
my ($data, $schema, $options) = @_;
|
||||
PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
|
||||
},
|
||||
$PVE::RESTHandler::standard_output_options,
|
||||
],
|
||||
update => ['PVE::API2::ACMEAccount', 'update_account', ['name'], {}, $upid_exit],
|
||||
},
|
||||
cert => {
|
||||
order =>
|
||||
['PVE::API2::ACME', 'new_certificate', [], { node => $nodename }, $upid_exit],
|
||||
renew =>
|
||||
['PVE::API2::ACME', 'renew_certificate', [], { node => $nodename }, $upid_exit],
|
||||
revoke =>
|
||||
['PVE::API2::ACME', 'revoke_certificate', [], { node => $nodename }, $upid_exit],
|
||||
},
|
||||
plugin => {
|
||||
list => [
|
||||
'PVE::API2::ACMEPlugin',
|
||||
'index',
|
||||
[],
|
||||
{},
|
||||
sub {
|
||||
my ($data, $schema, $options) = @_;
|
||||
PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
|
||||
},
|
||||
$PVE::RESTHandler::standard_output_options,
|
||||
],
|
||||
config => [
|
||||
'PVE::API2::ACMEPlugin',
|
||||
'get_plugin_config',
|
||||
['id'],
|
||||
{},
|
||||
sub {
|
||||
my ($data, $schema, $options) = @_;
|
||||
PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
|
||||
},
|
||||
$PVE::RESTHandler::standard_output_options,
|
||||
],
|
||||
add => ['PVE::API2::ACMEPlugin', 'add_plugin', ['type', 'id']],
|
||||
set => ['PVE::API2::ACMEPlugin', 'update_plugin', ['id']],
|
||||
remove => ['PVE::API2::ACMEPlugin', 'delete_plugin', ['id']],
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
wakeonlan => [ 'PVE::API2::Nodes::Nodeinfo', 'wakeonlan', [ 'node' ], {}, sub {
|
||||
my ($mac_addr) = @_;
|
||||
wakeonlan => [
|
||||
'PVE::API2::Nodes::Nodeinfo',
|
||||
'wakeonlan',
|
||||
['node'],
|
||||
{},
|
||||
sub {
|
||||
my ($mac_addr) = @_;
|
||||
|
||||
print "Wake on LAN packet send for '$mac_addr'\n";
|
||||
} ],
|
||||
print "Wake on LAN packet send for '$mac_addr'\n";
|
||||
},
|
||||
],
|
||||
|
||||
};
|
||||
|
||||
|
|
|
|||
495
PVE/CLI/pvesh.pm
495
PVE/CLI/pvesh.pm
|
|
@ -27,18 +27,18 @@ my $optmatch;
|
|||
do {
|
||||
$optmatch = 0;
|
||||
if ($ARGV[0]) {
|
||||
if ($ARGV[0] eq '--noproxy') {
|
||||
shift @ARGV;
|
||||
$disable_proxy = 1;
|
||||
$optmatch = 1;
|
||||
} elsif ($ARGV[0] eq '--nooutput') {
|
||||
# we use this when starting task in CLI (suppress printing upid)
|
||||
# for example 'pvesh --nooutput create /nodes/localhost/stopall'
|
||||
shift @ARGV;
|
||||
$opt_nooutput = 1;
|
||||
$optmatch = 1;
|
||||
}
|
||||
}
|
||||
if ($ARGV[0] eq '--noproxy') {
|
||||
shift @ARGV;
|
||||
$disable_proxy = 1;
|
||||
$optmatch = 1;
|
||||
} elsif ($ARGV[0] eq '--nooutput') {
|
||||
# we use this when starting task in CLI (suppress printing upid)
|
||||
# for example 'pvesh --nooutput create /nodes/localhost/stopall'
|
||||
shift @ARGV;
|
||||
$opt_nooutput = 1;
|
||||
$optmatch = 1;
|
||||
}
|
||||
}
|
||||
} while ($optmatch);
|
||||
|
||||
sub setup_environment {
|
||||
|
|
@ -46,7 +46,7 @@ sub setup_environment {
|
|||
}
|
||||
|
||||
sub complete_api_path {
|
||||
my($text) = @_;
|
||||
my ($text) = @_;
|
||||
|
||||
my ($dir, undef, $rest) = $text =~ m|^(.*/)?(([^/]*))?$|;
|
||||
|
||||
|
|
@ -60,16 +60,16 @@ sub complete_api_path {
|
|||
|
||||
my $di = dir_info($path);
|
||||
if (my $children = $di->{children}) {
|
||||
foreach my $c (@$children) {
|
||||
if ($c =~ /^\Q$rest/) {
|
||||
my $new = $dir ? "$dir$c" : $c;
|
||||
push @$res, $new;
|
||||
}
|
||||
}
|
||||
foreach my $c (@$children) {
|
||||
if ($c =~ /^\Q$rest/) {
|
||||
my $new = $dir ? "$dir$c" : $c;
|
||||
push @$res, $new;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scalar(@$res) == 1) {
|
||||
return [$res->[0], "$res->[0]/"];
|
||||
return [$res->[0], "$res->[0]/"];
|
||||
}
|
||||
|
||||
return $res;
|
||||
|
|
@ -90,14 +90,18 @@ sub check_proxyto {
|
|||
my $all_params = { %$uri_param, %$params };
|
||||
|
||||
if ($info->{proxyto} || $info->{proxyto_callback}) {
|
||||
my $node = PVE::API2Tools::resolve_proxyto(
|
||||
$rpcenv, $info->{proxyto_callback}, $info->{proxyto}, $all_params);
|
||||
my $node = PVE::API2Tools::resolve_proxyto(
|
||||
$rpcenv,
|
||||
$info->{proxyto_callback},
|
||||
$info->{proxyto},
|
||||
$all_params,
|
||||
);
|
||||
|
||||
if ($node ne 'localhost' && ($node ne PVE::INotify::nodename())) {
|
||||
die "proxy loop detected - aborting\n" if $disable_proxy;
|
||||
my $remip = PVE::Cluster::remote_node_ip($node);
|
||||
return ($node, $remip);
|
||||
}
|
||||
if ($node ne 'localhost' && ($node ne PVE::INotify::nodename())) {
|
||||
die "proxy loop detected - aborting\n" if $disable_proxy;
|
||||
my $remip = PVE::Cluster::remote_node_ip($node);
|
||||
return ($node, $remip);
|
||||
}
|
||||
}
|
||||
|
||||
return undef;
|
||||
|
|
@ -108,32 +112,32 @@ sub proxy_handler {
|
|||
|
||||
my $args = [];
|
||||
foreach my $key (keys %$param) {
|
||||
next if $key eq 'quiet' || $key eq 'output-format'; # just to be sure
|
||||
if (ref($param->{$key}) eq 'ARRAY') {
|
||||
push @$args, "--$key", $_ for $param->{$key}->@*;
|
||||
} else {
|
||||
push @$args, "--$key", $_ for split(/\0/, $param->{$key});
|
||||
}
|
||||
next if $key eq 'quiet' || $key eq 'output-format'; # just to be sure
|
||||
if (ref($param->{$key}) eq 'ARRAY') {
|
||||
push @$args, "--$key", $_ for $param->{$key}->@*;
|
||||
} else {
|
||||
push @$args, "--$key", $_ for split(/\0/, $param->{$key});
|
||||
}
|
||||
}
|
||||
|
||||
my $ssh_tunnel_cmd = PVE::SSHInfo::ssh_info_to_command({ ip => $remip, name => $node });
|
||||
|
||||
my @pvesh_cmd = ('pvesh', '--noproxy', $cmd, $path, '--output-format', 'json');
|
||||
if (scalar(@$args)) {
|
||||
my $cmdargs = [ String::ShellQuote::shell_quote(@$args) ];
|
||||
push @pvesh_cmd, @$cmdargs;
|
||||
my $cmdargs = [String::ShellQuote::shell_quote(@$args)];
|
||||
push @pvesh_cmd, @$cmdargs;
|
||||
}
|
||||
|
||||
my $res = '';
|
||||
PVE::Tools::run_command(
|
||||
[ $ssh_tunnel_cmd->@*, '--', @pvesh_cmd ],
|
||||
errmsg => "proxy handler failed",
|
||||
outfunc => sub { $res .= shift },
|
||||
[$ssh_tunnel_cmd->@*, '--', @pvesh_cmd],
|
||||
errmsg => "proxy handler failed",
|
||||
outfunc => sub { $res .= shift },
|
||||
);
|
||||
|
||||
my $decoded_json = eval { decode_json($res) };
|
||||
if ($@) {
|
||||
return $res; # do not error, '' (null) is valid too
|
||||
return $res; # do not error, '' (null) is valid too
|
||||
}
|
||||
return $decoded_json;
|
||||
}
|
||||
|
|
@ -147,13 +151,13 @@ sub extract_children {
|
|||
|
||||
my $href = $lnk->{href};
|
||||
if ($href =~ m/^\{(\S+)\}$/) {
|
||||
my $prop = $1;
|
||||
my $prop = $1;
|
||||
|
||||
foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @$data) {
|
||||
next if !ref($elem);
|
||||
my $value = $elem->{$prop};
|
||||
push @$res, $value;
|
||||
}
|
||||
foreach my $elem (sort { $a->{$prop} cmp $b->{$prop} } @$data) {
|
||||
next if !ref($elem);
|
||||
my $value = $elem->{$prop};
|
||||
push @$res, $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
|
|
@ -166,11 +170,11 @@ sub dir_info {
|
|||
my $uri_param = {};
|
||||
my ($handler, $info, $pm) = PVE::API2->find_handler('GET', $path, $uri_param);
|
||||
if ($handler && $info) {
|
||||
eval {
|
||||
my $data = $handler->handle($info, $uri_param);
|
||||
my $lnk = PVE::JSONSchema::method_get_child_link($info);
|
||||
$res->{children} = extract_children($lnk, $data);
|
||||
}; # ignore errors ?
|
||||
eval {
|
||||
my $data = $handler->handle($info, $uri_param);
|
||||
my $lnk = PVE::JSONSchema::method_get_child_link($info);
|
||||
$res->{children} = extract_children($lnk, $data);
|
||||
}; # ignore errors ?
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
|
@ -182,34 +186,34 @@ sub resource_cap {
|
|||
|
||||
my ($handler, $info) = PVE::API2->find_handler('GET', $path);
|
||||
if (!($handler && $info)) {
|
||||
$res .= '--';
|
||||
$res .= '--';
|
||||
} else {
|
||||
if (PVE::JSONSchema::method_get_child_link($info)) {
|
||||
$res .= 'Dr';
|
||||
} else {
|
||||
$res .= '-r';
|
||||
}
|
||||
if (PVE::JSONSchema::method_get_child_link($info)) {
|
||||
$res .= 'Dr';
|
||||
} else {
|
||||
$res .= '-r';
|
||||
}
|
||||
}
|
||||
|
||||
($handler, $info) = PVE::API2->find_handler('PUT', $path);
|
||||
if (!($handler && $info)) {
|
||||
$res .= '-';
|
||||
$res .= '-';
|
||||
} else {
|
||||
$res .= 'w';
|
||||
$res .= 'w';
|
||||
}
|
||||
|
||||
($handler, $info) = PVE::API2->find_handler('POST', $path);
|
||||
if (!($handler && $info)) {
|
||||
$res .= '-';
|
||||
$res .= '-';
|
||||
} else {
|
||||
$res .= 'c';
|
||||
$res .= 'c';
|
||||
}
|
||||
|
||||
($handler, $info) = PVE::API2->find_handler('DELETE', $path);
|
||||
if (!($handler && $info)) {
|
||||
$res .= '-';
|
||||
$res .= '-';
|
||||
} else {
|
||||
$res .= 'd';
|
||||
$res .= 'd';
|
||||
}
|
||||
|
||||
return $res;
|
||||
|
|
@ -224,52 +228,51 @@ sub extract_path_info {
|
|||
my ($handler, $info);
|
||||
|
||||
my $test_path_properties = sub {
|
||||
my ($method, $path) = @_;
|
||||
($handler, $info) = PVE::API2->find_handler($method, $path, $uri_param);
|
||||
my ($method, $path) = @_;
|
||||
($handler, $info) = PVE::API2->find_handler($method, $path, $uri_param);
|
||||
};
|
||||
|
||||
if (defined(my $cmd = $ARGV[0])) {
|
||||
if (my $method = $method_map->{$cmd}) {
|
||||
if (my $path = $ARGV[1]) {
|
||||
$test_path_properties->($method, $path);
|
||||
if (!defined($handler)) {
|
||||
print STDERR "No '$cmd' handler defined for '$path'\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
} elsif ($cmd eq 'bashcomplete') {
|
||||
my $cmdline = substr($ENV{COMP_LINE}, 0, $ENV{COMP_POINT});
|
||||
my $args = PVE::Tools::split_args($cmdline);
|
||||
if (defined(my $cmd = $args->[1])) {
|
||||
if (my $method = $method_map->{$cmd}) {
|
||||
if (my $path = $args->[2]) {
|
||||
$test_path_properties->($method, $path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (my $method = $method_map->{$cmd}) {
|
||||
if (my $path = $ARGV[1]) {
|
||||
$test_path_properties->($method, $path);
|
||||
if (!defined($handler)) {
|
||||
print STDERR "No '$cmd' handler defined for '$path'\n";
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
} elsif ($cmd eq 'bashcomplete') {
|
||||
my $cmdline = substr($ENV{COMP_LINE}, 0, $ENV{COMP_POINT});
|
||||
my $args = PVE::Tools::split_args($cmdline);
|
||||
if (defined(my $cmd = $args->[1])) {
|
||||
if (my $method = $method_map->{$cmd}) {
|
||||
if (my $path = $args->[2]) {
|
||||
$test_path_properties->($method, $path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
|
||||
my $path_properties = {};
|
||||
|
||||
my $api_path_property = {
|
||||
description => "API path.",
|
||||
type => 'string',
|
||||
completion => sub {
|
||||
my ($cmd, $pname, $cur, $args) = @_;
|
||||
return complete_api_path($cur);
|
||||
my ($cmd, $pname, $cur, $args) = @_;
|
||||
return complete_api_path($cur);
|
||||
},
|
||||
};
|
||||
|
||||
my $uri_param = {};
|
||||
if (my $info = extract_path_info($uri_param)) {
|
||||
foreach my $key (keys %{$info->{parameters}->{properties}}) {
|
||||
next if defined($uri_param->{$key});
|
||||
$path_properties->{$key} = $info->{parameters}->{properties}->{$key};
|
||||
foreach my $key (keys %{ $info->{parameters}->{properties} }) {
|
||||
next if defined($uri_param->{$key});
|
||||
$path_properties->{$key} = $info->{parameters}->{properties}->{$key};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,7 +288,8 @@ my $extract_std_options = 1;
|
|||
my $cond_add_standard_output_properties = sub {
|
||||
my ($props) = @_;
|
||||
|
||||
my $keys = [ grep { !defined($props->{$_}) } keys %$PVE::RESTHandler::standard_output_options ];
|
||||
my $keys =
|
||||
[grep { !defined($props->{$_}) } keys %$PVE::RESTHandler::standard_output_options];
|
||||
|
||||
return PVE::RESTHandler::add_standard_output_properties($props, $keys);
|
||||
};
|
||||
|
|
@ -293,28 +297,30 @@ my $cond_add_standard_output_properties = sub {
|
|||
my $handle_streamed_response = sub {
|
||||
my ($download) = @_;
|
||||
my ($fh, $path, $encoding, $type) =
|
||||
$download->@{'fh', 'path', 'content-encoding', 'content-type'};
|
||||
$download->@{ 'fh', 'path', 'content-encoding', 'content-type' };
|
||||
|
||||
die "{download} returned but neither fh nor path given\n" if !defined($fh) && !defined($path);
|
||||
die "{download} returned but neither fh nor path given\n"
|
||||
if !defined($fh) && !defined($path);
|
||||
|
||||
die "unknown 'content-encoding' $encoding\n" if defined($encoding) && $encoding ne 'gzip';
|
||||
die "unknown 'content-type' $type\n" if defined($type) && $type !~ qw!^(?:text/plain|application/json)$!;
|
||||
die "unknown 'content-type' $type\n"
|
||||
if defined($type) && $type !~ qw!^(?:text/plain|application/json)$!;
|
||||
|
||||
if (defined($path)) {
|
||||
open($fh, '<', $path) or die "open stream path '$path' for reading failed - $!\n";
|
||||
open($fh, '<', $path) or die "open stream path '$path' for reading failed - $!\n";
|
||||
}
|
||||
|
||||
local $/;
|
||||
my $data = <$fh>;
|
||||
|
||||
if (defined($encoding)) {
|
||||
my $out;
|
||||
gunzip(\$data => \$out);
|
||||
$data = $out;
|
||||
my $out;
|
||||
gunzip(\$data => \$out);
|
||||
$data = $out;
|
||||
}
|
||||
|
||||
if (defined($type) && $type eq 'application/json') {
|
||||
$data = decode_json($data)->{data};
|
||||
$data = decode_json($data)->{data};
|
||||
}
|
||||
|
||||
return $data;
|
||||
|
|
@ -328,7 +334,8 @@ sub call_api_method {
|
|||
my $path = PVE::Tools::extract_param($param, 'api_path');
|
||||
die "missing API path\n" if !defined($path);
|
||||
|
||||
my $stdopts = $extract_std_options
|
||||
my $stdopts =
|
||||
$extract_std_options
|
||||
? PVE::RESTHandler::extract_standard_output_properties($param)
|
||||
: {};
|
||||
|
||||
|
|
@ -337,28 +344,28 @@ sub call_api_method {
|
|||
my $uri_param = {};
|
||||
my ($handler, $info) = PVE::API2->find_handler($method, $path, $uri_param);
|
||||
if (!$handler || !$info) {
|
||||
die "no '$cmd' handler for '$path'\n";
|
||||
die "no '$cmd' handler for '$path'\n";
|
||||
}
|
||||
|
||||
my $data;
|
||||
my ($node, $remip) = check_proxyto($info, $uri_param, $param);
|
||||
if ($node) {
|
||||
$data = proxy_handler($node, $remip, $path, $cmd, $param);
|
||||
$data = proxy_handler($node, $remip, $path, $cmd, $param);
|
||||
} else {
|
||||
foreach my $p (keys %$uri_param) {
|
||||
$param->{$p} = $uri_param->{$p};
|
||||
}
|
||||
foreach my $p (keys %$uri_param) {
|
||||
$param->{$p} = $uri_param->{$p};
|
||||
}
|
||||
|
||||
$data = $handler->handle($info, $param);
|
||||
$data = $handler->handle($info, $param);
|
||||
|
||||
# TODO: remove 'download' check with PVE 9.0
|
||||
if (
|
||||
ref($data) eq 'HASH'
|
||||
&& ref($data->{download}) eq 'HASH'
|
||||
&& ($info->{download_allowed} || $info->{download})
|
||||
) {
|
||||
$data = $handle_streamed_response->($data->{download})
|
||||
}
|
||||
# TODO: remove 'download' check with PVE 9.0
|
||||
if (
|
||||
ref($data) eq 'HASH'
|
||||
&& ref($data->{download}) eq 'HASH'
|
||||
&& ($info->{download_allowed} || $info->{download})
|
||||
) {
|
||||
$data = $handle_streamed_response->($data->{download});
|
||||
}
|
||||
}
|
||||
|
||||
return if $opt_nooutput || $stdopts->{quiet};
|
||||
|
|
@ -366,208 +373,226 @@ sub call_api_method {
|
|||
PVE::CLIFormatter::print_api_result($data, $info->{returns}, undef, $stdopts);
|
||||
}
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'ls',
|
||||
path => 'ls',
|
||||
method => 'GET',
|
||||
description => "List child objects on <api_path>.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => $cond_add_standard_output_properties->($path_properties),
|
||||
additionalProperties => 0,
|
||||
properties => $cond_add_standard_output_properties->($path_properties),
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $path = PVE::Tools::extract_param($param, 'api_path');
|
||||
my $path = PVE::Tools::extract_param($param, 'api_path');
|
||||
|
||||
my $stdopts = PVE::RESTHandler::extract_standard_output_properties($param);
|
||||
my $stdopts = PVE::RESTHandler::extract_standard_output_properties($param);
|
||||
|
||||
my $uri_param = {};
|
||||
my ($handler, $info) = PVE::API2->find_handler('GET', $path, $uri_param);
|
||||
if (!$handler || !$info) {
|
||||
die "no such resource '$path'\n";
|
||||
}
|
||||
my $uri_param = {};
|
||||
my ($handler, $info) = PVE::API2->find_handler('GET', $path, $uri_param);
|
||||
if (!$handler || !$info) {
|
||||
die "no such resource '$path'\n";
|
||||
}
|
||||
|
||||
my $link = PVE::JSONSchema::method_get_child_link($info);
|
||||
die "resource '$path' does not define child links\n" if !$link;
|
||||
my $link = PVE::JSONSchema::method_get_child_link($info);
|
||||
die "resource '$path' does not define child links\n" if !$link;
|
||||
|
||||
my $res;
|
||||
my $res;
|
||||
|
||||
my ($node, $remip) = check_proxyto($info, $uri_param, $param);
|
||||
if ($node) {
|
||||
$res = proxy_handler($node, $remip, $path, 'ls', $param);
|
||||
} else {
|
||||
foreach my $p (keys %$uri_param) {
|
||||
$param->{$p} = $uri_param->{$p};
|
||||
}
|
||||
my ($node, $remip) = check_proxyto($info, $uri_param, $param);
|
||||
if ($node) {
|
||||
$res = proxy_handler($node, $remip, $path, 'ls', $param);
|
||||
} else {
|
||||
foreach my $p (keys %$uri_param) {
|
||||
$param->{$p} = $uri_param->{$p};
|
||||
}
|
||||
|
||||
my $data = $handler->handle($info, $param);
|
||||
my $data = $handler->handle($info, $param);
|
||||
|
||||
my $children = extract_children($link, $data);
|
||||
my $children = extract_children($link, $data);
|
||||
|
||||
$res = [];
|
||||
foreach my $c (@$children) {
|
||||
my $item = { name => $c, capabilities => resource_cap("$path/$c")};
|
||||
push @$res, $item;
|
||||
}
|
||||
}
|
||||
$res = [];
|
||||
foreach my $c (@$children) {
|
||||
my $item = { name => $c, capabilities => resource_cap("$path/$c") };
|
||||
push @$res, $item;
|
||||
}
|
||||
}
|
||||
|
||||
my $schema = { type => 'array', items => { type => 'object' }};
|
||||
$stdopts->{sort_key} = 'name';
|
||||
$stdopts->{noborder} //= 1;
|
||||
$stdopts->{noheader} //= 1;
|
||||
PVE::CLIFormatter::print_api_result($res, $schema, ['capabilities', 'name'], $stdopts);
|
||||
my $schema = { type => 'array', items => { type => 'object' } };
|
||||
$stdopts->{sort_key} = 'name';
|
||||
$stdopts->{noborder} //= 1;
|
||||
$stdopts->{noheader} //= 1;
|
||||
PVE::CLIFormatter::print_api_result($res, $schema, ['capabilities', 'name'], $stdopts);
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'get',
|
||||
path => 'get',
|
||||
method => 'GET',
|
||||
description => "Call API GET on <api_path>.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => $cond_add_standard_output_properties->($path_properties),
|
||||
additionalProperties => 0,
|
||||
properties => $cond_add_standard_output_properties->($path_properties),
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
call_api_method('get', $param);
|
||||
call_api_method('get', $param);
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'set',
|
||||
path => 'set',
|
||||
method => 'PUT',
|
||||
description => "Call API PUT on <api_path>.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => $cond_add_standard_output_properties->($path_properties),
|
||||
additionalProperties => 0,
|
||||
properties => $cond_add_standard_output_properties->($path_properties),
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
call_api_method('set', $param);
|
||||
call_api_method('set', $param);
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'create',
|
||||
path => 'create',
|
||||
method => 'POST',
|
||||
description => "Call API POST on <api_path>.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => $cond_add_standard_output_properties->($path_properties),
|
||||
additionalProperties => 0,
|
||||
properties => $cond_add_standard_output_properties->($path_properties),
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
call_api_method('create', $param);
|
||||
call_api_method('create', $param);
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'delete',
|
||||
path => 'delete',
|
||||
method => 'DELETE',
|
||||
description => "Call API DELETE on <api_path>.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => $cond_add_standard_output_properties->($path_properties),
|
||||
additionalProperties => 0,
|
||||
properties => $cond_add_standard_output_properties->($path_properties),
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
call_api_method('delete', $param);
|
||||
call_api_method('delete', $param);
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'usage',
|
||||
path => 'usage',
|
||||
method => 'GET',
|
||||
description => "print API usage information for <api_path>.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
api_path => $api_path_property,
|
||||
verbose => {
|
||||
description => "Verbose output format.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
returns => {
|
||||
description => "Including schema for returned data.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
command => {
|
||||
description => "API command.",
|
||||
type => 'string',
|
||||
enum => [ keys %$method_map ],
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
api_path => $api_path_property,
|
||||
verbose => {
|
||||
description => "Verbose output format.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
returns => {
|
||||
description => "Including schema for returned data.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
command => {
|
||||
description => "API command.",
|
||||
type => 'string',
|
||||
enum => [keys %$method_map],
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $path = $param->{api_path};
|
||||
my $path = $param->{api_path};
|
||||
|
||||
my $found = 0;
|
||||
foreach my $cmd (qw(get set create delete)) {
|
||||
next if $param->{command} && $cmd ne $param->{command};
|
||||
my $method = $method_map->{$cmd};
|
||||
my $uri_param = {};
|
||||
my ($handler, $info) = PVE::API2->find_handler($method, $path, $uri_param);
|
||||
next if !$handler;
|
||||
$found = 1;
|
||||
my $found = 0;
|
||||
foreach my $cmd (qw(get set create delete)) {
|
||||
next if $param->{command} && $cmd ne $param->{command};
|
||||
my $method = $method_map->{$cmd};
|
||||
my $uri_param = {};
|
||||
my ($handler, $info) = PVE::API2->find_handler($method, $path, $uri_param);
|
||||
next if !$handler;
|
||||
$found = 1;
|
||||
|
||||
if ($param->{verbose}) {
|
||||
print $handler->usage_str(
|
||||
$info->{name}, "pvesh $cmd $path", undef, $uri_param, 'full');
|
||||
if ($param->{verbose}) {
|
||||
print $handler->usage_str(
|
||||
$info->{name},
|
||||
"pvesh $cmd $path",
|
||||
undef,
|
||||
$uri_param,
|
||||
'full',
|
||||
);
|
||||
|
||||
} else {
|
||||
print "USAGE: " . $handler->usage_str(
|
||||
$info->{name}, "pvesh $cmd $path", undef, $uri_param, 'short');
|
||||
}
|
||||
if ($param-> {returns}) {
|
||||
my $schema = to_json($info->{returns}, {utf8 => 1, canonical => 1, pretty => 1 });
|
||||
print "RETURNS: $schema\n";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print "USAGE: "
|
||||
. $handler->usage_str(
|
||||
$info->{name},
|
||||
"pvesh $cmd $path",
|
||||
undef,
|
||||
$uri_param,
|
||||
'short',
|
||||
);
|
||||
}
|
||||
if ($param->{returns}) {
|
||||
my $schema =
|
||||
to_json($info->{returns}, { utf8 => 1, canonical => 1, pretty => 1 });
|
||||
print "RETURNS: $schema\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
if ($param->{command}) {
|
||||
die "no '$param->{command}' handler for '$path'\n";
|
||||
} else {
|
||||
die "no such resource '$path'\n"
|
||||
}
|
||||
}
|
||||
if (!$found) {
|
||||
if ($param->{command}) {
|
||||
die "no '$param->{command}' handler for '$path'\n";
|
||||
} else {
|
||||
die "no such resource '$path'\n";
|
||||
}
|
||||
}
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
our $cmddef = {
|
||||
usage => [ __PACKAGE__, 'usage', ['api_path']],
|
||||
get => [ __PACKAGE__, 'get', ['api_path']],
|
||||
ls => [ __PACKAGE__, 'ls', ['api_path']],
|
||||
set => [ __PACKAGE__, 'set', ['api_path']],
|
||||
create => [ __PACKAGE__, 'create', ['api_path']],
|
||||
delete => [ __PACKAGE__, 'delete', ['api_path']],
|
||||
usage => [__PACKAGE__, 'usage', ['api_path']],
|
||||
get => [__PACKAGE__, 'get', ['api_path']],
|
||||
ls => [__PACKAGE__, 'ls', ['api_path']],
|
||||
set => [__PACKAGE__, 'set', ['api_path']],
|
||||
create => [__PACKAGE__, 'create', ['api_path']],
|
||||
delete => [__PACKAGE__, 'delete', ['api_path']],
|
||||
};
|
||||
|
||||
1;
|
||||
|
|
|
|||
522
PVE/CLI/pvesr.pm
522
PVE/CLI/pvesr.pm
|
|
@ -36,306 +36,329 @@ my $check_wanted_volid = sub {
|
|||
my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
|
||||
my $scfg = PVE::Storage::storage_check_enabled($storecfg, $storeid, $local_node);
|
||||
die "storage '$storeid' is not replicatable\n"
|
||||
if !$replicatable_storage_types->{$scfg->{type}};
|
||||
if !$replicatable_storage_types->{ $scfg->{type} };
|
||||
|
||||
my ($vtype, undef, $ownervm) = PVE::Storage::parse_volname($storecfg, $volid);
|
||||
die "volume '$volid' has wrong vtype ($vtype != 'images')\n"
|
||||
if $vtype ne 'images';
|
||||
if $vtype ne 'images';
|
||||
die "volume '$volid' has wrong owner\n"
|
||||
if !$ownervm || $vmid != $ownervm;
|
||||
if !$ownervm || $vmid != $ownervm;
|
||||
|
||||
return $storeid;
|
||||
};
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'prepare_local_job',
|
||||
path => 'prepare_local_job',
|
||||
method => 'POST',
|
||||
description => "Prepare for starting a replication job. This is called on the target node before replication starts. This call is for internal use, and return a JSON object on stdout. The method first test if VM <vmid> reside on the local node. If so, stop immediately. After that the method scans all volume IDs for snapshots, and removes all replications snapshots with timestamps different than <last_sync>. It also removes any unused volumes. Returns a hash with boolean markers for all volumes with existing replication snapshots.",
|
||||
description =>
|
||||
"Prepare for starting a replication job. This is called on the target node before replication starts. This call is for internal use, and return a JSON object on stdout. The method first test if VM <vmid> reside on the local node. If so, stop immediately. After that the method scans all volume IDs for snapshots, and removes all replications snapshots with timestamps different than <last_sync>. It also removes any unused volumes. Returns a hash with boolean markers for all volumes with existing replication snapshots.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
'extra-args' => get_standard_option('extra-args', {
|
||||
description => "The list of volume IDs to consider." }),
|
||||
scan => {
|
||||
description => "List of storage IDs to scan for stale volumes.",
|
||||
type => 'string', format => 'pve-storage-id-list',
|
||||
optional => 1,
|
||||
},
|
||||
force => {
|
||||
description => "Allow to remove all existion volumes (empty volume list).",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
last_sync => {
|
||||
description => "Time (UNIX epoch) of last successful sync. If not specified, all replication snapshots get removed.",
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
optional => 1,
|
||||
},
|
||||
parent_snapname => get_standard_option('pve-snapshot-name', {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
'extra-args' => get_standard_option(
|
||||
'extra-args',
|
||||
{ description => "The list of volume IDs to consider." },
|
||||
),
|
||||
scan => {
|
||||
description => "List of storage IDs to scan for stale volumes.",
|
||||
type => 'string',
|
||||
format => 'pve-storage-id-list',
|
||||
optional => 1,
|
||||
},
|
||||
force => {
|
||||
description => "Allow to remove all existion volumes (empty volume list).",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 0,
|
||||
},
|
||||
last_sync => {
|
||||
description =>
|
||||
"Time (UNIX epoch) of last successful sync. If not specified, all replication snapshots get removed.",
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
optional => 1,
|
||||
},
|
||||
parent_snapname => get_standard_option('pve-snapshot-name', {
|
||||
optional => 1,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $logfunc = sub {
|
||||
my ($msg) = @_;
|
||||
print STDERR "$msg\n";
|
||||
};
|
||||
my $logfunc = sub {
|
||||
my ($msg) = @_;
|
||||
print STDERR "$msg\n";
|
||||
};
|
||||
|
||||
my $local_node = PVE::INotify::nodename();
|
||||
my $local_node = PVE::INotify::nodename();
|
||||
|
||||
die "no volumes specified\n"
|
||||
if !$param->{force} && !scalar(@{$param->{'extra-args'}});
|
||||
die "no volumes specified\n"
|
||||
if !$param->{force} && !scalar(@{ $param->{'extra-args'} });
|
||||
|
||||
my ($vmid, undef, $jobid) = PVE::ReplicationConfig::parse_replication_job_id($param->{id});
|
||||
my ($vmid, undef, $jobid) =
|
||||
PVE::ReplicationConfig::parse_replication_job_id($param->{id});
|
||||
|
||||
my $vms = PVE::Cluster::get_vmlist();
|
||||
die "guest '$vmid' is on local node\n"
|
||||
if $vms->{ids}->{$vmid} && $vms->{ids}->{$vmid}->{node} eq $local_node;
|
||||
my $vms = PVE::Cluster::get_vmlist();
|
||||
die "guest '$vmid' is on local node\n"
|
||||
if $vms->{ids}->{$vmid} && $vms->{ids}->{$vmid}->{node} eq $local_node;
|
||||
|
||||
my $last_sync = $param->{last_sync} // 0;
|
||||
my $parent_snapname = $param->{parent_snapname};
|
||||
my $last_sync = $param->{last_sync} // 0;
|
||||
my $parent_snapname = $param->{parent_snapname};
|
||||
|
||||
my $storecfg = PVE::Storage::config();
|
||||
my $storecfg = PVE::Storage::config();
|
||||
|
||||
# compute list of storages we want to scan
|
||||
my $storage_hash = {};
|
||||
foreach my $storeid (PVE::Tools::split_list($param->{scan})) {
|
||||
my $scfg = PVE::Storage::storage_check_enabled($storecfg, $storeid, $local_node, 1);
|
||||
next if !$scfg; # simply ignore unavailable storages here
|
||||
die "storage '$storeid' is not replicatable\n" if !$replicatable_storage_types->{$scfg->{type}};
|
||||
$storage_hash->{$storeid} = 1;
|
||||
}
|
||||
# compute list of storages we want to scan
|
||||
my $storage_hash = {};
|
||||
foreach my $storeid (PVE::Tools::split_list($param->{scan})) {
|
||||
my $scfg = PVE::Storage::storage_check_enabled($storecfg, $storeid, $local_node, 1);
|
||||
next if !$scfg; # simply ignore unavailable storages here
|
||||
die "storage '$storeid' is not replicatable\n"
|
||||
if !$replicatable_storage_types->{ $scfg->{type} };
|
||||
$storage_hash->{$storeid} = 1;
|
||||
}
|
||||
|
||||
my $wanted_volids = {};
|
||||
foreach my $volid (@{$param->{'extra-args'}}) {
|
||||
my $storeid = $check_wanted_volid->($storecfg, $vmid, $volid, $local_node);
|
||||
$wanted_volids->{$volid} = 1;
|
||||
$storage_hash->{$storeid} = 1;
|
||||
}
|
||||
my $storage_list = [ sort keys %$storage_hash ];
|
||||
my $wanted_volids = {};
|
||||
foreach my $volid (@{ $param->{'extra-args'} }) {
|
||||
my $storeid = $check_wanted_volid->($storecfg, $vmid, $volid, $local_node);
|
||||
$wanted_volids->{$volid} = 1;
|
||||
$storage_hash->{$storeid} = 1;
|
||||
}
|
||||
my $storage_list = [sort keys %$storage_hash];
|
||||
|
||||
# activate all used storage
|
||||
my $cache = {};
|
||||
PVE::Storage::activate_storage_list($storecfg, $storage_list, $cache);
|
||||
# activate all used storage
|
||||
my $cache = {};
|
||||
PVE::Storage::activate_storage_list($storecfg, $storage_list, $cache);
|
||||
|
||||
my $snapname = PVE::ReplicationState::replication_snapshot_name($jobid, $last_sync);
|
||||
my $snapname = PVE::ReplicationState::replication_snapshot_name($jobid, $last_sync);
|
||||
|
||||
# find replication snapshots
|
||||
my $volids = [];
|
||||
foreach my $storeid (@$storage_list) {
|
||||
my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
|
||||
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
||||
my $images = $plugin->list_images($storeid, $scfg, $vmid, undef, $cache);
|
||||
push @$volids, map { $_->{volid} } @$images;
|
||||
}
|
||||
my ($local_snapshots, $cleaned_replicated_volumes) = PVE::Replication::prepare($storecfg, $volids, $jobid, $last_sync, $parent_snapname, $logfunc);
|
||||
for my $volid ($volids->@*) {
|
||||
next if $wanted_volids->{$volid};
|
||||
# find replication snapshots
|
||||
my $volids = [];
|
||||
foreach my $storeid (@$storage_list) {
|
||||
my $scfg = PVE::Storage::storage_config($storecfg, $storeid);
|
||||
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
||||
my $images = $plugin->list_images($storeid, $scfg, $vmid, undef, $cache);
|
||||
push @$volids, map { $_->{volid} } @$images;
|
||||
}
|
||||
my ($local_snapshots, $cleaned_replicated_volumes) = PVE::Replication::prepare(
|
||||
$storecfg, $volids, $jobid, $last_sync, $parent_snapname, $logfunc,
|
||||
);
|
||||
for my $volid ($volids->@*) {
|
||||
next if $wanted_volids->{$volid};
|
||||
|
||||
my $stale = $cleaned_replicated_volumes->{$volid};
|
||||
# prepare() will not remove the last_sync snapshot, but if the volume was used by the
|
||||
# job and is not wanted anymore, it is stale too. And not removing it now might cause
|
||||
# it to be missed later, because the relevant storage might not get scanned anymore.
|
||||
$stale ||= grep {
|
||||
PVE::Replication::is_replication_snapshot($_, $jobid)
|
||||
} keys %{$local_snapshots->{$volid} // {}};
|
||||
my $stale = $cleaned_replicated_volumes->{$volid};
|
||||
# prepare() will not remove the last_sync snapshot, but if the volume was used by the
|
||||
# job and is not wanted anymore, it is stale too. And not removing it now might cause
|
||||
# it to be missed later, because the relevant storage might not get scanned anymore.
|
||||
$stale ||= grep {
|
||||
PVE::Replication::is_replication_snapshot($_, $jobid)
|
||||
} keys %{ $local_snapshots->{$volid} // {} };
|
||||
|
||||
if ($stale) {
|
||||
$logfunc->("$jobid: delete stale volume '$volid'");
|
||||
PVE::Storage::vdisk_free($storecfg, $volid);
|
||||
delete $local_snapshots->{$volid};
|
||||
}
|
||||
}
|
||||
if ($stale) {
|
||||
$logfunc->("$jobid: delete stale volume '$volid'");
|
||||
PVE::Storage::vdisk_free($storecfg, $volid);
|
||||
delete $local_snapshots->{$volid};
|
||||
}
|
||||
}
|
||||
|
||||
print to_json($local_snapshots) . "\n";
|
||||
print to_json($local_snapshots) . "\n";
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'finalize_local_job',
|
||||
path => 'finalize_local_job',
|
||||
method => 'POST',
|
||||
description => "Finalize a replication job. This removes all replications snapshots with timestamps different than <last_sync>.",
|
||||
description =>
|
||||
"Finalize a replication job. This removes all replications snapshots with timestamps different than <last_sync>.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
'extra-args' => get_standard_option('extra-args', {
|
||||
description => "The list of volume IDs to consider." }),
|
||||
last_sync => {
|
||||
description => "Time (UNIX epoch) of last successful sync. If not specified, all replication snapshots gets removed.",
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
'extra-args' => get_standard_option(
|
||||
'extra-args',
|
||||
{ description => "The list of volume IDs to consider." },
|
||||
),
|
||||
last_sync => {
|
||||
description =>
|
||||
"Time (UNIX epoch) of last successful sync. If not specified, all replication snapshots gets removed.",
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my ($vmid, undef, $jobid) = PVE::ReplicationConfig::parse_replication_job_id($param->{id});
|
||||
my $last_sync = $param->{last_sync} // 0;
|
||||
my ($vmid, undef, $jobid) =
|
||||
PVE::ReplicationConfig::parse_replication_job_id($param->{id});
|
||||
my $last_sync = $param->{last_sync} // 0;
|
||||
|
||||
my $local_node = PVE::INotify::nodename();
|
||||
my $local_node = PVE::INotify::nodename();
|
||||
|
||||
my $vms = PVE::Cluster::get_vmlist();
|
||||
die "guest '$vmid' is on local node\n"
|
||||
if $vms->{ids}->{$vmid} && $vms->{ids}->{$vmid}->{node} eq $local_node;
|
||||
my $vms = PVE::Cluster::get_vmlist();
|
||||
die "guest '$vmid' is on local node\n"
|
||||
if $vms->{ids}->{$vmid} && $vms->{ids}->{$vmid}->{node} eq $local_node;
|
||||
|
||||
my $storecfg = PVE::Storage::config();
|
||||
my $storecfg = PVE::Storage::config();
|
||||
|
||||
my $volids = [];
|
||||
my $volids = [];
|
||||
|
||||
die "no volumes specified\n" if !scalar(@{$param->{'extra-args'}});
|
||||
die "no volumes specified\n" if !scalar(@{ $param->{'extra-args'} });
|
||||
|
||||
foreach my $volid (@{$param->{'extra-args'}}) {
|
||||
$check_wanted_volid->($storecfg, $vmid, $volid, $local_node);
|
||||
push @$volids, $volid;
|
||||
}
|
||||
foreach my $volid (@{ $param->{'extra-args'} }) {
|
||||
$check_wanted_volid->($storecfg, $vmid, $volid, $local_node);
|
||||
push @$volids, $volid;
|
||||
}
|
||||
|
||||
$volids = [ sort @$volids ];
|
||||
$volids = [sort @$volids];
|
||||
|
||||
my $logfunc = sub {
|
||||
my ($msg) = @_;
|
||||
print STDERR "$msg\n";
|
||||
};
|
||||
my $logfunc = sub {
|
||||
my ($msg) = @_;
|
||||
print STDERR "$msg\n";
|
||||
};
|
||||
|
||||
PVE::Replication::prepare($storecfg, $volids, $jobid, $last_sync, undef, $logfunc);
|
||||
PVE::Replication::prepare($storecfg, $volids, $jobid, $last_sync, undef, $logfunc);
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'run',
|
||||
path => 'run',
|
||||
method => 'POST',
|
||||
description => "This method is called by the systemd-timer and executes all (or a specific) sync jobs.",
|
||||
description =>
|
||||
"This method is called by the systemd-timer and executes all (or a specific) sync jobs.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id', { optional => 1 }),
|
||||
verbose => {
|
||||
description => "Print more verbose logs to stdout.",
|
||||
type => 'boolean',
|
||||
default => 0,
|
||||
optional => 1,
|
||||
},
|
||||
mail => {
|
||||
description => "Send an email notification in case of a failure.",
|
||||
type => 'boolean',
|
||||
default => 0,
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id', { optional => 1 }),
|
||||
verbose => {
|
||||
description => "Print more verbose logs to stdout.",
|
||||
type => 'boolean',
|
||||
default => 0,
|
||||
optional => 1,
|
||||
},
|
||||
mail => {
|
||||
description => "Send an email notification in case of a failure.",
|
||||
type => 'boolean',
|
||||
default => 0,
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
die "Mail and id are mutually exclusive!\n"
|
||||
if $param->{id} && $param->{mail};
|
||||
die "Mail and id are mutually exclusive!\n"
|
||||
if $param->{id} && $param->{mail};
|
||||
|
||||
my $logfunc;
|
||||
my $logfunc;
|
||||
|
||||
if ($param->{verbose}) {
|
||||
$logfunc = sub {
|
||||
my ($msg) = @_;
|
||||
print "$msg\n";
|
||||
};
|
||||
}
|
||||
if ($param->{verbose}) {
|
||||
$logfunc = sub {
|
||||
my ($msg) = @_;
|
||||
print "$msg\n";
|
||||
};
|
||||
}
|
||||
|
||||
if (my $id = extract_param($param, 'id')) {
|
||||
if (my $id = extract_param($param, 'id')) {
|
||||
|
||||
PVE::API2::Replication::run_single_job($id, undef, $logfunc);
|
||||
PVE::API2::Replication::run_single_job($id, undef, $logfunc);
|
||||
|
||||
} else {
|
||||
} else {
|
||||
|
||||
PVE::API2::Replication::run_jobs(undef, $logfunc, 0, $param->{mail});
|
||||
}
|
||||
PVE::API2::Replication::run_jobs(undef, $logfunc, 0, $param->{mail});
|
||||
}
|
||||
|
||||
return undef;
|
||||
}});
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'enable',
|
||||
path => 'enable',
|
||||
method => 'POST',
|
||||
description => "Enable a replication job.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
$param->{disable} = 0;
|
||||
$param->{disable} = 0;
|
||||
|
||||
return PVE::API2::ReplicationConfig->update($param);
|
||||
}});
|
||||
return PVE::API2::ReplicationConfig->update($param);
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'disable',
|
||||
path => 'disable',
|
||||
method => 'POST',
|
||||
description => "Disable a replication job.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
id => get_standard_option('pve-replication-id'),
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
$param->{disable} = 1;
|
||||
$param->{disable} = 1;
|
||||
|
||||
return PVE::API2::ReplicationConfig->update($param);
|
||||
}});
|
||||
return PVE::API2::ReplicationConfig->update($param);
|
||||
},
|
||||
});
|
||||
|
||||
__PACKAGE__->register_method ({
|
||||
__PACKAGE__->register_method({
|
||||
name => 'set_state',
|
||||
path => '',
|
||||
protected => 1,
|
||||
method => 'POST',
|
||||
description => "Set the job replication state on migration. This call is for internal use. It will accept the job state as ja JSON obj.",
|
||||
description =>
|
||||
"Set the job replication state on migration. This call is for internal use. It will accept the job state as ja JSON obj.",
|
||||
permissions => {
|
||||
check => ['perm', '/storage', ['Datastore.Allocate']],
|
||||
check => ['perm', '/storage', ['Datastore.Allocate']],
|
||||
},
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_vmid }),
|
||||
state => {
|
||||
description => "Job state as JSON decoded string.",
|
||||
type => 'string',
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
vmid =>
|
||||
get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_vmid }),
|
||||
state => {
|
||||
description => "Job state as JSON decoded string.",
|
||||
type => 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $vmid = extract_param($param, 'vmid');
|
||||
my $json_string = extract_param($param, 'state');
|
||||
my $remote_job_state= decode_json $json_string;
|
||||
my $vmid = extract_param($param, 'vmid');
|
||||
my $json_string = extract_param($param, 'state');
|
||||
my $remote_job_state = decode_json $json_string;
|
||||
|
||||
PVE::ReplicationState::write_vmid_job_states($remote_job_state, $vmid);
|
||||
return undef;
|
||||
}});
|
||||
PVE::ReplicationState::write_vmid_job_states($remote_job_state, $vmid);
|
||||
return undef;
|
||||
},
|
||||
});
|
||||
|
||||
my $print_job_list = sub {
|
||||
my ($list) = @_;
|
||||
|
|
@ -345,14 +368,16 @@ my $print_job_list = sub {
|
|||
printf($format, "JobID", "Target", "Schedule", "Rate", "Enabled");
|
||||
|
||||
foreach my $job (sort { $a->{guest} <=> $b->{guest} } @$list) {
|
||||
my $plugin = PVE::ReplicationConfig->lookup($job->{type});
|
||||
my $tid = $plugin->get_unique_target_id($job);
|
||||
my $plugin = PVE::ReplicationConfig->lookup($job->{type});
|
||||
my $tid = $plugin->get_unique_target_id($job);
|
||||
|
||||
printf($format, $job->{id}, $tid,
|
||||
defined($job->{schedule}) ? $job->{schedule} : '*/15',
|
||||
defined($job->{rate}) ? $job->{rate} : '-',
|
||||
$job->{disable} ? 'no' : 'yes'
|
||||
);
|
||||
printf(
|
||||
$format,
|
||||
$job->{id}, $tid,
|
||||
defined($job->{schedule}) ? $job->{schedule} : '*/15',
|
||||
defined($job->{rate}) ? $job->{rate} : '-',
|
||||
$job->{disable} ? 'no' : 'yes',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -361,56 +386,73 @@ my $print_job_status = sub {
|
|||
|
||||
my $format = "%-10s %-10s %-20s %20s %20s %10s %10s %s\n";
|
||||
|
||||
printf($format, "JobID", "Enabled", "Target", "LastSync", "NextSync", "Duration", "FailCount", "State");
|
||||
printf($format,
|
||||
"JobID",
|
||||
"Enabled",
|
||||
"Target",
|
||||
"LastSync",
|
||||
"NextSync",
|
||||
"Duration",
|
||||
"FailCount",
|
||||
"State");
|
||||
|
||||
foreach my $job (sort { $a->{guest} <=> $b->{guest} } @$list) {
|
||||
my $plugin = PVE::ReplicationConfig->lookup($job->{type});
|
||||
my $tid = $plugin->get_unique_target_id($job);
|
||||
my $plugin = PVE::ReplicationConfig->lookup($job->{type});
|
||||
my $tid = $plugin->get_unique_target_id($job);
|
||||
|
||||
my $timestr = '-';
|
||||
if ($job->{last_sync}) {
|
||||
$timestr = strftime("%Y-%m-%d_%H:%M:%S", localtime($job->{last_sync}));
|
||||
}
|
||||
my $timestr = '-';
|
||||
if ($job->{last_sync}) {
|
||||
$timestr = strftime("%Y-%m-%d_%H:%M:%S", localtime($job->{last_sync}));
|
||||
}
|
||||
|
||||
my $nextstr = '-';
|
||||
if (my $next = $job->{next_sync}) {
|
||||
my $now = time();
|
||||
if ($next > $now) {
|
||||
$nextstr = strftime("%Y-%m-%d_%H:%M:%S", localtime($job->{next_sync}));
|
||||
} else {
|
||||
$nextstr = 'pending';
|
||||
}
|
||||
}
|
||||
my $nextstr = '-';
|
||||
if (my $next = $job->{next_sync}) {
|
||||
my $now = time();
|
||||
if ($next > $now) {
|
||||
$nextstr = strftime("%Y-%m-%d_%H:%M:%S", localtime($job->{next_sync}));
|
||||
} else {
|
||||
$nextstr = 'pending';
|
||||
}
|
||||
}
|
||||
|
||||
my $state = $job->{pid} ? "SYNCING" : $job->{error} // 'OK';
|
||||
my $enabled = $job->{disable} ? 'No' : 'Yes';
|
||||
my $state = $job->{pid} ? "SYNCING" : $job->{error} // 'OK';
|
||||
my $enabled = $job->{disable} ? 'No' : 'Yes';
|
||||
|
||||
printf($format, $job->{id}, $enabled, $tid,
|
||||
$timestr, $nextstr, $job->{duration} // '-',
|
||||
$job->{fail_count}, $state);
|
||||
printf($format,
|
||||
$job->{id}, $enabled, $tid, $timestr, $nextstr, $job->{duration} // '-',
|
||||
$job->{fail_count}, $state);
|
||||
}
|
||||
};
|
||||
|
||||
our $cmddef = {
|
||||
status => [ 'PVE::API2::Replication', 'status', [], { node => $nodename }, $print_job_status ],
|
||||
'schedule-now' => [ 'PVE::API2::Replication', 'schedule_now', ['id'], { node => $nodename }],
|
||||
status =>
|
||||
['PVE::API2::Replication', 'status', [], { node => $nodename }, $print_job_status],
|
||||
'schedule-now' => ['PVE::API2::Replication', 'schedule_now', ['id'], { node => $nodename }],
|
||||
|
||||
list => [ 'PVE::API2::ReplicationConfig', 'index' , [], {}, $print_job_list ],
|
||||
read => [ 'PVE::API2::ReplicationConfig', 'read' , ['id'], {},
|
||||
sub { my $res = shift; print to_json($res, { utf8 => 1, pretty => 1, canonical => 1}); }],
|
||||
update => [ 'PVE::API2::ReplicationConfig', 'update' , ['id'], {} ],
|
||||
delete => [ 'PVE::API2::ReplicationConfig', 'delete' , ['id'], {} ],
|
||||
'create-local-job' => [ 'PVE::API2::ReplicationConfig', 'create' , ['id', 'target'],
|
||||
{ type => 'local' } ],
|
||||
list => ['PVE::API2::ReplicationConfig', 'index', [], {}, $print_job_list],
|
||||
read => [
|
||||
'PVE::API2::ReplicationConfig',
|
||||
'read',
|
||||
['id'],
|
||||
{},
|
||||
sub {
|
||||
my $res = shift;
|
||||
print to_json($res, { utf8 => 1, pretty => 1, canonical => 1 });
|
||||
},
|
||||
],
|
||||
update => ['PVE::API2::ReplicationConfig', 'update', ['id'], {}],
|
||||
delete => ['PVE::API2::ReplicationConfig', 'delete', ['id'], {}],
|
||||
'create-local-job' =>
|
||||
['PVE::API2::ReplicationConfig', 'create', ['id', 'target'], { type => 'local' }],
|
||||
|
||||
enable => [ __PACKAGE__, 'enable', ['id'], {}],
|
||||
disable => [ __PACKAGE__, 'disable', ['id'], {}],
|
||||
enable => [__PACKAGE__, 'enable', ['id'], {}],
|
||||
disable => [__PACKAGE__, 'disable', ['id'], {}],
|
||||
|
||||
'prepare-local-job' => [ __PACKAGE__, 'prepare_local_job', ['id', 'extra-args'], {} ],
|
||||
'finalize-local-job' => [ __PACKAGE__, 'finalize_local_job', ['id', 'extra-args'], {} ],
|
||||
'prepare-local-job' => [__PACKAGE__, 'prepare_local_job', ['id', 'extra-args'], {}],
|
||||
'finalize-local-job' => [__PACKAGE__, 'finalize_local_job', ['id', 'extra-args'], {}],
|
||||
|
||||
run => [ __PACKAGE__ , 'run'],
|
||||
'set-state' => [ __PACKAGE__ , 'set_state', ['vmid', 'state']],
|
||||
run => [__PACKAGE__, 'run'],
|
||||
'set-state' => [__PACKAGE__, 'set_state', ['vmid', 'state']],
|
||||
};
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -25,42 +25,51 @@ __PACKAGE__->register_method({
|
|||
name => 'set_offline_key',
|
||||
path => 'set_offline_key',
|
||||
method => 'POST',
|
||||
description => "Internal use only! To set an offline key, use the package proxmox-offline-mirror-helper instead.",
|
||||
description =>
|
||||
"Internal use only! To set an offline key, use the package proxmox-offline-mirror-helper instead.",
|
||||
parameters => {
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
data => {
|
||||
description => "A signed subscription info blob",
|
||||
type => "string",
|
||||
},
|
||||
},
|
||||
additionalProperties => 0,
|
||||
properties => {
|
||||
data => {
|
||||
description => "A signed subscription info blob",
|
||||
type => "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
returns => { type => 'null' },
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
my ($param) = @_;
|
||||
|
||||
my $info = decode_json(decode_base64($param->{data}));
|
||||
my $info = decode_json(decode_base64($param->{data}));
|
||||
|
||||
$info = Proxmox::RS::Subscription::check_signature($info);
|
||||
$info = Proxmox::RS::Subscription::check_server_id($info);
|
||||
$info = Proxmox::RS::Subscription::check_age($info, 0);
|
||||
$info = Proxmox::RS::Subscription::check_signature($info);
|
||||
$info = Proxmox::RS::Subscription::check_server_id($info);
|
||||
$info = Proxmox::RS::Subscription::check_age($info, 0);
|
||||
|
||||
PVE::API2::Subscription::check_key($info->{key}, PVE::API2::Subscription::get_sockets());
|
||||
PVE::API2::Subscription::check_key($info->{key},
|
||||
PVE::API2::Subscription::get_sockets());
|
||||
|
||||
PVE::API2::Subscription::write_etc_subscription($info);
|
||||
PVE::API2::Subscription::write_etc_subscription($info);
|
||||
|
||||
return;
|
||||
}});
|
||||
return;
|
||||
},
|
||||
});
|
||||
|
||||
our $cmddef = {
|
||||
update => [ 'PVE::API2::Subscription', 'update', undef, { node => $nodename } ],
|
||||
get => [ 'PVE::API2::Subscription', 'get', undef, { node => $nodename }, sub {
|
||||
my $info = shift;
|
||||
print "$_: $info->{$_}\n" for sort keys %$info;
|
||||
}],
|
||||
set => [ 'PVE::API2::Subscription', 'set', ['key'], { node => $nodename } ],
|
||||
"set-offline-key" => [ __PACKAGE__, 'set_offline_key', ['data'] ],
|
||||
delete => [ 'PVE::API2::Subscription', 'delete', undef, { node => $nodename } ],
|
||||
update => ['PVE::API2::Subscription', 'update', undef, { node => $nodename }],
|
||||
get => [
|
||||
'PVE::API2::Subscription',
|
||||
'get',
|
||||
undef,
|
||||
{ node => $nodename },
|
||||
sub {
|
||||
my $info = shift;
|
||||
print "$_: $info->{$_}\n" for sort keys %$info;
|
||||
},
|
||||
],
|
||||
set => ['PVE::API2::Subscription', 'set', ['key'], { node => $nodename }],
|
||||
"set-offline-key" => [__PACKAGE__, 'set_offline_key', ['data']],
|
||||
delete => ['PVE::API2::Subscription', 'delete', undef, { node => $nodename }],
|
||||
};
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -14,12 +14,17 @@ sub setup_environment {
|
|||
}
|
||||
|
||||
# Note: use string 'vmid' as $arg_param option, to allow vmid lists
|
||||
our $cmddef = [ 'PVE::API2::VZDump', 'vzdump', 'vmid', undef,
|
||||
sub {
|
||||
my $upid = shift;
|
||||
exit(0) if $upid eq 'OK';
|
||||
my $status = PVE::Tools::upid_read_status($upid);
|
||||
exit(PVE::Tools::upid_status_is_error($status) ? -1 : 0);
|
||||
}];
|
||||
our $cmddef = [
|
||||
'PVE::API2::VZDump',
|
||||
'vzdump',
|
||||
'vmid',
|
||||
undef,
|
||||
sub {
|
||||
my $upid = shift;
|
||||
exit(0) if $upid eq 'OK';
|
||||
my $status = PVE::Tools::upid_read_status($upid);
|
||||
exit(PVE::Tools::upid_status_is_error($status) ? -1 : 0);
|
||||
},
|
||||
];
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@ use v5.36;
|
|||
use PVE::pvecfg;
|
||||
|
||||
my sub get_current_pve_major_release {
|
||||
my $release_tuples = [ split(/\./, PVE::pvecfg::release()) ];
|
||||
my $release_tuples = [split(/\./, PVE::pvecfg::release())];
|
||||
return $release_tuples->[0];
|
||||
}
|
||||
|
||||
my $_ceph_release_info;
|
||||
my sub get_ceph_release_def {
|
||||
if (defined($_ceph_release_info)) {
|
||||
return $_ceph_release_info;
|
||||
return $_ceph_release_info;
|
||||
}
|
||||
|
||||
# Track all ceph releases (from a initial cut-off) with some metainfo.
|
||||
|
|
@ -34,56 +34,57 @@ my sub get_ceph_release_def {
|
|||
# NOTE: very old releases got left out from the list, but there's _no_ need to clean-up
|
||||
# periodically check https://docs.ceph.com/en/latest/releases/ for all past releases
|
||||
my $ceph_release_info = {
|
||||
octopus => {
|
||||
release => '15.2',
|
||||
'initial-upstream-release' => '2020-03-23',
|
||||
'estimated-end-of-upstream-support' => '2022-08-09',
|
||||
'available-for-pve-release' => {
|
||||
6 => 1,
|
||||
7 => 1,
|
||||
},
|
||||
},
|
||||
pacific => {
|
||||
release => '16.2',
|
||||
'initial-upstream-release' => '2021-03-31',
|
||||
'estimated-end-of-upstream-support' => '2024-03-04',
|
||||
'available-for-pve-release' => {
|
||||
7 => 1,
|
||||
},
|
||||
},
|
||||
quincy => {
|
||||
release => '17.2',
|
||||
'current-backend-default' => 1,
|
||||
'initial-upstream-release' => '2022-04-19',
|
||||
'estimated-end-of-upstream-support' => '2024-06-01',
|
||||
'available-for-pve-release' => {
|
||||
7 => 1,
|
||||
8 => 1,
|
||||
},
|
||||
},
|
||||
reef => {
|
||||
release => '18.2',
|
||||
'initial-upstream-release' => '2023-08-07',
|
||||
'estimated-end-of-upstream-support' => '2025-08-01',
|
||||
'available-for-pve-release' => {
|
||||
8 => 1,
|
||||
},
|
||||
},
|
||||
squid => {
|
||||
release => '19.2',
|
||||
'initial-upstream-release' => '2024-09-27',
|
||||
'estimated-end-of-upstream-support' => '2026-09-19',
|
||||
'available-for-pve-release' => {
|
||||
8 => 1,
|
||||
},
|
||||
},
|
||||
octopus => {
|
||||
release => '15.2',
|
||||
'initial-upstream-release' => '2020-03-23',
|
||||
'estimated-end-of-upstream-support' => '2022-08-09',
|
||||
'available-for-pve-release' => {
|
||||
6 => 1,
|
||||
7 => 1,
|
||||
},
|
||||
},
|
||||
pacific => {
|
||||
release => '16.2',
|
||||
'initial-upstream-release' => '2021-03-31',
|
||||
'estimated-end-of-upstream-support' => '2024-03-04',
|
||||
'available-for-pve-release' => {
|
||||
7 => 1,
|
||||
},
|
||||
},
|
||||
quincy => {
|
||||
release => '17.2',
|
||||
'current-backend-default' => 1,
|
||||
'initial-upstream-release' => '2022-04-19',
|
||||
'estimated-end-of-upstream-support' => '2024-06-01',
|
||||
'available-for-pve-release' => {
|
||||
7 => 1,
|
||||
8 => 1,
|
||||
},
|
||||
},
|
||||
reef => {
|
||||
release => '18.2',
|
||||
'initial-upstream-release' => '2023-08-07',
|
||||
'estimated-end-of-upstream-support' => '2025-08-01',
|
||||
'available-for-pve-release' => {
|
||||
8 => 1,
|
||||
},
|
||||
},
|
||||
squid => {
|
||||
release => '19.2',
|
||||
'initial-upstream-release' => '2024-09-27',
|
||||
'estimated-end-of-upstream-support' => '2026-09-19',
|
||||
'available-for-pve-release' => {
|
||||
8 => 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
my $current_pve_major_release = get_current_pve_major_release();
|
||||
for my $codename (sort keys $ceph_release_info->%*) {
|
||||
my $ceph_release = $ceph_release_info->{$codename};
|
||||
$ceph_release->{codename} = $codename;
|
||||
$ceph_release->{'available-for-current-pve-release'} = $ceph_release->{'available-for-pve-release'}->{$current_pve_major_release};
|
||||
my $ceph_release = $ceph_release_info->{$codename};
|
||||
$ceph_release->{codename} = $codename;
|
||||
$ceph_release->{'available-for-current-pve-release'} =
|
||||
$ceph_release->{'available-for-pve-release'}->{$current_pve_major_release};
|
||||
}
|
||||
|
||||
$_ceph_release_info = $ceph_release_info;
|
||||
|
|
@ -95,15 +96,16 @@ sub get_ceph_release_info($codename) {
|
|||
}
|
||||
|
||||
my $_available_ceph_releases;
|
||||
|
||||
sub get_all_available_ceph_releases {
|
||||
if (!defined($_available_ceph_releases)) {
|
||||
my $ceph_releases = get_ceph_release_def();
|
||||
$_available_ceph_releases = {};
|
||||
for my $codename (sort keys $ceph_releases->%*) {
|
||||
if ($ceph_releases->{$codename}->{'available-for-current-pve-release'}) {
|
||||
$_available_ceph_releases->{$codename} = $ceph_releases->{$codename};
|
||||
}
|
||||
}
|
||||
my $ceph_releases = get_ceph_release_def();
|
||||
$_available_ceph_releases = {};
|
||||
for my $codename (sort keys $ceph_releases->%*) {
|
||||
if ($ceph_releases->{$codename}->{'available-for-current-pve-release'}) {
|
||||
$_available_ceph_releases->{$codename} = $ceph_releases->{$codename};
|
||||
}
|
||||
}
|
||||
}
|
||||
return $_available_ceph_releases;
|
||||
}
|
||||
|
|
@ -112,21 +114,22 @@ sub get_available_ceph_release_codenames($include_unstable_releases = 0) {
|
|||
my $available_releases = get_all_available_ceph_releases();
|
||||
|
||||
return $include_unstable_releases
|
||||
? [ sort keys $available_releases->%* ]
|
||||
: [ grep { !$available_releases->{$_}->{unsupported} } sort keys $available_releases->%* ]
|
||||
;
|
||||
? [sort keys $available_releases->%*]
|
||||
: [grep { !$available_releases->{$_}->{unsupported} } sort keys $available_releases->%*];
|
||||
}
|
||||
|
||||
my $_default_ceph_release_codename;
|
||||
|
||||
sub get_default_ceph_release_codename {
|
||||
if (!defined($_default_ceph_release_codename)) {
|
||||
my $ceph_releases = get_all_available_ceph_releases();
|
||||
my @default_release = grep { $ceph_releases->{$_}->{'current-backend-default'} } keys $ceph_releases->%*;
|
||||
die "internal error: got multiple ceph releases with 'current-backend-default' set\n"
|
||||
if scalar(@default_release) > 1;
|
||||
die "internal error: got no ceph releases with 'current-backend-default' set\n"
|
||||
if scalar(@default_release) < 1;
|
||||
$_default_ceph_release_codename = $default_release[0];
|
||||
my $ceph_releases = get_all_available_ceph_releases();
|
||||
my @default_release =
|
||||
grep { $ceph_releases->{$_}->{'current-backend-default'} } keys $ceph_releases->%*;
|
||||
die "internal error: got multiple ceph releases with 'current-backend-default' set\n"
|
||||
if scalar(@default_release) > 1;
|
||||
die "internal error: got no ceph releases with 'current-backend-default' set\n"
|
||||
if scalar(@default_release) < 1;
|
||||
$_default_ceph_release_codename = $default_release[0];
|
||||
}
|
||||
return $_default_ceph_release_codename;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,21 +19,29 @@ sub get_local_services {
|
|||
my $res = {};
|
||||
|
||||
for my $type (qw(mds mgr mon)) {
|
||||
$res->{$type} = {};
|
||||
$res->{$type} = {};
|
||||
|
||||
my $path = "/etc/systemd/system/ceph-$type.target.wants";
|
||||
my $regex = "ceph-$type\@(.*)\.service";
|
||||
PVE::Tools::dir_glob_foreach($path, $regex, sub {
|
||||
my (undef, $id) = @_;
|
||||
$res->{$type}->{$id}->{service} = 1;
|
||||
});
|
||||
my $path = "/etc/systemd/system/ceph-$type.target.wants";
|
||||
my $regex = "ceph-$type\@(.*)\.service";
|
||||
PVE::Tools::dir_glob_foreach(
|
||||
$path,
|
||||
$regex,
|
||||
sub {
|
||||
my (undef, $id) = @_;
|
||||
$res->{$type}->{$id}->{service} = 1;
|
||||
},
|
||||
);
|
||||
|
||||
$path = "/var/lib/ceph/$type";
|
||||
$regex = "([^-]+)-(.*)";
|
||||
PVE::Tools::dir_glob_foreach($path, $regex, sub {
|
||||
my (undef, $clustername, $id) = @_;
|
||||
$res->{$type}->{$id}->{direxists} = 1;
|
||||
});
|
||||
$path = "/var/lib/ceph/$type";
|
||||
$regex = "([^-]+)-(.*)";
|
||||
PVE::Tools::dir_glob_foreach(
|
||||
$path,
|
||||
$regex,
|
||||
sub {
|
||||
my (undef, $clustername, $id) = @_;
|
||||
$res->{$type}->{$id}->{direxists} = 1;
|
||||
},
|
||||
);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
|
@ -42,8 +50,8 @@ sub broadcast_ceph_services {
|
|||
my $services = get_local_services();
|
||||
|
||||
for my $type (keys %$services) {
|
||||
my $data = encode_json($services->{$type});
|
||||
PVE::Cluster::broadcast_node_kv("ceph-$type", $data);
|
||||
my $data = encode_json($services->{$type});
|
||||
PVE::Cluster::broadcast_node_kv("ceph-$type", $data);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -55,19 +63,23 @@ sub broadcast_ceph_versions {
|
|||
my $nodename = PVE::INotify::nodename();
|
||||
my $old_versions = PVE::Cluster::get_node_kv("ceph-versions", $nodename);
|
||||
if (length(my $old_version_raw = $old_versions->{$nodename})) {
|
||||
my $old = eval { decode_json($old_version_raw) };
|
||||
warn "failed to parse ceph-versions '$old_version_raw' as JSON - $@" if $@; # should not happen
|
||||
if (defined($old) && $old->{buildcommit} eq $buildcommit && $old->{version}->{str} eq $version) {
|
||||
return; # up to date, nothing to do so avoid (not exactly cheap) broadcast
|
||||
}
|
||||
my $old = eval { decode_json($old_version_raw) };
|
||||
warn "failed to parse ceph-versions '$old_version_raw' as JSON - $@" if $@; # should not happen
|
||||
if (
|
||||
defined($old)
|
||||
&& $old->{buildcommit} eq $buildcommit
|
||||
&& $old->{version}->{str} eq $version
|
||||
) {
|
||||
return; # up to date, nothing to do so avoid (not exactly cheap) broadcast
|
||||
}
|
||||
}
|
||||
|
||||
my $node_versions = {
|
||||
version => {
|
||||
str => $version,
|
||||
parts => $vers_parts,
|
||||
},
|
||||
buildcommit => $buildcommit,
|
||||
version => {
|
||||
str => $version,
|
||||
parts => $vers_parts,
|
||||
},
|
||||
buildcommit => $buildcommit,
|
||||
};
|
||||
PVE::Cluster::broadcast_node_kv("ceph-versions", encode_json($node_versions));
|
||||
}
|
||||
|
|
@ -76,9 +88,11 @@ sub get_ceph_versions {
|
|||
my $res;
|
||||
|
||||
if (defined(my $versions = PVE::Cluster::get_node_kv("ceph-versions"))) {
|
||||
$res = {
|
||||
map { eval { $_ => decode_json($versions->{$_}) } } keys %$versions
|
||||
};
|
||||
$res = {
|
||||
map {
|
||||
eval { $_ => decode_json($versions->{$_}) }
|
||||
} keys %$versions
|
||||
};
|
||||
}
|
||||
|
||||
return $res;
|
||||
|
|
@ -89,7 +103,9 @@ sub get_cluster_service {
|
|||
|
||||
my $raw = PVE::Cluster::get_node_kv("ceph-$type");
|
||||
my $res = {
|
||||
map { $_ => eval { decode_json($raw->{$_}) } } keys $raw->%*
|
||||
map {
|
||||
$_ => eval { decode_json($raw->{$_}) }
|
||||
} keys $raw->%*
|
||||
};
|
||||
|
||||
return $res;
|
||||
|
|
@ -99,9 +115,9 @@ sub ceph_service_cmd {
|
|||
my ($action, $service) = @_;
|
||||
|
||||
if ($service && $service =~ m/^(mon|osd|mds|mgr|radosgw)(\.(${\SERVICE_REGEX}))?$/) {
|
||||
$service = defined($3) ? "ceph-$1\@$3" : "ceph-$1.target";
|
||||
$service = defined($3) ? "ceph-$1\@$3" : "ceph-$1.target";
|
||||
} else {
|
||||
$service = "ceph.target";
|
||||
$service = "ceph.target";
|
||||
}
|
||||
|
||||
run_command(['/bin/systemctl', $action, $service]);
|
||||
|
|
@ -114,46 +130,46 @@ sub get_services_info {
|
|||
my $services = get_cluster_service($type);
|
||||
|
||||
foreach my $host (sort keys %$services) {
|
||||
foreach my $id (sort keys %{$services->{$host}}) {
|
||||
my $service = $result->{$id} = $services->{$host}->{$id};
|
||||
$service->{host} = $host;
|
||||
$service->{name} = $id;
|
||||
$service->{state} = 'unknown';
|
||||
if ($service->{service}) {
|
||||
$service->{state} = 'stopped';
|
||||
}
|
||||
}
|
||||
foreach my $id (sort keys %{ $services->{$host} }) {
|
||||
my $service = $result->{$id} = $services->{$host}->{$id};
|
||||
$service->{host} = $host;
|
||||
$service->{name} = $id;
|
||||
$service->{state} = 'unknown';
|
||||
if ($service->{service}) {
|
||||
$service->{state} = 'stopped';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$cfg) {
|
||||
$cfg = cfs_read_file('ceph.conf');
|
||||
$cfg = cfs_read_file('ceph.conf');
|
||||
}
|
||||
|
||||
foreach my $section (keys %$cfg) {
|
||||
my $d = $cfg->{$section};
|
||||
if ($section =~ m/^$type\.(\S+)$/) {
|
||||
my $id = $1;
|
||||
my $service = $result->{$id};
|
||||
my $addr = $d->{"${type}_addr"} // $d->{public_addr} // $d->{host};
|
||||
$service->{name} //= $id;
|
||||
$service->{addr} //= $addr;
|
||||
$service->{state} //= 'unknown';
|
||||
$service->{host} //= $d->{host};
|
||||
}
|
||||
my $d = $cfg->{$section};
|
||||
if ($section =~ m/^$type\.(\S+)$/) {
|
||||
my $id = $1;
|
||||
my $service = $result->{$id};
|
||||
my $addr = $d->{"${type}_addr"} // $d->{public_addr} // $d->{host};
|
||||
$service->{name} //= $id;
|
||||
$service->{addr} //= $addr;
|
||||
$service->{state} //= 'unknown';
|
||||
$service->{host} //= $d->{host};
|
||||
}
|
||||
}
|
||||
|
||||
if (!$rados) {
|
||||
return $result;
|
||||
return $result;
|
||||
}
|
||||
|
||||
my $metadata = $rados->mon_command({ prefix => "$type metadata" });
|
||||
foreach my $info (@$metadata) {
|
||||
my $id = $info->{name} // $info->{id};
|
||||
my $service = $result->{$id};
|
||||
$service->{ceph_version_short} = $info->{ceph_version_short};
|
||||
$service->{ceph_version} = $info->{ceph_version};
|
||||
$service->{host} //= $info->{hostname};
|
||||
$service->{addr} //= $info->{addr};
|
||||
my $id = $info->{name} // $info->{id};
|
||||
my $service = $result->{$id};
|
||||
$service->{ceph_version_short} = $info->{ceph_version_short};
|
||||
$service->{ceph_version} = $info->{ceph_version};
|
||||
$service->{host} //= $info->{hostname};
|
||||
$service->{addr} //= $info->{addr};
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
|
@ -166,10 +182,14 @@ sub list_local_mds_ids {
|
|||
my $ceph_mds_data_dir = PVE::Ceph::Tools::get_config('ceph_mds_data_dir');
|
||||
my $ccname = PVE::Ceph::Tools::get_config('ccname');
|
||||
|
||||
PVE::Tools::dir_glob_foreach($ceph_mds_data_dir, qr/$ccname-(\S+)/, sub {
|
||||
my (undef, $mds_id) = @_;
|
||||
push @$mds_list, $mds_id;
|
||||
});
|
||||
PVE::Tools::dir_glob_foreach(
|
||||
$ceph_mds_data_dir,
|
||||
qr/$ccname-(\S+)/,
|
||||
sub {
|
||||
my (undef, $mds_id) = @_;
|
||||
push @$mds_list, $mds_id;
|
||||
},
|
||||
);
|
||||
|
||||
return $mds_list;
|
||||
}
|
||||
|
|
@ -180,38 +200,37 @@ sub get_cluster_mds_state {
|
|||
my $mds_state = {};
|
||||
|
||||
if (!defined($rados)) {
|
||||
$rados = PVE::RADOS->new();
|
||||
$rados = PVE::RADOS->new();
|
||||
}
|
||||
|
||||
my $add_state = sub {
|
||||
my ($mds, $fsname) = @_;
|
||||
my ($mds, $fsname) = @_;
|
||||
|
||||
my $state = {};
|
||||
$state->{addr} = $mds->{addr};
|
||||
$state->{rank} = $mds->{rank};
|
||||
$state->{standby_replay} = $mds->{standby_replay} ? 1 : 0;
|
||||
$state->{state} = $mds->{state};
|
||||
$state->{fs_name} = $fsname;
|
||||
my $state = {};
|
||||
$state->{addr} = $mds->{addr};
|
||||
$state->{rank} = $mds->{rank};
|
||||
$state->{standby_replay} = $mds->{standby_replay} ? 1 : 0;
|
||||
$state->{state} = $mds->{state};
|
||||
$state->{fs_name} = $fsname;
|
||||
|
||||
$mds_state->{$mds->{name}} = $state;
|
||||
$mds_state->{ $mds->{name} } = $state;
|
||||
};
|
||||
|
||||
my $mds_dump = $rados->mon_command({ prefix => 'mds stat' });
|
||||
my $fsmap = $mds_dump->{fsmap};
|
||||
|
||||
|
||||
foreach my $mds (@{$fsmap->{standbys}}) {
|
||||
$add_state->($mds);
|
||||
foreach my $mds (@{ $fsmap->{standbys} }) {
|
||||
$add_state->($mds);
|
||||
}
|
||||
|
||||
for my $fs_info (@{$fsmap->{filesystems}}) {
|
||||
my $active_mds = $fs_info->{mdsmap}->{info};
|
||||
for my $fs_info (@{ $fsmap->{filesystems} }) {
|
||||
my $active_mds = $fs_info->{mdsmap}->{info};
|
||||
|
||||
# normally there's only one active MDS, but we can have multiple active for
|
||||
# different ranks (e.g., different cephs path hierarchy). So just add all.
|
||||
foreach my $mds (values %$active_mds) {
|
||||
$add_state->($mds, $fs_info->{mdsmap}->{fs_name});
|
||||
}
|
||||
# normally there's only one active MDS, but we can have multiple active for
|
||||
# different ranks (e.g., different cephs path hierarchy). So just add all.
|
||||
foreach my $mds (values %$active_mds) {
|
||||
$add_state->($mds, $fs_info->{mdsmap}->{fs_name});
|
||||
}
|
||||
}
|
||||
|
||||
return $mds_state;
|
||||
|
|
@ -221,22 +240,22 @@ sub is_mds_active {
|
|||
my ($rados, $fs_name) = @_;
|
||||
|
||||
if (!defined($rados)) {
|
||||
$rados = PVE::RADOS->new();
|
||||
$rados = PVE::RADOS->new();
|
||||
}
|
||||
|
||||
my $mds_dump = $rados->mon_command({ prefix => 'mds stat' });
|
||||
my $fsmap = $mds_dump->{fsmap}->{filesystems};
|
||||
|
||||
if (!($fsmap && scalar(@$fsmap) > 0)) {
|
||||
return undef;
|
||||
return undef;
|
||||
}
|
||||
for my $fs (@$fsmap) {
|
||||
next if defined($fs_name) && $fs->{mdsmap}->{fs_name} ne $fs_name;
|
||||
next if defined($fs_name) && $fs->{mdsmap}->{fs_name} ne $fs_name;
|
||||
|
||||
my $active_mds = $fs->{mdsmap}->{info};
|
||||
for my $mds (values %$active_mds) {
|
||||
return 1 if $mds->{state} eq 'up:active';
|
||||
}
|
||||
my $active_mds = $fs->{mdsmap}->{info};
|
||||
for my $mds (values %$active_mds) {
|
||||
return 1 if $mds->{state} eq 'up:active';
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -247,10 +266,10 @@ sub create_mds {
|
|||
|
||||
# `ceph fs status` fails with numeric only ID.
|
||||
die "ID: $id, numeric only IDs are not supported\n"
|
||||
if $id =~ /^\d+$/;
|
||||
if $id =~ /^\d+$/;
|
||||
|
||||
if (!defined($rados)) {
|
||||
$rados = PVE::RADOS->new();
|
||||
$rados = PVE::RADOS->new();
|
||||
}
|
||||
|
||||
my $ccname = PVE::Ceph::Tools::get_config('ccname');
|
||||
|
|
@ -259,7 +278,7 @@ sub create_mds {
|
|||
my $service_name = "mds.$id";
|
||||
|
||||
die "ceph MDS directory '$service_dir' already exists\n"
|
||||
if -d $service_dir;
|
||||
if -d $service_dir;
|
||||
|
||||
print "creating MDS directory '$service_dir'\n";
|
||||
eval { File::Path::mkpath($service_dir) };
|
||||
|
|
@ -268,17 +287,17 @@ sub create_mds {
|
|||
|
||||
# http://docs.ceph.com/docs/luminous/install/manual-deployment/#adding-mds
|
||||
my $priv = [
|
||||
mon => 'allow profile mds',
|
||||
osd => 'allow rwx',
|
||||
mds => 'allow *',
|
||||
mon => 'allow profile mds',
|
||||
osd => 'allow rwx',
|
||||
mds => 'allow *',
|
||||
];
|
||||
|
||||
print "creating keys for '$service_name'\n";
|
||||
my $output = $rados->mon_command({
|
||||
prefix => 'auth get-or-create',
|
||||
entity => $service_name,
|
||||
caps => $priv,
|
||||
format => 'plain',
|
||||
prefix => 'auth get-or-create',
|
||||
entity => $service_name,
|
||||
caps => $priv,
|
||||
format => 'plain',
|
||||
});
|
||||
|
||||
PVE::Tools::file_set_contents($service_keyring, $output);
|
||||
|
|
@ -294,13 +313,13 @@ sub create_mds {
|
|||
broadcast_ceph_services();
|
||||
|
||||
return undef;
|
||||
};
|
||||
}
|
||||
|
||||
sub destroy_mds {
|
||||
my ($id, $rados) = @_;
|
||||
|
||||
if (!defined($rados)) {
|
||||
$rados = PVE::RADOS->new();
|
||||
$rados = PVE::RADOS->new();
|
||||
}
|
||||
|
||||
my $ccname = PVE::Ceph::Tools::get_config('ccname');
|
||||
|
|
@ -314,23 +333,23 @@ sub destroy_mds {
|
|||
ceph_service_cmd('stop', $service_name);
|
||||
|
||||
if (-d $service_dir) {
|
||||
print "removing ceph-mds directory '$service_dir'\n";
|
||||
File::Path::remove_tree($service_dir);
|
||||
print "removing ceph-mds directory '$service_dir'\n";
|
||||
File::Path::remove_tree($service_dir);
|
||||
} else {
|
||||
warn "cannot cleanup MDS $id directory, '$service_dir' not found\n"
|
||||
warn "cannot cleanup MDS $id directory, '$service_dir' not found\n";
|
||||
}
|
||||
|
||||
print "removing ceph auth for '$service_name'\n";
|
||||
$rados->mon_command({
|
||||
prefix => 'auth del',
|
||||
entity => $service_name,
|
||||
format => 'plain'
|
||||
});
|
||||
prefix => 'auth del',
|
||||
entity => $service_name,
|
||||
format => 'plain',
|
||||
});
|
||||
|
||||
broadcast_ceph_services();
|
||||
|
||||
return undef;
|
||||
};
|
||||
}
|
||||
|
||||
# MGR
|
||||
|
||||
|
|
@ -348,14 +367,14 @@ sub create_mgr {
|
|||
mkdir $mgrdir;
|
||||
print "creating keys for '$mgrname'\n";
|
||||
my $output = $rados->mon_command({
|
||||
prefix => 'auth get-or-create',
|
||||
entity => $mgrname,
|
||||
caps => [
|
||||
mon => 'allow profile mgr',
|
||||
osd => 'allow *',
|
||||
mds => 'allow *',
|
||||
],
|
||||
format => 'plain'
|
||||
prefix => 'auth get-or-create',
|
||||
entity => $mgrname,
|
||||
caps => [
|
||||
mon => 'allow profile mgr',
|
||||
osd => 'allow *',
|
||||
mds => 'allow *',
|
||||
],
|
||||
format => 'plain',
|
||||
});
|
||||
PVE::Tools::file_set_contents($mgrkeyring, $output);
|
||||
|
||||
|
|
@ -380,7 +399,7 @@ sub destroy_mgr {
|
|||
my $mgrdir = "/var/lib/ceph/mgr/$clustername-$mgrid";
|
||||
|
||||
die "ceph manager directory '$mgrdir' not found\n"
|
||||
if ! -d $mgrdir;
|
||||
if !-d $mgrdir;
|
||||
|
||||
print "disabling service 'ceph-mgr\@$mgrid.service'\n";
|
||||
ceph_service_cmd('disable', $mgrname);
|
||||
|
|
@ -392,7 +411,7 @@ sub destroy_mgr {
|
|||
|
||||
print "removing authkeys for $mgrname\n";
|
||||
if (!$rados) {
|
||||
$rados = PVE::RADOS->new();
|
||||
$rados = PVE::RADOS->new();
|
||||
}
|
||||
|
||||
$rados->mon_command({ prefix => 'auth del', entity => "$mgrname" });
|
||||
|
|
|
|||
|
|
@ -61,9 +61,9 @@ sub get_local_version {
|
|||
|
||||
my $ceph_version;
|
||||
run_command(
|
||||
[ $ceph_service->{ceph_bin}, '--version' ],
|
||||
noerr => $noerr,
|
||||
outfunc => sub { $ceph_version = shift if !defined $ceph_version },
|
||||
[$ceph_service->{ceph_bin}, '--version'],
|
||||
noerr => $noerr,
|
||||
outfunc => sub { $ceph_version = shift if !defined $ceph_version },
|
||||
);
|
||||
|
||||
return undef if !defined $ceph_version;
|
||||
|
|
@ -91,13 +91,13 @@ sub parse_ceph_version : prototype($) {
|
|||
/x;
|
||||
|
||||
if ($ceph_version =~ /$re_ceph_version/) {
|
||||
my ($version, $buildcommit) = ($1, $2);
|
||||
my $subversions = [ split(/\.|-/, $version) ];
|
||||
my ($version, $buildcommit) = ($1, $2);
|
||||
my $subversions = [split(/\.|-/, $version)];
|
||||
|
||||
# return (version, buildid, [major, minor, ...]) : major;
|
||||
return wantarray
|
||||
? ($version, $buildcommit, $subversions)
|
||||
: $subversions->[0];
|
||||
# return (version, buildid, [major, minor, ...]) : major;
|
||||
return wantarray
|
||||
? ($version, $buildcommit, $subversions)
|
||||
: $subversions->[0];
|
||||
}
|
||||
|
||||
return undef;
|
||||
|
|
@ -116,7 +116,7 @@ sub get_config {
|
|||
|
||||
my $value = $config_values->{$key} // $config_files->{$key};
|
||||
|
||||
die "no such ceph config '$key'" if ! defined($value);
|
||||
die "no such ceph config '$key'" if !defined($value);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
|
@ -124,38 +124,41 @@ sub get_config {
|
|||
sub purge_all_ceph_files {
|
||||
my ($services) = @_;
|
||||
my $is_local_mon;
|
||||
my $monlist = [ split(',', PVE::CephConfig::get_monaddr_list($pve_ceph_cfgpath)) ];
|
||||
my $monlist = [split(',', PVE::CephConfig::get_monaddr_list($pve_ceph_cfgpath))];
|
||||
|
||||
foreach my $service (keys %$services) {
|
||||
my $type = $services->{$service};
|
||||
next if (!%$type);
|
||||
my $type = $services->{$service};
|
||||
next if (!%$type);
|
||||
|
||||
foreach my $name (keys %$type) {
|
||||
my $dir_exists = $type->{$name}->{direxists};
|
||||
foreach my $name (keys %$type) {
|
||||
my $dir_exists = $type->{$name}->{direxists};
|
||||
|
||||
$is_local_mon = grep($type->{$name}->{addr}, @$monlist)
|
||||
if $service eq 'mon';
|
||||
$is_local_mon = grep($type->{$name}->{addr}, @$monlist)
|
||||
if $service eq 'mon';
|
||||
|
||||
my $path = "/var/lib/ceph/$service";
|
||||
$path = '/var/log/ceph' if $service eq 'logs';
|
||||
if ($dir_exists) {
|
||||
my $err;
|
||||
File::Path::remove_tree($path, {
|
||||
keep_root => 1,
|
||||
error => \$err,
|
||||
});
|
||||
warn "Error removing path, '$path'\n" if @$err;
|
||||
}
|
||||
}
|
||||
my $path = "/var/lib/ceph/$service";
|
||||
$path = '/var/log/ceph' if $service eq 'logs';
|
||||
if ($dir_exists) {
|
||||
my $err;
|
||||
File::Path::remove_tree(
|
||||
$path,
|
||||
{
|
||||
keep_root => 1,
|
||||
error => \$err,
|
||||
},
|
||||
);
|
||||
warn "Error removing path, '$path'\n" if @$err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scalar @$monlist > 0 && !$is_local_mon) {
|
||||
warn "Foreign MON address in ceph.conf. Keeping config & keyrings\n"
|
||||
warn "Foreign MON address in ceph.conf. Keeping config & keyrings\n";
|
||||
} else {
|
||||
print "Removing config & keyring files\n";
|
||||
for my $file (%$config_files) {
|
||||
unlink $file if (-e $file);
|
||||
}
|
||||
print "Removing config & keyring files\n";
|
||||
for my $file (%$config_files) {
|
||||
unlink $file if (-e $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -163,24 +166,24 @@ sub purge_all_ceph_services {
|
|||
my ($services) = @_;
|
||||
|
||||
foreach my $service (keys %$services) {
|
||||
my $type = $services->{$service};
|
||||
next if (!%$type);
|
||||
my $type = $services->{$service};
|
||||
next if (!%$type);
|
||||
|
||||
foreach my $name (keys %$type) {
|
||||
my $service_exists = $type->{$name}->{service};
|
||||
foreach my $name (keys %$type) {
|
||||
my $service_exists = $type->{$name}->{service};
|
||||
|
||||
if ($service_exists) {
|
||||
eval { PVE::Ceph::Services::ceph_service_cmd('disable', "$service.$name") };
|
||||
warn "Could not disable ceph-$service\@$name, error: $@\n" if $@;
|
||||
if ($service_exists) {
|
||||
eval { PVE::Ceph::Services::ceph_service_cmd('disable', "$service.$name") };
|
||||
warn "Could not disable ceph-$service\@$name, error: $@\n" if $@;
|
||||
|
||||
eval { PVE::Ceph::Services::ceph_service_cmd('stop', "$service.$name") };
|
||||
warn "Could not stop ceph-$service\@$name, error: $@\n" if $@;
|
||||
}
|
||||
}
|
||||
eval { PVE::Ceph::Services::ceph_service_cmd('stop', "$service.$name") };
|
||||
warn "Could not stop ceph-$service\@$name, error: $@\n" if $@;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub ceph_install_flag_file { return '/run/pve-ceph-install-flag' };
|
||||
sub ceph_install_flag_file { return '/run/pve-ceph-install-flag' }
|
||||
|
||||
sub check_ceph_installed {
|
||||
my ($service, $noerr) = @_;
|
||||
|
|
@ -189,21 +192,20 @@ sub check_ceph_installed {
|
|||
|
||||
# NOTE: the flag file is checked as on a new installation, the binary gets
|
||||
# extracted by dpkg before the installation is finished
|
||||
if (! -x $ceph_service->{$service} || -f ceph_install_flag_file()) {
|
||||
die "binary not installed: $ceph_service->{$service}\n" if !$noerr;
|
||||
return undef;
|
||||
if (!-x $ceph_service->{$service} || -f ceph_install_flag_file()) {
|
||||
die "binary not installed: $ceph_service->{$service}\n" if !$noerr;
|
||||
return undef;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
sub check_ceph_configured {
|
||||
|
||||
check_ceph_inited();
|
||||
|
||||
die "ceph not fully configured - missing '$pve_ckeyring_path'\n"
|
||||
if ! -f $pve_ckeyring_path;
|
||||
if !-f $pve_ckeyring_path;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -215,13 +217,13 @@ sub check_ceph_inited {
|
|||
|
||||
my @errors;
|
||||
|
||||
push(@errors, "missing '$pve_ceph_cfgpath'") if ! -f $pve_ceph_cfgpath;
|
||||
push(@errors, "missing '$pve_ceph_cfgdir'") if ! -d $pve_ceph_cfgdir;
|
||||
push(@errors, "missing '$pve_ceph_cfgpath'") if !-f $pve_ceph_cfgpath;
|
||||
push(@errors, "missing '$pve_ceph_cfgdir'") if !-d $pve_ceph_cfgdir;
|
||||
|
||||
if (@errors) {
|
||||
my $err = 'pveceph configuration not initialized - ' . join(', ', @errors) . "\n";
|
||||
die $err if !$noerr;
|
||||
return undef;
|
||||
my $err = 'pveceph configuration not initialized - ' . join(', ', @errors) . "\n";
|
||||
die $err if !$noerr;
|
||||
return undef;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
|
@ -232,9 +234,9 @@ sub check_ceph_enabled {
|
|||
|
||||
return undef if !check_ceph_inited($noerr);
|
||||
|
||||
if (! -f $ceph_cfgpath) {
|
||||
die "pveceph configuration not enabled\n" if !$noerr;
|
||||
return undef;
|
||||
if (!-f $ceph_cfgpath) {
|
||||
die "pveceph configuration not enabled\n" if !$noerr;
|
||||
return undef;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
|
@ -245,19 +247,19 @@ my $set_pool_setting = sub {
|
|||
|
||||
my $command;
|
||||
if ($setting eq 'application') {
|
||||
$command = {
|
||||
prefix => "osd pool application enable",
|
||||
pool => "$pool",
|
||||
app => "$value",
|
||||
};
|
||||
$command = {
|
||||
prefix => "osd pool application enable",
|
||||
pool => "$pool",
|
||||
app => "$value",
|
||||
};
|
||||
} else {
|
||||
$command = {
|
||||
prefix => "osd pool set",
|
||||
pool => "$pool",
|
||||
var => "$setting",
|
||||
val => "$value",
|
||||
format => 'plain',
|
||||
};
|
||||
$command = {
|
||||
prefix => "osd pool set",
|
||||
pool => "$pool",
|
||||
var => "$setting",
|
||||
val => "$value",
|
||||
format => 'plain',
|
||||
};
|
||||
}
|
||||
|
||||
$rados = PVE::RADOS->new() if !$rados;
|
||||
|
|
@ -271,42 +273,42 @@ sub set_pool {
|
|||
my $rados = PVE::RADOS->new();
|
||||
|
||||
if (get_pool_type($pool, $rados) eq 'erasure') {
|
||||
#remove parameters that cannot be changed for erasure coded pools
|
||||
my $ignore_params = ['size', 'crush_rule'];
|
||||
for my $setting (@$ignore_params) {
|
||||
if ($param->{$setting}) {
|
||||
print "cannot set '${setting}' for erasure coded pool\n";
|
||||
delete $param->{$setting};
|
||||
}
|
||||
}
|
||||
#remove parameters that cannot be changed for erasure coded pools
|
||||
my $ignore_params = ['size', 'crush_rule'];
|
||||
for my $setting (@$ignore_params) {
|
||||
if ($param->{$setting}) {
|
||||
print "cannot set '${setting}' for erasure coded pool\n";
|
||||
delete $param->{$setting};
|
||||
}
|
||||
}
|
||||
}
|
||||
# by default, pool size always resets min_size, so set it as first item
|
||||
# https://tracker.ceph.com/issues/44862
|
||||
my $keys = [ grep { $_ ne 'size' } sort keys %$param ];
|
||||
my $keys = [grep { $_ ne 'size' } sort keys %$param];
|
||||
unshift @$keys, 'size' if exists $param->{size};
|
||||
|
||||
my $current_properties = get_pool_properties($pool, $rados);
|
||||
|
||||
for my $setting (@$keys) {
|
||||
my $value = $param->{$setting};
|
||||
my $value = $param->{$setting};
|
||||
|
||||
if (defined($current_properties->{$setting}) && $value eq $current_properties->{$setting}) {
|
||||
print "skipping '${setting}', did not change\n";
|
||||
delete $param->{$setting};
|
||||
next;
|
||||
}
|
||||
if (defined($current_properties->{$setting}) && $value eq $current_properties->{$setting}) {
|
||||
print "skipping '${setting}', did not change\n";
|
||||
delete $param->{$setting};
|
||||
next;
|
||||
}
|
||||
|
||||
print "pool $pool: applying $setting = $value\n";
|
||||
if (my $err = $set_pool_setting->($pool, $setting, $value, $rados)) {
|
||||
print "$err";
|
||||
} else {
|
||||
delete $param->{$setting};
|
||||
}
|
||||
print "pool $pool: applying $setting = $value\n";
|
||||
if (my $err = $set_pool_setting->($pool, $setting, $value, $rados)) {
|
||||
print "$err";
|
||||
} else {
|
||||
delete $param->{$setting};
|
||||
}
|
||||
}
|
||||
|
||||
if (scalar(keys %$param) > 0) {
|
||||
my $missing = join(', ', sort keys %$param );
|
||||
die "Could not set: $missing\n";
|
||||
my $missing = join(', ', sort keys %$param);
|
||||
die "Could not set: $missing\n";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -315,10 +317,10 @@ sub get_pool_properties {
|
|||
my ($pool, $rados) = @_;
|
||||
$rados = PVE::RADOS->new() if !defined($rados);
|
||||
my $command = {
|
||||
prefix => "osd pool get",
|
||||
pool => "$pool",
|
||||
var => "all",
|
||||
format => 'json',
|
||||
prefix => "osd pool get",
|
||||
pool => "$pool",
|
||||
var => "all",
|
||||
format => 'json',
|
||||
};
|
||||
return $rados->mon_command($command);
|
||||
}
|
||||
|
|
@ -337,14 +339,14 @@ sub create_pool {
|
|||
my $pg_num = $param->{pg_num} || 128;
|
||||
|
||||
my $mon_params = {
|
||||
prefix => "osd pool create",
|
||||
pool => $pool,
|
||||
pg_num => int($pg_num),
|
||||
format => 'plain',
|
||||
prefix => "osd pool create",
|
||||
pool => $pool,
|
||||
pg_num => int($pg_num),
|
||||
format => 'plain',
|
||||
};
|
||||
$mon_params->{pool_type} = extract_param($param, 'pool_type') if $param->{pool_type};
|
||||
$mon_params->{erasure_code_profile} = extract_param($param, 'erasure_code_profile')
|
||||
if $param->{erasure_code_profile};
|
||||
if $param->{erasure_code_profile};
|
||||
|
||||
$rados->mon_command($mon_params);
|
||||
|
||||
|
|
@ -367,11 +369,11 @@ sub destroy_pool {
|
|||
|
||||
# fixme: '--yes-i-really-really-mean-it'
|
||||
$rados->mon_command({
|
||||
prefix => "osd pool delete",
|
||||
pool => $pool,
|
||||
pool2 => $pool,
|
||||
'yes_i_really_really_mean_it' => JSON::true,
|
||||
format => 'plain',
|
||||
prefix => "osd pool delete",
|
||||
pool => $pool,
|
||||
pool2 => $pool,
|
||||
'yes_i_really_really_mean_it' => JSON::true,
|
||||
format => 'plain',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -396,15 +398,15 @@ sub create_fs {
|
|||
my ($fs, $param, $rados) = @_;
|
||||
|
||||
if (!defined($rados)) {
|
||||
$rados = PVE::RADOS->new();
|
||||
$rados = PVE::RADOS->new();
|
||||
}
|
||||
|
||||
$rados->mon_command({
|
||||
prefix => "fs new",
|
||||
fs_name => $fs,
|
||||
metadata => $param->{pool_metadata},
|
||||
data => $param->{pool_data},
|
||||
format => 'plain',
|
||||
prefix => "fs new",
|
||||
fs_name => $fs,
|
||||
metadata => $param->{pool_metadata},
|
||||
data => $param->{pool_data},
|
||||
format => 'plain',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -413,23 +415,23 @@ sub destroy_fs {
|
|||
$rados = PVE::RADOS->new() if !defined($rados);
|
||||
|
||||
$rados->mon_command({
|
||||
prefix => "fs rm",
|
||||
fs_name => $fs,
|
||||
'yes_i_really_mean_it' => JSON::true,
|
||||
format => 'plain',
|
||||
prefix => "fs rm",
|
||||
fs_name => $fs,
|
||||
'yes_i_really_mean_it' => JSON::true,
|
||||
format => 'plain',
|
||||
});
|
||||
}
|
||||
|
||||
sub setup_pve_symlinks {
|
||||
# fail if we find a real file instead of a link
|
||||
if (-f $ceph_cfgpath) {
|
||||
my $lnk = readlink($ceph_cfgpath);
|
||||
die "file '$ceph_cfgpath' already exists and is not a symlink to $pve_ceph_cfgpath\n"
|
||||
if !$lnk || $lnk ne $pve_ceph_cfgpath;
|
||||
my $lnk = readlink($ceph_cfgpath);
|
||||
die "file '$ceph_cfgpath' already exists and is not a symlink to $pve_ceph_cfgpath\n"
|
||||
if !$lnk || $lnk ne $pve_ceph_cfgpath;
|
||||
} else {
|
||||
mkdir $ceph_cfgdir;
|
||||
symlink($pve_ceph_cfgpath, $ceph_cfgpath) ||
|
||||
die "unable to create symlink '$ceph_cfgpath' - $!\n";
|
||||
mkdir $ceph_cfgdir;
|
||||
symlink($pve_ceph_cfgpath, $ceph_cfgpath)
|
||||
|| die "unable to create symlink '$ceph_cfgpath' - $!\n";
|
||||
}
|
||||
my $ceph_uid = getpwnam('ceph');
|
||||
my $ceph_gid = getgrnam('ceph');
|
||||
|
|
@ -437,18 +439,18 @@ sub setup_pve_symlinks {
|
|||
}
|
||||
|
||||
sub get_or_create_admin_keyring {
|
||||
if (! -f $pve_ckeyring_path) {
|
||||
run_command("ceph-authtool --create-keyring $pve_ckeyring_path " .
|
||||
"--gen-key -n client.admin " .
|
||||
"--cap mon 'allow *' " .
|
||||
"--cap osd 'allow *' " .
|
||||
"--cap mds 'allow *' " .
|
||||
"--cap mgr 'allow *' ");
|
||||
# we do not want to overwrite it
|
||||
if (! -f $ckeyring_path) {
|
||||
run_command("cp $pve_ckeyring_path $ckeyring_path");
|
||||
run_command("chown ceph:ceph $ckeyring_path");
|
||||
}
|
||||
if (!-f $pve_ckeyring_path) {
|
||||
run_command("ceph-authtool --create-keyring $pve_ckeyring_path "
|
||||
. "--gen-key -n client.admin "
|
||||
. "--cap mon 'allow *' "
|
||||
. "--cap osd 'allow *' "
|
||||
. "--cap mds 'allow *' "
|
||||
. "--cap mgr 'allow *' ");
|
||||
# we do not want to overwrite it
|
||||
if (!-f $ckeyring_path) {
|
||||
run_command("cp $pve_ckeyring_path $ckeyring_path");
|
||||
run_command("chown ceph:ceph $ckeyring_path");
|
||||
}
|
||||
}
|
||||
return $pve_ckeyring_path;
|
||||
}
|
||||
|
|
@ -458,29 +460,29 @@ sub create_or_update_crash_keyring_file {
|
|||
my ($rados) = @_;
|
||||
|
||||
if (!defined($rados)) {
|
||||
$rados = PVE::RADOS->new();
|
||||
$rados = PVE::RADOS->new();
|
||||
}
|
||||
|
||||
my $output = $rados->mon_command({
|
||||
prefix => 'auth get-or-create',
|
||||
entity => 'client.crash',
|
||||
caps => [
|
||||
mon => 'profile crash',
|
||||
mgr => 'profile crash',
|
||||
],
|
||||
format => 'plain',
|
||||
prefix => 'auth get-or-create',
|
||||
entity => 'client.crash',
|
||||
caps => [
|
||||
mon => 'profile crash',
|
||||
mgr => 'profile crash',
|
||||
],
|
||||
format => 'plain',
|
||||
});
|
||||
|
||||
if (-f $pve_ceph_crash_key_path) {
|
||||
my $contents = PVE::Tools::file_get_contents($pve_ceph_crash_key_path);
|
||||
my $contents = PVE::Tools::file_get_contents($pve_ceph_crash_key_path);
|
||||
|
||||
if ($contents ne $output) {
|
||||
PVE::Tools::file_set_contents($pve_ceph_crash_key_path, $output);
|
||||
return 1;
|
||||
}
|
||||
if ($contents ne $output) {
|
||||
PVE::Tools::file_set_contents($pve_ceph_crash_key_path, $output);
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
PVE::Tools::file_set_contents($pve_ceph_crash_key_path, $output);
|
||||
return 1;
|
||||
PVE::Tools::file_set_contents($pve_ceph_crash_key_path, $output);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -491,11 +493,11 @@ sub ceph_volume_list {
|
|||
my $result = {};
|
||||
|
||||
if (!check_ceph_installed('ceph_volume', 1)) {
|
||||
return $result;
|
||||
return $result;
|
||||
}
|
||||
|
||||
my $output = '';
|
||||
my $cmd = [ $ceph_service->{ceph_volume}, 'lvm', 'list', '--format', 'json' ];
|
||||
my $cmd = [$ceph_service->{ceph_volume}, 'lvm', 'list', '--format', 'json'];
|
||||
run_command($cmd, outfunc => sub { $output .= shift });
|
||||
|
||||
$result = eval { decode_json($output) };
|
||||
|
|
@ -508,7 +510,7 @@ sub ceph_volume_zap {
|
|||
|
||||
die "no osdid given\n" if !defined($osdid);
|
||||
|
||||
my $cmd = [ $ceph_service->{ceph_volume}, 'lvm', 'zap', '--osd-id', $osdid ];
|
||||
my $cmd = [$ceph_service->{ceph_volume}, 'lvm', 'zap', '--osd-id', $osdid];
|
||||
push @$cmd, '--destroy' if $destroy;
|
||||
|
||||
run_command($cmd);
|
||||
|
|
@ -520,84 +522,88 @@ sub get_db_wal_sizes {
|
|||
my $rados = PVE::RADOS->new();
|
||||
my $db_config = $rados->mon_command({ prefix => 'config-key dump', key => 'config/' });
|
||||
|
||||
$res->{db} = $db_config->{"config/osd/bluestore_block_db_size"} //
|
||||
$db_config->{"config/global/bluestore_block_db_size"};
|
||||
$res->{db} = $db_config->{"config/osd/bluestore_block_db_size"}
|
||||
// $db_config->{"config/global/bluestore_block_db_size"};
|
||||
|
||||
$res->{wal} = $db_config->{"config/osd/bluestore_block_wal_size"} //
|
||||
$db_config->{"config/global/bluestore_block_wal_size"};
|
||||
$res->{wal} = $db_config->{"config/osd/bluestore_block_wal_size"}
|
||||
// $db_config->{"config/global/bluestore_block_wal_size"};
|
||||
|
||||
if (!$res->{db} || !$res->{wal}) {
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
if (!$res->{db}) {
|
||||
$res->{db} = $cfg->{osd}->{bluestore_block_db_size} //
|
||||
$cfg->{global}->{bluestore_block_db_size};
|
||||
}
|
||||
my $cfg = cfs_read_file('ceph.conf');
|
||||
if (!$res->{db}) {
|
||||
$res->{db} = $cfg->{osd}->{bluestore_block_db_size}
|
||||
// $cfg->{global}->{bluestore_block_db_size};
|
||||
}
|
||||
|
||||
if (!$res->{wal}) {
|
||||
$res->{wal} = $cfg->{osd}->{bluestore_block_wal_size} //
|
||||
$cfg->{global}->{bluestore_block_wal_size};
|
||||
}
|
||||
if (!$res->{wal}) {
|
||||
$res->{wal} = $cfg->{osd}->{bluestore_block_wal_size}
|
||||
// $cfg->{global}->{bluestore_block_wal_size};
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
sub get_possible_osd_flags {
|
||||
my $possible_flags = {
|
||||
pause => {
|
||||
description => 'Pauses read and writes.',
|
||||
type => 'boolean',
|
||||
optional=> 1,
|
||||
},
|
||||
noup => {
|
||||
description => 'OSDs are not allowed to start.',
|
||||
type => 'boolean',
|
||||
optional=> 1,
|
||||
},
|
||||
nodown => {
|
||||
description => 'OSD failure reports are being ignored, such that the monitors will not mark OSDs down.',
|
||||
type => 'boolean',
|
||||
optional=> 1,
|
||||
},
|
||||
noout => {
|
||||
description => 'OSDs will not automatically be marked out after the configured interval.',
|
||||
type => 'boolean',
|
||||
optional=> 1,
|
||||
},
|
||||
noin => {
|
||||
description => 'OSDs that were previously marked out will not be marked back in when they start.',
|
||||
type => 'boolean',
|
||||
optional=> 1,
|
||||
},
|
||||
nobackfill => {
|
||||
description => 'Backfilling of PGs is suspended.',
|
||||
type => 'boolean',
|
||||
optional=> 1,
|
||||
},
|
||||
norebalance => {
|
||||
description => 'Rebalancing of PGs is suspended.',
|
||||
type => 'boolean',
|
||||
optional=> 1,
|
||||
},
|
||||
norecover => {
|
||||
description => 'Recovery of PGs is suspended.',
|
||||
type => 'boolean',
|
||||
optional=> 1,
|
||||
},
|
||||
noscrub => {
|
||||
description => 'Scrubbing is disabled.',
|
||||
type => 'boolean',
|
||||
optional=> 1,
|
||||
},
|
||||
'nodeep-scrub' => {
|
||||
description => 'Deep Scrubbing is disabled.',
|
||||
type => 'boolean',
|
||||
optional=> 1,
|
||||
},
|
||||
notieragent => {
|
||||
description => 'Cache tiering activity is suspended.',
|
||||
type => 'boolean',
|
||||
optional=> 1,
|
||||
},
|
||||
pause => {
|
||||
description => 'Pauses read and writes.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
noup => {
|
||||
description => 'OSDs are not allowed to start.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
nodown => {
|
||||
description =>
|
||||
'OSD failure reports are being ignored, such that the monitors will not mark OSDs down.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
noout => {
|
||||
description =>
|
||||
'OSDs will not automatically be marked out after the configured interval.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
noin => {
|
||||
description =>
|
||||
'OSDs that were previously marked out will not be marked back in when they start.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
nobackfill => {
|
||||
description => 'Backfilling of PGs is suspended.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
norebalance => {
|
||||
description => 'Rebalancing of PGs is suspended.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
norecover => {
|
||||
description => 'Recovery of PGs is suspended.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
noscrub => {
|
||||
description => 'Scrubbing is disabled.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
'nodeep-scrub' => {
|
||||
description => 'Deep Scrubbing is disabled.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
notieragent => {
|
||||
description => 'Cache tiering activity is suspended.',
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
};
|
||||
return $possible_flags;
|
||||
}
|
||||
|
|
@ -608,7 +614,7 @@ sub get_real_flag_name {
|
|||
# the 'pause' flag gets always set to both 'pauserd' and 'pausewr'
|
||||
# so decide that the 'pause' flag is set if we detect 'pauserd'
|
||||
my $flagmap = {
|
||||
'pause' => 'pauserd',
|
||||
'pause' => 'pauserd',
|
||||
};
|
||||
|
||||
return $flagmap->{$flag} // $flag;
|
||||
|
|
@ -622,8 +628,8 @@ sub ceph_cluster_status {
|
|||
$status->{health} = $rados->mon_command({ prefix => 'health', detail => 'detail' });
|
||||
|
||||
if (!exists $status->{monmap}->{mons}) { # octopus moved most info out of status, re-add
|
||||
$status->{monmap} = $rados->mon_command({ prefix => 'mon dump' });
|
||||
$status->{mgrmap} = $rados->mon_command({ prefix => 'mgr dump' });
|
||||
$status->{monmap} = $rados->mon_command({ prefix => 'mon dump' });
|
||||
$status->{mgrmap} = $rados->mon_command({ prefix => 'mgr dump' });
|
||||
}
|
||||
|
||||
return $status;
|
||||
|
|
@ -646,17 +652,15 @@ sub create_ecprofile {
|
|||
$failure_domain = 'host' if !$failure_domain;
|
||||
|
||||
my $profile = [
|
||||
"crush-failure-domain=${failure_domain}",
|
||||
"k=${k}",
|
||||
"m=${m}",
|
||||
"crush-failure-domain=${failure_domain}", "k=${k}", "m=${m}",
|
||||
];
|
||||
|
||||
push(@$profile, "crush-device-class=${device_class}") if $device_class;
|
||||
|
||||
$rados->mon_command({
|
||||
prefix => 'osd erasure-code-profile set',
|
||||
name => $name,
|
||||
profile => $profile,
|
||||
prefix => 'osd erasure-code-profile set',
|
||||
name => $name,
|
||||
profile => $profile,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -665,9 +669,9 @@ sub destroy_ecprofile {
|
|||
$rados = PVE::RADOS->new() if !$rados;
|
||||
|
||||
my $command = {
|
||||
prefix => 'osd erasure-code-profile rm',
|
||||
name => $profile,
|
||||
format => 'plain',
|
||||
prefix => 'osd erasure-code-profile rm',
|
||||
name => $profile,
|
||||
format => 'plain',
|
||||
};
|
||||
return $rados->mon_command($command);
|
||||
}
|
||||
|
|
@ -682,9 +686,9 @@ sub destroy_crush_rule {
|
|||
$rados = PVE::RADOS->new() if !$rados;
|
||||
|
||||
my $command = {
|
||||
prefix => 'osd crush rule rm',
|
||||
name => $rule,
|
||||
format => 'plain',
|
||||
prefix => 'osd crush rule rm',
|
||||
name => $rule,
|
||||
format => 'plain',
|
||||
};
|
||||
return $rados->mon_command($command);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,37 +18,36 @@ sub update_cert_cache {
|
|||
my ($update_node, $clear) = @_;
|
||||
|
||||
syslog('info', "Clearing outdated entries from certificate cache")
|
||||
if $clear;
|
||||
if $clear;
|
||||
|
||||
$cert_cache_timestamp = time() if !defined($update_node);
|
||||
|
||||
my $node_list = defined($update_node) ?
|
||||
[ $update_node ] : [ keys %$cert_cache_nodes ];
|
||||
my $node_list = defined($update_node) ? [$update_node] : [keys %$cert_cache_nodes];
|
||||
|
||||
foreach my $node (@$node_list) {
|
||||
my $clear_old = sub {
|
||||
if (my $old_fp = $cert_cache_nodes->{$node}) {
|
||||
# distrust old fingerprint
|
||||
delete $cert_cache_fingerprints->{$old_fp};
|
||||
# ensure reload on next proxied request
|
||||
delete $cert_cache_nodes->{$node};
|
||||
}
|
||||
};
|
||||
my $clear_old = sub {
|
||||
if (my $old_fp = $cert_cache_nodes->{$node}) {
|
||||
# distrust old fingerprint
|
||||
delete $cert_cache_fingerprints->{$old_fp};
|
||||
# ensure reload on next proxied request
|
||||
delete $cert_cache_nodes->{$node};
|
||||
}
|
||||
};
|
||||
|
||||
my $fp = eval { PVE::Cluster::get_node_fingerprint($node) };
|
||||
if (my $err = $@) {
|
||||
warn "$err\n";
|
||||
&$clear_old() if $clear;
|
||||
next;
|
||||
}
|
||||
my $fp = eval { PVE::Cluster::get_node_fingerprint($node) };
|
||||
if (my $err = $@) {
|
||||
warn "$err\n";
|
||||
&$clear_old() if $clear;
|
||||
next;
|
||||
}
|
||||
|
||||
my $old_fp = $cert_cache_nodes->{$node};
|
||||
$cert_cache_fingerprints->{$fp} = 1;
|
||||
$cert_cache_nodes->{$node} = $fp;
|
||||
my $old_fp = $cert_cache_nodes->{$node};
|
||||
$cert_cache_fingerprints->{$fp} = 1;
|
||||
$cert_cache_nodes->{$node} = $fp;
|
||||
|
||||
if (defined($old_fp) && $fp ne $old_fp) {
|
||||
delete $cert_cache_fingerprints->{$old_fp};
|
||||
}
|
||||
if (defined($old_fp) && $fp ne $old_fp) {
|
||||
delete $cert_cache_fingerprints->{$old_fp};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,33 +56,36 @@ sub initialize_cert_cache {
|
|||
my ($node) = @_;
|
||||
|
||||
update_cert_cache($node)
|
||||
if defined($node) && !defined($cert_cache_nodes->{$node});
|
||||
if defined($node) && !defined($cert_cache_nodes->{$node});
|
||||
}
|
||||
|
||||
sub check_cert_fingerprint {
|
||||
my ($cert) = @_;
|
||||
|
||||
# clear cache every 30 minutes at least
|
||||
update_cert_cache(undef, 1) if time() - $cert_cache_timestamp >= 60*30;
|
||||
update_cert_cache(undef, 1) if time() - $cert_cache_timestamp >= 60 * 30;
|
||||
|
||||
# get fingerprint of server certificate
|
||||
my $fp = Net::SSLeay::X509_get_fingerprint($cert, 'sha256');
|
||||
return 0 if !defined($fp) || $fp eq ''; # error
|
||||
|
||||
my $check = sub {
|
||||
for my $expected (keys %$cert_cache_fingerprints) {
|
||||
return 1 if $fp eq $expected;
|
||||
}
|
||||
return 0;
|
||||
for my $expected (keys %$cert_cache_fingerprints) {
|
||||
return 1 if $fp eq $expected;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
return 1 if &$check();
|
||||
|
||||
# clear cache and retry at most once every minute
|
||||
if (time() - $cert_cache_timestamp >= 60) {
|
||||
syslog ('info', "Could not verify remote node certificate '$fp' with list of pinned certificates, refreshing cache");
|
||||
update_cert_cache();
|
||||
return &$check();
|
||||
syslog(
|
||||
'info',
|
||||
"Could not verify remote node certificate '$fp' with list of pinned certificates, refreshing cache",
|
||||
);
|
||||
update_cert_cache();
|
||||
return &$check();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -9,26 +9,35 @@ use PVE::Tools;
|
|||
|
||||
my $account_prefix = '/etc/pve/priv/acme';
|
||||
|
||||
PVE::JSONSchema::register_standard_option('pve-acme-account-name', {
|
||||
description => 'ACME account config file name.',
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
format_description => 'name',
|
||||
optional => 1,
|
||||
default => 'default',
|
||||
});
|
||||
PVE::JSONSchema::register_standard_option(
|
||||
'pve-acme-account-name',
|
||||
{
|
||||
description => 'ACME account config file name.',
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
format_description => 'name',
|
||||
optional => 1,
|
||||
default => 'default',
|
||||
},
|
||||
);
|
||||
|
||||
PVE::JSONSchema::register_standard_option('pve-acme-account-contact', {
|
||||
type => 'string',
|
||||
format => 'email-list',
|
||||
description => 'Contact email addresses.',
|
||||
});
|
||||
PVE::JSONSchema::register_standard_option(
|
||||
'pve-acme-account-contact',
|
||||
{
|
||||
type => 'string',
|
||||
format => 'email-list',
|
||||
description => 'Contact email addresses.',
|
||||
},
|
||||
);
|
||||
|
||||
PVE::JSONSchema::register_standard_option('pve-acme-directory-url', {
|
||||
type => 'string',
|
||||
description => 'URL of ACME CA directory endpoint.',
|
||||
pattern => '^https?://.*',
|
||||
});
|
||||
PVE::JSONSchema::register_standard_option(
|
||||
'pve-acme-directory-url',
|
||||
{
|
||||
type => 'string',
|
||||
description => 'URL of ACME CA directory endpoint.',
|
||||
pattern => '^https?://.*',
|
||||
},
|
||||
);
|
||||
|
||||
my $local_cert_lock = '/var/lock/pve-certs.lock';
|
||||
|
||||
|
|
@ -61,30 +70,30 @@ sub set_cert_files {
|
|||
my $key_path_tmp = "${path_prefix}.key.old";
|
||||
|
||||
die "Custom certificate file exists but force flag is not set.\n"
|
||||
if !$force && -e $cert_path;
|
||||
if !$force && -e $cert_path;
|
||||
die "Custom certificate key file exists but force flag is not set.\n"
|
||||
if !$force && -e $key_path;
|
||||
if !$force && -e $key_path;
|
||||
|
||||
PVE::Tools::file_copy($cert_path, $cert_path_tmp) if -e $cert_path;
|
||||
PVE::Tools::file_copy($key_path, $key_path_tmp) if -e $key_path;
|
||||
|
||||
eval {
|
||||
PVE::Tools::file_set_contents($cert_path, $cert);
|
||||
PVE::Tools::file_set_contents($key_path, $key) if $key;
|
||||
$info = PVE::Certificate::get_certificate_info($cert_path);
|
||||
PVE::Tools::file_set_contents($cert_path, $cert);
|
||||
PVE::Tools::file_set_contents($key_path, $key) if $key;
|
||||
$info = PVE::Certificate::get_certificate_info($cert_path);
|
||||
};
|
||||
my $err = $@;
|
||||
|
||||
if ($err) {
|
||||
if (-e $cert_path_tmp && -e $key_path_tmp) {
|
||||
eval {
|
||||
warn "Attempting to restore old certificate files..\n";
|
||||
PVE::Tools::file_copy($cert_path_tmp, $cert_path);
|
||||
PVE::Tools::file_copy($key_path_tmp, $key_path);
|
||||
};
|
||||
warn "$@\n" if $@;
|
||||
}
|
||||
die "Setting certificate files failed - $err\n"
|
||||
if (-e $cert_path_tmp && -e $key_path_tmp) {
|
||||
eval {
|
||||
warn "Attempting to restore old certificate files..\n";
|
||||
PVE::Tools::file_copy($cert_path_tmp, $cert_path);
|
||||
PVE::Tools::file_copy($key_path_tmp, $key_path);
|
||||
};
|
||||
warn "$@\n" if $@;
|
||||
}
|
||||
die "Setting certificate files failed - $err\n";
|
||||
}
|
||||
|
||||
unlink $cert_path_tmp;
|
||||
|
|
@ -100,14 +109,18 @@ sub acme_account_dir {
|
|||
sub list_acme_accounts {
|
||||
my $accounts = [];
|
||||
|
||||
return $accounts if ! -d $account_prefix;
|
||||
return $accounts if !-d $account_prefix;
|
||||
|
||||
PVE::Tools::dir_glob_foreach($account_prefix, qr/[^.]+.*/, sub {
|
||||
my ($name) = @_;
|
||||
PVE::Tools::dir_glob_foreach(
|
||||
$account_prefix,
|
||||
qr/[^.]+.*/,
|
||||
sub {
|
||||
my ($name) = @_;
|
||||
|
||||
push @$accounts, $name
|
||||
if PVE::JSONSchema::pve_verify_configid($name, 1);
|
||||
});
|
||||
push @$accounts, $name
|
||||
if PVE::JSONSchema::pve_verify_configid($name, 1);
|
||||
},
|
||||
);
|
||||
|
||||
return $accounts;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,12 +14,12 @@ PVE::Status::Plugin->init();
|
|||
sub foreach_plug($&) {
|
||||
my ($status_cfg, $code) = @_;
|
||||
|
||||
for my $id (sort keys %{$status_cfg->{ids}}) {
|
||||
my $plugin_config = $status_cfg->{ids}->{$id};
|
||||
next if $plugin_config->{disable};
|
||||
for my $id (sort keys %{ $status_cfg->{ids} }) {
|
||||
my $plugin_config = $status_cfg->{ids}->{$id};
|
||||
next if $plugin_config->{disable};
|
||||
|
||||
my $plugin = PVE::Status::Plugin->lookup($plugin_config->{type});
|
||||
$code->($plugin, $id, $plugin_config);
|
||||
my $plugin = PVE::Status::Plugin->lookup($plugin_config->{type});
|
||||
$code->($plugin, $id, $plugin_config);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -29,9 +29,9 @@ sub update_all($$@) {
|
|||
my $method = "update_${subsystem}_status";
|
||||
|
||||
for my $txn (@$transactions) {
|
||||
my $plugin = PVE::Status::Plugin->lookup($txn->{cfg}->{type});
|
||||
my $plugin = PVE::Status::Plugin->lookup($txn->{cfg}->{type});
|
||||
|
||||
$plugin->$method($txn, @params);
|
||||
$plugin->$method($txn, @params);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -46,18 +46,22 @@ sub transactions_start {
|
|||
|
||||
my $transactions = [];
|
||||
|
||||
foreach_plug($cfg, sub {
|
||||
my ($plugin, $id, $plugin_config) = @_;
|
||||
foreach_plug(
|
||||
$cfg,
|
||||
sub {
|
||||
my ($plugin, $id, $plugin_config) = @_;
|
||||
|
||||
my $connection = $plugin->_connect($plugin_config, $id);
|
||||
my $connection = $plugin->_connect($plugin_config, $id);
|
||||
|
||||
push @$transactions, {
|
||||
connection => $connection,
|
||||
cfg => $plugin_config,
|
||||
id => $id,
|
||||
data => '',
|
||||
};
|
||||
});
|
||||
push @$transactions,
|
||||
{
|
||||
connection => $connection,
|
||||
cfg => $plugin_config,
|
||||
id => $id,
|
||||
data => '',
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return $transactions;
|
||||
}
|
||||
|
|
@ -66,16 +70,16 @@ sub transactions_finish {
|
|||
my ($transactions) = @_;
|
||||
|
||||
for my $txn (@$transactions) {
|
||||
my $plugin = PVE::Status::Plugin->lookup($txn->{cfg}->{type});
|
||||
my $plugin = PVE::Status::Plugin->lookup($txn->{cfg}->{type});
|
||||
|
||||
eval { $plugin->flush_data($txn) };
|
||||
my $flush_err = $@;
|
||||
warn "$flush_err" if $flush_err;
|
||||
eval { $plugin->flush_data($txn) };
|
||||
my $flush_err = $@;
|
||||
warn "$flush_err" if $flush_err;
|
||||
|
||||
$plugin->_disconnect($txn->{connection}, $txn->{cfg});
|
||||
$txn->{connection} = undef;
|
||||
# avoid log spam, already got a send error; disconnect would fail too
|
||||
warn "disconnect failed: $@" if $@ && !$flush_err;
|
||||
$plugin->_disconnect($txn->{connection}, $txn->{cfg});
|
||||
$txn->{connection} = undef;
|
||||
# avoid log spam, already got a send error; disconnect would fail too
|
||||
warn "disconnect failed: $@" if $@ && !$flush_err;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ sub new {
|
|||
my $self = $class->SUPER::new(%args);
|
||||
|
||||
$self->{rpcenv} = PVE::RPCEnvironment->init(
|
||||
$self->{trusted_env} ? 'priv' : 'pub',
|
||||
atfork => sub { $self->atfork_handler() },
|
||||
$self->{trusted_env} ? 'priv' : 'pub',
|
||||
atfork => sub { $self->atfork_handler() },
|
||||
);
|
||||
|
||||
return $self;
|
||||
|
|
@ -67,11 +67,13 @@ sub auth_handler {
|
|||
my $require_auth = 1;
|
||||
|
||||
# explicitly allow some calls without auth
|
||||
if (($rel_uri eq '/access/domains' && $method eq 'GET') ||
|
||||
($rel_uri eq '/access/ticket' && ($method eq 'GET' || $method eq 'POST')) ||
|
||||
($rel_uri eq '/access/openid/login' && $method eq 'POST') ||
|
||||
($rel_uri eq '/access/openid/auth-url' && $method eq 'POST')) {
|
||||
$require_auth = 0;
|
||||
if (
|
||||
($rel_uri eq '/access/domains' && $method eq 'GET')
|
||||
|| ($rel_uri eq '/access/ticket' && ($method eq 'GET' || $method eq 'POST'))
|
||||
|| ($rel_uri eq '/access/openid/login' && $method eq 'POST')
|
||||
|| ($rel_uri eq '/access/openid/auth-url' && $method eq 'POST')
|
||||
) {
|
||||
$require_auth = 0;
|
||||
}
|
||||
|
||||
my ($username, $age);
|
||||
|
|
@ -79,50 +81,50 @@ sub auth_handler {
|
|||
my $isUpload = 0;
|
||||
|
||||
if ($require_auth) {
|
||||
if ($api_token) {
|
||||
# the token-ID `<user>@<realm>!<tokenname>` is the user for token based authentication
|
||||
$username = PVE::AccessControl::verify_token($api_token);
|
||||
} else {
|
||||
die "No ticket\n" if !$ticket;
|
||||
if ($api_token) {
|
||||
# the token-ID `<user>@<realm>!<tokenname>` is the user for token based authentication
|
||||
$username = PVE::AccessControl::verify_token($api_token);
|
||||
} else {
|
||||
die "No ticket\n" if !$ticket;
|
||||
|
||||
($username, $age, my $tfa_info) = PVE::AccessControl::verify_ticket($ticket);
|
||||
$rpcenv->check_user_enabled($username);
|
||||
($username, $age, my $tfa_info) = PVE::AccessControl::verify_ticket($ticket);
|
||||
$rpcenv->check_user_enabled($username);
|
||||
|
||||
if (defined($tfa_info)) {
|
||||
if (defined(my $challenge = $tfa_info->{challenge})) {
|
||||
$rpcenv->set_u2f_challenge($challenge);
|
||||
}
|
||||
die "No ticket\n" if ($rel_uri ne '/access/tfa' || $method ne 'POST');
|
||||
}
|
||||
}
|
||||
if (defined($tfa_info)) {
|
||||
if (defined(my $challenge = $tfa_info->{challenge})) {
|
||||
$rpcenv->set_u2f_challenge($challenge);
|
||||
}
|
||||
die "No ticket\n" if ($rel_uri ne '/access/tfa' || $method ne 'POST');
|
||||
}
|
||||
}
|
||||
|
||||
$rpcenv->set_user($username);
|
||||
$rpcenv->set_user($username);
|
||||
|
||||
if ($method eq 'POST' && $rel_uri =~ m|^/nodes/([^/]+)/storage/([^/]+)/upload$|) {
|
||||
my ($node, $storeid) = ($1, $2);
|
||||
# CSRF check are omitted if $isUpload is set, so check user upload permission here
|
||||
my $perm = { check => ['perm', "/storage/$storeid", ['Datastore.AllocateTemplate']] };
|
||||
$rpcenv->check_api2_permissions($perm, $username, {});
|
||||
$isUpload = 1;
|
||||
}
|
||||
if ($method eq 'POST' && $rel_uri =~ m|^/nodes/([^/]+)/storage/([^/]+)/upload$|) {
|
||||
my ($node, $storeid) = ($1, $2);
|
||||
# CSRF check are omitted if $isUpload is set, so check user upload permission here
|
||||
my $perm = { check => ['perm', "/storage/$storeid", ['Datastore.AllocateTemplate']] };
|
||||
$rpcenv->check_api2_permissions($perm, $username, {});
|
||||
$isUpload = 1;
|
||||
}
|
||||
|
||||
# Skip CSRF check for file upload (difficult to pass CSRF header with native html forms).
|
||||
# Also skip the check with API tokens, as one of the design goals of API tokens was to
|
||||
# provide stateless API access without requiring round-trips to get such CSRF tokens.
|
||||
# CSRF-prevention also does not make much sense outside of the browser context.
|
||||
if ($method ne 'GET' && !($api_token || $isUpload)) {
|
||||
my $euid = $>;
|
||||
PVE::AccessControl::verify_csrf_prevention_token($username, $token) if $euid != 0;
|
||||
}
|
||||
# Skip CSRF check for file upload (difficult to pass CSRF header with native html forms).
|
||||
# Also skip the check with API tokens, as one of the design goals of API tokens was to
|
||||
# provide stateless API access without requiring round-trips to get such CSRF tokens.
|
||||
# CSRF-prevention also does not make much sense outside of the browser context.
|
||||
if ($method ne 'GET' && !($api_token || $isUpload)) {
|
||||
my $euid = $>;
|
||||
PVE::AccessControl::verify_csrf_prevention_token($username, $token) if $euid != 0;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ticket => $ticket,
|
||||
token => $token,
|
||||
userid => $username,
|
||||
age => $age,
|
||||
isUpload => $isUpload,
|
||||
api_token => $api_token,
|
||||
ticket => $ticket,
|
||||
token => $token,
|
||||
userid => $username,
|
||||
age => $age,
|
||||
isUpload => $isUpload,
|
||||
api_token => $api_token,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -132,78 +134,83 @@ sub rest_handler {
|
|||
my $rpcenv = $self->{rpcenv};
|
||||
|
||||
my $resp = {
|
||||
status => HTTP_NOT_IMPLEMENTED,
|
||||
message => "Method '$method $rel_uri' not implemented",
|
||||
status => HTTP_NOT_IMPLEMENTED,
|
||||
message => "Method '$method $rel_uri' not implemented",
|
||||
};
|
||||
|
||||
my ($handler, $info);
|
||||
|
||||
eval {
|
||||
my $uri_param = {};
|
||||
($handler, $info) = PVE::API2->find_handler($method, $rel_uri, $uri_param);
|
||||
return if !$handler || !$info;
|
||||
my $uri_param = {};
|
||||
($handler, $info) = PVE::API2->find_handler($method, $rel_uri, $uri_param);
|
||||
return if !$handler || !$info;
|
||||
|
||||
for my $p (sort keys %{$params}) {
|
||||
if (defined($uri_param->{$p}) && $uri_param->{$p} ne $params->{$p}) {
|
||||
raise_param_exc({
|
||||
$p => "duplicate parameter (already defined in URI) with conflicting values!"
|
||||
});
|
||||
}
|
||||
$uri_param->{$p} = $params->{$p};
|
||||
}
|
||||
for my $p (sort keys %{$params}) {
|
||||
if (defined($uri_param->{$p}) && $uri_param->{$p} ne $params->{$p}) {
|
||||
raise_param_exc({
|
||||
$p =>
|
||||
"duplicate parameter (already defined in URI) with conflicting values!",
|
||||
});
|
||||
}
|
||||
$uri_param->{$p} = $params->{$p};
|
||||
}
|
||||
|
||||
raise_perm_exc("URI '$rel_uri' not available with API token, need proper ticket.\n")
|
||||
if $auth->{api_token} && !$info->{allowtoken};
|
||||
raise_perm_exc("URI '$rel_uri' not available with API token, need proper ticket.\n")
|
||||
if $auth->{api_token} && !$info->{allowtoken};
|
||||
|
||||
# check access permissions
|
||||
$rpcenv->check_api2_permissions($info->{permissions}, $auth->{userid}, $uri_param);
|
||||
# check access permissions
|
||||
$rpcenv->check_api2_permissions($info->{permissions}, $auth->{userid}, $uri_param);
|
||||
|
||||
if ($info->{proxyto} || $info->{proxyto_callback}) {
|
||||
my $node = PVE::API2Tools::resolve_proxyto(
|
||||
$rpcenv, $info->{proxyto_callback}, $info->{proxyto}, $uri_param);
|
||||
if ($info->{proxyto} || $info->{proxyto_callback}) {
|
||||
my $node = PVE::API2Tools::resolve_proxyto(
|
||||
$rpcenv,
|
||||
$info->{proxyto_callback},
|
||||
$info->{proxyto},
|
||||
$uri_param,
|
||||
);
|
||||
|
||||
if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
|
||||
die "unable to proxy file uploads" if $auth->{isUpload};
|
||||
my $remip = $self->remote_node_ip($node);
|
||||
$resp = { proxy => $remip, proxynode => $node, proxy_params => $params };
|
||||
return;
|
||||
}
|
||||
}
|
||||
if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
|
||||
die "unable to proxy file uploads" if $auth->{isUpload};
|
||||
my $remip = $self->remote_node_ip($node);
|
||||
$resp = { proxy => $remip, proxynode => $node, proxy_params => $params };
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
my $euid = $>;
|
||||
if ($info->{protected} && ($euid != 0)) {
|
||||
$resp = { proxy => 'localhost' , proxy_params => $params };
|
||||
return;
|
||||
}
|
||||
my $euid = $>;
|
||||
if ($info->{protected} && ($euid != 0)) {
|
||||
$resp = { proxy => 'localhost', proxy_params => $params };
|
||||
return;
|
||||
}
|
||||
|
||||
$resp = {
|
||||
data => $handler->handle($info, $uri_param),
|
||||
info => $info, # useful to format output
|
||||
status => HTTP_OK,
|
||||
};
|
||||
$resp = {
|
||||
data => $handler->handle($info, $uri_param),
|
||||
info => $info, # useful to format output
|
||||
status => HTTP_OK,
|
||||
};
|
||||
|
||||
if (my $count = $rpcenv->get_result_attrib('total')) {
|
||||
$resp->{total} = $count;
|
||||
}
|
||||
if (my $count = $rpcenv->get_result_attrib('total')) {
|
||||
$resp->{total} = $count;
|
||||
}
|
||||
|
||||
if (my $diff = $rpcenv->get_result_attrib('changes')) {
|
||||
$resp->{changes} = $diff;
|
||||
}
|
||||
if (my $diff = $rpcenv->get_result_attrib('changes')) {
|
||||
$resp->{changes} = $diff;
|
||||
}
|
||||
};
|
||||
my $err = $@;
|
||||
|
||||
$rpcenv->set_user(undef); # clear after request
|
||||
|
||||
if ($err) {
|
||||
$resp = { info => $info };
|
||||
if (ref($err) eq "PVE::Exception") {
|
||||
$resp->{status} = $err->{code} || HTTP_INTERNAL_SERVER_ERROR;
|
||||
$resp->{errors} = $err->{errors} if $err->{errors};
|
||||
$resp->{message} = $err->{msg};
|
||||
} else {
|
||||
$resp->{status} = HTTP_INTERNAL_SERVER_ERROR;
|
||||
$resp->{message} = $err;
|
||||
}
|
||||
$resp = { info => $info };
|
||||
if (ref($err) eq "PVE::Exception") {
|
||||
$resp->{status} = $err->{code} || HTTP_INTERNAL_SERVER_ERROR;
|
||||
$resp->{errors} = $err->{errors} if $err->{errors};
|
||||
$resp->{message} = $err->{msg};
|
||||
} else {
|
||||
$resp->{status} = HTTP_INTERNAL_SERVER_ERROR;
|
||||
$resp->{message} = $err;
|
||||
}
|
||||
}
|
||||
|
||||
return $resp;
|
||||
|
|
|
|||
326
PVE/Jobs.pm
326
PVE/Jobs.pm
|
|
@ -41,37 +41,41 @@ my $saved_config_props = [qw(enabled schedule)];
|
|||
sub detect_changed_runtime_props {
|
||||
my ($jobid, $type, $cfg) = @_;
|
||||
|
||||
lock_job_state($jobid, $type, sub {
|
||||
my $old_state = read_job_state($jobid, $type) // $default_state;
|
||||
lock_job_state(
|
||||
$jobid,
|
||||
$type,
|
||||
sub {
|
||||
my $old_state = read_job_state($jobid, $type) // $default_state;
|
||||
|
||||
my $updated = 0;
|
||||
for my $prop (@$saved_config_props) {
|
||||
my $old_prop = $old_state->{config}->{$prop} // '';
|
||||
my $new_prop = $cfg->{$prop} // '';
|
||||
next if "$old_prop" eq "$new_prop";
|
||||
my $updated = 0;
|
||||
for my $prop (@$saved_config_props) {
|
||||
my $old_prop = $old_state->{config}->{$prop} // '';
|
||||
my $new_prop = $cfg->{$prop} // '';
|
||||
next if "$old_prop" eq "$new_prop";
|
||||
|
||||
if (defined($cfg->{$prop})) {
|
||||
$old_state->{config}->{$prop} = $cfg->{$prop};
|
||||
} else {
|
||||
delete $old_state->{config}->{$prop};
|
||||
}
|
||||
if (defined($cfg->{$prop})) {
|
||||
$old_state->{config}->{$prop} = $cfg->{$prop};
|
||||
} else {
|
||||
delete $old_state->{config}->{$prop};
|
||||
}
|
||||
|
||||
$updated = 1;
|
||||
}
|
||||
$updated = 1;
|
||||
}
|
||||
|
||||
return if !$updated;
|
||||
$old_state->{updated} = time();
|
||||
return if !$updated;
|
||||
$old_state->{updated} = time();
|
||||
|
||||
my $path = $get_state_file->($jobid, $type);
|
||||
PVE::Tools::file_set_contents($path, encode_json($old_state));
|
||||
});
|
||||
my $path = $get_state_file->($jobid, $type);
|
||||
PVE::Tools::file_set_contents($path, encode_json($old_state));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
# lockless, since we use file_get_contents, which is atomic
|
||||
sub read_job_state {
|
||||
my ($jobid, $type) = @_;
|
||||
my $path = $get_state_file->($jobid, $type);
|
||||
return if ! -e $path;
|
||||
return if !-e $path;
|
||||
|
||||
my $raw = PVE::Tools::file_get_contents($path);
|
||||
|
||||
|
|
@ -79,7 +83,7 @@ sub read_job_state {
|
|||
|
||||
# untaint $raw
|
||||
if ($raw =~ m/^(\{.*\})$/) {
|
||||
return decode_json($1);
|
||||
return decode_json($1);
|
||||
}
|
||||
|
||||
die "invalid json data in '$path'\n";
|
||||
|
|
@ -100,16 +104,16 @@ my $get_job_task_status = sub {
|
|||
my ($state) = @_;
|
||||
|
||||
if (!defined($state->{upid})) {
|
||||
return; # not started
|
||||
return; # not started
|
||||
}
|
||||
|
||||
my ($task, $filename) = PVE::Tools::upid_decode($state->{upid}, 1);
|
||||
die "unable to parse worker upid - $state->{upid}\n" if !$task;
|
||||
die "no such task\n" if ! -f $filename;
|
||||
die "no such task\n" if !-f $filename;
|
||||
|
||||
my $pstart = PVE::ProcFSTools::read_proc_starttime($task->{pid});
|
||||
if ($pstart && $pstart == $task->{pstart}) {
|
||||
return; # still running
|
||||
return; # still running
|
||||
}
|
||||
|
||||
return PVE::Tools::upid_read_status($state->{upid});
|
||||
|
|
@ -125,24 +129,28 @@ sub update_job_stopped {
|
|||
return if !defined($state) || $state->{state} ne 'started'; # removed or not started
|
||||
|
||||
if (defined($get_job_task_status->($state))) {
|
||||
lock_job_state($jobid, $type, sub {
|
||||
my $state = read_job_state($jobid, $type);
|
||||
return if !defined($state) || $state->{state} ne 'started'; # removed or not started
|
||||
lock_job_state(
|
||||
$jobid,
|
||||
$type,
|
||||
sub {
|
||||
my $state = read_job_state($jobid, $type);
|
||||
return if !defined($state) || $state->{state} ne 'started'; # removed or not started
|
||||
|
||||
my $new_state = {
|
||||
state => 'stopped',
|
||||
msg => $get_job_task_status->($state) // 'internal error',
|
||||
upid => $state->{upid},
|
||||
config => $state->{config},
|
||||
};
|
||||
my $new_state = {
|
||||
state => 'stopped',
|
||||
msg => $get_job_task_status->($state) // 'internal error',
|
||||
upid => $state->{upid},
|
||||
config => $state->{config},
|
||||
};
|
||||
|
||||
if ($state->{updated}) { # save updated time stamp
|
||||
$new_state->{updated} = $state->{updated};
|
||||
}
|
||||
if ($state->{updated}) { # save updated time stamp
|
||||
$new_state->{updated} = $state->{updated};
|
||||
}
|
||||
|
||||
my $path = $get_state_file->($jobid, $type);
|
||||
PVE::Tools::file_set_contents($path, encode_json($new_state));
|
||||
});
|
||||
my $path = $get_state_file->($jobid, $type);
|
||||
PVE::Tools::file_set_contents($path, encode_json($new_state));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -150,23 +158,27 @@ sub update_job_stopped {
|
|||
sub create_job {
|
||||
my ($jobid, $type, $cfg) = @_;
|
||||
|
||||
lock_job_state($jobid, $type, sub {
|
||||
my $state = read_job_state($jobid, $type) // $default_state;
|
||||
lock_job_state(
|
||||
$jobid,
|
||||
$type,
|
||||
sub {
|
||||
my $state = read_job_state($jobid, $type) // $default_state;
|
||||
|
||||
if ($state->{state} ne 'created') {
|
||||
die "job state already exists\n";
|
||||
}
|
||||
if ($state->{state} ne 'created') {
|
||||
die "job state already exists\n";
|
||||
}
|
||||
|
||||
$state->{time} = time();
|
||||
for my $prop (@$saved_config_props) {
|
||||
if (defined($cfg->{$prop})) {
|
||||
$state->{config}->{$prop} = $cfg->{$prop};
|
||||
}
|
||||
}
|
||||
$state->{time} = time();
|
||||
for my $prop (@$saved_config_props) {
|
||||
if (defined($cfg->{$prop})) {
|
||||
$state->{config}->{$prop} = $cfg->{$prop};
|
||||
}
|
||||
}
|
||||
|
||||
my $path = $get_state_file->($jobid, $type);
|
||||
PVE::Tools::file_set_contents($path, encode_json($state));
|
||||
});
|
||||
my $path = $get_state_file->($jobid, $type);
|
||||
PVE::Tools::file_set_contents($path, encode_json($state));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
# to be called when the job is removed
|
||||
|
|
@ -185,61 +197,73 @@ sub starting_job {
|
|||
my $state = read_job_state($jobid, $type);
|
||||
return 0 if !defined($state) || $state->{state} eq 'started'; # removed or already started
|
||||
|
||||
lock_job_state($jobid, $type, sub {
|
||||
my $state = read_job_state($jobid, $type);
|
||||
return 0 if !defined($state) || $state->{state} eq 'started'; # removed or already started
|
||||
lock_job_state(
|
||||
$jobid,
|
||||
$type,
|
||||
sub {
|
||||
my $state = read_job_state($jobid, $type);
|
||||
return 0 if !defined($state) || $state->{state} eq 'started'; # removed or already started
|
||||
|
||||
my $new_state = {
|
||||
state => 'starting',
|
||||
time => time(),
|
||||
config => $state->{config},
|
||||
};
|
||||
my $new_state = {
|
||||
state => 'starting',
|
||||
time => time(),
|
||||
config => $state->{config},
|
||||
};
|
||||
|
||||
my $path = $get_state_file->($jobid, $type);
|
||||
PVE::Tools::file_set_contents($path, encode_json($new_state));
|
||||
});
|
||||
my $path = $get_state_file->($jobid, $type);
|
||||
PVE::Tools::file_set_contents($path, encode_json($new_state));
|
||||
},
|
||||
);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub started_job {
|
||||
my ($jobid, $type, $upid, $msg) = @_;
|
||||
|
||||
lock_job_state($jobid, $type, sub {
|
||||
my $state = read_job_state($jobid, $type);
|
||||
return if !defined($state); # job was removed, do not update
|
||||
die "unexpected state '$state->{state}'\n" if $state->{state} ne 'starting';
|
||||
lock_job_state(
|
||||
$jobid,
|
||||
$type,
|
||||
sub {
|
||||
my $state = read_job_state($jobid, $type);
|
||||
return if !defined($state); # job was removed, do not update
|
||||
die "unexpected state '$state->{state}'\n" if $state->{state} ne 'starting';
|
||||
|
||||
my $new_state;
|
||||
if (defined($msg)) {
|
||||
$new_state = {
|
||||
state => 'stopped',
|
||||
msg => $msg,
|
||||
time => time(),
|
||||
};
|
||||
} else {
|
||||
$new_state = {
|
||||
state => 'started',
|
||||
upid => $upid,
|
||||
};
|
||||
}
|
||||
$new_state->{config} = $state->{config};
|
||||
my $new_state;
|
||||
if (defined($msg)) {
|
||||
$new_state = {
|
||||
state => 'stopped',
|
||||
msg => $msg,
|
||||
time => time(),
|
||||
};
|
||||
} else {
|
||||
$new_state = {
|
||||
state => 'started',
|
||||
upid => $upid,
|
||||
};
|
||||
}
|
||||
$new_state->{config} = $state->{config};
|
||||
|
||||
my $path = $get_state_file->($jobid, $type);
|
||||
PVE::Tools::file_set_contents($path, encode_json($new_state));
|
||||
});
|
||||
my $path = $get_state_file->($jobid, $type);
|
||||
PVE::Tools::file_set_contents($path, encode_json($new_state));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
# will be called when the job schedule is updated
|
||||
sub update_last_runtime {
|
||||
my ($jobid, $type) = @_;
|
||||
lock_job_state($jobid, $type, sub {
|
||||
my $old_state = read_job_state($jobid, $type) // $default_state;
|
||||
lock_job_state(
|
||||
$jobid,
|
||||
$type,
|
||||
sub {
|
||||
my $old_state = read_job_state($jobid, $type) // $default_state;
|
||||
|
||||
$old_state->{updated} = time();
|
||||
$old_state->{updated} = time();
|
||||
|
||||
my $path = $get_state_file->($jobid, $type);
|
||||
PVE::Tools::file_set_contents($path, encode_json($old_state));
|
||||
});
|
||||
my $path = $get_state_file->($jobid, $type);
|
||||
PVE::Tools::file_set_contents($path, encode_json($old_state));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
sub get_last_runtime {
|
||||
|
|
@ -250,9 +274,9 @@ sub get_last_runtime {
|
|||
return $state->{updated} if defined($state->{updated});
|
||||
|
||||
if (my $upid = $state->{upid}) {
|
||||
my ($task) = PVE::Tools::upid_decode($upid, 1);
|
||||
die "unable to parse worker upid\n" if !$task;
|
||||
return $task->{starttime};
|
||||
my ($task) = PVE::Tools::upid_decode($upid, 1);
|
||||
die "unable to parse worker upid\n" if !$task;
|
||||
return $task->{starttime};
|
||||
}
|
||||
|
||||
return $state->{time} // 0;
|
||||
|
|
@ -266,77 +290,85 @@ sub run_jobs {
|
|||
my $jobs_cfg = cfs_read_file('jobs.cfg');
|
||||
my $nodename = PVE::INotify::nodename();
|
||||
|
||||
foreach my $id (sort keys %{$jobs_cfg->{ids}}) {
|
||||
my $cfg = $jobs_cfg->{ids}->{$id};
|
||||
my $type = $cfg->{type};
|
||||
my $schedule = delete $cfg->{schedule};
|
||||
foreach my $id (sort keys %{ $jobs_cfg->{ids} }) {
|
||||
my $cfg = $jobs_cfg->{ids}->{$id};
|
||||
my $type = $cfg->{type};
|
||||
my $schedule = delete $cfg->{schedule};
|
||||
|
||||
# only schedule local jobs
|
||||
next if defined($cfg->{node}) && $cfg->{node} ne $nodename;
|
||||
# only schedule local jobs
|
||||
next if defined($cfg->{node}) && $cfg->{node} ne $nodename;
|
||||
|
||||
eval { update_job_stopped($id, $type) };
|
||||
if (my $err = $@) {
|
||||
warn "could not update job state, skipping - $err\n";
|
||||
next;
|
||||
}
|
||||
eval { update_job_stopped($id, $type) };
|
||||
if (my $err = $@) {
|
||||
warn "could not update job state, skipping - $err\n";
|
||||
next;
|
||||
}
|
||||
|
||||
# update last runtime on the first run when 'repeat-missed' is 0, so that a missed job
|
||||
# will not start immediately after boot
|
||||
update_last_runtime($id, $type) if $first_run && !$cfg->{'repeat-missed'};
|
||||
# update last runtime on the first run when 'repeat-missed' is 0, so that a missed job
|
||||
# will not start immediately after boot
|
||||
update_last_runtime($id, $type) if $first_run && !$cfg->{'repeat-missed'};
|
||||
|
||||
next if defined($cfg->{enabled}) && !$cfg->{enabled}; # only schedule actually enabled jobs
|
||||
next if defined($cfg->{enabled}) && !$cfg->{enabled}; # only schedule actually enabled jobs
|
||||
|
||||
my $last_run = get_last_runtime($id, $type);
|
||||
my $calspec = PVE::CalendarEvent::parse_calendar_event($schedule);
|
||||
my $next_sync = PVE::CalendarEvent::compute_next_event($calspec, $last_run);
|
||||
my $last_run = get_last_runtime($id, $type);
|
||||
my $calspec = PVE::CalendarEvent::parse_calendar_event($schedule);
|
||||
my $next_sync = PVE::CalendarEvent::compute_next_event($calspec, $last_run);
|
||||
|
||||
next if !defined($next_sync) || time() < $next_sync; # not yet its (next) turn
|
||||
next if !defined($next_sync) || time() < $next_sync; # not yet its (next) turn
|
||||
|
||||
my $plugin = PVE::Job::Registry->lookup($type);
|
||||
if (starting_job($id, $type)) {
|
||||
PVE::Cluster::cfs_update();
|
||||
my $plugin = PVE::Job::Registry->lookup($type);
|
||||
if (starting_job($id, $type)) {
|
||||
PVE::Cluster::cfs_update();
|
||||
|
||||
my $upid = eval { $plugin->run($cfg, $id, $schedule) };
|
||||
if (my $err = $@) {
|
||||
warn $@ if $@;
|
||||
started_job($id, $type, undef, $err);
|
||||
} elsif ($upid eq 'OK') { # some jobs return OK immediately
|
||||
started_job($id, $type, undef, 'OK');
|
||||
} else {
|
||||
started_job($id, $type, $upid);
|
||||
}
|
||||
}
|
||||
my $upid = eval { $plugin->run($cfg, $id, $schedule) };
|
||||
if (my $err = $@) {
|
||||
warn $@ if $@;
|
||||
started_job($id, $type, undef, $err);
|
||||
} elsif ($upid eq 'OK') { # some jobs return OK immediately
|
||||
started_job($id, $type, undef, 'OK');
|
||||
} else {
|
||||
started_job($id, $type, $upid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# creates and removes statefiles for job configs
|
||||
sub synchronize_job_states_with_config {
|
||||
cfs_lock_file('jobs.cfg', undef, sub {
|
||||
my $data = cfs_read_file('jobs.cfg');
|
||||
cfs_lock_file(
|
||||
'jobs.cfg',
|
||||
undef,
|
||||
sub {
|
||||
my $data = cfs_read_file('jobs.cfg');
|
||||
|
||||
for my $id (keys $data->{ids}->%*) {
|
||||
my $job = $data->{ids}->{$id};
|
||||
my $type = $job->{type};
|
||||
for my $id (keys $data->{ids}->%*) {
|
||||
my $job = $data->{ids}->{$id};
|
||||
my $type = $job->{type};
|
||||
|
||||
my $path = $get_state_file->($id, $type);
|
||||
if (-e $path) {
|
||||
detect_changed_runtime_props($id, $type, $job);
|
||||
} else {
|
||||
create_job($id, $type, $job);
|
||||
}
|
||||
}
|
||||
my $path = $get_state_file->($id, $type);
|
||||
if (-e $path) {
|
||||
detect_changed_runtime_props($id, $type, $job);
|
||||
} else {
|
||||
create_job($id, $type, $job);
|
||||
}
|
||||
}
|
||||
|
||||
my $valid_types = PVE::Job::Registry->lookup_types();
|
||||
my $type_regex = join("|", $valid_types->@*);
|
||||
my $valid_types = PVE::Job::Registry->lookup_types();
|
||||
my $type_regex = join("|", $valid_types->@*);
|
||||
|
||||
PVE::Tools::dir_glob_foreach($state_dir, "(${type_regex})-(.*).json", sub {
|
||||
my ($path, $type, $id) = @_;
|
||||
PVE::Tools::dir_glob_foreach(
|
||||
$state_dir,
|
||||
"(${type_regex})-(.*).json",
|
||||
sub {
|
||||
my ($path, $type, $id) = @_;
|
||||
|
||||
if (!defined($data->{ids}->{$id})) {
|
||||
remove_job($id, $type);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!defined($data->{ids}->{$id})) {
|
||||
remove_job($id, $type);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
die $@ if $@;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,17 +17,17 @@ sub run {
|
|||
my $props = $class->properties();
|
||||
# remove all non vzdump related options
|
||||
foreach my $opt (keys %$conf) {
|
||||
delete $conf->{$opt} if !defined($props->{$opt});
|
||||
delete $conf->{$opt} if !defined($props->{$opt});
|
||||
}
|
||||
|
||||
$conf->{'job-id'} = $job_id;
|
||||
|
||||
# Required as string parameters # FIXME why?! we could just check ref()
|
||||
for my $key (keys $PVE::VZDump::Common::PROPERTY_STRINGS->%*) {
|
||||
if ($conf->{$key} && ref($conf->{$key}) eq 'HASH') {
|
||||
my $format = $PVE::VZDump::Common::PROPERTY_STRINGS->{$key};
|
||||
$conf->{$key} = PVE::JSONSchema::print_property_string($conf->{$key}, $format);
|
||||
}
|
||||
if ($conf->{$key} && ref($conf->{$key}) eq 'HASH') {
|
||||
my $format = $PVE::VZDump::Common::PROPERTY_STRINGS->{$key};
|
||||
$conf->{$key} = PVE::JSONSchema::print_property_string($conf->{$key}, $format);
|
||||
}
|
||||
}
|
||||
|
||||
$conf->{quiet} = 1; # do not write to stdout/stderr
|
||||
|
|
|
|||
|
|
@ -15,25 +15,31 @@ my $MAXDOMAINS = 5;
|
|||
|
||||
my $node_config_lock = '/var/lock/pvenode.lock';
|
||||
|
||||
PVE::JSONSchema::register_format('pve-acme-domain', sub {
|
||||
my ($domain, $noerr) = @_;
|
||||
PVE::JSONSchema::register_format(
|
||||
'pve-acme-domain',
|
||||
sub {
|
||||
my ($domain, $noerr) = @_;
|
||||
|
||||
my $label = qr/[a-z0-9][a-z0-9_-]*/i;
|
||||
my $label = qr/[a-z0-9][a-z0-9_-]*/i;
|
||||
|
||||
return $domain if $domain =~ /^$label(?:\.$label)+$/;
|
||||
return undef if $noerr;
|
||||
die "value '$domain' does not look like a valid domain name!\n";
|
||||
});
|
||||
return $domain if $domain =~ /^$label(?:\.$label)+$/;
|
||||
return undef if $noerr;
|
||||
die "value '$domain' does not look like a valid domain name!\n";
|
||||
},
|
||||
);
|
||||
|
||||
PVE::JSONSchema::register_format('pve-acme-alias', sub {
|
||||
my ($alias, $noerr) = @_;
|
||||
PVE::JSONSchema::register_format(
|
||||
'pve-acme-alias',
|
||||
sub {
|
||||
my ($alias, $noerr) = @_;
|
||||
|
||||
my $label = qr/[a-z0-9_][a-z0-9_-]*/i;
|
||||
my $label = qr/[a-z0-9_][a-z0-9_-]*/i;
|
||||
|
||||
return $alias if $alias =~ /^$label(?:\.$label)+$/;
|
||||
return undef if $noerr;
|
||||
die "value '$alias' does not look like a valid alias name!\n";
|
||||
});
|
||||
return $alias if $alias =~ /^$label(?:\.$label)+$/;
|
||||
return undef if $noerr;
|
||||
die "value '$alias' does not look like a valid alias name!\n";
|
||||
},
|
||||
);
|
||||
|
||||
sub config_file {
|
||||
my ($node) = @_;
|
||||
|
|
@ -66,8 +72,8 @@ sub lock_config {
|
|||
|
||||
# make sure configuration file is up-to-date
|
||||
my $code = sub {
|
||||
PVE::Cluster::cfs_update();
|
||||
$realcode->(@_);
|
||||
PVE::Cluster::cfs_update();
|
||||
$realcode->(@_);
|
||||
};
|
||||
|
||||
my $res = lock_file($node_config_lock, 10, $code, @param);
|
||||
|
|
@ -79,53 +85,54 @@ sub lock_config {
|
|||
|
||||
my $confdesc = {
|
||||
description => {
|
||||
type => 'string',
|
||||
description => "Description for the Node. Shown in the web-interface node notes panel."
|
||||
." This is saved as comment inside the configuration file.",
|
||||
maxLength => 64 * 1024,
|
||||
optional => 1,
|
||||
type => 'string',
|
||||
description => "Description for the Node. Shown in the web-interface node notes panel."
|
||||
. " This is saved as comment inside the configuration file.",
|
||||
maxLength => 64 * 1024,
|
||||
optional => 1,
|
||||
},
|
||||
'startall-onboot-delay' => {
|
||||
description => 'Initial delay in seconds, before starting all the Virtual Guests with on-boot enabled.',
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
maximum => 300,
|
||||
default => 0,
|
||||
optional => 1,
|
||||
description =>
|
||||
'Initial delay in seconds, before starting all the Virtual Guests with on-boot enabled.',
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
maximum => 300,
|
||||
default => 0,
|
||||
optional => 1,
|
||||
},
|
||||
'ballooning-target' => {
|
||||
description => 'RAM usage target for ballooning (in percent of total memory)',
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
maximum => 100,
|
||||
default => 80,
|
||||
optional => 1,
|
||||
description => 'RAM usage target for ballooning (in percent of total memory)',
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
maximum => 100,
|
||||
default => 80,
|
||||
optional => 1,
|
||||
},
|
||||
};
|
||||
|
||||
my $wakeonlan_desc = {
|
||||
mac => {
|
||||
type => 'string',
|
||||
description => 'MAC address for wake on LAN',
|
||||
format => 'mac-addr',
|
||||
format_description => 'MAC address',
|
||||
default_key => 1,
|
||||
type => 'string',
|
||||
description => 'MAC address for wake on LAN',
|
||||
format => 'mac-addr',
|
||||
format_description => 'MAC address',
|
||||
default_key => 1,
|
||||
},
|
||||
'bind-interface' => {
|
||||
type => 'string',
|
||||
description => 'Bind to this interface when sending wake on LAN packet',
|
||||
default => 'The interface carrying the default route',
|
||||
format => 'pve-iface',
|
||||
format_description => 'bind interface',
|
||||
optional => 1,
|
||||
type => 'string',
|
||||
description => 'Bind to this interface when sending wake on LAN packet',
|
||||
default => 'The interface carrying the default route',
|
||||
format => 'pve-iface',
|
||||
format_description => 'bind interface',
|
||||
optional => 1,
|
||||
},
|
||||
'broadcast-address' => {
|
||||
type => 'string',
|
||||
description => 'IPv4 broadcast address to use when sending wake on LAN packet',
|
||||
default => '255.255.255.255',
|
||||
format => 'ipv4',
|
||||
format_description => 'IPv4 broadcast address',
|
||||
optional => 1,
|
||||
type => 'string',
|
||||
description => 'IPv4 broadcast address to use when sending wake on LAN packet',
|
||||
default => '255.255.255.255',
|
||||
format => 'ipv4',
|
||||
format_description => 'IPv4 broadcast address',
|
||||
optional => 1,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -138,37 +145,37 @@ $confdesc->{wakeonlan} = {
|
|||
|
||||
my $acme_domain_desc = {
|
||||
domain => {
|
||||
type => 'string',
|
||||
format => 'pve-acme-domain',
|
||||
format_description => 'domain',
|
||||
description => 'domain for this node\'s ACME certificate',
|
||||
default_key => 1,
|
||||
type => 'string',
|
||||
format => 'pve-acme-domain',
|
||||
format_description => 'domain',
|
||||
description => 'domain for this node\'s ACME certificate',
|
||||
default_key => 1,
|
||||
},
|
||||
plugin => {
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
description => 'The ACME plugin ID',
|
||||
format_description => 'name of the plugin configuration',
|
||||
optional => 1,
|
||||
default => 'standalone',
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
description => 'The ACME plugin ID',
|
||||
format_description => 'name of the plugin configuration',
|
||||
optional => 1,
|
||||
default => 'standalone',
|
||||
},
|
||||
alias => {
|
||||
type => 'string',
|
||||
format => 'pve-acme-alias',
|
||||
format_description => 'domain',
|
||||
description => 'Alias for the Domain to verify ACME Challenge over DNS',
|
||||
optional => 1,
|
||||
type => 'string',
|
||||
format => 'pve-acme-alias',
|
||||
format_description => 'domain',
|
||||
description => 'Alias for the Domain to verify ACME Challenge over DNS',
|
||||
optional => 1,
|
||||
},
|
||||
};
|
||||
|
||||
my $acmedesc = {
|
||||
account => get_standard_option('pve-acme-account-name'),
|
||||
domains => {
|
||||
type => 'string',
|
||||
format => 'pve-acme-domain-list',
|
||||
format_description => 'domain[;domain;...]',
|
||||
description => 'List of domains for this node\'s ACME certificate',
|
||||
optional => 1,
|
||||
type => 'string',
|
||||
format => 'pve-acme-domain-list',
|
||||
format_description => 'domain[;domain;...]',
|
||||
description => 'List of domains for this node\'s ACME certificate',
|
||||
optional => 1,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -179,14 +186,14 @@ $confdesc->{acme} = {
|
|||
optional => 1,
|
||||
};
|
||||
|
||||
for my $i (0..$MAXDOMAINS) {
|
||||
for my $i (0 .. $MAXDOMAINS) {
|
||||
$confdesc->{"acmedomain$i"} = {
|
||||
type => 'string',
|
||||
description => 'ACME domain and validation plugin',
|
||||
format => $acme_domain_desc,
|
||||
optional => 1,
|
||||
type => 'string',
|
||||
description => 'ACME domain and validation plugin',
|
||||
format => $acme_domain_desc,
|
||||
optional => 1,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
my $conf_schema = {
|
||||
type => 'object',
|
||||
|
|
@ -212,17 +219,17 @@ sub write_node_config {
|
|||
# add description as comment to top of file
|
||||
my $descr = $conf->{description} || '';
|
||||
foreach my $cl (split(/\n/, $descr)) {
|
||||
$raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
|
||||
$raw .= '#' . PVE::Tools::encode_text($cl) . "\n";
|
||||
}
|
||||
|
||||
for my $key (sort keys %$conf) {
|
||||
next if ($key eq 'description');
|
||||
next if ($key eq 'digest');
|
||||
next if ($key eq 'description');
|
||||
next if ($key eq 'digest');
|
||||
|
||||
my $value = $conf->{$key};
|
||||
die "detected invalid newline inside property '$key'\n"
|
||||
if $value =~ m/\n/;
|
||||
$raw .= "$key: $value\n";
|
||||
my $value = $conf->{$key};
|
||||
die "detected invalid newline inside property '$key'\n"
|
||||
if $value =~ m/\n/;
|
||||
$raw .= "$key: $value\n";
|
||||
}
|
||||
|
||||
return $raw;
|
||||
|
|
@ -235,10 +242,10 @@ sub get_wakeonlan_config {
|
|||
|
||||
my $res = {};
|
||||
if (defined($node_conf->{wakeonlan})) {
|
||||
$res = eval {
|
||||
PVE::JSONSchema::parse_property_string($wakeonlan_desc, $node_conf->{wakeonlan})
|
||||
};
|
||||
die $@ if $@;
|
||||
$res = eval {
|
||||
PVE::JSONSchema::parse_property_string($wakeonlan_desc, $node_conf->{wakeonlan});
|
||||
};
|
||||
die $@ if $@;
|
||||
}
|
||||
|
||||
return $res;
|
||||
|
|
@ -254,55 +261,52 @@ sub get_acme_conf {
|
|||
|
||||
my $res = {};
|
||||
if (defined($node_conf->{acme})) {
|
||||
$res = eval {
|
||||
PVE::JSONSchema::parse_property_string($acmedesc, $node_conf->{acme})
|
||||
};
|
||||
if (my $err = $@) {
|
||||
return undef if $noerr;
|
||||
die $err;
|
||||
}
|
||||
my $standalone_domains = delete($res->{domains}) // '';
|
||||
$res->{domains} = {};
|
||||
for my $domain (split(";", $standalone_domains)) {
|
||||
$domain = lc($domain);
|
||||
die "duplicate domain '$domain' in ACME config properties\n"
|
||||
if defined($res->{domains}->{$domain});
|
||||
$res = eval { PVE::JSONSchema::parse_property_string($acmedesc, $node_conf->{acme}) };
|
||||
if (my $err = $@) {
|
||||
return undef if $noerr;
|
||||
die $err;
|
||||
}
|
||||
my $standalone_domains = delete($res->{domains}) // '';
|
||||
$res->{domains} = {};
|
||||
for my $domain (split(";", $standalone_domains)) {
|
||||
$domain = lc($domain);
|
||||
die "duplicate domain '$domain' in ACME config properties\n"
|
||||
if defined($res->{domains}->{$domain});
|
||||
|
||||
$res->{domains}->{$domain}->{plugin} = 'standalone';
|
||||
$res->{domains}->{$domain}->{_configkey} = 'acme';
|
||||
}
|
||||
$res->{domains}->{$domain}->{plugin} = 'standalone';
|
||||
$res->{domains}->{$domain}->{_configkey} = 'acme';
|
||||
}
|
||||
}
|
||||
|
||||
$res->{account} //= 'default';
|
||||
|
||||
for my $index (0..$MAXDOMAINS) {
|
||||
my $domain_rec = $node_conf->{"acmedomain$index"};
|
||||
next if !defined($domain_rec);
|
||||
for my $index (0 .. $MAXDOMAINS) {
|
||||
my $domain_rec = $node_conf->{"acmedomain$index"};
|
||||
next if !defined($domain_rec);
|
||||
|
||||
my $parsed = eval {
|
||||
PVE::JSONSchema::parse_property_string($acme_domain_desc, $domain_rec)
|
||||
};
|
||||
if (my $err = $@) {
|
||||
return undef if $noerr;
|
||||
die $err;
|
||||
}
|
||||
my $domain = lc(delete $parsed->{domain});
|
||||
if (my $exists = $res->{domains}->{$domain}) {
|
||||
return undef if $noerr;
|
||||
die "duplicate domain '$domain' in ACME config properties"
|
||||
." 'acmedomain$index' and '$exists->{_configkey}'\n";
|
||||
}
|
||||
$parsed->{plugin} //= 'standalone';
|
||||
my $parsed =
|
||||
eval { PVE::JSONSchema::parse_property_string($acme_domain_desc, $domain_rec) };
|
||||
if (my $err = $@) {
|
||||
return undef if $noerr;
|
||||
die $err;
|
||||
}
|
||||
my $domain = lc(delete $parsed->{domain});
|
||||
if (my $exists = $res->{domains}->{$domain}) {
|
||||
return undef if $noerr;
|
||||
die "duplicate domain '$domain' in ACME config properties"
|
||||
. " 'acmedomain$index' and '$exists->{_configkey}'\n";
|
||||
}
|
||||
$parsed->{plugin} //= 'standalone';
|
||||
|
||||
my $plugin_id = $parsed->{plugin};
|
||||
if ($plugin_id ne 'standalone') {
|
||||
my $plugins = PVE::API2::ACMEPlugin::load_config();
|
||||
die "plugin '$plugin_id' for domain '$domain' not found!\n"
|
||||
if !$plugins->{ids}->{$plugin_id};
|
||||
}
|
||||
my $plugin_id = $parsed->{plugin};
|
||||
if ($plugin_id ne 'standalone') {
|
||||
my $plugins = PVE::API2::ACMEPlugin::load_config();
|
||||
die "plugin '$plugin_id' for domain '$domain' not found!\n"
|
||||
if !$plugins->{ids}->{$plugin_id};
|
||||
}
|
||||
|
||||
$parsed->{_configkey} = "acmedomain$index";
|
||||
$res->{domains}->{$domain} = $parsed;
|
||||
$parsed->{_configkey} = "acmedomain$index";
|
||||
$res->{domains}->{$domain} = $parsed;
|
||||
}
|
||||
|
||||
return $res;
|
||||
|
|
|
|||
|
|
@ -12,17 +12,18 @@ my $cache;
|
|||
my $get_cache = sub {
|
||||
if (!defined($cache)) {
|
||||
|
||||
my $uid = getpwnam('root');
|
||||
my $gid = getgrnam('www-data');
|
||||
my $uid = getpwnam('root');
|
||||
my $gid = getgrnam('www-data');
|
||||
|
||||
$cache = Proxmox::RS::SharedCache->new({
|
||||
path => "/run/pve/metrics",
|
||||
owner => $uid,
|
||||
group => $gid,
|
||||
entry_mode => 0640, # Entry permissions
|
||||
keep_old => OLD_GENERATIONS,
|
||||
}
|
||||
);
|
||||
$cache = Proxmox::RS::SharedCache->new(
|
||||
{
|
||||
path => "/run/pve/metrics",
|
||||
owner => $uid,
|
||||
group => $gid,
|
||||
entry_mode => 0640, # Entry permissions
|
||||
keep_old => OLD_GENERATIONS,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return $cache;
|
||||
|
|
@ -55,24 +56,24 @@ my sub gauge {
|
|||
my ($id, $timestamp, $metric, $value) = @_;
|
||||
|
||||
return {
|
||||
metric => $metric,
|
||||
id => $id,
|
||||
value => $value + 0,
|
||||
timestamp => $timestamp + 0,
|
||||
type => 'gauge',
|
||||
}
|
||||
metric => $metric,
|
||||
id => $id,
|
||||
value => $value + 0,
|
||||
timestamp => $timestamp + 0,
|
||||
type => 'gauge',
|
||||
};
|
||||
}
|
||||
|
||||
my sub derive {
|
||||
my ($id, $timestamp, $metric, $value) = @_;
|
||||
|
||||
return {
|
||||
metric => $metric,
|
||||
id => $id,
|
||||
value => $value + 0,
|
||||
timestamp => $timestamp + 0,
|
||||
type => 'derive',
|
||||
}
|
||||
metric => $metric,
|
||||
id => $id,
|
||||
value => $value + 0,
|
||||
timestamp => $timestamp + 0,
|
||||
type => 'derive',
|
||||
};
|
||||
}
|
||||
|
||||
my $nodename = PVE::INotify::nodename();
|
||||
|
|
@ -91,8 +92,8 @@ my sub get_node_metrics {
|
|||
|
||||
my ($netin, $netout) = (0, 0);
|
||||
for my $dev (grep { /^$PVE::Network::PHYSICAL_NIC_RE$/ } keys $data->{nics}->%*) {
|
||||
$netin += $data->{nics}->{$dev}->{receive};
|
||||
$netout += $data->{nics}->{$dev}->{transmit};
|
||||
$netin += $data->{nics}->{$dev}->{receive};
|
||||
$netout += $data->{nics}->{$dev}->{transmit};
|
||||
}
|
||||
push @$metrics, derive($id, $timestamp, "net_in", $netin);
|
||||
push @$metrics, derive($id, $timestamp, "net_out", $netout);
|
||||
|
|
@ -127,24 +128,24 @@ my sub get_qemu_metrics {
|
|||
my $timestamp = $stats->{timestamp};
|
||||
|
||||
for my $vmid (keys $stats->{data}->%*) {
|
||||
my $id = "qemu/$vmid";
|
||||
my $guest_data = $stats->{data}->{$vmid};
|
||||
my $id = "qemu/$vmid";
|
||||
my $guest_data = $stats->{data}->{$vmid};
|
||||
|
||||
if ($guest_data->{status} eq 'running') {
|
||||
push @$metrics, gauge($id, $timestamp, "cpu_current", $guest_data->{cpu});
|
||||
push @$metrics, gauge($id, $timestamp, "mem_used", $guest_data->{mem});
|
||||
push @$metrics, derive($id, $timestamp, "disk_read", $guest_data->{diskread});
|
||||
push @$metrics, derive($id, $timestamp, "disk_write", $guest_data->{diskwrite});
|
||||
push @$metrics, derive($id, $timestamp, "net_in", $guest_data->{netin});
|
||||
push @$metrics, derive($id, $timestamp, "net_out", $guest_data->{netout});
|
||||
}
|
||||
if ($guest_data->{status} eq 'running') {
|
||||
push @$metrics, gauge($id, $timestamp, "cpu_current", $guest_data->{cpu});
|
||||
push @$metrics, gauge($id, $timestamp, "mem_used", $guest_data->{mem});
|
||||
push @$metrics, derive($id, $timestamp, "disk_read", $guest_data->{diskread});
|
||||
push @$metrics, derive($id, $timestamp, "disk_write", $guest_data->{diskwrite});
|
||||
push @$metrics, derive($id, $timestamp, "net_in", $guest_data->{netin});
|
||||
push @$metrics, derive($id, $timestamp, "net_out", $guest_data->{netout});
|
||||
}
|
||||
|
||||
push @$metrics, gauge($id, $timestamp, "uptime", $guest_data->{uptime});
|
||||
push @$metrics, gauge($id, $timestamp, "cpu_max", $guest_data->{cpus});
|
||||
push @$metrics, gauge($id, $timestamp, "mem_total", $guest_data->{maxmem});
|
||||
push @$metrics, gauge($id, $timestamp, "disk_total", $guest_data->{maxdisk});
|
||||
# TODO: This one always seems to be 0?
|
||||
# push @$metrics, num_metric("disk_used", $id, $guest_data->{disk}, $timestamp);
|
||||
push @$metrics, gauge($id, $timestamp, "uptime", $guest_data->{uptime});
|
||||
push @$metrics, gauge($id, $timestamp, "cpu_max", $guest_data->{cpus});
|
||||
push @$metrics, gauge($id, $timestamp, "mem_total", $guest_data->{maxmem});
|
||||
push @$metrics, gauge($id, $timestamp, "disk_total", $guest_data->{maxdisk});
|
||||
# TODO: This one always seems to be 0?
|
||||
# push @$metrics, num_metric("disk_used", $id, $guest_data->{disk}, $timestamp);
|
||||
}
|
||||
|
||||
return $metrics;
|
||||
|
|
@ -158,23 +159,23 @@ my sub get_lxc_metrics {
|
|||
my $timestamp = $stats->{timestamp};
|
||||
|
||||
for my $vmid (keys $stats->{data}->%*) {
|
||||
my $id = "lxc/$vmid";
|
||||
my $guest_data = $stats->{data}->{$vmid};
|
||||
my $id = "lxc/$vmid";
|
||||
my $guest_data = $stats->{data}->{$vmid};
|
||||
|
||||
if ($guest_data->{status} eq 'running') {
|
||||
push @$metrics, gauge($id, $timestamp, "cpu_current", $guest_data->{cpu});
|
||||
push @$metrics, gauge($id, $timestamp, "mem_used", $guest_data->{mem});
|
||||
push @$metrics, derive($id, $timestamp, "disk_read", $guest_data->{diskread});
|
||||
push @$metrics, derive($id, $timestamp, "disk_write", $guest_data->{diskwrite});
|
||||
push @$metrics, derive($id, $timestamp, "net_in", $guest_data->{netin});
|
||||
push @$metrics, derive($id, $timestamp, "net_out", $guest_data->{netout});
|
||||
push @$metrics, gauge($id, $timestamp, "disk_used", $guest_data->{disk});
|
||||
}
|
||||
if ($guest_data->{status} eq 'running') {
|
||||
push @$metrics, gauge($id, $timestamp, "cpu_current", $guest_data->{cpu});
|
||||
push @$metrics, gauge($id, $timestamp, "mem_used", $guest_data->{mem});
|
||||
push @$metrics, derive($id, $timestamp, "disk_read", $guest_data->{diskread});
|
||||
push @$metrics, derive($id, $timestamp, "disk_write", $guest_data->{diskwrite});
|
||||
push @$metrics, derive($id, $timestamp, "net_in", $guest_data->{netin});
|
||||
push @$metrics, derive($id, $timestamp, "net_out", $guest_data->{netout});
|
||||
push @$metrics, gauge($id, $timestamp, "disk_used", $guest_data->{disk});
|
||||
}
|
||||
|
||||
push @$metrics, gauge($id, $timestamp, "uptime", $guest_data->{uptime});
|
||||
push @$metrics, gauge($id, $timestamp, "cpu_max", $guest_data->{cpus});
|
||||
push @$metrics, gauge($id, $timestamp, "mem_total", $guest_data->{maxmem});
|
||||
push @$metrics, gauge($id, $timestamp, "disk_total", $guest_data->{maxdisk});
|
||||
push @$metrics, gauge($id, $timestamp, "uptime", $guest_data->{uptime});
|
||||
push @$metrics, gauge($id, $timestamp, "cpu_max", $guest_data->{cpus});
|
||||
push @$metrics, gauge($id, $timestamp, "mem_total", $guest_data->{maxmem});
|
||||
push @$metrics, gauge($id, $timestamp, "disk_total", $guest_data->{maxdisk});
|
||||
}
|
||||
|
||||
return $metrics;
|
||||
|
|
@ -188,11 +189,11 @@ my sub get_storage_metrics {
|
|||
my $timestamp = $stats->{timestamp};
|
||||
|
||||
for my $sid (keys $stats->{data}->%*) {
|
||||
my $id = "storage/$nodename/$sid";
|
||||
my $data = $stats->{data}->{$sid};
|
||||
my $id = "storage/$nodename/$sid";
|
||||
my $data = $stats->{data}->{$sid};
|
||||
|
||||
push @$metrics, gauge($id, $timestamp, "disk_total", $data->{total});
|
||||
push @$metrics, gauge($id, $timestamp, "disk_used", $data->{used});
|
||||
push @$metrics, gauge($id, $timestamp, "disk_total", $data->{total});
|
||||
push @$metrics, gauge($id, $timestamp, "disk_used", $data->{used});
|
||||
}
|
||||
|
||||
return $metrics;
|
||||
|
|
@ -213,10 +214,10 @@ sub get_local_metrics {
|
|||
my $data = $get_cache->()->get_last($history);
|
||||
|
||||
for my $stat_gen ($data->@*) {
|
||||
push @$metrics, get_node_metrics($stat_gen->{node})->@*;
|
||||
push @$metrics, get_qemu_metrics($stat_gen->{qemu})->@*;
|
||||
push @$metrics, get_lxc_metrics($stat_gen->{lxc})->@*;
|
||||
push @$metrics, get_storage_metrics($stat_gen->{storage})->@*;
|
||||
push @$metrics, get_node_metrics($stat_gen->{node})->@*;
|
||||
push @$metrics, get_qemu_metrics($stat_gen->{qemu})->@*;
|
||||
push @$metrics, get_lxc_metrics($stat_gen->{lxc})->@*;
|
||||
push @$metrics, get_storage_metrics($stat_gen->{storage})->@*;
|
||||
}
|
||||
|
||||
return $metrics;
|
||||
|
|
|
|||
325
PVE/Report.pm
325
PVE/Report.pm
|
|
@ -11,12 +11,16 @@ my sub dir2text {
|
|||
|
||||
print STDERR "dir2text '${target_dir}${regexp}'...";
|
||||
my $text = "# output '${target_dir}${regexp}' file(s)\n";
|
||||
PVE::Tools::dir_glob_foreach($target_dir, $regexp, sub {
|
||||
my ($file) = @_;
|
||||
return if $file eq '.' || $file eq '..';
|
||||
$text .= "\n# cat $target_dir$file\n";
|
||||
$text .= PVE::Tools::file_get_contents($target_dir.$file)."\n";
|
||||
});
|
||||
PVE::Tools::dir_glob_foreach(
|
||||
$target_dir,
|
||||
$regexp,
|
||||
sub {
|
||||
my ($file) = @_;
|
||||
return if $file eq '.' || $file eq '..';
|
||||
$text .= "\n# cat $target_dir$file\n";
|
||||
$text .= PVE::Tools::file_get_contents($target_dir . $file) . "\n";
|
||||
},
|
||||
);
|
||||
return $text;
|
||||
}
|
||||
|
||||
|
|
@ -25,147 +29,143 @@ my sub cmd_exists { system("command -v '$_[0]' > /dev/null 2>&1") == 0 }
|
|||
|
||||
my $init_report_cmds = sub {
|
||||
my $report_def = {
|
||||
general => {
|
||||
title => 'general system info',
|
||||
order => 10,
|
||||
cmds => [
|
||||
'hostname',
|
||||
'date -R',
|
||||
'cat /proc/cmdline',
|
||||
'pveversion --verbose',
|
||||
'cat /etc/hosts',
|
||||
'pvesubscription get',
|
||||
'cat /etc/apt/sources.list',
|
||||
sub { dir2text('/etc/apt/sources.list.d/', '.+\.list') },
|
||||
sub { dir2text('/etc/apt/sources.list.d/', '.+\.sources') },
|
||||
'apt-cache policy | grep -vP "^ +origin "',
|
||||
'apt-mark showhold',
|
||||
'lscpu',
|
||||
'pvesh get /cluster/resources --type node --output-format=yaml',
|
||||
],
|
||||
},
|
||||
'system-load' => {
|
||||
title => 'overall system load info',
|
||||
order => 20,
|
||||
cmds => [
|
||||
'top -b -c -w512 -n 1 -o TIME | head -n 30',
|
||||
'head /proc/pressure/*',
|
||||
],
|
||||
},
|
||||
storage => {
|
||||
order => 30,
|
||||
cmds => [
|
||||
'cat /etc/pve/storage.cfg',
|
||||
'pvesm status',
|
||||
'cat /etc/fstab',
|
||||
'findmnt --ascii',
|
||||
'df --human -T',
|
||||
'proxmox-boot-tool status',
|
||||
],
|
||||
},
|
||||
'virtual guests' => {
|
||||
order => 40,
|
||||
cmds => [
|
||||
'qm list',
|
||||
sub { dir2text('/etc/pve/qemu-server/', '\d+\.conf') },
|
||||
'pct list',
|
||||
sub { dir2text('/etc/pve/lxc/', '\d+\.conf') },
|
||||
],
|
||||
},
|
||||
network => {
|
||||
order => 45,
|
||||
cmds => [
|
||||
'ip -details -statistics address',
|
||||
'ip -details -4 route show',
|
||||
'ip -details -6 route show',
|
||||
'cat /etc/network/interfaces',
|
||||
sub { dir2text('/etc/network/interfaces.d/', '.*') },
|
||||
'cat /etc/pve/sdn/.running-config',
|
||||
sub { dir2text('/etc/pve/sdn/', '.+\.cfg') },
|
||||
sub { dir2text('/etc/pve/sdn/', '.+\.json') },
|
||||
],
|
||||
},
|
||||
firewall => {
|
||||
order => 50,
|
||||
cmds => [
|
||||
sub { dir2text('/etc/pve/firewall/', '.+\.fw') },
|
||||
'cat /etc/pve/local/host.fw',
|
||||
sub { dir2text('/etc/pve/sdn/firewall/', '.+\.fw') },
|
||||
'iptables-save -c | column -t -l4 -o" "',
|
||||
],
|
||||
},
|
||||
cluster => {
|
||||
order => 60,
|
||||
cmds => [
|
||||
'pvecm nodes',
|
||||
'pvecm status',
|
||||
'cat /etc/pve/corosync.conf 2>/dev/null',
|
||||
'ha-manager status',
|
||||
'cat /etc/pve/datacenter.cfg',
|
||||
],
|
||||
},
|
||||
jobs => {
|
||||
order => 65,
|
||||
cmds => [
|
||||
'cat /etc/pve/jobs.cfg',
|
||||
],
|
||||
},
|
||||
hardware => {
|
||||
order => 70,
|
||||
cmds => [
|
||||
'dmidecode -t bios',
|
||||
'lspci -nnk',
|
||||
],
|
||||
},
|
||||
'block devices' => {
|
||||
order => 80,
|
||||
cmds => [
|
||||
'lsblk --ascii -M -o +HOTPLUG,ROTA,PHY-SEC,FSTYPE,MODEL,TRAN,WWN',
|
||||
'ls -l /dev/disk/by-*/',
|
||||
'iscsiadm -m node',
|
||||
'iscsiadm -m session',
|
||||
],
|
||||
},
|
||||
volumes => {
|
||||
order => 90,
|
||||
cmds => [
|
||||
'pvs',
|
||||
'lvs',
|
||||
'vgs',
|
||||
],
|
||||
},
|
||||
general => {
|
||||
title => 'general system info',
|
||||
order => 10,
|
||||
cmds => [
|
||||
'hostname',
|
||||
'date -R',
|
||||
'cat /proc/cmdline',
|
||||
'pveversion --verbose',
|
||||
'cat /etc/hosts',
|
||||
'pvesubscription get',
|
||||
'cat /etc/apt/sources.list',
|
||||
sub { dir2text('/etc/apt/sources.list.d/', '.+\.list') },
|
||||
sub { dir2text('/etc/apt/sources.list.d/', '.+\.sources') },
|
||||
'apt-cache policy | grep -vP "^ +origin "',
|
||||
'apt-mark showhold',
|
||||
'lscpu',
|
||||
'pvesh get /cluster/resources --type node --output-format=yaml',
|
||||
],
|
||||
},
|
||||
'system-load' => {
|
||||
title => 'overall system load info',
|
||||
order => 20,
|
||||
cmds => [
|
||||
'top -b -c -w512 -n 1 -o TIME | head -n 30', 'head /proc/pressure/*',
|
||||
],
|
||||
},
|
||||
storage => {
|
||||
order => 30,
|
||||
cmds => [
|
||||
'cat /etc/pve/storage.cfg',
|
||||
'pvesm status',
|
||||
'cat /etc/fstab',
|
||||
'findmnt --ascii',
|
||||
'df --human -T',
|
||||
'proxmox-boot-tool status',
|
||||
],
|
||||
},
|
||||
'virtual guests' => {
|
||||
order => 40,
|
||||
cmds => [
|
||||
'qm list',
|
||||
sub { dir2text('/etc/pve/qemu-server/', '\d+\.conf') },
|
||||
'pct list',
|
||||
sub { dir2text('/etc/pve/lxc/', '\d+\.conf') },
|
||||
],
|
||||
},
|
||||
network => {
|
||||
order => 45,
|
||||
cmds => [
|
||||
'ip -details -statistics address',
|
||||
'ip -details -4 route show',
|
||||
'ip -details -6 route show',
|
||||
'cat /etc/network/interfaces',
|
||||
sub { dir2text('/etc/network/interfaces.d/', '.*') },
|
||||
'cat /etc/pve/sdn/.running-config',
|
||||
sub { dir2text('/etc/pve/sdn/', '.+\.cfg') },
|
||||
sub { dir2text('/etc/pve/sdn/', '.+\.json') },
|
||||
],
|
||||
},
|
||||
firewall => {
|
||||
order => 50,
|
||||
cmds => [
|
||||
sub { dir2text('/etc/pve/firewall/', '.+\.fw') },
|
||||
'cat /etc/pve/local/host.fw',
|
||||
sub { dir2text('/etc/pve/sdn/firewall/', '.+\.fw') },
|
||||
'iptables-save -c | column -t -l4 -o" "',
|
||||
],
|
||||
},
|
||||
cluster => {
|
||||
order => 60,
|
||||
cmds => [
|
||||
'pvecm nodes',
|
||||
'pvecm status',
|
||||
'cat /etc/pve/corosync.conf 2>/dev/null',
|
||||
'ha-manager status',
|
||||
'cat /etc/pve/datacenter.cfg',
|
||||
],
|
||||
},
|
||||
jobs => {
|
||||
order => 65,
|
||||
cmds => [
|
||||
'cat /etc/pve/jobs.cfg',
|
||||
],
|
||||
},
|
||||
hardware => {
|
||||
order => 70,
|
||||
cmds => [
|
||||
'dmidecode -t bios', 'lspci -nnk',
|
||||
],
|
||||
},
|
||||
'block devices' => {
|
||||
order => 80,
|
||||
cmds => [
|
||||
'lsblk --ascii -M -o +HOTPLUG,ROTA,PHY-SEC,FSTYPE,MODEL,TRAN,WWN',
|
||||
'ls -l /dev/disk/by-*/',
|
||||
'iscsiadm -m node',
|
||||
'iscsiadm -m session',
|
||||
],
|
||||
},
|
||||
volumes => {
|
||||
order => 90,
|
||||
cmds => [
|
||||
'pvs', 'lvs', 'vgs',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
if (cmd_exists('zfs')) {
|
||||
push @{$report_def->{volumes}->{cmds}},
|
||||
'zpool status',
|
||||
'zpool list -v',
|
||||
'zfs list',
|
||||
'arcstat',
|
||||
;
|
||||
push @{ $report_def->{volumes}->{cmds} },
|
||||
'zpool status',
|
||||
'zpool list -v',
|
||||
'zfs list',
|
||||
'arcstat',
|
||||
;
|
||||
}
|
||||
|
||||
if (-e '/etc/ceph/ceph.conf') {
|
||||
push @{$report_def->{volumes}->{cmds}},
|
||||
'pveceph status',
|
||||
'ceph osd status',
|
||||
'ceph df',
|
||||
'ceph osd df tree',
|
||||
'ceph device ls',
|
||||
'cat /etc/ceph/ceph.conf',
|
||||
'ceph config dump',
|
||||
'pveceph pool ls',
|
||||
'ceph versions',
|
||||
'ceph health detail',
|
||||
;
|
||||
push @{ $report_def->{volumes}->{cmds} },
|
||||
'pveceph status',
|
||||
'ceph osd status',
|
||||
'ceph df',
|
||||
'ceph osd df tree',
|
||||
'ceph device ls',
|
||||
'cat /etc/ceph/ceph.conf',
|
||||
'ceph config dump',
|
||||
'pveceph pool ls',
|
||||
'ceph versions',
|
||||
'ceph health detail',
|
||||
;
|
||||
}
|
||||
|
||||
if (cmd_exists('multipath')) {
|
||||
push @{$report_def->{disks}->{cmds}},
|
||||
'cat /etc/multipath.conf',
|
||||
'cat /etc/multipath/wwids',
|
||||
'multipath -ll',
|
||||
;
|
||||
push @{ $report_def->{disks}->{cmds} },
|
||||
'cat /etc/multipath.conf',
|
||||
'cat /etc/multipath/wwids',
|
||||
'multipath -ll',
|
||||
;
|
||||
}
|
||||
|
||||
return $report_def;
|
||||
|
|
@ -176,40 +176,41 @@ sub generate {
|
|||
|
||||
my $report = '';
|
||||
my $record_output = sub {
|
||||
$report .= shift . "\n";
|
||||
$report .= shift . "\n";
|
||||
};
|
||||
|
||||
local $ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin';
|
||||
my $cmd_timeout = 10; # generous timeout
|
||||
|
||||
my $run_cmd_params = {
|
||||
outfunc => $record_output,
|
||||
errfunc => $record_output,
|
||||
timeout => $cmd_timeout,
|
||||
noerr => 1, # avoid checking programs exit code
|
||||
outfunc => $record_output,
|
||||
errfunc => $record_output,
|
||||
timeout => $cmd_timeout,
|
||||
noerr => 1, # avoid checking programs exit code
|
||||
};
|
||||
|
||||
my $sorter = sub { ($def->{$_[0]}->{order} // 1<<30) <=> ($def->{$_[1]}->{order} // 1<<30) };
|
||||
my $sorter =
|
||||
sub { ($def->{ $_[0] }->{order} // 1 << 30) <=> ($def->{ $_[1] }->{order} // 1 << 30) };
|
||||
|
||||
for my $section ( sort { $sorter->($a, $b) } keys %$def) {
|
||||
my $s = $def->{$section};
|
||||
my $title = $s->{title} // "info about $section";
|
||||
for my $section (sort { $sorter->($a, $b) } keys %$def) {
|
||||
my $s = $def->{$section};
|
||||
my $title = $s->{title} // "info about $section";
|
||||
|
||||
$report .= "\n==== $title ====\n";
|
||||
for my $command (@{$s->{cmds}}) {
|
||||
eval {
|
||||
if (ref $command eq 'CODE') {
|
||||
$report .= PVE::Tools::run_with_timeout($cmd_timeout, $command);
|
||||
} else {
|
||||
print STDERR "Process ".$command."...";
|
||||
$report .= "\n# $command\n";
|
||||
PVE::Tools::run_command($command, %$run_cmd_params);
|
||||
}
|
||||
print STDERR "OK";
|
||||
};
|
||||
print STDERR "\n";
|
||||
$report .= "\nERROR: $@\n" if $@;
|
||||
}
|
||||
$report .= "\n==== $title ====\n";
|
||||
for my $command (@{ $s->{cmds} }) {
|
||||
eval {
|
||||
if (ref $command eq 'CODE') {
|
||||
$report .= PVE::Tools::run_with_timeout($cmd_timeout, $command);
|
||||
} else {
|
||||
print STDERR "Process " . $command . "...";
|
||||
$report .= "\n# $command\n";
|
||||
PVE::Tools::run_command($command, %$run_cmd_params);
|
||||
}
|
||||
print STDERR "OK";
|
||||
};
|
||||
print STDERR "\n";
|
||||
$report .= "\nERROR: $@\n" if $@;
|
||||
}
|
||||
}
|
||||
|
||||
return $report;
|
||||
|
|
|
|||
|
|
@ -28,27 +28,27 @@ sub init {
|
|||
|
||||
my $accept_lock_fn = "/var/lock/pvedaemon.lck";
|
||||
|
||||
my $lockfh = IO::File->new(">>${accept_lock_fn}") ||
|
||||
die "unable to open lock file '${accept_lock_fn}' - $!\n";
|
||||
my $lockfh = IO::File->new(">>${accept_lock_fn}")
|
||||
|| die "unable to open lock file '${accept_lock_fn}' - $!\n";
|
||||
|
||||
my $socket = $self->create_reusable_socket(85, '127.0.0.1');
|
||||
|
||||
$self->{server_config} = {
|
||||
keep_alive => 100,
|
||||
max_conn => 500,
|
||||
max_requests => 1000,
|
||||
lockfile => $accept_lock_fn,
|
||||
socket => $socket,
|
||||
lockfh => $lockfh,
|
||||
debug => $self->{debug},
|
||||
trusted_env => 1,
|
||||
keep_alive => 100,
|
||||
max_conn => 500,
|
||||
max_requests => 1000,
|
||||
lockfile => $accept_lock_fn,
|
||||
socket => $socket,
|
||||
lockfh => $lockfh,
|
||||
debug => $self->{debug},
|
||||
trusted_env => 1,
|
||||
};
|
||||
}
|
||||
|
||||
sub run {
|
||||
my ($self) = @_;
|
||||
|
||||
my $server = PVE::HTTPServer->new(%{$self->{server_config}});
|
||||
my $server = PVE::HTTPServer->new(%{ $self->{server_config} });
|
||||
$server->run();
|
||||
}
|
||||
|
||||
|
|
@ -58,10 +58,10 @@ $daemon->register_stop_command();
|
|||
$daemon->register_status_command();
|
||||
|
||||
our $cmddef = {
|
||||
start => [ __PACKAGE__, 'start', []],
|
||||
restart => [ __PACKAGE__, 'restart', []],
|
||||
stop => [ __PACKAGE__, 'stop', []],
|
||||
status => [ __PACKAGE__, 'status', [], undef, sub { print shift . "\n";} ],
|
||||
start => [__PACKAGE__, 'start', []],
|
||||
restart => [__PACKAGE__, 'restart', []],
|
||||
stop => [__PACKAGE__, 'stop', []],
|
||||
status => [__PACKAGE__, 'status', [], undef, sub { print shift . "\n"; }],
|
||||
};
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ sub init {
|
|||
|
||||
my $accept_lock_fn = "/var/lock/pveproxy.lck";
|
||||
|
||||
my $lockfh = IO::File->new(">>${accept_lock_fn}") ||
|
||||
die "unable to open lock file '${accept_lock_fn}' - $!\n";
|
||||
my $lockfh = IO::File->new(">>${accept_lock_fn}")
|
||||
|| die "unable to open lock file '${accept_lock_fn}' - $!\n";
|
||||
|
||||
my $listen_ip = $proxyconf->{LISTEN_IP};
|
||||
my $socket = $self->create_reusable_socket(8006, $listen_ip);
|
||||
|
|
@ -78,13 +78,13 @@ sub init {
|
|||
add_dirs($dirs, '/novnc/' => "$basedirs->{novnc}/");
|
||||
add_dirs($dirs, '/pve-docs/' => "$basedirs->{docs}/");
|
||||
add_dirs($dirs, '/pve-docs/api-viewer/extjs/' => "$basedirs->{extjs}/");
|
||||
add_dirs($dirs, '/pve2/css/' => "$basedirs->{manager}/css/");
|
||||
add_dirs($dirs, '/pve2/css/' => "$basedirs->{manager}/css/");
|
||||
add_dirs($dirs, '/pve2/ext6/', "$basedirs->{extjs}/");
|
||||
add_dirs($dirs, '/pve2/fa/css/' => "$basedirs->{fontawesome}/css/");
|
||||
add_dirs($dirs, '/pve2/fa/fonts/' => "$basedirs->{fontawesome}/fonts/");
|
||||
add_dirs($dirs, '/pve2/font-logos/' => "$basedirs->{fontlogos}/");
|
||||
add_dirs($dirs, '/pve2/images/' => "$basedirs->{manager}/images/");
|
||||
add_dirs($dirs, '/pve2/js/' => "$basedirs->{manager}/js/");
|
||||
add_dirs($dirs, '/pve2/fa/css/' => "$basedirs->{fontawesome}/css/");
|
||||
add_dirs($dirs, '/pve2/fa/fonts/' => "$basedirs->{fontawesome}/fonts/");
|
||||
add_dirs($dirs, '/pve2/font-logos/' => "$basedirs->{fontlogos}/");
|
||||
add_dirs($dirs, '/pve2/images/' => "$basedirs->{manager}/images/");
|
||||
add_dirs($dirs, '/pve2/js/' => "$basedirs->{manager}/js/");
|
||||
add_dirs($dirs, '/pve2/locale/', "$basedirs->{i18n}/");
|
||||
add_dirs($dirs, '/pve2/sencha-touch/', "$basedirs->{sencha_touch}/");
|
||||
add_dirs($dirs, '/pve2/touch/', "$basedirs->{manager}/touch/");
|
||||
|
|
@ -94,70 +94,73 @@ sub init {
|
|||
add_dirs($dirs, '/xtermjs/' => "$basedirs->{xtermjs}/");
|
||||
|
||||
$self->{server_config} = {
|
||||
title => 'Proxmox VE API',
|
||||
keep_alive => 100,
|
||||
max_conn => 500,
|
||||
max_requests => 1000,
|
||||
lockfile => $accept_lock_fn,
|
||||
socket => $socket,
|
||||
lockfh => $lockfh,
|
||||
debug => $self->{debug},
|
||||
trusted_env => 0, # not trusted, anyone can connect
|
||||
logfile => '/var/log/pveproxy/access.log',
|
||||
allow_from => $proxyconf->{ALLOW_FROM},
|
||||
deny_from => $proxyconf->{DENY_FROM},
|
||||
policy => $proxyconf->{POLICY},
|
||||
ssl => {
|
||||
cipher_list => $proxyconf->{CIPHERS},
|
||||
ciphersuites => $proxyconf->{CIPHERSUITES},
|
||||
key_file => '/etc/pve/local/pve-ssl.key',
|
||||
cert_file => '/etc/pve/local/pve-ssl.pem',
|
||||
honor_cipher_order => $proxyconf->{HONOR_CIPHER_ORDER},
|
||||
},
|
||||
compression => $proxyconf->{COMPRESSION},
|
||||
proxy_real_ip_header => $proxyconf->{PROXY_REAL_IP_HEADER},
|
||||
proxy_real_ip_allow_from => $proxyconf->{PROXY_REAL_IP_ALLOW_FROM},
|
||||
# Note: there is no authentication for those pages and dirs!
|
||||
pages => {
|
||||
'/' => sub { get_index($self->{nodename}, @_) },
|
||||
# avoid authentication when accessing favicon
|
||||
'/favicon.ico' => {
|
||||
file => "$basedirs->{manager}/images/favicon.ico",
|
||||
},
|
||||
'/proxmoxlib.js' => {
|
||||
file => "$basedirs->{widgettoolkit}/proxmoxlib.js",
|
||||
},
|
||||
'/qrcode.min.js' => {
|
||||
file => '/usr/share/javascript/qrcodejs/qrcode.min.js',
|
||||
},
|
||||
},
|
||||
dirs => $dirs,
|
||||
title => 'Proxmox VE API',
|
||||
keep_alive => 100,
|
||||
max_conn => 500,
|
||||
max_requests => 1000,
|
||||
lockfile => $accept_lock_fn,
|
||||
socket => $socket,
|
||||
lockfh => $lockfh,
|
||||
debug => $self->{debug},
|
||||
trusted_env => 0, # not trusted, anyone can connect
|
||||
logfile => '/var/log/pveproxy/access.log',
|
||||
allow_from => $proxyconf->{ALLOW_FROM},
|
||||
deny_from => $proxyconf->{DENY_FROM},
|
||||
policy => $proxyconf->{POLICY},
|
||||
ssl => {
|
||||
cipher_list => $proxyconf->{CIPHERS},
|
||||
ciphersuites => $proxyconf->{CIPHERSUITES},
|
||||
key_file => '/etc/pve/local/pve-ssl.key',
|
||||
cert_file => '/etc/pve/local/pve-ssl.pem',
|
||||
honor_cipher_order => $proxyconf->{HONOR_CIPHER_ORDER},
|
||||
},
|
||||
compression => $proxyconf->{COMPRESSION},
|
||||
proxy_real_ip_header => $proxyconf->{PROXY_REAL_IP_HEADER},
|
||||
proxy_real_ip_allow_from => $proxyconf->{PROXY_REAL_IP_ALLOW_FROM},
|
||||
# Note: there is no authentication for those pages and dirs!
|
||||
pages => {
|
||||
'/' => sub { get_index($self->{nodename}, @_) },
|
||||
# avoid authentication when accessing favicon
|
||||
'/favicon.ico' => {
|
||||
file => "$basedirs->{manager}/images/favicon.ico",
|
||||
},
|
||||
'/proxmoxlib.js' => {
|
||||
file => "$basedirs->{widgettoolkit}/proxmoxlib.js",
|
||||
},
|
||||
'/qrcode.min.js' => {
|
||||
file => '/usr/share/javascript/qrcodejs/qrcode.min.js',
|
||||
},
|
||||
},
|
||||
dirs => $dirs,
|
||||
};
|
||||
|
||||
if (defined($proxyconf->{DHPARAMS})) {
|
||||
$self->{server_config}->{ssl}->{dh_file} = $proxyconf->{DHPARAMS};
|
||||
$self->{server_config}->{ssl}->{dh_file} = $proxyconf->{DHPARAMS};
|
||||
}
|
||||
if (defined($proxyconf->{DISABLE_TLS_1_2})) {
|
||||
$self->{server_config}->{ssl}->{tlsv1_2} = !$proxyconf->{DISABLE_TLS_1_2};
|
||||
$self->{server_config}->{ssl}->{tlsv1_2} = !$proxyconf->{DISABLE_TLS_1_2};
|
||||
}
|
||||
if (defined($proxyconf->{DISABLE_TLS_1_3})) {
|
||||
$self->{server_config}->{ssl}->{tlsv1_3} = !$proxyconf->{DISABLE_TLS_1_3};
|
||||
$self->{server_config}->{ssl}->{tlsv1_3} = !$proxyconf->{DISABLE_TLS_1_3};
|
||||
}
|
||||
my $custom_key_path = '/etc/pve/local/pveproxy-ssl.key';
|
||||
if (defined($proxyconf->{TLS_KEY_FILE})) {
|
||||
$custom_key_path = $proxyconf->{TLS_KEY_FILE};
|
||||
$custom_key_path = $proxyconf->{TLS_KEY_FILE};
|
||||
}
|
||||
if (-f '/etc/pve/local/pveproxy-ssl.pem' && -f $custom_key_path) {
|
||||
$self->{server_config}->{ssl}->{cert_file} = '/etc/pve/local/pveproxy-ssl.pem';
|
||||
$self->{server_config}->{ssl}->{key_file} = $custom_key_path;
|
||||
syslog('info', 'Using \'/etc/pve/local/pveproxy-ssl.pem\' as certificate for the web interface.');
|
||||
$self->{server_config}->{ssl}->{cert_file} = '/etc/pve/local/pveproxy-ssl.pem';
|
||||
$self->{server_config}->{ssl}->{key_file} = $custom_key_path;
|
||||
syslog(
|
||||
'info',
|
||||
'Using \'/etc/pve/local/pveproxy-ssl.pem\' as certificate for the web interface.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
sub run {
|
||||
my ($self) = @_;
|
||||
|
||||
my $server = PVE::HTTPServer->new(%{$self->{server_config}});
|
||||
my $server = PVE::HTTPServer->new(%{ $self->{server_config} });
|
||||
$server->run();
|
||||
}
|
||||
|
||||
|
|
@ -167,10 +170,10 @@ $daemon->register_stop_command();
|
|||
$daemon->register_status_command();
|
||||
|
||||
our $cmddef = {
|
||||
start => [ __PACKAGE__, 'start', []],
|
||||
restart => [ __PACKAGE__, 'restart', []],
|
||||
stop => [ __PACKAGE__, 'stop', []],
|
||||
status => [ __PACKAGE__, 'status', [], undef, sub { print shift . "\n";} ],
|
||||
start => [__PACKAGE__, 'start', []],
|
||||
restart => [__PACKAGE__, 'restart', []],
|
||||
stop => [__PACKAGE__, 'stop', []],
|
||||
status => [__PACKAGE__, 'status', [], undef, sub { print shift . "\n"; }],
|
||||
};
|
||||
|
||||
sub is_phone {
|
||||
|
|
@ -181,8 +184,8 @@ sub is_phone {
|
|||
return 1 if $ua =~ m/(iPhone|iPod|Windows Phone)/;
|
||||
|
||||
if ($ua =~ m/Mobile(\/|\s)/) {
|
||||
return 1 if $ua =~ m/(BlackBerry|BB)/;
|
||||
return 1 if ($ua =~ m/(Android)/) && ($ua !~ m/(Silk)/);
|
||||
return 1 if $ua =~ m/(BlackBerry|BB)/;
|
||||
return 1 if ($ua =~ m/(Android)/) && ($ua !~ m/(Silk)/);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -200,38 +203,40 @@ sub get_index {
|
|||
my $theme = "auto";
|
||||
|
||||
if (my $cookie = $r->header('Cookie')) {
|
||||
if (my $newlang = ($cookie =~ /(?:^|\s)PVELangCookie=([^;]*)/)[0]) {
|
||||
if ($newlang =~ m/^[a-z]{2,3}(_[A-Z]{2,3})?$/) {
|
||||
$lang = $newlang;
|
||||
}
|
||||
}
|
||||
if (my $newlang = ($cookie =~ /(?:^|\s)PVELangCookie=([^;]*)/)[0]) {
|
||||
if ($newlang =~ m/^[a-z]{2,3}(_[A-Z]{2,3})?$/) {
|
||||
$lang = $newlang;
|
||||
}
|
||||
}
|
||||
|
||||
if (my $newtheme = ($cookie =~ /(?:^|\s)PVEThemeCookie=([^;]*)/)[0]) {
|
||||
# theme names need to be kebab case, with each segment a maximum of 10 characters long
|
||||
# and at most 6 segments
|
||||
if ($newtheme =~ m/^[a-z]{1,10}(-[a-z]{1,10}){0,5}$/) {
|
||||
$theme = $newtheme;
|
||||
}
|
||||
}
|
||||
if (my $newtheme = ($cookie =~ /(?:^|\s)PVEThemeCookie=([^;]*)/)[0]) {
|
||||
# theme names need to be kebab case, with each segment a maximum of 10 characters long
|
||||
# and at most 6 segments
|
||||
if ($newtheme =~ m/^[a-z]{1,10}(-[a-z]{1,10}){0,5}$/) {
|
||||
$theme = $newtheme;
|
||||
}
|
||||
}
|
||||
|
||||
my $ticket = PVE::APIServer::Formatter::extract_auth_value($cookie, $server->{cookie_name});
|
||||
if (($username = PVE::AccessControl::verify_ticket($ticket, 1))) {
|
||||
$token = PVE::AccessControl::assemble_csrf_prevention_token($username);
|
||||
}
|
||||
my $ticket = PVE::APIServer::Formatter::extract_auth_value($cookie, $server->{cookie_name});
|
||||
if (($username = PVE::AccessControl::verify_ticket($ticket, 1))) {
|
||||
$token = PVE::AccessControl::assemble_csrf_prevention_token($username);
|
||||
}
|
||||
}
|
||||
|
||||
my $consent_text;
|
||||
eval {
|
||||
my $dc_conf = PVE::Cluster::cfs_read_file('datacenter.cfg');
|
||||
$consent_text = $dc_conf->{'consent-text'};
|
||||
my $dc_conf = PVE::Cluster::cfs_read_file('datacenter.cfg');
|
||||
$consent_text = $dc_conf->{'consent-text'};
|
||||
|
||||
if (!$lang) {
|
||||
$lang = $dc_conf->{language} // 'en';
|
||||
}
|
||||
if (!$lang) {
|
||||
$lang = $dc_conf->{language} // 'en';
|
||||
}
|
||||
};
|
||||
warn "$@\n" if $@;
|
||||
|
||||
my $mobile = (is_phone($r->header('User-Agent')) && (!defined($args->{mobile}) || $args->{mobile})) || $args->{mobile};
|
||||
my $mobile =
|
||||
(is_phone($r->header('User-Agent')) && (!defined($args->{mobile}) || $args->{mobile}))
|
||||
|| $args->{mobile};
|
||||
|
||||
my $novnc = defined($args->{console}) && $args->{novnc};
|
||||
my $xtermjs = defined($args->{console}) && $args->{xtermjs};
|
||||
|
|
@ -245,36 +250,36 @@ sub get_index {
|
|||
|
||||
my $debug = $server->{debug};
|
||||
if (exists $args->{debug}) {
|
||||
$debug = !defined($args->{debug}) || $args->{debug};
|
||||
$debug = !defined($args->{debug}) || $args->{debug};
|
||||
}
|
||||
|
||||
my $vars = {
|
||||
lang => $lang,
|
||||
langfile => $langfile,
|
||||
username => $username || '',
|
||||
token => $token,
|
||||
console => $args->{console},
|
||||
nodename => $nodename,
|
||||
debug => $debug,
|
||||
version => "$version",
|
||||
wtversion => $wtversion,
|
||||
theme => $theme,
|
||||
consenttext => $consent_text
|
||||
lang => $lang,
|
||||
langfile => $langfile,
|
||||
username => $username || '',
|
||||
token => $token,
|
||||
console => $args->{console},
|
||||
nodename => $nodename,
|
||||
debug => $debug,
|
||||
version => "$version",
|
||||
wtversion => $wtversion,
|
||||
theme => $theme,
|
||||
consenttext => $consent_text,
|
||||
};
|
||||
|
||||
# by default, load the normal index
|
||||
my $dir = $basedirs->{manager};
|
||||
|
||||
if ($novnc) {
|
||||
$dir = $basedirs->{novnc};
|
||||
$dir = $basedirs->{novnc};
|
||||
} elsif ($xtermjs) {
|
||||
$dir = $basedirs->{xtermjs};
|
||||
$dir = $basedirs->{xtermjs};
|
||||
} elsif ($mobile) {
|
||||
$dir = "$basedirs->{manager}/touch";
|
||||
$dir = "$basedirs->{manager}/touch";
|
||||
}
|
||||
|
||||
my $page = '';
|
||||
my $template = Template->new({ABSOLUTE => 1});
|
||||
my $template = Template->new({ ABSOLUTE => 1 });
|
||||
|
||||
$template->process("$dir/index.html.tpl", $vars, \$page) || die $template->error(), "\n";
|
||||
|
||||
|
|
|
|||
|
|
@ -21,28 +21,28 @@ my @JOB_TYPES = qw(replication jobs);
|
|||
|
||||
my sub running_job_pids : prototype($) {
|
||||
my ($self) = @_;
|
||||
my $pids = [ map { keys $_->%* } values $self->{jobs}->%* ];
|
||||
my $pids = [map { keys $_->%* } values $self->{jobs}->%*];
|
||||
return scalar($pids->@*) ? $pids : undef;
|
||||
}
|
||||
|
||||
my sub finish_jobs : prototype($) {
|
||||
my ($self) = @_;
|
||||
for my $type (@JOB_TYPES) {
|
||||
for my $cpid (keys $self->{jobs}->{$type}->%*) {
|
||||
if (my $waitpid = waitpid($cpid, WNOHANG)) {
|
||||
delete $self->{jobs}->{$type}->{$cpid} if $waitpid == $cpid || $waitpid == -1;
|
||||
}
|
||||
}
|
||||
for my $cpid (keys $self->{jobs}->{$type}->%*) {
|
||||
if (my $waitpid = waitpid($cpid, WNOHANG)) {
|
||||
delete $self->{jobs}->{$type}->{$cpid} if $waitpid == $cpid || $waitpid == -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
sub hup {
|
||||
my ($self) = @_;
|
||||
|
||||
my $old_workers = "";
|
||||
for my $type (@JOB_TYPES) {
|
||||
my $worker = $self->{jobs}->{$type} // next;
|
||||
$old_workers .= "$type:$_;" for keys $worker->%*;
|
||||
my $worker = $self->{jobs}->{$type} // next;
|
||||
$old_workers .= "$type:$_;" for keys $worker->%*;
|
||||
}
|
||||
$ENV{"PVE_DAEMON_WORKER_PIDS"} = $old_workers;
|
||||
$self->{got_hup_signal} = 1;
|
||||
|
|
@ -56,107 +56,111 @@ sub run {
|
|||
|
||||
# modelled after PVE::Daemons logic, but with type added to PID
|
||||
if (my $wpids = $ENV{PVE_DAEMON_WORKER_PIDS}) {
|
||||
print STDERR "got workers from previous daemon run: $wpids\n"; # FIXME: only log on debug?
|
||||
for my $pid (split(';', $wpids)) {
|
||||
if ($pid =~ m/^(\w+):(\d+)$/) { # check & untaint
|
||||
$self->{jobs}->{$1}->{$2} = 1;
|
||||
} else {
|
||||
warn "could not parse previous pid entry '$pid', ignoring\n";
|
||||
}
|
||||
}
|
||||
print STDERR "got workers from previous daemon run: $wpids\n"; # FIXME: only log on debug?
|
||||
for my $pid (split(';', $wpids)) {
|
||||
if ($pid =~ m/^(\w+):(\d+)$/) { # check & untaint
|
||||
$self->{jobs}->{$1}->{$2} = 1;
|
||||
} else {
|
||||
warn "could not parse previous pid entry '$pid', ignoring\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $old_sig_chld = $SIG{CHLD};
|
||||
local $SIG{CHLD} = sub {
|
||||
local ($@, $!, $?); # do not overwrite error vars
|
||||
finish_jobs($self);
|
||||
$old_sig_chld->(@_) if $old_sig_chld;
|
||||
local ($@, $!, $?); # do not overwrite error vars
|
||||
finish_jobs($self);
|
||||
$old_sig_chld->(@_) if $old_sig_chld;
|
||||
};
|
||||
|
||||
my $fork = sub {
|
||||
my ($type, $sub) = @_;
|
||||
my ($type, $sub) = @_;
|
||||
|
||||
# don't fork again if the previous iteration still runs
|
||||
# FIXME: some job types may handle this better themself or just not care - make configurable
|
||||
return if scalar(keys $self->{jobs}->{$type}->%*);
|
||||
# don't fork again if the previous iteration still runs
|
||||
# FIXME: some job types may handle this better themself or just not care - make configurable
|
||||
return if scalar(keys $self->{jobs}->{$type}->%*);
|
||||
|
||||
my $child = fork();
|
||||
if (!defined($child)) {
|
||||
die "fork failed: $!\n";
|
||||
} elsif ($child == 0) {
|
||||
$self->after_fork_cleanup();
|
||||
eval {
|
||||
$sub->();
|
||||
};
|
||||
if (my $err = $@) {
|
||||
syslog('err', "$type: $err");
|
||||
}
|
||||
POSIX::_exit(0);
|
||||
}
|
||||
my $child = fork();
|
||||
if (!defined($child)) {
|
||||
die "fork failed: $!\n";
|
||||
} elsif ($child == 0) {
|
||||
$self->after_fork_cleanup();
|
||||
eval { $sub->(); };
|
||||
if (my $err = $@) {
|
||||
syslog('err', "$type: $err");
|
||||
}
|
||||
POSIX::_exit(0);
|
||||
}
|
||||
|
||||
$jobs->{$type}->{$child} = 1;
|
||||
$jobs->{$type}->{$child} = 1;
|
||||
};
|
||||
|
||||
my $first_run = 1;
|
||||
|
||||
my $run_jobs = sub {
|
||||
# TODO: actually integrate replication in PVE::Jobs and do not always fork here, we could
|
||||
# do the state lookup and check if there's new work scheduled before doing so, e.g., by
|
||||
# extending the PVE::Jobs interfacae e.g.;
|
||||
# my $scheduled_jobs = PVE::Jobs::get_pending() or return;
|
||||
# forked { PVE::Jobs::run_jobs($scheduled_jobs) }
|
||||
# TODO: actually integrate replication in PVE::Jobs and do not always fork here, we could
|
||||
# do the state lookup and check if there's new work scheduled before doing so, e.g., by
|
||||
# extending the PVE::Jobs interfacae e.g.;
|
||||
# my $scheduled_jobs = PVE::Jobs::get_pending() or return;
|
||||
# forked { PVE::Jobs::run_jobs($scheduled_jobs) }
|
||||
|
||||
$fork->('replication', sub {
|
||||
PVE::API2::Replication::run_jobs(undef, sub {}, 0, 1);
|
||||
});
|
||||
$fork->(
|
||||
'replication',
|
||||
sub {
|
||||
PVE::API2::Replication::run_jobs(undef, sub { }, 0, 1);
|
||||
},
|
||||
);
|
||||
|
||||
$fork->('jobs', sub {
|
||||
PVE::Jobs::run_jobs($first_run);
|
||||
});
|
||||
$fork->(
|
||||
'jobs',
|
||||
sub {
|
||||
PVE::Jobs::run_jobs($first_run);
|
||||
},
|
||||
);
|
||||
|
||||
$first_run = 0;
|
||||
$first_run = 0;
|
||||
};
|
||||
|
||||
PVE::Jobs::setup_dirs();
|
||||
|
||||
for (my $count = 1000;;$count++) {
|
||||
return if $self->{got_hup_signal}; # keep workers running, PVE::Daemon re-execs us on return
|
||||
last if $self->{shutdown_request}; # exit main-run loop for shutdown
|
||||
for (my $count = 1000;; $count++) {
|
||||
return if $self->{got_hup_signal}; # keep workers running, PVE::Daemon re-execs us on return
|
||||
last if $self->{shutdown_request}; # exit main-run loop for shutdown
|
||||
|
||||
$run_jobs->();
|
||||
$run_jobs->();
|
||||
|
||||
my $sleep_time = 60;
|
||||
if ($count >= 1000) {
|
||||
# Job schedule has minute precision, so try running near the minute boundary.
|
||||
my ($current_seconds) = localtime;
|
||||
$sleep_time = (60 - $current_seconds) if (60 - $current_seconds >= 5);
|
||||
$count = 0;
|
||||
}
|
||||
my $sleep_time = 60;
|
||||
if ($count >= 1000) {
|
||||
# Job schedule has minute precision, so try running near the minute boundary.
|
||||
my ($current_seconds) = localtime;
|
||||
$sleep_time = (60 - $current_seconds) if (60 - $current_seconds >= 5);
|
||||
$count = 0;
|
||||
}
|
||||
|
||||
my $slept = 0; # SIGCHLD interrupts sleep, so we need to keep track
|
||||
while ($slept < $sleep_time) {
|
||||
last if $self->{shutdown_request} || $self->{got_hup_signal};
|
||||
$slept += sleep($sleep_time - $slept);
|
||||
# TODO: check if there's new work to do, e.g., if a job finished
|
||||
# that had a longer runtime than run period
|
||||
}
|
||||
my $slept = 0; # SIGCHLD interrupts sleep, so we need to keep track
|
||||
while ($slept < $sleep_time) {
|
||||
last if $self->{shutdown_request} || $self->{got_hup_signal};
|
||||
$slept += sleep($sleep_time - $slept);
|
||||
# TODO: check if there's new work to do, e.g., if a job finished
|
||||
# that had a longer runtime than run period
|
||||
}
|
||||
}
|
||||
|
||||
# NOTE: we only get here on shutdown_request, so we already sent a TERM to all job-types
|
||||
my $timeout = 0;
|
||||
while(my $pids = running_job_pids($self)) {
|
||||
kill 'TERM', $pids->@*; # send TERM to all workers at once, possible thundering herd - FIXME?
|
||||
while (my $pids = running_job_pids($self)) {
|
||||
kill 'TERM', $pids->@*; # send TERM to all workers at once, possible thundering herd - FIXME?
|
||||
|
||||
finish_jobs($self);
|
||||
finish_jobs($self);
|
||||
|
||||
# some jobs have a lock timeout of 60s, wait a bit more for graceful termination
|
||||
last if $timeout > 75;
|
||||
$timeout += sleep(3);
|
||||
# some jobs have a lock timeout of 60s, wait a bit more for graceful termination
|
||||
last if $timeout > 75;
|
||||
$timeout += sleep(3);
|
||||
}
|
||||
|
||||
if (my $pids = running_job_pids($self)) {
|
||||
syslog('warn', "unresponsive job-worker, killing now: " . join(', ', $pids->@*));
|
||||
kill 'KILL', $pids->@*;
|
||||
syslog('warn', "unresponsive job-worker, killing now: " . join(', ', $pids->@*));
|
||||
kill 'KILL', $pids->@*;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -166,7 +170,7 @@ sub shutdown {
|
|||
syslog('info', 'got shutdown request, signal running jobs to stop');
|
||||
|
||||
for my $jobs (values $self->{jobs}->%*) {
|
||||
kill 'TERM', keys $jobs->%*;
|
||||
kill 'TERM', keys $jobs->%*;
|
||||
}
|
||||
$self->{shutdown_request} = 1;
|
||||
}
|
||||
|
|
@ -177,10 +181,10 @@ $daemon->register_restart_command(1);
|
|||
$daemon->register_status_command();
|
||||
|
||||
our $cmddef = {
|
||||
start => [ __PACKAGE__, 'start', []],
|
||||
stop => [ __PACKAGE__, 'stop', []],
|
||||
restart => [ __PACKAGE__, 'restart', []],
|
||||
status => [ __PACKAGE__, 'status', [], undef, sub { print shift . "\n";} ],
|
||||
start => [__PACKAGE__, 'start', []],
|
||||
stop => [__PACKAGE__, 'stop', []],
|
||||
restart => [__PACKAGE__, 'restart', []],
|
||||
status => [__PACKAGE__, 'status', [], undef, sub { print shift . "\n"; }],
|
||||
};
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ sub init {
|
|||
sub shutdown {
|
||||
my ($self) = @_;
|
||||
|
||||
syslog('info' , "server closing");
|
||||
syslog('info', "server closing");
|
||||
|
||||
# wait for children
|
||||
1 while (waitpid(-1, POSIX::WNOHANG()) > 0);
|
||||
|
|
@ -90,32 +90,37 @@ sub update_supported_cpuflags {
|
|||
return if $cached_kvm_version && $cached_kvm_version eq $kvm_version;
|
||||
|
||||
if ($next_flag_update_time && $next_flag_update_time > time()) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
$next_flag_update_time = 0;
|
||||
|
||||
my $supported_cpuflags = eval { PVE::QemuServer::query_supported_cpu_flags() };
|
||||
warn $@ if $@;
|
||||
|
||||
if (!$supported_cpuflags ||
|
||||
(!$supported_cpuflags->{tcg} && !$supported_cpuflags->{kvm})) {
|
||||
# something went wrong, clear broadcast flags and set try-again delay
|
||||
warn "CPU flag detection failed, will try again after delay\n";
|
||||
$next_flag_update_time = time() + $failed_flag_update_delay_sec;
|
||||
if (
|
||||
!$supported_cpuflags
|
||||
|| (!$supported_cpuflags->{tcg} && !$supported_cpuflags->{kvm})
|
||||
) {
|
||||
# something went wrong, clear broadcast flags and set try-again delay
|
||||
warn "CPU flag detection failed, will try again after delay\n";
|
||||
$next_flag_update_time = time() + $failed_flag_update_delay_sec;
|
||||
|
||||
$supported_cpuflags = {};
|
||||
$supported_cpuflags = {};
|
||||
} else {
|
||||
# only set cached version if there's actually something to broadcast
|
||||
$cached_kvm_version = $kvm_version;
|
||||
# only set cached version if there's actually something to broadcast
|
||||
$cached_kvm_version = $kvm_version;
|
||||
}
|
||||
|
||||
for my $accel ("tcg", "kvm") {
|
||||
if ($supported_cpuflags->{$accel}) {
|
||||
PVE::Cluster::broadcast_node_kv("cpuflags-$accel", join(' ', @{$supported_cpuflags->{$accel}}));
|
||||
} else {
|
||||
# clear potentially invalid data
|
||||
PVE::Cluster::broadcast_node_kv("cpuflags-$accel", '');
|
||||
}
|
||||
if ($supported_cpuflags->{$accel}) {
|
||||
PVE::Cluster::broadcast_node_kv(
|
||||
"cpuflags-$accel",
|
||||
join(' ', @{ $supported_cpuflags->{$accel} }),
|
||||
);
|
||||
} else {
|
||||
# clear potentially invalid data
|
||||
PVE::Cluster::broadcast_node_kv("cpuflags-$accel", '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -135,16 +140,18 @@ my sub broadcast_static_node_info {
|
|||
$old = eval { decode_json($old->{$nodename}) } if defined($old->{$nodename});
|
||||
|
||||
if (
|
||||
!defined($old->{cpus}) || $old->{cpus} != $cpus
|
||||
|| !defined($old->{memory}) || $old->{memory} != $memory
|
||||
|| ($old->{'cgroup-mode'} // -1) != ($cgroup_mode // -1)
|
||||
!defined($old->{cpus})
|
||||
|| $old->{cpus} != $cpus
|
||||
|| !defined($old->{memory})
|
||||
|| $old->{memory} != $memory
|
||||
|| ($old->{'cgroup-mode'} // -1) != ($cgroup_mode // -1)
|
||||
) {
|
||||
my $info = {
|
||||
cpus => $cpus,
|
||||
memory => $memory,
|
||||
};
|
||||
$info->{'cgroup-mode'} = $cgroup_mode if defined($cgroup_mode);
|
||||
PVE::Cluster::broadcast_node_kv('static-info', encode_json($info));
|
||||
my $info = {
|
||||
cpus => $cpus,
|
||||
memory => $memory,
|
||||
};
|
||||
$info->{'cgroup-mode'} = $cgroup_mode if defined($cgroup_mode);
|
||||
PVE::Cluster::broadcast_node_kv('static-info', encode_json($info));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,32 +174,45 @@ sub update_node_status {
|
|||
# traffic from/to physical interface cards
|
||||
my ($netin, $netout) = (0, 0);
|
||||
for my $dev (grep { /^$PVE::Network::PHYSICAL_NIC_RE$/ } keys %$netdev) {
|
||||
$netin += $netdev->{$dev}->{receive};
|
||||
$netout += $netdev->{$dev}->{transmit};
|
||||
$netin += $netdev->{$dev}->{receive};
|
||||
$netout += $netdev->{$dev}->{transmit};
|
||||
}
|
||||
|
||||
my $meminfo = PVE::ProcFSTools::read_meminfo();
|
||||
|
||||
my $dinfo = df('/', 1); # output is bytes
|
||||
my $dinfo = df('/', 1); # output is bytes
|
||||
# everything not free is considered to be used
|
||||
my $dused = $dinfo->{blocks} - $dinfo->{bfree};
|
||||
|
||||
my $ctime = time();
|
||||
|
||||
my $data = $generate_rrd_string->(
|
||||
[$uptime, $sublevel, $ctime, $avg1, $maxcpu, $stat->{cpu}, $stat->{wait},
|
||||
$meminfo->{memtotal}, $meminfo->{memused},
|
||||
$meminfo->{swaptotal}, $meminfo->{swapused},
|
||||
$dinfo->{blocks}, $dused, $netin, $netout]
|
||||
[
|
||||
$uptime,
|
||||
$sublevel,
|
||||
$ctime,
|
||||
$avg1,
|
||||
$maxcpu,
|
||||
$stat->{cpu},
|
||||
$stat->{wait},
|
||||
$meminfo->{memtotal},
|
||||
$meminfo->{memused},
|
||||
$meminfo->{swaptotal},
|
||||
$meminfo->{swapused},
|
||||
$dinfo->{blocks},
|
||||
$dused,
|
||||
$netin,
|
||||
$netout,
|
||||
],
|
||||
);
|
||||
PVE::Cluster::broadcast_rrd("pve2-node/$nodename", $data);
|
||||
|
||||
my $node_metric = {
|
||||
uptime => $uptime,
|
||||
cpustat => $stat,
|
||||
memory => $meminfo,
|
||||
blockstat => $dinfo,
|
||||
nics => $netdev,
|
||||
uptime => $uptime,
|
||||
cpustat => $stat,
|
||||
memory => $meminfo,
|
||||
blockstat => $dinfo,
|
||||
nics => $netdev,
|
||||
};
|
||||
$node_metric->{cpustat}->@{qw(avg1 avg5 avg15)} = ($avg1, $avg5, $avg15);
|
||||
$node_metric->{cpustat}->{cpus} = $maxcpu;
|
||||
|
|
@ -207,7 +227,7 @@ sub update_node_status {
|
|||
}
|
||||
|
||||
sub auto_balloning {
|
||||
my ($vmstatus) = @_;
|
||||
my ($vmstatus) = @_;
|
||||
|
||||
my $log = sub { $opt_debug and printf @_ };
|
||||
|
||||
|
|
@ -221,19 +241,20 @@ sub auto_balloning {
|
|||
my $target = int($config->{'ballooning-target'} // 80);
|
||||
# goal is the change amount required to achieve that
|
||||
my $goal = int($hostmeminfo->{memtotal} * $target / 100 - $hostmeminfo->{memused});
|
||||
$log->("target: $target%% host goal: $goal free: $hostfreemem total: $hostmeminfo->{memtotal}\n");
|
||||
$log->(
|
||||
"target: $target%% host goal: $goal free: $hostfreemem total: $hostmeminfo->{memtotal}\n");
|
||||
|
||||
my $maxchange = 100*1024*1024;
|
||||
my $maxchange = 100 * 1024 * 1024;
|
||||
my $res = PVE::AutoBalloon::compute_alg1($vmstatus, $goal, $maxchange);
|
||||
|
||||
for my $vmid (sort keys %$res) {
|
||||
my $target = int($res->{$vmid});
|
||||
my $current = int($vmstatus->{$vmid}->{balloon});
|
||||
next if $target == $current; # no need to change
|
||||
my $target = int($res->{$vmid});
|
||||
my $current = int($vmstatus->{$vmid}->{balloon});
|
||||
next if $target == $current; # no need to change
|
||||
|
||||
$log->("BALLOON $vmid to $target (%d)\n", $target - $current);
|
||||
eval { PVE::QemuServer::Monitor::mon_cmd($vmid, "balloon", value => int($target)) };
|
||||
warn $@ if $@;
|
||||
$log->("BALLOON $vmid to $target (%d)\n", $target - $current);
|
||||
eval { PVE::QemuServer::Monitor::mon_cmd($vmid, "balloon", value => int($target)) };
|
||||
warn $@ if $@;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -248,23 +269,50 @@ sub update_qemu_status {
|
|||
|
||||
my $transactions = PVE::ExtMetric::transactions_start($status_cfg);
|
||||
foreach my $vmid (keys %$vmstatus) {
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $data;
|
||||
my $status = $d->{qmpstatus} || $d->{status} || 'stopped';
|
||||
my $template = $d->{template} ? $d->{template} : "0";
|
||||
if ($d->{pid}) { # running
|
||||
$data = $generate_rrd_string->(
|
||||
[$d->{uptime}, $d->{name}, $status, $template, $ctime, $d->{cpus}, $d->{cpu},
|
||||
$d->{maxmem}, $d->{mem}, $d->{maxdisk}, $d->{disk},
|
||||
$d->{netin}, $d->{netout}, $d->{diskread}, $d->{diskwrite}]);
|
||||
} else {
|
||||
$data = $generate_rrd_string->(
|
||||
[0, $d->{name}, $status, $template, $ctime, $d->{cpus}, undef,
|
||||
$d->{maxmem}, undef, $d->{maxdisk}, $d->{disk}, undef, undef, undef, undef]);
|
||||
}
|
||||
PVE::Cluster::broadcast_rrd("pve2.3-vm/$vmid", $data);
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $data;
|
||||
my $status = $d->{qmpstatus} || $d->{status} || 'stopped';
|
||||
my $template = $d->{template} ? $d->{template} : "0";
|
||||
if ($d->{pid}) { # running
|
||||
$data = $generate_rrd_string->([
|
||||
$d->{uptime},
|
||||
$d->{name},
|
||||
$status,
|
||||
$template,
|
||||
$ctime,
|
||||
$d->{cpus},
|
||||
$d->{cpu},
|
||||
$d->{maxmem},
|
||||
$d->{mem},
|
||||
$d->{maxdisk},
|
||||
$d->{disk},
|
||||
$d->{netin},
|
||||
$d->{netout},
|
||||
$d->{diskread},
|
||||
$d->{diskwrite},
|
||||
]);
|
||||
} else {
|
||||
$data = $generate_rrd_string->([
|
||||
0,
|
||||
$d->{name},
|
||||
$status,
|
||||
$template,
|
||||
$ctime,
|
||||
$d->{cpus},
|
||||
undef,
|
||||
$d->{maxmem},
|
||||
undef,
|
||||
$d->{maxdisk},
|
||||
$d->{disk},
|
||||
undef,
|
||||
undef,
|
||||
undef,
|
||||
undef,
|
||||
]);
|
||||
}
|
||||
PVE::Cluster::broadcast_rrd("pve2.3-vm/$vmid", $data);
|
||||
|
||||
PVE::ExtMetric::update_all($transactions, 'qemu', $vmid, $d, $ctime, $nodename);
|
||||
PVE::ExtMetric::update_all($transactions, 'qemu', $vmid, $d, $ctime, $nodename);
|
||||
}
|
||||
|
||||
PVE::ExtMetric::transactions_finish($transactions);
|
||||
|
|
@ -278,28 +326,29 @@ sub remove_stale_lxc_consoles {
|
|||
my $pidhash = PVE::LXC::find_lxc_console_pids();
|
||||
|
||||
foreach my $vmid (keys %$pidhash) {
|
||||
next if defined($vmstatus->{$vmid});
|
||||
syslog('info', "remove stale lxc-console for CT $vmid");
|
||||
foreach my $pid (@{$pidhash->{$vmid}}) {
|
||||
kill(9, $pid);
|
||||
}
|
||||
next if defined($vmstatus->{$vmid});
|
||||
syslog('info', "remove stale lxc-console for CT $vmid");
|
||||
foreach my $pid (@{ $pidhash->{$vmid} }) {
|
||||
kill(9, $pid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $rebalance_error_count = {};
|
||||
|
||||
my $NO_REBALANCE;
|
||||
|
||||
sub rebalance_lxc_containers {
|
||||
# Make sure we can find the cpuset controller path:
|
||||
return if $NO_REBALANCE;
|
||||
my $cpuset_base = eval { PVE::CGroup::cpuset_controller_path() };
|
||||
if (my $err = $@) {
|
||||
syslog('info', "could not get cpuset controller path: $err");
|
||||
syslog('info', "could not get cpuset controller path: $err");
|
||||
}
|
||||
|
||||
if (!defined($cpuset_base)) {
|
||||
$NO_REBALANCE = 1;
|
||||
return;
|
||||
$NO_REBALANCE = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
# Figure out the cpu count & highest ID
|
||||
|
|
@ -308,7 +357,7 @@ sub rebalance_lxc_containers {
|
|||
my $cpucount = scalar(@allowed_cpus);
|
||||
my $max_cpuid = $allowed_cpus[-1];
|
||||
|
||||
my @cpu_ctcount = (0) x ($max_cpuid+1);
|
||||
my @cpu_ctcount = (0) x ($max_cpuid + 1);
|
||||
my @balanced_cts;
|
||||
|
||||
# A mapping { vmid => cgroup_payload_path } for containers where namespace
|
||||
|
|
@ -316,135 +365,132 @@ sub rebalance_lxc_containers {
|
|||
my $ctinfo = {};
|
||||
|
||||
my $modify_cpuset = sub {
|
||||
my ($vmid, $cpuset, $newset) = @_;
|
||||
my ($vmid, $cpuset, $newset) = @_;
|
||||
|
||||
if (!$rebalance_error_count->{$vmid}) {
|
||||
syslog('info', "modified cpu set for lxc/$vmid: " . $newset->short_string());
|
||||
}
|
||||
if (!$rebalance_error_count->{$vmid}) {
|
||||
syslog('info', "modified cpu set for lxc/$vmid: " . $newset->short_string());
|
||||
}
|
||||
|
||||
eval {
|
||||
my $cgbase = $ctinfo->{$vmid};
|
||||
eval {
|
||||
my $cgbase = $ctinfo->{$vmid};
|
||||
|
||||
if (defined($cgbase)) {
|
||||
# allow all, so that we can set new cpuset in /ns
|
||||
$all_cpus->write_to_path($cgbase);
|
||||
eval {
|
||||
$newset->write_to_path("$cgbase/ns");
|
||||
};
|
||||
if (my $err = $@) {
|
||||
warn $err if !$rebalance_error_count->{$vmid}++;
|
||||
# restore original
|
||||
$cpuset->write_to_path($cgbase);
|
||||
} else {
|
||||
# also apply to container root cgroup
|
||||
$newset->write_to_path($cgbase);
|
||||
$rebalance_error_count->{$vmid} = 0;
|
||||
}
|
||||
} else {
|
||||
# old style container
|
||||
$newset->write_to_path($cgbase);
|
||||
$rebalance_error_count->{$vmid} = 0;
|
||||
}
|
||||
};
|
||||
if (my $err = $@) {
|
||||
warn $err if !$rebalance_error_count->{$vmid}++;
|
||||
}
|
||||
if (defined($cgbase)) {
|
||||
# allow all, so that we can set new cpuset in /ns
|
||||
$all_cpus->write_to_path($cgbase);
|
||||
eval { $newset->write_to_path("$cgbase/ns"); };
|
||||
if (my $err = $@) {
|
||||
warn $err if !$rebalance_error_count->{$vmid}++;
|
||||
# restore original
|
||||
$cpuset->write_to_path($cgbase);
|
||||
} else {
|
||||
# also apply to container root cgroup
|
||||
$newset->write_to_path($cgbase);
|
||||
$rebalance_error_count->{$vmid} = 0;
|
||||
}
|
||||
} else {
|
||||
# old style container
|
||||
$newset->write_to_path($cgbase);
|
||||
$rebalance_error_count->{$vmid} = 0;
|
||||
}
|
||||
};
|
||||
if (my $err = $@) {
|
||||
warn $err if !$rebalance_error_count->{$vmid}++;
|
||||
}
|
||||
};
|
||||
|
||||
my $ctlist = PVE::LXC::config_list();
|
||||
|
||||
foreach my $vmid (sort keys %$ctlist) {
|
||||
my $cgpath = "$cpuset_base/lxc/$vmid";
|
||||
if (-d "$cgpath/ns") {
|
||||
$ctinfo->{$vmid} = $cgpath;
|
||||
} else {
|
||||
next; # old style container
|
||||
}
|
||||
my $cgpath = "$cpuset_base/lxc/$vmid";
|
||||
if (-d "$cgpath/ns") {
|
||||
$ctinfo->{$vmid} = $cgpath;
|
||||
} else {
|
||||
next; # old style container
|
||||
}
|
||||
|
||||
my ($conf, $cpuset) = eval {(
|
||||
PVE::LXC::Config->load_config($vmid),
|
||||
PVE::CpuSet->new_from_path($cgpath),
|
||||
)};
|
||||
if (my $err = $@) {
|
||||
warn $err;
|
||||
next;
|
||||
}
|
||||
my ($conf, $cpuset) =
|
||||
eval { (PVE::LXC::Config->load_config($vmid), PVE::CpuSet->new_from_path($cgpath)) };
|
||||
if (my $err = $@) {
|
||||
warn $err;
|
||||
next;
|
||||
}
|
||||
|
||||
my @cpuset_members = $cpuset->members();
|
||||
my @cpuset_members = $cpuset->members();
|
||||
|
||||
if (!PVE::LXC::Config->has_lxc_entry($conf, 'lxc.cgroup.cpuset.cpus')
|
||||
&& !PVE::LXC::Config->has_lxc_entry($conf, 'lxc.cgroup2.cpuset.cpus')
|
||||
) {
|
||||
my $cores = $conf->{cores} || $cpucount;
|
||||
$cores = $cpucount if $cores > $cpucount;
|
||||
if (
|
||||
!PVE::LXC::Config->has_lxc_entry($conf, 'lxc.cgroup.cpuset.cpus')
|
||||
&& !PVE::LXC::Config->has_lxc_entry($conf, 'lxc.cgroup2.cpuset.cpus')
|
||||
) {
|
||||
my $cores = $conf->{cores} || $cpucount;
|
||||
$cores = $cpucount if $cores > $cpucount;
|
||||
|
||||
# see if the number of cores was hot-reduced or hasn't been enacted at all yet
|
||||
my $newset = PVE::CpuSet->new();
|
||||
if ($cores < scalar(@cpuset_members)) {
|
||||
for (my $i = 0; $i < $cores; $i++) {
|
||||
$newset->insert($cpuset_members[$i]);
|
||||
}
|
||||
} elsif ($cores > scalar(@cpuset_members)) {
|
||||
my $count = $newset->insert(@cpuset_members);
|
||||
foreach my $cpu (@allowed_cpus) {
|
||||
$count += $newset->insert($cpu);
|
||||
last if $count >= $cores;
|
||||
}
|
||||
} else {
|
||||
$newset->insert(@cpuset_members);
|
||||
}
|
||||
# see if the number of cores was hot-reduced or hasn't been enacted at all yet
|
||||
my $newset = PVE::CpuSet->new();
|
||||
if ($cores < scalar(@cpuset_members)) {
|
||||
for (my $i = 0; $i < $cores; $i++) {
|
||||
$newset->insert($cpuset_members[$i]);
|
||||
}
|
||||
} elsif ($cores > scalar(@cpuset_members)) {
|
||||
my $count = $newset->insert(@cpuset_members);
|
||||
foreach my $cpu (@allowed_cpus) {
|
||||
$count += $newset->insert($cpu);
|
||||
last if $count >= $cores;
|
||||
}
|
||||
} else {
|
||||
$newset->insert(@cpuset_members);
|
||||
}
|
||||
|
||||
# Apply hot-plugged changes if any:
|
||||
if (!$newset->is_equal($cpuset)) {
|
||||
@cpuset_members = $newset->members();
|
||||
$modify_cpuset->($vmid, $cpuset, $newset);
|
||||
}
|
||||
# Apply hot-plugged changes if any:
|
||||
if (!$newset->is_equal($cpuset)) {
|
||||
@cpuset_members = $newset->members();
|
||||
$modify_cpuset->($vmid, $cpuset, $newset);
|
||||
}
|
||||
|
||||
# Note: no need to rebalance if we already use all cores
|
||||
push @balanced_cts, [$vmid, $cores, $newset]
|
||||
if defined($conf->{cores}) && ($cores != $cpucount);
|
||||
}
|
||||
# Note: no need to rebalance if we already use all cores
|
||||
push @balanced_cts, [$vmid, $cores, $newset]
|
||||
if defined($conf->{cores}) && ($cores != $cpucount);
|
||||
}
|
||||
|
||||
foreach my $cpu (@cpuset_members) {
|
||||
$cpu_ctcount[$cpu]++ if $cpu <= $max_cpuid;
|
||||
}
|
||||
foreach my $cpu (@cpuset_members) {
|
||||
$cpu_ctcount[$cpu]++ if $cpu <= $max_cpuid;
|
||||
}
|
||||
}
|
||||
|
||||
my $find_best_cpu = sub {
|
||||
my ($cpulist, $cpu) = @_;
|
||||
my ($cpulist, $cpu) = @_;
|
||||
|
||||
my $cur_cost = $cpu_ctcount[$cpu];
|
||||
my $cur_cpu = $cpu;
|
||||
my $cur_cost = $cpu_ctcount[$cpu];
|
||||
my $cur_cpu = $cpu;
|
||||
|
||||
foreach my $candidate (@$cpulist) {
|
||||
my $cost = $cpu_ctcount[$candidate];
|
||||
if ($cost < ($cur_cost - 1)) {
|
||||
$cur_cost = $cost;
|
||||
$cur_cpu = $candidate;
|
||||
}
|
||||
}
|
||||
foreach my $candidate (@$cpulist) {
|
||||
my $cost = $cpu_ctcount[$candidate];
|
||||
if ($cost < ($cur_cost - 1)) {
|
||||
$cur_cost = $cost;
|
||||
$cur_cpu = $candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return $cur_cpu;
|
||||
return $cur_cpu;
|
||||
};
|
||||
|
||||
foreach my $bct (@balanced_cts) {
|
||||
my ($vmid, $cores, $cpuset) = @$bct;
|
||||
my ($vmid, $cores, $cpuset) = @$bct;
|
||||
|
||||
my $rest = [ grep { !$cpuset->has($_) } @allowed_cpus ];
|
||||
my $rest = [grep { !$cpuset->has($_) } @allowed_cpus];
|
||||
|
||||
my $newset = PVE::CpuSet->new();
|
||||
for my $cpu ($cpuset->members()) {
|
||||
my $best = $find_best_cpu->($rest, $cpu);
|
||||
if ($best != $cpu) {
|
||||
$cpu_ctcount[$best]++;
|
||||
$cpu_ctcount[$cpu]--;
|
||||
}
|
||||
$newset->insert($best);
|
||||
}
|
||||
my $newset = PVE::CpuSet->new();
|
||||
for my $cpu ($cpuset->members()) {
|
||||
my $best = $find_best_cpu->($rest, $cpu);
|
||||
if ($best != $cpu) {
|
||||
$cpu_ctcount[$best]++;
|
||||
$cpu_ctcount[$cpu]--;
|
||||
}
|
||||
$newset->insert($best);
|
||||
}
|
||||
|
||||
if (!$newset->is_equal($cpuset)) {
|
||||
$modify_cpuset->($vmid, $cpuset, $newset);
|
||||
}
|
||||
if (!$newset->is_equal($cpuset)) {
|
||||
$modify_cpuset->($vmid, $cpuset, $newset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -457,25 +503,49 @@ sub update_lxc_status {
|
|||
my $transactions = PVE::ExtMetric::transactions_start($status_cfg);
|
||||
|
||||
foreach my $vmid (keys %$vmstatus) {
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $template = $d->{template} ? $d->{template} : "0";
|
||||
my $data;
|
||||
if ($d->{status} eq 'running') { # running
|
||||
$data = $generate_rrd_string->(
|
||||
[$d->{uptime}, $d->{name}, $d->{status}, $template,
|
||||
$ctime, $d->{cpus}, $d->{cpu},
|
||||
$d->{maxmem}, $d->{mem},
|
||||
$d->{maxdisk}, $d->{disk},
|
||||
$d->{netin}, $d->{netout},
|
||||
$d->{diskread}, $d->{diskwrite}]);
|
||||
} else {
|
||||
$data = $generate_rrd_string->(
|
||||
[0, $d->{name}, $d->{status}, $template, $ctime, $d->{cpus}, undef,
|
||||
$d->{maxmem}, undef, $d->{maxdisk}, $d->{disk}, undef, undef, undef, undef]);
|
||||
}
|
||||
PVE::Cluster::broadcast_rrd("pve2.3-vm/$vmid", $data);
|
||||
my $d = $vmstatus->{$vmid};
|
||||
my $template = $d->{template} ? $d->{template} : "0";
|
||||
my $data;
|
||||
if ($d->{status} eq 'running') { # running
|
||||
$data = $generate_rrd_string->([
|
||||
$d->{uptime},
|
||||
$d->{name},
|
||||
$d->{status},
|
||||
$template,
|
||||
$ctime,
|
||||
$d->{cpus},
|
||||
$d->{cpu},
|
||||
$d->{maxmem},
|
||||
$d->{mem},
|
||||
$d->{maxdisk},
|
||||
$d->{disk},
|
||||
$d->{netin},
|
||||
$d->{netout},
|
||||
$d->{diskread},
|
||||
$d->{diskwrite},
|
||||
]);
|
||||
} else {
|
||||
$data = $generate_rrd_string->([
|
||||
0,
|
||||
$d->{name},
|
||||
$d->{status},
|
||||
$template,
|
||||
$ctime,
|
||||
$d->{cpus},
|
||||
undef,
|
||||
$d->{maxmem},
|
||||
undef,
|
||||
$d->{maxdisk},
|
||||
$d->{disk},
|
||||
undef,
|
||||
undef,
|
||||
undef,
|
||||
undef,
|
||||
]);
|
||||
}
|
||||
PVE::Cluster::broadcast_rrd("pve2.3-vm/$vmid", $data);
|
||||
|
||||
PVE::ExtMetric::update_all($transactions, 'lxc', $vmid, $d, $ctime, $nodename);
|
||||
PVE::ExtMetric::update_all($transactions, 'lxc', $vmid, $d, $ctime, $nodename);
|
||||
}
|
||||
PVE::ExtMetric::transactions_finish($transactions);
|
||||
|
||||
|
|
@ -492,15 +562,15 @@ sub update_storage_status {
|
|||
my $transactions = PVE::ExtMetric::transactions_start($status_cfg);
|
||||
|
||||
foreach my $storeid (keys %$info) {
|
||||
my $d = $info->{$storeid};
|
||||
next if !$d->{active};
|
||||
my $d = $info->{$storeid};
|
||||
next if !$d->{active};
|
||||
|
||||
my $data = $generate_rrd_string->([$ctime, $d->{total}, $d->{used}]);
|
||||
my $data = $generate_rrd_string->([$ctime, $d->{total}, $d->{used}]);
|
||||
|
||||
my $key = "pve2-storage/${nodename}/$storeid";
|
||||
PVE::Cluster::broadcast_rrd($key, $data);
|
||||
my $key = "pve2-storage/${nodename}/$storeid";
|
||||
PVE::Cluster::broadcast_rrd($key, $data);
|
||||
|
||||
PVE::ExtMetric::update_all($transactions, 'storage', $nodename, $storeid, $d, $ctime);
|
||||
PVE::ExtMetric::update_all($transactions, 'storage', $nodename, $storeid, $d, $ctime);
|
||||
}
|
||||
PVE::ExtMetric::transactions_finish($transactions);
|
||||
|
||||
|
|
@ -521,22 +591,21 @@ sub update_ceph_metadata {
|
|||
|
||||
sub update_sdn_status {
|
||||
|
||||
if($have_sdn) {
|
||||
my ($transport_status, $vnet_status) = PVE::Network::SDN::status();
|
||||
if ($have_sdn) {
|
||||
my ($transport_status, $vnet_status) = PVE::Network::SDN::status();
|
||||
|
||||
my $status = $transport_status ? encode_json($transport_status) : undef;
|
||||
PVE::Cluster::broadcast_node_kv("sdn", $status);
|
||||
my $status = $transport_status ? encode_json($transport_status) : undef;
|
||||
PVE::Cluster::broadcast_node_kv("sdn", $status);
|
||||
}
|
||||
}
|
||||
|
||||
my $broadcast_version_info_done = 0;
|
||||
my sub broadcast_version_info : prototype() {
|
||||
if (!$broadcast_version_info_done) {
|
||||
PVE::Cluster::broadcast_node_kv(
|
||||
'version-info',
|
||||
encode_json(PVE::pvecfg::version_info()),
|
||||
);
|
||||
$broadcast_version_info_done = 1;
|
||||
PVE::Cluster::broadcast_node_kv(
|
||||
'version-info', encode_json(PVE::pvecfg::version_info()),
|
||||
);
|
||||
$broadcast_version_info_done = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -548,77 +617,55 @@ sub update_status {
|
|||
my $pull_txn = PVE::PullMetric::transaction_start();
|
||||
|
||||
eval {
|
||||
my $tlist = $rpcenv->active_workers();
|
||||
PVE::Cluster::broadcast_tasklist($tlist);
|
||||
my $tlist = $rpcenv->active_workers();
|
||||
PVE::Cluster::broadcast_tasklist($tlist);
|
||||
};
|
||||
my $err = $@;
|
||||
syslog('err', $err) if $err;
|
||||
|
||||
my $status_cfg = PVE::Cluster::cfs_read_file('status.cfg');
|
||||
|
||||
eval {
|
||||
update_node_status($status_cfg, $pull_txn);
|
||||
};
|
||||
eval { update_node_status($status_cfg, $pull_txn); };
|
||||
$err = $@;
|
||||
syslog('err', "node status update error: $err") if $err;
|
||||
|
||||
eval {
|
||||
update_qemu_status($status_cfg, $pull_txn);
|
||||
};
|
||||
eval { update_qemu_status($status_cfg, $pull_txn); };
|
||||
$err = $@;
|
||||
syslog('err', "qemu status update error: $err") if $err;
|
||||
|
||||
eval {
|
||||
update_lxc_status($status_cfg, $pull_txn);
|
||||
};
|
||||
eval { update_lxc_status($status_cfg, $pull_txn); };
|
||||
$err = $@;
|
||||
syslog('err', "lxc status update error: $err") if $err;
|
||||
|
||||
eval {
|
||||
rebalance_lxc_containers();
|
||||
};
|
||||
eval { rebalance_lxc_containers(); };
|
||||
$err = $@;
|
||||
syslog('err', "lxc cpuset rebalance error: $err") if $err;
|
||||
|
||||
eval {
|
||||
update_storage_status($status_cfg, $pull_txn);
|
||||
};
|
||||
eval { update_storage_status($status_cfg, $pull_txn); };
|
||||
$err = $@;
|
||||
syslog('err', "storage status update error: $err") if $err;
|
||||
|
||||
eval {
|
||||
remove_stale_lxc_consoles();
|
||||
};
|
||||
eval { remove_stale_lxc_consoles(); };
|
||||
$err = $@;
|
||||
syslog('err', "lxc console cleanup error: $err") if $err;
|
||||
|
||||
eval {
|
||||
rotate_authkeys();
|
||||
};
|
||||
eval { rotate_authkeys(); };
|
||||
$err = $@;
|
||||
syslog('err', "authkey rotation error: $err") if $err;
|
||||
|
||||
eval {
|
||||
update_ceph_metadata();
|
||||
};
|
||||
eval { update_ceph_metadata(); };
|
||||
$err = $@;
|
||||
syslog('err', "ceph metadata update error: $err") if $err;
|
||||
|
||||
eval {
|
||||
update_sdn_status();
|
||||
};
|
||||
eval { update_sdn_status(); };
|
||||
$err = $@;
|
||||
syslog('err', "sdn status update error: $err") if $err;
|
||||
|
||||
eval {
|
||||
broadcast_version_info();
|
||||
};
|
||||
eval { broadcast_version_info(); };
|
||||
$err = $@;
|
||||
syslog('err', "version info update error: $err") if $err;
|
||||
|
||||
eval {
|
||||
PVE::PullMetric::transaction_finish($pull_txn);
|
||||
};
|
||||
eval { PVE::PullMetric::transaction_finish($pull_txn); };
|
||||
$err = $@;
|
||||
syslog('err', "could not populate metric data cache: $err") if $err;
|
||||
}
|
||||
|
|
@ -627,7 +674,7 @@ my $next_update = 0;
|
|||
|
||||
# do not update directly after startup, because install scripts
|
||||
# have a problem with that
|
||||
my $cycle = 0;
|
||||
my $cycle = 0;
|
||||
my $updatetime = 10;
|
||||
|
||||
my $initial_memory_usage;
|
||||
|
|
@ -637,50 +684,59 @@ sub run {
|
|||
|
||||
for (;;) { # forever
|
||||
|
||||
$next_update = time() + $updatetime;
|
||||
$next_update = time() + $updatetime;
|
||||
|
||||
if ($cycle) {
|
||||
my ($ccsec, $cusec) = gettimeofday ();
|
||||
eval {
|
||||
# syslog('info', "start status update");
|
||||
PVE::Cluster::cfs_update();
|
||||
update_status();
|
||||
};
|
||||
my $err = $@;
|
||||
if ($cycle) {
|
||||
my ($ccsec, $cusec) = gettimeofday();
|
||||
eval {
|
||||
# syslog('info', "start status update");
|
||||
PVE::Cluster::cfs_update();
|
||||
update_status();
|
||||
};
|
||||
my $err = $@;
|
||||
|
||||
if ($err) {
|
||||
syslog('err', "status update error: $err");
|
||||
}
|
||||
if ($err) {
|
||||
syslog('err', "status update error: $err");
|
||||
}
|
||||
|
||||
my ($ccsec_end, $cusec_end) = gettimeofday ();
|
||||
my $cptime = ($ccsec_end-$ccsec) + ($cusec_end - $cusec)/1000000;
|
||||
my ($ccsec_end, $cusec_end) = gettimeofday();
|
||||
my $cptime = ($ccsec_end - $ccsec) + ($cusec_end - $cusec) / 1000000;
|
||||
|
||||
syslog('info', sprintf("status update time (%.3f seconds)", $cptime))
|
||||
if ($cptime > 5);
|
||||
}
|
||||
syslog('info', sprintf("status update time (%.3f seconds)", $cptime))
|
||||
if ($cptime > 5);
|
||||
}
|
||||
|
||||
$cycle++;
|
||||
$cycle++;
|
||||
|
||||
my $mem = PVE::ProcFSTools::read_memory_usage();
|
||||
my $resident_kb = $mem->{resident} / 1024;
|
||||
my $mem = PVE::ProcFSTools::read_memory_usage();
|
||||
my $resident_kb = $mem->{resident} / 1024;
|
||||
|
||||
if (!defined($initial_memory_usage) || ($cycle < 10)) {
|
||||
$initial_memory_usage = $resident_kb;
|
||||
} else {
|
||||
my $diff = $resident_kb - $initial_memory_usage;
|
||||
if ($diff > 15 * 1024) {
|
||||
syslog ('info', "restarting server after $cycle cycles to " .
|
||||
"reduce memory usage (free $resident_kb ($diff) KB)");
|
||||
$self->restart_daemon();
|
||||
}
|
||||
}
|
||||
if (!defined($initial_memory_usage) || ($cycle < 10)) {
|
||||
$initial_memory_usage = $resident_kb;
|
||||
} else {
|
||||
my $diff = $resident_kb - $initial_memory_usage;
|
||||
if ($diff > 15 * 1024) {
|
||||
syslog(
|
||||
'info',
|
||||
"restarting server after $cycle cycles to "
|
||||
. "reduce memory usage (free $resident_kb ($diff) KB)",
|
||||
);
|
||||
$self->restart_daemon();
|
||||
}
|
||||
}
|
||||
|
||||
my $wcount = 0;
|
||||
while ((time() < $next_update) &&
|
||||
($wcount < $updatetime) && # protect against time wrap
|
||||
!$restart_request) { $wcount++; sleep (1); };
|
||||
my $wcount = 0;
|
||||
while (
|
||||
(time() < $next_update)
|
||||
&& ($wcount < $updatetime)
|
||||
&& # protect against time wrap
|
||||
!$restart_request
|
||||
) {
|
||||
$wcount++;
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
$self->restart_daemon() if $restart_request;
|
||||
$self->restart_daemon() if $restart_request;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -690,10 +746,10 @@ $daemon->register_stop_command();
|
|||
$daemon->register_status_command();
|
||||
|
||||
our $cmddef = {
|
||||
start => [ __PACKAGE__, 'start', []],
|
||||
restart => [ __PACKAGE__, 'restart', []],
|
||||
stop => [ __PACKAGE__, 'stop', []],
|
||||
status => [ __PACKAGE__, 'status', [], undef, sub { print shift . "\n";} ],
|
||||
start => [__PACKAGE__, 'start', []],
|
||||
restart => [__PACKAGE__, 'restart', []],
|
||||
stop => [__PACKAGE__, 'stop', []],
|
||||
status => [__PACKAGE__, 'status', [], undef, sub { print shift . "\n"; }],
|
||||
};
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package PVE::Service::spiceproxy;
|
||||
|
||||
# Note: In theory, all this can be done by 'pveproxy' daemon. But some
|
||||
# API call still have blocking code, so we use a separate daemon to avoid
|
||||
# Note: In theory, all this can be done by 'pveproxy' daemon. But some
|
||||
# API call still have blocking code, so we use a separate daemon to avoid
|
||||
# that the console gets blocked.
|
||||
|
||||
use strict;
|
||||
|
|
@ -18,15 +18,15 @@ my $cmdline = [$0, @ARGV];
|
|||
|
||||
my %daemon_options = (
|
||||
max_workers => 1, # todo: do we need more?
|
||||
restart_on_error => 5,
|
||||
restart_on_error => 5,
|
||||
stop_wait_time => 15,
|
||||
leave_children_open_on_reload => 1,
|
||||
setuid => 'www-data',
|
||||
setgid => 'www-data',
|
||||
pidfile => '/var/run/pveproxy/spiceproxy.pid',
|
||||
);
|
||||
);
|
||||
|
||||
my $daemon = __PACKAGE__->new('spiceproxy', $cmdline, %daemon_options);
|
||||
my $daemon = __PACKAGE__->new('spiceproxy', $cmdline, %daemon_options);
|
||||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
|
|
@ -36,32 +36,32 @@ sub init {
|
|||
|
||||
my $accept_lock_fn = "/var/lock/spiceproxy.lck";
|
||||
|
||||
my $lockfh = IO::File->new(">>${accept_lock_fn}") ||
|
||||
die "unable to open lock file '${accept_lock_fn}' - $!\n";
|
||||
my $lockfh = IO::File->new(">>${accept_lock_fn}")
|
||||
|| die "unable to open lock file '${accept_lock_fn}' - $!\n";
|
||||
|
||||
my $listen_ip = $proxyconf->{LISTEN_IP};
|
||||
my $socket = $self->create_reusable_socket(3128, $listen_ip);
|
||||
|
||||
$self->{server_config} = {
|
||||
keep_alive => 0,
|
||||
max_conn => 500,
|
||||
lockfile => $accept_lock_fn,
|
||||
socket => $socket,
|
||||
lockfh => $lockfh,
|
||||
debug => $self->{debug},
|
||||
spiceproxy => 1,
|
||||
trusted_env => 0,
|
||||
logfile => '/var/log/pveproxy/access.log',
|
||||
allow_from => $proxyconf->{ALLOW_FROM},
|
||||
deny_from => $proxyconf->{DENY_FROM},
|
||||
policy => $proxyconf->{POLICY},
|
||||
keep_alive => 0,
|
||||
max_conn => 500,
|
||||
lockfile => $accept_lock_fn,
|
||||
socket => $socket,
|
||||
lockfh => $lockfh,
|
||||
debug => $self->{debug},
|
||||
spiceproxy => 1,
|
||||
trusted_env => 0,
|
||||
logfile => '/var/log/pveproxy/access.log',
|
||||
allow_from => $proxyconf->{ALLOW_FROM},
|
||||
deny_from => $proxyconf->{DENY_FROM},
|
||||
policy => $proxyconf->{POLICY},
|
||||
};
|
||||
}
|
||||
|
||||
sub run {
|
||||
my ($self) = @_;
|
||||
|
||||
my $server = PVE::HTTPServer->new(%{$self->{server_config}});
|
||||
my $server = PVE::HTTPServer->new(%{ $self->{server_config} });
|
||||
$server->run();
|
||||
}
|
||||
|
||||
|
|
@ -71,10 +71,10 @@ $daemon->register_stop_command();
|
|||
$daemon->register_status_command();
|
||||
|
||||
our $cmddef = {
|
||||
start => [ __PACKAGE__, 'start', []],
|
||||
restart => [ __PACKAGE__, 'restart', []],
|
||||
stop => [ __PACKAGE__, 'stop', []],
|
||||
status => [ __PACKAGE__, 'status', [], undef, sub { print shift . "\n";} ],
|
||||
start => [__PACKAGE__, 'start', []],
|
||||
restart => [__PACKAGE__, 'restart', []],
|
||||
stop => [__PACKAGE__, 'stop', []],
|
||||
status => [__PACKAGE__, 'status', [], undef, sub { print shift . "\n"; }],
|
||||
};
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -26,35 +26,36 @@ sub type {
|
|||
|
||||
sub properties {
|
||||
return {
|
||||
path => {
|
||||
type => 'string', format => 'graphite-path',
|
||||
description => "root graphite path (ex: proxmox.mycluster.mykey)",
|
||||
},
|
||||
timeout => {
|
||||
type => 'integer',
|
||||
description => "graphite TCP socket timeout (default=1)",
|
||||
minimum => 0,
|
||||
default => 1,
|
||||
optional => 1
|
||||
},
|
||||
proto => {
|
||||
type => 'string',
|
||||
enum => ['udp', 'tcp'],
|
||||
description => "Protocol to send graphite data. TCP or UDP (default)",
|
||||
optional => 1,
|
||||
},
|
||||
path => {
|
||||
type => 'string',
|
||||
format => 'graphite-path',
|
||||
description => "root graphite path (ex: proxmox.mycluster.mykey)",
|
||||
},
|
||||
timeout => {
|
||||
type => 'integer',
|
||||
description => "graphite TCP socket timeout (default=1)",
|
||||
minimum => 0,
|
||||
default => 1,
|
||||
optional => 1,
|
||||
},
|
||||
proto => {
|
||||
type => 'string',
|
||||
enum => ['udp', 'tcp'],
|
||||
description => "Protocol to send graphite data. TCP or UDP (default)",
|
||||
optional => 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
return {
|
||||
server => {},
|
||||
port => { optional => 1 },
|
||||
mtu => { optional => 1 },
|
||||
proto => { optional => 1 },
|
||||
timeout => { optional => 1 },
|
||||
path => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
server => {},
|
||||
port => { optional => 1 },
|
||||
mtu => { optional => 1 },
|
||||
proto => { optional => 1 },
|
||||
timeout => { optional => 1 },
|
||||
path => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -88,7 +89,7 @@ sub _send_batch_size {
|
|||
my ($class, $cfg) = @_;
|
||||
my $proto = $cfg->{proto} || 'udp';
|
||||
if ($proto eq 'tcp') {
|
||||
return 56000;
|
||||
return 56000;
|
||||
}
|
||||
return $class->SUPER::_send_batch_size($cfg);
|
||||
}
|
||||
|
|
@ -96,23 +97,23 @@ sub _send_batch_size {
|
|||
sub _connect {
|
||||
my ($class, $cfg) = @_;
|
||||
|
||||
my $host = $cfg->{server};
|
||||
my $port = $cfg->{port} || 2003;
|
||||
my $proto = $cfg->{proto} || 'udp';
|
||||
my $host = $cfg->{server};
|
||||
my $port = $cfg->{port} || 2003;
|
||||
my $proto = $cfg->{proto} || 'udp';
|
||||
my $timeout = $cfg->{timeout} // 1;
|
||||
|
||||
my $carbon_socket = IO::Socket::IP->new(
|
||||
PeerAddr => $host,
|
||||
PeerPort => $port,
|
||||
Proto => $proto,
|
||||
Timeout => $timeout,
|
||||
PeerAddr => $host,
|
||||
PeerPort => $port,
|
||||
Proto => $proto,
|
||||
Timeout => $timeout,
|
||||
) || die "couldn't create carbon socket [$host]:$port - $@\n";
|
||||
|
||||
if ($proto eq 'tcp') {
|
||||
# seconds and µs
|
||||
my $timeout_struct = pack( 'l!l!', $timeout, 0);
|
||||
setsockopt($carbon_socket, SOL_SOCKET, SO_SNDTIMEO, $timeout_struct);
|
||||
setsockopt($carbon_socket, SOL_SOCKET, SO_RCVTIMEO, $timeout_struct);
|
||||
# seconds and µs
|
||||
my $timeout_struct = pack('l!l!', $timeout, 0);
|
||||
setsockopt($carbon_socket, SOL_SOCKET, SO_SNDTIMEO, $timeout_struct);
|
||||
setsockopt($carbon_socket, SOL_SOCKET, SO_RCVTIMEO, $timeout_struct);
|
||||
}
|
||||
|
||||
return $carbon_socket;
|
||||
|
|
@ -126,29 +127,29 @@ sub assemble {
|
|||
|
||||
# we do not want boolean/state information to export to graphite
|
||||
my $key_blacklist = {
|
||||
'template' => 1,
|
||||
'pid' => 1,
|
||||
'agent' => 1,
|
||||
'serial' => 1,
|
||||
'template' => 1,
|
||||
'pid' => 1,
|
||||
'agent' => 1,
|
||||
'serial' => 1,
|
||||
};
|
||||
|
||||
my $assemble_graphite_data;
|
||||
$assemble_graphite_data = sub {
|
||||
my ($metric, $path) = @_;
|
||||
my ($metric, $path) = @_;
|
||||
|
||||
for my $key (sort keys %$metric) {
|
||||
my $value = $metric->{$key};
|
||||
next if !defined($value);
|
||||
for my $key (sort keys %$metric) {
|
||||
my $value = $metric->{$key};
|
||||
next if !defined($value);
|
||||
|
||||
$key =~ s/\./-/g;
|
||||
my $metricpath = $path . ".$key";
|
||||
$key =~ s/\./-/g;
|
||||
my $metricpath = $path . ".$key";
|
||||
|
||||
if (ref($value) eq 'HASH') {
|
||||
$assemble_graphite_data->($value, $metricpath);
|
||||
} elsif ($value =~ m/^[+-]?[0-9]*\.?[0-9]+$/ && !$key_blacklist->{$key}) {
|
||||
$class->add_metric_data($txn, "$metricpath $value $ctime\n");
|
||||
}
|
||||
}
|
||||
if (ref($value) eq 'HASH') {
|
||||
$assemble_graphite_data->($value, $metricpath);
|
||||
} elsif ($value =~ m/^[+-]?[0-9]*\.?[0-9]+$/ && !$key_blacklist->{$key}) {
|
||||
$class->add_metric_data($txn, "$metricpath $value $ctime\n");
|
||||
}
|
||||
}
|
||||
};
|
||||
$assemble_graphite_data->($data, $path);
|
||||
|
||||
|
|
@ -156,18 +157,18 @@ sub assemble {
|
|||
}
|
||||
|
||||
PVE::JSONSchema::register_format('graphite-path', \&pve_verify_graphite_path);
|
||||
|
||||
sub pve_verify_graphite_path {
|
||||
my ($path, $noerr) = @_;
|
||||
|
||||
my $regex = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)";
|
||||
|
||||
if ($path !~ /^(${regex}\.)*${regex}$/) {
|
||||
return undef if $noerr;
|
||||
die "value does not look like a valid graphite path\n";
|
||||
return undef if $noerr;
|
||||
die "value does not look like a valid graphite path\n";
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -21,64 +21,68 @@ sub type {
|
|||
|
||||
sub properties {
|
||||
return {
|
||||
organization => {
|
||||
description => "The InfluxDB organization. Only necessary when using the http v2 api."
|
||||
." Has no meaning when using v2 compatibility api.",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
bucket => {
|
||||
description => "The InfluxDB bucket/db. Only necessary when using the http v2 api.",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
token => {
|
||||
description => "The InfluxDB access token. Only necessary when using the http v2 api."
|
||||
." If the v2 compatibility api is used, use 'user:password' instead.",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
'api-path-prefix' => {
|
||||
description => "An API path prefix inserted between '<host>:<port>/' and '/api2/'."
|
||||
." Can be useful if the InfluxDB service runs behind a reverse proxy.",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
influxdbproto => {
|
||||
type => 'string',
|
||||
enum => ['udp', 'http', 'https'],
|
||||
default => 'udp',
|
||||
optional => 1,
|
||||
},
|
||||
'max-body-size' => {
|
||||
description => "InfluxDB max-body-size in bytes. Requests are batched up to this size.",
|
||||
type => 'integer',
|
||||
minimum => 1,
|
||||
default => 25_000_000,
|
||||
},
|
||||
'verify-certificate' => {
|
||||
description => "Set to 0 to disable certificate verification for https endpoints.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 1,
|
||||
},
|
||||
organization => {
|
||||
description =>
|
||||
"The InfluxDB organization. Only necessary when using the http v2 api."
|
||||
. " Has no meaning when using v2 compatibility api.",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
bucket => {
|
||||
description => "The InfluxDB bucket/db. Only necessary when using the http v2 api.",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
token => {
|
||||
description =>
|
||||
"The InfluxDB access token. Only necessary when using the http v2 api."
|
||||
. " If the v2 compatibility api is used, use 'user:password' instead.",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
'api-path-prefix' => {
|
||||
description => "An API path prefix inserted between '<host>:<port>/' and '/api2/'."
|
||||
. " Can be useful if the InfluxDB service runs behind a reverse proxy.",
|
||||
type => 'string',
|
||||
optional => 1,
|
||||
},
|
||||
influxdbproto => {
|
||||
type => 'string',
|
||||
enum => ['udp', 'http', 'https'],
|
||||
default => 'udp',
|
||||
optional => 1,
|
||||
},
|
||||
'max-body-size' => {
|
||||
description =>
|
||||
"InfluxDB max-body-size in bytes. Requests are batched up to this size.",
|
||||
type => 'integer',
|
||||
minimum => 1,
|
||||
default => 25_000_000,
|
||||
},
|
||||
'verify-certificate' => {
|
||||
description => "Set to 0 to disable certificate verification for https endpoints.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
default => 1,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
return {
|
||||
server => {},
|
||||
port => {},
|
||||
mtu => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
organization => { optional => 1},
|
||||
bucket => { optional => 1},
|
||||
token => { optional => 1},
|
||||
influxdbproto => { optional => 1},
|
||||
timeout => { optional => 1},
|
||||
'max-body-size' => { optional => 1 },
|
||||
'api-path-prefix' => { optional => 1 },
|
||||
'verify-certificate' => { optional => 1 },
|
||||
};
|
||||
server => {},
|
||||
port => {},
|
||||
mtu => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
organization => { optional => 1 },
|
||||
bucket => { optional => 1 },
|
||||
token => { optional => 1 },
|
||||
influxdbproto => { optional => 1 },
|
||||
timeout => { optional => 1 },
|
||||
'max-body-size' => { optional => 1 },
|
||||
'api-path-prefix' => { optional => 1 },
|
||||
'verify-certificate' => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
my $set_ssl_opts = sub {
|
||||
|
|
@ -86,10 +90,10 @@ my $set_ssl_opts = sub {
|
|||
|
||||
my $cert_verify = $cfg->{'verify-certificate'} // 1;
|
||||
if (!$cert_verify) {
|
||||
$ua->ssl_opts(
|
||||
verify_hostname => 0,
|
||||
SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
|
||||
);
|
||||
$ua->ssl_opts(
|
||||
verify_hostname => 0,
|
||||
SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE,
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -110,8 +114,8 @@ sub update_qemu_status {
|
|||
$ctime *= 1000000000;
|
||||
|
||||
my $object = "object=qemu,vmid=$vmid,nodename=$nodename";
|
||||
if($data->{name} && $data->{name} ne '') {
|
||||
$object .= ",host=$data->{name}";
|
||||
if ($data->{name} && $data->{name} ne '') {
|
||||
$object .= ",host=$data->{name}";
|
||||
}
|
||||
$object =~ s/\s/\\ /g;
|
||||
|
||||
|
|
@ -125,8 +129,8 @@ sub update_lxc_status {
|
|||
$ctime *= 1000000000;
|
||||
|
||||
my $object = "object=lxc,vmid=$vmid,nodename=$nodename";
|
||||
if($data->{name} && $data->{name} ne '') {
|
||||
$object .= ",host=$data->{name}";
|
||||
if ($data->{name} && $data->{name} ne '') {
|
||||
$object .= ",host=$data->{name}";
|
||||
}
|
||||
$object =~ s/\s/\\ /g;
|
||||
|
||||
|
|
@ -140,8 +144,8 @@ sub update_storage_status {
|
|||
$ctime *= 1000000000;
|
||||
|
||||
my $object = "object=storages,nodename=$nodename,host=$storeid";
|
||||
if($data->{type} && $data->{type} ne '') {
|
||||
$object .= ",type=$data->{type}";
|
||||
if ($data->{type} && $data->{type} ne '') {
|
||||
$object .= ",type=$data->{type}";
|
||||
}
|
||||
$object =~ s/\s/\\ /g;
|
||||
|
||||
|
|
@ -152,7 +156,7 @@ sub _send_batch_size {
|
|||
my ($class, $cfg) = @_;
|
||||
my $proto = $cfg->{influxdbproto} // 'udp';
|
||||
if ($proto ne 'udp') {
|
||||
return $cfg->{'max-body-size'} // 25_000_000;
|
||||
return $cfg->{'max-body-size'} // 25_000_000;
|
||||
}
|
||||
|
||||
return $class->SUPER::_send_batch_size($cfg);
|
||||
|
|
@ -163,20 +167,20 @@ sub send {
|
|||
|
||||
my $proto = $cfg->{influxdbproto} // 'udp';
|
||||
if ($proto eq 'udp') {
|
||||
return $class->SUPER::send($connection, $data, $cfg);
|
||||
return $class->SUPER::send($connection, $data, $cfg);
|
||||
} elsif ($proto =~ m/^https?$/) {
|
||||
my $ua = LWP::UserAgent->new();
|
||||
$set_ssl_opts->($cfg, $ua);
|
||||
$ua->timeout($cfg->{timeout} // 1);
|
||||
$connection->content($data);
|
||||
my $response = $ua->request($connection);
|
||||
my $ua = LWP::UserAgent->new();
|
||||
$set_ssl_opts->($cfg, $ua);
|
||||
$ua->timeout($cfg->{timeout} // 1);
|
||||
$connection->content($data);
|
||||
my $response = $ua->request($connection);
|
||||
|
||||
if (!$response->is_success) {
|
||||
my $err = $response->status_line;
|
||||
die "$err\n";
|
||||
}
|
||||
if (!$response->is_success) {
|
||||
my $err = $response->status_line;
|
||||
die "$err\n";
|
||||
}
|
||||
} else {
|
||||
die "invalid protocol\n";
|
||||
die "invalid protocol\n";
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -186,7 +190,7 @@ sub _disconnect {
|
|||
my ($class, $connection, $cfg) = @_;
|
||||
my $proto = $cfg->{influxdbproto} // 'udp';
|
||||
if ($proto eq 'udp') {
|
||||
return $class->SUPER::_disconnect($connection, $cfg);
|
||||
return $class->SUPER::_disconnect($connection, $cfg);
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -197,11 +201,11 @@ sub _get_v2url {
|
|||
my ($proto, $host, $port) = $cfg->@{qw(influxdbproto server port)};
|
||||
my $api_prefix = $cfg->{'api-path-prefix'} // '/';
|
||||
if ($api_prefix ne '/' && $api_prefix =~ m!^/*(.+)/*$!) {
|
||||
$api_prefix = "/$1/";
|
||||
$api_prefix = "/$1/";
|
||||
}
|
||||
|
||||
if ($api_path ne 'health') {
|
||||
$api_path = "api/v2/${api_path}";
|
||||
$api_path = "api/v2/${api_path}";
|
||||
}
|
||||
|
||||
return "${proto}://${host}:${port}${api_prefix}${api_path}";
|
||||
|
|
@ -215,27 +219,27 @@ sub _connect {
|
|||
my $proto = $cfg->{influxdbproto} // 'udp';
|
||||
|
||||
if ($proto eq 'udp') {
|
||||
my $socket = IO::Socket::IP->new(
|
||||
PeerAddr => $host,
|
||||
PeerPort => $port,
|
||||
Proto => 'udp',
|
||||
) || die "couldn't create influxdb socket [$host]:$port - $@\n";
|
||||
my $socket = IO::Socket::IP->new(
|
||||
PeerAddr => $host,
|
||||
PeerPort => $port,
|
||||
Proto => 'udp',
|
||||
) || die "couldn't create influxdb socket [$host]:$port - $@\n";
|
||||
|
||||
$socket->blocking(0);
|
||||
$socket->blocking(0);
|
||||
|
||||
return $socket;
|
||||
return $socket;
|
||||
} elsif ($proto =~ m/^https?$/) {
|
||||
my $token = get_credentials($id, 1);
|
||||
my $org = $cfg->{organization} // 'proxmox';
|
||||
my $bucket = $cfg->{bucket} // 'proxmox';
|
||||
my $url = _get_v2url($cfg, "write?org=${org}&bucket=${bucket}");
|
||||
my $token = get_credentials($id, 1);
|
||||
my $org = $cfg->{organization} // 'proxmox';
|
||||
my $bucket = $cfg->{bucket} // 'proxmox';
|
||||
my $url = _get_v2url($cfg, "write?org=${org}&bucket=${bucket}");
|
||||
|
||||
my $req = HTTP::Request->new(POST => $url);
|
||||
if (defined($token)) {
|
||||
$req->header( "Authorization", "Token $token");
|
||||
}
|
||||
my $req = HTTP::Request->new(POST => $url);
|
||||
if (defined($token)) {
|
||||
$req->header("Authorization", "Token $token");
|
||||
}
|
||||
|
||||
return $req;
|
||||
return $req;
|
||||
}
|
||||
|
||||
die "cannot connect to InfluxDB: invalid protocol '$proto'\n";
|
||||
|
|
@ -249,25 +253,25 @@ sub test_connection {
|
|||
|
||||
my $proto = $cfg->{influxdbproto} // 'udp';
|
||||
if ($proto eq 'udp') {
|
||||
return $class->SUPER::test_connection($cfg, $id);
|
||||
return $class->SUPER::test_connection($cfg, $id);
|
||||
} elsif ($proto =~ m/^https?$/) {
|
||||
my $url = _get_v2url($cfg, "health");
|
||||
my $ua = LWP::UserAgent->new();
|
||||
$set_ssl_opts->($cfg, $ua);
|
||||
$ua->timeout($cfg->{timeout} // 1);
|
||||
# in the initial add connection test, the token may still be in $cfg
|
||||
my $token = $cfg->{token} // get_credentials($id, 1);
|
||||
if (defined($token)) {
|
||||
$ua->default_header("Authorization" => "Token $token");
|
||||
}
|
||||
my $response = $ua->get($url);
|
||||
my $url = _get_v2url($cfg, "health");
|
||||
my $ua = LWP::UserAgent->new();
|
||||
$set_ssl_opts->($cfg, $ua);
|
||||
$ua->timeout($cfg->{timeout} // 1);
|
||||
# in the initial add connection test, the token may still be in $cfg
|
||||
my $token = $cfg->{token} // get_credentials($id, 1);
|
||||
if (defined($token)) {
|
||||
$ua->default_header("Authorization" => "Token $token");
|
||||
}
|
||||
my $response = $ua->get($url);
|
||||
|
||||
if (!$response->is_success) {
|
||||
my $err = $response->status_line;
|
||||
die "$err\n";
|
||||
}
|
||||
if (!$response->is_success) {
|
||||
my $err = $response->status_line;
|
||||
die "$err\n";
|
||||
}
|
||||
} else {
|
||||
die "invalid protocol\n";
|
||||
die "invalid protocol\n";
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -277,40 +281,42 @@ sub build_influxdb_payload {
|
|||
my ($class, $txn, $data, $ctime, $tags, $excluded, $measurement, $instance) = @_;
|
||||
|
||||
# 'abc' and '123' are both valid hostnames/tags, that confuses influx's type detection
|
||||
my $to_quote = { name => 1, tags => 1, };
|
||||
my $to_quote = { name => 1, tags => 1 };
|
||||
|
||||
my @values = ();
|
||||
|
||||
foreach my $key (sort keys %$data) {
|
||||
next if defined($excluded) && $excluded->{$key};
|
||||
my $value = $data->{$key};
|
||||
next if !defined($value);
|
||||
next if defined($excluded) && $excluded->{$key};
|
||||
my $value = $data->{$key};
|
||||
next if !defined($value);
|
||||
|
||||
if (!ref($value) && $value ne '') {
|
||||
# value is scalar
|
||||
if (!ref($value) && $value ne '') {
|
||||
# value is scalar
|
||||
|
||||
if (defined(my $v = prepare_value($value, $to_quote->{$key}))) {
|
||||
push @values, "$key=$v";
|
||||
}
|
||||
} elsif (ref($value) eq 'HASH') {
|
||||
# value is a hash
|
||||
if (defined(my $v = prepare_value($value, $to_quote->{$key}))) {
|
||||
push @values, "$key=$v";
|
||||
}
|
||||
} elsif (ref($value) eq 'HASH') {
|
||||
# value is a hash
|
||||
|
||||
if (!defined($measurement)) {
|
||||
build_influxdb_payload($class, $txn, $value, $ctime, $tags, $excluded, $key);
|
||||
} elsif(!defined($instance)) {
|
||||
build_influxdb_payload($class, $txn, $value, $ctime, $tags, $excluded, $measurement, $key);
|
||||
} else {
|
||||
push @values, get_recursive_values($value);
|
||||
}
|
||||
}
|
||||
if (!defined($measurement)) {
|
||||
build_influxdb_payload($class, $txn, $value, $ctime, $tags, $excluded, $key);
|
||||
} elsif (!defined($instance)) {
|
||||
build_influxdb_payload(
|
||||
$class, $txn, $value, $ctime, $tags, $excluded, $measurement, $key,
|
||||
);
|
||||
} else {
|
||||
push @values, get_recursive_values($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (@values > 0) {
|
||||
my $mm = $measurement // 'system';
|
||||
my $tagstring = $tags;
|
||||
$tagstring .= ",instance=$instance" if defined($instance);
|
||||
my $valuestr = join(',', @values);
|
||||
$class->add_metric_data($txn, "$mm,$tagstring $valuestr $ctime\n");
|
||||
my $mm = $measurement // 'system';
|
||||
my $tagstring = $tags;
|
||||
$tagstring .= ",instance=$instance" if defined($instance);
|
||||
my $valuestr = join(',', @values);
|
||||
$class->add_metric_data($txn, "$mm,$tagstring $valuestr $ctime\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -320,14 +326,14 @@ sub get_recursive_values {
|
|||
my @values = ();
|
||||
|
||||
foreach my $key (keys %$hash) {
|
||||
my $value = $hash->{$key};
|
||||
if(ref($value) eq 'HASH') {
|
||||
push(@values, get_recursive_values($value));
|
||||
} elsif (!ref($value) && $value ne '') {
|
||||
if (defined(my $v = prepare_value($value))) {
|
||||
push @values, "$key=$v";
|
||||
}
|
||||
}
|
||||
my $value = $hash->{$key};
|
||||
if (ref($value) eq 'HASH') {
|
||||
push(@values, get_recursive_values($value));
|
||||
} elsif (!ref($value) && $value ne '') {
|
||||
if (defined(my $v = prepare_value($value))) {
|
||||
push @values, "$key=$v";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return @values;
|
||||
|
|
@ -338,13 +344,13 @@ sub prepare_value {
|
|||
|
||||
# don't treat value like a number if quote is 1
|
||||
if (!$force_quote && looks_like_number($value)) {
|
||||
if (isnan($value) || isinf($value)) {
|
||||
# we cannot send influxdb NaN or Inf
|
||||
return undef;
|
||||
}
|
||||
if (isnan($value) || isinf($value)) {
|
||||
# we cannot send influxdb NaN or Inf
|
||||
return undef;
|
||||
}
|
||||
|
||||
# influxdb also accepts 1.0e+10, etc.
|
||||
return $value;
|
||||
# influxdb also accepts 1.0e+10, etc.
|
||||
return $value;
|
||||
}
|
||||
|
||||
# non-numeric values require to be quoted, so escape " with \"
|
||||
|
|
@ -365,8 +371,8 @@ sub delete_credentials {
|
|||
my ($id) = @_;
|
||||
|
||||
if (my $cred_file = cred_file_name($id)) {
|
||||
unlink($cred_file)
|
||||
or warn "removing influxdb credentials file '$cred_file' failed: $!\n";
|
||||
unlink($cred_file)
|
||||
or warn "removing influxdb credentials file '$cred_file' failed: $!\n";
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -399,9 +405,9 @@ sub on_add_hook {
|
|||
my $token = $sensitive_opts->{token};
|
||||
|
||||
if (defined($token)) {
|
||||
set_credentials($id, $token);
|
||||
set_credentials($id, $token);
|
||||
} else {
|
||||
delete_credentials($id);
|
||||
delete_credentials($id);
|
||||
}
|
||||
|
||||
return undef;
|
||||
|
|
@ -413,9 +419,9 @@ sub on_update_hook {
|
|||
|
||||
my $token = $sensitive_opts->{token};
|
||||
if (defined($token)) {
|
||||
set_credentials($id, $token);
|
||||
set_credentials($id, $token);
|
||||
} else {
|
||||
delete_credentials($id);
|
||||
delete_credentials($id);
|
||||
}
|
||||
|
||||
return undef;
|
||||
|
|
@ -429,5 +435,4 @@ sub on_delete_hook {
|
|||
return undef;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -9,44 +9,48 @@ use PVE::SectionConfig;
|
|||
|
||||
use base qw(PVE::SectionConfig);
|
||||
|
||||
cfs_register_file('status.cfg',
|
||||
cfs_register_file(
|
||||
'status.cfg',
|
||||
sub { __PACKAGE__->parse_config(@_); },
|
||||
sub { __PACKAGE__->write_config(@_); }
|
||||
sub { __PACKAGE__->write_config(@_); },
|
||||
);
|
||||
|
||||
my $defaultData = {
|
||||
propertyList => {
|
||||
id => {
|
||||
description => "The ID of the entry.",
|
||||
type => 'string', format => 'pve-configid',
|
||||
},
|
||||
type => {
|
||||
description => "Plugin type.",
|
||||
type => 'string', format => 'pve-configid',
|
||||
},
|
||||
disable => {
|
||||
description => "Flag to disable the plugin.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
server => {
|
||||
type => 'string', format => 'address',
|
||||
description => "server dns name or IP address",
|
||||
},
|
||||
port => {
|
||||
type => 'integer',
|
||||
description => "server network port",
|
||||
minimum => 1,
|
||||
maximum => 64*1024,
|
||||
},
|
||||
mtu => {
|
||||
type => 'integer',
|
||||
description => "MTU for metrics transmission over UDP",
|
||||
default => 1500,
|
||||
minimum => 512,
|
||||
maximum => 64*1024,
|
||||
optional => 1,
|
||||
},
|
||||
id => {
|
||||
description => "The ID of the entry.",
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
type => {
|
||||
description => "Plugin type.",
|
||||
type => 'string',
|
||||
format => 'pve-configid',
|
||||
},
|
||||
disable => {
|
||||
description => "Flag to disable the plugin.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
server => {
|
||||
type => 'string',
|
||||
format => 'address',
|
||||
description => "server dns name or IP address",
|
||||
},
|
||||
port => {
|
||||
type => 'integer',
|
||||
description => "server network port",
|
||||
minimum => 1,
|
||||
maximum => 64 * 1024,
|
||||
},
|
||||
mtu => {
|
||||
type => 'integer',
|
||||
description => "MTU for metrics transmission over UDP",
|
||||
default => 1500,
|
||||
minimum => 512,
|
||||
maximum => 64 * 1024,
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -58,13 +62,13 @@ sub parse_section_header {
|
|||
my ($class, $line) = @_;
|
||||
|
||||
if ($line =~ m/^(\S+):\s*(\S+)?\s*$/) {
|
||||
my $type = lc($1);
|
||||
my $id = $2 // $type;
|
||||
my $errmsg = undef; # set if you want to skip whole section
|
||||
eval { PVE::JSONSchema::pve_verify_configid($id) };
|
||||
$errmsg = $@ if $@;
|
||||
my $config = {}; # to return additional attributes
|
||||
return ($type, $id, $errmsg, $config);
|
||||
my $type = lc($1);
|
||||
my $id = $2 // $type;
|
||||
my $errmsg = undef; # set if you want to skip whole section
|
||||
eval { PVE::JSONSchema::pve_verify_configid($id) };
|
||||
$errmsg = $@ if $@;
|
||||
my $config = {}; # to return additional attributes
|
||||
return ($type, $id, $errmsg, $config);
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
|
@ -99,7 +103,7 @@ sub add_metric_data {
|
|||
my $dataq_len = length($txn->{data}) // 0;
|
||||
|
||||
if (($dataq_len + $data_length) >= $batch_size) {
|
||||
$class->flush_data($txn);
|
||||
$class->flush_data($txn);
|
||||
}
|
||||
$txn->{data} //= '';
|
||||
$txn->{data} .= "$data";
|
||||
|
|
@ -109,8 +113,8 @@ sub flush_data {
|
|||
my ($class, $txn) = @_;
|
||||
|
||||
if (!$txn->{connection}) {
|
||||
return if !$txn->{data}; # OK, if data was already sent/flushed
|
||||
die "cannot flush metric data, no connection available!\n";
|
||||
return if !$txn->{data}; # OK, if data was already sent/flushed
|
||||
die "cannot flush metric data, no connection available!\n";
|
||||
}
|
||||
return if !defined($txn->{data}) || $txn->{data} eq '';
|
||||
|
||||
|
|
@ -123,7 +127,7 @@ sub send {
|
|||
my ($class, $connection, $data, $cfg) = @_;
|
||||
|
||||
defined($connection->send($data))
|
||||
or die "failed to send metrics: $!\n";
|
||||
or die "failed to send metrics: $!\n";
|
||||
}
|
||||
|
||||
sub test_connection {
|
||||
|
|
|
|||
1704
PVE/VZDump.pm
1704
PVE/VZDump.pm
File diff suppressed because it is too large
Load diff
|
|
@ -10,7 +10,7 @@ my $pkglist = PVE::APLInfo::load_data();
|
|||
|
||||
my $err = 0;
|
||||
|
||||
foreach my $k (keys %{$pkglist->{'all'}}) {
|
||||
foreach my $k (keys %{ $pkglist->{'all'} }) {
|
||||
next if $k eq 'pve-web-news';
|
||||
my $res = $pkglist->{all}->{$k};
|
||||
|
||||
|
|
@ -18,11 +18,11 @@ foreach my $k (keys %{$pkglist->{'all'}}) {
|
|||
my $template = "$res->{package}_$res->{version}_$res->{architecture}.tar";
|
||||
|
||||
if ($k !~ m/^($res->{os}-)?\Q$template\E\.(gz|xz|zst)$/) {
|
||||
print "ERROR: $k != $template\n";
|
||||
#print Dumper($res) . "\n";
|
||||
$err = 1;
|
||||
print "ERROR: $k != $template\n";
|
||||
#print Dumper($res) . "\n";
|
||||
$err = 1;
|
||||
}
|
||||
}
|
||||
|
||||
$err ? exit (-11) : exit (0);
|
||||
$err ? exit(-11) : exit(0);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,13 +13,13 @@ use PVE::Tools;
|
|||
my $country = {};
|
||||
|
||||
my $line;
|
||||
open (TMP, "</usr/share/zoneinfo/iso3166.tab");
|
||||
while (defined ($line = <TMP>)) {
|
||||
open(TMP, "</usr/share/zoneinfo/iso3166.tab");
|
||||
while (defined($line = <TMP>)) {
|
||||
if ($line =~ m/^([A-Z][A-Z])\s+(.*\S)\s*$/) {
|
||||
$country->{lc($1)} = $2;
|
||||
$country->{ lc($1) } = $2;
|
||||
}
|
||||
}
|
||||
close (TMP);
|
||||
close(TMP);
|
||||
|
||||
# we need mappings for X11, console, and kvm vnc
|
||||
|
||||
|
|
@ -27,52 +27,51 @@ close (TMP);
|
|||
my $keymaps = PVE::Tools::kvmkeymaps();
|
||||
|
||||
foreach my $km (sort keys %$keymaps) {
|
||||
my ($desc, $kvm, $console, $x11, $x11var) = @{$keymaps->{$km}};
|
||||
my ($desc, $kvm, $console, $x11, $x11var) = @{ $keymaps->{$km} };
|
||||
|
||||
if ($km =~m/^([a-z][a-z])-([a-z][a-z])$/i) {
|
||||
defined ($country->{$2}) || die "undefined country code '$2'";
|
||||
if ($km =~ m/^([a-z][a-z])-([a-z][a-z])$/i) {
|
||||
defined($country->{$2}) || die "undefined country code '$2'";
|
||||
} else {
|
||||
defined ($country->{$km}) || die "undefined country code '$km'";
|
||||
defined($country->{$km}) || die "undefined country code '$km'";
|
||||
}
|
||||
|
||||
$x11var = '' if !defined ($x11var);
|
||||
$x11var = '' if !defined($x11var);
|
||||
print "map:$km:$desc:$kvm:$console:$x11:$x11var:\n";
|
||||
}
|
||||
|
||||
my $defmap = {
|
||||
'us' => 'en-us',
|
||||
'be' => 'fr-be',
|
||||
'br' => 'pt-br',
|
||||
'ca' => 'en-us',
|
||||
'dk' => 'dk',
|
||||
'nl' => 'en-us', # most Dutch people us US layout
|
||||
'fi' => 'fi',
|
||||
'fr' => 'fr',
|
||||
'de' => 'de',
|
||||
'at' => 'de',
|
||||
'hu' => 'hu',
|
||||
'is' => 'is',
|
||||
'it' => 'it',
|
||||
'va' => 'it',
|
||||
'jp' => 'jp',
|
||||
'lt' => 'lt',
|
||||
'mk' => 'mk',
|
||||
'no' => 'no',
|
||||
'pl' => 'pl',
|
||||
'pt' => 'pt',
|
||||
'si' => 'si',
|
||||
'es' => 'es',
|
||||
'gi' => 'es',
|
||||
'ch' => 'de-ch',
|
||||
'gb' => 'en-gb',
|
||||
'lu' => 'fr-ch',
|
||||
'li' => 'de-ch',
|
||||
'us' => 'en-us',
|
||||
'be' => 'fr-be',
|
||||
'br' => 'pt-br',
|
||||
'ca' => 'en-us',
|
||||
'dk' => 'dk',
|
||||
'nl' => 'en-us', # most Dutch people us US layout
|
||||
'fi' => 'fi',
|
||||
'fr' => 'fr',
|
||||
'de' => 'de',
|
||||
'at' => 'de',
|
||||
'hu' => 'hu',
|
||||
'is' => 'is',
|
||||
'it' => 'it',
|
||||
'va' => 'it',
|
||||
'jp' => 'jp',
|
||||
'lt' => 'lt',
|
||||
'mk' => 'mk',
|
||||
'no' => 'no',
|
||||
'pl' => 'pl',
|
||||
'pt' => 'pt',
|
||||
'si' => 'si',
|
||||
'es' => 'es',
|
||||
'gi' => 'es',
|
||||
'ch' => 'de-ch',
|
||||
'gb' => 'en-gb',
|
||||
'lu' => 'fr-ch',
|
||||
'li' => 'de-ch',
|
||||
};
|
||||
|
||||
|
||||
my $mirrors = PVE::Tools::debmirrors();
|
||||
foreach my $cc (keys %$mirrors) {
|
||||
die "undefined country code '$cc'" if !defined ($country->{$cc});
|
||||
die "undefined country code '$cc'" if !defined($country->{$cc});
|
||||
}
|
||||
|
||||
foreach my $cc (sort keys %$country) {
|
||||
|
|
|
|||
|
|
@ -14,67 +14,64 @@ use Data::Dumper;
|
|||
# NOTE: not exhausive, reduced to actually required fields!
|
||||
my $tree = {
|
||||
nodes => [
|
||||
{
|
||||
id => -3,
|
||||
name => 'pveA',
|
||||
children => [ 0,1,2,3 ],
|
||||
type => 'host',
|
||||
},
|
||||
{
|
||||
id => -5,
|
||||
name => 'pveB',
|
||||
children => [ 4,5,6,7 ],
|
||||
type => 'host',
|
||||
},
|
||||
{
|
||||
id => -7,
|
||||
name => 'pveC',
|
||||
children => [ 8,9,10,11 ],
|
||||
type => 'host',
|
||||
},
|
||||
{
|
||||
id => -3,
|
||||
name => 'pveA',
|
||||
children => [0, 1, 2, 3],
|
||||
type => 'host',
|
||||
},
|
||||
{
|
||||
id => -5,
|
||||
name => 'pveB',
|
||||
children => [4, 5, 6, 7],
|
||||
type => 'host',
|
||||
},
|
||||
{
|
||||
id => -7,
|
||||
name => 'pveC',
|
||||
children => [8, 9, 10, 11],
|
||||
type => 'host',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
# Check if all the grep and casts are correct
|
||||
my @belong_to_B = ( 4,5 );
|
||||
my @not_belong_to_B = ( -1,1,10,15 );
|
||||
my @belong_to_B = (4, 5);
|
||||
my @not_belong_to_B = (-1, 1, 10, 15);
|
||||
foreach (@belong_to_B) {
|
||||
is (
|
||||
PVE::API2::Ceph::OSD::osd_belongs_to_node($tree, 'pveB', $_),
|
||||
1,
|
||||
"OSD $_ belongs to node pveB",
|
||||
is(
|
||||
PVE::API2::Ceph::OSD::osd_belongs_to_node($tree, 'pveB', $_),
|
||||
1,
|
||||
"OSD $_ belongs to node pveB",
|
||||
);
|
||||
}
|
||||
foreach (@not_belong_to_B) {
|
||||
is (
|
||||
PVE::API2::Ceph::OSD::osd_belongs_to_node($tree, 'pveB', $_),
|
||||
0,
|
||||
"OSD $_ does not belong to node pveB",
|
||||
is(
|
||||
PVE::API2::Ceph::OSD::osd_belongs_to_node($tree, 'pveB', $_),
|
||||
0,
|
||||
"OSD $_ does not belong to node pveB",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
my $double_nodes_tree = {
|
||||
nodes => [
|
||||
{
|
||||
name => 'pveA',
|
||||
type => 'host',
|
||||
},
|
||||
{
|
||||
name => 'pveA',
|
||||
type => 'host',
|
||||
}
|
||||
]
|
||||
{
|
||||
name => 'pveA',
|
||||
type => 'host',
|
||||
},
|
||||
{
|
||||
name => 'pveA',
|
||||
type => 'host',
|
||||
},
|
||||
],
|
||||
};
|
||||
eval { PVE::API2::Ceph::OSD::osd_belongs_to_node($double_nodes_tree, 'pveA') };
|
||||
like($@, qr/duplicate host name found/, "Die if node occurs too often");
|
||||
|
||||
is (
|
||||
is(
|
||||
PVE::API2::Ceph::OSD::osd_belongs_to_node(undef),
|
||||
0,
|
||||
"Early-return false if there's no/empty node tree",
|
||||
);
|
||||
|
||||
|
||||
done_testing(@belong_to_B + @not_belong_to_B + 2);
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ our $mocked_ct_configs = {};
|
|||
|
||||
my $mocked_get_members = sub {
|
||||
return {
|
||||
node1 => { online => 1 },
|
||||
node2 => { online => 1 },
|
||||
node3 => { online => 1 },
|
||||
node1 => { online => 1 },
|
||||
node2 => { online => 1 },
|
||||
node3 => { online => 1 },
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -45,18 +45,18 @@ my $mocked_vmlist = sub {
|
|||
my $res = {};
|
||||
|
||||
foreach my $id (keys %$mocked_ct_configs) {
|
||||
my $d = $mocked_ct_configs->{$id};
|
||||
$res->{$id} = { 'type' => 'lxc', 'node' => $d->{node}, 'version' => 1 };
|
||||
my $d = $mocked_ct_configs->{$id};
|
||||
$res->{$id} = { 'type' => 'lxc', 'node' => $d->{node}, 'version' => 1 };
|
||||
}
|
||||
foreach my $id (keys %$mocked_vm_configs) {
|
||||
my $d = $mocked_vm_configs->{$id};
|
||||
$res->{$id} = { 'type' => 'qemu', 'node' => $d->{node}, 'version' => 1 };
|
||||
my $d = $mocked_vm_configs->{$id};
|
||||
$res->{$id} = { 'type' => 'qemu', 'node' => $d->{node}, 'version' => 1 };
|
||||
}
|
||||
|
||||
return { 'ids' => $res };
|
||||
};
|
||||
|
||||
my $mocked_get_ssh_info = sub {
|
||||
my $mocked_get_ssh_info = sub {
|
||||
my ($node, $network_cidr) = @_;
|
||||
|
||||
return { node => $node };
|
||||
|
|
@ -129,24 +129,24 @@ my $mocked_replication_config_new = sub {
|
|||
|
||||
my $mocked_storage_config = {
|
||||
ids => {
|
||||
local => {
|
||||
type => 'dir',
|
||||
shared => 0,
|
||||
content => {
|
||||
'iso' => 1,
|
||||
'backup' => 1,
|
||||
},
|
||||
path => "/var/lib/vz",
|
||||
},
|
||||
'local-zfs' => {
|
||||
type => 'zfspool',
|
||||
pool => 'nonexistent-testpool',
|
||||
shared => 0,
|
||||
content => {
|
||||
'images' => 1,
|
||||
'rootdir' => 1
|
||||
},
|
||||
},
|
||||
local => {
|
||||
type => 'dir',
|
||||
shared => 0,
|
||||
content => {
|
||||
'iso' => 1,
|
||||
'backup' => 1,
|
||||
},
|
||||
path => "/var/lib/vz",
|
||||
},
|
||||
'local-zfs' => {
|
||||
type => 'zfspool',
|
||||
pool => 'nonexistent-testpool',
|
||||
shared => 0,
|
||||
content => {
|
||||
'images' => 1,
|
||||
'rootdir' => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -160,8 +160,8 @@ sub generate_snapshot_info {
|
|||
$timestamp_counter++;
|
||||
|
||||
return {
|
||||
id => $timestamp_counter,
|
||||
timestamp => $timestamp_counter,
|
||||
id => $timestamp_counter,
|
||||
timestamp => $timestamp_counter,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -169,8 +169,8 @@ sub register_mocked_volid {
|
|||
my ($volid, $snapname) = @_;
|
||||
|
||||
my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
|
||||
my $scfg = $mocked_storage_config->{ids}->{$storeid} ||
|
||||
die "no such storage '$storeid'\n";
|
||||
my $scfg = $mocked_storage_config->{ids}->{$storeid}
|
||||
|| die "no such storage '$storeid'\n";
|
||||
|
||||
my $d = $mocked_storage_content->{$storeid}->{$volname} //= {};
|
||||
|
||||
|
|
@ -261,9 +261,9 @@ sub setup {
|
|||
$pve_storage_module->mock(volume_snapshot_info => $mocked_volume_snapshot_info);
|
||||
|
||||
$pve_replication_config_module->mock(
|
||||
new => $mocked_replication_config_new,
|
||||
lock => sub { $mocked_cfs_lock_file->('replication.cfg', undef, $_[0]); },
|
||||
write => sub { $mocked_cfs_write_file->('replication.cfg', $_[0]); },
|
||||
new => $mocked_replication_config_new,
|
||||
lock => sub { $mocked_cfs_lock_file->('replication.cfg', undef, $_[0]); },
|
||||
write => sub { $mocked_cfs_write_file->('replication.cfg', $_[0]); },
|
||||
);
|
||||
$pve_qemuserver_module->mock(check_running => sub { return 0; });
|
||||
$pve_qemuconfig_module->mock(load_config => $mocked_qemu_load_conf);
|
||||
|
|
@ -271,20 +271,20 @@ sub setup {
|
|||
$pve_lxc_config_module->mock(load_config => $mocked_lxc_load_conf);
|
||||
|
||||
$pve_sshinfo_module->mock(
|
||||
get_ssh_info => $mocked_get_ssh_info,
|
||||
ssh_info_to_command => $mocked_ssh_info_to_command,
|
||||
get_ssh_info => $mocked_get_ssh_info,
|
||||
ssh_info_to_command => $mocked_ssh_info_to_command,
|
||||
);
|
||||
|
||||
$pve_cluster_module->mock(
|
||||
get_vmlist => sub { return $mocked_vmlist->(); },
|
||||
get_members => $mocked_get_members,
|
||||
cfs_update => sub {},
|
||||
cfs_lock_file => $mocked_cfs_lock_file,
|
||||
cfs_write_file => $mocked_cfs_write_file,
|
||||
cfs_read_file => $mocked_cfs_read_file,
|
||||
get_vmlist => sub { return $mocked_vmlist->(); },
|
||||
get_members => $mocked_get_members,
|
||||
cfs_update => sub { },
|
||||
cfs_lock_file => $mocked_cfs_lock_file,
|
||||
cfs_write_file => $mocked_cfs_write_file,
|
||||
cfs_read_file => $mocked_cfs_read_file,
|
||||
);
|
||||
$pve_inotify_module->mock('nodename' => sub { return $mocked_nodename; });
|
||||
};
|
||||
}
|
||||
|
||||
# code to generate/conpare test logs
|
||||
|
||||
|
|
@ -295,19 +295,19 @@ sub openlog {
|
|||
my ($filename) = @_;
|
||||
|
||||
if (!$filename) {
|
||||
# compute from $0
|
||||
$filename = basename($0);
|
||||
if ($filename =~ m/^(\S+)\.pl$/) {
|
||||
$filename = "$1.log";
|
||||
} else {
|
||||
die "unable to compute log name for $0";
|
||||
}
|
||||
# compute from $0
|
||||
$filename = basename($0);
|
||||
if ($filename =~ m/^(\S+)\.pl$/) {
|
||||
$filename = "$1.log";
|
||||
} else {
|
||||
die "unable to compute log name for $0";
|
||||
}
|
||||
}
|
||||
|
||||
die "log already open" if defined($logname);
|
||||
|
||||
open (my $fh, ">", "$filename.tmp") ||
|
||||
die "unable to open log - $!";
|
||||
open(my $fh, ">", "$filename.tmp")
|
||||
|| die "unable to open log - $!";
|
||||
|
||||
$logname = $filename;
|
||||
$logfh = $fh;
|
||||
|
|
@ -318,15 +318,15 @@ sub commit_log {
|
|||
close($logfh);
|
||||
|
||||
if (-f $logname) {
|
||||
my $diff = `diff -u '$logname' '$logname.tmp'`;
|
||||
if ($diff) {
|
||||
warn "got unexpected output\n";
|
||||
print "# diff -u '$logname' '$logname.tmp'\n";
|
||||
print $diff;
|
||||
exit(-1);
|
||||
}
|
||||
my $diff = `diff -u '$logname' '$logname.tmp'`;
|
||||
if ($diff) {
|
||||
warn "got unexpected output\n";
|
||||
print "# diff -u '$logname' '$logname.tmp'\n";
|
||||
print $diff;
|
||||
exit(-1);
|
||||
}
|
||||
} else {
|
||||
rename("$logname.tmp", $logname) || die "rename log failed - $!";
|
||||
rename("$logname.tmp", $logname) || die "rename log failed - $!";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -339,18 +339,18 @@ sub track_jobs {
|
|||
$mocked_log_time = $ctime;
|
||||
|
||||
my $logmsg = sub {
|
||||
my ($msg) = @_;
|
||||
my ($msg) = @_;
|
||||
|
||||
print "$msg\n";
|
||||
print $logfh "$msg\n";
|
||||
print "$msg\n";
|
||||
print $logfh "$msg\n";
|
||||
};
|
||||
|
||||
if (!$status) {
|
||||
$status = PVE::ReplicationState::job_status();
|
||||
foreach my $jobid (sort keys %$status) {
|
||||
my $jobcfg = $status->{$jobid};
|
||||
$logmsg->("$ctime $jobid: new job next_sync => $jobcfg->{next_sync}");
|
||||
}
|
||||
$status = PVE::ReplicationState::job_status();
|
||||
foreach my $jobid (sort keys %$status) {
|
||||
my $jobcfg = $status->{$jobid};
|
||||
$logmsg->("$ctime $jobid: new job next_sync => $jobcfg->{next_sync}");
|
||||
}
|
||||
}
|
||||
|
||||
PVE::API2::Replication::run_jobs($ctime, $logmsg, 1);
|
||||
|
|
@ -359,62 +359,61 @@ sub track_jobs {
|
|||
|
||||
# detect removed jobs
|
||||
foreach my $jobid (sort keys %$status) {
|
||||
if (!$new->{$jobid}) {
|
||||
$logmsg->("$ctime $jobid: vanished job");
|
||||
}
|
||||
if (!$new->{$jobid}) {
|
||||
$logmsg->("$ctime $jobid: vanished job");
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $jobid (sort keys %$new) {
|
||||
my $jobcfg = $new->{$jobid};
|
||||
my $oldcfg = $status->{$jobid};
|
||||
if (!$oldcfg) {
|
||||
$logmsg->("$ctime $jobid: new job next_sync => $jobcfg->{next_sync}");
|
||||
next; # no old state to compare
|
||||
} else {
|
||||
foreach my $k (qw(target guest vmtype next_sync)) {
|
||||
my $changes = '';
|
||||
if ($oldcfg->{$k} ne $jobcfg->{$k}) {
|
||||
$changes .= ', ' if $changes;
|
||||
$changes .= "$k => $jobcfg->{$k}";
|
||||
}
|
||||
$logmsg->("$ctime $jobid: changed config $changes") if $changes;
|
||||
}
|
||||
}
|
||||
my $jobcfg = $new->{$jobid};
|
||||
my $oldcfg = $status->{$jobid};
|
||||
if (!$oldcfg) {
|
||||
$logmsg->("$ctime $jobid: new job next_sync => $jobcfg->{next_sync}");
|
||||
next; # no old state to compare
|
||||
} else {
|
||||
foreach my $k (qw(target guest vmtype next_sync)) {
|
||||
my $changes = '';
|
||||
if ($oldcfg->{$k} ne $jobcfg->{$k}) {
|
||||
$changes .= ', ' if $changes;
|
||||
$changes .= "$k => $jobcfg->{$k}";
|
||||
}
|
||||
$logmsg->("$ctime $jobid: changed config $changes") if $changes;
|
||||
}
|
||||
}
|
||||
|
||||
my $oldstate = $oldcfg->{state};
|
||||
my $oldstate = $oldcfg->{state};
|
||||
|
||||
my $state = $jobcfg->{state};
|
||||
my $state = $jobcfg->{state};
|
||||
|
||||
my $changes = '';
|
||||
foreach my $k (qw(last_node last_try last_sync fail_count error)) {
|
||||
if (($oldstate->{$k} // '') ne ($state->{$k} // '')) {
|
||||
my $value = $state->{$k} // '';
|
||||
chomp $value;
|
||||
$changes .= ', ' if $changes;
|
||||
$changes .= "$k => $value";
|
||||
}
|
||||
}
|
||||
$logmsg->("$ctime $jobid: changed state $changes") if $changes;
|
||||
my $changes = '';
|
||||
foreach my $k (qw(last_node last_try last_sync fail_count error)) {
|
||||
if (($oldstate->{$k} // '') ne ($state->{$k} // '')) {
|
||||
my $value = $state->{$k} // '';
|
||||
chomp $value;
|
||||
$changes .= ', ' if $changes;
|
||||
$changes .= "$k => $value";
|
||||
}
|
||||
}
|
||||
$logmsg->("$ctime $jobid: changed state $changes") if $changes;
|
||||
|
||||
my $old_storeid_list = $oldstate->{storeid_list};
|
||||
my $storeid_list = $state->{storeid_list};
|
||||
my $old_storeid_list = $oldstate->{storeid_list};
|
||||
my $storeid_list = $state->{storeid_list};
|
||||
|
||||
my $storeid_list_changes = 0;
|
||||
foreach my $storeid (@$storeid_list) {
|
||||
next if grep { $_ eq $storeid } @$old_storeid_list;
|
||||
$storeid_list_changes = 1;
|
||||
}
|
||||
my $storeid_list_changes = 0;
|
||||
foreach my $storeid (@$storeid_list) {
|
||||
next if grep { $_ eq $storeid } @$old_storeid_list;
|
||||
$storeid_list_changes = 1;
|
||||
}
|
||||
|
||||
foreach my $storeid (@$old_storeid_list) {
|
||||
next if grep { $_ eq $storeid } @$storeid_list;
|
||||
$storeid_list_changes = 1;
|
||||
}
|
||||
foreach my $storeid (@$old_storeid_list) {
|
||||
next if grep { $_ eq $storeid } @$storeid_list;
|
||||
$storeid_list_changes = 1;
|
||||
}
|
||||
|
||||
$logmsg->("$ctime $jobid: changed storeid list " . join(',', @$storeid_list))
|
||||
if $storeid_list_changes;
|
||||
$logmsg->("$ctime $jobid: changed storeid list " . join(',', @$storeid_list))
|
||||
if $storeid_list_changes;
|
||||
}
|
||||
$status = $new;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -10,18 +10,18 @@ my $debug = 0;
|
|||
|
||||
my $test_status1 = {
|
||||
100 => {
|
||||
maxmem => GB(2),
|
||||
shares => 2000,
|
||||
balloon => GB(1),
|
||||
balloon_min => GB(1),
|
||||
freemem => MB(0),
|
||||
maxmem => GB(2),
|
||||
shares => 2000,
|
||||
balloon => GB(1),
|
||||
balloon_min => GB(1),
|
||||
freemem => MB(0),
|
||||
},
|
||||
101 => {
|
||||
maxmem => GB(2),
|
||||
shares => 1000,
|
||||
balloon => GB(1),
|
||||
balloon_min => GB(1),
|
||||
freemem => MB(0),
|
||||
maxmem => GB(2),
|
||||
shares => 1000,
|
||||
balloon => GB(1),
|
||||
balloon_min => GB(1),
|
||||
freemem => MB(0),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -36,18 +36,18 @@ absim($test_status1, MB(900), 100 => MB(1600), 101 => MB(1300));
|
|||
|
||||
my $test_status2 = {
|
||||
100 => {
|
||||
maxmem => GB(2),
|
||||
shares => 2000,
|
||||
balloon => GB(2),
|
||||
balloon_min => GB(2),
|
||||
freemem => MB(0),
|
||||
maxmem => GB(2),
|
||||
shares => 2000,
|
||||
balloon => GB(2),
|
||||
balloon_min => GB(2),
|
||||
freemem => MB(0),
|
||||
},
|
||||
101 => {
|
||||
maxmem => GB(2),
|
||||
shares => 1000,
|
||||
balloon => GB(1),
|
||||
balloon_min => GB(1),
|
||||
freemem => MB(0),
|
||||
maxmem => GB(2),
|
||||
shares => 1000,
|
||||
balloon => GB(1),
|
||||
balloon_min => GB(1),
|
||||
freemem => MB(0),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -57,38 +57,38 @@ abtest($test_status2, MB(500), 101 => MB(1100));
|
|||
|
||||
my $test_status3 = {
|
||||
100 => {
|
||||
maxmem => GB(2),
|
||||
shares => 2000,
|
||||
balloon => GB(2),
|
||||
balloon_min => GB(2),
|
||||
freemem => MB(0),
|
||||
maxmem => GB(2),
|
||||
shares => 2000,
|
||||
balloon => GB(2),
|
||||
balloon_min => GB(2),
|
||||
freemem => MB(0),
|
||||
},
|
||||
101 => {
|
||||
maxmem => GB(2),
|
||||
shares => 1000,
|
||||
balloon => GB(1)+MB(7),
|
||||
balloon_min => GB(1),
|
||||
freemem => MB(0),
|
||||
maxmem => GB(2),
|
||||
shares => 1000,
|
||||
balloon => GB(1) + MB(7),
|
||||
balloon_min => GB(1),
|
||||
freemem => MB(0),
|
||||
},
|
||||
102 => {
|
||||
maxmem => GB(2),
|
||||
shares => 1000,
|
||||
balloon => GB(1),
|
||||
balloon_min => GB(1),
|
||||
freemem => MB(512),
|
||||
maxmem => GB(2),
|
||||
shares => 1000,
|
||||
balloon => GB(1),
|
||||
balloon_min => GB(1),
|
||||
freemem => MB(512),
|
||||
},
|
||||
};
|
||||
|
||||
abtest($test_status3, 0);
|
||||
abtest($test_status3, MB(11), 101 => MB(1018));
|
||||
abtest($test_status3, MB(80), 101 => MB(1087));
|
||||
abtest($test_status3, MB(200), 101 => MB(1107));
|
||||
abtest($test_status3, MB(11), 101 => MB(1018));
|
||||
abtest($test_status3, MB(80), 101 => MB(1087));
|
||||
abtest($test_status3, MB(200), 101 => MB(1107));
|
||||
|
||||
my $status = absim($test_status3, MB(593), 101 => MB(1300), 102 => MB(1300));
|
||||
my $status = absim($test_status3, MB(593), 101 => MB(1300), 102 => MB(1300));
|
||||
absim($status, -MB(200), 101 => MB(1200), 102 => MB(1200));
|
||||
absim($status, -MB(400), 101 => MB(1200), 102 => GB(1));
|
||||
absim($status, -MB(593), 101 => MB(1007), 102 => GB(1));
|
||||
exit (0);
|
||||
exit(0);
|
||||
|
||||
sub abapply {
|
||||
my ($vmstatus, $res, $sum) = @_;
|
||||
|
|
@ -96,39 +96,40 @@ sub abapply {
|
|||
my $changes = 0;
|
||||
my $abschanges = 0;
|
||||
foreach my $vmid (keys %$res) {
|
||||
my $diff = $res->{$vmid} - $vmstatus->{$vmid}->{balloon};
|
||||
if ($diff != 0) {
|
||||
# fixme: adjust freemem ?
|
||||
$vmstatus->{$vmid}->{freemem} += $diff;
|
||||
$vmstatus->{$vmid}->{freemem} = 0 if $vmstatus->{$vmid}->{freemem} < 0;
|
||||
$vmstatus->{$vmid}->{balloon} = $res->{$vmid};
|
||||
$sum->{$vmid} = $res->{$vmid};
|
||||
$changes += $diff;
|
||||
$abschanges += $diff > 0 ? $diff : -$diff;
|
||||
}
|
||||
my $diff = $res->{$vmid} - $vmstatus->{$vmid}->{balloon};
|
||||
if ($diff != 0) {
|
||||
# fixme: adjust freemem ?
|
||||
$vmstatus->{$vmid}->{freemem} += $diff;
|
||||
$vmstatus->{$vmid}->{freemem} = 0 if $vmstatus->{$vmid}->{freemem} < 0;
|
||||
$vmstatus->{$vmid}->{balloon} = $res->{$vmid};
|
||||
$sum->{$vmid} = $res->{$vmid};
|
||||
$changes += $diff;
|
||||
$abschanges += $diff > 0 ? $diff : -$diff;
|
||||
}
|
||||
}
|
||||
|
||||
return ($changes, $abschanges);
|
||||
}
|
||||
|
||||
my $tcount = 0;
|
||||
|
||||
sub absim {
|
||||
my ($vmstatus, $goal, %expect) = @_;
|
||||
|
||||
$tcount++;
|
||||
|
||||
print "BALLOON SIM $tcount\n" if $debug;
|
||||
|
||||
|
||||
$vmstatus = dclone($vmstatus); # do not change original
|
||||
|
||||
my $changes = 0;
|
||||
my $abschanges = 0;
|
||||
my $sum = {};
|
||||
do {
|
||||
my $res = PVE::AutoBalloon::compute_alg1($vmstatus, $goal, MB(100), $debug);
|
||||
print Dumper($res) if $debug;
|
||||
($changes, $abschanges) = abapply($vmstatus, $res, $sum);
|
||||
$goal -= $changes;
|
||||
my $res = PVE::AutoBalloon::compute_alg1($vmstatus, $goal, MB(100), $debug);
|
||||
print Dumper($res) if $debug;
|
||||
($changes, $abschanges) = abapply($vmstatus, $res, $sum);
|
||||
$goal -= $changes;
|
||||
} while ($abschanges);
|
||||
|
||||
abcheck($sum, %expect);
|
||||
|
|
@ -143,19 +144,19 @@ sub abcheck {
|
|||
my ($res, %expect) = @_;
|
||||
|
||||
foreach my $vmid (keys %expect) {
|
||||
my $ev = $expect{$vmid};
|
||||
if (defined ($res->{$vmid})) {
|
||||
die "T$tcount: wrong value for VM $vmid ($ev != $res->{$vmid})\n"
|
||||
if $ev != $res->{$vmid};
|
||||
} else {
|
||||
die "T$tcount: missing value for VM $vmid (extected $ev)\n";
|
||||
}
|
||||
my $ev = $expect{$vmid};
|
||||
if (defined($res->{$vmid})) {
|
||||
die "T$tcount: wrong value for VM $vmid ($ev != $res->{$vmid})\n"
|
||||
if $ev != $res->{$vmid};
|
||||
} else {
|
||||
die "T$tcount: missing value for VM $vmid (extected $ev)\n";
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $vmid (keys %$res) {
|
||||
die "T$tcount: got unexpected result for $vmid\n"
|
||||
if (defined($res->{$vmid}) &&
|
||||
!defined($expect{$vmid}));
|
||||
die "T$tcount: got unexpected result for $vmid\n"
|
||||
if (defined($res->{$vmid})
|
||||
&& !defined($expect{$vmid}));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -177,9 +178,10 @@ sub abtest {
|
|||
|
||||
sub MB {
|
||||
my $mb = shift;
|
||||
return $mb*1000*1000;
|
||||
};
|
||||
return $mb * 1000 * 1000;
|
||||
}
|
||||
|
||||
sub GB {
|
||||
my $gb = shift;
|
||||
return $gb*1000*1000*1000;
|
||||
};
|
||||
return $gb * 1000 * 1000 * 1000;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,45 +23,48 @@ sub test_rpc {
|
|||
my ($host) = @_;
|
||||
|
||||
for (my $i = 0; $i < $qcount; $i++) {
|
||||
eval {
|
||||
my ($page, $response, %reply_headers)
|
||||
= get_https($host, 8006, '/api2/json',
|
||||
make_headers(Cookie => "PVEAuthCookie=$ticket"));
|
||||
die "$response\n" if $response !~ m/200 OK/;
|
||||
};
|
||||
eval {
|
||||
my ($page, $response, %reply_headers) = get_https(
|
||||
$host,
|
||||
8006,
|
||||
'/api2/json',
|
||||
make_headers(Cookie => "PVEAuthCookie=$ticket"),
|
||||
);
|
||||
die "$response\n" if $response !~ m/200 OK/;
|
||||
};
|
||||
|
||||
my $err = $@;
|
||||
my $err = $@;
|
||||
|
||||
if ($err) {
|
||||
if ($err) {
|
||||
|
||||
print "ERROR: $err\n";
|
||||
last;
|
||||
}
|
||||
print "ERROR: $err\n";
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub run_tests {
|
||||
my ($host) = @_;
|
||||
|
||||
|
||||
my $workers;
|
||||
|
||||
my $starttime = [gettimeofday];
|
||||
|
||||
for (my $i = 0; $i < $wcount; $i++) {
|
||||
if (my $pid = fork ()) {
|
||||
$workers->{$pid} = 1;
|
||||
} else {
|
||||
test_rpc ($host);
|
||||
exit (0);
|
||||
}
|
||||
if (my $pid = fork()) {
|
||||
$workers->{$pid} = 1;
|
||||
} else {
|
||||
test_rpc($host);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
# wait for children
|
||||
1 while (wait > 0);
|
||||
|
||||
my $elapsed = int(tv_interval ($starttime) * 1000);
|
||||
my $elapsed = int(tv_interval($starttime) * 1000);
|
||||
|
||||
my $tpq = $elapsed / ($wcount*$qcount);
|
||||
my $tpq = $elapsed / ($wcount * $qcount);
|
||||
|
||||
print "$host: $tpq ms per query\n";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use Test::More tests => 3;
|
|||
$ReplicationTestEnv::mocked_nodename = 'node1';
|
||||
|
||||
my $testjob = {
|
||||
'type' => 'local',
|
||||
'type' => 'local',
|
||||
'target' => 'node1',
|
||||
'guest' => 900,
|
||||
};
|
||||
|
|
@ -26,11 +26,11 @@ $ReplicationTestEnv::mocked_replication_jobs = {
|
|||
|
||||
$ReplicationTestEnv::mocked_vm_configs = {
|
||||
900 => {
|
||||
node => 'node1',
|
||||
snapshots => {},
|
||||
ide0 => 'local-lvm:vm-900-disk-1,size=4G',
|
||||
memory => 512,
|
||||
ide2 => 'none,media=cdrom',
|
||||
node => 'node1',
|
||||
snapshots => {},
|
||||
ide0 => 'local-lvm:vm-900-disk-1,size=4G',
|
||||
memory => 512,
|
||||
ide2 => 'none,media=cdrom',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -39,8 +39,8 @@ ReplicationTestEnv::setup();
|
|||
ok(PVE::INotify::nodename() eq 'node1');
|
||||
|
||||
my $list = PVE::Cluster::get_vmlist();
|
||||
is_deeply($list, { ids => {900 => { node => 'node1', type => 'qemu', version => 1}}});
|
||||
is_deeply($list, { ids => { 900 => { node => 'node1', type => 'qemu', version => 1 } } });
|
||||
my $cfg = PVE::ReplicationConfig->new();
|
||||
is_deeply($cfg, { ids => { job_900_to_node1 => $testjob }});
|
||||
is_deeply($cfg, { ids => { job_900_to_node1 => $testjob } });
|
||||
|
||||
exit(0);
|
||||
|
|
|
|||
|
|
@ -21,14 +21,15 @@ my $schedule = [];
|
|||
my $mocked_replicate = sub {
|
||||
my ($guest_class, $jobcfg, $state, $start_time, $logfunc) = @_;
|
||||
|
||||
push @$schedule, {
|
||||
id => $jobcfg->{id},
|
||||
guest => $jobcfg->{guest},
|
||||
vmtype => $jobcfg->{vmtype},
|
||||
guest_class => $guest_class,
|
||||
last_sync => $state->{last_sync},
|
||||
start => $start_time,
|
||||
};
|
||||
push @$schedule,
|
||||
{
|
||||
id => $jobcfg->{id},
|
||||
guest => $jobcfg->{guest},
|
||||
vmtype => $jobcfg->{vmtype},
|
||||
guest_class => $guest_class,
|
||||
last_sync => $state->{last_sync},
|
||||
start => $start_time,
|
||||
};
|
||||
};
|
||||
|
||||
my $pve_replication_module = Test::MockModule->new('PVE::Replication');
|
||||
|
|
@ -36,66 +37,66 @@ $pve_replication_module->mock(replicate => $mocked_replicate);
|
|||
|
||||
$ReplicationTestEnv::mocked_replication_jobs = {
|
||||
'900-1_to_node2' => {
|
||||
'type' => 'local',
|
||||
'target' => 'node2',
|
||||
'guest' => 900,
|
||||
'type' => 'local',
|
||||
'target' => 'node2',
|
||||
'guest' => 900,
|
||||
},
|
||||
'900-2_to_node1' => {
|
||||
'type' => 'local',
|
||||
'target' => 'node1', # local node, job should be skipped
|
||||
'guest' => 900,
|
||||
'type' => 'local',
|
||||
'target' => 'node1', # local node, job should be skipped
|
||||
'guest' => 900,
|
||||
},
|
||||
};
|
||||
|
||||
$ReplicationTestEnv::mocked_vm_configs = {
|
||||
900 => {
|
||||
node => 'node1',
|
||||
snapshots => {},
|
||||
ide0 => 'local-lvm:vm-900-disk-1,size=4G',
|
||||
memory => 512,
|
||||
ide2 => 'none,media=cdrom',
|
||||
node => 'node1',
|
||||
snapshots => {},
|
||||
ide0 => 'local-lvm:vm-900-disk-1,size=4G',
|
||||
memory => 512,
|
||||
ide2 => 'none,media=cdrom',
|
||||
},
|
||||
};
|
||||
|
||||
ReplicationTestEnv::setup();
|
||||
|
||||
for (my $i = 0; $i < 61; $i++) {
|
||||
PVE::API2::Replication::run_jobs($i*60);
|
||||
PVE::API2::Replication::run_jobs($i * 60);
|
||||
}
|
||||
|
||||
my $exptected_schedule = [
|
||||
{
|
||||
last_sync => 0,
|
||||
start => 900,
|
||||
vmtype => 'qemu',
|
||||
guest_class => 'PVE::QemuConfig',
|
||||
id => '900-1_to_node2',
|
||||
guest => 900
|
||||
last_sync => 0,
|
||||
start => 900,
|
||||
vmtype => 'qemu',
|
||||
guest_class => 'PVE::QemuConfig',
|
||||
id => '900-1_to_node2',
|
||||
guest => 900,
|
||||
},
|
||||
{
|
||||
last_sync => 900,
|
||||
start => 1800,
|
||||
vmtype => 'qemu',
|
||||
guest_class => 'PVE::QemuConfig',
|
||||
id => '900-1_to_node2',
|
||||
guest => 900,
|
||||
},
|
||||
{
|
||||
last_sync => 1800,
|
||||
start => 2700,
|
||||
vmtype => 'qemu',
|
||||
guest_class => 'PVE::QemuConfig',
|
||||
id => '900-1_to_node2',
|
||||
guest => 900
|
||||
last_sync => 900,
|
||||
start => 1800,
|
||||
vmtype => 'qemu',
|
||||
guest_class => 'PVE::QemuConfig',
|
||||
id => '900-1_to_node2',
|
||||
guest => 900,
|
||||
},
|
||||
{
|
||||
last_sync => 2700,
|
||||
start => 3600,
|
||||
vmtype => 'qemu',
|
||||
guest_class => 'PVE::QemuConfig',
|
||||
id => '900-1_to_node2',
|
||||
guest => 900
|
||||
}
|
||||
last_sync => 1800,
|
||||
start => 2700,
|
||||
vmtype => 'qemu',
|
||||
guest_class => 'PVE::QemuConfig',
|
||||
id => '900-1_to_node2',
|
||||
guest => 900,
|
||||
},
|
||||
{
|
||||
last_sync => 2700,
|
||||
start => 3600,
|
||||
vmtype => 'qemu',
|
||||
guest_class => 'PVE::QemuConfig',
|
||||
id => '900-1_to_node2',
|
||||
guest => 900,
|
||||
},
|
||||
];
|
||||
|
||||
is_deeply($schedule, $exptected_schedule);
|
||||
|
|
|
|||
|
|
@ -18,26 +18,26 @@ use PVE::API2::Replication;
|
|||
$ReplicationTestEnv::mocked_nodename = 'node1';
|
||||
|
||||
my $testjob = {
|
||||
'type' => 'local',
|
||||
'type' => 'local',
|
||||
'target' => 'node1',
|
||||
'guest' => 900,
|
||||
};
|
||||
|
||||
$ReplicationTestEnv::mocked_replication_jobs = {
|
||||
job_900_to_node1 => {
|
||||
'type' => 'local',
|
||||
'target' => 'node1', # local node, job should be skipped
|
||||
'guest' => 900,
|
||||
job_900_to_node1 => {
|
||||
'type' => 'local',
|
||||
'target' => 'node1', # local node, job should be skipped
|
||||
'guest' => 900,
|
||||
},
|
||||
};
|
||||
|
||||
$ReplicationTestEnv::mocked_vm_configs = {
|
||||
900 => {
|
||||
node => 'node1',
|
||||
snapshots => {},
|
||||
ide0 => 'local-lvm:vm-900-disk-1,size=4G',
|
||||
memory => 512,
|
||||
ide2 => 'none,media=cdrom',
|
||||
node => 'node1',
|
||||
snapshots => {},
|
||||
ide0 => 'local-lvm:vm-900-disk-1,size=4G',
|
||||
memory => 512,
|
||||
ide2 => 'none,media=cdrom',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -16,35 +16,34 @@ use PVE::Tools;
|
|||
$ReplicationTestEnv::mocked_nodename = 'node1';
|
||||
|
||||
my $pve_replication_module = Test::MockModule->new('PVE::Replication');
|
||||
$pve_replication_module->mock(
|
||||
replicate => sub { die "faked replication error\n"; });
|
||||
$pve_replication_module->mock(replicate => sub { die "faked replication error\n"; });
|
||||
|
||||
my $testjob = {
|
||||
'type' => 'local',
|
||||
'type' => 'local',
|
||||
'target' => 'node1',
|
||||
'guest' => 900,
|
||||
};
|
||||
|
||||
$ReplicationTestEnv::mocked_replication_jobs = {
|
||||
job_900_to_node2 => {
|
||||
'type' => 'local',
|
||||
'target' => 'node2',
|
||||
'guest' => 900,
|
||||
'type' => 'local',
|
||||
'target' => 'node2',
|
||||
'guest' => 900,
|
||||
},
|
||||
job_900_to_node1 => {
|
||||
'type' => 'local',
|
||||
'target' => 'node1', # local node, job should be skipped
|
||||
'guest' => 900,
|
||||
'type' => 'local',
|
||||
'target' => 'node1', # local node, job should be skipped
|
||||
'guest' => 900,
|
||||
},
|
||||
};
|
||||
|
||||
$ReplicationTestEnv::mocked_vm_configs = {
|
||||
900 => {
|
||||
node => 'node1',
|
||||
snapshots => {},
|
||||
ide0 => 'local-lvm:vm-900-disk-1,size=4G',
|
||||
memory => 512,
|
||||
ide2 => 'none,media=cdrom',
|
||||
node => 'node1',
|
||||
snapshots => {},
|
||||
ide0 => 'local-lvm:vm-900-disk-1,size=4G',
|
||||
memory => 512,
|
||||
ide2 => 'none,media=cdrom',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,8 @@ use PVE::Storage;
|
|||
my $replicated_volume_status = {};
|
||||
|
||||
my $mocked_remote_prepare_local_job = sub {
|
||||
my ($ssh_info, $jobid, $vmid, $volumes, $storeid_list, $last_sync, $parent_snapname, $force) = @_;
|
||||
my ($ssh_info, $jobid, $vmid, $volumes, $storeid_list, $last_sync, $parent_snapname, $force)
|
||||
= @_;
|
||||
|
||||
my $target = $ssh_info->{node};
|
||||
|
||||
|
|
@ -38,16 +39,17 @@ my $mocked_remote_prepare_local_job = sub {
|
|||
|
||||
return $last_snapshots if !defined($replicated_volume_status->{$target});
|
||||
|
||||
my $last_sync_snapname = PVE::ReplicationState::replication_snapshot_name($jobid, $last_sync);
|
||||
my $last_sync_snapname =
|
||||
PVE::ReplicationState::replication_snapshot_name($jobid, $last_sync);
|
||||
|
||||
foreach my $volid (keys %{$replicated_volume_status->{$target}}) {
|
||||
if (!grep { $_ eq $volid } @$volumes) {
|
||||
delete $replicated_volume_status->{$target}->{$volid};
|
||||
next;
|
||||
}
|
||||
my $snapname = $replicated_volume_status->{$target}->{$volid};
|
||||
foreach my $volid (keys %{ $replicated_volume_status->{$target} }) {
|
||||
if (!grep { $_ eq $volid } @$volumes) {
|
||||
delete $replicated_volume_status->{$target}->{$volid};
|
||||
next;
|
||||
}
|
||||
my $snapname = $replicated_volume_status->{$target}->{$volid};
|
||||
|
||||
$last_snapshots->{$volid}->{$snapname} = 1 if $last_sync_snapname eq $snapname;
|
||||
$last_snapshots->{$volid}->{$snapname} = 1 if $last_sync_snapname eq $snapname;
|
||||
}
|
||||
|
||||
return $last_snapshots;
|
||||
|
|
@ -80,29 +82,30 @@ my $pve_replication_module = Test::MockModule->new('PVE::Replication');
|
|||
$pve_replication_module->mock(
|
||||
remote_prepare_local_job => $mocked_remote_prepare_local_job,
|
||||
remote_finalize_local_job => $mocked_remote_finalize_local_job,
|
||||
replicate_volume => $mocked_replicate_volume);
|
||||
replicate_volume => $mocked_replicate_volume,
|
||||
);
|
||||
|
||||
my $testjob = {
|
||||
'type' => 'local',
|
||||
'type' => 'local',
|
||||
'target' => 'node1',
|
||||
'guest' => 900,
|
||||
};
|
||||
|
||||
$ReplicationTestEnv::mocked_replication_jobs = {
|
||||
job_900_to_node2 => {
|
||||
'type' => 'local',
|
||||
'target' => 'node2',
|
||||
'guest' => 900,
|
||||
'type' => 'local',
|
||||
'target' => 'node2',
|
||||
'guest' => 900,
|
||||
},
|
||||
};
|
||||
|
||||
$ReplicationTestEnv::mocked_vm_configs = {
|
||||
900 => {
|
||||
node => 'node1',
|
||||
snapshots => {},
|
||||
ide0 => 'local-zfs:vm-900-disk-1,size=4G',
|
||||
memory => 512,
|
||||
ide2 => 'none,media=cdrom',
|
||||
node => 'node1',
|
||||
snapshots => {},
|
||||
ide0 => 'local-zfs:vm-900-disk-1,size=4G',
|
||||
memory => 512,
|
||||
ide2 => 'none,media=cdrom',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -122,7 +125,7 @@ for (my $i = 0; $i < 15; $i++) {
|
|||
}
|
||||
|
||||
# add a new, disk (but disk does not exist, so replication fails)
|
||||
$ReplicationTestEnv::mocked_vm_configs->{900}->{ide1} = 'local-zfs:vm-900-disk-2,size=4G';
|
||||
$ReplicationTestEnv::mocked_vm_configs->{900}->{ide1} = 'local-zfs:vm-900-disk-2,size=4G';
|
||||
for (my $i = 0; $i < 15; $i++) {
|
||||
ReplicationTestEnv::track_jobs($ctime);
|
||||
$ctime += 60;
|
||||
|
|
@ -142,8 +145,6 @@ for (my $i = 0; $i < 15; $i++) {
|
|||
$ctime += 60;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ReplicationTestEnv::commit_log();
|
||||
|
||||
exit(0);
|
||||
|
|
|
|||
|
|
@ -20,31 +20,30 @@ my $mocked_delete_job = sub {
|
|||
};
|
||||
|
||||
my $pve_replication_config_module = Test::MockModule->new('PVE::ReplicationConfig');
|
||||
$pve_replication_config_module->mock(
|
||||
delete_job => $mocked_delete_job);
|
||||
$pve_replication_config_module->mock(delete_job => $mocked_delete_job);
|
||||
|
||||
my $testjob = {
|
||||
'type' => 'local',
|
||||
'type' => 'local',
|
||||
'target' => 'node1',
|
||||
'guest' => 900,
|
||||
};
|
||||
|
||||
$ReplicationTestEnv::mocked_replication_jobs = {
|
||||
job_900_to_node1 => {
|
||||
remove_job => 'full',
|
||||
type => 'local',
|
||||
target => 'node1', # local node, job should be skipped
|
||||
guest => 900,
|
||||
remove_job => 'full',
|
||||
type => 'local',
|
||||
target => 'node1', # local node, job should be skipped
|
||||
guest => 900,
|
||||
},
|
||||
};
|
||||
|
||||
$ReplicationTestEnv::mocked_vm_configs = {
|
||||
900 => {
|
||||
node => 'node1',
|
||||
snapshots => {},
|
||||
ide0 => 'local-zfs:vm-900-disk-1,size=4G',
|
||||
memory => 512,
|
||||
ide2 => 'none,media=cdrom',
|
||||
node => 'node1',
|
||||
snapshots => {},
|
||||
ide0 => 'local-zfs:vm-900-disk-1,size=4G',
|
||||
memory => 512,
|
||||
ide2 => 'none,media=cdrom',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -14,39 +14,39 @@ use Data::Dumper;
|
|||
|
||||
my $vmlist = {
|
||||
'ids' => {
|
||||
100 => {
|
||||
'type' => 'qemu',
|
||||
'node' => 'node1',
|
||||
},
|
||||
101 => {
|
||||
'type' => 'qemu',
|
||||
'node' => 'node1',
|
||||
},
|
||||
112 => {
|
||||
'type' => 'lxc',
|
||||
'node' => 'node1',
|
||||
},
|
||||
113 => {
|
||||
'type' => 'lxc',
|
||||
'node' => 'node1',
|
||||
},
|
||||
200 => {
|
||||
'type' => 'qemu',
|
||||
'node' => 'node2',
|
||||
},
|
||||
201 => {
|
||||
'type' => 'qemu',
|
||||
'node' => 'node2',
|
||||
},
|
||||
212 => {
|
||||
'type' => 'lxc',
|
||||
'node' => 'node2',
|
||||
},
|
||||
213 => {
|
||||
'type' => 'lxc',
|
||||
'node' => 'node2',
|
||||
},
|
||||
}
|
||||
100 => {
|
||||
'type' => 'qemu',
|
||||
'node' => 'node1',
|
||||
},
|
||||
101 => {
|
||||
'type' => 'qemu',
|
||||
'node' => 'node1',
|
||||
},
|
||||
112 => {
|
||||
'type' => 'lxc',
|
||||
'node' => 'node1',
|
||||
},
|
||||
113 => {
|
||||
'type' => 'lxc',
|
||||
'node' => 'node1',
|
||||
},
|
||||
200 => {
|
||||
'type' => 'qemu',
|
||||
'node' => 'node2',
|
||||
},
|
||||
201 => {
|
||||
'type' => 'qemu',
|
||||
'node' => 'node2',
|
||||
},
|
||||
212 => {
|
||||
'type' => 'lxc',
|
||||
'node' => 'node2',
|
||||
},
|
||||
213 => {
|
||||
'type' => 'lxc',
|
||||
'node' => 'node2',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
my $pools = {
|
||||
|
|
@ -56,135 +56,164 @@ my $pools = {
|
|||
my $pve_cluster_module = Test::MockModule->new('PVE::Cluster');
|
||||
$pve_cluster_module->mock(
|
||||
get_vmlist => sub {
|
||||
return $vmlist;
|
||||
}
|
||||
return $vmlist;
|
||||
},
|
||||
);
|
||||
|
||||
my $pve_inotify = Test::MockModule->new('PVE::INotify');
|
||||
$pve_inotify->mock(
|
||||
nodename => sub {
|
||||
return 'node1';
|
||||
}
|
||||
return 'node1';
|
||||
},
|
||||
);
|
||||
|
||||
my $pve_api2tools = Test::MockModule->new('PVE::API2Tools');
|
||||
$pve_api2tools->mock(
|
||||
get_resource_pool_guest_members => sub {
|
||||
return $pools->{testpool};
|
||||
}
|
||||
return $pools->{testpool};
|
||||
},
|
||||
);
|
||||
|
||||
my $tests = [];
|
||||
my $addtest = sub {
|
||||
my ($name, $test) = @_;
|
||||
push @$tests, {
|
||||
name => $name,
|
||||
test => $test,
|
||||
};
|
||||
push @$tests,
|
||||
{
|
||||
name => $name,
|
||||
test => $test,
|
||||
};
|
||||
};
|
||||
|
||||
$addtest->('Test all guests', {
|
||||
expected => {
|
||||
node1 => [ 100, 101, 112, 113 ],
|
||||
node2 => [ 200, 201, 212, 213 ],
|
||||
$addtest->(
|
||||
'Test all guests',
|
||||
{
|
||||
expected => {
|
||||
node1 => [100, 101, 112, 113],
|
||||
node2 => [200, 201, 212, 213],
|
||||
},
|
||||
param => {
|
||||
all => 1,
|
||||
},
|
||||
},
|
||||
param => {
|
||||
all => 1,
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
$addtest->('Test all guests with node limit', {
|
||||
expected => {
|
||||
node2 => [ 200, 201, 212, 213 ],
|
||||
$addtest->(
|
||||
'Test all guests with node limit',
|
||||
{
|
||||
expected => {
|
||||
node2 => [200, 201, 212, 213],
|
||||
},
|
||||
param => {
|
||||
all => 1,
|
||||
node => 'node2',
|
||||
},
|
||||
},
|
||||
param => {
|
||||
all => 1,
|
||||
node => 'node2',
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
$addtest->('Test exclude', {
|
||||
expected => {
|
||||
node1 =>[ 101, 112, 113 ],
|
||||
node2 => [ 201, 212, 213 ],
|
||||
$addtest->(
|
||||
'Test exclude',
|
||||
{
|
||||
expected => {
|
||||
node1 => [101, 112, 113],
|
||||
node2 => [201, 212, 213],
|
||||
},
|
||||
param => {
|
||||
all => 1,
|
||||
exclude => '100, 102, 200, 202',
|
||||
},
|
||||
},
|
||||
param => {
|
||||
all => 1,
|
||||
exclude => '100, 102, 200, 202',
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
$addtest->('Test exclude with node limit', {
|
||||
expected => {
|
||||
node1 =>[ 101, 112, 113 ],
|
||||
$addtest->(
|
||||
'Test exclude with node limit',
|
||||
{
|
||||
expected => {
|
||||
node1 => [101, 112, 113],
|
||||
},
|
||||
param => {
|
||||
all => 1,
|
||||
exclude => '100, 102, 200, 202',
|
||||
node => 'node1',
|
||||
},
|
||||
},
|
||||
param => {
|
||||
all => 1,
|
||||
exclude => '100, 102, 200, 202',
|
||||
node => 'node1',
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
$addtest->('Test pool members', {
|
||||
expected => {
|
||||
node1 =>[ 100, 101 ],
|
||||
node2 => [ 200, 201 ],
|
||||
$addtest->(
|
||||
'Test pool members',
|
||||
{
|
||||
expected => {
|
||||
node1 => [100, 101],
|
||||
node2 => [200, 201],
|
||||
},
|
||||
param => {
|
||||
pool => 'testpool',
|
||||
},
|
||||
},
|
||||
param => {
|
||||
pool => 'testpool',
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
$addtest->('Test pool members with node limit', {
|
||||
expected => {
|
||||
node2 => [ 200, 201 ],
|
||||
$addtest->(
|
||||
'Test pool members with node limit',
|
||||
{
|
||||
expected => {
|
||||
node2 => [200, 201],
|
||||
},
|
||||
param => {
|
||||
pool => 'testpool',
|
||||
node => 'node2',
|
||||
},
|
||||
},
|
||||
param => {
|
||||
pool => 'testpool',
|
||||
node => 'node2',
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
$addtest->('Test selected VMIDs', {
|
||||
expected => {
|
||||
node1 =>[ 100 ],
|
||||
node2 => [ 201, 212 ],
|
||||
$addtest->(
|
||||
'Test selected VMIDs',
|
||||
{
|
||||
expected => {
|
||||
node1 => [100],
|
||||
node2 => [201, 212],
|
||||
},
|
||||
param => {
|
||||
vmid => '100, 201, 212',
|
||||
},
|
||||
},
|
||||
param => {
|
||||
vmid => '100, 201, 212',
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
$addtest->('Test selected VMIDs with node limit', {
|
||||
expected => {
|
||||
node1 =>[ 100 ],
|
||||
$addtest->(
|
||||
'Test selected VMIDs with node limit',
|
||||
{
|
||||
expected => {
|
||||
node1 => [100],
|
||||
},
|
||||
param => {
|
||||
vmid => '100, 201, 212',
|
||||
node => 'node1',
|
||||
},
|
||||
},
|
||||
param => {
|
||||
vmid => '100, 201, 212',
|
||||
node => 'node1',
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
$addtest->('Test selected VMIDs on other nodes', {
|
||||
expected => {
|
||||
$addtest->(
|
||||
'Test selected VMIDs on other nodes',
|
||||
{
|
||||
expected => {},
|
||||
param => {
|
||||
vmid => '201, 212',
|
||||
node => 'node1',
|
||||
},
|
||||
},
|
||||
param => {
|
||||
vmid => '201, 212',
|
||||
node => 'node1',
|
||||
}
|
||||
});
|
||||
|
||||
$addtest->('Test VMID not present in vmlist', {
|
||||
expected => {
|
||||
node1 => [ 100 ],
|
||||
node2 => [ 201, 212 ],
|
||||
'' => [ 7654 ],
|
||||
},
|
||||
param => {
|
||||
vmid => '100, 201, 212, 7654',
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
$addtest->(
|
||||
'Test VMID not present in vmlist',
|
||||
{
|
||||
expected => {
|
||||
node1 => [100],
|
||||
node2 => [201, 212],
|
||||
'' => [7654],
|
||||
},
|
||||
param => {
|
||||
vmid => '100, 201, 212, 7654',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
for my $test (@{$tests}) {
|
||||
my $testname = $test->{name};
|
||||
|
|
@ -192,7 +221,7 @@ for my $test (@{$tests}) {
|
|||
|
||||
# note($testname);
|
||||
|
||||
my $result = PVE::VZDump::get_included_guests($testdata->{param});
|
||||
my $result = PVE::VZDump::get_included_guests($testdata->{param});
|
||||
|
||||
is_deeply($result, $testdata->{expected}, $testname);
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -4,17 +4,18 @@
|
|||
# This can also be added as a line in /etc/vzdump.conf
|
||||
# e.g. 'script: /usr/local/bin/vzdump-hook-script.pl'
|
||||
|
||||
|
||||
use strict;
|
||||
|
||||
print "HOOK: " . join (' ', @ARGV) . "\n";
|
||||
print "HOOK: " . join(' ', @ARGV) . "\n";
|
||||
|
||||
my $phase = shift;
|
||||
|
||||
if ($phase eq 'job-init' ||
|
||||
$phase eq 'job-start' ||
|
||||
$phase eq 'job-end' ||
|
||||
$phase eq 'job-abort') {
|
||||
if (
|
||||
$phase eq 'job-init'
|
||||
|| $phase eq 'job-start'
|
||||
|| $phase eq 'job-end'
|
||||
|| $phase eq 'job-abort'
|
||||
) {
|
||||
|
||||
# undef for Proxmox Backup Server storages
|
||||
# undef in phase 'job-init' except when --dumpdir is used directly
|
||||
|
|
@ -30,21 +31,23 @@ if ($phase eq 'job-init' ||
|
|||
|
||||
# example: wake up remote storage node and enable storage
|
||||
if ($phase eq 'job-init') {
|
||||
#system("wakeonlan AA:BB:CC:DD:EE:FF");
|
||||
#sleep(30);
|
||||
#system ("/sbin/pvesm set $storeid --disable 0") == 0 ||
|
||||
# die "enabling storage $storeid failed";
|
||||
#system("wakeonlan AA:BB:CC:DD:EE:FF");
|
||||
#sleep(30);
|
||||
#system ("/sbin/pvesm set $storeid --disable 0") == 0 ||
|
||||
# die "enabling storage $storeid failed";
|
||||
}
|
||||
|
||||
# do what you want
|
||||
|
||||
} elsif ($phase eq 'backup-start' ||
|
||||
$phase eq 'backup-end' ||
|
||||
$phase eq 'backup-abort' ||
|
||||
$phase eq 'log-end' ||
|
||||
$phase eq 'pre-stop' ||
|
||||
$phase eq 'pre-restart' ||
|
||||
$phase eq 'post-restart') {
|
||||
} elsif (
|
||||
$phase eq 'backup-start'
|
||||
|| $phase eq 'backup-end'
|
||||
|| $phase eq 'backup-abort'
|
||||
|| $phase eq 'log-end'
|
||||
|| $phase eq 'pre-stop'
|
||||
|| $phase eq 'pre-restart'
|
||||
|| $phase eq 'post-restart'
|
||||
) {
|
||||
|
||||
my $mode = shift; # stop/suspend/snapshot
|
||||
|
||||
|
|
@ -69,7 +72,7 @@ if ($phase eq 'job-init' ||
|
|||
|
||||
print "HOOK-ENV: ";
|
||||
for my $var (qw(vmtype dumpdir storeid hostname target logfile)) {
|
||||
print "$var=$ENV{uc($var)};" if defined($ENV{uc($var)});
|
||||
print "$var=$ENV{uc($var)};" if defined($ENV{ uc($var) });
|
||||
}
|
||||
print "\n";
|
||||
|
||||
|
|
@ -91,4 +94,4 @@ if ($phase eq 'job-init' ||
|
|||
|
||||
}
|
||||
|
||||
exit (0);
|
||||
exit(0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue