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:
Thomas Lamprecht 2025-06-17 18:22:29 +02:00
parent a750c44c42
commit 138cae897a
88 changed files with 19180 additions and 17920 deletions

View file

@ -18,32 +18,32 @@ 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',
@ -61,12 +61,12 @@ __PACKAGE__->register_method ({
subdir => { type => 'string' },
},
},
links => [ { rel => 'child', href => "{subdir}" } ],
links => [{ rel => 'child', href => "{subdir}" }],
},
code => sub {
my ($param) = @_;
my $res = [ { subdir => 'version' } ];
my $res = [{ subdir => 'version' }];
my $ma = PVE::API2->method_attributes();
@ -79,9 +79,10 @@ __PACKAGE__->register_method ({
}
return $res;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'version',
path => 'version',
method => 'GET',
@ -132,5 +133,6 @@ __PACKAGE__->register_method ({
$res->{$_} = $version_info->{$_} for qw(version release repoid);
return $res;
}});
},
});
1;

View file

@ -17,7 +17,7 @@ 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',
@ -35,7 +35,7 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
@ -43,7 +43,8 @@ __PACKAGE__->register_method ({
return [
{ name => 'certificate' },
];
}});
},
});
my $order_certificate = sub {
my ($acme, $acme_node_config) = @_;
@ -51,9 +52,9 @@ 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}}) {
for my $auth_url (@{ $order->{authorizations} }) {
print "\nGetting authorization details from '$auth_url'\n";
my $auth = $acme->get_authorization($auth_url);
@ -123,9 +124,7 @@ my $order_certificate = sub {
# 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));
};
eval { $acme->finalize_order($order, PVE::Certificate::pem_to_der($csr)); };
if (my $err = $@) {
die $err if $finalize_error_cnt >= 5;
@ -156,12 +155,12 @@ 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,
@ -187,13 +186,13 @@ __PACKAGE__->register_method ({
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."})
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}};
if !$acme_node_config || !%{ $acme_node_config->{domains} };
my $rpcenv = PVE::RPCEnvironment::get();
@ -204,7 +203,7 @@ __PACKAGE__->register_method ({
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;
if !-e $account_file;
my $acme = PVE::ACME->new($account_file);
@ -225,14 +224,15 @@ __PACKAGE__->register_method ({
};
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,
@ -259,16 +259,21 @@ __PACKAGE__->register_method ({
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";
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}};
if !$acme_node_config || !%{ $acme_node_config->{domains} };
my $rpcenv = PVE::RPCEnvironment::get();
@ -281,7 +286,7 @@ __PACKAGE__->register_method ({
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;
if !-e $account_file;
my $acme = PVE::ACME->new($account_file);
@ -306,14 +311,15 @@ __PACKAGE__->register_method ({
};
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,
@ -336,7 +342,7 @@ __PACKAGE__->register_method ({
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}};
if !$acme_node_config || !%{ $acme_node_config->{domains} };
my $rpcenv = PVE::RPCEnvironment::get();
@ -349,7 +355,7 @@ __PACKAGE__->register_method ({
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;
if !-e $account_file;
my $acme = PVE::ACME->new($account_file);
@ -377,6 +383,7 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker("acmerevoke", undef, $authuser, $realcmd);
}});
},
});
1;

View file

@ -15,7 +15,7 @@ use PVE::API2::ACMEPlugin;
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::ACMEPlugin",
path => 'plugins',
});
@ -33,11 +33,11 @@ my $acme_directories = [
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',
@ -45,8 +45,7 @@ __PACKAGE__->register_method ({
description => "ACMEAccount index.",
parameters => {
additionalProperties => 0,
properties => {
},
properties => {},
},
returns => {
type => 'array',
@ -54,7 +53,7 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
@ -67,9 +66,10 @@ __PACKAGE__->register_method ({
{ name => 'plugins' },
{ name => 'challenge-schema' },
];
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'account_index',
path => 'account',
method => 'GET',
@ -78,8 +78,7 @@ __PACKAGE__->register_method ({
protected => 1,
parameters => {
additionalProperties => 0,
properties => {
},
properties => {},
},
returns => {
type => 'array',
@ -87,16 +86,17 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
my $accounts = PVE::CertHelpers::list_acme_accounts();
return [ map { { name => $_ } } @$accounts ];
}});
return [map { { name => $_ } } @$accounts];
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'register_account',
path => 'account',
method => 'POST',
@ -112,10 +112,13 @@ __PACKAGE__->register_method ({
type => 'string',
optional => 1,
},
directory => get_standard_option('pve-acme-directory-url', {
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',
@ -141,19 +144,23 @@ __PACKAGE__->register_method ({
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;
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');
raise_param_exc({'name' => "ACME account config file '${account_name}' already exists."})
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 $realcmd = sub {
PVE::Cluster::cfs_lock_acme($account_name, 10, sub {
PVE::Cluster::cfs_lock_acme(
$account_name,
10,
sub {
die "ACME account config file '${account_name}' already exists.\n"
if -e $account_file;
@ -166,7 +173,7 @@ __PACKAGE__->register_method ({
if (defined($eab_kid)) {
$info{eab} = {
kid => $eab_kid,
hmac_key => $eab_hmac_key
hmac_key => $eab_hmac_key,
};
}
@ -177,12 +184,14 @@ __PACKAGE__->register_method ({
die "Registration failed: $err\n";
}
print "Registration successful, account URL: '$acme->{location}'\n";
});
},
);
die $@ if $@;
};
return $rpcenv->fork_worker('acmeregister', undef, $authuser, $realcmd);
}});
},
});
my $update_account = sub {
my ($param, $msg, %info) = @_;
@ -190,50 +199,56 @@ 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 {
PVE::Cluster::cfs_lock_acme(
$account_name,
10,
sub {
die "ACME account config file '${account_name}' does not exist.\n"
if ! -e $account_file;
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) {
for my $i (0 .. 100) {
my $candidate = "${acme_account_dir}/_deactivated_${account_name}_${i}";
if (! -e $candidate) {
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";
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";
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,
@ -256,9 +271,10 @@ __PACKAGE__->register_method ({
} else {
return $update_account->($param, 'refresh');
}
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'get_account',
path => 'account/{name}',
method => 'GET',
@ -298,8 +314,9 @@ __PACKAGE__->register_method ({
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();
@ -311,9 +328,10 @@ __PACKAGE__->register_method ({
$res->{tos} = $acme->{tos};
return $res;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'deactivate_account',
path => 'account/{name}',
method => 'DELETE',
@ -332,22 +350,27 @@ __PACKAGE__->register_method ({
my ($param) = @_;
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', {
directory => get_standard_option(
'pve-acme-directory-url',
{
default => $acme_default_directory_url,
optional => 1,
}),
},
),
},
},
returns => {
@ -364,23 +387,27 @@ __PACKAGE__->register_method ({
my $meta = $acme->get_meta();
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', {
directory => get_standard_option(
'pve-acme-directory-url',
{
default => $acme_default_directory_url,
optional => 1,
}),
},
),
},
},
returns => {
@ -421,9 +448,10 @@ __PACKAGE__->register_method ({
my $meta = $acme->get_meta();
return $meta;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'get_directories',
path => 'directories',
method => 'GET',
@ -450,9 +478,10 @@ __PACKAGE__->register_method ({
my ($param) = @_;
return $acme_directories;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'challengeschema',
path => 'challenge-schema',
method => 'GET',
@ -499,7 +528,8 @@ __PACKAGE__->register_method ({
my $plugins = $plugin->get_supported_plugins();
for my $id (sort keys %$plugins) {
my $schema = $plugins->{$id};
push @$res, {
push @$res,
{
id => $id,
name => $schema->{name} // $id,
type => $plugin_type,
@ -509,6 +539,7 @@ __PACKAGE__->register_method ({
}
return $res;
}});
},
});
1;

View file

@ -17,7 +17,8 @@ use base qw(PVE::RESTHandler);
my $plugin_config_file = "priv/acme/plugins.cfg";
cfs_register_file($plugin_config_file,
cfs_register_file(
$plugin_config_file,
sub { PVE::ACME::Challenge->parse_config(@_); },
sub { PVE::ACME::Challenge->write_config(@_); },
);
@ -26,11 +27,14 @@ PVE::ACME::DNSChallenge->register();
PVE::ACME::StandAlone->register();
PVE::ACME::Challenge->init();
PVE::JSONSchema::register_standard_option('pve-acme-pluginid', {
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,12 +50,12 @@ 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,
@ -74,7 +78,7 @@ __PACKAGE__->register_method ({
plugin => get_standard_option('pve-acme-pluginid'),
},
},
links => [ { rel => 'child', href => "{plugin}" } ],
links => [{ rel => 'child', href => "{plugin}" }],
},
code => sub {
my ($param) = @_;
@ -82,14 +86,14 @@ __PACKAGE__->register_method ({
my $cfg = load_config();
my $res = [];
foreach my $pluginid (keys %{$cfg->{ids}}) {
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;
}
},
});
__PACKAGE__->register_method({
@ -98,7 +102,7 @@ __PACKAGE__->register_method({
method => 'GET',
description => "Get ACME plugin configuration.",
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
protected => 1,
parameters => {
@ -115,7 +119,7 @@ __PACKAGE__->register_method({
my $cfg = load_config();
return $modify_cfg_for_api->($cfg, $param->{id});
}
},
});
__PACKAGE__->register_method({
@ -124,12 +128,12 @@ __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) = @_;
@ -137,7 +141,10 @@ __PACKAGE__->register_method({
my $id = extract_param($param, 'id');
my $type = extract_param($param, 'type');
cfs_lock_file($plugin_config_file, undef, sub {
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});
@ -148,11 +155,12 @@ __PACKAGE__->register_method({
$cfg->{ids}->{$id}->{type} = $type;
cfs_write_file($plugin_config_file, $cfg);
});
},
);
die "$@" if $@;
return undef;
}
},
});
__PACKAGE__->register_method({
@ -161,12 +169,12 @@ __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) = @_;
@ -175,7 +183,10 @@ __PACKAGE__->register_method({
my $delete = extract_param($param, 'delete');
my $digest = extract_param($param, 'digest');
cfs_lock_file($plugin_config_file, undef, sub {
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};
@ -201,11 +212,12 @@ __PACKAGE__->register_method({
}
cfs_write_file($plugin_config_file, $cfg);
});
},
);
die "$@" if $@;
return undef;
}
},
});
__PACKAGE__->register_method({
@ -214,7 +226,7 @@ __PACKAGE__->register_method({
method => 'DELETE',
description => "Delete ACME plugin configuration.",
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
protected => 1,
parameters => {
@ -224,24 +236,28 @@ __PACKAGE__->register_method({
},
},
returns => {
type => "null"
type => "null",
},
code => sub {
my ($param) = @_;
my $id = extract_param($param, 'id');
cfs_lock_file($plugin_config_file, undef, sub {
cfs_lock_file(
$plugin_config_file,
undef,
sub {
my $cfg = load_config();
delete $cfg->{ids}->{$id};
cfs_write_file($plugin_config_file, $cfg);
});
},
);
die "$@" if $@;
return undef;
}
},
});
sub load_config {

View file

@ -62,7 +62,7 @@ __PACKAGE__->register_method({
id => { type => 'string' },
},
},
links => [ { rel => 'child', href => "{id}" } ],
links => [{ rel => 'child', href => "{id}" }],
},
code => sub {
my ($param) = @_;
@ -75,12 +75,13 @@ __PACKAGE__->register_method({
];
return $res;
}});
},
});
my $get_pkgfile = sub {
my ($veriter) = @_;
foreach my $verfile (@{$veriter->{FileList}}) {
foreach my $verfile (@{ $veriter->{FileList} }) {
my $pkgfile = $verfile->{File};
next if !$pkgfile->{Origin};
return $pkgfile;
@ -121,7 +122,9 @@ my $assemble_pkginfo = sub {
# we try to cache results
my $pve_pkgstatus_fn = "/var/lib/pve-manager/pkgupdates";
my $read_cached_pkgstatus = sub {
my $data = eval { decode_json(PVE::Tools::file_get_contents($pve_pkgstatus_fn, 5*1024*1024)) } // [];
my $data =
eval { decode_json(PVE::Tools::file_get_contents($pve_pkgstatus_fn, 5 * 1024 * 1024)) }
// [];
warn "error reading cached package status in '$pve_pkgstatus_fn' - $@\n" if $@;
return $data;
};
@ -186,7 +189,7 @@ my $update_pve_pkgstatus = sub {
# keep notification status (avoid sending mails abou new packages more than once)
foreach my $pi (@$pkglist) {
if (my $ns = $notify_status->{$pi->{Package}}) {
if (my $ns = $notify_status->{ $pi->{Package} }) {
$pi->{NotifyStatus} = $ns if $ns eq $pi->{Version};
}
}
@ -202,7 +205,7 @@ __PACKAGE__->register_method({
method => 'GET',
description => "List available updates.",
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
},
protected => 1,
proxyto => 'node',
@ -236,15 +239,17 @@ __PACKAGE__->register_method({
my $pkglist = &$update_pve_pkgstatus();
return $pkglist;
}});
},
});
__PACKAGE__->register_method({
name => 'update_database',
path => 'update',
method => 'POST',
description => "This is used to resynchronize the package index files from their sources (apt-get update).",
description =>
"This is used to resynchronize the package index files from their sources (apt-get update).",
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
},
protected => 1,
proxyto => 'node',
@ -260,7 +265,8 @@ __PACKAGE__->register_method({
},
quiet => {
type => 'boolean',
description => "Only produces output suitable for logging, omitting progress indicators.",
description =>
"Only produces output suitable for logging, omitting progress indicators.",
optional => 1,
default => 0,
},
@ -294,7 +300,7 @@ __PACKAGE__->register_method({
print "starting apt-get update\n" if !$param->{quiet};
if ($param->{quiet}) {
PVE::Tools::run_command($cmd, outfunc => sub {}, errfunc => sub {});
PVE::Tools::run_command($cmd, outfunc => sub { }, errfunc => sub { });
} else {
PVE::Tools::run_command($cmd);
}
@ -316,21 +322,22 @@ __PACKAGE__->register_method({
{
label => "Available Version",
id => "available-version",
}
]
},
data => []
],
},
data => [],
};
my $count = 0;
foreach my $p (sort {$a->{Package} cmp $b->{Package} } @$pkglist) {
foreach my $p (sort { $a->{Package} cmp $b->{Package} } @$pkglist) {
next if $p->{NotifyStatus} && $p->{NotifyStatus} eq $p->{Version};
$count++;
push @{$updates_table->{data}}, {
push @{ $updates_table->{data} },
{
"package-name" => $p->{Package},
"installed-version" => $p->{OldVersion},
"available-version" => $p->{Version}
"available-version" => $p->{Version},
};
}
@ -348,9 +355,7 @@ __PACKAGE__->register_method({
};
PVE::Notify::info(
"package-updates",
$template_data,
$metadata_fields,
"package-updates", $template_data, $metadata_fields,
);
foreach my $pi (@$pkglist) {
@ -364,9 +369,8 @@ __PACKAGE__->register_method({
return $rpcenv->fork_worker('aptupdate', undef, $authuser, $realcmd);
}});
},
});
__PACKAGE__->register_method({
name => 'changelog',
@ -374,7 +378,7 @@ __PACKAGE__->register_method({
method => 'GET',
description => "Get package changelogs.",
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
},
proxyto => 'node',
parameters => {
@ -422,7 +426,8 @@ __PACKAGE__->register_method({
$output .= "RC: $rc" if $rc != 0;
return $output;
}});
},
});
__PACKAGE__->register_method({
name => 'repositories',
@ -431,7 +436,7 @@ __PACKAGE__->register_method({
proxyto => 'node',
description => "Get APT repository information.",
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
check => ['perm', '/nodes/{node}', ['Sys.Audit']],
},
parameters => {
additionalProperties => 0,
@ -455,7 +460,7 @@ __PACKAGE__->register_method({
},
'file-type' => {
type => "string",
enum => [ 'list', 'sources' ],
enum => ['list', 'sources'],
description => "Format of the file.",
},
repositories => {
@ -469,7 +474,7 @@ __PACKAGE__->register_method({
description => "List of package types.",
items => {
type => "string",
enum => [ 'deb', 'deb-src' ],
enum => ['deb', 'deb-src'],
},
},
URIs => {
@ -520,12 +525,13 @@ __PACKAGE__->register_method({
},
FileType => {
type => "string",
enum => [ 'list', 'sources' ],
enum => ['list', 'sources'],
description => "Format of the defining file.",
},
Enabled => {
type => "boolean",
description => "Whether the repository is enabled or not",
description =>
"Whether the repository is enabled or not",
},
},
},
@ -573,7 +579,8 @@ __PACKAGE__->register_method({
},
index => {
type => "string",
description => "Index of the associated repository within the file.",
description =>
"Index of the associated repository within the file.",
},
property => {
type => "string",
@ -587,7 +594,7 @@ __PACKAGE__->register_method({
message => {
type => "string",
description => "Information message.",
}
},
},
},
},
@ -608,8 +615,8 @@ __PACKAGE__->register_method({
status => {
type => "boolean",
optional => 1,
description => "Indicating enabled/disabled status, if the " .
"repository is configured.",
description => "Indicating enabled/disabled status, if the "
. "repository is configured.",
},
},
},
@ -620,7 +627,8 @@ __PACKAGE__->register_method({
my ($param) = @_;
return Proxmox::RS::APT::Repositories::repositories("pve");
}});
},
});
__PACKAGE__->register_method({
name => 'add_repository',
@ -628,7 +636,7 @@ __PACKAGE__->register_method({
method => 'PUT',
description => "Add a standard repository to the configuration",
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
},
protected => 1,
proxyto => 'node',
@ -654,16 +662,20 @@ __PACKAGE__->register_method({
code => sub {
my ($param) = @_;
Proxmox::RS::APT::Repositories::add_repository($param->{handle}, "pve", $param->{digest});
}});
Proxmox::RS::APT::Repositories::add_repository(
$param->{handle}, "pve", $param->{digest},
);
},
});
__PACKAGE__->register_method({
name => 'change_repository',
path => 'repositories',
method => 'POST',
description => "Change the properties of a repository. Currently only allows enabling/disabling.",
description =>
"Change the properties of a repository. Currently only allows enabling/disabling.",
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
},
protected => 1,
proxyto => 'node',
@ -707,9 +719,10 @@ __PACKAGE__->register_method({
$param->{path},
int($param->{index}),
$options,
$param->{digest}
$param->{digest},
);
}});
},
});
__PACKAGE__->register_method({
name => 'versions',
@ -718,7 +731,7 @@ __PACKAGE__->register_method({
proxyto => 'node',
description => "Get package information for important Proxmox packages.",
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
check => ['perm', '/nodes/{node}', ['Sys.Audit']],
},
parameters => {
additionalProperties => 0,
@ -744,8 +757,16 @@ __PACKAGE__->register_method({
my @list = qw(proxmox-ve pve-manager);
my $aptver = $AptPkg::System::_system->versioning();
my $byver = sub { $aptver->compare($cache->{$b}->{CurrentVer}->{VerStr}, $cache->{$a}->{CurrentVer}->{VerStr}) };
push @list, sort $byver grep { /^(?:pve|proxmox)-kernel-/ && $cache->{$_}->{CurrentState} eq 'Installed' } keys %$cache;
my $byver = sub {
$aptver->compare(
$cache->{$b}->{CurrentVer}->{VerStr},
$cache->{$a}->{CurrentVer}->{VerStr},
);
};
push @list,
sort $byver
grep { /^(?:pve|proxmox)-kernel-/ && $cache->{$_}->{CurrentState} eq 'Installed' }
keys %$cache;
my @opt_pack = qw(
amd64-microcode
@ -830,7 +851,9 @@ __PACKAGE__->register_method({
my $candidate_ver = defined($p) ? $policy->candidate($p) : undef;
my $res;
if (my $current_ver = $p->{CurrentVer}) {
$res = $assemble_pkginfo->($pkgname, $info, $current_ver, $candidate_ver || $current_ver);
$res = $assemble_pkginfo->(
$pkgname, $info, $current_ver, $candidate_ver || $current_ver,
);
} elsif ($candidate_ver) {
$res = $assemble_pkginfo->($pkgname, $info, $candidate_ver, $candidate_ver);
delete $res->{OldVersion};
@ -845,7 +868,7 @@ __PACKAGE__->register_method({
} elsif ($pkgname eq 'proxmox-ve') {
$res->{RunningKernel} = $kernel_release;
}
if (grep( /^$pkgname$/, @opt_pack)) {
if (grep(/^$pkgname$/, @opt_pack)) {
next if $res->{CurrentState} eq 'NotInstalled';
}
@ -853,6 +876,7 @@ __PACKAGE__->register_method({
}
return $pkglist;
}});
},
});
1;

View file

@ -24,6 +24,7 @@ use base qw(PVE::RESTHandler);
use constant ALL_DAYS => 'mon,tue,wed,thu,fri,sat,sun';
PVE::JSONSchema::register_format('pve-day-of-week', \&verify_day_of_week);
sub verify_day_of_week {
my ($value, $noerr) = @_;
@ -40,16 +41,16 @@ sub assert_param_permission_common {
return if $user eq 'root@pam'; # always OK
for my $key (qw(tmpdir dumpdir script job-id)) {
raise_param_exc({ $key => "Only root may set this option."}) if exists $param->{$key};
raise_param_exc({ $key => "Only root may set this option." }) if exists $param->{$key};
}
if (grep { defined($param->{$_}) } qw(bwlimit ionice performance)) {
$rpcenv->check($user, "/", [ 'Sys.Modify' ]);
$rpcenv->check($user, "/", ['Sys.Modify']);
}
if ($param->{fleecing} && !$is_delete) {
my $fleecing = PVE::VZDump::parse_fleecing($param) // {};
$rpcenv->check($user, "/storage/$fleecing->{storage}", [ 'Datastore.AllocateSpace' ])
$rpcenv->check($user, "/storage/$fleecing->{storage}", ['Datastore.AllocateSpace'])
if $fleecing->{storage};
}
}
@ -61,7 +62,7 @@ my sub assert_param_permission_create {
assert_param_permission_common($rpcenv, $user, $param);
if (my $storeid = PVE::VZDump::get_storage_param($param)) {
$rpcenv->check($user, "/storage/$storeid", [ 'Datastore.Allocate' ]);
$rpcenv->check($user, "/storage/$storeid", ['Datastore.Allocate']);
}
}
@ -73,9 +74,9 @@ my sub assert_param_permission_update {
assert_param_permission_common($rpcenv, $user, $delete, 1);
if ($update->{storage}) {
$rpcenv->check($user, "/storage/$update->{storage}", [ 'Datastore.Allocate' ])
$rpcenv->check($user, "/storage/$update->{storage}", ['Datastore.Allocate']);
} elsif ($delete->{storage}) {
$rpcenv->check($user, "/storage/local", [ 'Datastore.Allocate' ]);
$rpcenv->check($user, "/storage/local", ['Datastore.Allocate']);
}
return if !$current; # early check done
@ -84,7 +85,7 @@ my sub assert_param_permission_update {
die "only root\@pam may edit jobs with a 'dumpdir' option.";
} else {
if (my $storeid = PVE::VZDump::get_storage_param($current)) {
$rpcenv->check($user, "/storage/$storeid", [ 'Datastore.Allocate' ]);
$rpcenv->check($user, "/storage/$storeid", ['Datastore.Allocate']);
}
}
}
@ -139,7 +140,7 @@ __PACKAGE__->register_method({
id => get_standard_option('pve-backup-jobid'),
},
},
links => [ { rel => 'child', href => "{id}" } ],
links => [{ rel => 'child', href => "{id}" }],
},
code => sub {
my ($param) = @_;
@ -178,7 +179,8 @@ __PACKAGE__->register_method({
}
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'create_job',
@ -188,7 +190,8 @@ __PACKAGE__->register_method({
description => "Create new vzdump backup job.",
permissions => {
check => ['perm', '/', ['Sys.Modify']],
description => "The 'tmpdir', 'dumpdir' and 'script' parameters are additionally restricted to the 'root\@pam' user.",
description =>
"The 'tmpdir', 'dumpdir' and 'script' parameters are additionally restricted to the 'root\@pam' user.",
},
parameters => {
additionalProperties => 0,
@ -200,8 +203,10 @@ __PACKAGE__->register_method({
optional => 1, # FIXME: make required on 8.0
},
schedule => {
description => "Backup schedule. The format is a subset of `systemd` calendar events.",
type => 'string', format => 'pve-calendar-event',
description =>
"Backup schedule. The format is a subset of `systemd` calendar events.",
type => 'string',
format => 'pve-calendar-event',
maxLength => 128,
optional => 1,
},
@ -213,7 +218,8 @@ __PACKAGE__->register_method({
optional => 1,
},
dow => {
type => 'string', format => 'pve-day-of-week-list',
type => 'string',
format => 'pve-day-of-week-list',
optional => 1,
description => "Day of week selection.",
requires => 'starttime',
@ -228,8 +234,9 @@ __PACKAGE__->register_method({
'repeat-missed' => {
optional => 1,
type => 'boolean',
description => "If true, the job will be run as soon as possible if it was missed".
" while the scheduler was not running.",
description =>
"If true, the job will be run as soon as possible if it was missed"
. " while the scheduler was not running.",
default => 0,
},
comment => {
@ -261,7 +268,10 @@ __PACKAGE__->register_method({
# autogenerate id for api compatibility FIXME remove with 8.0
my $id = extract_param($param, 'id') // UUID::uuid();
cfs_lock_file('jobs.cfg', undef, sub {
cfs_lock_file(
'jobs.cfg',
undef,
sub {
my $data = cfs_read_file('jobs.cfg');
die "Job '$id' already exists\n"
@ -275,11 +285,13 @@ __PACKAGE__->register_method({
PVE::Jobs::create_job($id, 'vzdump', $opts);
cfs_write_file('jobs.cfg', $data);
});
},
);
die "$@" if ($@);
return undef;
}});
},
});
__PACKAGE__->register_method({
name => 'read_job',
@ -316,7 +328,7 @@ __PACKAGE__->register_method({
}
my $jobs_data = cfs_read_file('jobs.cfg');
my $job = $jobs_data->{ids}->{$param->{id}};
my $job = $jobs_data->{ids}->{ $param->{id} };
if ($job && $job->{type} eq 'vzdump') {
# FIXME remove in PVE 8.0?
# backwards compat: before moving the job registry to pve-common, id was auto-injected
@ -326,7 +338,8 @@ __PACKAGE__->register_method({
raise_param_exc({ id => "No such job '$param->{id}'" });
}});
},
});
__PACKAGE__->register_method({
name => 'delete_job',
@ -368,7 +381,10 @@ __PACKAGE__->register_method({
}
if (!$found) {
cfs_lock_file('jobs.cfg', undef, sub {
cfs_lock_file(
'jobs.cfg',
undef,
sub {
my $jobs_data = cfs_read_file('jobs.cfg');
if (!defined($jobs_data->{ids}->{$id})) {
@ -379,7 +395,8 @@ __PACKAGE__->register_method({
PVE::Jobs::remove_job($id, 'vzdump');
cfs_write_file('jobs.cfg', $jobs_data);
});
},
);
die "$@" if $@;
} else {
$data->{jobs} = $newjobs;
@ -391,7 +408,8 @@ __PACKAGE__->register_method({
die "$@" if ($@);
return undef;
}});
},
});
__PACKAGE__->register_method({
name => 'update_job',
@ -401,15 +419,18 @@ __PACKAGE__->register_method({
description => "Update vzdump backup job definition.",
permissions => {
check => ['perm', '/', ['Sys.Modify']],
description => "The 'tmpdir', 'dumpdir' and 'script' parameters are additionally restricted to the 'root\@pam' user.",
description =>
"The 'tmpdir', 'dumpdir' and 'script' parameters are additionally restricted to the 'root\@pam' user.",
},
parameters => {
additionalProperties => 0,
properties => PVE::VZDump::Common::json_config_properties({
id => get_standard_option('pve-backup-jobid'),
schedule => {
description => "Backup schedule. The format is a subset of `systemd` calendar events.",
type => 'string', format => 'pve-calendar-event',
description =>
"Backup schedule. The format is a subset of `systemd` calendar events.",
type => 'string',
format => 'pve-calendar-event',
maxLength => 128,
optional => 1,
},
@ -421,13 +442,15 @@ __PACKAGE__->register_method({
optional => 1,
},
dow => {
type => 'string', format => 'pve-day-of-week-list',
type => 'string',
format => 'pve-day-of-week-list',
optional => 1,
requires => 'starttime',
description => "Day of week selection.",
},
delete => {
type => 'string', format => 'pve-configid-list',
type => 'string',
format => 'pve-configid-list',
description => "A list of settings you want to delete.",
optional => 1,
},
@ -440,8 +463,9 @@ __PACKAGE__->register_method({
'repeat-missed' => {
optional => 1,
type => 'boolean',
description => "If true, the job will be run as soon as possible if it was missed".
" while the scheduler was not running.",
description =>
"If true, the job will be run as soon as possible if it was missed"
. " while the scheduler was not running.",
default => 0,
},
comment => {
@ -478,7 +502,8 @@ __PACKAGE__->register_method({
my $jobs = $data->{jobs} || [];
die "no options specified\n" if !scalar(keys $param->%*) && !scalar(keys $delete->%*);
die "no options specified\n"
if !scalar(keys $param->%*) && !scalar(keys $delete->%*);
PVE::VZDump::verify_vzdump_parameters($param);
my $opts = PVE::VZDump::JobBase->check_config($id, $param, 0, 1);
@ -545,19 +570,25 @@ __PACKAGE__->register_method({
return;
};
cfs_lock_file('vzdump.cron', undef, sub {
cfs_lock_file(
'vzdump.cron',
undef,
sub {
cfs_lock_file('jobs.cfg', undef, $update_job);
die "$@" if ($@);
});
},
);
die "$@" if ($@);
}});
},
});
__PACKAGE__->register_method({
name => 'get_volume_backup_included',
path => '{id}/included_volumes',
method => 'GET',
protected => 1,
description => "Returns included guests and the backup status of their disks. Optimized to be used in ExtJS tree views.",
description =>
"Returns included guests and the backup status of their disks. Optimized to be used in ExtJS tree views.",
permissions => {
check => ['perm', '/', ['Sys.Audit']],
},
@ -569,7 +600,8 @@ __PACKAGE__->register_method({
},
returns => {
type => 'object',
description => 'Root node of the tree object. Children represent guests, grandchildren represent volumes of that guest.',
description =>
'Root node of the tree object. Children represent guests, grandchildren represent volumes of that guest.',
properties => {
children => {
type => 'array',
@ -587,13 +619,15 @@ __PACKAGE__->register_method({
},
type => {
type => 'string',
description => 'Type of the guest, VM, CT or unknown for removed but not purged guests.',
description =>
'Type of the guest, VM, CT or unknown for removed but not purged guests.',
enum => ['qemu', 'lxc', 'unknown'],
},
children => {
type => 'array',
optional => 1,
description => 'The volumes of the guest with the information if they will be included in backups.',
description =>
'The volumes of the guest with the information if they will be included in backups.',
items => {
type => 'object',
properties => {
@ -607,11 +641,13 @@ __PACKAGE__->register_method({
},
included => {
type => 'boolean',
description => 'Whether the volume is included in the backup or not.',
description =>
'Whether the volume is included in the backup or not.',
},
reason => {
type => 'string',
description => 'The reason why the volume is included (or excluded).',
description =>
'The reason why the volume is included (or excluded).',
},
},
},
@ -641,7 +677,7 @@ __PACKAGE__->register_method({
}
if (!$job) {
my $jobs_data = cfs_read_file('jobs.cfg');
my $j = $jobs_data->{ids}->{$param->{id}};
my $j = $jobs_data->{ids}->{ $param->{id} };
if ($j && $j->{type} eq 'vzdump') {
$job = $j;
}
@ -662,7 +698,7 @@ __PACKAGE__->register_method({
# 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);
$rpcenv->check($user, "/vms/$_", ['VM.Audit'], 1);
} @job_vmids;
my $result = {
@ -678,11 +714,14 @@ __PACKAGE__->register_method({
# Since there is no more data available we can only deliver the VMID
# and no volumes.
if (!defined $vmlist->{ids}->{$vmid}) {
push(@{$result->{children}}, {
push(
@{ $result->{children} },
{
id => int($vmid),
type => 'unknown',
leaf => 1,
});
},
);
next;
}
@ -709,8 +748,9 @@ __PACKAGE__->register_method({
my $disk = {
# id field must be unique for ExtJS tree view
id => "$vmid:$volume->{key}",
name => $volume->{volume_config}->{file} // $volume->{volume_config}->{volume},
included=> $volume->{included},
name => $volume->{volume_config}->{file}
// $volume->{volume_config}->{volume},
included => $volume->{included},
reason => $volume->{reason},
leaf => 1,
};
@ -721,16 +761,20 @@ __PACKAGE__->register_method({
# it's possible for a guest to have no volumes configured
$leaf = 1 if !@{$children};
push(@{$result->{children}}, {
push(
@{ $result->{children} },
{
id => int($vmid),
type => $type,
name => $name,
children => $children,
leaf => $leaf,
});
},
);
}
return $result;
}});
},
});
1;

View file

@ -10,18 +10,17 @@ 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',
@ -39,7 +38,7 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
@ -49,11 +48,10 @@ __PACKAGE__->register_method ({
];
return $result;
}
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'qemu_caps_index',
path => 'qemu',
method => 'GET',
@ -71,18 +69,17 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
my $result = [
{ name => 'cpu' },
{ name => 'machines' },
{ name => 'cpu' }, { name => 'machines' },
];
return $result;
}
},
});
1;

View file

@ -29,51 +29,51 @@ use PVE::API2::Storage::Config;
use base qw(PVE::RESTHandler);
my $pve_osd_default_journal_size = 1024*5;
my $pve_osd_default_journal_size = 1024 * 5;
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Ceph::Cfg",
path => 'cfg',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Ceph::OSD",
path => 'osd',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Ceph::MDS",
path => 'mds',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Ceph::MGR",
path => 'mgr',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Ceph::MON",
path => 'mon',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Ceph::FS",
path => 'fs',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Ceph::Pool",
path => 'pool',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '',
method => 'GET',
description => "Directory index.",
permissions => { user => 'all' },
permissions => {
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
},
parameters => {
additionalProperties => 0,
@ -87,7 +87,7 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
@ -112,9 +112,10 @@ __PACKAGE__->register_method ({
];
return $result;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'init',
path => 'init',
method => 'POST',
@ -122,7 +123,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -130,14 +131,16 @@ __PACKAGE__->register_method ({
node => get_standard_option('pve-node'),
network => {
description => "Use specific network for all ceph related traffic",
type => 'string', format => 'CIDR',
type => 'string',
format => 'CIDR',
optional => 1,
maxLength => 128,
},
'cluster-network' => {
description => "Declare a separate cluster network, OSDs will route" .
"heartbeat, object replication and recovery traffic over it",
type => 'string', format => 'CIDR',
description => "Declare a separate cluster network, OSDs will route"
. "heartbeat, object replication and recovery traffic over it",
type => 'string',
format => 'CIDR',
requires => 'network',
optional => 1,
maxLength => 128,
@ -160,9 +163,9 @@ __PACKAGE__->register_method ({
},
# TODO: deprecrated, remove with PVE 9
pg_bits => {
description => "Placement group bits, used to specify the " .
"default number of placement groups.\n\nDepreacted. This " .
"setting was deprecated in recent Ceph versions.",
description => "Placement group bits, used to specify the "
. "default number of placement groups.\n\nDepreacted. This "
. "setting was deprecated in recent Ceph versions.",
type => 'integer',
default => 6,
optional => 1,
@ -170,10 +173,10 @@ __PACKAGE__->register_method ({
maximum => 14,
},
disable_cephx => {
description => "Disable cephx authentication.\n\n" .
"WARNING: cephx is a security feature protecting against " .
"man-in-the-middle attacks. Only consider disabling cephx ".
"if your network is private!",
description => "Disable cephx authentication.\n\n"
. "WARNING: cephx is a security feature protecting against "
. "man-in-the-middle attacks. Only consider disabling cephx "
. "if your network is private!",
type => 'boolean',
optional => 1,
default => 0,
@ -193,14 +196,17 @@ __PACKAGE__->register_method ({
}
my $pve_ceph_cfgdir = PVE::Ceph::Tools::get_config('pve_ceph_cfgdir');
if (! -d $pve_ceph_cfgdir) {
if (!-d $pve_ceph_cfgdir) {
File::Path::make_path($pve_ceph_cfgdir);
}
my $auth = $param->{disable_cephx} ? 'none' : 'cephx';
# simply load old config if it already exists
PVE::Cluster::cfs_lock_file('ceph.conf', undef, sub {
PVE::Cluster::cfs_lock_file(
'ceph.conf',
undef,
sub {
my $cfg = cfs_read_file('ceph.conf');
if (!$cfg->{global}) {
@ -245,13 +251,15 @@ __PACKAGE__->register_method ({
PVE::Ceph::Tools::get_or_create_admin_keyring();
}
PVE::Ceph::Tools::setup_pve_symlinks();
});
},
);
die $@ if $@;
return undef;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'stop',
path => 'stop',
method => 'POST',
@ -259,7 +267,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -270,7 +278,8 @@ __PACKAGE__->register_method ({
type => 'string',
optional => 1,
default => 'ceph.target',
pattern => '(ceph|mon|mds|osd|mgr)(\.'.PVE::Ceph::Services::SERVICE_REGEX.')?',
pattern => '(ceph|mon|mds|osd|mgr)(\.'
. PVE::Ceph::Services::SERVICE_REGEX . ')?',
},
},
},
@ -298,11 +307,11 @@ __PACKAGE__->register_method ({
PVE::Ceph::Services::ceph_service_cmd(@$cmd);
};
return $rpcenv->fork_worker('srvstop', $param->{service} || 'ceph',
$authuser, $worker);
}});
return $rpcenv->fork_worker('srvstop', $param->{service} || 'ceph', $authuser, $worker);
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'start',
path => 'start',
method => 'POST',
@ -310,7 +319,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -321,7 +330,8 @@ __PACKAGE__->register_method ({
type => 'string',
optional => 1,
default => 'ceph.target',
pattern => '(ceph|mon|mds|osd|mgr)(\.'.PVE::Ceph::Services::SERVICE_REGEX.')?',
pattern => '(ceph|mon|mds|osd|mgr)(\.'
. PVE::Ceph::Services::SERVICE_REGEX . ')?',
},
},
},
@ -351,9 +361,10 @@ __PACKAGE__->register_method ({
return $rpcenv->fork_worker('srvstart', $param->{service} || 'ceph',
$authuser, $worker);
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'restart',
path => 'restart',
method => 'POST',
@ -361,7 +372,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -372,7 +383,7 @@ __PACKAGE__->register_method ({
type => 'string',
optional => 1,
default => 'ceph.target',
pattern => '(mon|mds|osd|mgr)(\.'.PVE::Ceph::Services::SERVICE_REGEX.')?',
pattern => '(mon|mds|osd|mgr)(\.' . PVE::Ceph::Services::SERVICE_REGEX . ')?',
},
},
},
@ -402,9 +413,10 @@ __PACKAGE__->register_method ({
return $rpcenv->fork_worker('srvrestart', $param->{service} || 'ceph',
$authuser, $worker);
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'status',
path => 'status',
method => 'GET',
@ -412,7 +424,7 @@ __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,
@ -427,10 +439,10 @@ __PACKAGE__->register_method ({
PVE::Ceph::Tools::check_ceph_inited();
return PVE::Ceph::Tools::ceph_cluster_status();
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'crush',
path => 'crush',
method => 'GET',
@ -438,7 +450,7 @@ __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,
@ -463,7 +475,8 @@ __PACKAGE__->register_method ({
my $rados = PVE::RADOS->new();
eval {
my $bindata = $rados->mon_command({ prefix => 'osd getcrushmap', format => 'plain' });
my $bindata =
$rados->mon_command({ prefix => 'osd getcrushmap', format => 'plain' });
file_set_contents($mapfile, $bindata);
run_command(['crushtool', '-d', $mapfile, '-o', $mapdata]);
$txt = file_get_contents($mapdata);
@ -476,7 +489,8 @@ __PACKAGE__->register_method ({
die $err if $err;
return $txt;
}});
},
});
__PACKAGE__->register_method({
name => 'log',
@ -485,7 +499,7 @@ __PACKAGE__->register_method({
description => "Read ceph log",
proxyto => 'node',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
check => ['perm', '/nodes/{node}', ['Sys.Syslog']],
},
protected => 1,
parameters => {
@ -510,15 +524,15 @@ __PACKAGE__->register_method({
type => "object",
properties => {
n => {
description=> "Line number",
type=> 'integer',
description => "Line number",
type => 'integer',
},
t => {
description=> "Line text",
description => "Line text",
type => 'string',
}
}
}
},
},
},
},
code => sub {
my ($param) = @_;
@ -530,14 +544,16 @@ __PACKAGE__->register_method({
my $node = $param->{node};
my $logfile = "/var/log/ceph/ceph.log";
my ($count, $lines) = PVE::Tools::dump_logfile($logfile, $param->{start}, $param->{limit});
my ($count, $lines) =
PVE::Tools::dump_logfile($logfile, $param->{start}, $param->{limit});
$rpcenv->set_result_attrib('total', $count);
return $lines;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'rules',
path => 'rules',
method => 'GET',
@ -545,7 +561,7 @@ __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,
@ -561,10 +577,10 @@ __PACKAGE__->register_method ({
name => {
description => "Name of the CRUSH rule.",
type => "string",
}
},
},
links => [ { rel => 'child', href => "{name}" } ],
},
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
@ -582,9 +598,10 @@ __PACKAGE__->register_method ({
}
return $res;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'cmd_safety',
path => 'cmd-safety',
method => 'GET',
@ -592,7 +609,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Audit' ]],
check => ['perm', '/', ['Sys.Audit']],
},
parameters => {
additionalProperties => 0,
@ -624,7 +641,7 @@ __PACKAGE__->register_method ({
status => {
type => 'string',
optional => 1,
description => 'Status message given by Ceph.'
description => 'Status message given by Ceph.',
},
},
},
@ -668,7 +685,7 @@ __PACKAGE__->register_method ({
if ($service eq 'mon' && $action eq 'destroy') {
$params->{id} = $id;
} else {
$params->{ids} = [ $id ];
$params->{ids} = [$id];
}
$result = $rados->mon_cmd($params, 1);
@ -678,6 +695,7 @@ __PACKAGE__->register_method ({
$result->{status} = $result->{status_message};
return $result;
}});
},
});
1;

View file

@ -11,7 +11,7 @@ use PVE::Tools qw(file_get_contents);
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '',
method => 'GET',
@ -29,27 +29,26 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
my $result = [
{ name => 'raw' },
{ name => 'db' },
{ name => 'value' },
{ name => 'raw' }, { name => 'db' }, { name => 'value' },
];
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 => {
@ -67,16 +66,17 @@ __PACKAGE__->register_method ({
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 => {
@ -90,11 +90,11 @@ __PACKAGE__->register_method ({
items => {
type => 'object',
properties => {
section => { type => "string", },
name => { type => "string", },
value => { type => "string", },
level => { type => "string", },
'can_update_at_runtime' => { type => "boolean", },
section => { type => "string" },
name => { type => "string" },
value => { type => "string" },
level => { type => "string" },
'can_update_at_runtime' => { type => "boolean" },
mask => { type => "string" },
},
},
@ -105,26 +105,26 @@ __PACKAGE__->register_method ({
PVE::Ceph::Tools::check_ceph_inited();
my $rados = PVE::RADOS->new();
my $res = $rados->mon_command( { prefix => 'config dump', format => 'json' });
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
$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 => {
@ -136,7 +136,7 @@ __PACKAGE__->register_method ({
typetext => "<section>:<config key>[;<section>:<config key>]",
pattern => $CONFIGKEYS_RE,
description => "List of <section>:<config key> items.",
}
},
},
},
returns => {
@ -167,9 +167,9 @@ __PACKAGE__->register_method ({
my $config = {};
my $rados = PVE::RADOS->new();
my $configdb = $rados->mon_command( { prefix => 'config dump', format => 'json' });
my $configdb = $rados->mon_command({ prefix => 'config dump', format => 'json' });
for my $s (@{$configdb}) {
my ($section, $name, $value) = $s->@{'section', 'name', 'value'};
my ($section, $name, $value) = $s->@{ 'section', 'name', 'value' };
my $n_section = $normalize->($section);
my $n_name = $normalize->($name);
@ -183,7 +183,7 @@ __PACKAGE__->register_method ({
my $n_section = $normalize->($section);
next if !defined $requested_keys->{$n_section};
for my $key (keys %{$config_file->{$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};
@ -191,4 +191,5 @@ __PACKAGE__->register_method ({
}
return $config;
}});
},
});

View file

@ -15,14 +15,14 @@ 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 => {
@ -50,7 +50,7 @@ __PACKAGE__->register_method ({
},
},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
@ -62,18 +62,18 @@ __PACKAGE__->register_method ({
my $cephfs_list = PVE::Ceph::Tools::ls_fs($rados);
my $res = [
map {{
map { {
name => $_->{name},
metadata_pool => $_->{metadata_pool},
data_pool => $_->{data_pools}->[0],
}} @$cephfs_list
} } @$cephfs_list
];
return $res;
}
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'createfs',
path => '{name}',
method => 'POST',
@ -81,7 +81,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -95,7 +95,8 @@ __PACKAGE__->register_method ({
pattern => qr|^[^:/\s]+$|,
},
pg_num => {
description => "Number of placement groups for the backing data pool. The metadata pool will use a quarter of this.",
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,
@ -204,10 +205,11 @@ __PACKAGE__->register_method ({
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 $@;
die "adding storage for CephFS '$fs_name' failed, check log "
. "and add manually!\n$@\n"
if $@;
}
};
@ -215,7 +217,7 @@ __PACKAGE__->register_method ({
my $user = $rpcenv->get_user();
return $rpcenv->fork_worker('cephfscreate', $fs_name, $user, $worker);
}
},
});
1;

View file

@ -14,13 +14,13 @@ 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,
@ -53,7 +53,8 @@ __PACKAGE__->register_method ({
standby_replay => {
type => 'boolean',
optional => 1,
description => 'If true, the standby MDS is polling the active MDS for faster recovery (hot standby).',
description =>
'If true, the standby MDS is polling the active MDS for faster recovery (hot standby).',
},
rank => {
type => 'integer',
@ -61,7 +62,7 @@ __PACKAGE__->register_method ({
},
},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
@ -83,10 +84,10 @@ __PACKAGE__->register_method ({
}
return PVE::RESTHandler::hash_to_array($mds_hash, 'name');
}
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'createmds',
path => '{name}',
method => 'POST',
@ -94,7 +95,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -112,8 +113,9 @@ __PACKAGE__->register_method ({
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.",
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.",
},
},
},
@ -144,7 +146,7 @@ __PACKAGE__->register_method ({
my $section = "mds.$mds_id";
if (defined($cfg->{$section})) {
die "MDS '$mds_id' already referenced in ceph config, abort!\n"
die "MDS '$mds_id' already referenced in ceph config, abort!\n";
}
if (!defined($cfg->{mds}->{keyring})) {
@ -178,10 +180,10 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('cephcreatemds', "mds.$mds_id", $authuser, $worker);
}
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'destroymds',
path => '{name}',
method => 'DELETE',
@ -189,7 +191,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -229,7 +231,7 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('cephdestroymds', "mds.$mds_id", $authuser, $worker);
}
},
});
1;

View file

@ -15,13 +15,13 @@ 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,
@ -53,7 +53,7 @@ __PACKAGE__->register_method ({
},
},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
@ -72,15 +72,15 @@ __PACKAGE__->register_method ({
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');
}
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'createmgr',
path => '{id}',
method => 'POST',
@ -88,7 +88,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -126,9 +126,10 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('cephcreatemgr', "mgr.$mgrid", $authuser, $worker);
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'destroymgr',
path => '{id}',
method => 'DELETE',
@ -136,7 +137,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -168,4 +169,5 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('cephdestroymgr', "mgr.$mgrid", $authuser, $worker);
}});
},
});

View file

@ -23,13 +23,17 @@ 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' });
$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+)$/;
@ -42,11 +46,11 @@ my $find_mon_ips = sub {
} else {
# don't refactor into '[ PVE::Cluster::remote... ]' as it uses wantarray
my $ip = PVE::Cluster::remote_node_ip($node);
return [ $ip ];
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";
@ -59,21 +63,23 @@ my $find_mon_ips = sub {
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"
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";
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 = [];
for my $net (@{$public_nets}) {
push @{$allowed_list}, @{PVE::Network::get_local_ip_from_cidr($net)};
push @{$allowed_list}, @{ PVE::Network::get_local_ip_from_cidr($net) };
}
my $allowed_ips = PVE::Network::unique_ips($allowed_list);
@ -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;
};
@ -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,7 +204,7 @@ __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,
@ -221,7 +229,7 @@ __PACKAGE__->register_method ({
state => { type => 'string', optional => 1 },
},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
@ -245,7 +253,7 @@ __PACKAGE__->register_method ({
my $name = $d->{name};
$monhash->{$name}->{rank} = $d->{rank};
$monhash->{$name}->{addr} = $d->{addr};
if (grep { $_ eq $d->{rank} } @{$monstat->{quorum}}) {
if (grep { $_ eq $d->{rank} } @{ $monstat->{quorum} }) {
$monhash->{$name}->{quorum} = 1;
$monhash->{$name}->{state} = 'running';
}
@ -260,9 +268,10 @@ __PACKAGE__->register_method ({
}
return PVE::RESTHandler::hash_to_array($monhash, 'name');
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'createmon',
path => '{monid}',
method => 'POST',
@ -270,7 +279,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -284,9 +293,10 @@ __PACKAGE__->register_method ({
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',
description => 'Overwrites autodetected monitor IP address(es). '
. 'Must be in the public network(s) of Ceph.',
type => 'string',
format => 'ip-list',
optional => 1,
},
},
@ -321,12 +331,16 @@ __PACKAGE__->register_method ({
my $worker = sub {
my $upid = shift;
PVE::Cluster::cfs_lock_file('ceph.conf', undef, sub {
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'));
$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);
@ -334,7 +348,7 @@ __PACKAGE__->register_method ({
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) {
if (!-f $mon_keyring) {
print "creating new monitor keyring\n";
run_command([
'ceph-authtool',
@ -348,10 +362,7 @@ __PACKAGE__->register_method ({
'allow *',
]);
run_command([
'ceph-authtool',
$mon_keyring,
'--import-keyring',
$client_keyring,
'ceph-authtool', $mon_keyring, '--import-keyring', $client_keyring,
]);
}
@ -397,7 +408,8 @@ __PACKAGE__->register_method ({
];
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' });
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
@ -442,20 +454,29 @@ __PACKAGE__->register_method ({
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";
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'],
[
'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';
$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 $@;
@ -465,21 +486,23 @@ __PACKAGE__->register_method ({
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}
})
id => $param->{node},
});
}
};
return $rpcenv->fork_worker('cephcreatemon', $monsection, $authuser, $worker);
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'destroymon',
path => '{monid}',
method => 'DELETE',
@ -487,7 +510,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -527,12 +550,16 @@ __PACKAGE__->register_method ({
my $worker = sub {
my $upid = shift;
PVE::Cluster::cfs_lock_file('ceph.conf', undef, sub {
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'));
$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};
@ -565,7 +592,8 @@ __PACKAGE__->register_method ({
$assert_mon_can_remove->($monhash, $monlist, $monid, $mondir);
# this also stops the service
$rados->mon_command({ prefix => "mon remove", name => $monid, format => 'plain' });
$rados->mon_command(
{ prefix => "mon remove", name => $monid, format => 'plain' });
# delete section
delete $cfg->{$monsection};
@ -584,7 +612,8 @@ __PACKAGE__->register_method ({
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);
$monhost =
$remove_addr_from_mon_host->($monhost, $mon_host_ip);
}
}
}
@ -597,12 +626,14 @@ __PACKAGE__->register_method ({
eval { PVE::Ceph::Services::ceph_service_cmd('disable', $monsection) };
warn $@ if $@;
PVE::Ceph::Services::broadcast_ceph_services();
});
},
);
die $@ if $@;
};
return $rpcenv->fork_worker('cephdestroymon', $monsection, $authuser, $worker);
}});
},
});
1;

View file

@ -39,7 +39,7 @@ my $get_osd_status = sub {
my $osdstat;
foreach my $d (@$osdlist) {
$osdstat->{$d->{osd}} = $d if defined($d->{osd});
$osdstat->{ $d->{osd} } = $d if defined($d->{osd});
}
if (defined($osdid)) {
die "no such OSD '$osdid'\n" if !$osdstat->{$osdid};
@ -52,7 +52,7 @@ my $get_osd_status = sub {
my $get_osd_usage = sub {
my ($rados) = @_;
my $osdlist = $rados->mon_command({ prefix => 'pg dump', dumpcontents => [ 'osds' ]});
my $osdlist = $rados->mon_command({ prefix => 'pg dump', dumpcontents => ['osds'] });
if (!($osdlist && ref($osdlist))) {
warn "got unknown result format for 'pg dump osds' command\n";
return [];
@ -64,7 +64,7 @@ my $get_osd_usage = sub {
my $osdstat = {};
for my $d (@$osdlist) {
$osdstat->{$d->{osd}} = $d if defined($d->{osd});
$osdstat->{ $d->{osd} } = $d if defined($d->{osd});
}
return $osdstat;
@ -74,7 +74,7 @@ my sub get_proc_pss_from_pid {
my ($pid) = @_;
return if !defined($pid) || $pid <= 1;
open (my $SMAPS_FH, '<', "/proc/$pid/smaps_rollup")
open(my $SMAPS_FH, '<', "/proc/$pid/smaps_rollup")
or die "failed to open PSS memory-stat from process - $!\n";
while (my $line = <$SMAPS_FH>) {
@ -87,8 +87,7 @@ my sub get_proc_pss_from_pid {
die "internal error: failed to find PSS memory-stat in procfs for PID $pid\n";
}
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '',
method => 'GET',
@ -96,7 +95,7 @@ __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,
@ -124,7 +123,7 @@ __PACKAGE__->register_method ({
PVE::Ceph::Tools::check_ceph_inited();
my $rados = PVE::RADOS->new();
my $res = $rados->mon_command({ prefix => 'osd df', output_method => 'tree', });
my $res = $rados->mon_command({ prefix => 'osd df', output_method => 'tree' });
die "no tree nodes found\n" if !($res && $res->{nodes});
@ -139,7 +138,7 @@ __PACKAGE__->register_method ({
my $nodes = {};
my $newnodes = {};
foreach my $e (@{$res->{nodes}}) {
foreach my $e (@{ $res->{nodes} }) {
my ($id, $name) = $e->@{qw(id name)};
$nodes->{$id} = $e;
@ -147,7 +146,7 @@ __PACKAGE__->register_method ({
my $new = {
id => $id,
name => $name,
type => $e->{type}
type => $e->{type},
};
foreach my $opt (qw(status crush_weight reweight device_class pgs)) {
@ -161,7 +160,7 @@ __PACKAGE__->register_method ({
if (my $stat = $osd_usage->{$id}) {
$new->{total_space} = ($stat->{kb} || 1) * 1024;
$new->{bytes_used} = ($stat->{kb_used} || 0) * 1024;
$new->{percent_used} = ($new->{bytes_used}*100)/$new->{total_space};
$new->{percent_used} = ($new->{bytes_used} * 100) / $new->{total_space};
if (my $d = $stat->{perf_stat}) {
$new->{commit_latency_ms} = $d->{commit_latency_ms};
$new->{apply_latency_ms} = $d->{apply_latency_ms};
@ -186,19 +185,19 @@ __PACKAGE__->register_method ({
$newnodes->{$id} = $new;
}
foreach my $e (@{$res->{nodes}}) {
foreach my $e (@{ $res->{nodes} }) {
my ($id, $name) = $e->@{qw(id name)};
my $new = $newnodes->{$id};
if ($e->{children} && scalar(@{$e->{children}})) {
if ($e->{children} && scalar(@{ $e->{children} })) {
$new->{children} = [];
$new->{leaf} = 0;
foreach my $cid (@{$e->{children}}) {
foreach my $cid (@{ $e->{children} }) {
$nodes->{$cid}->{parent} = $id;
if ($nodes->{$cid}->{type} eq 'osd' && $e->{type} eq 'host') {
$newnodes->{$cid}->{host} = $name;
}
push @{$new->{children}}, $newnodes->{$cid};
push @{ $new->{children} }, $newnodes->{$cid};
}
} else {
$new->{leaf} = ($id >= 0) ? 1 : 0;
@ -210,7 +209,7 @@ __PACKAGE__->register_method ({
}
my $realroots = [];
foreach my $e (@{$res->{nodes}}) {
foreach my $e (@{ $res->{nodes} }) {
my $id = $e->{id};
if (!$nodes->{$id}->{parent}) {
push @$realroots, $newnodes->{$id};
@ -222,16 +221,17 @@ __PACKAGE__->register_method ({
my $data = {
root => {
leaf => 0,
children => $realroots
children => $realroots,
},
};
$data->{flags} = $flags if $flags; # we want this for the noout flag
return $data;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'createosd',
path => '',
method => 'POST',
@ -253,11 +253,12 @@ __PACKAGE__->register_method ({
},
db_dev_size => {
description => "Size in GiB for block.db.",
verbose_description => "If a block.db is requested but the size is not given, will"
." be automatically selected by: bluestore_block_db_size from the ceph database"
." (osd or global section) or config (osd or global section) in that order."
." If this is not available, it will be sized 10% of the size of the OSD device."
." Fails if the available size is not enough.",
verbose_description =>
"If a block.db is requested but the size is not given, will"
. " be automatically selected by: bluestore_block_db_size from the ceph database"
. " (osd or global section) or config (osd or global section) in that order."
. " If this is not available, it will be sized 10% of the size of the OSD device."
. " Fails if the available size is not enough.",
optional => 1,
type => 'number',
default => 'bluestore_block_db_size or 10% of OSD size',
@ -271,11 +272,12 @@ __PACKAGE__->register_method ({
},
wal_dev_size => {
description => "Size in GiB for block.wal.",
verbose_description => "If a block.wal is requested but the size is not given, will"
." be automatically selected by: bluestore_block_wal_size from the ceph database"
." (osd or global section) or config (osd or global section) in that order."
." If this is not available, it will be sized 1% of the size of the OSD device."
." Fails if the available size is not enough.",
verbose_description =>
"If a block.wal is requested but the size is not given, will"
. " be automatically selected by: bluestore_block_wal_size from the ceph database"
. " (osd or global section) or config (osd or global section) in that order."
. " If this is not available, it will be sized 1% of the size of the OSD device."
. " Fails if the available size is not enough.",
optional => 1,
minimum => 0.5,
default => 'bluestore_block_wal_size or 1% of OSD size',
@ -286,18 +288,19 @@ __PACKAGE__->register_method ({
type => 'boolean',
optional => 1,
default => 0,
description => "Enables encryption of the OSD."
description => "Enables encryption of the OSD.",
},
'crush-device-class' => {
optional => 1,
type => 'string',
description => "Set the device class of the OSD in crush."
description => "Set the device class of the OSD in crush.",
},
'osds-per-device' => {
optional => 1,
type => 'integer',
minimum => '1',
description => 'OSD services per physical device. Only useful for fast NVMe devices"
description =>
'OSD services per physical device. Only useful for fast NVMe devices"
." to utilize their performance better.',
},
},
@ -322,8 +325,9 @@ __PACKAGE__->register_method ({
# allow 'osds-per-device' only without dedicated db and/or wal devs. We cannot specify them with
# 'ceph-volume lvm batch' and they don't make a lot of sense on fast NVMEs anyway.
if ($param->{'osds-per-device'}) {
for my $type ( qw(db_dev wal_dev) ) {
raise_param_exc({ $type => "cannot use 'osds-per-device' parameter with '${type}'" })
for my $type (qw(db_dev wal_dev)) {
raise_param_exc(
{ $type => "cannot use 'osds-per-device' parameter with '${type}'" })
if $param->{$type};
}
}
@ -339,12 +343,13 @@ __PACKAGE__->register_method ({
my $osd_net_obj = PVE::Network::IP_from_cidr($osd_network);
my $osd_base_cidr = $osd_net_obj->{ip} . "/" . $osd_net_obj->{prefixlen};
die "No address from ceph cluster network (${osd_base_cidr}) found on node '$nodename'. ".
"Check your network config.\n";
die
"No address from ceph cluster network (${osd_base_cidr}) found on node '$nodename'. "
. "Check your network config.\n";
}
}
for my $type ( qw(dev db_dev wal_dev) ) {
for my $type (qw(dev db_dev wal_dev)) {
next if !$param->{$type};
my $type_dev = PVE::Diskmanage::verify_blockdev_path($param->{$type});
@ -359,7 +364,7 @@ __PACKAGE__->register_method ({
};
if (my $size = $param->{"${type}_size"}) {
$devs->{$type}->{size} = PVE::Tools::convert_size($size, 'gb' => 'b') ;
$devs->{$type}->{size} = PVE::Tools::convert_size($size, 'gb' => 'b');
}
}
@ -371,12 +376,13 @@ __PACKAGE__->register_method ({
die "unable to get device info for '$dev'\n" if !$disklist->{$devname};
die "device '$dev' is already in use\n" if $disklist->{$devname}->{used};
for my $type ( qw(db_dev wal_dev) ) {
for my $type (qw(db_dev wal_dev)) {
my $d = $devs->{$type};
next if !$d;
my $name = $d->{name};
my $info = $disklist->{$name};
die "unable to get device info for '$d->{dev}' for type $type\n" if !$disklist->{$name};
die "unable to get device info for '$d->{dev}' for type $type\n"
if !$disklist->{$name};
if (my $usage = $info->{used}) {
if ($usage eq 'partitions') {
die "device '$d->{dev}' is not GPT partitioned\n" if !$info->{gpt};
@ -387,9 +393,8 @@ __PACKAGE__->register_method ({
}
};
# test disk requirements early
my $devlist = [ map { $_->{name} } values %$devs ];
my $devlist = [map { $_->{name} } values %$devs];
my $disklist = PVE::Diskmanage::get_disks($devlist, 1, 1);
$test_disk_requirements->($disklist);
@ -397,19 +402,23 @@ __PACKAGE__->register_method ({
my $rados = PVE::RADOS->new();
my $monstat = $rados->mon_command({ prefix => 'quorum_status' });
my $ceph_bootstrap_osd_keyring = PVE::Ceph::Tools::get_config('ceph_bootstrap_osd_keyring');
my $ceph_bootstrap_osd_keyring =
PVE::Ceph::Tools::get_config('ceph_bootstrap_osd_keyring');
if (! -f $ceph_bootstrap_osd_keyring && $ceph_conf->{global}->{auth_client_required} eq 'cephx') {
if (
!-f $ceph_bootstrap_osd_keyring
&& $ceph_conf->{global}->{auth_client_required} eq 'cephx'
) {
my $bindata = $rados->mon_command({
prefix => 'auth get-or-create',
entity => 'client.bootstrap-osd',
caps => [
'mon' => 'allow profile bootstrap-osd'
'mon' => 'allow profile bootstrap-osd',
],
format => 'plain',
});
file_set_contents($ceph_bootstrap_osd_keyring, $bindata);
};
}
# See FIXME below
my @udev_trigger_devs = ();
@ -450,10 +459,10 @@ __PACKAGE__->register_method ({
my $vgs = PVE::Storage::LVMPlugin::lvm_vgs(1);
my $vg;
for my $vgname ( sort keys %$vgs ) {
for my $vgname (sort keys %$vgs) {
next if $vgname !~ /^ceph-/;
for my $pv ( @{$vgs->{$vgname}->{pvs}} ) {
for my $pv (@{ $vgs->{$vgname}->{pvs} }) {
next if $pv->{name} ne $dev->{devpath};
$vg = $vgname;
last;
@ -503,7 +512,7 @@ __PACKAGE__->register_method ({
# create allows for detailed configuration of DB and WAL devices
# batch for easy creation of multiple OSDs (per device)
my $create_mode = $param->{'osds-per-device'} ? 'batch' : 'create';
my $cmd = ['ceph-volume', 'lvm', $create_mode ];
my $cmd = ['ceph-volume', 'lvm', $create_mode];
push @$cmd, '--crush-device-class', $dev_class if $dev_class;
my $devname = $devs->{dev}->{name};
@ -519,7 +528,7 @@ __PACKAGE__->register_method ({
};
my $sizes;
foreach my $type ( qw(db wal) ) {
foreach my $type (qw(db wal)) {
my $fallback_size = $size_map->{$type};
my $d = $devs->{"${type}_dev"};
next if !$d;
@ -531,7 +540,8 @@ __PACKAGE__->register_method ({
}
print "creating block.$type on '$d->{dev}'\n";
my $name = $d->{name};
my $part_or_lv = $create_part_or_lv->($disklist->{$name}, $d->{size}, "osd-$type");
my $part_or_lv =
$create_part_or_lv->($disklist->{$name}, $d->{size}, "osd-$type");
print "using '$part_or_lv' for block.$type\n";
push @$cmd, "--block.$type", $part_or_lv;
@ -566,7 +576,8 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('cephcreateosd', $devs->{dev}->{name}, $authuser, $worker);
}});
},
});
my $OSD_DEV_RETURN_PROPS = {
device => {
@ -596,7 +607,7 @@ my $OSD_DEV_RETURN_PROPS = {
},
};
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'osdindex',
path => '{osdid}',
method => 'GET',
@ -618,20 +629,20 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
my $result = [
{ name => 'metadata' },
{ name => 'lv-info' },
{ name => 'metadata' }, { name => 'lv-info' },
];
return $result;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'osddetails',
path => '{osdid}/metadata',
method => 'GET',
@ -639,7 +650,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Audit' ], any => 1],
check => ['perm', '/', ['Sys.Audit'], any => 1],
},
parameters => {
additionalProperties => 0,
@ -711,8 +722,8 @@ __PACKAGE__->register_method ({
type => "object",
properties => $OSD_DEV_RETURN_PROPS,
},
}
}
},
},
},
code => sub {
my ($param) = @_;
@ -735,13 +746,13 @@ __PACKAGE__->register_method ({
};
my $cmd = [
'/bin/systemctl',
'show',
"ceph-osd\@${osdid}.service",
'--property',
'MainPID',
'/bin/systemctl', 'show', "ceph-osd\@${osdid}.service", '--property', 'MainPID',
];
run_command($cmd, errmsg => 'fetching OSD PID and memory usage failed', outfunc => $parser);
run_command(
$cmd,
errmsg => 'fetching OSD PID and memory usage failed',
outfunc => $parser,
);
my $osd_pss_memory = eval { get_proc_pss_from_pid($pid) } // 0;
warn $@ if $@;
@ -766,8 +777,8 @@ __PACKAGE__->register_method ({
my $get_data = sub {
my ($dev, $prefix, $device) = @_;
push (
@{$data->{devices}},
push(
@{ $data->{devices} },
{
dev_node => $metadata->{"${prefix}_${dev}_dev_node"},
physical_device => $metadata->{"${prefix}_${dev}_devices"},
@ -775,7 +786,7 @@ __PACKAGE__->register_method ({
support_discard => int($metadata->{"${prefix}_${dev}_support_discard"}),
type => $metadata->{"${prefix}_${dev}_type"},
device => $device,
}
},
);
};
@ -784,9 +795,10 @@ __PACKAGE__->register_method ({
$get_data->("wal", "bluefs", "wal") if $metadata->{bluefs_dedicated_wal};
return $data;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'osdvolume',
path => '{osdid}/lv-info',
method => 'GET',
@ -794,7 +806,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Audit' ], any => 1],
check => ['perm', '/', ['Sys.Audit'], any => 1],
},
parameters => {
additionalProperties => 0,
@ -863,11 +875,12 @@ __PACKAGE__->register_method ({
}
if (!$result->{$osdid}) {
die "OSD '${osdid}' not found in 'ceph-volume lvm list' on node '${nodename}'.\n"
."Maybe it was created before LVM became the default?\n";
. "Maybe it was created before LVM became the default?\n";
}
my $lv_data = { map { $_->{type} => $_ } @{$result->{$osdid}} };
my $volume = $lv_data->{$type} || die "volume type '${type}' not found for OSD ${osdid}\n";
my $lv_data = { map { $_->{type} => $_ } @{ $result->{$osdid} } };
my $volume = $lv_data->{$type}
|| die "volume type '${type}' not found for OSD ${osdid}\n";
$raw = '';
$cmd = ['/sbin/lvs', $volume->{lv_path}, '--reportformat', 'json', '-o', 'lv_time'];
@ -882,10 +895,11 @@ __PACKAGE__->register_method ({
my $data = { map { $_ => $volume->{$_} } qw(lv_name lv_path lv_uuid vg_name) };
$data->{lv_size} = int($volume->{lv_size});
$data->{creation_time} = @{$result->{report}}[0]->{lv}[0]->{lv_time};
$data->{creation_time} = @{ $result->{report} }[0]->{lv}->[0]->{lv_time};
return $data;
}});
},
});
# Check if $osdid belongs to $nodename
# $tree ... rados osd tree (passing the tree makes it easy to test)
@ -894,7 +908,7 @@ sub osd_belongs_to_node {
return 0 if !($tree && $tree->{nodes});
my $node_map = {};
for my $el (grep { defined($_->{type}) && $_->{type} eq 'host' } @{$tree->{nodes}}) {
for my $el (grep { defined($_->{type}) && $_->{type} eq 'host' } @{ $tree->{nodes} }) {
my $name = $el->{name};
die "internal error: duplicate host name found '$name'\n" if $node_map->{$name};
$node_map->{$name} = $el;
@ -906,7 +920,7 @@ sub osd_belongs_to_node {
return grep($_ == $osdid, @$osds);
}
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'destroyosd',
path => '{osdid}',
method => 'DELETE',
@ -966,7 +980,8 @@ __PACKAGE__->register_method ({
my $upid = shift;
# reopen with longer timeout
$rados = PVE::RADOS->new(timeout => PVE::Ceph::Tools::get_config('long_rados_timeout'));
$rados =
PVE::RADOS->new(timeout => PVE::Ceph::Tools::get_config('long_rados_timeout'));
print "destroy OSD $osdsection\n";
@ -977,17 +992,32 @@ __PACKAGE__->register_method ({
warn $@ if $@;
print "Remove $osdsection from the CRUSH map\n";
$rados->mon_command({ prefix => "osd crush remove", name => $osdsection, format => 'plain' });
$rados->mon_command(
{ prefix => "osd crush remove", name => $osdsection, format => 'plain' });
print "Remove the $osdsection authentication key.\n";
$rados->mon_command({ prefix => "auth del", entity => $osdsection, format => 'plain' });
$rados->mon_command({
prefix => "auth del", entity => $osdsection, format => 'plain' });
print "Remove OSD $osdsection\n";
$rados->mon_command({ prefix => "osd rm", ids => [ $osdsection ], format => 'plain' });
$rados->mon_command({
prefix => "osd rm", ids => [$osdsection], format => 'plain' });
print "Remove $osdsection mclock max capacity iops settings from config\n";
$rados->mon_command({ prefix => "config rm", who => $osdsection, name => 'osd_mclock_max_capacity_iops_ssd' });
$rados->mon_command({ prefix => "config rm", who => $osdsection, name => 'osd_mclock_max_capacity_iops_hdd' });
$rados->mon_command(
{
prefix => "config rm",
who => $osdsection,
name => 'osd_mclock_max_capacity_iops_ssd',
},
);
$rados->mon_command(
{
prefix => "config rm",
who => $osdsection,
name => 'osd_mclock_max_capacity_iops_hdd',
},
);
# try to unmount from standard mount point
my $mountpoint = "/var/lib/ceph/osd/ceph-$osdid";
@ -998,7 +1028,7 @@ __PACKAGE__->register_method ({
my $remove_partition = sub {
my ($part) = @_;
return if !$part || (! -b $part );
return if !$part || (!-b $part);
my $partnum = PVE::Diskmanage::get_partnum($part);
my $devpath = PVE::Diskmanage::get_blockdev($part);
@ -1019,11 +1049,13 @@ __PACKAGE__->register_method ({
if ($cleanup) {
# try to remove pvs, but do not fail if it does not work
for my $osd_part (@{$osd_list->{$osdid}}) {
for my $dev (@{$osd_part->{devices}}) {
for my $osd_part (@{ $osd_list->{$osdid} }) {
for my $dev (@{ $osd_part->{devices} }) {
($dev) = ($dev =~ m|^(/dev/[-_.a-zA-Z0-9\/]+)$|); #untaint
eval { run_command(['/sbin/pvremove', $dev], errfunc => sub {}) };
eval {
run_command(['/sbin/pvremove', $dev], errfunc => sub { });
};
warn $@ if $@;
$udev_trigger_devs->{$dev} = 1;
@ -1048,7 +1080,8 @@ __PACKAGE__->register_method ({
}
foreach my $path (qw(journal block block.db block.wal)) {
abs_path("$mountpoint/$path") =~ m|^(/.+)| or die "invalid path: $path\n";
abs_path("$mountpoint/$path") =~ m|^(/.+)|
or die "invalid path: $path\n";
push @$partitions_to_remove, $1;
}
}
@ -1075,9 +1108,10 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('cephdestroyosd', $osdsection, $authuser, $worker);
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'in',
path => '{osdid}/in',
method => 'POST',
@ -1085,7 +1119,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -1111,12 +1145,13 @@ __PACKAGE__->register_method ({
my $osdsection = "osd.$osdid";
$rados->mon_command({ prefix => "osd in", ids => [ $osdsection ], format => 'plain' });
$rados->mon_command({ prefix => "osd in", ids => [$osdsection], format => 'plain' });
return undef;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'out',
path => '{osdid}/out',
method => 'POST',
@ -1124,7 +1159,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -1150,12 +1185,13 @@ __PACKAGE__->register_method ({
my $osdsection = "osd.$osdid";
$rados->mon_command({ prefix => "osd out", ids => [ $osdsection ], format => 'plain' });
$rados->mon_command({ prefix => "osd out", ids => [$osdsection], format => 'plain' });
return undef;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'scrub',
path => '{osdid}/scrub',
method => 'POST',
@ -1163,7 +1199,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -1198,6 +1234,7 @@ __PACKAGE__->register_method ({
$rados->mon_command({ prefix => $prefix, who => $osdid });
return undef;
}});
},
});
1;

View file

@ -21,27 +21,26 @@ my $get_autoscale_status = sub {
$rados = PVE::RADOS->new() if !defined($rados);
my $autoscale = $rados->mon_command({
prefix => 'osd pool autoscale-status'});
my $autoscale = $rados->mon_command({ prefix => 'osd pool autoscale-status' });
my $data;
foreach my $p (@$autoscale) {
$data->{$p->{pool_name}} = $p;
$data->{ $p->{pool_name} } = $p;
}
return $data;
};
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'lspools',
path => '',
method => 'GET',
description => "List all pools and their settings (which are settable by the POST/PUT endpoints).",
description =>
"List all pools and their settings (which are settable by the POST/PUT endpoints).",
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
},
parameters => {
additionalProperties => 0,
@ -132,7 +131,7 @@ __PACKAGE__->register_method ({
},
},
},
links => [ { rel => 'child', href => "{pool_name}" } ],
links => [{ rel => 'child', href => "{pool_name}" }],
},
code => sub {
my ($param) = @_;
@ -144,18 +143,18 @@ __PACKAGE__->register_method ({
my $stats = {};
my $res = $rados->mon_command({ prefix => 'df' });
foreach my $d (@{$res->{pools}}) {
foreach my $d (@{ $res->{pools} }) {
next if !$d->{stats};
next if !defined($d->{id});
$stats->{$d->{id}} = $d->{stats};
$stats->{ $d->{id} } = $d->{stats};
}
$res = $rados->mon_command({ prefix => 'osd dump' });
my $rulestmp = $rados->mon_command({ prefix => 'osd crush rule dump'});
my $rulestmp = $rados->mon_command({ prefix => 'osd crush rule dump' });
my $rules = {};
for my $rule (@$rulestmp) {
$rules->{$rule->{rule_id}} = $rule->{rule_name};
$rules->{ $rule->{rule_id} } = $rule->{rule_name};
}
my $data = [];
@ -173,14 +172,14 @@ __PACKAGE__->register_method ({
# pg_autoscaler module is not enabled in Nautilus
my $autoscale = eval { $get_autoscale_status->($rados) };
foreach my $e (@{$res->{pools}}) {
foreach my $e (@{ $res->{pools} }) {
my $d = {};
foreach my $attr (@$attr_list) {
$d->{$attr} = $e->{$attr} if defined($e->{$attr});
}
if ($autoscale) {
$d->{autoscale_status} = $autoscale->{$d->{pool_name}};
$d->{autoscale_status} = $autoscale->{ $d->{pool_name} };
$d->{pg_num_final} = $d->{autoscale_status}->{pg_num_final};
# some info is nested under options instead
$d->{pg_num_min} = $e->{options}->{pg_num_min};
@ -188,11 +187,11 @@ __PACKAGE__->register_method ({
$d->{target_size_ratio} = $e->{options}->{target_size_ratio};
}
if (defined($d->{crush_rule}) && defined($rules->{$d->{crush_rule}})) {
$d->{crush_rule_name} = $rules->{$d->{crush_rule}};
if (defined($d->{crush_rule}) && defined($rules->{ $d->{crush_rule} })) {
$d->{crush_rule_name} = $rules->{ $d->{crush_rule} };
}
if (my $s = $stats->{$d->{pool}}) {
if (my $s = $stats->{ $d->{pool} }) {
$d->{bytes_used} = $s->{bytes_used};
$d->{percent_used} = $s->{percent_used};
}
@ -210,10 +209,9 @@ __PACKAGE__->register_method ({
push @$data, $d;
}
return $data;
}});
},
});
my $ceph_pool_common_options = sub {
my ($nodefault) = shift;
@ -301,7 +299,6 @@ my $ceph_pool_common_options = sub {
return $options;
};
my $add_storage = sub {
my ($pool, $storeid, $ec_data_pool) = @_;
@ -330,8 +327,8 @@ my $get_storages = sub {
next if $curr->{type} ne 'rbd';
$curr->{pool} = 'rbd' if !defined $curr->{pool}; # set default
if (
$pool eq $curr->{pool} ||
(defined $curr->{'data-pool'} && $pool eq $curr->{'data-pool'})
$pool eq $curr->{pool}
|| (defined $curr->{'data-pool'} && $pool eq $curr->{'data-pool'})
) {
$res->{$storeid} = $storages->{$storeid};
}
@ -344,19 +341,19 @@ my $ec_format = {
k => {
type => 'integer',
description => "Number of data chunks. Will create an erasure coded pool plus a"
." replicated pool for metadata.",
. " replicated pool for metadata.",
minimum => 2,
},
m => {
type => 'integer',
description => "Number of coding chunks. Will create an erasure coded pool plus a"
." replicated pool for metadata.",
. " replicated pool for metadata.",
minimum => 1,
},
'failure-domain' => {
type => 'string',
description => "CRUSH failure domain. Default is 'host'. Will create an erasure"
." coded pool plus a replicated pool for metadata.",
. " coded pool plus a replicated pool for metadata.",
format_description => 'domain',
optional => 1,
default => 'host',
@ -364,13 +361,13 @@ my $ec_format = {
'device-class' => {
type => 'string',
description => "CRUSH device class. Will create an erasure coded pool plus a"
." replicated pool for metadata.",
. " replicated pool for metadata.",
format_description => 'class',
optional => 1,
},
profile => {
description => "Override the erasure code (EC) profile to use. Will create an"
." erasure coded pool plus a replicated pool for metadata.",
. " erasure coded pool plus a replicated pool for metadata.",
type => 'string',
format_description => 'profile',
optional => 1,
@ -389,8 +386,7 @@ sub ec_parse_and_check {
return $ec;
}
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'createpool',
path => '',
method => 'POST',
@ -398,7 +394,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -412,8 +408,8 @@ __PACKAGE__->register_method ({
},
'erasure-coding' => {
description => "Create an erasure coded pool for RBD with an accompaning"
." replicated pool for metadata storage. With EC, the common ceph options 'size',"
." 'min_size' and 'crush_rule' parameters will be applied to the metadata pool.",
. " replicated pool for metadata storage. With EC, the common ceph options 'size',"
. " 'min_size' and 'crush_rule' parameters will be applied to the metadata pool.",
type => 'string',
format => $ec_format,
optional => 1,
@ -459,14 +455,15 @@ __PACKAGE__->register_method ({
my $worker = sub {
# reopen with longer timeout
$rados = PVE::RADOS->new(timeout => PVE::Ceph::Tools::get_config('long_rados_timeout'));
$rados =
PVE::RADOS->new(timeout => PVE::Ceph::Tools::get_config('long_rados_timeout'));
if ($ec) {
if (!$ec->{profile}) {
$ec->{profile} = PVE::Ceph::Tools::get_ecprofile_name($pool, $rados);
eval {
PVE::Ceph::Tools::create_ecprofile(
$ec->@{'profile', 'k', 'm', 'failure-domain', 'device-class'},
$ec->@{ 'profile', 'k', 'm', 'failure-domain', 'device-class' },
$rados,
);
};
@ -503,10 +500,10 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('cephcreatepool', $pool, $user, $worker);
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'destroypool',
path => '{name}',
method => 'DELETE',
@ -514,7 +511,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -537,7 +534,8 @@ __PACKAGE__->register_method ({
default => 0,
},
remove_ecprofile => {
description => "Remove the erasure code profile. Defaults to true, if applicable.",
description =>
"Remove the erasure code profile. Defaults to true, if applicable.",
type => 'boolean',
optional => 1,
default => 1,
@ -571,7 +569,7 @@ __PACKAGE__->register_method ({
print "checking storage '$storeid' for RBD images..\n";
my $res = PVE::Storage::vdisk_list($storagecfg, $storeid);
die "ceph pool '$pool' still in use by storage '$storeid'\n"
if @{$res->{$storeid}} != 0;
if @{ $res->{$storeid} } != 0;
}
}
my $rados = PVE::RADOS->new();
@ -598,7 +596,7 @@ __PACKAGE__->register_method ({
foreach my $storeid (keys %$storages) {
# skip external clusters, not managed by pveceph
next if $storages->{$storeid}->{monhost};
eval { PVE::API2::Storage::Config->delete({storage => $storeid}) };
eval { PVE::API2::Storage::Config->delete({ storage => $storeid }) };
if ($@) {
warn "failed to remove storage '$storeid': $@\n";
$err = 1;
@ -609,10 +607,10 @@ __PACKAGE__->register_method ({
}
};
return $rpcenv->fork_worker('cephdestroypool', $pool, $user, $worker);
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'setpool',
path => '{name}',
method => 'PUT',
@ -620,7 +618,7 @@ __PACKAGE__->register_method ({
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
parameters => {
additionalProperties => 0,
@ -652,14 +650,15 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('cephsetpool', $pool, $authuser, $worker);
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'poolindex',
path => '{name}',
method => 'GET',
permissions => {
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1],
},
description => "Pool index.",
parameters => {
@ -678,7 +677,7 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
@ -688,10 +687,10 @@ __PACKAGE__->register_method ({
];
return $result;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'getpool',
path => '{name}/status',
method => 'GET',
@ -699,7 +698,7 @@ __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,
@ -713,8 +712,7 @@ __PACKAGE__->register_method ({
type => 'boolean',
default => 0,
optional => 1,
description => "If enabled, will display additional data".
"(eg. statistics).",
description => "If enabled, will display additional data" . "(eg. statistics).",
},
},
},
@ -734,7 +732,8 @@ __PACKAGE__->register_method ({
fast_read => { type => 'boolean', title => 'Fast Read' },
application_list => { type => 'array', title => 'Application', optional => 1 },
statistics => { type => 'object', title => 'Statistics', optional => 1 },
autoscale_status => { type => 'object', title => 'Autoscale Status', optional => 1 },
autoscale_status =>
{ type => 'object', title => 'Autoscale Status', optional => 1 },
%{ $ceph_pool_common_options->() },
},
},
@ -785,18 +784,19 @@ __PACKAGE__->register_method ({
my $autoscale_status = eval { $get_autoscale_status->() };
$data->{autoscale_status} = $autoscale_status->{$pool};
foreach my $d (@{$res->{pools}}) {
foreach my $d (@{ $res->{pools} }) {
next if !$d->{stats};
next if !defined($d->{name}) && !$d->{name} ne "$pool";
$data->{statistics} = $d->{stats};
}
my $apps = $rados->mon_command({ prefix => "osd pool application get", pool => "$pool", });
$data->{application_list} = [ keys %$apps ];
my $apps =
$rados->mon_command({ prefix => "osd pool application get", pool => "$pool" });
$data->{application_list} = [keys %$apps];
}
return $data;
}});
},
});
1;

View file

@ -5,20 +5,19 @@ 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',
@ -36,20 +35,18 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
return [
{ name => 'acme' },
{ name => 'custom' },
{ name => 'info' },
{ name => 'acme' }, { name => 'custom' }, { name => 'info' },
];
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'info',
path => 'info',
method => 'GET',
@ -73,9 +70,7 @@ __PACKAGE__->register_method ({
my $res = [];
my $cert_paths = [
'/etc/pve/pve-root-ca.pem',
"$node_path/pve-ssl.pem",
"$node_path/pveproxy-ssl.pem",
'/etc/pve/pve-root-ca.pem', "$node_path/pve-ssl.pem", "$node_path/pveproxy-ssl.pem",
];
for my $path (@$cert_paths) {
eval {
@ -87,12 +82,12 @@ __PACKAGE__->register_method ({
},
});
__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,
@ -140,15 +135,17 @@ __PACKAGE__->register_method ({
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";
raise_param_exc(
{ 'key' => "Attempted to upload custom certificate without (existing) key." })
if !-e "${cert_prefix}.key";
}
my $info;
my $code = sub {
print "Setting custom certificate files\n";
$info = PVE::CertHelpers::set_cert_files($certs, $key, $cert_prefix, $param->{force});
$info =
PVE::CertHelpers::set_cert_files($certs, $key, $cert_prefix, $param->{force});
if ($param->{restart}) {
print "Restarting pveproxy\n";
@ -160,14 +157,15 @@ __PACKAGE__->register_method ({
die "$@\n" if $@;
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,
@ -208,6 +206,7 @@ __PACKAGE__->register_method ({
die "$@\n" if $@;
return undef;
}});
},
});
1;

View file

@ -43,68 +43,68 @@ eval {
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::ReplicationConfig",
path => 'replication',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Cluster::MetricServer",
path => 'metrics',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Cluster::Notifications",
path => 'notifications',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::ClusterConfig",
path => 'config',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Firewall::Cluster",
path => 'firewall',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Backup",
path => 'backup',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Cluster::BackupInfo",
path => 'backup-info',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::HAConfig",
path => 'ha',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::ACMEAccount",
path => 'acme',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Cluster::Ceph",
path => 'ceph',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Cluster::Jobs",
path => 'jobs',
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Cluster::Mapping",
path => 'mapping',
});
if ($have_sdn) {
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::Network::SDN",
path => 'sdn',
});
@ -113,16 +113,17 @@ if ($have_sdn) {
my $dc_schema = PVE::DataCenterConfig::get_datacenter_schema();
my $dc_properties = {
delete => {
type => 'string', format => 'pve-configid-list',
type => 'string',
format => 'pve-configid-list',
description => "A list of settings you want to delete.",
optional => 1,
}
},
};
foreach my $opt (keys %{$dc_schema->{properties}}) {
foreach my $opt (keys %{ $dc_schema->{properties} }) {
$dc_properties->{$opt} = $dc_schema->{properties}->{$opt};
}
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '',
method => 'GET',
@ -138,7 +139,7 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
@ -169,7 +170,8 @@ __PACKAGE__->register_method ({
}
return $result;
}});
},
});
__PACKAGE__->register_method({
name => 'log',
@ -185,7 +187,7 @@ __PACKAGE__->register_method({
description => "Maximum number of entries.",
optional => 1,
minimum => 1,
}
},
},
},
returns => {
@ -203,18 +205,19 @@ __PACKAGE__->register_method({
my $max = $param->{max} || 0;
my $user = $rpcenv->get_user();
my $admin = $rpcenv->check($user, "/", [ 'Sys.Syslog' ], 1);
my $admin = $rpcenv->check($user, "/", ['Sys.Syslog'], 1);
my $loguser = $admin ? '' : $user;
my $res = decode_json(PVE::Cluster::get_cluster_log($loguser, $max));
foreach my $entry (@{$res->{data}}) {
foreach my $entry (@{ $res->{data} }) {
$entry->{id} = "$entry->{uid}:$entry->{node}";
}
return $res->{data};
}});
},
});
__PACKAGE__->register_method({
name => 'resources',
@ -257,15 +260,21 @@ __PACKAGE__->register_method({
type => 'string',
optional => 1,
},
node => get_standard_option('pve-node', {
node => get_standard_option(
'pve-node',
{
description => "The cluster node name"
." (for types 'node', 'storage', 'qemu', and 'lxc').",
. " (for types 'node', 'storage', 'qemu', and 'lxc').",
optional => 1,
}),
storage => get_standard_option('pve-storage-id', {
},
),
storage => get_standard_option(
'pve-storage-id',
{
description => "The storage identifier (for type 'storage').",
optional => 1,
}),
},
),
pool => {
description => "The pool name (for types 'pool', 'qemu' and 'lxc').",
type => 'string',
@ -279,7 +288,8 @@ __PACKAGE__->register_method({
renderer => 'fraction_as_percentage',
},
maxcpu => {
description => "Number of available CPUs (for types 'node', 'qemu' and 'lxc').",
description =>
"Number of available CPUs (for types 'node', 'qemu' and 'lxc').",
type => 'number',
optional => 1,
minimum => 0,
@ -293,21 +303,22 @@ __PACKAGE__->register_method({
},
maxmem => {
description => "Number of available memory in bytes"
." (for types 'node', 'qemu' and 'lxc').",
. " (for types 'node', 'qemu' and 'lxc').",
type => 'integer',
optional => 1,
renderer => 'bytes',
},
netin => {
description => "The amount of traffic in bytes that was sent to the guest over"
." the network since it was started. (for types 'qemu' and 'lxc')",
description =>
"The amount of traffic in bytes that was sent to the guest over"
. " the network since it was started. (for types 'qemu' and 'lxc')",
type => 'integer',
optional => 1,
renderer => 'bytes',
},
netout => {
description => "The amount of traffic in bytes that was sent from the guest"
." over the network since it was started. (for types 'qemu' and 'lxc')",
. " over the network since it was started. (for types 'qemu' and 'lxc')",
type => 'integer',
optional => 1,
renderer => 'bytes',
@ -318,13 +329,14 @@ __PACKAGE__->register_method({
optional => 1,
},
lock => {
description => "The guest's current config lock (for types 'qemu' and 'lxc')",
description =>
"The guest's current config lock (for types 'qemu' and 'lxc')",
type => 'string',
optional => 1,
},
uptime => {
description => "Uptime of node or virtual guest in seconds"
." (for types 'node', 'qemu' and 'lxc').",
. " (for types 'node', 'qemu' and 'lxc').",
type => 'integer',
optional => 1,
renderer => 'duration',
@ -336,7 +348,7 @@ __PACKAGE__->register_method({
},
disk => {
description => "Used disk space in bytes (for type 'storage'),"
." used root image space for VMs (for types 'qemu' and 'lxc').",
. " used root image space for VMs (for types 'qemu' and 'lxc').",
type => 'integer',
optional => 1,
renderer => 'bytes',
@ -344,24 +356,26 @@ __PACKAGE__->register_method({
},
maxdisk => {
description => "Storage size in bytes (for type 'storage'),"
." root image size for VMs (for types 'qemu' and 'lxc').",
. " root image size for VMs (for types 'qemu' and 'lxc').",
type => 'integer',
optional => 1,
renderer => 'bytes',
minimum => 0,
},
diskread => {
description => "The amount of bytes the guest read from its block devices since"
." the guest was started. This info is not available for all storage types."
." (for types 'qemu' and 'lxc')",
description =>
"The amount of bytes the guest read from its block devices since"
. " the guest was started. This info is not available for all storage types."
. " (for types 'qemu' and 'lxc')",
type => 'integer',
optional => 1,
renderer => 'bytes',
},
diskwrite => {
description => "The amount of bytes the guest wrote to its block devices since"
." the guest was started. This info is not available for all storage types."
." (for types 'qemu' and 'lxc')",
description =>
"The amount of bytes the guest wrote to its block devices since"
. " the guest was started. This info is not available for all storage types."
. " (for types 'qemu' and 'lxc')",
type => 'integer',
optional => 1,
renderer => 'bytes',
@ -377,10 +391,13 @@ __PACKAGE__->register_method({
type => 'string',
optional => 1,
},
vmid => get_standard_option('pve-vmid', {
vmid => get_standard_option(
'pve-vmid',
{
description => "The numerical vmid (for types 'qemu' and 'lxc').",
optional => 1,
}),
},
),
'cgroup-mode' => {
description => "The cgroup mode the node operates under (for type 'node').",
type => 'integer',
@ -393,7 +410,7 @@ __PACKAGE__->register_method({
},
template => {
description => "Determines if the guest is a template."
." (for types 'qemu' and 'lxc')",
. " (for types 'qemu' and 'lxc')",
type => 'boolean',
optional => 1,
default => 0,
@ -422,15 +439,15 @@ __PACKAGE__->register_method({
my $haresources = PVE::HA::Config::read_resources_config();
my $hatypemap = {
'qemu' => 'vm',
'lxc' => 'ct'
'lxc' => 'ct',
};
my $pooldata = {};
if (!$param->{type} || $param->{type} eq 'pool') {
for my $pool (sort keys %{$usercfg->{pools}}) {
for my $pool (sort keys %{ $usercfg->{pools} }) {
my $d = $usercfg->{pools}->{$pool};
next if !$rpcenv->check($authuser, "/pool/$pool", [ 'Pool.Audit' ], 1);
next if !$rpcenv->check($authuser, "/pool/$pool", ['Pool.Audit'], 1);
my $entry = {
id => "/pool/$pool",
@ -458,7 +475,8 @@ __PACKAGE__->register_method({
$entry->{pool} = $pool;
if (my $pe = $pooldata->{$pool}) {
if ($entry->{uptime}) {
$pe->{uptime} = $entry->{uptime} if !$pe->{uptime} || $entry->{uptime} > $pe->{uptime};
$pe->{uptime} = $entry->{uptime}
if !$pe->{uptime} || $entry->{uptime} > $pe->{uptime};
$pe->{mem} = 0 if !$pe->{mem};
$pe->{mem} += $entry->{mem};
$pe->{maxmem} = 0 if !$pe->{maxmem};
@ -470,14 +488,16 @@ __PACKAGE__->register_method({
# so we calculate the current % of the cpu
# but we had already the old cpu % before this vm, so:
# new% = (old%*oldmax + cur%*curmax) / (oldmax+curmax)
$pe->{cpu} = (($pe->{cpu} * $pe->{maxcpu}) + ($entry->{cpu} * $entry->{maxcpu})) / ($pe->{maxcpu} + $entry->{maxcpu});
$pe->{cpu} =
(($pe->{cpu} * $pe->{maxcpu}) + ($entry->{cpu} * $entry->{maxcpu}))
/ ($pe->{maxcpu} + $entry->{maxcpu});
$pe->{maxcpu} += $entry->{maxcpu};
}
}
}
# only skip now to next to ensure that the pool stats above are filled, if eligible
next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
next if !$rpcenv->check($authuser, "/vms/$vmid", ['VM.Audit'], 1);
for my $prop (@$prop_list) {
if (defined(my $value = $props->{$vmid}->{$prop})) {
@ -485,13 +505,15 @@ __PACKAGE__->register_method({
}
}
if (defined($entry->{pool}) &&
!$rpcenv->check($authuser, "/pool/$entry->{pool}", ['Pool.Audit'], 1)) {
if (
defined($entry->{pool})
&& !$rpcenv->check($authuser, "/pool/$entry->{pool}", ['Pool.Audit'], 1)
) {
delete $entry->{pool};
}
# get ha status
if (my $hatype = $hatypemap->{$entry->{type}}) {
if (my $hatype = $hatypemap->{ $entry->{type} }) {
my $sid = "$hatype:$vmid";
my $service;
if ($service = $hastatus->{service_status}->{$sid}) {
@ -509,8 +531,9 @@ __PACKAGE__->register_method({
if (!$param->{type} || $param->{type} eq 'node') {
foreach my $node (@$nodelist) {
my $can_audit = $rpcenv->check($authuser, "/nodes/$node", [ 'Sys.Audit' ], 1);
my $entry = PVE::API2Tools::extract_node_stats($node, $members, $rrd, !$can_audit);
my $can_audit = $rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'], 1);
my $entry =
PVE::API2Tools::extract_node_stats($node, $members, $rrd, !$can_audit);
my $info = eval { decode_json($static_node_info->{$node}); };
if (defined(my $mode = $info->{'cgroup-mode'})) {
@ -527,17 +550,18 @@ __PACKAGE__->register_method({
if (!$param->{type} || $param->{type} eq 'storage') {
my $cfg = PVE::Storage::config();
my @sids = PVE::Storage::storage_ids ($cfg);
my @sids = PVE::Storage::storage_ids($cfg);
foreach my $storeid (@sids) {
next if !$rpcenv->check($authuser, "/storage/$storeid", [ 'Datastore.Audit' ], 1);
next if !$rpcenv->check($authuser, "/storage/$storeid", ['Datastore.Audit'], 1);
my $scfg = PVE::Storage::storage_config($cfg, $storeid);
# we create a entry for each node
foreach my $node (@$nodelist) {
next if !PVE::Storage::storage_check_enabled($cfg, $storeid, $node, 1);
my $entry = PVE::API2Tools::extract_storage_stats($storeid, $scfg, $node, $rrd);
my $entry =
PVE::API2Tools::extract_storage_stats($storeid, $scfg, $node, $rrd);
push @$res, $entry;
}
}
@ -545,7 +569,7 @@ __PACKAGE__->register_method({
if (!$param->{type} || $param->{type} eq 'sdn') {
#add default "localnetwork" zone
if ($rpcenv->check($authuser, "/sdn/zones/localnetwork", [ 'SDN.Audit' ], 1)) {
if ($rpcenv->check($authuser, "/sdn/zones/localnetwork", ['SDN.Audit'], 1)) {
foreach my $node (@$nodelist) {
my $local_sdn = {
id => "sdn/$node/localnetwork",
@ -565,7 +589,7 @@ __PACKAGE__->register_method({
my $sdns = decode_json($nodes->{$node});
for my $id (sort keys %{$sdns}) {
next if !$rpcenv->check($authuser, "/sdn/zones/$id", [ 'SDN.Audit' ], 1);
next if !$rpcenv->check($authuser, "/sdn/zones/$id", ['SDN.Audit'], 1);
my $sdn = $sdns->{$id};
my $entry = {
id => "sdn/$node/$id",
@ -581,7 +605,8 @@ __PACKAGE__->register_method({
}
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'tasks',
@ -611,27 +636,30 @@ __PACKAGE__->register_method({
my $tlist = PVE::Cluster::get_tasklist();
return [] if !$tlist;
my $all = $rpcenv->check($authuser, "/", [ 'Sys.Audit' ], 1);
my $all = $rpcenv->check($authuser, "/", ['Sys.Audit'], 1);
my $res = [];
foreach my $task (@$tlist) {
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});
}
push @$res, $task if $all || ($task->{user} eq $authuser);
}
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'get_options',
path => 'options',
method => 'GET',
description => "Get datacenter options. Without 'Sys.Audit' on '/' not all options are returned.",
description =>
"Get datacenter options. Without 'Sys.Audit' on '/' not all options are returned.",
permissions => {
user => 'all',
check => ['perm', '/', [ 'Sys.Audit' ]],
check => ['perm', '/', ['Sys.Audit']],
},
parameters => {
additionalProperties => 0,
@ -663,7 +691,8 @@ __PACKAGE__->register_method({
$res->{'allowed-tags'} = [sort keys $tags->%*];
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'set_options',
@ -671,7 +700,7 @@ __PACKAGE__->register_method({
method => 'PUT',
description => "Set datacenter options.",
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
protected => 1,
parameters => {
@ -684,7 +713,10 @@ __PACKAGE__->register_method({
my $delete = extract_param($param, 'delete');
cfs_lock_file('datacenter.cfg', undef, sub {
cfs_lock_file(
'datacenter.cfg',
undef,
sub {
my $conf = cfs_read_file('datacenter.cfg');
$conf->{$_} = $param->{$_} for keys $param->%*;
@ -692,11 +724,13 @@ __PACKAGE__->register_method({
delete $conf->{$_} for PVE::Tools::split_list($delete);
cfs_write_file('datacenter.cfg', $conf);
});
},
);
die $@ if $@;
return undef;
}});
},
});
__PACKAGE__->register_method({
name => 'get_status',
@ -704,7 +738,7 @@ __PACKAGE__->register_method({
method => 'GET',
description => "Get cluster status information.",
permissions => {
check => ['perm', '/', [ 'Sys.Audit' ]],
check => ['perm', '/', ['Sys.Audit']],
},
protected => 1,
parameters => {
@ -719,7 +753,8 @@ __PACKAGE__->register_method({
type => {
type => 'string',
enum => ['cluster', 'node'],
description => 'Indicates the type, either cluster or node. The type defines the object properties e.g. quorate available for type cluster.'
description =>
'Indicates the type, either cluster or node. The type defines the object properties e.g. quorate available for type cluster.',
},
id => {
type => 'string',
@ -735,12 +770,14 @@ __PACKAGE__->register_method({
version => {
type => 'integer',
optional => 1,
description => '[cluster] Current version of the corosync configuration file.',
description =>
'[cluster] Current version of the corosync configuration file.',
},
quorate => {
type => 'boolean',
optional => 1,
description => '[cluster] Indicates if there is a majority of nodes online to make decisions',
description =>
'[cluster] Indicates if there is a majority of nodes online to make decisions',
},
nodeid => {
type => 'integer',
@ -765,8 +802,9 @@ __PACKAGE__->register_method({
level => {
type => 'string',
optional => 1,
description => '[node] Proxmox VE Subscription level, indicates if eligible for enterprise support as well as access to the stable Proxmox VE Enterprise Repository.',
}
description =>
'[node] Proxmox VE Subscription level, indicates if eligible for enterprise support as well as access to the stable Proxmox VE Enterprise Repository.',
},
},
},
},
@ -786,7 +824,8 @@ __PACKAGE__->register_method({
my $res = [];
if (my $d = $clinfo->{cluster}) {
push @$res, {
push @$res,
{
type => 'cluster',
id => 'cluster',
nodes => $d->{nodes},
@ -836,13 +875,15 @@ __PACKAGE__->register_method({
level => $sublevel,
}];
}
}});
},
});
__PACKAGE__->register_method({
name => 'nextid',
path => 'nextid',
method => 'GET',
description => "Get next free VMID. Pass a VMID to assert that its free (at time of check).",
description =>
"Get next free VMID. Pass a VMID to assert that its free (at time of check).",
permissions => { user => 'all' },
parameters => {
additionalProperties => 0,
@ -878,6 +919,7 @@ __PACKAGE__->register_method({
}
die "unable to get any free VMID in range [$lower, $upper]\n";
}});
},
});
1;

View file

@ -53,13 +53,14 @@ __PACKAGE__->register_method({
},
},
},
links => [ { rel => 'child', href => "{subdir}" } ],
links => [{ rel => 'child', href => "{subdir}" }],
},
code => sub {
return [
{ subdir => 'not-backed-up' },
];
}});
},
});
__PACKAGE__->register_method({
name => 'get_guests_not_in_backup',
@ -105,13 +106,14 @@ __PACKAGE__->register_method({
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}->%*;
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 ($type, $node) = $vmlist->{ids}->{$vmid}->@{'type', 'node'};
my ($type, $node) = $vmlist->{ids}->{$vmid}->@{ 'type', 'node' };
my ($conf, $name);
if ($type eq 'qemu') {
@ -121,7 +123,8 @@ __PACKAGE__->register_method({
$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";
die
"Unexpected error: unknown guest type for VMID $vmid, neither QEMU nor LXC\n";
}
my $entry = {
@ -134,6 +137,7 @@ __PACKAGE__->register_method({
}
return $result;
}});
},
});
1;

View 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;

View file

@ -17,7 +17,7 @@ use PVE::Tools qw(extract_param);
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'cephindex',
path => '',
method => 'GET',
@ -33,19 +33,17 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
my $result = [
{ name => 'metadata' },
{ name => 'status' },
{ name => 'flags' },
{ name => 'metadata' }, { name => 'status' }, { name => 'flags' },
];
return $result;
}
},
});
my $metadata_common_props = {
@ -75,14 +73,14 @@ my $metadata_common_props = {
},
};
__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,
@ -91,7 +89,7 @@ __PACKAGE__->register_method ({
type => 'string',
optional => 1,
default => 'all',
enum => ['all', 'versions', ],
enum => ['all', 'versions'],
},
},
},
@ -101,7 +99,8 @@ __PACKAGE__->register_method ({
properties => {
mds => {
type => "object",
description => "Metadata servers configured in the cluster and their properties.",
description =>
"Metadata servers configured in the cluster and their properties.",
properties => {
"{id}" => {
type => "object",
@ -205,11 +204,13 @@ __PACKAGE__->register_method ({
},
front_addr => {
type => "string",
description => "Bind addresses and ports for frontend traffic to OSDs.",
description =>
"Bind addresses and ports for frontend traffic to OSDs.",
},
back_addr => {
type => "string",
description => "Bind addresses and ports for backend inter OSD traffic.",
description =>
"Bind addresses and ports for backend inter OSD traffic.",
},
device_id => {
type => "string",
@ -247,18 +248,18 @@ __PACKAGE__->register_method ({
PVE::Ceph::Tools::check_ceph_inited();
my $rados = PVE::RADOS->new();
for my $type ( qw(mon mgr mds) ) {
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}}) {
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 ) {
for my $service (@$services) {
my $hostname = $service->{hostname};
next if !defined($hostname); # can happen if node is dead
@ -266,7 +267,7 @@ __PACKAGE__->register_method ({
my $id = "$servicename\@$hostname";
if ($data->{$id}) { # copy values over to the metadata hash
for my $k (keys %{$data->{$id}}) {
for my $k (keys %{ $data->{$id} }) {
$service->{$k} = $data->{$id}->{$k};
}
}
@ -279,21 +280,21 @@ __PACKAGE__->register_method ({
$res->{osd} = $rados->mon_command({ prefix => "osd metadata" });
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 => { },
properties => {},
},
returns => { type => 'object' },
code => sub {
@ -302,11 +303,11 @@ __PACKAGE__->register_method ({
PVE::Ceph::Tools::check_ceph_inited();
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,19 +319,18 @@ 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 => {
},
properties => {},
},
returns => {
type => 'array',
@ -340,7 +340,8 @@ __PACKAGE__->register_method ({
properties => {
name => {
description => "Flag name.",
type => 'string', enum => $possible_flags_list,
type => 'string',
enum => $possible_flags_list,
},
description => {
description => "Flag description.",
@ -352,7 +353,7 @@ __PACKAGE__->register_method ({
},
},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
@ -378,17 +379,17 @@ __PACKAGE__->register_method ({
}
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,
@ -418,7 +419,7 @@ __PACKAGE__->register_method ({
my $prefix = $val ? 'set' : 'unset';
eval {
print "$prefix $flag\n";
$rados->mon_command({ prefix => "osd $prefix", key => $flag, });
$rados->mon_command({ prefix => "osd $prefix", key => $flag });
};
if (my $err = $@) {
warn "error with $flag: '$err'\n";
@ -432,24 +433,25 @@ __PACKAGE__->register_method ({
};
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,
type => 'string',
enum => $possible_flags_list,
},
},
},
@ -469,16 +471,17 @@ __PACKAGE__->register_method ({
}
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,
@ -509,7 +512,7 @@ __PACKAGE__->register_method ({
});
return undef;
}});
},
});
1;

View file

@ -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',
});
@ -37,14 +37,14 @@ __PACKAGE__->register_method({
},
},
},
links => [ { rel => 'child', href => "{subdir}" } ],
links => [{ rel => 'child', href => "{subdir}" }],
},
code => sub {
return [
{ subdir => 'schedule-analyze' },
{ subdir => 'realm-sync' },
{ subdir => 'schedule-analyze' }, { subdir => 'realm-sync' },
];
}});
},
});
__PACKAGE__->register_method({
name => 'schedule-analyze',
@ -56,12 +56,15 @@ __PACKAGE__->register_method({
additionalProperties => 0,
properties => {
schedule => {
description => "Job schedule. The format is a subset of `systemd` calendar events.",
type => 'string', format => 'pve-calendar-event',
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.",
description =>
"UNIX timestamp to start the calculation from. Defaults to the current time.",
optional => 1,
type => 'integer',
},
@ -106,7 +109,8 @@ __PACKAGE__->register_method({
for (my $count = 0; $count < $iterations; $count++) {
my $next = PVE::CalendarEvent::compute_next_event($event, $starttime);
last if !defined($next);
push @$result, {
push @$result,
{
timestamp => $next,
utc => scalar(gmtime($next)),
};
@ -114,6 +118,7 @@ __PACKAGE__->register_method({
}
return $result;
}});
},
});
1;

View file

@ -9,22 +9,22 @@ 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',
@ -41,18 +41,17 @@ __PACKAGE__->register_method ({
items => {
type => "object",
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
my $result = [
{ name => 'dir' },
{ name => 'pci' },
{ name => 'usb' },
{ name => 'dir' }, { name => 'pci' }, { name => 'usb' },
];
return $result;
}});
},
});
1;

View file

@ -14,7 +14,7 @@ use PVE::Tools qw(extract_param);
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '',
method => 'GET',
@ -26,17 +26,20 @@ __PACKAGE__->register_method ({
description => "List directory mapping",
permissions => {
description => "Only lists entries where you have 'Mapping.Modify', 'Mapping.Use' or"
." 'Mapping.Audit' permissions on '/mapping/dir/<id>'.",
. " 'Mapping.Audit' permissions on '/mapping/dir/<id>'.",
user => 'all',
},
parameters => {
additionalProperties => 0,
properties => {
'check-node' => get_standard_option('pve-node', {
'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.",
. " correctness, and adds relevant diagnostics for the directory to the response.",
optional => 1,
}),
},
),
},
},
returns => {
@ -46,7 +49,7 @@ __PACKAGE__->register_method ({
properties => {
id => {
type => 'string',
description => "The logical ID of the mapping."
description => "The logical ID of the mapping.",
},
map => {
type => 'array',
@ -77,11 +80,11 @@ __PACKAGE__->register_method ({
description => "The message of the error",
},
},
}
},
},
},
links => [ { rel => 'child', href => "{id}" } ],
},
links => [{ rel => 'child', href => "{id}" }],
},
code => sub {
my ($param) = @_;
@ -101,7 +104,8 @@ __PACKAGE__->register_method ({
my $res = [];
for my $id (keys $cfg->{ids}->%*) {
next if !$rpcenv->check_any($authuser, "/mapping/dir/$id", $can_see_mapping_privs, 1);
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});
@ -110,9 +114,12 @@ __PACKAGE__->register_method ({
if (defined($check_node)) {
$entry->{checks} = [];
if (my $mappings = PVE::Mapping::Dir::get_node_mapping($cfg, $id, $check_node)) {
if (
my $mappings = PVE::Mapping::Dir::get_node_mapping($cfg, $id, $check_node)
) {
if (!scalar($mappings->@*)) {
push $entry->{checks}->@*, {
push $entry->{checks}->@*,
{
severity => 'warning',
message => "No mapping for node $check_node.",
};
@ -120,7 +127,8 @@ __PACKAGE__->register_method ({
for my $mapping ($mappings->@*) {
eval { PVE::Mapping::Dir::assert_valid($mapping) };
if (my $err = $@) {
push $entry->{checks}->@*, {
push $entry->{checks}->@*,
{
severity => 'error',
message => "Invalid configuration: $err",
};
@ -136,14 +144,15 @@ __PACKAGE__->register_method ({
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'get',
protected => 1,
path => '{id}',
method => 'GET',
description => "Get directory mapping.",
permissions => {
check =>['or',
check => [
'or',
['perm', '/mapping/dir/{id}', ['Mapping.Use']],
['perm', '/mapping/dir/{id}', ['Mapping.Modify']],
['perm', '/mapping/dir/{id}', ['Mapping.Audit']],
@ -156,7 +165,7 @@ __PACKAGE__->register_method ({
type => 'string',
format => 'pve-configid',
},
}
},
},
returns => { type => 'object' },
code => sub {
@ -173,9 +182,10 @@ __PACKAGE__->register_method ({
$data->{digest} = $cfg->{digest};
return $data;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'create',
protected => 1,
path => '',
@ -199,7 +209,8 @@ __PACKAGE__->register_method ({
my $map_list = $opts->{map};
PVE::Mapping::Dir::assert_valid_map_list($map_list);
PVE::Mapping::Dir::lock_dir_config(sub {
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});
@ -208,13 +219,15 @@ __PACKAGE__->register_method ({
PVE::Mapping::Dir::write_dir_config($cfg);
}, "create directory mapping failed");
},
"create directory mapping failed",
);
return;
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'update',
protected => 1,
path => '{id}',
@ -235,10 +248,11 @@ __PACKAGE__->register_method ({
my $id = extract_param($param, 'id');
if ($delete) {
$delete = [ PVE::Tools::split_list($delete) ];
$delete = [PVE::Tools::split_list($delete)];
}
PVE::Mapping::Dir::lock_dir_config(sub {
PVE::Mapping::Dir::lock_dir_config(
sub {
my $cfg = PVE::Mapping::Dir::config();
PVE::Tools::assert_if_modified($cfg->{digest}, $digest) if defined($digest);
@ -260,20 +274,22 @@ __PACKAGE__->register_method ({
PVE::Mapping::Dir::write_dir_config($cfg);
}, "update directory mapping failed");
},
"update directory mapping failed",
);
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,
@ -282,7 +298,7 @@ __PACKAGE__->register_method ({
type => 'string',
format => 'pve-configid',
},
}
},
},
returns => { type => 'null' },
code => sub {
@ -290,7 +306,8 @@ __PACKAGE__->register_method ({
my $id = $param->{id};
PVE::Mapping::Dir::lock_dir_config(sub {
PVE::Mapping::Dir::lock_dir_config(
sub {
my $cfg = PVE::Mapping::Dir::config();
if ($cfg->{ids}->{$id}) {
@ -299,10 +316,12 @@ __PACKAGE__->register_method ({
PVE::Mapping::Dir::write_dir_config($cfg);
}, "delete dir mapping failed");
},
"delete dir mapping failed",
);
return;
}
},
});
1;

View file

@ -11,7 +11,7 @@ use PVE::Tools qw(extract_param);
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '',
method => 'GET',
@ -22,18 +22,21 @@ __PACKAGE__->register_method ({
},
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>'.",
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.",
'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 => {
@ -43,7 +46,7 @@ __PACKAGE__->register_method ({
properties => {
id => {
type => 'string',
description => "The logical ID of the mapping."
description => "The logical ID of the mapping.",
},
map => {
type => 'array',
@ -74,11 +77,11 @@ __PACKAGE__->register_method ({
description => "The message of the error",
},
},
}
},
},
},
links => [ { rel => 'child', href => "{id}" } ],
},
links => [{ rel => 'child', href => "{id}" }],
},
code => sub {
my ($param) = @_;
@ -98,7 +101,8 @@ __PACKAGE__->register_method ({
my $res = [];
for my $id (keys $cfg->{ids}->%*) {
next if !$rpcenv->check_any($authuser, "/mapping/pci/$id", $can_see_mapping_privs, 1);
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});
@ -107,9 +111,12 @@ __PACKAGE__->register_method ({
if (defined($check_node)) {
$entry->{checks} = [];
if (my $mappings = PVE::Mapping::PCI::get_node_mapping($cfg, $id, $check_node)) {
if (
my $mappings = PVE::Mapping::PCI::get_node_mapping($cfg, $id, $check_node)
) {
if (!scalar($mappings->@*)) {
push $entry->{checks}->@*, {
push $entry->{checks}->@*,
{
severity => 'warning',
message => "No mapping for node $check_node.",
};
@ -117,7 +124,8 @@ __PACKAGE__->register_method ({
for my $mapping ($mappings->@*) {
eval { PVE::Mapping::PCI::assert_valid($id, $mapping, $entry) };
if (my $err = $@) {
push $entry->{checks}->@*, {
push $entry->{checks}->@*,
{
severity => 'error',
message => "Invalid configuration: $err",
};
@ -133,14 +141,15 @@ __PACKAGE__->register_method ({
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'get',
protected => 1,
path => '{id}',
method => 'GET',
description => "Get PCI Mapping.",
permissions => {
check =>['or',
check => [
'or',
['perm', '/mapping/pci/{id}', ['Mapping.Use']],
['perm', '/mapping/pci/{id}', ['Mapping.Modify']],
['perm', '/mapping/pci/{id}', ['Mapping.Audit']],
@ -153,7 +162,7 @@ __PACKAGE__->register_method ({
type => 'string',
format => 'pve-configid',
},
}
},
},
returns => { type => 'object' },
code => sub {
@ -170,9 +179,10 @@ __PACKAGE__->register_method ({
$data->{digest} = $cfg->{digest};
return $data;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'create',
protected => 1,
path => '',
@ -193,7 +203,8 @@ __PACKAGE__->register_method ({
my $plugin = PVE::Mapping::PCI->lookup('pci');
my $opts = $plugin->check_config($id, $param, 1, 1);
PVE::Mapping::PCI::lock_pci_config(sub {
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});
@ -202,13 +213,15 @@ __PACKAGE__->register_method ({
PVE::Mapping::PCI::write_pci_config($cfg);
}, "create hardware mapping failed");
},
"create hardware mapping failed",
);
return;
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'update',
protected => 1,
path => '{id}',
@ -229,10 +242,11 @@ __PACKAGE__->register_method ({
my $id = extract_param($param, 'id');
if ($delete) {
$delete = [ PVE::Tools::split_list($delete) ];
$delete = [PVE::Tools::split_list($delete)];
}
PVE::Mapping::PCI::lock_pci_config(sub {
PVE::Mapping::PCI::lock_pci_config(
sub {
my $cfg = PVE::Mapping::PCI::config();
PVE::Tools::assert_if_modified($cfg->{digest}, $digest) if defined($digest);
@ -251,20 +265,22 @@ __PACKAGE__->register_method ({
PVE::Mapping::PCI::write_pci_config($cfg);
}, "update hardware mapping failed");
},
"update hardware mapping failed",
);
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,
@ -273,7 +289,7 @@ __PACKAGE__->register_method ({
type => 'string',
format => 'pve-configid',
},
}
},
},
returns => { type => 'null' },
code => sub {
@ -281,7 +297,8 @@ __PACKAGE__->register_method ({
my $id = $param->{id};
PVE::Mapping::PCI::lock_pci_config(sub {
PVE::Mapping::PCI::lock_pci_config(
sub {
my $cfg = PVE::Mapping::PCI::config();
if ($cfg->{ids}->{$id}) {
@ -290,10 +307,12 @@ __PACKAGE__->register_method ({
PVE::Mapping::PCI::write_pci_config($cfg);
}, "delete pci mapping failed");
},
"delete pci mapping failed",
);
return;
}
},
});
1;

View file

@ -11,7 +11,7 @@ use PVE::Tools qw(extract_param);
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '',
method => 'GET',
@ -22,18 +22,21 @@ __PACKAGE__->register_method ({
},
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>'.",
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.",
'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 => {
@ -43,7 +46,7 @@ __PACKAGE__->register_method ({
properties => {
id => {
type => 'string',
description => "The logical ID of the mapping."
description => "The logical ID of the mapping.",
},
map => {
type => 'array',
@ -71,11 +74,11 @@ __PACKAGE__->register_method ({
description => "The message of the error",
},
},
}
},
},
},
links => [ { rel => 'child', href => "{id}" } ],
},
links => [{ rel => 'child', href => "{id}" }],
},
code => sub {
my ($param) = @_;
@ -105,17 +108,17 @@ __PACKAGE__->register_method ({
$entry->{errors} = [];
if (my $mappings = PVE::Mapping::USB::get_node_mapping($cfg, $id, $node)) {
if (!scalar($mappings->@*)) {
push $entry->{errors}->@*, {
push $entry->{errors}->@*,
{
severity => 'warning',
message => "No mapping for node $node.",
};
}
for my $mapping ($mappings->@*) {
eval {
PVE::Mapping::USB::assert_valid($id, $mapping);
};
eval { PVE::Mapping::USB::assert_valid($id, $mapping); };
if (my $err = $@) {
push $entry->{errors}->@*, {
push $entry->{errors}->@*,
{
severity => 'error',
message => "Invalid configuration: $err",
};
@ -131,14 +134,15 @@ __PACKAGE__->register_method ({
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'get',
protected => 1,
path => '{id}',
method => 'GET',
description => "Get USB Mapping.",
permissions => {
check =>['or',
check => [
'or',
['perm', '/mapping/usb/{id}', ['Mapping.Audit']],
['perm', '/mapping/usb/{id}', ['Mapping.Use']],
['perm', '/mapping/usb/{id}', ['Mapping.Modify']],
@ -151,7 +155,7 @@ __PACKAGE__->register_method ({
type => 'string',
format => 'pve-configid',
},
}
},
},
returns => { type => 'object' },
code => sub {
@ -168,9 +172,10 @@ __PACKAGE__->register_method ({
$data->{digest} = $cfg->{digest};
return $data;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'create',
protected => 1,
path => '',
@ -191,7 +196,8 @@ __PACKAGE__->register_method ({
my $plugin = PVE::Mapping::USB->lookup('usb');
my $opts = $plugin->check_config($id, $param, 1, 1);
PVE::Mapping::USB::lock_usb_config(sub {
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});
@ -200,13 +206,15 @@ __PACKAGE__->register_method ({
PVE::Mapping::USB::write_usb_config($cfg);
}, "create hardware mapping failed");
},
"create hardware mapping failed",
);
return;
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'update',
protected => 1,
path => '{id}',
@ -227,10 +235,11 @@ __PACKAGE__->register_method ({
my $id = extract_param($param, 'id');
if ($delete) {
$delete = [ PVE::Tools::split_list($delete) ];
$delete = [PVE::Tools::split_list($delete)];
}
PVE::Mapping::USB::lock_usb_config(sub {
PVE::Mapping::USB::lock_usb_config(
sub {
my $cfg = PVE::Mapping::USB::config();
PVE::Tools::assert_if_modified($cfg->{digest}, $digest) if defined($digest);
@ -249,20 +258,22 @@ __PACKAGE__->register_method ({
PVE::Mapping::USB::write_usb_config($cfg);
}, "update hardware mapping failed");
},
"update hardware mapping failed",
);
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,
@ -271,7 +282,7 @@ __PACKAGE__->register_method ({
type => 'string',
format => 'pve-configid',
},
}
},
},
returns => { type => 'null' },
code => sub {
@ -279,7 +290,8 @@ __PACKAGE__->register_method ({
my $id = $param->{id};
PVE::Mapping::USB::lock_usb_config(sub {
PVE::Mapping::USB::lock_usb_config(
sub {
my $cfg = PVE::Mapping::USB::config();
if ($cfg->{ids}->{$id}) {
@ -288,10 +300,12 @@ __PACKAGE__->register_method ({
PVE::Mapping::USB::write_usb_config($cfg);
}, "delete usb mapping failed");
},
"delete usb mapping failed",
);
return;
}
},
});
1;

View file

@ -16,7 +16,7 @@ use PVE::RESTHandler;
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '',
method => 'GET',
@ -32,7 +32,7 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
@ -42,10 +42,10 @@ __PACKAGE__->register_method ({
];
return $result;
}
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'server_index',
path => 'server',
method => 'GET',
@ -64,7 +64,7 @@ __PACKAGE__->register_method ({
properties => {
id => {
description => "The ID of the entry.",
type => 'string'
type => 'string',
},
disable => {
description => "Flag to disable the plugin.",
@ -84,7 +84,7 @@ __PACKAGE__->register_method ({
},
},
},
links => [ { rel => 'child', href => "{id}" } ],
links => [{ rel => 'child', href => "{id}" }],
},
code => sub {
my ($param) = @_;
@ -92,9 +92,10 @@ __PACKAGE__->register_method ({
my $res = [];
my $status_cfg = PVE::Cluster::cfs_read_file('status.cfg');
for my $id (sort keys %{$status_cfg->{ids}}) {
for my $id (sort keys %{ $status_cfg->{ids} }) {
my $plugin_config = $status_cfg->{ids}->{$id};
push @$res, {
push @$res,
{
id => $id,
disable => $plugin_config->{disable} // 0,
type => $plugin_config->{type},
@ -104,9 +105,10 @@ __PACKAGE__->register_method ({
}
return $res;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'read',
path => 'server/{id}',
method => 'GET',
@ -135,9 +137,10 @@ __PACKAGE__->register_method ({
}
return $status_cfg->{ids}->{$id};
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'create',
path => 'server/{id}',
protected => 1,
@ -157,7 +160,10 @@ __PACKAGE__->register_method ({
my $sensitive_params = extract_sensitive_params($param, ['token'], []);
PVE::Cluster::cfs_lock_file('status.cfg', undef, sub {
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"
@ -169,9 +175,7 @@ __PACKAGE__->register_method ({
$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) };
@ -180,14 +184,15 @@ __PACKAGE__->register_method ({
}
PVE::Cluster::cfs_write_file('status.cfg', $cfg);
});
},
);
die $@ if $@;
return;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'update',
protected => 1,
path => 'server/{id}',
@ -211,7 +216,10 @@ __PACKAGE__->register_method ({
my $sensitive_params = extract_sensitive_params($param, ['token'], $delete);
PVE::Cluster::cfs_lock_file('status.cfg', undef, sub {
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);
@ -227,7 +235,7 @@ __PACKAGE__->register_method ({
}
if ($delete) {
my $options = $plugin->private()->{options}->{$data->{type}};
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};
@ -244,13 +252,15 @@ __PACKAGE__->register_method ({
$plugin->test_connection($data, $id);
PVE::Cluster::cfs_write_file('status.cfg', $cfg);
});
},
);
die $@ if $@;
return;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'delete',
protected => 1,
path => 'server/{id}',
@ -266,13 +276,16 @@ __PACKAGE__->register_method ({
type => 'string',
format => 'pve-configid',
},
}
},
},
returns => { type => 'null' },
code => sub {
my ($param) = @_;
PVE::Cluster::cfs_lock_file('status.cfg', undef, sub {
PVE::Cluster::cfs_lock_file(
'status.cfg',
undef,
sub {
my $cfg = PVE::Cluster::cfs_read_file('status.cfg');
my $id = $param->{id};
@ -285,13 +298,15 @@ __PACKAGE__->register_method ({
delete $cfg->{ids}->{$id};
PVE::Cluster::cfs_write_file('status.cfg', $cfg);
});
},
);
die $@ if $@;
return;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'export',
path => 'export',
method => 'GET',
@ -331,7 +346,8 @@ __PACKAGE__->register_method ({
properties => {
data => {
type => 'array',
description => 'Array of system metrics. Metrics are sorted by their timestamp.',
description =>
'Array of system metrics. Metrics are sorted by their timestamp.',
items => {
type => 'object',
additionalProperties => 0,
@ -344,7 +360,7 @@ __PACKAGE__->register_method ({
type => 'string',
description => "Unique identifier for this metric object,"
. " for instance 'node/<nodename>' or"
. " 'qemu/<vmid>'."
. " 'qemu/<vmid>'.",
},
metric => {
type => 'string',
@ -358,13 +374,13 @@ __PACKAGE__->register_method ({
type => 'string',
description => 'Type of the metric.',
enum => [qw(gauge counter derive)],
}
}
},
},
},
},
}
},
},
code => sub {
my ($param) = @_;
@ -386,9 +402,9 @@ __PACKAGE__->register_method ({
} else {
$generations = 0;
};
}
my @metrics = @{PVE::PullMetric::get_local_metrics($generations)};
my @metrics = @{ PVE::PullMetric::get_local_metrics($generations) };
if (defined($start)) {
@metrics = grep {
@ -463,7 +479,7 @@ __PACKAGE__->register_method ({
}
}
my @sorted = sort {$a->{timestamp} <=> $b->{timestamp}} @metrics;
my @sorted = sort { $a->{timestamp} <=> $b->{timestamp} } @metrics;
return {
data => \@sorted,

File diff suppressed because it is too large Load diff

View file

@ -16,17 +16,17 @@ use PVE::API2::HA::Status;
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::HA::Resources",
path => 'resources',
});
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::HA::Groups",
path => 'groups',
});
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
subclass => "PVE::API2::HA::Status",
path => 'status',
});
@ -37,7 +37,7 @@ __PACKAGE__->register_method({
method => 'GET',
description => "Directory index.",
permissions => {
check => ['perm', '/', [ 'Sys.Audit' ]],
check => ['perm', '/', ['Sys.Audit']],
},
parameters => {
additionalProperties => 0,
@ -51,19 +51,17 @@ __PACKAGE__->register_method({
id => { type => 'string' },
},
},
links => [ { rel => 'child', href => "{id}" } ],
links => [{ rel => 'child', href => "{id}" }],
},
code => sub {
my ($param) = @_;
my $res = [
{ id => 'status' },
{ id => 'resources' },
{ id => 'groups' },
{ id => 'status' }, { id => 'resources' }, { id => 'groups' },
];
return $res;
}});
},
});
1;

View file

@ -11,17 +11,17 @@ 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',
@ -39,19 +39,18 @@ __PACKAGE__->register_method ({
type => 'array',
items => {
type => "object",
properties => { type => { type => 'string'} },
properties => { type => { type => 'string' } },
},
links => [ { rel => 'child', href => "{type}" } ],
links => [{ rel => 'child', href => "{type}" }],
},
code => sub {
my ($param) = @_;
my $res = [
{ type => 'pci' },
{ type => 'usb' },
{ type => 'pci' }, { type => 'usb' },
];
return $res;
}
},
});

View file

@ -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',
@ -32,20 +32,20 @@ __PACKAGE__->register_method ({
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).",
. " 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.",
. " information like vendor and device will be returned.",
},
},
},
returns => {
links => [ { rel => 'child', href => "{id}" } ],
links => [{ rel => 'child', href => "{id}" }],
type => 'array',
items => {
type => "object",
@ -94,14 +94,16 @@ __PACKAGE__->register_method ({
},
iommugroup => {
type => 'integer',
description => "The IOMMU group in which the device is in. If no IOMMU group is"
." detected, it is set to -1.",
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.",
description =>
"If set, marks that the device is capable of creating mediated"
. " devices.",
},
},
},
@ -129,9 +131,10 @@ __PACKAGE__->register_method ({
my $verbose = $param->{verbose} // 1;
return PVE::SysFSTools::lspci($filter, $verbose);
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'pci_index',
path => '{pci-id-or-mapping}',
method => 'GET',
@ -145,7 +148,8 @@ __PACKAGE__->register_method ({
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_-]+)',
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_-]+)',
},
},
},
@ -153,9 +157,9 @@ __PACKAGE__->register_method ({
type => 'array',
items => {
type => "object",
properties => { method => { type => 'string'} },
properties => { method => { type => 'string' } },
},
links => [ { rel => 'child', href => "{method}" } ],
links => [{ rel => 'child', href => "{method}" }],
},
code => sub {
my ($param) = @_;
@ -165,9 +169,10 @@ __PACKAGE__->register_method ({
];
return $res;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'mdevscan',
path => '{pci-id-or-mapping}/mdev',
method => 'GET',
@ -183,8 +188,9 @@ __PACKAGE__->register_method ({
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."
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.",
},
},
},
@ -203,7 +209,7 @@ __PACKAGE__->register_method ({
},
description => {
type => 'string',
description => "Additional description of the type."
description => "Additional description of the type.",
},
name => {
type => 'string',
@ -216,7 +222,9 @@ __PACKAGE__->register_method ({
code => sub {
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]$/) {
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'};
@ -240,4 +248,5 @@ __PACKAGE__->register_method ({
return [sort { $a->{type} cmp $b->{type} } values($types->%*)];
}
}});
},
});

View file

@ -30,18 +30,18 @@ __PACKAGE__->register_method({
items => {
type => "object",
properties => {
busnum => { type => 'integer'},
class => { type => 'integer'},
devnum => { type => 'integer'},
level => { type => 'integer'},
busnum => { type => 'integer' },
class => { type => 'integer' },
devnum => { type => 'integer' },
level => { type => 'integer' },
manufacturer => { type => 'string', optional => 1 },
port => { type => 'integer'},
prodid => { type => 'string'},
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'},
speed => { type => 'string' },
usbpath => { type => 'string', optional => 1 },
vendid => { type => 'string' },
},
},
},
@ -49,5 +49,5 @@ __PACKAGE__->register_method({
my ($param) = @_;
return PVE::SysFSTools::scan_usb();
}
},
});

View file

@ -35,10 +35,20 @@ my $bond_mode_enum = [
'balance-slb', # OVS
'lacp-balance-slb', # OVS
'lacp-balance-tcp', # OVS
];
];
my $network_type_enum = ['bridge', 'bond', 'eth', 'alias', 'vlan',
'OVSBridge', 'OVSBond', 'OVSPort', 'OVSIntPort', 'vnet'];
my $network_type_enum = [
'bridge',
'bond',
'eth',
'alias',
'vlan',
'OVSBridge',
'OVSBond',
'OVSPort',
'OVSIntPort',
'vnet',
];
my $confdesc = {
type => {
@ -67,19 +77,23 @@ my $confdesc = {
optional => 1,
},
bridge_vids => {
description => "Specify the allowed VLANs. For example: '2 4 100-200'. Only used if the bridge is VLAN aware.",
description =>
"Specify the allowed VLANs. For example: '2 4 100-200'. Only used if the bridge is VLAN aware.",
optional => 1,
type => 'string', format => 'pve-vlan-id-or-range-list',
type => 'string',
format => 'pve-vlan-id-or-range-list',
},
bridge_ports => {
description => "Specify the interfaces you want to add to your bridge.",
optional => 1,
type => 'string', format => 'pve-iface-list',
type => 'string',
format => 'pve-iface-list',
},
ovs_ports => {
description => "Specify the interfaces you want to add to your bridge.",
optional => 1,
type => 'string', format => 'pve-iface-list',
type => 'string',
format => 'pve-iface-list',
},
ovs_tag => {
description => "Specify a VLan tag (used by OVSPort, OVSIntPort, OVSBond)",
@ -95,40 +109,48 @@ my $confdesc = {
maxLength => 1024,
},
ovs_bridge => {
description => "The OVS bridge associated with a OVS port. This is required when you create an OVS port.",
description =>
"The OVS bridge associated with a OVS port. This is required when you create an OVS port.",
optional => 1,
type => 'string', format => 'pve-iface',
type => 'string',
format => 'pve-iface',
},
slaves => {
description => "Specify the interfaces used by the bonding device.",
optional => 1,
type => 'string', format => 'pve-iface-list',
type => 'string',
format => 'pve-iface-list',
},
ovs_bonds => {
description => "Specify the interfaces used by the bonding device.",
optional => 1,
type => 'string', format => 'pve-iface-list',
type => 'string',
format => 'pve-iface-list',
},
bond_mode => {
description => "Bonding mode.",
optional => 1,
type => 'string', enum => $bond_mode_enum,
type => 'string',
enum => $bond_mode_enum,
},
'bond-primary' => {
description => "Specify the primary interface for active-backup bond.",
optional => 1,
type => 'string', format => 'pve-iface',
type => 'string',
format => 'pve-iface',
},
bond_xmit_hash_policy => {
description => "Selects the transmit hash policy to use for slave selection in balance-xor and 802.3ad modes.",
description =>
"Selects the transmit hash policy to use for slave selection in balance-xor and 802.3ad modes.",
optional => 1,
type => 'string',
enum => ['layer2', 'layer2+3', 'layer3+4' ],
enum => ['layer2', 'layer2+3', 'layer3+4'],
},
'vlan-raw-device' => {
description => "Specify the raw interface for the vlan interface.",
optional => 1,
type => 'string', format => 'pve-iface',
type => 'string',
format => 'pve-iface',
},
'vlan-id' => {
description => "vlan-id for a custom named vlan interface (ifupdown2 only).",
@ -139,24 +161,28 @@ my $confdesc = {
},
gateway => {
description => 'Default gateway address.',
type => 'string', format => 'ipv4',
type => 'string',
format => 'ipv4',
optional => 1,
},
netmask => {
description => 'Network mask.',
type => 'string', format => 'ipv4mask',
type => 'string',
format => 'ipv4mask',
optional => 1,
requires => 'address',
},
address => {
description => 'IP address.',
type => 'string', format => 'ipv4',
type => 'string',
format => 'ipv4',
optional => 1,
requires => 'netmask',
},
cidr => {
description => 'IPv4 CIDR.',
type => 'string', format => 'CIDRv4',
type => 'string',
format => 'CIDRv4',
optional => 1,
},
mtu => {
@ -168,24 +194,29 @@ my $confdesc = {
},
gateway6 => {
description => 'Default ipv6 gateway address.',
type => 'string', format => 'ipv6',
type => 'string',
format => 'ipv6',
optional => 1,
},
netmask6 => {
description => 'Network mask.',
type => 'integer', minimum => 0, maximum => 128,
type => 'integer',
minimum => 0,
maximum => 128,
optional => 1,
requires => 'address6',
},
address6 => {
description => 'IP address.',
type => 'string', format => 'ipv6',
type => 'string',
format => 'ipv6',
optional => 1,
requires => 'netmask6',
},
cidr6 => {
description => 'IPv6 CIDR.',
type => 'string', format => 'CIDRv6',
type => 'string',
format => 'CIDRv6',
optional => 1,
},
};
@ -214,7 +245,7 @@ __PACKAGE__->register_method({
type => {
description => "Only list specific interface types.",
type => 'string',
enum => [ @$network_type_enum, 'any_bridge', 'any_local_bridge' ],
enum => [@$network_type_enum, 'any_bridge', 'any_local_bridge'],
optional => 1,
},
},
@ -343,7 +374,7 @@ __PACKAGE__->register_method({
},
}),
},
links => [ { rel => 'child', href => "{iface}" } ],
links => [{ rel => 'child', href => "{iface}" }],
},
code => sub {
my ($param) = @_;
@ -377,29 +408,34 @@ __PACKAGE__->register_method({
}
if (defined($vnets)) {
$ifaces->{$_} = $vnets->{$_} for keys $vnets->%*
$ifaces->{$_} = $vnets->{$_} for keys $vnets->%*;
}
}
#always check bridge access
my $can_access_vnet = sub {
return 1 if $authuser eq 'root@pam';
return 1 if $rpcenv->check_sdn_bridge($authuser, "localnetwork", $_[0], ['SDN.Audit', 'SDN.Use'], 1);
return 1
if $rpcenv->check_sdn_bridge(
$authuser, "localnetwork", $_[0], ['SDN.Audit', 'SDN.Use'], 1,
);
};
for my $k (sort keys $ifaces->%*) {
my $type = $ifaces->{$k}->{type};
delete $ifaces->{$k} if ($type eq 'bridge' || $type eq 'OVSBridge') && !$can_access_vnet->($k);
delete $ifaces->{$k}
if ($type eq 'bridge' || $type eq 'OVSBridge') && !$can_access_vnet->($k);
}
return PVE::RESTHandler::hash_to_array($ifaces, 'iface');
}});
},
});
__PACKAGE__->register_method({
name => 'revert_network_changes',
path => '',
method => 'DELETE',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
},
protected => 1,
description => "Revert network configuration changes.",
@ -417,7 +453,8 @@ __PACKAGE__->register_method({
unlink "/etc/network/interfaces.new";
return undef;
}});
},
});
my $check_duplicate = sub {
my ($config, $newiface, $key, $name) = @_;
@ -490,7 +527,8 @@ my $check_ipv6_settings = sub {
my $type = ($binip eq $binmask) ? 'ANYCAST' : Net::IP::ip_iptypev6($binip);
if (defined($type) && $type !~ /^(?:(?:GLOBAL|(?:UNIQUE|LINK)-LOCAL)-UNICAST)$/) {
raise_param_exc({ address => "$address with type '$type', cannot be used as host IPv6 address." });
raise_param_exc(
{ address => "$address with type '$type', cannot be used as host IPv6 address." });
}
};
@ -527,7 +565,7 @@ __PACKAGE__->register_method({
path => '',
method => 'POST',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
},
description => "Create network device configuration",
protected => 1,
@ -536,7 +574,8 @@ __PACKAGE__->register_method({
additionalProperties => 0,
properties => json_config_properties({
node => get_standard_option('pve-node'),
iface => get_standard_option('pve-iface')}),
iface => get_standard_option('pve-iface'),
}),
},
returns => { type => 'null' },
code => sub {
@ -575,8 +614,8 @@ __PACKAGE__->register_method({
$param->{method6} = $param->{address6} ? 'static' : 'manual';
if ($param->{type} =~ m/^OVS/) {
-x '/usr/bin/ovs-vsctl' ||
die "Open VSwitch is not installed (need package 'openvswitch-switch')\n";
-x '/usr/bin/ovs-vsctl'
|| die "Open VSwitch is not installed (need package 'openvswitch-switch')\n";
}
if ($param->{type} eq 'OVSIntPort' || $param->{type} eq 'OVSBond') {
@ -587,12 +626,15 @@ __PACKAGE__->register_method({
raise_param_exc({ ovs_bridge => "interface '$brname' is no OVS bridge" })
if $br->{type} ne 'OVSBridge';
my @ports = split (/\s+/, $br->{ovs_ports} || '');
my @ports = split(/\s+/, $br->{ovs_ports} || '');
$br->{ovs_ports} = join(' ', @ports, $iface)
if ! grep { $_ eq $iface } @ports;
if !grep { $_ eq $iface } @ports;
}
if ($param->{bridge_vids} && scalar(PVE::Tools::split_list($param->{bridge_vids}) == 0)) {
if (
$param->{bridge_vids}
&& scalar(PVE::Tools::split_list($param->{bridge_vids}) == 0)
) {
raise_param_exc({ bridge_vids => "VLAN list items are empty" });
}
# make sure the list is space separated! other separators will cause problems in the
@ -609,14 +651,15 @@ __PACKAGE__->register_method({
die $@ if $@;
return undef;
}});
},
});
__PACKAGE__->register_method({
name => 'update_network',
path => '{iface}',
method => 'PUT',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
},
description => "Update network device configuration",
protected => 1,
@ -627,10 +670,12 @@ __PACKAGE__->register_method({
node => get_standard_option('pve-node'),
iface => get_standard_option('pve-iface'),
delete => {
type => 'string', format => 'pve-configid-list',
type => 'string',
format => 'pve-configid-list',
description => "A list of settings you want to delete.",
optional => 1,
}}),
},
}),
},
returns => { type => 'null' },
code => sub {
@ -690,7 +735,10 @@ __PACKAGE__->register_method({
$ifaces->{$iface}->{$k} = $param->{$k};
}
if ($param->{bridge_vids} && scalar(PVE::Tools::split_list($param->{bridge_vids}) == 0)) {
if (
$param->{bridge_vids}
&& scalar(PVE::Tools::split_list($param->{bridge_vids}) == 0)
) {
raise_param_exc({ bridge_vids => "VLAN list items are empty" });
}
# make sure the list is space separated! other separators will cause problems in the
@ -705,14 +753,15 @@ __PACKAGE__->register_method({
die $@ if $@;
return undef;
}});
},
});
__PACKAGE__->register_method({
name => 'network_config',
path => '{iface}',
method => 'GET',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
check => ['perm', '/nodes/{node}', ['Sys.Audit']],
},
description => "Read network device configuration",
proxyto => 'node',
@ -741,10 +790,11 @@ __PACKAGE__->register_method({
my $ifaces = $config->{ifaces};
raise_param_exc({ iface => "interface does not exist" })
if !$ifaces->{$param->{iface}};
if !$ifaces->{ $param->{iface} };
return $ifaces->{$param->{iface}};
}});
return $ifaces->{ $param->{iface} };
},
});
sub ifupdown2_version {
my $v;
@ -756,11 +806,13 @@ sub ifupdown2_version {
return ($major * 100000 + $minor * 1000 + $extra * 10, $is_pve, $v);
}
sub assert_ifupdown2_installed {
die "you need ifupdown2 to reload network configuration\n" if ! -e '/usr/share/ifupdown2';
die "you need ifupdown2 to reload network configuration\n" if !-e '/usr/share/ifupdown2';
my ($v, $pve, $v_str) = ifupdown2_version();
die "incompatible 'ifupdown2' package version '$v_str'! Did you installed from Proxmox repositories?\n"
if $v < (1*100000 + 2*1000 + 8*10) || !$pve;
die
"incompatible 'ifupdown2' package version '$v_str'! Did you installed from Proxmox repositories?\n"
if $v < (1 * 100000 + 2 * 1000 + 8 * 10) || !$pve;
}
__PACKAGE__->register_method({
@ -768,7 +820,7 @@ __PACKAGE__->register_method({
path => '',
method => 'PUT',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
},
description => "Reload network configuration",
protected => 1,
@ -815,14 +867,15 @@ __PACKAGE__->register_method({
}
};
return $rpcenv->fork_worker('srvreload', 'networking', $authuser, $worker);
}});
},
});
__PACKAGE__->register_method({
name => 'delete_network',
path => '{iface}',
method => 'DELETE',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]],
check => ['perm', '/nodes/{node}', ['Sys.Modify']],
},
description => "Delete network device configuration",
protected => 1,
@ -843,14 +896,14 @@ __PACKAGE__->register_method({
my $ifaces = $config->{ifaces};
raise_param_exc({ iface => "interface does not exist" })
if !$ifaces->{$param->{iface}};
if !$ifaces->{ $param->{iface} };
my $d = $ifaces->{$param->{iface}};
my $d = $ifaces->{ $param->{iface} };
if ($d->{type} eq 'OVSIntPort' || $d->{type} eq 'OVSBond') {
if (my $brname = $d->{ovs_bridge}) {
if (my $br = $ifaces->{$brname}) {
if ($br->{ovs_ports}) {
my @ports = split (/\s+/, $br->{ovs_ports});
my @ports = split(/\s+/, $br->{ovs_ports});
my @new = grep { $_ ne $param->{iface} } @ports;
$br->{ovs_ports} = join(' ', @new);
}
@ -858,7 +911,7 @@ __PACKAGE__->register_method({
}
}
delete $ifaces->{$param->{iface}};
delete $ifaces->{ $param->{iface} };
PVE::INotify::write_file('interfaces', $config);
};
@ -867,4 +920,5 @@ __PACKAGE__->register_method({
die $@ if $@;
return undef;
}});
},
});

View file

@ -10,11 +10,12 @@ 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.',
description =>
'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
maxLength => 40,
optional => 1,
},
@ -22,7 +23,8 @@ my $node_config_return_properties = {
};
my $node_config_properties = {
delete => {
type => 'string', format => 'pve-configid-list',
type => 'string',
format => 'pve-configid-list',
description => "A list of settings you want to delete.",
optional => 1,
},
@ -36,7 +38,7 @@ __PACKAGE__->register_method({
method => 'GET',
description => "Get node configuration options.",
permissions => {
check => ['perm', '/', [ 'Sys.Audit' ]],
check => ['perm', '/', ['Sys.Audit']],
},
proxyto => 'node',
parameters => {
@ -61,13 +63,14 @@ __PACKAGE__->register_method({
my $config = PVE::NodeConfig::load_config($param->{node});
if (defined (my $prop = $param->{property})) {
if (defined(my $prop = $param->{property})) {
return {} if !exists $config->{$prop};
return { $prop => $config->{$prop} };
}
return $config;
}});
},
});
__PACKAGE__->register_method({
name => 'set_options',
@ -75,7 +78,7 @@ __PACKAGE__->register_method({
method => 'PUT',
description => "Set node configuration options.",
permissions => {
check => ['perm', '/', [ 'Sys.Modify' ]],
check => ['perm', '/', ['Sys.Modify']],
},
protected => 1,
proxyto => 'node',
@ -102,7 +105,7 @@ __PACKAGE__->register_method({
foreach my $opt (PVE::Tools::split_list($delete)) {
delete $conf->{$opt};
};
}
PVE::NodeConfig::verify_conf($conf);
PVE::NodeConfig::write_config($node, $conf);
@ -112,6 +115,7 @@ __PACKAGE__->register_method({
die $@ if $@;
return undef;
}});
},
});
1;

File diff suppressed because it is too large Load diff

View file

@ -16,13 +16,14 @@ 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}",
description =>
"List all pools where you have Pool.Audit permissions on /pool/<pool>, or the pool specific with {poolid}",
user => 'all',
},
parameters => {
@ -35,7 +36,7 @@ __PACKAGE__->register_method ({
},
type => {
type => 'string',
enum => [ 'qemu', 'lxc', 'storage' ],
enum => ['qemu', 'lxc', 'storage'],
optional => 1,
requires => 'poolid',
},
@ -60,7 +61,7 @@ __PACKAGE__->register_method ({
properties => {
type => {
type => 'string',
enum => [ 'qemu', 'lxc', 'openvz', 'storage' ],
enum => ['qemu', 'lxc', 'openvz', 'storage'],
},
id => {
type => 'string',
@ -81,7 +82,7 @@ __PACKAGE__->register_method ({
},
},
},
links => [ { rel => 'child', href => "{poolid}" } ],
links => [{ rel => 'child', href => "{poolid}" }],
},
code => sub {
my ($param) = @_;
@ -93,7 +94,7 @@ __PACKAGE__->register_method ({
my $res = [];
if (my $poolid = $param->{poolid}) {
$rpcenv->check($authuser, "/pool/$poolid", [ 'Pool.Audit' ], 1);
$rpcenv->check($authuser, "/pool/$poolid", ['Pool.Audit'], 1);
my $vmlist = PVE::Cluster::get_vmlist() || {};
my $idlist = $vmlist->{ids} || {};
@ -105,9 +106,10 @@ __PACKAGE__->register_method ({
die "pool '$poolid' does not exist\n" if !$pool_config;
my $members = [];
for my $vmid (sort keys %{$pool_config->{vms}}) {
for my $vmid (sort keys %{ $pool_config->{vms} }) {
my $vmdata = $idlist->{$vmid};
next if !$vmdata || defined($param->{type}) && $param->{type} ne $vmdata->{type};
next
if !$vmdata || defined($param->{type}) && $param->{type} ne $vmdata->{type};
my $entry = PVE::API2Tools::extract_vm_stats($vmid, $vmdata, $rrd);
push @$members, $entry;
}
@ -115,19 +117,20 @@ __PACKAGE__->register_method ({
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);
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}})) {
for my $node (sort keys(%{ $scfg->{nodes} })) {
$storage_node = $node;
last;
}
}
my $entry = PVE::API2Tools::extract_storage_stats($storeid, $scfg, $storage_node, $rrd);
my $entry =
PVE::API2Tools::extract_storage_stats($storeid, $scfg, $storage_node, $rrd);
push @$members, $entry;
}
}
@ -140,8 +143,8 @@ __PACKAGE__->register_method ({
push @$res, $pool_info;
} else {
for my $pool (sort keys %{$usercfg->{pools}}) {
next if !$rpcenv->check($authuser, "/pool/$pool", [ 'Pool.Audit' ], 1);
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};
@ -151,9 +154,10 @@ __PACKAGE__->register_method ({
}
return $res;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'create_pool',
protected => 1,
path => '',
@ -179,7 +183,8 @@ __PACKAGE__->register_method ({
code => sub {
my ($param) = @_;
PVE::AccessControl::lock_user_config(sub {
PVE::AccessControl::lock_user_config(
sub {
my $usercfg = cfs_read_file("user.cfg");
my $pool = $param->{poolid};
@ -198,21 +203,26 @@ __PACKAGE__->register_method ({
$usercfg->{pools}->{$pool}->{comment} = $param->{comment} if $param->{comment};
cfs_write_file("user.cfg", $usercfg);
}, "create pool failed");
},
"create pool failed",
);
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.",
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 => {
@ -220,23 +230,26 @@ __PACKAGE__->register_method ({
comment => { type => 'string', optional => 1 },
vms => {
description => 'List of guest VMIDs to add or remove from this pool.',
type => 'string', format => 'pve-vmid-list',
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',
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.',
. ' 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.',
description =>
'Remove the passed VMIDs and/or storage IDs instead of adding them.',
type => 'boolean',
optional => 1,
default => 0,
@ -248,15 +261,17 @@ __PACKAGE__->register_method ({
my ($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.",
description =>
"You also need the right to modify permissions on any object you add/delete.",
check => ['perm', '/pool/{poolid}', ['Pool.Allocate']],
},
description => "Update pool.",
@ -267,23 +282,26 @@ __PACKAGE__->register_method ({
comment => { type => 'string', optional => 1 },
vms => {
description => 'List of guest VMIDs to add or remove from this pool.',
type => 'string', format => 'pve-vmid-list',
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',
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.',
. ' 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.',
description =>
'Remove the passed VMIDs and/or storage IDs instead of adding them.',
type => 'boolean',
optional => 1,
default => 0,
@ -297,7 +315,8 @@ __PACKAGE__->register_method ({
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
PVE::AccessControl::lock_user_config(sub {
PVE::AccessControl::lock_user_config(
sub {
my $usercfg = cfs_read_file("user.cfg");
my $pool = $param->{poolid};
my $pool_config = $usercfg->{pools}->{$pool};
@ -311,16 +330,21 @@ __PACKAGE__->register_method ({
$rpcenv->check_perm_modify($authuser, "/vms/$vmid");
if ($param->{delete}) {
die "VM $vmid is not a pool member\n" if !$pool_config->{vms}->{$vmid};
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};
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"
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']);
$rpcenv->check(
$authuser, "/pool/$existing_pool", ['Pool.Allocate'],
);
delete $usercfg->{pools}->{$existing_pool}->{vms}->{$vmid};
}
$pool_config->{vms}->{$vmid} = 1;
@ -347,19 +371,23 @@ __PACKAGE__->register_method ({
}
cfs_write_file("user.cfg", $usercfg);
}, "update pools failed");
},
"update pools failed",
);
return;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'read_pool',
path => '{poolid}',
method => 'GET',
permissions => {
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 => {
@ -369,7 +397,7 @@ __PACKAGE__->register_method ({
},
type => {
type => 'string',
enum => [ 'qemu', 'lxc', 'storage' ],
enum => ['qemu', 'lxc', 'storage'],
optional => 1,
},
},
@ -390,7 +418,7 @@ __PACKAGE__->register_method ({
properties => {
type => {
type => 'string',
enum => [ 'qemu', 'lxc', 'openvz', 'storage' ],
enum => ['qemu', 'lxc', 'openvz', 'storage'],
},
id => {
type => 'string',
@ -416,10 +444,10 @@ __PACKAGE__->register_method ({
my $pool_info = __PACKAGE__->index($param);
return $pool_info->[0];
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'delete_pool_deprecated',
protected => 1,
path => '{poolid}',
@ -428,21 +456,23 @@ __PACKAGE__->register_method ({
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' },
}
},
},
returns => { type => 'null' },
code => sub {
my ($param) = @_;
return __PACKAGE__->delete_pool($param);
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'delete_pool',
protected => 1,
path => '',
@ -456,7 +486,7 @@ __PACKAGE__->register_method ({
additionalProperties => 0,
properties => {
poolid => { type => 'string', format => 'pve-poolid' },
}
},
},
returns => { type => 'null' },
code => sub {
@ -465,7 +495,8 @@ __PACKAGE__->register_method ({
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
PVE::AccessControl::lock_user_config(sub {
PVE::AccessControl::lock_user_config(
sub {
my $vmlist = PVE::Cluster::get_vmlist() || {};
my $idlist = $vmlist->{ids} || {};
@ -476,27 +507,30 @@ __PACKAGE__->register_method ({
my $pool_config = $usercfg->{pools}->{$pool};
die "pool '$pool' does not exist\n" if !$pool_config;
for my $subpool (sort keys %{$pool_config->{pools}}) {
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}}) {
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);
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});
delete($usercfg->{pools}->{$pool});
PVE::AccessControl::delete_pool_acl($pool, $usercfg);
cfs_write_file("user.cfg", $usercfg);
}, "delete pool failed");
},
"delete pool failed",
);
return;
}});
},
});
1;

View file

@ -73,7 +73,6 @@ sub run_single_job {
die $@ if $@;
}
# TODO: below two should probably part of the general job framework/plugin system
my sub _should_mail_at_failcount {
my ($fail_count) = @_;
@ -90,8 +89,7 @@ my sub _should_mail_at_failcount {
$i = $i * 2;
}
return $i * 48 == $fail_count;
};
}
my sub _handle_job_err {
my ($job, $err, $mail) = @_;
@ -129,9 +127,7 @@ my sub _handle_job_err {
eval {
PVE::Notify::error(
"replication",
$template_data,
$metadata_fields
"replication", $template_data, $metadata_fields,
);
};
@ -153,7 +149,9 @@ sub run_jobs {
my $guest_class = $lookup_guest_class->($jobcfg->{vmtype});
eval {
PVE::Replication::run_replication($guest_class, $jobcfg, $iteration, $start_time, $logfunc, $verbose);
PVE::Replication::run_replication(
$guest_class, $jobcfg, $iteration, $start_time, $logfunc, $verbose,
);
};
if (my $err = $@) {
_handle_job_err($jobcfg, $err, $mail);
@ -189,7 +187,7 @@ my $extract_job_status = sub {
return $data;
};
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'status',
path => '',
method => 'GET',
@ -204,10 +202,13 @@ __PACKAGE__->register_method ({
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
guest => get_standard_option('pve-vmid', {
guest => get_standard_option(
'pve-vmid',
{
optional => 1,
description => "Only list replication jobs for this guest.",
}),
},
),
},
},
returns => {
@ -218,7 +219,7 @@ __PACKAGE__->register_method ({
id => { type => 'string' },
},
},
links => [ { rel => 'child', href => "{id}" } ],
links => [{ rel => 'child', href => "{id}" }],
},
code => sub {
my ($param) = @_;
@ -233,14 +234,15 @@ __PACKAGE__->register_method ({
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' ]);
next if !$rpcenv->check($authuser, "/vms/$guest", ['VM.Audit']);
push @$res, $data;
}
return $res;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '{id}',
method => 'GET',
@ -259,20 +261,18 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
return [
{ name => 'schedule_now' },
{ name => 'log' },
{ name => 'status' },
{ name => 'schedule_now' }, { name => 'log' }, { name => 'status' },
];
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'job_status',
path => '{id}/status',
method => 'GET',
@ -309,17 +309,19 @@ __PACKAGE__->register_method ({
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;
}});
},
});
__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>'",
description =>
"Requires the VM.Audit permission on /vms/<vmid>, or 'Sys.Audit' on '/nodes/<node>'",
user => 'all',
},
protected => 1,
@ -348,15 +350,15 @@ __PACKAGE__->register_method({
type => "object",
properties => {
n => {
description=> "Line number",
type=> 'integer',
description => "Line number",
type => 'integer',
},
t => {
description=> "Line text",
description => "Line text",
type => 'string',
}
}
}
},
},
},
},
code => sub {
my ($param) = @_;
@ -375,17 +377,22 @@ __PACKAGE__->register_method({
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' ])));
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);
return $lines;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'schedule_now',
path => '{id}/schedule_now',
method => 'POST',
@ -417,6 +424,7 @@ __PACKAGE__->register_method ({
PVE::ReplicationState::schedule_job_now($jobcfg);
}});
},
});
1;

View file

@ -14,7 +14,7 @@ use PVE::RESTHandler;
use base qw(PVE::RESTHandler);
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'index',
path => '',
method => 'GET',
@ -34,7 +34,7 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{id}" } ],
links => [{ rel => 'child', href => "{id}" }],
},
code => sub {
my ($param) = @_;
@ -45,18 +45,19 @@ __PACKAGE__->register_method ({
my $cfg = PVE::ReplicationConfig->new();
my $res = [];
foreach my $id (sort keys %{$cfg->{ids}}) {
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);
next if !$rpcenv->check($authuser, "/vms/$vmid", ['VM.Audit'], 1);
$d->{id} = $id;
push @$res, $d;
}
return $res;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'read',
path => '{id}',
method => 'GET',
@ -80,22 +81,23 @@ __PACKAGE__->register_method ({
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);
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->{digest} = $cfg->{digest};
return $data;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'create',
path => '',
protected => 1,
@ -124,10 +126,11 @@ __PACKAGE__->register_method ({
die "Guest '$guest' does not exist.\n"
if !defined($guest_info);
die "Target '$param->{target}' does not exist.\n"
if !defined($nodelist->{$param->{target}});
if !defined($nodelist->{ $param->{target} });
my $source = $guest_info->{node};
die "Source '$param->{source}' does not match current node of guest '$guest' ($source)\n"
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;
@ -137,7 +140,9 @@ __PACKAGE__->register_method ({
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);
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 {
@ -158,10 +163,10 @@ __PACKAGE__->register_method ({
PVE::ReplicationConfig::lock($code);
return undef;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'update',
protected => 1,
path => '{id}',
@ -195,10 +200,10 @@ __PACKAGE__->register_method ({
}
if ($delete) {
my $options = $plugin->private()->{options}->{$data->{type}};
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";
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"
@ -213,9 +218,10 @@ __PACKAGE__->register_method ({
PVE::ReplicationConfig::lock($code);
return undef;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'delete',
protected => 1,
path => '{id}',
@ -240,7 +246,7 @@ __PACKAGE__->register_method ({
optional => 1,
default => 0,
},
}
},
},
returns => { type => 'null' },
code => sub {
@ -253,7 +259,8 @@ __PACKAGE__->register_method ({
my $id = $param->{id};
if ($param->{force}) {
raise_param_exc({ 'keep' => "conflicts with parameter 'force'" }) if $param->{keep};
raise_param_exc({ 'keep' => "conflicts with parameter 'force'" })
if $param->{keep};
delete $cfg->{ids}->{$id};
} else {
my $jobcfg = $cfg->{ids}->{$id};
@ -276,6 +283,7 @@ __PACKAGE__->register_method ({
PVE::ReplicationConfig::lock($code);
return undef;
}});
},
});
1;

View file

@ -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 {
@ -87,7 +87,6 @@ sub get_service_list {
return $static_service_list;
}
my $service_prop_desc = {
description => "Service ID",
type => 'string',
@ -99,7 +98,8 @@ 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";
@ -134,12 +134,12 @@ my $service_state = sub {
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',
@ -156,7 +156,7 @@ __PACKAGE__->register_method ({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{service}" } ],
links => [{ rel => 'child', href => "{service}" }],
},
code => sub {
my ($param) = @_;
@ -166,7 +166,8 @@ __PACKAGE__->register_method ({
my $res = [];
for my $id (sort keys %{$service_list}) {
my $state = $service_state->($id);
push @$res, {
push @$res,
{
service => $id,
name => $service_list->{$id}->{name},
desc => $service_list->{$id}->{desc},
@ -175,7 +176,8 @@ __PACKAGE__->register_method ({
}
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'srvcmdidx',
@ -183,7 +185,7 @@ __PACKAGE__->register_method({
method => 'GET',
description => "Directory index",
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]],
check => ['perm', '/nodes/{node}', ['Sys.Audit']],
},
parameters => {
additionalProperties => 0,
@ -200,7 +202,7 @@ __PACKAGE__->register_method({
subdir => { type => 'string' },
},
},
links => [ { rel => 'child', href => "{subdir}" } ],
links => [{ rel => 'child', href => "{subdir}" }],
},
code => sub {
my ($param) = @_;
@ -214,14 +216,15 @@ __PACKAGE__->register_method({
];
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',
@ -254,15 +257,16 @@ __PACKAGE__->register_method ({
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,
@ -293,15 +297,16 @@ __PACKAGE__->register_method ({
};
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,
@ -332,15 +337,16 @@ __PACKAGE__->register_method ({
};
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,
@ -368,15 +374,16 @@ __PACKAGE__->register_method ({
};
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,
@ -405,4 +412,5 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('srvreload', $param->{service}, $user, $realcmd);
}});
},
});

View file

@ -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"
. " 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";
$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',
@ -217,22 +224,23 @@ __PACKAGE__->register_method ({
status => $info->{status},
message => $info->{message},
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',
@ -245,11 +253,11 @@ __PACKAGE__->register_method ({
description => "Always connect to server, even if local cache is still valid.",
type => 'boolean',
optional => 1,
default => 0
}
default => 0,
},
},
returns => { type => 'null'},
},
returns => { type => 'null' },
code => sub {
my ($param) = @_;
@ -259,7 +267,8 @@ __PACKAGE__->register_method ({
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"
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
@ -270,19 +279,22 @@ __PACKAGE__->register_method ({
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);
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',
@ -299,7 +311,7 @@ __PACKAGE__->register_method ({
},
},
},
returns => { type => 'null'},
returns => { type => 'null' },
code => sub {
my ($param) = @_;
@ -322,19 +334,21 @@ __PACKAGE__->register_method ({
my $proxy = $dccfg->{http_proxy};
my $checked_info = Proxmox::RS::Subscription::check_subscription(
$key, $server_id, "", "Proxmox VE", $proxy);
$key, $server_id, "", "Proxmox VE", $proxy,
);
write_etc_subscription($checked_info);
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',
@ -345,12 +359,13 @@ __PACKAGE__->register_method ({
node => get_standard_option('pve-node'),
},
},
returns => { type => 'null'},
returns => { type => 'null' },
code => sub {
my $subscription_file = '/etc/subscription';
return if ! -e $subscription_file;
return if !-e $subscription_file;
unlink($subscription_file) or die "cannot delete subscription key: $!";
return undef;
}});
},
});
1;

View file

@ -41,8 +41,9 @@ __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',
@ -74,10 +75,13 @@ __PACKAGE__->register_method({
optional => 1,
description => 'Only list tasks of this type (e.g., vzstart, vzdump).',
},
vmid => get_standard_option('pve-vmid', {
vmid => get_standard_option(
'pve-vmid',
{
description => "Only list tasks for this VM.",
optional => 1,
}),
},
),
errors => {
type => 'boolean',
default => 0,
@ -114,19 +118,19 @@ __PACKAGE__->register_method({
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', },
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}" } ],
links => [{ rel => 'child', href => "{upid}" }],
},
code => sub {
my ($param) = @_;
@ -162,7 +166,7 @@ __PACKAGE__->register_method({
unknown => 0,
};
for my $filter (PVE::Tools::split_list($param->{statusfilter})) {
$statusfilter->{lc($filter)} = 1 ;
$statusfilter->{ lc($filter) } = 1;
}
} elsif ($errors) {
$statusfilter->{ok} = 0;
@ -171,7 +175,7 @@ __PACKAGE__->register_method({
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;
@ -229,13 +233,13 @@ __PACKAGE__->register_method({
if ($source ne 'active') {
if (my $bw = File::ReadBackwards->new($filename)) {
while (defined ($line = $bw->readline)) {
while (defined($line = $bw->readline)) {
&$parse_line();
}
$bw->close();
}
if (my $bw = File::ReadBackwards->new("$filename.1")) {
while (defined ($line = $bw->readline)) {
while (defined($line = $bw->readline)) {
&$parse_line();
}
$bw->close();
@ -245,7 +249,8 @@ __PACKAGE__->register_method({
$rpcenv->set_result_attrib('total', $count);
return $res;
}});
},
});
__PACKAGE__->register_method({
name => 'upid_index',
@ -258,7 +263,7 @@ __PACKAGE__->register_method({
properties => {
node => get_standard_option('pve-node'),
upid => { type => 'string' },
}
},
},
returns => {
type => 'array',
@ -266,16 +271,16 @@ __PACKAGE__->register_method({
type => "object",
properties => {},
},
links => [ { rel => 'child', href => "{name}" } ],
links => [{ rel => 'child', href => "{name}" }],
},
code => sub {
my ($param) = @_;
return [
{ name => 'log' },
{ name => 'status' }
{ name => 'log' }, { name => 'status' },
];
}});
},
});
__PACKAGE__->register_method({
name => 'stop_task',
@ -283,7 +288,8 @@ __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.",
description =>
"The user needs 'Sys.Modify' permissions on '/nodes/<node>' if they aren't the owner of the task.",
user => 'all',
},
protected => 1,
@ -293,7 +299,7 @@ __PACKAGE__->register_method({
properties => {
node => get_standard_option('pve-node'),
upid => { type => 'string' },
}
},
},
returns => { type => 'null' },
code => sub {
@ -301,7 +307,7 @@ __PACKAGE__->register_method({
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;
raise_param_exc({ upid => "no such task" }) if !-f $filename;
my $rpcenv = PVE::RPCEnvironment::get();
my $user = $rpcenv->get_user();
@ -310,20 +316,22 @@ __PACKAGE__->register_method({
$convert_token_task->($task);
if (!$check_task_user->($task, $user)) {
$rpcenv->check($user, "/nodes/$node", [ 'Sys.Modify' ]);
$rpcenv->check($user, "/nodes/$node", ['Sys.Modify']);
}
PVE::RPCEnvironment->check_worker($param->{upid}, 1);
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.",
description =>
"The user needs 'Sys.Audit' permissions on '/nodes/<node>' if they aren't the owner of the task.",
user => 'all',
},
protected => 1,
@ -355,8 +363,9 @@ __PACKAGE__->register_method({
download => {
type => 'boolean',
optional => 1,
description => "Whether the tasklog file should be downloaded. This parameter can't be used in conjunction with other parameters",
}
description =>
"Whether the tasklog file should be downloaded. This parameter can't be used in conjunction with other parameters",
},
},
},
returns => {
@ -365,15 +374,15 @@ __PACKAGE__->register_method({
type => "object",
properties => {
n => {
description=> "Line number",
type=> 'integer',
description => "Line number",
type => 'integer',
},
t => {
description=> "Line text",
description => "Line text",
type => 'string',
}
}
}
},
},
},
},
code => sub {
my ($param) = @_;
@ -388,7 +397,7 @@ __PACKAGE__->register_method({
$convert_token_task->($task);
if (!$check_task_user->($task, $user)) {
$rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
$rpcenv->check($user, "/nodes/$node", ['Sys.Audit']);
}
if ($param->{download}) {
@ -396,7 +405,7 @@ __PACKAGE__->register_method({
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 $use_compression = (-s $filename) > 1024;
my $fh;
if ($use_compression) {
@ -407,7 +416,8 @@ __PACKAGE__->register_method({
}
my $task_time = strftime('%FT%TZ', gmtime($task->{starttime}));
my $download_name = 'task-'.$task->{node}.'-'.$task->{type}.'-'.$task_time.'.log';
my $download_name =
'task-' . $task->{node} . '-' . $task->{type} . '-' . $task_time . '.log';
return {
download => {
@ -415,9 +425,10 @@ __PACKAGE__->register_method({
stream => 1,
'content-encoding' => $use_compression ? 'gzip' : undef,
'content-type' => "text/plain",
'content-disposition' => "attachment; filename=\"".$download_name."\"",
'content-disposition' => "attachment; filename=\"" . $download_name . "\"",
},
},
;
} else {
my $start = $param->{start} // 0;
my $limit = $param->{limit} // 50;
@ -428,8 +439,8 @@ __PACKAGE__->register_method({
return $lines;
}
}});
},
});
my $exit_status_cache = {};
@ -438,7 +449,8 @@ __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.",
description =>
"The user needs 'Sys.Audit' permissions on '/nodes/<node>' if they are not the owner of the task.",
user => 'all',
},
protected => 1,
@ -458,10 +470,11 @@ __PACKAGE__->register_method({
type => "object",
properties => {
pid => {
type => 'integer'
type => 'integer',
},
status => {
type => 'string', enum => ['running', 'stopped'],
type => 'string',
enum => ['running', 'stopped'],
},
type => {
type => 'string',
@ -495,7 +508,7 @@ __PACKAGE__->register_method({
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;
raise_param_exc({ upid => "no such task" }) if !-f $filename;
my $lines = [];
@ -506,24 +519,24 @@ __PACKAGE__->register_method({
$convert_token_task->($task);
if (!$check_task_user->($task, $user)) {
$rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]);
$rpcenv->check($user, "/nodes/$node", ['Sys.Audit']);
}
my $pstart = PVE::ProcFSTools::read_proc_starttime($task->{pid});
$task->{status} = ($pstart && ($pstart == $task->{pstart})) ?
'running' : 'stopped';
$task->{status} = ($pstart && ($pstart == $task->{pstart})) ? 'running' : 'stopped';
$task->{upid} = $param->{upid}; # include upid
if ($task->{status} eq 'stopped') {
if (!defined($exit_status_cache->{$task->{upid}})) {
$exit_status_cache->{$task->{upid}} =
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}};
$task->{exitstatus} = $exit_status_cache->{ $task->{upid} };
}
return $task;
}});
},
});
1;

View file

@ -29,23 +29,23 @@ my sub assert_param_permission_vzdump {
if (defined($param->{maxfiles}) || defined($param->{'prune-backups'})) {
if (my $storeid = PVE::VZDump::get_storage_param($param)) {
$rpcenv->check($user, "/storage/$storeid", [ 'Datastore.Allocate' ]);
$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 '/'.",
. "'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,
@ -53,12 +53,16 @@ __PACKAGE__->register_method ({
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"
'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.",
@ -77,10 +81,11 @@ __PACKAGE__->register_method ({
my $nodename = PVE::INotify::nodename();
if ($rpcenv->{type} ne 'cli') {
raise_param_exc({ node => "option is only allowed on the command line interface."})
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."})
raise_param_exc({
stdout => "option is only allowed on the command line interface." })
if $param->{stdout};
}
@ -101,9 +106,9 @@ __PACKAGE__->register_method ({
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}){
if ($param->{stop}) {
PVE::VZDump::stop_running_backups();
return 'OK' if !scalar(@{$local_vmids});
}
@ -117,7 +122,7 @@ __PACKAGE__->register_method ({
if $param->{stdout} && scalar(@{$local_vmids}) != 1;
if (my $storeid = PVE::VZDump::get_storage_param($param)) {
$rpcenv->check($user, "/storage/$storeid", [ 'Datastore.AllocateSpace' ]);
$rpcenv->check($user, "/storage/$storeid", ['Datastore.AllocateSpace']);
}
my $worker = sub {
@ -159,8 +164,8 @@ __PACKAGE__->register_method ({
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;
}
@ -170,17 +175,18 @@ __PACKAGE__->register_method ({
$taskid = $local_vmids->[0] if scalar(@{$local_vmids}) == 1;
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.",
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',
@ -228,15 +234,14 @@ __PACKAGE__->register_method ({
if (defined($res->{'prune-backups'})) {
$res->{'prune-backups'} = PVE::JSONSchema::print_property_string(
$res->{'prune-backups'},
'prune-backups',
$res->{'prune-backups'}, 'prune-backups',
);
}
$res->{mailto} = join(",", @{$res->{mailto}})
$res->{mailto} = join(",", @{ $res->{mailto} })
if defined($res->{mailto});
$res->{'exclude-path'} = join(",", @{$res->{'exclude-path'}})
$res->{'exclude-path'} = join(",", @{ $res->{'exclude-path'} })
if defined($res->{'exclude-path'});
# normal backup users don't need to know these
@ -249,21 +254,25 @@ __PACKAGE__->register_method ({
}
my $pool = $res->{pool};
if (defined($pool) &&
!$rpcenv->check($authuser, "/pool/$pool", ['Pool.Audit'], 1)) {
if (
defined($pool)
&& !$rpcenv->check($authuser, "/pool/$pool", ['Pool.Audit'], 1)
) {
delete $res->{pool};
}
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.",
description =>
"The user needs 'VM.Backup' permissions on the backed up guest ID, and 'Datastore.AllocateSpace' on the backup storage.",
user => 'all',
},
protected => 1,
@ -290,12 +299,7 @@ __PACKAGE__->register_method ({
my $storage_cfg = PVE::Storage::config();
PVE::Storage::check_volume_access(
$rpcenv,
$authuser,
$storage_cfg,
undef,
$volume,
'backup',
$rpcenv, $authuser, $storage_cfg, undef, $volume, 'backup',
);
if (PVE::Storage::parse_volume_id($volume, 1)) {
@ -304,6 +308,7 @@ __PACKAGE__->register_method ({
}
return PVE::Storage::extract_vzdump_config($storage_cfg, $volume);
}});
},
});
1;

View file

@ -25,16 +25,18 @@ sub get_hwaddress {
my $fn = '/etc/ssh/ssh_host_rsa_key.pub';
my $st = stat($fn);
if (defined($hwaddress)
if (
defined($hwaddress)
&& $hwaddress_st->{mtime} == $st->mtime
&& $hwaddress_st->{ino} == $st->ino
&& $hwaddress_st->{dev} == $st->dev) {
&& $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;
}
@ -51,8 +53,10 @@ sub extract_node_stats {
if (my $d = $rrd->{"pve2-node/$node"}) {
if (!$members || # no cluster
($members->{$node} && $members->{$node}->{online})) {
if (
!$members || # no cluster
($members->{$node} && $members->{$node}->{online})
) {
if (!$exclude_stats) {
$entry->{uptime} = ($d->[0] || 0) + 0;
$entry->{cpu} = ($d->[5] || 0) + 0;
@ -69,8 +73,11 @@ sub extract_node_stats {
}
}
if ($members && $members->{$node} &&
!$members->{$node}->{online}) {
if (
$members
&& $members->{$node}
&& !$members->{$node}->{online}
) {
$entry->{status} = 'offline';
}
@ -123,7 +130,7 @@ sub extract_vm_stats {
$entry->{netout} = ($d->[12] || 0) + 0;
$entry->{diskread} = ($d->[13] || 0) + 0;
$entry->{diskwrite} = ($d->[14] || 0) + 0;
};
}
return $entry;
}
@ -187,9 +194,19 @@ sub run_spiceterm {
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};
@ -199,14 +216,20 @@ sub run_spiceterm {
my $realcmd = sub {
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' ;
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} = '/';
@ -215,7 +238,7 @@ sub run_spiceterm {
PVE::Tools::run_command($cmd, errmsg => 'spiceterm failed\n', keeplocale => 1);
};
if (my $err = $@) {
syslog ('err', $err);
syslog('err', $err);
}
return;
@ -242,7 +265,7 @@ sub resolve_proxyto {
if !$node;
} else {
$node = $uri_param->{$proxyto};
raise_param_exc({ $proxyto => "proxyto parameter does not exist"})
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;
}

View file

@ -20,9 +20,9 @@ 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)) {
foreach my $line (split(/\n/, $msg)) {
print $logfd "$tstr $line\n";
}
}
@ -63,7 +63,7 @@ sub read_aplinfo_from_fh {
$update ? die $msg : warn $msg;
}
} elsif ($rec =~ s/^([^:]+):\s*(.*\S)\s*\n//) {
$res->{lc $1} = $2;
$res->{ lc $1 } = $2;
} else {
my $msg = "unable to parse appliance record: $rec\n";
$update ? die $msg : warn $msg;
@ -73,14 +73,19 @@ sub read_aplinfo_from_fh {
}
if ($res->{'package'} eq 'pve-web-news' && $res->{description}) {
$list->{'all'}->{$res->{'package'}} = $res;
$list->{'all'}->{ $res->{'package'} } = $res;
next;
}
$res->{section} = 'unknown' if !$res->{section};
if ($res->{'package'} && $res->{type} && $res->{os} && $res->{version} &&
$res->{infopage}) {
if (
$res->{'package'}
&& $res->{type}
&& $res->{os}
&& $res->{version}
&& $res->{infopage}
) {
my $template;
if ($res->{location}) {
$template = $res->{location};
@ -97,7 +102,7 @@ sub read_aplinfo_from_fh {
}
$res->{source} = $source;
$res->{template} = $template;
$list->{$res->{section}}->{$template} = $res;
$list->{ $res->{section} }->{$template} = $res;
$list->{'all'}->{$template} = $res;
} else {
my $msg = "found incomplete appliance records\n";
@ -109,8 +114,8 @@ sub read_aplinfo_from_fh {
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);
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;
}
@ -169,17 +174,15 @@ sub download_aplinfo {
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);
};
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 $@;
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");
};
@ -217,7 +220,7 @@ sub update {
if ($logfile_size > 1024 * 256) {
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;
@ -236,11 +239,9 @@ sub update {
my @dlerr = ();
foreach my $info (@$sources) {
eval {
download_aplinfo($ua, $info, $logfd);
};
eval { download_aplinfo($ua, $info, $logfd); };
if (my $err = $@) {
logmsg ($logfd, $err);
logmsg($logfd, $err);
push @dlerr, $info->{url};
}
}

View file

@ -41,7 +41,7 @@ sub compute_alg1 {
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);
my $desired = $d->{balloon_min} + int(($alloc_new / $shares_total) * $shares);
if ($desired > $d->{maxmem}) {
$desired = $d->{maxmem};
@ -74,7 +74,9 @@ sub compute_alg1 {
$progress += $absdiff;
$repeat = 1;
}
&$log("change request for $vmid ($balloon, $diff, $desired, $new, $changes, $progress)\n");
&$log(
"change request for $vmid ($balloon, $diff, $desired, $new, $changes, $progress)\n"
);
}
}
@ -84,7 +86,6 @@ sub compute_alg1 {
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
@ -94,14 +95,17 @@ sub compute_alg1 {
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
next
if defined($d->{shares})
&& ($d->{shares} == 0); # skip if shares set to zero
push @$idlist, $vmid;
if ($d->{freemem} &&
($d->{freemem} > $d->{balloon_min}*0.25) &&
($d->{balloon} >= $d->{balloon_min})) {
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 {
@ -112,7 +116,7 @@ sub compute_alg1 {
my $res = {};
if ($goal > 10*1024*1024) {
if ($goal > 10 * 1024 * 1024) {
&$log("grow request start $goal\n");
# priorize VMs with memory pressure
my $rest = &$change_func($res, $idlist1, $goal);
@ -122,7 +126,7 @@ sub compute_alg1 {
}
&$log("grow request end $rest\n");
} elsif ($goal < -10*1024*1024) {
} elsif ($goal < -10 * 1024 * 1024) {
&$log("shrink request $goal\n");
# priorize VMs with enough free memory
my $rest = &$change_func($res, $idlist2, $goal);

View file

@ -106,6 +106,7 @@ sub log_warn { $log_line->('warn', @_); }
sub log_fail { $log_line->('fail', @_); }
my $print_header_first = 1;
sub print_header {
my ($h) = @_;
print "\n" if !$print_header_first;
@ -123,7 +124,8 @@ my $get_systemd_unit_state = sub {
};
my %extra = (outfunc => $filter_output, noerr => 1);
$extra{errfunc} = sub { } if $suppress_stderr;
$extra{errfunc} = sub { }
if $suppress_stderr;
eval {
run_command(['systemctl', 'is-enabled', "$unit"], %extra);
@ -163,7 +165,7 @@ my $get_pkg = sub {
return undef;
}
my $pkgs = [ grep { $_->{Package} eq $pkg } @$versions ];
my $pkgs = [grep { $_->{Package} eq $pkg } @$versions];
if (!defined $pkgs || $pkgs == 0) {
log_fail("unable to determine installed $pkg version.");
return undef;
@ -235,7 +237,9 @@ sub check_pve_packages {
}
} elsif ($get_pkg->($kinstalled)) {
# with 6.2 kernel being available in both we might want to fine-tune the check?
log_warn("a suitable kernel ($kinstalled) is installed, but an unsuitable ($kernel_ver) is booted, missing reboot?!");
log_warn(
"a suitable kernel ($kinstalled) is installed, but an unsuitable ($kernel_ver) is booted, missing reboot?!"
);
} else {
log_warn("unexpected running and installed kernel '$kernel_ver'.");
}
@ -251,9 +255,9 @@ sub check_pve_packages {
if (scalar(@$outdated_kernel_meta_pkgs) > 0) {
log_info(
"Found outdated kernel meta-packages, taking up extra space on boot partitions.\n"
." After a successful upgrade, you can remove them using this command:\n"
." apt remove " . join(' ', $outdated_kernel_meta_pkgs->@*)
);
. " After a successful upgrade, you can remove them using this command:\n"
. " apt remove "
. join(' ', $outdated_kernel_meta_pkgs->@*));
}
}
} else {
@ -261,7 +265,6 @@ sub check_pve_packages {
}
}
sub check_storage_health {
print_header("CHECKING CONFIGURED STORAGES");
my $cfg = PVE::Storage::config();
@ -315,8 +318,9 @@ sub check_cluster_corosync {
} else {
if (grep { $conf_nodelist->{$_}->{quorum_votes} != 1 } keys %$conf_nodelist) {
log_warn("non-default quorum_votes distribution detected!");
}
map { $node_votes += $conf_nodelist->{$_}->{quorum_votes} // 0 } keys %$conf_nodelist;
} map {
$node_votes += $conf_nodelist->{$_}->{quorum_votes} // 0
} keys %$conf_nodelist;
}
my ($expected_votes, $total_votes);
@ -327,9 +331,7 @@ sub check_cluster_corosync {
($total_votes) = $line =~ /^Total votes:\s*(\d+)\s*$/
if !defined($total_votes);
};
eval {
run_command(['corosync-quorumtool', '-s'], outfunc => $filter_output, noerr => 1);
};
eval { run_command(['corosync-quorumtool', '-s'], outfunc => $filter_output, noerr => 1); };
if (!defined($expected_votes)) {
log_fail("unable to get expected number of votes, assuming 0.");
@ -366,8 +368,9 @@ sub check_cluster_corosync {
log_warn("cluster consists of less than three quorum-providing nodes!")
if $conf_nodelist_count < 3 && $conf_nodelist_count + $qdevice_votes < 3;
log_fail("corosync.conf ($conf_nodelist_count) and pmxcfs ($cfs_nodelist_count) don't agree about size of nodelist.")
if $conf_nodelist_count != $cfs_nodelist_count;
log_fail(
"corosync.conf ($conf_nodelist_count) and pmxcfs ($cfs_nodelist_count) don't agree about size of nodelist."
) if $conf_nodelist_count != $cfs_nodelist_count;
print "\nChecking nodelist entries..\n";
my $nodelist_pass = 1;
@ -382,7 +385,7 @@ sub check_cluster_corosync {
log_fail("$cs_node: no nodeid configured in corosync.conf.");
}
my $gotLinks = 0;
for my $link (0..7) {
for my $link (0 .. 7) {
$gotLinks++ if defined($entry->{"ring${link}_addr"});
}
if ($gotLinks <= 0) {
@ -393,25 +396,24 @@ sub check_cluster_corosync {
my $verify_ring_ip = sub {
my $key = shift;
if (defined(my $ring = $entry->{$key})) {
my ($resolved_ip, undef) = PVE::Corosync::resolve_hostname_like_corosync($ring, $conf);
my ($resolved_ip, undef) =
PVE::Corosync::resolve_hostname_like_corosync($ring, $conf);
if (defined($resolved_ip)) {
if ($resolved_ip ne $ring) {
$nodelist_pass = 0;
log_warn(
"$cs_node: $key '$ring' resolves to '$resolved_ip'.\n"
." Consider replacing it with the currently resolved IP address."
);
log_warn("$cs_node: $key '$ring' resolves to '$resolved_ip'.\n"
. " Consider replacing it with the currently resolved IP address.");
}
} else {
$nodelist_pass = 0;
log_fail(
"$cs_node: unable to resolve $key '$ring' to an IP address according to Corosync's"
." resolve strategy - cluster will potentially fail with Corosync 3.x/kronosnet!"
. " resolve strategy - cluster will potentially fail with Corosync 3.x/kronosnet!"
);
}
}
};
for my $link (0..7) {
for my $link (0 .. 7) {
$verify_ring_ip->("ring${link}_addr");
}
}
@ -425,17 +427,24 @@ sub check_cluster_corosync {
if (defined($transport)) {
if ($transport ne 'knet') {
$totem_pass = 0;
log_fail("Corosync transport explicitly set to '$transport' instead of implicit default!");
log_fail(
"Corosync transport explicitly set to '$transport' instead of implicit default!");
}
}
# TODO: are those values still up-to-date?
if ((!defined($totem->{secauth}) || $totem->{secauth} ne 'on') && (!defined($totem->{crypto_cipher}) || $totem->{crypto_cipher} eq 'none')) {
if (
(!defined($totem->{secauth}) || $totem->{secauth} ne 'on')
&& (!defined($totem->{crypto_cipher}) || $totem->{crypto_cipher} eq 'none')
) {
$totem_pass = 0;
log_fail("Corosync authentication/encryption is not explicitly enabled (secauth / crypto_cipher / crypto_hash)!");
log_fail(
"Corosync authentication/encryption is not explicitly enabled (secauth / crypto_cipher / crypto_hash)!"
);
} elsif (defined($totem->{crypto_cipher}) && $totem->{crypto_cipher} eq '3des') {
$totem_pass = 0;
log_fail("Corosync encryption cipher set to '3des', no longer supported in Corosync 3.x!"); # FIXME: can be removed?
log_fail("Corosync encryption cipher set to '3des', no longer supported in Corosync 3.x!")
; # FIXME: can be removed?
}
log_pass("totem settings OK") if $totem_pass;
@ -478,13 +487,18 @@ sub check_ceph {
log_fail("unable to determine Ceph health!");
} elsif ($ceph_health eq 'HEALTH_OK') {
log_pass("Ceph health reported as 'HEALTH_OK'.");
} elsif ($ceph_health eq 'HEALTH_WARN' && $noout && (keys %{$ceph_status->{health}->{checks}} == 1)) {
log_pass("Ceph health reported as 'HEALTH_WARN' with a single failing check and 'noout' flag set.");
} elsif (
$ceph_health eq 'HEALTH_WARN'
&& $noout
&& (keys %{ $ceph_status->{health}->{checks} } == 1)
) {
log_pass(
"Ceph health reported as 'HEALTH_WARN' with a single failing check and 'noout' flag set."
);
} else {
log_warn(
"Ceph health reported as '$ceph_health'.\n Use the PVE dashboard or 'ceph -s'"
." to determine the specific issues and try to resolve them."
);
. " to determine the specific issues and try to resolve them.");
}
}
@ -494,18 +508,16 @@ sub check_ceph {
if (my $release = eval { PVE::Ceph::Tools::get_local_version(1) }) {
my $code_name = $ceph_release2code->{"$release"} || 'unknown';
if ($release == $ceph_supported_release) {
log_pass("found expected Ceph $ceph_supported_release $ceph_supported_code_name release.")
log_pass(
"found expected Ceph $ceph_supported_release $ceph_supported_code_name release.");
} elsif ($release > $ceph_supported_release) {
log_warn(
"found newer Ceph release $release $code_name as the expected $ceph_supported_release"
." $ceph_supported_code_name, installed third party repos?!"
)
. " $ceph_supported_code_name, installed third party repos?!");
} else {
log_fail(
"Hyper-converged Ceph $release $code_name is to old for upgrade!\n"
." Upgrade Ceph first to $ceph_supported_code_name following our how-to:\n"
." <https://pve.proxmox.com/wiki/Category:Ceph_Upgrade>"
);
log_fail("Hyper-converged Ceph $release $code_name is to old for upgrade!\n"
. " Upgrade Ceph first to $ceph_supported_code_name following our how-to:\n"
. " <https://pve.proxmox.com/wiki/Category:Ceph_Upgrade>");
}
} else {
log_fail("unable to determine local Ceph version!");
@ -539,7 +551,7 @@ sub check_ceph {
}
for my $service (@$services) {
my ($name, $key) = $service->@{'name', 'key'};
my ($name, $key) = $service->@{ 'name', 'key' };
if (my $service_versions = $ceph_versions_simple->{$key}) {
if (keys %$service_versions == 0) {
log_skip("no running instances detected for daemon type $name.");
@ -552,8 +564,9 @@ sub check_ceph {
log_skip("unable to determine versions of running Ceph $name instances.");
}
my $service_commits = $ceph_versions_commits->{$key};
log_info("different builds of same version detected for an $name. Are you in the middle of the upgrade?")
if $service_commits && keys %$service_commits > 1;
log_info(
"different builds of same version detected for an $name. Are you in the middle of the upgrade?"
) if $service_commits && keys %$service_commits > 1;
}
my $overall_versions = $ceph_versions->{overall};
@ -563,7 +576,8 @@ sub check_ceph {
log_pass("single running overall version detected for all Ceph daemon types.");
$noout_wanted = !$upgraded; # off post-upgrade, on pre-upgrade
} elsif (keys $ceph_versions_simple->{overall}->%* != 1) {
log_warn("overall version mismatch detected, check 'ceph versions' output for details!");
log_warn(
"overall version mismatch detected, check 'ceph versions' output for details!");
}
}
@ -586,17 +600,18 @@ sub check_ceph {
if (!defined($global_monhost)) {
log_warn(
"No 'mon_host' entry found in ceph config.\n It's recommended to add mon_host with"
." all monitor addresses (without ports) to the global section."
);
. " all monitor addresses (without ports) to the global section.");
}
my $ipv6 = $global->{ms_bind_ipv6} // $global->{"ms bind ipv6"} // $global->{"ms-bind-ipv6"};
my $ipv6 = $global->{ms_bind_ipv6} // $global->{"ms bind ipv6"}
// $global->{"ms-bind-ipv6"};
if ($ipv6) {
my $ipv4 = $global->{ms_bind_ipv4} // $global->{"ms bind ipv4"} // $global->{"ms-bind-ipv4"};
my $ipv4 = $global->{ms_bind_ipv4} // $global->{"ms bind ipv4"}
// $global->{"ms-bind-ipv4"};
if ($ipv6 eq 'true' && (!defined($ipv4) || $ipv4 ne 'false')) {
log_warn(
"'ms_bind_ipv6' is enabled but 'ms_bind_ipv4' is not disabled.\n Make sure to"
." disable 'ms_bind_ipv4' for ipv6 only clusters, or add an ipv4 network to public/cluster network."
. " disable 'ms_bind_ipv4' for ipv6 only clusters, or add an ipv4 network to public/cluster network."
);
}
}
@ -604,7 +619,7 @@ sub check_ceph {
if (defined($global->{keyring})) {
log_warn(
"[global] config section contains 'keyring' option, which will prevent services from"
." starting with Nautilus.\n Move 'keyring' option to [client] section instead."
. " starting with Nautilus.\n Move 'keyring' option to [client] section instead."
);
}
@ -627,8 +642,8 @@ sub check_backup_retention_settings {
my $pass = 1;
my $maxfiles_msg = "parameter 'maxfiles' is deprecated with PVE 7.x and will be removed in a " .
"future version, use 'prune-backups' instead.";
my $maxfiles_msg = "parameter 'maxfiles' is deprecated with PVE 7.x and will be removed in a "
. "future version, use 'prune-backups' instead.";
eval {
my $confdesc = PVE::VZDump::Common::get_confdesc();
@ -638,7 +653,7 @@ sub check_backup_retention_settings {
my $fn = "/etc/vzdump.conf";
my $raw = PVE::Tools::file_get_contents($fn);
my $conf_schema = { type => 'object', properties => $confdesc, };
my $conf_schema = { type => 'object', properties => $confdesc };
my $param = PVE::JSONSchema::parse_config($conf_schema, $fn, $raw);
if (defined($param->{maxfiles})) {
@ -686,18 +701,20 @@ sub check_cifs_credential_location {
my $found;
PVE::Tools::dir_glob_foreach('/etc/pve/priv/', $regex, sub {
PVE::Tools::dir_glob_foreach(
'/etc/pve/priv/',
$regex,
sub {
my ($filename) = @_;
my ($basename) = $filename =~ $regex;
log_warn(
"CIFS credentials '/etc/pve/priv/$filename' will be moved to"
." '/etc/pve/priv/storage/$basename.pw' during the update"
);
log_warn("CIFS credentials '/etc/pve/priv/$filename' will be moved to"
. " '/etc/pve/priv/storage/$basename.pw' during the update");
$found = 1;
});
},
);
log_pass("no CIFS credentials at outdated location found.") if !$found;
}
@ -705,7 +722,7 @@ sub check_cifs_credential_location {
sub check_custom_pool_roles {
log_info("Checking permission system changes..");
if (! -f "/etc/pve/user.cfg") {
if (!-f "/etc/pve/user.cfg") {
log_skip("user.cfg does not exist");
return;
}
@ -721,10 +738,10 @@ sub check_custom_pool_roles {
my $line = $1;
my @data;
for my $d (split (/:/, $line)) {
for my $d (split(/:/, $line)) {
$d =~ s/^\s+//;
$d =~ s/\s+$//;
push @data, $d
push @data, $d;
}
my $et = shift @data;
@ -745,8 +762,7 @@ sub check_custom_pool_roles {
if ($role eq 'PVESysAdmin' || $role eq 'PVEAdmin') {
log_warn(
"found ACL entry on '$pathtxt' for '$uglist' with role '$role' - this role"
." will no longer have 'Permissions.Modify' after the upgrade!"
);
. " will no longer have 'Permissions.Modify' after the upgrade!");
}
}
}
@ -764,11 +780,17 @@ sub check_custom_pool_roles {
}
}
if ($pve_namespace_clashes > 0) {
log_fail("$pve_namespace_clashes custom role(s) will clash with 'PVE' namespace for built-in roles enforced in Proxmox VE 8");
log_fail(
"$pve_namespace_clashes custom role(s) will clash with 'PVE' namespace for built-in roles enforced in Proxmox VE 8"
);
} elsif ($custom_roles > 0) {
log_pass("none of the $custom_roles custom roles will clash with newly enforced 'PVE' namespace")
log_pass(
"none of the $custom_roles custom roles will clash with newly enforced 'PVE' namespace"
);
} else {
log_pass("no custom roles defined, so no clash with 'PVE' role ID namespace enforced in Proxmox VE 8")
log_pass(
"no custom roles defined, so no clash with 'PVE' role ID namespace enforced in Proxmox VE 8"
);
}
}
@ -786,7 +808,8 @@ sub check_node_and_guest_configurations {
} PVE::Cluster::get_nodelist();
if (scalar(@affected_nodes) > 0) {
log_warn("Node config description of the following nodes too long for new limit of 64 KiB:\n "
log_warn(
"Node config description of the following nodes too long for new limit of 64 KiB:\n "
. join(', ', @affected_nodes));
} else {
log_pass("All node config descriptions fit in the new limit of 64 KiB");
@ -803,7 +826,8 @@ sub check_node_and_guest_configurations {
push @$affected_guests_long_desc, "CT $vmid" if defined($desc) && length($desc) > 8 * 1024;
my $lxc_raw_conf = $conf->{lxc};
push @$affected_cts_cgroup_keys, "CT $vmid" if (grep (@$_[0] =~ /^lxc\.cgroup\./, @$lxc_raw_conf));
push @$affected_cts_cgroup_keys, "CT $vmid"
if (grep (@$_[0] =~ /^lxc\.cgroup\./, @$lxc_raw_conf));
}
my $vms = PVE::QemuServer::config_list();
for my $vmid (sort { $a <=> $b } keys %$vms) {
@ -811,8 +835,10 @@ sub check_node_and_guest_configurations {
push @$affected_guests_long_desc, "VM $vmid" if defined($desc) && length($desc) > 8 * 1024;
}
if (scalar($affected_guests_long_desc->@*) > 0) {
log_warn("Guest config description of the following virtual-guests too long for new limit of 64 KiB:\n"
." " . join(", ", $affected_guests_long_desc->@*));
log_warn(
"Guest config description of the following virtual-guests too long for new limit of 64 KiB:\n"
. " "
. join(", ", $affected_guests_long_desc->@*));
} else {
log_pass("All guest config descriptions fit in the new limit of 8 KiB");
}
@ -821,11 +847,16 @@ sub check_node_and_guest_configurations {
if (scalar($affected_cts_cgroup_keys->@*) > 0) {
if ($forced_legacy_cgroup) {
log_pass("Found legacy 'lxc.cgroup' keys, but system explicitly configured for legacy hybrid cgroup hierarchy.");
log_pass(
"Found legacy 'lxc.cgroup' keys, but system explicitly configured for legacy hybrid cgroup hierarchy."
);
} else {
log_warn("The following CTs have 'lxc.cgroup' keys configured, which will be ignored in the new default unified cgroupv2:\n"
." " . join(", ", $affected_cts_cgroup_keys->@*) ."\n"
." Often it can be enough to change to the new 'lxc.cgroup2' prefix after the upgrade to Proxmox VE 7.x");
log_warn(
"The following CTs have 'lxc.cgroup' keys configured, which will be ignored in the new default unified cgroupv2:\n"
. " "
. join(", ", $affected_cts_cgroup_keys->@*) . "\n"
. " Often it can be enough to change to the new 'lxc.cgroup2' prefix after the upgrade to Proxmox VE 7.x"
);
}
} else {
log_pass("No legacy 'lxc.cgroup' keys found.");
@ -880,8 +911,7 @@ sub check_storage_content {
if ($number > 0) {
log_info(
"storage '$storeid' - neither content type 'images' nor 'rootdir' configured, but"
."found $number guest volume(s)"
);
. "found $number guest volume(s)");
}
}
@ -974,8 +1004,8 @@ sub check_storage_content {
}
if ($found) {
log_warn("Proxmox VE enforces stricter content type checks since 7.0. The guests above " .
"might not work until the storage configuration is fixed.");
log_warn("Proxmox VE enforces stricter content type checks since 7.0. The guests above "
. "might not work until the storage configuration is fixed.");
}
if ($pass) {
@ -1010,22 +1040,26 @@ sub check_storage_content_dirs {
for my $subdir (keys $resolved_subdirs->%*) {
if (scalar($resolved_subdirs->{$subdir}->@*) > 1) {
my $types = join(", ", $resolved_subdirs->{$subdir}->@*);
log_warn("storage '$storeid' uses directory $subdir for multiple content types ($types).");
log_warn(
"storage '$storeid' uses directory $subdir for multiple content types ($types)."
);
$any_problematic = 1;
}
}
}
if ($any_problematic) {
log_fail("re-using directory for multiple content types (see above) is no longer supported in Proxmox VE 8!")
log_fail(
"re-using directory for multiple content types (see above) is no longer supported in Proxmox VE 8!"
);
} else {
log_pass("no storage re-uses a directory for multiple content types.")
log_pass("no storage re-uses a directory for multiple content types.");
}
}
sub check_containers_cgroup_compat {
if ($forced_legacy_cgroup) {
log_warn("System explicitly configured for legacy hybrid cgroup hierarchy.\n"
." NOTE: support for the hybrid cgroup hierarchy will be removed in future Proxmox VE 9 (~ 2025)."
. " NOTE: support for the hybrid cgroup hierarchy will be removed in future Proxmox VE 9 (~ 2025)."
);
}
@ -1039,14 +1073,16 @@ sub check_containers_cgroup_compat {
'/lib/systemd',
'/usr/lib/systemd',
'/usr/lib/x86_64-linux-gnu/systemd',
'/usr/lib64/systemd'
'/usr/lib64/systemd',
);
my $libsd;
for my $dir (@dirs) {
$libsd = PVE::Tools::dir_glob_regex($dir, "libsystemd-shared-.+\.so");
last if defined($libsd);
}
if (defined($libsd) && $libsd =~ /libsystemd-shared-(\d+)(\.\d-\d)?(\.fc\d\d)?\.so/) {
if (
defined($libsd) && $libsd =~ /libsystemd-shared-(\d+)(\.\d-\d)?(\.fc\d\d)?\.so/
) {
return $1;
}
@ -1087,10 +1123,13 @@ sub check_containers_cgroup_compat {
my $log_problem = sub {
my ($ctid) = @_;
my $extra = $forced_legacy_cgroup ? '' : " or set systemd.unified_cgroup_hierarchy=0 in the Proxmox VE hosts' kernel cmdline";
my $extra =
$forced_legacy_cgroup
? ''
: " or set systemd.unified_cgroup_hierarchy=0 in the Proxmox VE hosts' kernel cmdline";
log_warn(
"Found at least one CT ($ctid) which does not support running in a unified cgroup v2 layout\n"
." Consider upgrading the Containers distro${extra}! Skipping further CT compat checks."
. " Consider upgrading the Containers distro${extra}! Skipping further CT compat checks."
);
};
@ -1151,7 +1190,7 @@ sub check_containers_cgroup_compat {
eval { PVE::LXC::umount_all($ctid, $storage_cfg, $conf) };
}
};
}
sub check_lxcfs_fuse_version {
log_info("Checking if LXCFS is running with FUSE3 library, if already upgraded..");
@ -1174,9 +1213,11 @@ sub check_lxcfs_fuse_version {
}
if ($lxcfs_maps =~ /\/libfuse.so.2/s) {
log_warn("systems seems to be upgraded but LXCFS is still running with FUSE 2 library, not yet rebooted?")
log_warn(
"systems seems to be upgraded but LXCFS is still running with FUSE 2 library, not yet rebooted?"
);
} elsif ($lxcfs_maps =~ /\/libfuse3.so.3/s) {
log_pass("systems seems to be upgraded and LXCFS is running with FUSE 3 library")
log_pass("systems seems to be upgraded and LXCFS is running with FUSE 3 library");
}
return;
}
@ -1234,10 +1275,10 @@ sub check_apt_repos {
if (!defined($mismatches)) {
$mismatches = [];
push $mismatches->@*,
{ suite => $found_suite, where => $found_suite_where},
{ suite => $suite, where => $where};
{ suite => $found_suite, where => $found_suite_where },
{ suite => $suite, where => $where };
} else {
push $mismatches->@*, { suite => $suite, where => $where};
push $mismatches->@*, { suite => $suite, where => $where };
}
}
}
@ -1253,8 +1294,9 @@ sub check_apt_repos {
my @strange_list = map { "found suite $_->{suite} at $_->{where}" } $strange_suites->@*;
log_notice(
"found unusual suites that are neither old '$old_suite' nor new '$new_suite':"
."\n " . join("\n ", @strange_list)
."\n Please ensure these repositories are shipping compatible packages for the upgrade!"
. "\n "
. join("\n ", @strange_list)
. "\n Please ensure these repositories are shipping compatible packages for the upgrade!"
);
}
if (defined($mismatches)) {
@ -1262,11 +1304,13 @@ sub check_apt_repos {
log_fail(
"Found mixed old and new package repository suites, fix before upgrading! Mismatches:"
."\n " . join("\n ", @mismatch_list)
."\n Configure the same base-suite for all Proxmox and Debian provided repos and ask"
." original vendor for any third-party repos."
."\n E.g., for the upgrade to Proxmox VE ".($min_pve_major + 1)." use the '$new_suite' suite."
);
. "\n "
. join("\n ", @mismatch_list)
. "\n Configure the same base-suite for all Proxmox and Debian provided repos and ask"
. " original vendor for any third-party repos."
. "\n E.g., for the upgrade to Proxmox VE "
. ($min_pve_major + 1)
. " use the '$new_suite' suite.");
} elsif (defined($strange_suites)) {
log_notice("found no suite mismatches, but found at least one strange suite");
} else {
@ -1278,7 +1322,7 @@ sub check_nvidia_vgpu_service {
log_info("Checking for existence of NVIDIA vGPU Manager..");
my $msg = "NVIDIA vGPU Service found, possibly not compatible with newer kernel versions, check"
." with their documentation and https://pve.proxmox.com/wiki/Upgrade_from_7_to_8#Known_upgrade_issues.";
. " with their documentation and https://pve.proxmox.com/wiki/Upgrade_from_7_to_8#Known_upgrade_issues.";
my $state = $get_systemd_unit_state->("nvidia-vgpu-mgr.service", 1);
if ($state && $state eq 'active') {
@ -1291,57 +1335,59 @@ sub check_nvidia_vgpu_service {
}
sub check_time_sync {
my $unit_active = sub { return $get_systemd_unit_state->($_[0], 1) eq 'active' ? $_[0] : undef };
my $unit_active =
sub { return $get_systemd_unit_state->($_[0], 1) eq 'active' ? $_[0] : undef };
log_info("Checking for supported & active NTP service..");
if ($unit_active->('systemd-timesyncd.service')) {
log_warn(
"systemd-timesyncd is not the best choice for time-keeping on servers, due to only applying"
." updates on boot.\n While not necessary for the upgrade it's recommended to use one of:\n"
." * chrony (Default in new Proxmox VE installations)\n * ntpsec\n * openntpd\n"
. " updates on boot.\n While not necessary for the upgrade it's recommended to use one of:\n"
. " * chrony (Default in new Proxmox VE installations)\n * ntpsec\n * openntpd\n"
);
} elsif ($unit_active->('ntp.service')) {
log_info("Debian deprecated and removed the ntp package for Bookworm, but the system"
." will automatically migrate to the 'ntpsec' replacement package on upgrade.");
} elsif (my $active_ntp = ($unit_active->('chrony.service') || $unit_active->('openntpd.service') || $unit_active->('ntpsec.service'))) {
. " will automatically migrate to the 'ntpsec' replacement package on upgrade.");
} elsif (
my $active_ntp = (
$unit_active->('chrony.service')
|| $unit_active->('openntpd.service')
|| $unit_active->('ntpsec.service')
)
) {
log_pass("Detected active time synchronisation unit '$active_ntp'");
} else {
log_warn(
"No (active) time synchronisation daemon (NTP) detected, but synchronized systems are important,"
." especially for cluster and/or ceph!"
);
. " especially for cluster and/or ceph!");
}
}
sub check_bootloader {
log_info("Checking bootloader configuration...");
if (! -d '/sys/firmware/efi') {
if (!-d '/sys/firmware/efi') {
log_skip("System booted in legacy-mode - no need for additional packages");
return;
}
if ( -f "/etc/kernel/proxmox-boot-uuids") {
if (-f "/etc/kernel/proxmox-boot-uuids") {
if (!$upgraded) {
log_skip("not yet upgraded, no need to check the presence of systemd-boot");
return;
}
if ( -f "/usr/share/doc/systemd-boot/changelog.Debian.gz") {
if (-f "/usr/share/doc/systemd-boot/changelog.Debian.gz") {
log_pass("bootloader packages installed correctly");
return;
}
log_warn(
"proxmox-boot-tool is used for bootloader configuration in uefi mode"
log_warn("proxmox-boot-tool is used for bootloader configuration in uefi mode"
. " but the separate systemd-boot package is not installed,"
. " initializing new ESPs will not work until the package is installed"
);
. " initializing new ESPs will not work until the package is installed");
return;
} elsif ( ! -f "/usr/share/doc/grub-efi-amd64/changelog.Debian.gz" ) {
log_warn(
"System booted in uefi mode but grub-efi-amd64 meta-package not installed,"
} elsif (!-f "/usr/share/doc/grub-efi-amd64/changelog.Debian.gz") {
log_warn("System booted in uefi mode but grub-efi-amd64 meta-package not installed,"
. " new grub versions will not be installed to /boot/efi!"
. " Install grub-efi-amd64."
);
. " Install grub-efi-amd64.");
return;
} else {
log_pass("bootloader packages installed correctly");
@ -1359,7 +1405,7 @@ sub check_dkms_modules {
my $sig_pipe = $SIG{PIPE};
$SIG{PIPE} = "DEFAULT";
my $exit_code = eval {
run_command(['dkms', 'status', '-k', '`uname -r`'], outfunc => $set_count, noerr => 1)
run_command(['dkms', 'status', '-k', '`uname -r`'], outfunc => $set_count, noerr => 1);
};
$SIG{PIPE} = $sig_pipe;
@ -1392,7 +1438,7 @@ sub check_misc {
my $root_free = PVE::Tools::df('/', 10);
log_warn("Less than 5 GB free space on root file system.")
if defined($root_free) && $root_free->{avail} < 5 * 1000*1000*1000;
if defined($root_free) && $root_free->{avail} < 5 * 1000 * 1000 * 1000;
log_info("Checking for running guests..");
my $running_guests = 0;
@ -1406,9 +1452,10 @@ sub check_misc {
$running_guests += grep { $_->{status} eq 'running' } @$cts if defined($cts);
if ($running_guests > 0) {
log_warn("$running_guests running guest(s) detected - consider migrating or stopping them.")
log_warn(
"$running_guests running guest(s) detected - consider migrating or stopping them.");
} else {
log_pass("no running guest detected.")
log_pass("no running guest detected.");
}
log_info("Checking if the local node's hostname '$nodename' is resolvable..");
@ -1461,7 +1508,9 @@ sub check_misc {
log_fail("'$fn', certificate's $check->{name} public key size is less than 2048 bit");
$certs_check_failed = 1;
} else {
log_pass("Certificate '$fn' passed Debian Busters (and newer) security level for TLS connections ($size >= 2048)");
log_pass(
"Certificate '$fn' passed Debian Busters (and newer) security level for TLS connections ($size >= 2048)"
);
}
}
@ -1478,10 +1527,10 @@ sub check_misc {
my sub colored_if {
my ($str, $color, $condition) = @_;
return "". ($condition ? colored($str, $color) : $str);
return "" . ($condition ? colored($str, $color) : $str);
}
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'checklist',
path => 'checklist',
method => 'GET',
@ -1502,7 +1551,7 @@ __PACKAGE__->register_method ({
my ($param) = @_;
my $kernel_cli = PVE::Tools::file_get_contents('/proc/cmdline');
if ($kernel_cli =~ /systemd.unified_cgroup_hierarchy=0/){
if ($kernel_cli =~ /systemd.unified_cgroup_hierarchy=0/) {
$forced_legacy_cgroup = 1;
}
@ -1515,7 +1564,9 @@ __PACKAGE__->register_method ({
if ($param->{full}) {
check_containers_cgroup_compat();
} else {
log_skip("NOTE: Expensive checks, like CT cgroupv2 compat, not performed without '--full' parameter");
log_skip(
"NOTE: Expensive checks, like CT cgroupv2 compat, not performed without '--full' parameter"
);
}
print_header("SUMMARY");
@ -1531,13 +1582,18 @@ __PACKAGE__->register_method ({
if ($counters->{warn} > 0 || $counters->{fail} > 0) {
my $color = $counters->{fail} > 0 ? 'bold red' : 'yellow';
print colored("\nATTENTION: Please check the output for detailed information!\n", $color);
print colored("Try to solve the problems one at a time and then run this checklist tool again.\n", $color) if $counters->{fail} > 0;
print colored("\nATTENTION: Please check the output for detailed information!\n",
$color);
print colored(
"Try to solve the problems one at a time and then run this checklist tool again.\n",
$color,
) if $counters->{fail} > 0;
}
return undef;
}});
},
});
our $cmddef = [ __PACKAGE__, 'checklist', [], {}];
our $cmddef = [__PACKAGE__, 'checklist', [], {}];
1;

View file

@ -24,7 +24,7 @@ sub setup_environment {
PVE::RPCEnvironment->setup_default_cli_env();
}
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'update',
path => 'update',
method => 'PUT',
@ -33,7 +33,7 @@ __PACKAGE__->register_method ({
additionalProperties => 0,
properties => {},
},
returns => { type => 'null'},
returns => { type => 'null' },
code => sub {
my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg');
@ -41,16 +41,17 @@ __PACKAGE__->register_method ({
my $res = PVE::APLInfo::update($dccfg->{http_proxy});
if ($res) {
print STDOUT "update successful\n"
print STDOUT "update successful\n";
} else {
print STDERR "update failed - see /var/log/pveam.log for details\n"
print STDERR "update failed - see /var/log/pveam.log for details\n";
}
return undef;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'available',
path => 'available',
method => 'GET',
@ -64,9 +65,9 @@ __PACKAGE__->register_method ({
enum => ['system', 'mail', 'turnkeylinux'],
optional => 1,
},
}
},
returns => { type => 'null'},
},
returns => { type => 'null' },
code => sub {
my ($param) = @_;
@ -75,15 +76,16 @@ __PACKAGE__->register_method ({
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}}) {
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',
@ -98,10 +100,13 @@ __PACKAGE__->register_method ({
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
storage => get_standard_option('pve-storage-id', {
storage => get_standard_option(
'pve-storage-id',
{
description => "Only list templates on specified storage",
completion => \&PVE::Storage::complete_storage_enabled,
}),
},
),
},
},
returns => {
@ -124,21 +129,27 @@ __PACKAGE__->register_method ({
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 $res = [];
foreach my $item (@$vollist) {
eval { PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $item->{volid}); };
eval {
PVE::Storage::check_volume_access(
$rpcenv, $authuser, $cfg, undef, $item->{volid},
);
};
next if $@;
push @$res, $item;
}
return $res;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'remove',
path => 'remove',
method => 'DELETE',
@ -179,26 +190,30 @@ __PACKAGE__->register_method ({
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;

View file

@ -45,7 +45,7 @@ sub setup_environment {
PVE::RPCEnvironment->setup_default_cli_env();
}
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'purge',
path => 'purge',
method => 'POST',
@ -107,7 +107,8 @@ __PACKAGE__->register_method ({
PVE::Ceph::Tools::purge_all_ceph_files($services);
return undef;
}});
},
});
my sub has_valid_subscription {
my $info = eval { Proxmox::RS::Subscription::read_subscription('/etc/subscription') } // {};
@ -118,7 +119,7 @@ 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',
@ -156,27 +157,33 @@ __PACKAGE__->register_method ({
my $repo = $param->{'repository'} // 'enterprise';
my $enterprise_repo = $repo eq 'enterprise';
my $cdn = $enterprise_repo ? 'https://enterprise.proxmox.com' : 'http://download.proxmox.com';
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"
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";
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"
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});
die "unsupported ceph version: $cephver"
if !exists($available_ceph_releases->{$cephver});
my $repolist = "deb ${cdn}/debian/ceph-${cephver} bookworm $repo\n";
my $rendered_release = $available_ceph_releases->{$cephver}->{release} . ' ' . ucfirst($cephver);
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)? ";
@ -190,15 +197,18 @@ __PACKAGE__->register_method ({
if ($available_ceph_releases->{$cephver}->{unsupported}) {
if ($param->{'allow-experimental'}) {
warn "NOTE: installing experimental/tech-preview Ceph release ${rendered_release}!\n";
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)? ";
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
"refusing to install tech-preview Ceph release ${rendered_release} without 'allow-experimental' parameter!\n";
}
}
@ -207,12 +217,13 @@ __PACKAGE__->register_method ({
eval {
run_command(
['apt-get', '-q', 'update'],
outfunc => sub {},
outfunc => sub { },
errfunc => sub { print STDERR "$_[0]\n" },
)
);
};
my @apt_install = qw(apt-get --no-install-recommends -o Dpkg::Options::=--force-confnew install --);
my @apt_install =
qw(apt-get --no-install-recommends -o Dpkg::Options::=--force-confnew install --);
my @ceph_packages = qw(
ceph
ceph-common
@ -227,7 +238,8 @@ __PACKAGE__->register_method ({
# 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";
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) {
@ -241,13 +253,14 @@ __PACKAGE__->register_method ({
print "\nreloading API to load new Ceph RADOS library...\n";
run_command([
'systemctl', 'try-reload-or-restart', 'pvedaemon.service', 'pveproxy.service'
'systemctl', 'try-reload-or-restart', 'pvedaemon.service', 'pveproxy.service',
]);
return undef;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'status',
path => 'status',
method => 'GET',
@ -266,7 +279,8 @@ __PACKAGE__->register_method ({
timeout => 15,
);
return undef;
}});
},
});
my $get_storages = sub {
my ($fs, $is_default) = @_;
@ -286,7 +300,7 @@ my $get_storages = sub {
return $res;
};
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'destroyfs',
path => 'destroyfs',
method => 'DELETE',
@ -350,8 +364,8 @@ __PACKAGE__->register_method ({
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";
die "storage '$storeid' is not disabled, make sure to disable "
. "and unmount the storage first\n";
}
}
@ -359,7 +373,7 @@ __PACKAGE__->register_method ({
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}) };
eval { PVE::API2::Storage::Config->delete({ storage => $storeid }) };
if ($@) {
warn "failed to remove storage '$storeid': $@\n";
$err = 1;
@ -385,9 +399,10 @@ __PACKAGE__->register_method ({
};
return $rpcenv->fork_worker('cephdestroyfs', $fs_name, $user, $worker);
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'osd-details',
path => 'osd-details',
method => 'GET',
@ -428,7 +443,8 @@ __PACKAGE__->register_method ({
}
$res->{verbose} = 1 if $param->{verbose};
return $res;
}});
},
});
my $format_osddetails = sub {
my ($data, $schema, $options) = @_;
@ -442,10 +458,11 @@ my $format_osddetails = sub {
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'};
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";
$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 {
@ -454,11 +471,18 @@ my $format_osddetails = sub {
};
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 {
ls => [
'PVE::API2::Ceph::Pool',
'lspools',
[],
{ node => $nodename },
sub {
my ($data, $schema, $options) = @_;
PVE::CLIFormatter::print_api_result($data, $schema,
PVE::CLIFormatter::print_api_result(
$data,
$schema,
[
'pool_name',
'size',
@ -473,54 +497,73 @@ our $cmddef = {
'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 {
$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],
},
$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],
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,
__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;

View file

@ -45,19 +45,32 @@ sub param_mapping {
my $load_file_and_encode = sub {
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',
'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],
[
'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],
[
'data',
$load_file_and_encode,
"File with one key-value pair per line, will be base64url encode for storage in plugin config.",
0,
],
],
};
@ -89,7 +102,8 @@ __PACKAGE__->register_method({
print "Directory endpoints:\n";
my $i = 0;
while ($i < @$directories) {
print $i, ") ", $directories->[$i]->{name}, " (", $directories->[$i]->{url}, ")\n";
print $i, ") ", $directories->[$i]->{name}, " (", $directories->[$i]->{url},
")\n";
$i++;
}
print $i, ") Custom\n";
@ -136,7 +150,8 @@ __PACKAGE__->register_method({
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]: ');
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";
@ -154,31 +169,50 @@ __PACKAGE__->register_method({
print "\nAttempting to register account with '$param->{directory}'..\n";
$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 {
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 } ],
},
],
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 {
info => [
'PVE::API2::Certificates',
'info',
[],
{ node => $nodename },
sub {
my ($res, $schema, $options) = @_;
if (!$options->{'output-format'} || $options->{'output-format'} eq 'text') {
@ -189,84 +223,157 @@ our $cmddef = {
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 {
},
$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,
],
delete =>
['PVE::API2::Certificates', 'remove_custom_cert', ['restart'], { node => $nodename }],
},
task => {
list => [ 'PVE::API2::Tasks', 'node_tasks', [], { node => $nodename }, sub {
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')
{
} 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 {
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],
},
$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 {
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 {
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 {
},
],
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 ],
},
$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 ],
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 {
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 {
},
$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'] ],
},
$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 {
wakeonlan => [
'PVE::API2::Nodes::Nodeinfo',
'wakeonlan',
['node'],
{},
sub {
my ($mac_addr) = @_;
print "Wake on LAN packet send for '$mac_addr'\n";
} ],
},
],
};

View file

@ -46,7 +46,7 @@ sub setup_environment {
}
sub complete_api_path {
my($text) = @_;
my ($text) = @_;
my ($dir, undef, $rest) = $text =~ m|^(.*/)?(([^/]*))?$|;
@ -91,7 +91,11 @@ sub check_proxyto {
if ($info->{proxyto} || $info->{proxyto_callback}) {
my $node = PVE::API2Tools::resolve_proxyto(
$rpcenv, $info->{proxyto_callback}, $info->{proxyto}, $all_params);
$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;
@ -120,13 +124,13 @@ sub proxy_handler {
my @pvesh_cmd = ('pvesh', '--noproxy', $cmd, $path, '--output-format', 'json');
if (scalar(@$args)) {
my $cmdargs = [ String::ShellQuote::shell_quote(@$args) ];
my $cmdargs = [String::ShellQuote::shell_quote(@$args)];
push @pvesh_cmd, @$cmdargs;
}
my $res = '';
PVE::Tools::run_command(
[ $ssh_tunnel_cmd->@*, '--', @pvesh_cmd ],
[$ssh_tunnel_cmd->@*, '--', @pvesh_cmd],
errmsg => "proxy handler failed",
outfunc => sub { $res .= shift },
);
@ -149,7 +153,7 @@ sub extract_children {
if ($href =~ m/^\{(\S+)\}$/) {
my $prop = $1;
foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @$data) {
foreach my $elem (sort { $a->{$prop} cmp $b->{$prop} } @$data) {
next if !ref($elem);
my $value = $elem->{$prop};
push @$res, $value;
@ -253,7 +257,6 @@ sub extract_path_info {
return $info;
}
my $path_properties = {};
my $api_path_property = {
@ -267,7 +270,7 @@ my $api_path_property = {
my $uri_param = {};
if (my $info = extract_path_info($uri_param)) {
foreach my $key (keys %{$info->{parameters}->{properties}}) {
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,12 +297,14 @@ 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";
@ -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)
: {};
@ -357,7 +364,7 @@ sub call_api_method {
&& ref($data->{download}) eq 'HASH'
&& ($info->{download_allowed} || $info->{download})
) {
$data = $handle_streamed_response->($data->{download})
$data = $handle_streamed_response->($data->{download});
}
}
@ -366,7 +373,7 @@ 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',
@ -408,21 +415,22 @@ __PACKAGE__->register_method ({
$res = [];
foreach my $c (@$children) {
my $item = { name => $c, capabilities => resource_cap("$path/$c")};
my $item = { name => $c, capabilities => resource_cap("$path/$c") };
push @$res, $item;
}
}
my $schema = { type => 'array', items => { type => 'object' }};
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;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'get',
path => 'get',
method => 'GET',
@ -438,9 +446,10 @@ __PACKAGE__->register_method ({
call_api_method('get', $param);
return undef;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'set',
path => 'set',
method => 'PUT',
@ -456,9 +465,10 @@ __PACKAGE__->register_method ({
call_api_method('set', $param);
return undef;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'create',
path => 'create',
method => 'POST',
@ -474,9 +484,10 @@ __PACKAGE__->register_method ({
call_api_method('create', $param);
return undef;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'delete',
path => 'delete',
method => 'DELETE',
@ -492,9 +503,10 @@ __PACKAGE__->register_method ({
call_api_method('delete', $param);
return undef;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'usage',
path => 'usage',
method => 'GET',
@ -516,7 +528,7 @@ __PACKAGE__->register_method ({
command => {
description => "API command.",
type => 'string',
enum => [ keys %$method_map ],
enum => [keys %$method_map],
optional => 1,
},
},
@ -538,14 +550,26 @@ __PACKAGE__->register_method ({
if ($param->{verbose}) {
print $handler->usage_str(
$info->{name}, "pvesh $cmd $path", undef, $uri_param, 'full');
$info->{name},
"pvesh $cmd $path",
undef,
$uri_param,
'full',
);
} else {
print "USAGE: " . $handler->usage_str(
$info->{name}, "pvesh $cmd $path", undef, $uri_param, 'short');
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 });
if ($param->{returns}) {
my $schema =
to_json($info->{returns}, { utf8 => 1, canonical => 1, pretty => 1 });
print "RETURNS: $schema\n";
}
}
@ -554,20 +578,21 @@ __PACKAGE__->register_method ({
if ($param->{command}) {
die "no '$param->{command}' handler for '$path'\n";
} else {
die "no such resource '$path'\n"
die "no such resource '$path'\n";
}
}
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;

View file

@ -36,7 +36,7 @@ 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"
@ -47,20 +47,24 @@ my $check_wanted_volid = sub {
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." }),
'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',
type => 'string',
format => 'pve-storage-id-list',
optional => 1,
},
force => {
@ -70,7 +74,8 @@ __PACKAGE__->register_method ({
default => 0,
},
last_sync => {
description => "Time (UNIX epoch) of last successful sync. If not specified, all replication snapshots get removed.",
description =>
"Time (UNIX epoch) of last successful sync. If not specified, all replication snapshots get removed.",
type => 'integer',
minimum => 0,
optional => 1,
@ -92,9 +97,10 @@ __PACKAGE__->register_method ({
my $local_node = PVE::INotify::nodename();
die "no volumes specified\n"
if !$param->{force} && !scalar(@{$param->{'extra-args'}});
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"
@ -110,17 +116,18 @@ __PACKAGE__->register_method ({
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}};
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'}}) {
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 $storage_list = [sort keys %$storage_hash];
# activate all used storage
my $cache = {};
@ -136,7 +143,9 @@ __PACKAGE__->register_method ({
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);
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};
@ -146,7 +155,7 @@ __PACKAGE__->register_method ({
# 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} // {}};
} keys %{ $local_snapshots->{$volid} // {} };
if ($stale) {
$logfunc->("$jobid: delete stale volume '$volid'");
@ -158,21 +167,26 @@ __PACKAGE__->register_method ({
print to_json($local_snapshots) . "\n";
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." }),
'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.",
description =>
"Time (UNIX epoch) of last successful sync. If not specified, all replication snapshots gets removed.",
type => 'integer',
minimum => 0,
optional => 1,
@ -183,7 +197,8 @@ __PACKAGE__->register_method ({
code => sub {
my ($param) = @_;
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 $last_sync = $param->{last_sync} // 0;
my $local_node = PVE::INotify::nodename();
@ -196,14 +211,14 @@ __PACKAGE__->register_method ({
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'}}) {
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) = @_;
@ -213,13 +228,15 @@ __PACKAGE__->register_method ({
PVE::Replication::prepare($storecfg, $volids, $jobid, $last_sync, undef, $logfunc);
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 => {
@ -264,9 +281,10 @@ __PACKAGE__->register_method ({
}
return undef;
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'enable',
path => 'enable',
method => 'POST',
@ -284,9 +302,10 @@ __PACKAGE__->register_method ({
$param->{disable} = 0;
return PVE::API2::ReplicationConfig->update($param);
}});
},
});
__PACKAGE__->register_method ({
__PACKAGE__->register_method({
name => 'disable',
path => 'disable',
method => 'POST',
@ -304,21 +323,24 @@ __PACKAGE__->register_method ({
$param->{disable} = 1;
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']],
},
parameters => {
additionalProperties => 0,
properties => {
vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_vmid }),
vmid =>
get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_vmid }),
state => {
description => "Job state as JSON decoded string.",
type => 'string',
@ -331,11 +353,12 @@ __PACKAGE__->register_method ({
my $vmid = extract_param($param, 'vmid');
my $json_string = extract_param($param, 'state');
my $remote_job_state= decode_json $json_string;
my $remote_job_state = decode_json $json_string;
PVE::ReplicationState::write_vmid_job_states($remote_job_state, $vmid);
return undef;
}});
},
});
my $print_job_list = sub {
my ($list) = @_;
@ -348,10 +371,12 @@ my $print_job_list = sub {
my $plugin = PVE::ReplicationConfig->lookup($job->{type});
my $tid = $plugin->get_unique_target_id($job);
printf($format, $job->{id}, $tid,
printf(
$format,
$job->{id}, $tid,
defined($job->{schedule}) ? $job->{schedule} : '*/15',
defined($job->{rate}) ? $job->{rate} : '-',
$job->{disable} ? 'no' : 'yes'
$job->{disable} ? 'no' : 'yes',
);
}
};
@ -361,7 +386,15 @@ 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});
@ -385,32 +418,41 @@ my $print_job_status = sub {
my $state = $job->{pid} ? "SYNCING" : $job->{error} // 'OK';
my $enabled = $job->{disable} ? 'No' : 'Yes';
printf($format, $job->{id}, $enabled, $tid,
$timestr, $nextstr, $job->{duration} // '-',
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;

View file

@ -25,7 +25,8 @@ __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 => {
@ -45,22 +46,30 @@ __PACKAGE__->register_method({
$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);
return;
}});
},
});
our $cmddef = {
update => [ 'PVE::API2::Subscription', 'update', undef, { node => $nodename } ],
get => [ 'PVE::API2::Subscription', 'get', undef, { node => $nodename }, sub {
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 } ],
},
],
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;

View file

@ -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,
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;

View file

@ -5,7 +5,7 @@ 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];
}
@ -83,7 +83,8 @@ my sub get_ceph_release_def {
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};
$ceph_release->{'available-for-current-pve-release'} =
$ceph_release->{'available-for-pve-release'}->{$current_pve_major_release};
}
$_ceph_release_info = $ceph_release_info;
@ -95,6 +96,7 @@ 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();
@ -112,16 +114,17 @@ 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->%*;
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"

View file

@ -23,17 +23,25 @@ sub get_local_services {
my $path = "/etc/systemd/system/ceph-$type.target.wants";
my $regex = "ceph-$type\@(.*)\.service";
PVE::Tools::dir_glob_foreach($path, $regex, sub {
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 {
PVE::Tools::dir_glob_foreach(
$path,
$regex,
sub {
my (undef, $clustername, $id) = @_;
$res->{$type}->{$id}->{direxists} = 1;
});
},
);
}
return $res;
}
@ -57,7 +65,11 @@ sub broadcast_ceph_versions {
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) {
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
}
}
@ -77,7 +89,9 @@ sub get_ceph_versions {
if (defined(my $versions = PVE::Cluster::get_node_kv("ceph-versions"))) {
$res = {
map { eval { $_ => decode_json($versions->{$_}) } } keys %$versions
map {
eval { $_ => decode_json($versions->{$_}) }
} keys %$versions
};
}
@ -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;
@ -114,7 +130,7 @@ sub get_services_info {
my $services = get_cluster_service($type);
foreach my $host (sort keys %$services) {
foreach my $id (sort keys %{$services->{$host}}) {
foreach my $id (sort keys %{ $services->{$host} }) {
my $service = $result->{$id} = $services->{$host}->{$id};
$service->{host} = $host;
$service->{name} = $id;
@ -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 {
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;
}
@ -193,18 +213,17 @@ sub get_cluster_mds_state {
$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}}) {
foreach my $mds (@{ $fsmap->{standbys} }) {
$add_state->($mds);
}
for my $fs_info (@{$fsmap->{filesystems}}) {
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
@ -294,7 +313,7 @@ sub create_mds {
broadcast_ceph_services();
return undef;
};
}
sub destroy_mds {
my ($id, $rados) = @_;
@ -317,20 +336,20 @@ sub destroy_mds {
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'
format => 'plain',
});
broadcast_ceph_services();
return undef;
};
}
# MGR
@ -355,7 +374,7 @@ sub create_mgr {
osd => 'allow *',
mds => 'allow *',
],
format => 'plain'
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);

View file

@ -61,7 +61,7 @@ sub get_local_version {
my $ceph_version;
run_command(
[ $ceph_service->{ceph_bin}, '--version' ],
[$ceph_service->{ceph_bin}, '--version'],
noerr => $noerr,
outfunc => sub { $ceph_version = shift if !defined $ceph_version },
);
@ -92,7 +92,7 @@ sub parse_ceph_version : prototype($) {
if ($ceph_version =~ /$re_ceph_version/) {
my ($version, $buildcommit) = ($1, $2);
my $subversions = [ split(/\.|-/, $version) ];
my $subversions = [split(/\.|-/, $version)];
# return (version, buildid, [major, minor, ...]) : major;
return wantarray
@ -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,7 +124,7 @@ 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};
@ -140,17 +140,20 @@ sub purge_all_ceph_files {
$path = '/var/log/ceph' if $service eq 'logs';
if ($dir_exists) {
my $err;
File::Path::remove_tree($path, {
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) {
@ -180,7 +183,7 @@ sub purge_all_ceph_services {
}
}
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,7 +192,7 @@ 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()) {
if (!-x $ceph_service->{$service} || -f ceph_install_flag_file()) {
die "binary not installed: $ceph_service->{$service}\n" if !$noerr;
return undef;
}
@ -197,13 +200,12 @@ sub check_ceph_installed {
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,8 +217,8 @@ 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";
@ -232,7 +234,7 @@ sub check_ceph_enabled {
return undef if !check_ceph_inited($noerr);
if (! -f $ceph_cfgpath) {
if (!-f $ceph_cfgpath) {
die "pveceph configuration not enabled\n" if !$noerr;
return undef;
}
@ -282,7 +284,7 @@ sub set_pool {
}
# 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);
@ -305,7 +307,7 @@ sub set_pool {
}
if (scalar(keys %$param) > 0) {
my $missing = join(', ', sort keys %$param );
my $missing = join(', ', sort keys %$param);
die "Could not set: $missing\n";
}
@ -428,8 +430,8 @@ sub setup_pve_symlinks {
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";
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,15 +439,15 @@ 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 *' ");
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) {
if (!-f $ckeyring_path) {
run_command("cp $pve_ckeyring_path $ckeyring_path");
run_command("chown ceph:ceph $ckeyring_path");
}
@ -495,7 +497,7 @@ sub ceph_volume_list {
}
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,83 +522,87 @@ 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};
$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};
$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,
optional => 1,
},
noup => {
description => 'OSDs are not allowed to start.',
type => 'boolean',
optional=> 1,
optional => 1,
},
nodown => {
description => 'OSD failure reports are being ignored, such that the monitors will not mark OSDs down.',
description =>
'OSD failure reports are being ignored, such that the monitors will not mark OSDs down.',
type => 'boolean',
optional=> 1,
optional => 1,
},
noout => {
description => 'OSDs will not automatically be marked out after the configured interval.',
description =>
'OSDs will not automatically be marked out after the configured interval.',
type => 'boolean',
optional=> 1,
optional => 1,
},
noin => {
description => 'OSDs that were previously marked out will not be marked back in when they start.',
description =>
'OSDs that were previously marked out will not be marked back in when they start.',
type => 'boolean',
optional=> 1,
optional => 1,
},
nobackfill => {
description => 'Backfilling of PGs is suspended.',
type => 'boolean',
optional=> 1,
optional => 1,
},
norebalance => {
description => 'Rebalancing of PGs is suspended.',
type => 'boolean',
optional=> 1,
optional => 1,
},
norecover => {
description => 'Recovery of PGs is suspended.',
type => 'boolean',
optional=> 1,
optional => 1,
},
noscrub => {
description => 'Scrubbing is disabled.',
type => 'boolean',
optional=> 1,
optional => 1,
},
'nodeep-scrub' => {
description => 'Deep Scrubbing is disabled.',
type => 'boolean',
optional=> 1,
optional => 1,
},
notieragent => {
description => 'Cache tiering activity is suspended.',
type => 'boolean',
optional=> 1,
optional => 1,
},
};
return $possible_flags;
@ -646,9 +652,7 @@ 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;

View file

@ -22,8 +22,7 @@ sub update_cert_cache {
$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 {
@ -64,7 +63,7 @@ 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');
@ -81,7 +80,10 @@ sub check_cert_fingerprint {
# 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");
syslog(
'info',
"Could not verify remote node certificate '$fp' with list of pinned certificates, refreshing cache",
);
update_cert_cache();
return &$check();
}

View file

@ -9,26 +9,35 @@ use PVE::Tools;
my $account_prefix = '/etc/pve/priv/acme';
PVE::JSONSchema::register_standard_option('pve-acme-account-name', {
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', {
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', {
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';
@ -84,7 +93,7 @@ sub set_cert_files {
};
warn "$@\n" if $@;
}
die "Setting certificate files failed - $err\n"
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 {
PVE::Tools::dir_glob_foreach(
$account_prefix,
qr/[^.]+.*/,
sub {
my ($name) = @_;
push @$accounts, $name
if PVE::JSONSchema::pve_verify_configid($name, 1);
});
},
);
return $accounts;
}

View file

@ -14,7 +14,7 @@ PVE::Status::Plugin->init();
sub foreach_plug($&) {
my ($status_cfg, $code) = @_;
for my $id (sort keys %{$status_cfg->{ids}}) {
for my $id (sort keys %{ $status_cfg->{ids} }) {
my $plugin_config = $status_cfg->{ids}->{$id};
next if $plugin_config->{disable};
@ -46,18 +46,22 @@ sub transactions_start {
my $transactions = [];
foreach_plug($cfg, sub {
foreach_plug(
$cfg,
sub {
my ($plugin, $id, $plugin_config) = @_;
my $connection = $plugin->_connect($plugin_config, $id);
push @$transactions, {
push @$transactions,
{
connection => $connection,
cfg => $plugin_config,
id => $id,
data => '',
};
});
},
);
return $transactions;
}

View file

@ -67,10 +67,12 @@ 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')) {
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;
}
@ -146,7 +148,8 @@ sub rest_handler {
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!"
$p =>
"duplicate parameter (already defined in URI) with conflicting values!",
});
}
$uri_param->{$p} = $params->{$p};
@ -160,7 +163,11 @@ sub rest_handler {
if ($info->{proxyto} || $info->{proxyto_callback}) {
my $node = PVE::API2Tools::resolve_proxyto(
$rpcenv, $info->{proxyto_callback}, $info->{proxyto}, $uri_param);
$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};
@ -172,7 +179,7 @@ sub rest_handler {
my $euid = $>;
if ($info->{protected} && ($euid != 0)) {
$resp = { proxy => 'localhost' , proxy_params => $params };
$resp = { proxy => 'localhost', proxy_params => $params };
return;
}

View file

@ -41,7 +41,10 @@ my $saved_config_props = [qw(enabled schedule)];
sub detect_changed_runtime_props {
my ($jobid, $type, $cfg) = @_;
lock_job_state($jobid, $type, sub {
lock_job_state(
$jobid,
$type,
sub {
my $old_state = read_job_state($jobid, $type) // $default_state;
my $updated = 0;
@ -64,14 +67,15 @@ sub detect_changed_runtime_props {
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);
@ -105,7 +109,7 @@ my $get_job_task_status = sub {
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}) {
@ -125,7 +129,10 @@ 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 {
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
@ -142,7 +149,8 @@ sub update_job_stopped {
my $path = $get_state_file->($jobid, $type);
PVE::Tools::file_set_contents($path, encode_json($new_state));
});
},
);
}
}
@ -150,7 +158,10 @@ sub update_job_stopped {
sub create_job {
my ($jobid, $type, $cfg) = @_;
lock_job_state($jobid, $type, sub {
lock_job_state(
$jobid,
$type,
sub {
my $state = read_job_state($jobid, $type) // $default_state;
if ($state->{state} ne 'created') {
@ -166,7 +177,8 @@ sub create_job {
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,7 +197,10 @@ 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 {
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
@ -197,14 +212,18 @@ sub starting_job {
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 {
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';
@ -226,20 +245,25 @@ sub started_job {
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 {
lock_job_state(
$jobid,
$type,
sub {
my $old_state = read_job_state($jobid, $type) // $default_state;
$old_state->{updated} = time();
my $path = $get_state_file->($jobid, $type);
PVE::Tools::file_set_contents($path, encode_json($old_state));
});
},
);
}
sub get_last_runtime {
@ -266,7 +290,7 @@ sub run_jobs {
my $jobs_cfg = cfs_read_file('jobs.cfg');
my $nodename = PVE::INotify::nodename();
foreach my $id (sort keys %{$jobs_cfg->{ids}}) {
foreach my $id (sort keys %{ $jobs_cfg->{ids} }) {
my $cfg = $jobs_cfg->{ids}->{$id};
my $type = $cfg->{type};
my $schedule = delete $cfg->{schedule};
@ -311,7 +335,10 @@ sub run_jobs {
# creates and removes statefiles for job configs
sub synchronize_job_states_with_config {
cfs_lock_file('jobs.cfg', undef, sub {
cfs_lock_file(
'jobs.cfg',
undef,
sub {
my $data = cfs_read_file('jobs.cfg');
for my $id (keys $data->{ids}->%*) {
@ -329,14 +356,19 @@ sub synchronize_job_states_with_config {
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 {
PVE::Tools::dir_glob_foreach(
$state_dir,
"(${type_regex})-(.*).json",
sub {
my ($path, $type, $id) = @_;
if (!defined($data->{ids}->{$id})) {
remove_job($id, $type);
}
});
});
},
);
},
);
die $@ if $@;
}

View file

@ -15,7 +15,9 @@ my $MAXDOMAINS = 5;
my $node_config_lock = '/var/lock/pvenode.lock';
PVE::JSONSchema::register_format('pve-acme-domain', sub {
PVE::JSONSchema::register_format(
'pve-acme-domain',
sub {
my ($domain, $noerr) = @_;
my $label = qr/[a-z0-9][a-z0-9_-]*/i;
@ -23,9 +25,12 @@ PVE::JSONSchema::register_format('pve-acme-domain', sub {
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 {
PVE::JSONSchema::register_format(
'pve-acme-alias',
sub {
my ($alias, $noerr) = @_;
my $label = qr/[a-z0-9_][a-z0-9_-]*/i;
@ -33,7 +38,8 @@ PVE::JSONSchema::register_format('pve-acme-alias', sub {
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) = @_;
@ -81,12 +87,13 @@ 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.",
. " 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.',
description =>
'Initial delay in seconds, before starting all the Virtual Guests with on-boot enabled.',
type => 'integer',
minimum => 0,
maximum => 300,
@ -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,
};
};
}
my $conf_schema = {
type => 'object',
@ -236,7 +243,7 @@ sub get_wakeonlan_config {
my $res = {};
if (defined($node_conf->{wakeonlan})) {
$res = eval {
PVE::JSONSchema::parse_property_string($wakeonlan_desc, $node_conf->{wakeonlan})
PVE::JSONSchema::parse_property_string($wakeonlan_desc, $node_conf->{wakeonlan});
};
die $@ if $@;
}
@ -254,9 +261,7 @@ sub get_acme_conf {
my $res = {};
if (defined($node_conf->{acme})) {
$res = eval {
PVE::JSONSchema::parse_property_string($acmedesc, $node_conf->{acme})
};
$res = eval { PVE::JSONSchema::parse_property_string($acmedesc, $node_conf->{acme}) };
if (my $err = $@) {
return undef if $noerr;
die $err;
@ -275,13 +280,12 @@ sub get_acme_conf {
$res->{account} //= 'default';
for my $index (0..$MAXDOMAINS) {
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)
};
my $parsed =
eval { PVE::JSONSchema::parse_property_string($acme_domain_desc, $domain_rec) };
if (my $err = $@) {
return undef if $noerr;
die $err;
@ -290,7 +294,7 @@ sub get_acme_conf {
if (my $exists = $res->{domains}->{$domain}) {
return undef if $noerr;
die "duplicate domain '$domain' in ACME config properties"
." 'acmedomain$index' and '$exists->{_configkey}'\n";
. " 'acmedomain$index' and '$exists->{_configkey}'\n";
}
$parsed->{plugin} //= 'standalone';

View file

@ -15,13 +15,14 @@ my $get_cache = sub {
my $uid = getpwnam('root');
my $gid = getgrnam('www-data');
$cache = Proxmox::RS::SharedCache->new({
$cache = Proxmox::RS::SharedCache->new(
{
path => "/run/pve/metrics",
owner => $uid,
group => $gid,
entry_mode => 0640, # Entry permissions
keep_old => OLD_GENERATIONS,
}
},
);
}
@ -60,7 +61,7 @@ my sub gauge {
value => $value + 0,
timestamp => $timestamp + 0,
type => 'gauge',
}
};
}
my sub derive {
@ -72,7 +73,7 @@ my sub derive {
value => $value + 0,
timestamp => $timestamp + 0,
type => 'derive',
}
};
}
my $nodename = PVE::INotify::nodename();

View file

@ -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 {
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";
});
$text .= PVE::Tools::file_get_contents($target_dir . $file) . "\n";
},
);
return $text;
}
@ -48,8 +52,7 @@ my $init_report_cmds = sub {
title => 'overall system load info',
order => 20,
cmds => [
'top -b -c -w512 -n 1 -o TIME | head -n 30',
'head /proc/pressure/*',
'top -b -c -w512 -n 1 -o TIME | head -n 30', 'head /proc/pressure/*',
],
},
storage => {
@ -113,8 +116,7 @@ my $init_report_cmds = sub {
hardware => {
order => 70,
cmds => [
'dmidecode -t bios',
'lspci -nnk',
'dmidecode -t bios', 'lspci -nnk',
],
},
'block devices' => {
@ -129,15 +131,13 @@ my $init_report_cmds = sub {
volumes => {
order => 90,
cmds => [
'pvs',
'lvs',
'vgs',
'pvs', 'lvs', 'vgs',
],
},
};
if (cmd_exists('zfs')) {
push @{$report_def->{volumes}->{cmds}},
push @{ $report_def->{volumes}->{cmds} },
'zpool status',
'zpool list -v',
'zfs list',
@ -146,7 +146,7 @@ my $init_report_cmds = sub {
}
if (-e '/etc/ceph/ceph.conf') {
push @{$report_def->{volumes}->{cmds}},
push @{ $report_def->{volumes}->{cmds} },
'pveceph status',
'ceph osd status',
'ceph df',
@ -161,7 +161,7 @@ my $init_report_cmds = sub {
}
if (cmd_exists('multipath')) {
push @{$report_def->{disks}->{cmds}},
push @{ $report_def->{disks}->{cmds} },
'cat /etc/multipath.conf',
'cat /etc/multipath/wwids',
'multipath -ll',
@ -189,19 +189,20 @@ sub generate {
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) {
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}}) {
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."...";
print STDERR "Process " . $command . "...";
$report .= "\n# $command\n";
PVE::Tools::run_command($command, %$run_cmd_params);
}

View file

@ -28,8 +28,8 @@ 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');
@ -48,7 +48,7 @@ sub init {
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;

View file

@ -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);
@ -150,14 +150,17 @@ sub init {
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.');
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 {
@ -231,7 +234,9 @@ sub get_index {
};
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};
@ -259,7 +264,7 @@ sub get_index {
version => "$version",
wtversion => $wtversion,
theme => $theme,
consenttext => $consent_text
consenttext => $consent_text,
};
# by default, load the normal index
@ -274,7 +279,7 @@ sub get_index {
}
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";

View file

@ -21,7 +21,7 @@ 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;
}
@ -34,7 +34,7 @@ my sub finish_jobs : prototype($) {
}
}
}
};
}
sub hup {
my ($self) = @_;
@ -85,9 +85,7 @@ sub run {
die "fork failed: $!\n";
} elsif ($child == 0) {
$self->after_fork_cleanup();
eval {
$sub->();
};
eval { $sub->(); };
if (my $err = $@) {
syslog('err', "$type: $err");
}
@ -106,20 +104,26 @@ sub run {
# 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 {
$fork->(
'jobs',
sub {
PVE::Jobs::run_jobs($first_run);
});
},
);
$first_run = 0;
};
PVE::Jobs::setup_dirs();
for (my $count = 1000;;$count++) {
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
@ -144,7 +148,7 @@ sub run {
# 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)) {
while (my $pids = running_job_pids($self)) {
kill 'TERM', $pids->@*; # send TERM to all workers at once, possible thundering herd - FIXME?
finish_jobs($self);
@ -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;

View file

@ -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);
@ -97,8 +97,10 @@ sub update_supported_cpuflags {
my $supported_cpuflags = eval { PVE::QemuServer::query_supported_cpu_flags() };
warn $@ if $@;
if (!$supported_cpuflags ||
(!$supported_cpuflags->{tcg} && !$supported_cpuflags->{kvm})) {
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;
@ -111,7 +113,10 @@ sub update_supported_cpuflags {
for my $accel ("tcg", "kvm") {
if ($supported_cpuflags->{$accel}) {
PVE::Cluster::broadcast_node_kv("cpuflags-$accel", join(' ', @{$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,8 +140,10 @@ 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
!defined($old->{cpus})
|| $old->{cpus} != $cpus
|| !defined($old->{memory})
|| $old->{memory} != $memory
|| ($old->{'cgroup-mode'} // -1) != ($cgroup_mode // -1)
) {
my $info = {
@ -180,10 +187,23 @@ sub update_node_status {
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);
@ -221,9 +241,10 @@ 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) {
@ -253,14 +274,41 @@ sub update_qemu_status {
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}]);
$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]);
$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);
@ -280,7 +328,7 @@ sub remove_stale_lxc_consoles {
foreach my $vmid (keys %$pidhash) {
next if defined($vmstatus->{$vmid});
syslog('info', "remove stale lxc-console for CT $vmid");
foreach my $pid (@{$pidhash->{$vmid}}) {
foreach my $pid (@{ $pidhash->{$vmid} }) {
kill(9, $pid);
}
}
@ -289,6 +337,7 @@ sub remove_stale_lxc_consoles {
my $rebalance_error_count = {};
my $NO_REBALANCE;
sub rebalance_lxc_containers {
# Make sure we can find the cpuset controller path:
return if $NO_REBALANCE;
@ -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
@ -328,9 +377,7 @@ sub rebalance_lxc_containers {
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");
};
eval { $newset->write_to_path("$cgbase/ns"); };
if (my $err = $@) {
warn $err if !$rebalance_error_count->{$vmid}++;
# restore original
@ -361,10 +408,8 @@ sub rebalance_lxc_containers {
next; # old style container
}
my ($conf, $cpuset) = eval {(
PVE::LXC::Config->load_config($vmid),
PVE::CpuSet->new_from_path($cgpath),
)};
my ($conf, $cpuset) =
eval { (PVE::LXC::Config->load_config($vmid), PVE::CpuSet->new_from_path($cgpath)) };
if (my $err = $@) {
warn $err;
next;
@ -372,7 +417,8 @@ sub rebalance_lxc_containers {
my @cpuset_members = $cpuset->members();
if (!PVE::LXC::Config->has_lxc_entry($conf, 'lxc.cgroup.cpuset.cpus')
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;
@ -430,7 +476,7 @@ sub rebalance_lxc_containers {
foreach my $bct (@balanced_cts) {
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()) {
@ -461,17 +507,41 @@ sub update_lxc_status {
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}]);
$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]);
$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);
@ -521,7 +591,7 @@ sub update_ceph_metadata {
sub update_sdn_status {
if($have_sdn) {
if ($have_sdn) {
my ($transport_status, $vnet_status) = PVE::Network::SDN::status();
my $status = $transport_status ? encode_json($transport_status) : undef;
@ -533,8 +603,7 @@ 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()),
'version-info', encode_json(PVE::pvecfg::version_info()),
);
$broadcast_version_info_done = 1;
}
@ -556,69 +625,47 @@ sub update_status {
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;
}
@ -640,7 +687,7 @@ sub run {
$next_update = time() + $updatetime;
if ($cycle) {
my ($ccsec, $cusec) = gettimeofday ();
my ($ccsec, $cusec) = gettimeofday();
eval {
# syslog('info', "start status update");
PVE::Cluster::cfs_update();
@ -652,8 +699,8 @@ sub run {
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);
@ -669,16 +716,25 @@ sub run {
} 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)");
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); };
while (
(time() < $next_update)
&& ($wcount < $updatetime)
&& # protect against time wrap
!$restart_request
) {
$wcount++;
sleep(1);
}
$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;

View file

@ -24,7 +24,7 @@ my %daemon_options = (
setuid => 'www-data',
setgid => 'www-data',
pidfile => '/var/run/pveproxy/spiceproxy.pid',
);
);
my $daemon = __PACKAGE__->new('spiceproxy', $cmdline, %daemon_options);
@ -36,8 +36,8 @@ 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);
@ -61,7 +61,7 @@ sub init {
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;

View file

@ -27,7 +27,8 @@ sub type {
sub properties {
return {
path => {
type => 'string', format => 'graphite-path',
type => 'string',
format => 'graphite-path',
description => "root graphite path (ex: proxmox.mycluster.mykey)",
},
timeout => {
@ -35,7 +36,7 @@ sub properties {
description => "graphite TCP socket timeout (default=1)",
minimum => 0,
default => 1,
optional => 1
optional => 1,
},
proto => {
type => 'string',
@ -110,7 +111,7 @@ sub _connect {
if ($proto eq 'tcp') {
# seconds and µs
my $timeout_struct = pack( 'l!l!', $timeout, 0);
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);
}
@ -156,6 +157,7 @@ sub assemble {
}
PVE::JSONSchema::register_format('graphite-path', \&pve_verify_graphite_path);
sub pve_verify_graphite_path {
my ($path, $noerr) = @_;
@ -169,5 +171,4 @@ sub pve_verify_graphite_path {
return $path;
}
1;

View file

@ -22,8 +22,9 @@ 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.",
description =>
"The InfluxDB organization. Only necessary when using the http v2 api."
. " Has no meaning when using v2 compatibility api.",
type => 'string',
optional => 1,
},
@ -33,14 +34,15 @@ sub properties {
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.",
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.",
. " Can be useful if the InfluxDB service runs behind a reverse proxy.",
type => 'string',
optional => 1,
},
@ -51,7 +53,8 @@ sub properties {
optional => 1,
},
'max-body-size' => {
description => "InfluxDB max-body-size in bytes. Requests are batched up to this size.",
description =>
"InfluxDB max-body-size in bytes. Requests are batched up to this size.",
type => 'integer',
minimum => 1,
default => 25_000_000,
@ -64,17 +67,18 @@ sub properties {
},
};
}
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},
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 },
@ -110,7 +114,7 @@ sub update_qemu_status {
$ctime *= 1000000000;
my $object = "object=qemu,vmid=$vmid,nodename=$nodename";
if($data->{name} && $data->{name} ne '') {
if ($data->{name} && $data->{name} ne '') {
$object .= ",host=$data->{name}";
}
$object =~ s/\s/\\ /g;
@ -125,7 +129,7 @@ sub update_lxc_status {
$ctime *= 1000000000;
my $object = "object=lxc,vmid=$vmid,nodename=$nodename";
if($data->{name} && $data->{name} ne '') {
if ($data->{name} && $data->{name} ne '') {
$object .= ",host=$data->{name}";
}
$object =~ s/\s/\\ /g;
@ -140,7 +144,7 @@ sub update_storage_status {
$ctime *= 1000000000;
my $object = "object=storages,nodename=$nodename,host=$storeid";
if($data->{type} && $data->{type} ne '') {
if ($data->{type} && $data->{type} ne '') {
$object .= ",type=$data->{type}";
}
$object =~ s/\s/\\ /g;
@ -232,7 +236,7 @@ sub _connect {
my $req = HTTP::Request->new(POST => $url);
if (defined($token)) {
$req->header( "Authorization", "Token $token");
$req->header("Authorization", "Token $token");
}
return $req;
@ -277,7 +281,7 @@ 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 = ();
@ -297,8 +301,10 @@ sub build_influxdb_payload {
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);
} elsif (!defined($instance)) {
build_influxdb_payload(
$class, $txn, $value, $ctime, $tags, $excluded, $measurement, $key,
);
} else {
push @values, get_recursive_values($value);
}
@ -321,7 +327,7 @@ sub get_recursive_values {
foreach my $key (keys %$hash) {
my $value = $hash->{$key};
if(ref($value) eq 'HASH') {
if (ref($value) eq 'HASH') {
push(@values, get_recursive_values($value));
} elsif (!ref($value) && $value ne '') {
if (defined(my $v = prepare_value($value))) {
@ -429,5 +435,4 @@ sub on_delete_hook {
return undef;
}
1;

View file

@ -9,20 +9,23 @@ 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 => 'string',
format => 'pve-configid',
},
type => {
description => "Plugin type.",
type => 'string', format => 'pve-configid',
type => 'string',
format => 'pve-configid',
},
disable => {
description => "Flag to disable the plugin.",
@ -30,21 +33,22 @@ my $defaultData = {
optional => 1,
},
server => {
type => 'string', format => 'address',
type => 'string',
format => 'address',
description => "server dns name or IP address",
},
port => {
type => 'integer',
description => "server network port",
minimum => 1,
maximum => 64*1024,
maximum => 64 * 1024,
},
mtu => {
type => 'integer',
description => "MTU for metrics transmission over UDP",
default => 1500,
minimum => 512,
maximum => 64*1024,
maximum => 64 * 1024,
optional => 1,
},
},

File diff suppressed because it is too large Load diff

View file

@ -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};
@ -24,5 +24,5 @@ foreach my $k (keys %{$pkglist->{'all'}}) {
}
}
$err ? exit (-11) : exit (0);
$err ? exit(-11) : exit(0);

View file

@ -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,15 +27,15 @@ 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";
}
@ -69,10 +69,9 @@ my $defmap = {
'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) {

View file

@ -17,44 +17,42 @@ my $tree = {
{
id => -3,
name => 'pveA',
children => [ 0,1,2,3 ],
children => [0, 1, 2, 3],
type => 'host',
},
{
id => -5,
name => 'pveB',
children => [ 4,5,6,7 ],
children => [4, 5, 6, 7],
type => 'host',
},
{
id => -7,
name => 'pveC',
children => [ 8,9,10,11 ],
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 (
is(
PVE::API2::Ceph::OSD::osd_belongs_to_node($tree, 'pveB', $_),
1,
"OSD $_ belongs to node pveB",
);
}
foreach (@not_belong_to_B) {
is (
is(
PVE::API2::Ceph::OSD::osd_belongs_to_node($tree, 'pveB', $_),
0,
"OSD $_ does not belong to node pveB",
);
}
my $double_nodes_tree = {
nodes => [
{
@ -64,17 +62,16 @@ my $double_nodes_tree = {
{
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);

View file

@ -144,7 +144,7 @@ my $mocked_storage_config = {
shared => 0,
content => {
'images' => 1,
'rootdir' => 1
'rootdir' => 1,
},
},
},
@ -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} //= {};
@ -278,13 +278,13 @@ sub setup {
$pve_cluster_module->mock(
get_vmlist => sub { return $mocked_vmlist->(); },
get_members => $mocked_get_members,
cfs_update => sub {},
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
@ -306,8 +306,8 @@ sub openlog {
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;
@ -416,5 +416,4 @@ sub track_jobs {
$status = $new;
}
1;

View file

@ -66,7 +66,7 @@ my $test_status3 = {
101 => {
maxmem => GB(2),
shares => 1000,
balloon => GB(1)+MB(7),
balloon => GB(1) + MB(7),
balloon_min => GB(1),
freemem => MB(0),
},
@ -88,7 +88,7 @@ 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) = @_;
@ -112,6 +112,7 @@ sub abapply {
}
my $tcount = 0;
sub absim {
my ($vmstatus, $goal, %expect) = @_;
@ -144,7 +145,7 @@ sub abcheck {
foreach my $vmid (keys %expect) {
my $ev = $expect{$vmid};
if (defined ($res->{$vmid})) {
if (defined($res->{$vmid})) {
die "T$tcount: wrong value for VM $vmid ($ev != $res->{$vmid})\n"
if $ev != $res->{$vmid};
} else {
@ -154,8 +155,8 @@ sub abcheck {
foreach my $vmid (keys %$res) {
die "T$tcount: got unexpected result for $vmid\n"
if (defined($res->{$vmid}) &&
!defined($expect{$vmid}));
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;
}

View file

@ -24,9 +24,12 @@ sub test_rpc {
for (my $i = 0; $i < $qcount; $i++) {
eval {
my ($page, $response, %reply_headers)
= get_https($host, 8006, '/api2/json',
make_headers(Cookie => "PVEAuthCookie=$ticket"));
my ($page, $response, %reply_headers) = get_https(
$host,
8006,
'/api2/json',
make_headers(Cookie => "PVEAuthCookie=$ticket"),
);
die "$response\n" if $response !~ m/200 OK/;
};
@ -48,20 +51,20 @@ sub run_tests {
my $starttime = [gettimeofday];
for (my $i = 0; $i < $wcount; $i++) {
if (my $pid = fork ()) {
if (my $pid = fork()) {
$workers->{$pid} = 1;
} else {
test_rpc ($host);
exit (0);
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";
}

View file

@ -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);

View file

@ -21,7 +21,8 @@ my $schedule = [];
my $mocked_replicate = sub {
my ($guest_class, $jobcfg, $state, $start_time, $logfunc) = @_;
push @$schedule, {
push @$schedule,
{
id => $jobcfg->{id},
guest => $jobcfg->{guest},
vmtype => $jobcfg->{vmtype},
@ -60,7 +61,7 @@ $ReplicationTestEnv::mocked_vm_configs = {
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 = [
@ -70,7 +71,7 @@ my $exptected_schedule = [
vmtype => 'qemu',
guest_class => 'PVE::QemuConfig',
id => '900-1_to_node2',
guest => 900
guest => 900,
},
{
last_sync => 900,
@ -86,7 +87,7 @@ my $exptected_schedule = [
vmtype => 'qemu',
guest_class => 'PVE::QemuConfig',
id => '900-1_to_node2',
guest => 900
guest => 900,
},
{
last_sync => 2700,
@ -94,8 +95,8 @@ my $exptected_schedule = [
vmtype => 'qemu',
guest_class => 'PVE::QemuConfig',
id => '900-1_to_node2',
guest => 900
}
guest => 900,
},
];
is_deeply($schedule, $exptected_schedule);

View file

@ -16,8 +16,7 @@ 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',

View file

@ -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,9 +39,10 @@ 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}}) {
foreach my $volid (keys %{ $replicated_volume_status->{$target} }) {
if (!grep { $_ eq $volid } @$volumes) {
delete $replicated_volume_status->{$target}->{$volid};
next;
@ -80,7 +82,8 @@ 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',
@ -142,8 +145,6 @@ for (my $i = 0; $i < 15; $i++) {
$ctime += 60;
}
ReplicationTestEnv::commit_log();
exit(0);

View file

@ -20,8 +20,7 @@ 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',

View file

@ -46,7 +46,7 @@ my $vmlist = {
'type' => 'lxc',
'node' => 'node2',
},
}
},
};
my $pools = {
@ -57,134 +57,163 @@ my $pve_cluster_module = Test::MockModule->new('PVE::Cluster');
$pve_cluster_module->mock(
get_vmlist => sub {
return $vmlist;
}
},
);
my $pve_inotify = Test::MockModule->new('PVE::INotify');
$pve_inotify->mock(
nodename => sub {
return 'node1';
}
},
);
my $pve_api2tools = Test::MockModule->new('PVE::API2Tools');
$pve_api2tools->mock(
get_resource_pool_guest_members => sub {
return $pools->{testpool};
}
},
);
my $tests = [];
my $addtest = sub {
my ($name, $test) = @_;
push @$tests, {
push @$tests,
{
name => $name,
test => $test,
};
};
$addtest->('Test all guests', {
$addtest->(
'Test all guests',
{
expected => {
node1 => [ 100, 101, 112, 113 ],
node2 => [ 200, 201, 212, 213 ],
node1 => [100, 101, 112, 113],
node2 => [200, 201, 212, 213],
},
param => {
all => 1,
}
});
},
},
);
$addtest->('Test all guests with node limit', {
$addtest->(
'Test all guests with node limit',
{
expected => {
node2 => [ 200, 201, 212, 213 ],
node2 => [200, 201, 212, 213],
},
param => {
all => 1,
node => 'node2',
}
});
},
},
);
$addtest->('Test exclude', {
$addtest->(
'Test exclude',
{
expected => {
node1 =>[ 101, 112, 113 ],
node2 => [ 201, 212, 213 ],
node1 => [101, 112, 113],
node2 => [201, 212, 213],
},
param => {
all => 1,
exclude => '100, 102, 200, 202',
}
});
},
},
);
$addtest->('Test exclude with node limit', {
$addtest->(
'Test exclude with node limit',
{
expected => {
node1 =>[ 101, 112, 113 ],
node1 => [101, 112, 113],
},
param => {
all => 1,
exclude => '100, 102, 200, 202',
node => 'node1',
}
});
},
},
);
$addtest->('Test pool members', {
$addtest->(
'Test pool members',
{
expected => {
node1 =>[ 100, 101 ],
node2 => [ 200, 201 ],
node1 => [100, 101],
node2 => [200, 201],
},
param => {
pool => 'testpool',
}
});
},
},
);
$addtest->('Test pool members with node limit', {
$addtest->(
'Test pool members with node limit',
{
expected => {
node2 => [ 200, 201 ],
node2 => [200, 201],
},
param => {
pool => 'testpool',
node => 'node2',
}
});
},
},
);
$addtest->('Test selected VMIDs', {
$addtest->(
'Test selected VMIDs',
{
expected => {
node1 =>[ 100 ],
node2 => [ 201, 212 ],
node1 => [100],
node2 => [201, 212],
},
param => {
vmid => '100, 201, 212',
}
});
},
},
);
$addtest->('Test selected VMIDs with node limit', {
$addtest->(
'Test selected VMIDs with node limit',
{
expected => {
node1 =>[ 100 ],
node1 => [100],
},
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',
}
});
},
},
);
$addtest->('Test VMID not present in vmlist', {
$addtest->(
'Test VMID not present in vmlist',
{
expected => {
node1 => [ 100 ],
node2 => [ 201, 212 ],
'' => [ 7654 ],
node1 => [100],
node2 => [201, 212],
'' => [7654],
},
param => {
vmid => '100, 201, 212, 7654',
}
});
},
},
);
for my $test (@{$tests}) {
my $testname = $test->{name};

View file

@ -180,8 +180,8 @@ my @tests = (
{
description => 'prune-backups vzdump 1',
vzdump_param => {
'prune-backups' => 'keep-last=1,keep-hourly=2,keep-daily=3,' .
'keep-weekly=4,keep-monthly=5,keep-yearly=6',
'prune-backups' => 'keep-last=1,keep-hourly=2,keep-daily=3,'
. 'keep-weekly=4,keep-monthly=5,keep-yearly=6',
},
expected => {
'prune-backups' => {
@ -235,8 +235,8 @@ my @tests = (
{
description => 'prune-backups storage 1',
storage_param => {
'prune-backups' => 'keep-last=1,keep-hourly=2,keep-daily=3,' .
'keep-weekly=4,keep-monthly=5,keep-yearly=6',
'prune-backups' => 'keep-last=1,keep-hourly=2,keep-daily=3,'
. 'keep-weekly=4,keep-monthly=5,keep-yearly=6',
},
expected => {
'prune-backups' => {
@ -253,8 +253,8 @@ my @tests = (
{
description => 'prune-backups storage 2',
storage_param => {
'prune-backups' => 'keep-last=0,keep-hourly=0,keep-daily=0,' .
'keep-weekly=0,keep-monthly=0,keep-yearly=0',
'prune-backups' => 'keep-last=0,keep-hourly=0,keep-daily=0,'
. 'keep-weekly=0,keep-monthly=0,keep-yearly=0',
},
expected => {
'prune-backups' => {
@ -293,8 +293,8 @@ my @tests = (
{
description => 'prune-backups CLI 1',
cli_param => {
'prune-backups' => 'keep-last=1,keep-hourly=2,keep-daily=3,' .
'keep-weekly=4,keep-monthly=5,keep-yearly=6',
'prune-backups' => 'keep-last=1,keep-hourly=2,keep-daily=3,'
. 'keep-weekly=4,keep-monthly=5,keep-yearly=6',
},
expected => {
'prune-backups' => {
@ -311,8 +311,8 @@ my @tests = (
{
description => 'prune-backups CLI 2',
cli_param => {
'prune-backups' => 'keep-last=0,keep-hourly=0,keep-daily=0,' .
'keep-weekly=0,keep-monthly=0,keep-yearly=0',
'prune-backups' => 'keep-last=0,keep-hourly=0,keep-daily=0,'
. 'keep-weekly=0,keep-monthly=0,keep-yearly=0',
},
expected => {
'prune-backups' => {
@ -326,8 +326,8 @@ my @tests = (
cli_param => {
'prune-backups' => 'foo=bar',
},
expected => "format error\n" .
"foo: property is not defined in schema and the schema does not allow additional properties\n",
expected => "format error\n"
. "foo: property is not defined in schema and the schema does not allow additional properties\n",
},
{
description => 'both CLI 1',
@ -335,8 +335,8 @@ my @tests = (
'prune-backups' => 'keep-hourly=1,keep-monthly=2,keep-yearly=3',
maxfiles => 4,
},
expected => "400 Parameter verification failed.\n" .
"prune-backups: option conflicts with option 'maxfiles'\n",
expected => "400 Parameter verification failed.\n"
. "prune-backups: option conflicts with option 'maxfiles'\n",
},
{
description => 'mixed 1',
@ -499,8 +499,7 @@ my @tests = (
},
expected => {
'mailto' => [
'developer@proxmox.com',
'admin@proxmox.com',
'developer@proxmox.com', 'admin@proxmox.com',
],
},
},
@ -511,8 +510,7 @@ my @tests = (
},
expected => {
'mailto' => [
'developer@proxmox.com',
'admin@proxmox.com',
'developer@proxmox.com', 'admin@proxmox.com',
],
},
},
@ -523,8 +521,7 @@ my @tests = (
},
expected => {
'mailto' => [
'developer@proxmox.com',
'admin@proxmox.com',
'developer@proxmox.com', 'admin@proxmox.com',
],
},
},
@ -535,8 +532,7 @@ my @tests = (
},
expected => {
'mailto' => [
'developer@proxmox.com',
'admin@proxmox.com',
'developer@proxmox.com', 'admin@proxmox.com',
],
},
},
@ -567,8 +563,7 @@ my @tests = (
},
expected => {
'mailto' => [
'developer@proxmox.com',
'admin@proxmox.com',
'developer@proxmox.com', 'admin@proxmox.com',
],
},
},
@ -579,8 +574,7 @@ my @tests = (
},
expected => {
'mailto' => [
'developer@proxmox.com',
'admin@proxmox.com',
'developer@proxmox.com', 'admin@proxmox.com',
],
},
},
@ -591,8 +585,7 @@ my @tests = (
},
expected => {
'mailto' => [
'developer@proxmox.com',
'admin@proxmox.com',
'developer@proxmox.com', 'admin@proxmox.com',
],
},
},
@ -603,8 +596,7 @@ my @tests = (
},
expected => {
'mailto' => [
'developer@proxmox.com',
'admin@proxmox.com',
'developer@proxmox.com', 'admin@proxmox.com',
],
},
},

View file

@ -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
@ -38,13 +39,15 @@ if ($phase eq 'job-init' ||
# 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);