From 138cae897ae9c58e75d9f21a7e8b7052a2ccc8a0 Mon Sep 17 00:00:00 2001 From: Thomas Lamprecht Date: Tue, 17 Jun 2025 18:22:29 +0200 Subject: [PATCH] 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 --- PVE/API2.pm | 130 +- PVE/API2/ACME.pm | 479 ++-- PVE/API2/ACMEAccount.pm | 671 +++--- PVE/API2/ACMEPlugin.pm | 238 +- PVE/API2/APT.pm | 1206 +++++----- PVE/API2/Backup.pm | 990 ++++---- PVE/API2/Capabilities.pm | 75 +- PVE/API2/Ceph.pm | 846 +++---- PVE/API2/Ceph/Cfg.pm | 221 +- PVE/API2/Ceph/FS.pm | 298 +-- PVE/API2/Ceph/MDS.pm | 286 +-- PVE/API2/Ceph/MGR.pm | 186 +- PVE/API2/Ceph/MON.pm | 783 +++--- PVE/API2/Ceph/OSD.pm | 1677 ++++++------- PVE/API2/Ceph/Pool.pm | 1152 ++++----- PVE/API2/Certificates.pm | 249 +- PVE/API2/Cluster.pm | 1250 +++++----- PVE/API2/Cluster/BackupInfo.pm | 150 +- PVE/API2/Cluster/BulkActions.pm | 139 ++ PVE/API2/Cluster/Ceph.pm | 683 +++--- PVE/API2/Cluster/Jobs.pm | 151 +- PVE/API2/Cluster/Mapping.pm | 39 +- PVE/API2/Cluster/Mapping/Dir.pm | 375 +-- PVE/API2/Cluster/Mapping/PCI.pm | 367 +-- PVE/API2/Cluster/Mapping/USB.pm | 358 +-- PVE/API2/Cluster/MetricServer.pm | 632 ++--- PVE/API2/Cluster/Notifications.pm | 2359 +++++++++--------- PVE/API2/HAConfig.pm | 58 +- PVE/API2/Hardware.pm | 41 +- PVE/API2/Hardware/PCI.pm | 359 +-- PVE/API2/Hardware/USB.pm | 52 +- PVE/API2/Network.pm | 1126 ++++----- PVE/API2/NodeConfig.pm | 110 +- PVE/API2/Nodes.pm | 3556 ++++++++++++++-------------- PVE/API2/Pool.pm | 746 +++--- PVE/API2/Replication.pm | 376 +-- PVE/API2/ReplicationConfig.pm | 328 +-- PVE/API2/Services.pm | 330 +-- PVE/API2/Subscription.pm | 393 +-- PVE/API2/Tasks.pm | 759 +++--- PVE/API2/VZDump.pm | 377 +-- PVE/API2Tools.pm | 231 +- PVE/APLInfo.pm | 245 +- PVE/AutoBalloon.pm | 212 +- PVE/CLI/pve7to8.pm | 1662 ++++++------- PVE/CLI/pveam.pm | 211 +- PVE/CLI/pveceph.pm | 703 +++--- PVE/CLI/pvenode.pm | 453 ++-- PVE/CLI/pvesh.pm | 495 ++-- PVE/CLI/pvesr.pm | 522 ++-- PVE/CLI/pvesubscription.pm | 59 +- PVE/CLI/vzdump.pm | 19 +- PVE/Ceph/Releases.pm | 133 +- PVE/Ceph/Services.pm | 255 +- PVE/Ceph/Tools.pm | 476 ++-- PVE/CertCache.pm | 66 +- PVE/CertHelpers.pm | 89 +- PVE/ExtMetric.pm | 54 +- PVE/HTTPServer.pm | 197 +- PVE/Jobs.pm | 326 +-- PVE/Jobs/VZDump.pm | 10 +- PVE/NodeConfig.pm | 262 +- PVE/PullMetric.pm | 127 +- PVE/Report.pm | 325 +-- PVE/Service/pvedaemon.pm | 30 +- PVE/Service/pveproxy.pm | 201 +- PVE/Service/pvescheduler.pm | 166 +- PVE/Service/pvestatd.pm | 618 ++--- PVE/Service/spiceproxy.pm | 48 +- PVE/Status/Graphite.pm | 111 +- PVE/Status/InfluxDB.pm | 317 +-- PVE/Status/Plugin.pm | 92 +- PVE/VZDump.pm | 1704 ++++++------- aplinfo/apltest.pl | 10 +- configs/country.pl | 75 +- test/OSD_test.pl | 79 +- test/ReplicationTestEnv.pm | 221 +- test/balloontest.pl | 140 +- test/perftest3.pl | 43 +- test/replication_test1.pl | 16 +- test/replication_test2.pl | 95 +- test/replication_test3.pl | 20 +- test/replication_test4.pl | 27 +- test/replication_test5.pl | 45 +- test/replication_test6.pl | 23 +- test/vzdump_guest_included_test.pl | 285 ++- test/vzdump_new_test.pl | 960 ++++---- vzdump-hook-script.pl | 41 +- 88 files changed, 19180 insertions(+), 17920 deletions(-) create mode 100644 PVE/API2/Cluster/BulkActions.pm diff --git a/PVE/API2.pm b/PVE/API2.pm index 42941fd2..0c7e4654 100644 --- a/PVE/API2.pm +++ b/PVE/API2.pm @@ -18,119 +18,121 @@ use PVE::API2::Pool; use PVE::API2::AccessControl; use PVE::API2::Storage::Config; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Cluster", path => 'cluster', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Nodes", path => 'nodes', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Storage::Config", path => 'storage', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::AccessControl", path => 'access', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Pool", path => 'pools', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', permissions => { user => 'all' }, description => "Directory index.", parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - subdir => { type => 'string' }, - }, - }, - links => [ { rel => 'child', href => "{subdir}" } ], + type => 'array', + items => { + type => "object", + properties => { + subdir => { type => 'string' }, + }, + }, + links => [{ rel => 'child', href => "{subdir}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $res = [ { subdir => 'version' } ]; + my $res = [{ subdir => 'version' }]; - my $ma = PVE::API2->method_attributes(); + my $ma = PVE::API2->method_attributes(); - foreach my $info (@$ma) { - next if !$info->{subclass}; + foreach my $info (@$ma) { + next if !$info->{subclass}; - my $subpath = $info->{match_re}->[0]; + my $subpath = $info->{match_re}->[0]; - push @$res, { subdir => $subpath }; - } + push @$res, { subdir => $subpath }; + } - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'version', path => 'version', method => 'GET', permissions => { user => 'all' }, description => "API version details, including some parts of the global datacenter config.", parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => "object", - properties => { - version => { - type => 'string', - description => 'The full pve-manager package version of this node.', - }, - release => { - type => 'string', - description => 'The current Proxmox VE point release in `x.y` format.', - }, - repoid => { - type => 'string', - # length 8 is old (< 8.0) short-id, 16 is new short id, 40 is sha1 and 64 is sha256 - pattern => '[0-9a-fA-F]{8,64}', - description => 'The short git revision from which this version was build.', - }, - console => { - type => 'string', - enum => ['applet', 'vv', 'html5', 'xtermjs'], - optional => 1, - description => 'The default console viewer to use.', - }, - }, + type => "object", + properties => { + version => { + type => 'string', + description => 'The full pve-manager package version of this node.', + }, + release => { + type => 'string', + description => 'The current Proxmox VE point release in `x.y` format.', + }, + repoid => { + type => 'string', + # length 8 is old (< 8.0) short-id, 16 is new short id, 40 is sha1 and 64 is sha256 + pattern => '[0-9a-fA-F]{8,64}', + description => 'The short git revision from which this version was build.', + }, + console => { + type => 'string', + enum => ['applet', 'vv', 'html5', 'xtermjs'], + optional => 1, + description => 'The default console viewer to use.', + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $res = {}; + my $res = {}; - # TODO remove with next major release - my $datacenter_confg = eval { PVE::Cluster::cfs_read_file('datacenter.cfg') } // {}; - for my $k (qw(console)) { - $res->{$k} = $datacenter_confg->{$k} if exists $datacenter_confg->{$k}; - } + # TODO remove with next major release + my $datacenter_confg = eval { PVE::Cluster::cfs_read_file('datacenter.cfg') } // {}; + for my $k (qw(console)) { + $res->{$k} = $datacenter_confg->{$k} if exists $datacenter_confg->{$k}; + } - my $version_info = PVE::pvecfg::version_info(); - # force set all version keys independent of their definedness - $res->{$_} = $version_info->{$_} for qw(version release repoid); + my $version_info = PVE::pvecfg::version_info(); + # force set all version keys independent of their definedness + $res->{$_} = $version_info->{$_} for qw(version release repoid); - return $res; - }}); + return $res; + }, +}); 1; diff --git a/PVE/API2/ACME.pm b/PVE/API2/ACME.pm index 393e6b01..a948a72a 100644 --- a/PVE/API2/ACME.pm +++ b/PVE/API2/ACME.pm @@ -17,33 +17,34 @@ use base qw(PVE::RESTHandler); my $acme_account_dir = PVE::CertHelpers::acme_account_dir(); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', permissions => { user => 'all' }, description => "ACME index.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - return [ - { name => 'certificate' }, - ]; - }}); + return [ + { name => 'certificate' }, + ]; + }, +}); my $order_certificate = sub { my ($acme, $acme_node_config) = @_; @@ -51,62 +52,62 @@ my $order_certificate = sub { my $plugins = PVE::API2::ACMEPlugin::load_config(); print "Placing ACME order\n"; - my ($order_url, $order) = $acme->new_order([ keys %{$acme_node_config->{domains}} ]); + my ($order_url, $order) = $acme->new_order([keys %{ $acme_node_config->{domains} }]); print "Order URL: $order_url\n"; - for my $auth_url (@{$order->{authorizations}}) { - print "\nGetting authorization details from '$auth_url'\n"; - my $auth = $acme->get_authorization($auth_url); + for my $auth_url (@{ $order->{authorizations} }) { + print "\nGetting authorization details from '$auth_url'\n"; + my $auth = $acme->get_authorization($auth_url); - # force lower case, like get_acme_conf does - my $domain = lc($auth->{identifier}->{value}); - if ($auth->{status} eq 'valid') { - print "$domain is already validated!\n"; - } else { - print "The validation for $domain is pending!\n"; + # force lower case, like get_acme_conf does + my $domain = lc($auth->{identifier}->{value}); + if ($auth->{status} eq 'valid') { + print "$domain is already validated!\n"; + } else { + print "The validation for $domain is pending!\n"; - my $domain_config = $acme_node_config->{domains}->{$domain}; - die "no config for domain '$domain'\n" if !$domain_config; + my $domain_config = $acme_node_config->{domains}->{$domain}; + die "no config for domain '$domain'\n" if !$domain_config; - my $plugin_id = $domain_config->{plugin}; + my $plugin_id = $domain_config->{plugin}; - my $plugin_cfg = $plugins->{ids}->{$plugin_id}; - die "plugin '$plugin_id' for domain '$domain' not found!\n" - if !$plugin_cfg; + my $plugin_cfg = $plugins->{ids}->{$plugin_id}; + die "plugin '$plugin_id' for domain '$domain' not found!\n" + if !$plugin_cfg; - my $data = { - plugin => $plugin_cfg, - alias => $domain_config->{alias}, - }; + my $data = { + plugin => $plugin_cfg, + alias => $domain_config->{alias}, + }; - my $plugin = PVE::ACME::Challenge->lookup($plugin_cfg->{type}); - $plugin->setup($acme, $auth, $data); + my $plugin = PVE::ACME::Challenge->lookup($plugin_cfg->{type}); + $plugin->setup($acme, $auth, $data); - print "Triggering validation\n"; - eval { - die "no validation URL returned by plugin '$plugin_id' for domain '$domain'\n" - if !defined($data->{url}); + print "Triggering validation\n"; + eval { + die "no validation URL returned by plugin '$plugin_id' for domain '$domain'\n" + if !defined($data->{url}); - $acme->request_challenge_validation($data->{url}); - print "Sleeping for 5 seconds\n"; - sleep 5; - while (1) { - $auth = $acme->get_authorization($auth_url); - if ($auth->{status} eq 'pending') { - print "Status is still 'pending', trying again in 10 seconds\n"; - sleep 10; - next; - } elsif ($auth->{status} eq 'valid') { - print "Status is 'valid', domain '$domain' OK!\n"; - last; - } - die "validating challenge '$auth_url' failed - status: $auth->{status}\n"; - } - }; - my $err = $@; - eval { $plugin->teardown($acme, $auth, $data) }; - warn "$@\n" if $@; - die $err if $err; - } + $acme->request_challenge_validation($data->{url}); + print "Sleeping for 5 seconds\n"; + sleep 5; + while (1) { + $auth = $acme->get_authorization($auth_url); + if ($auth->{status} eq 'pending') { + print "Status is still 'pending', trying again in 10 seconds\n"; + sleep 10; + next; + } elsif ($auth->{status} eq 'valid') { + print "Status is 'valid', domain '$domain' OK!\n"; + last; + } + die "validating challenge '$auth_url' failed - status: $auth->{status}\n"; + } + }; + my $err = $@; + eval { $plugin->teardown($acme, $auth, $data) }; + warn "$@\n" if $@; + die $err if $err; + } } print "\nAll domains validated!\n"; print "\nCreating CSR\n"; @@ -115,39 +116,37 @@ my $order_certificate = sub { my $finalize_error_cnt = 0; print "Checking order status\n"; while (1) { - $order = $acme->get_order($order_url); - if ($order->{status} eq 'pending') { - print "still pending, trying to finalize order\n"; - # FIXME - # to be compatible with and without the order ready state we try to - # finalize even at the 'pending' state and give up after 5 - # unsuccessful tries this can be removed when the letsencrypt api - # definitely has implemented the 'ready' state - eval { - $acme->finalize_order($order, PVE::Certificate::pem_to_der($csr)); - }; - if (my $err = $@) { - die $err if $finalize_error_cnt >= 5; + $order = $acme->get_order($order_url); + if ($order->{status} eq 'pending') { + print "still pending, trying to finalize order\n"; + # FIXME + # to be compatible with and without the order ready state we try to + # finalize even at the 'pending' state and give up after 5 + # unsuccessful tries this can be removed when the letsencrypt api + # definitely has implemented the 'ready' state + eval { $acme->finalize_order($order, PVE::Certificate::pem_to_der($csr)); }; + if (my $err = $@) { + die $err if $finalize_error_cnt >= 5; - $finalize_error_cnt++; - warn $err; - } - sleep 5; - next; - } elsif ($order->{status} eq 'ready') { - print "Order is ready, finalizing order\n"; - $acme->finalize_order($order, PVE::Certificate::pem_to_der($csr)); - sleep 5; - next; - } elsif ($order->{status} eq 'processing') { - print "still processing, trying again in 30 seconds\n"; - sleep 30; - next; - } elsif ($order->{status} eq 'valid') { - print "valid!\n"; - last; - } - die "order status: $order->{status}\n"; + $finalize_error_cnt++; + warn $err; + } + sleep 5; + next; + } elsif ($order->{status} eq 'ready') { + print "Order is ready, finalizing order\n"; + $acme->finalize_order($order, PVE::Certificate::pem_to_der($csr)); + sleep 5; + next; + } elsif ($order->{status} eq 'processing') { + print "still processing, trying again in 30 seconds\n"; + sleep 30; + next; + } elsif ($order->{status} eq 'valid') { + print "valid!\n"; + last; + } + die "order status: $order->{status}\n"; } print "\nDownloading certificate\n"; @@ -156,227 +155,235 @@ my $order_certificate = sub { return ($cert, $key); }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'new_certificate', path => 'certificate', method => 'POST', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, description => "Order a new certificate from ACME-compatible CA.", protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - force => { - type => 'boolean', - description => 'Overwrite existing custom certificate.', - optional => 1, - default => 0, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + force => { + type => 'boolean', + description => 'Overwrite existing custom certificate.', + optional => 1, + default => 0, + }, + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $node = extract_param($param, 'node'); - my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node); + my $node = extract_param($param, 'node'); + my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node); - raise_param_exc({'force' => "Custom certificate exists but 'force' is not set."}) - if !$param->{force} && -e "${cert_prefix}.pem"; + raise_param_exc({ 'force' => "Custom certificate exists but 'force' is not set." }) + if !$param->{force} && -e "${cert_prefix}.pem"; - my $node_config = PVE::NodeConfig::load_config($node); - my $acme_node_config = PVE::NodeConfig::get_acme_conf($node_config); - raise("ACME domain list in node configuration is missing!", 400) - if !$acme_node_config || !%{$acme_node_config->{domains}}; + my $node_config = PVE::NodeConfig::load_config($node); + my $acme_node_config = PVE::NodeConfig::get_acme_conf($node_config); + raise("ACME domain list in node configuration is missing!", 400) + if !$acme_node_config || !%{ $acme_node_config->{domains} }; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); - my $realcmd = sub { - STDOUT->autoflush(1); - my $account = $acme_node_config->{account}; - my $account_file = "${acme_account_dir}/${account}"; - die "ACME account config file '$account' does not exist.\n" - if ! -e $account_file; + my $realcmd = sub { + STDOUT->autoflush(1); + my $account = $acme_node_config->{account}; + my $account_file = "${acme_account_dir}/${account}"; + die "ACME account config file '$account' does not exist.\n" + if !-e $account_file; - my $acme = PVE::ACME->new($account_file); + my $acme = PVE::ACME->new($account_file); - print "Loading ACME account details\n"; - $acme->load(); + print "Loading ACME account details\n"; + $acme->load(); - my ($cert, $key) = $order_certificate->($acme, $acme_node_config); + my ($cert, $key) = $order_certificate->($acme, $acme_node_config); - my $code = sub { - print "Setting pveproxy certificate and key\n"; - PVE::CertHelpers::set_cert_files($cert, $key, $cert_prefix, $param->{force}); + my $code = sub { + print "Setting pveproxy certificate and key\n"; + PVE::CertHelpers::set_cert_files($cert, $key, $cert_prefix, $param->{force}); - print "Restarting pveproxy\n"; - PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']); - }; - PVE::CertHelpers::cert_lock(10, $code); - die "$@\n" if $@; - }; + print "Restarting pveproxy\n"; + PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']); + }; + PVE::CertHelpers::cert_lock(10, $code); + die "$@\n" if $@; + }; - return $rpcenv->fork_worker("acmenewcert", undef, $authuser, $realcmd); - }}); + return $rpcenv->fork_worker("acmenewcert", undef, $authuser, $realcmd); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'renew_certificate', path => 'certificate', method => 'PUT', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, description => "Renew existing certificate from CA.", protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - force => { - type => 'boolean', - description => 'Force renewal even if expiry is more than 30 days away.', - optional => 1, - default => 0, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + force => { + type => 'boolean', + description => 'Force renewal even if expiry is more than 30 days away.', + optional => 1, + default => 0, + }, + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $node = extract_param($param, 'node'); - my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node); + my $node = extract_param($param, 'node'); + my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node); - raise("No current (custom) certificate found, please order a new certificate!\n") - if ! -e "${cert_prefix}.pem"; + raise("No current (custom) certificate found, please order a new certificate!\n") + if !-e "${cert_prefix}.pem"; - my $expires_soon = PVE::Certificate::check_expiry("${cert_prefix}.pem", time() + 30*24*60*60); - raise_param_exc({'force' => "Certificate does not expire within the next 30 days, and 'force' is not set."}) - if !$expires_soon && !$param->{force}; + my $expires_soon = + PVE::Certificate::check_expiry("${cert_prefix}.pem", time() + 30 * 24 * 60 * 60); + raise_param_exc( + { + 'force' => + "Certificate does not expire within the next 30 days, and 'force' is not set.", + }, + ) if !$expires_soon && !$param->{force}; - my $node_config = PVE::NodeConfig::load_config($node); - my $acme_node_config = PVE::NodeConfig::get_acme_conf($node_config); - raise("ACME domain list in node configuration is missing!", 400) - if !$acme_node_config || !%{$acme_node_config->{domains}}; + my $node_config = PVE::NodeConfig::load_config($node); + my $acme_node_config = PVE::NodeConfig::get_acme_conf($node_config); + raise("ACME domain list in node configuration is missing!", 400) + if !$acme_node_config || !%{ $acme_node_config->{domains} }; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); - my $old_cert = PVE::Tools::file_get_contents("${cert_prefix}.pem"); + my $old_cert = PVE::Tools::file_get_contents("${cert_prefix}.pem"); - my $realcmd = sub { - STDOUT->autoflush(1); - my $account = $acme_node_config->{account}; - my $account_file = "${acme_account_dir}/${account}"; - die "ACME account config file '$account' does not exist.\n" - if ! -e $account_file; + my $realcmd = sub { + STDOUT->autoflush(1); + my $account = $acme_node_config->{account}; + my $account_file = "${acme_account_dir}/${account}"; + die "ACME account config file '$account' does not exist.\n" + if !-e $account_file; - my $acme = PVE::ACME->new($account_file); + my $acme = PVE::ACME->new($account_file); - print "Loading ACME account details\n"; - $acme->load(); + print "Loading ACME account details\n"; + $acme->load(); - my ($cert, $key) = $order_certificate->($acme, $acme_node_config); + my ($cert, $key) = $order_certificate->($acme, $acme_node_config); - my $code = sub { - print "Setting pveproxy certificate and key\n"; - PVE::CertHelpers::set_cert_files($cert, $key, $cert_prefix, 1); + my $code = sub { + print "Setting pveproxy certificate and key\n"; + PVE::CertHelpers::set_cert_files($cert, $key, $cert_prefix, 1); - print "Restarting pveproxy\n"; - PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']); - }; - PVE::CertHelpers::cert_lock(10, $code); - die "$@\n" if $@; + print "Restarting pveproxy\n"; + PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']); + }; + PVE::CertHelpers::cert_lock(10, $code); + die "$@\n" if $@; - print "Revoking old certificate\n"; - eval { $acme->revoke_certificate($old_cert) }; - warn "Revoke request to CA failed: $@" if $@; - }; + print "Revoking old certificate\n"; + eval { $acme->revoke_certificate($old_cert) }; + warn "Revoke request to CA failed: $@" if $@; + }; - return $rpcenv->fork_worker("acmerenew", undef, $authuser, $realcmd); - }}); + return $rpcenv->fork_worker("acmerenew", undef, $authuser, $realcmd); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'revoke_certificate', path => 'certificate', method => 'DELETE', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, description => "Revoke existing certificate from CA.", protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $node = extract_param($param, 'node'); - my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node); + my $node = extract_param($param, 'node'); + my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node); - my $node_config = PVE::NodeConfig::load_config($node); - my $acme_node_config = PVE::NodeConfig::get_acme_conf($node_config); - raise("ACME domain list in node configuration is missing!", 400) - if !$acme_node_config || !%{$acme_node_config->{domains}}; + my $node_config = PVE::NodeConfig::load_config($node); + my $acme_node_config = PVE::NodeConfig::get_acme_conf($node_config); + raise("ACME domain list in node configuration is missing!", 400) + if !$acme_node_config || !%{ $acme_node_config->{domains} }; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); - my $cert = PVE::Tools::file_get_contents("${cert_prefix}.pem"); + my $cert = PVE::Tools::file_get_contents("${cert_prefix}.pem"); - my $realcmd = sub { - STDOUT->autoflush(1); - my $account = $acme_node_config->{account}; - my $account_file = "${acme_account_dir}/${account}"; - die "ACME account config file '$account' does not exist.\n" - if ! -e $account_file; + my $realcmd = sub { + STDOUT->autoflush(1); + my $account = $acme_node_config->{account}; + my $account_file = "${acme_account_dir}/${account}"; + die "ACME account config file '$account' does not exist.\n" + if !-e $account_file; - my $acme = PVE::ACME->new($account_file); + my $acme = PVE::ACME->new($account_file); - print "Loading ACME account details\n"; - $acme->load(); + print "Loading ACME account details\n"; + $acme->load(); - print "Revoking old certificate\n"; - eval { $acme->revoke_certificate($cert) }; - if (my $err = $@) { - # is there a better check? - die "Revoke request to CA failed: $err" if $err !~ /"Certificate is expired"/; - } + print "Revoking old certificate\n"; + eval { $acme->revoke_certificate($cert) }; + if (my $err = $@) { + # is there a better check? + die "Revoke request to CA failed: $err" if $err !~ /"Certificate is expired"/; + } - my $code = sub { - print "Deleting certificate files\n"; - unlink "${cert_prefix}.pem"; - unlink "${cert_prefix}.key"; + my $code = sub { + print "Deleting certificate files\n"; + unlink "${cert_prefix}.pem"; + unlink "${cert_prefix}.key"; - print "Restarting pveproxy to revert to self-signed certificates\n"; - PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']); - }; + print "Restarting pveproxy to revert to self-signed certificates\n"; + PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']); + }; - PVE::CertHelpers::cert_lock(10, $code); - die "$@\n" if $@; - }; + PVE::CertHelpers::cert_lock(10, $code); + die "$@\n" if $@; + }; - return $rpcenv->fork_worker("acmerevoke", undef, $authuser, $realcmd); - }}); + return $rpcenv->fork_worker("acmerevoke", undef, $authuser, $realcmd); + }, +}); 1; diff --git a/PVE/API2/ACMEAccount.pm b/PVE/API2/ACMEAccount.pm index aac1f59a..86946ec6 100644 --- a/PVE/API2/ACMEAccount.pm +++ b/PVE/API2/ACMEAccount.pm @@ -15,61 +15,61 @@ use PVE::API2::ACMEPlugin; use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::ACMEPlugin", path => 'plugins', }); my $acme_directories = [ { - name => 'Let\'s Encrypt V2', - url => 'https://acme-v02.api.letsencrypt.org/directory', + name => 'Let\'s Encrypt V2', + url => 'https://acme-v02.api.letsencrypt.org/directory', }, { - name => 'Let\'s Encrypt V2 Staging', - url => 'https://acme-staging-v02.api.letsencrypt.org/directory', + name => 'Let\'s Encrypt V2 Staging', + url => 'https://acme-staging-v02.api.letsencrypt.org/directory', }, ]; my $acme_default_directory_url = $acme_directories->[0]->{url}; my $account_contact_from_param = sub { my @addresses = PVE::Tools::split_list(extract_param($_[0], 'contact')); - return [ map { "mailto:$_" } @addresses ]; + return [map { "mailto:$_" } @addresses]; }; my $acme_account_dir = PVE::CertHelpers::acme_account_dir(); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', permissions => { user => 'all' }, description => "ACMEAccount index.", parameters => { - additionalProperties => 0, - properties => { - }, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - return [ - { name => 'account' }, - { name => 'tos' }, - { name => 'meta' }, - { name => 'directories' }, - { name => 'plugins' }, - { name => 'challenge-schema' }, - ]; - }}); + return [ + { name => 'account' }, + { name => 'tos' }, + { name => 'meta' }, + { name => 'directories' }, + { name => 'plugins' }, + { name => 'challenge-schema' }, + ]; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'account_index', path => 'account', method => 'GET', @@ -77,112 +77,121 @@ __PACKAGE__->register_method ({ description => "ACMEAccount index.", protected => 1, parameters => { - additionalProperties => 0, - properties => { - }, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $accounts = PVE::CertHelpers::list_acme_accounts(); - return [ map { { name => $_ } } @$accounts ]; - }}); + my $accounts = PVE::CertHelpers::list_acme_accounts(); + return [map { { name => $_ } } @$accounts]; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'register_account', path => 'account', method => 'POST', description => "Register a new ACME account with CA.", protected => 1, parameters => { - additionalProperties => 0, - properties => { - name => get_standard_option('pve-acme-account-name'), - contact => get_standard_option('pve-acme-account-contact'), - tos_url => { - description => 'URL of CA TermsOfService - setting this indicates agreement.', - type => 'string', - optional => 1, - }, - directory => get_standard_option('pve-acme-directory-url', { - default => $acme_default_directory_url, - optional => 1, - }), - 'eab-kid' => { - description => 'Key Identifier for External Account Binding.', - type => 'string', - requires => 'eab-hmac-key', - optional => 1, - }, - 'eab-hmac-key' => { - description => 'HMAC key for External Account Binding.', - type => 'string', - requires => 'eab-kid', - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + name => get_standard_option('pve-acme-account-name'), + contact => get_standard_option('pve-acme-account-contact'), + tos_url => { + description => 'URL of CA TermsOfService - setting this indicates agreement.', + type => 'string', + optional => 1, + }, + directory => get_standard_option( + 'pve-acme-directory-url', + { + default => $acme_default_directory_url, + optional => 1, + }, + ), + 'eab-kid' => { + description => 'Key Identifier for External Account Binding.', + type => 'string', + requires => 'eab-hmac-key', + optional => 1, + }, + 'eab-hmac-key' => { + description => 'HMAC key for External Account Binding.', + type => 'string', + requires => 'eab-kid', + optional => 1, + }, + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $account_name = extract_param($param, 'name') // 'default'; - my $account_file = "${acme_account_dir}/${account_name}"; - mkdir $acme_account_dir if ! -e $acme_account_dir; + my $account_name = extract_param($param, 'name') // 'default'; + my $account_file = "${acme_account_dir}/${account_name}"; + mkdir $acme_account_dir if !-e $acme_account_dir; - my $eab_kid = extract_param($param, 'eab-kid'); - my $eab_hmac_key = extract_param($param, 'eab-hmac-key'); + my $eab_kid = extract_param($param, 'eab-kid'); + my $eab_hmac_key = extract_param($param, 'eab-hmac-key'); - raise_param_exc({'name' => "ACME account config file '${account_name}' already exists."}) - if -e $account_file; + raise_param_exc({ + 'name' => "ACME account config file '${account_name}' already exists." }) + if -e $account_file; - my $directory = extract_param($param, 'directory') // $acme_default_directory_url; - my $contact = $account_contact_from_param->($param); + my $directory = extract_param($param, 'directory') // $acme_default_directory_url; + my $contact = $account_contact_from_param->($param); - my $realcmd = sub { - PVE::Cluster::cfs_lock_acme($account_name, 10, sub { - die "ACME account config file '${account_name}' already exists.\n" - if -e $account_file; + my $realcmd = sub { + PVE::Cluster::cfs_lock_acme( + $account_name, + 10, + sub { + die "ACME account config file '${account_name}' already exists.\n" + if -e $account_file; - my $acme = PVE::ACME->new($account_file, $directory); - print "Generating ACME account key..\n"; - $acme->init(4096); - print "Registering ACME account..\n"; + my $acme = PVE::ACME->new($account_file, $directory); + print "Generating ACME account key..\n"; + $acme->init(4096); + print "Registering ACME account..\n"; - my %info = (contact => $contact); - if (defined($eab_kid)) { - $info{eab} = { - kid => $eab_kid, - hmac_key => $eab_hmac_key - }; - } + my %info = (contact => $contact); + if (defined($eab_kid)) { + $info{eab} = { + kid => $eab_kid, + hmac_key => $eab_hmac_key, + }; + } - eval { $acme->new_account($param->{tos_url}, %info); }; + eval { $acme->new_account($param->{tos_url}, %info); }; - if (my $err = $@) { - unlink $account_file; - die "Registration failed: $err\n"; - } - print "Registration successful, account URL: '$acme->{location}'\n"; - }); - die $@ if $@; - }; + if (my $err = $@) { + unlink $account_file; + die "Registration failed: $err\n"; + } + print "Registration successful, account URL: '$acme->{location}'\n"; + }, + ); + die $@ if $@; + }; - return $rpcenv->fork_worker('acmeregister', undef, $authuser, $realcmd); - }}); + return $rpcenv->fork_worker('acmeregister', undef, $authuser, $realcmd); + }, +}); my $update_account = sub { my ($param, $msg, %info) = @_; @@ -190,325 +199,347 @@ my $update_account = sub { my $account_name = extract_param($param, 'name') // 'default'; my $account_file = "${acme_account_dir}/${account_name}"; - raise_param_exc({'name' => "ACME account config file '${account_name}' does not exist."}) - if ! -e $account_file; - + raise_param_exc({ 'name' => "ACME account config file '${account_name}' does not exist." }) + if !-e $account_file; my $rpcenv = PVE::RPCEnvironment::get(); my $authuser = $rpcenv->get_user(); my $realcmd = sub { - PVE::Cluster::cfs_lock_acme($account_name, 10, sub { - die "ACME account config file '${account_name}' does not exist.\n" - if ! -e $account_file; + PVE::Cluster::cfs_lock_acme( + $account_name, + 10, + sub { + die "ACME account config file '${account_name}' does not exist.\n" + if !-e $account_file; - my $acme = PVE::ACME->new($account_file); - $acme->load(); - $acme->update_account(%info); - if ($info{status} && $info{status} eq 'deactivated') { - my $deactivated_name; - for my $i (0..100) { - my $candidate = "${acme_account_dir}/_deactivated_${account_name}_${i}"; - if (! -e $candidate) { - $deactivated_name = $candidate; - last; - } - } - if ($deactivated_name) { - print "Renaming account file from '$account_file' to '$deactivated_name'\n"; - rename($account_file, $deactivated_name) or - warn ".. failed - $!\n"; - } else { - warn "No free slot to rename deactivated account file '$account_file', leaving in place\n"; - } - } - }); - die $@ if $@; + my $acme = PVE::ACME->new($account_file); + $acme->load(); + $acme->update_account(%info); + if ($info{status} && $info{status} eq 'deactivated') { + my $deactivated_name; + for my $i (0 .. 100) { + my $candidate = "${acme_account_dir}/_deactivated_${account_name}_${i}"; + if (!-e $candidate) { + $deactivated_name = $candidate; + last; + } + } + if ($deactivated_name) { + print + "Renaming account file from '$account_file' to '$deactivated_name'\n"; + rename($account_file, $deactivated_name) + or warn ".. failed - $!\n"; + } else { + warn + "No free slot to rename deactivated account file '$account_file', leaving in place\n"; + } + } + }, + ); + die $@ if $@; }; return $rpcenv->fork_worker("acme${msg}", undef, $authuser, $realcmd); }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update_account', path => 'account/{name}', method => 'PUT', - description => "Update existing ACME account information with CA. Note: not specifying any new account information triggers a refresh.", + description => + "Update existing ACME account information with CA. Note: not specifying any new account information triggers a refresh.", protected => 1, parameters => { - additionalProperties => 0, - properties => { - name => get_standard_option('pve-acme-account-name'), - contact => get_standard_option('pve-acme-account-contact', { - optional => 1, - }), - }, + additionalProperties => 0, + properties => { + name => get_standard_option('pve-acme-account-name'), + contact => get_standard_option('pve-acme-account-contact', { + optional => 1, + }), + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $contact = $account_contact_from_param->($param); - if (scalar @$contact) { - return $update_account->($param, 'update', contact => $contact); - } else { - return $update_account->($param, 'refresh'); - } - }}); + my $contact = $account_contact_from_param->($param); + if (scalar @$contact) { + return $update_account->($param, 'update', contact => $contact); + } else { + return $update_account->($param, 'refresh'); + } + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_account', path => 'account/{name}', method => 'GET', description => "Return existing ACME account information.", protected => 1, parameters => { - additionalProperties => 0, - properties => { - name => get_standard_option('pve-acme-account-name'), - }, + additionalProperties => 0, + properties => { + name => get_standard_option('pve-acme-account-name'), + }, }, returns => { - type => 'object', - additionalProperties => 0, - properties => { - account => { - type => 'object', - optional => 1, - renderer => 'yaml', - }, - directory => get_standard_option('pve-acme-directory-url', { - optional => 1, - }), - location => { - type => 'string', - optional => 1, - }, - tos => { - type => 'string', - optional => 1, - }, - }, + type => 'object', + additionalProperties => 0, + properties => { + account => { + type => 'object', + optional => 1, + renderer => 'yaml', + }, + directory => get_standard_option('pve-acme-directory-url', { + optional => 1, + }), + location => { + type => 'string', + optional => 1, + }, + tos => { + type => 'string', + optional => 1, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $account_name = extract_param($param, 'name') // 'default'; - my $account_file = "${acme_account_dir}/${account_name}"; + my $account_name = extract_param($param, 'name') // 'default'; + my $account_file = "${acme_account_dir}/${account_name}"; - raise_param_exc({'name' => "ACME account config file '${account_name}' does not exist."}) - if ! -e $account_file; + raise_param_exc({ + 'name' => "ACME account config file '${account_name}' does not exist." }) + if !-e $account_file; - my $acme = PVE::ACME->new($account_file); - $acme->load(); + my $acme = PVE::ACME->new($account_file); + $acme->load(); - my $res = {}; - $res->{account} = $acme->{account}; - $res->{directory} = $acme->{directory}; - $res->{location} = $acme->{location}; - $res->{tos} = $acme->{tos}; + my $res = {}; + $res->{account} = $acme->{account}; + $res->{directory} = $acme->{directory}; + $res->{location} = $acme->{location}; + $res->{tos} = $acme->{tos}; - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'deactivate_account', path => 'account/{name}', method => 'DELETE', description => "Deactivate existing ACME account at CA.", protected => 1, parameters => { - additionalProperties => 0, - properties => { - name => get_standard_option('pve-acme-account-name'), - }, + additionalProperties => 0, + properties => { + name => get_standard_option('pve-acme-account-name'), + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - return $update_account->($param, 'deactivate', status => 'deactivated'); - }}); + return $update_account->($param, 'deactivate', status => 'deactivated'); + }, +}); # TODO: deprecated, remove with pve 9 -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_tos', path => 'tos', method => 'GET', - description => "Retrieve ACME TermsOfService URL from CA. Deprecated, please use /cluster/acme/meta.", + description => + "Retrieve ACME TermsOfService URL from CA. Deprecated, please use /cluster/acme/meta.", permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => { - directory => get_standard_option('pve-acme-directory-url', { - default => $acme_default_directory_url, - optional => 1, - }), - }, + additionalProperties => 0, + properties => { + directory => get_standard_option( + 'pve-acme-directory-url', + { + default => $acme_default_directory_url, + optional => 1, + }, + ), + }, }, returns => { - type => 'string', - optional => 1, - description => 'ACME TermsOfService URL.', + type => 'string', + optional => 1, + description => 'ACME TermsOfService URL.', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $directory = extract_param($param, 'directory') // $acme_default_directory_url; + my $directory = extract_param($param, 'directory') // $acme_default_directory_url; - my $acme = PVE::ACME->new(undef, $directory); - my $meta = $acme->get_meta(); + my $acme = PVE::ACME->new(undef, $directory); + my $meta = $acme->get_meta(); - return $meta ? $meta->{termsOfService} : undef; - }}); + return $meta ? $meta->{termsOfService} : undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_meta', path => 'meta', method => 'GET', description => "Retrieve ACME Directory Meta Information", permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], + check => ['perm', '/nodes/{node}', ['Sys.Audit']], }, parameters => { - additionalProperties => 0, - properties => { - directory => get_standard_option('pve-acme-directory-url', { - default => $acme_default_directory_url, - optional => 1, - }), - }, + additionalProperties => 0, + properties => { + directory => get_standard_option( + 'pve-acme-directory-url', + { + default => $acme_default_directory_url, + optional => 1, + }, + ), + }, }, returns => { - type => 'object', - additionalProperties => 1, - properties => { - termsOfService => { - description => 'ACME TermsOfService URL.', - type => 'string', - optional => 1, - }, - externalAccountRequired => { - description => 'EAB Required', - type => 'boolean', - optional => 1, - }, - website => { - description => 'URL to more information about the ACME server.', - type => 'string', - optional => 1, - }, - caaIdentities => { - description => 'Hostnames referring to the ACME servers.', - type => 'array', - items => { - type => 'string', - }, - optional => 1, - }, - }, + type => 'object', + additionalProperties => 1, + properties => { + termsOfService => { + description => 'ACME TermsOfService URL.', + type => 'string', + optional => 1, + }, + externalAccountRequired => { + description => 'EAB Required', + type => 'boolean', + optional => 1, + }, + website => { + description => 'URL to more information about the ACME server.', + type => 'string', + optional => 1, + }, + caaIdentities => { + description => 'Hostnames referring to the ACME servers.', + type => 'array', + items => { + type => 'string', + }, + optional => 1, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $directory = extract_param($param, 'directory') // $acme_default_directory_url; + my $directory = extract_param($param, 'directory') // $acme_default_directory_url; - my $acme = PVE::ACME->new(undef, $directory); - my $meta = $acme->get_meta(); + my $acme = PVE::ACME->new(undef, $directory); + my $meta = $acme->get_meta(); - return $meta; - }}); + return $meta; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_directories', path => 'directories', method => 'GET', description => "Get named known ACME directory endpoints.", permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => 'object', - additionalProperties => 0, - properties => { - name => { - type => 'string', - }, - url => get_standard_option('pve-acme-directory-url'), - }, - }, + type => 'array', + items => { + type => 'object', + additionalProperties => 0, + properties => { + name => { + type => 'string', + }, + url => get_standard_option('pve-acme-directory-url'), + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - return $acme_directories; - }}); + return $acme_directories; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'challengeschema', path => 'challenge-schema', method => 'GET', description => "Get schema of ACME challenge types.", permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => 'object', - additionalProperties => 0, - properties => { - id => { - type => 'string', - }, - name => { - description => 'Human readable name, falls back to id', - type => 'string', - }, - type => { - type => 'string', - }, - schema => { - type => 'object', - }, - }, - }, + type => 'array', + items => { + type => 'object', + additionalProperties => 0, + properties => { + id => { + type => 'string', + }, + name => { + description => 'Human readable name, falls back to id', + type => 'string', + }, + type => { + type => 'string', + }, + schema => { + type => 'object', + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $plugin_type_enum = PVE::ACME::Challenge->lookup_types(); + my $plugin_type_enum = PVE::ACME::Challenge->lookup_types(); - my $res = []; + my $res = []; - for my $type (@$plugin_type_enum) { - my $plugin = PVE::ACME::Challenge->lookup($type); - next if !$plugin->can('get_supported_plugins'); + for my $type (@$plugin_type_enum) { + my $plugin = PVE::ACME::Challenge->lookup($type); + next if !$plugin->can('get_supported_plugins'); - my $plugin_type = $plugin->type(); - my $plugins = $plugin->get_supported_plugins(); - for my $id (sort keys %$plugins) { - my $schema = $plugins->{$id}; - push @$res, { - id => $id, - name => $schema->{name} // $id, - type => $plugin_type, - schema => $schema, - }; - } - } + my $plugin_type = $plugin->type(); + my $plugins = $plugin->get_supported_plugins(); + for my $id (sort keys %$plugins) { + my $schema = $plugins->{$id}; + push @$res, + { + id => $id, + name => $schema->{name} // $id, + type => $plugin_type, + schema => $schema, + }; + } + } - return $res; - }}); + return $res; + }, +}); 1; diff --git a/PVE/API2/ACMEPlugin.pm b/PVE/API2/ACMEPlugin.pm index 30616625..510101aa 100644 --- a/PVE/API2/ACMEPlugin.pm +++ b/PVE/API2/ACMEPlugin.pm @@ -17,20 +17,24 @@ use base qw(PVE::RESTHandler); my $plugin_config_file = "priv/acme/plugins.cfg"; -cfs_register_file($plugin_config_file, - sub { PVE::ACME::Challenge->parse_config(@_); }, - sub { PVE::ACME::Challenge->write_config(@_); }, +cfs_register_file( + $plugin_config_file, + sub { PVE::ACME::Challenge->parse_config(@_); }, + sub { PVE::ACME::Challenge->write_config(@_); }, ); PVE::ACME::DNSChallenge->register(); PVE::ACME::StandAlone->register(); PVE::ACME::Challenge->init(); -PVE::JSONSchema::register_standard_option('pve-acme-pluginid', { - type => 'string', - format => 'pve-configid', - description => 'Unique identifier for ACME plugin instance.', -}); +PVE::JSONSchema::register_standard_option( + 'pve-acme-pluginid', + { + type => 'string', + format => 'pve-configid', + description => 'Unique identifier for ACME plugin instance.', + }, +); my $plugin_type_enum = PVE::ACME::Challenge->lookup_types(); @@ -46,50 +50,50 @@ my $modify_cfg_for_api = sub { return $plugin_cfg; }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, description => "ACME plugin index.", protected => 1, parameters => { - additionalProperties => 0, - properties => { - type => { - description => "Only list ACME plugins of a specific type", - type => 'string', - enum => $plugin_type_enum, - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + type => { + description => "Only list ACME plugins of a specific type", + type => 'string', + enum => $plugin_type_enum, + optional => 1, + }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - plugin => get_standard_option('pve-acme-pluginid'), - }, - }, - links => [ { rel => 'child', href => "{plugin}" } ], + type => 'array', + items => { + type => "object", + properties => { + plugin => get_standard_option('pve-acme-pluginid'), + }, + }, + links => [{ rel => 'child', href => "{plugin}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $cfg = load_config(); + my $cfg = load_config(); - my $res = []; - foreach my $pluginid (keys %{$cfg->{ids}}) { - my $plugin_cfg = $modify_cfg_for_api->($cfg, $pluginid); - next if $param->{type} && $param->{type} ne $plugin_cfg->{type}; - push @$res, $plugin_cfg; - } + my $res = []; + foreach my $pluginid (keys %{ $cfg->{ids} }) { + my $plugin_cfg = $modify_cfg_for_api->($cfg, $pluginid); + next if $param->{type} && $param->{type} ne $plugin_cfg->{type}; + push @$res, $plugin_cfg; + } - return $res; - } + return $res; + }, }); __PACKAGE__->register_method({ @@ -98,24 +102,24 @@ __PACKAGE__->register_method({ method => 'GET', description => "Get ACME plugin configuration.", permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, protected => 1, parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-acme-pluginid'), - }, + additionalProperties => 0, + properties => { + id => get_standard_option('pve-acme-pluginid'), + }, }, returns => { - type => 'object', + type => 'object', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $cfg = load_config(); - return $modify_cfg_for_api->($cfg, $param->{id}); - } + my $cfg = load_config(); + return $modify_cfg_for_api->($cfg, $param->{id}); + }, }); __PACKAGE__->register_method({ @@ -124,35 +128,39 @@ __PACKAGE__->register_method({ method => 'POST', description => "Add ACME plugin configuration.", permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, protected => 1, parameters => PVE::ACME::Challenge->createSchema(), returns => { - type => "null" + type => "null", }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'id'); - my $type = extract_param($param, 'type'); + my $id = extract_param($param, 'id'); + my $type = extract_param($param, 'type'); - cfs_lock_file($plugin_config_file, undef, sub { - my $cfg = load_config(); - die "ACME plugin ID '$id' already exists\n" if defined($cfg->{ids}->{$id}); + cfs_lock_file( + $plugin_config_file, + undef, + sub { + my $cfg = load_config(); + die "ACME plugin ID '$id' already exists\n" if defined($cfg->{ids}->{$id}); - my $plugin = PVE::ACME::Challenge->lookup($type); - my $opts = $plugin->check_config($id, $param, 1, 1); + my $plugin = PVE::ACME::Challenge->lookup($type); + my $opts = $plugin->check_config($id, $param, 1, 1); - $cfg->{ids}->{$id} = $opts; - $cfg->{ids}->{$id}->{type} = $type; + $cfg->{ids}->{$id} = $opts; + $cfg->{ids}->{$id}->{type} = $type; - cfs_write_file($plugin_config_file, $cfg); - }); - die "$@" if $@; + cfs_write_file($plugin_config_file, $cfg); + }, + ); + die "$@" if $@; - return undef; - } + return undef; + }, }); __PACKAGE__->register_method({ @@ -161,51 +169,55 @@ __PACKAGE__->register_method({ method => 'PUT', description => "Update ACME plugin configuration.", permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, protected => 1, parameters => PVE::ACME::Challenge->updateSchema(), returns => { - type => "null" + type => "null", }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'id'); - my $delete = extract_param($param, 'delete'); - my $digest = extract_param($param, 'digest'); + my $id = extract_param($param, 'id'); + my $delete = extract_param($param, 'delete'); + my $digest = extract_param($param, 'digest'); - cfs_lock_file($plugin_config_file, undef, sub { - my $cfg = load_config(); - PVE::Tools::assert_if_modified($cfg->{digest}, $digest); - my $plugin_cfg = $cfg->{ids}->{$id}; - die "ACME plugin ID '$id' does not exist\n" if !$plugin_cfg; + cfs_lock_file( + $plugin_config_file, + undef, + sub { + my $cfg = load_config(); + PVE::Tools::assert_if_modified($cfg->{digest}, $digest); + my $plugin_cfg = $cfg->{ids}->{$id}; + die "ACME plugin ID '$id' does not exist\n" if !$plugin_cfg; - my $type = $plugin_cfg->{type}; - my $plugin = PVE::ACME::Challenge->lookup($type); + my $type = $plugin_cfg->{type}; + my $plugin = PVE::ACME::Challenge->lookup($type); - if (defined($delete)) { - my $schema = $plugin->private(); - my $options = $schema->{options}->{$type}; - for my $k (PVE::Tools::split_list($delete)) { - my $d = $options->{$k} || die "no such option '$k'\n"; - die "unable to delete required option '$k'\n" if !$d->{optional}; + if (defined($delete)) { + my $schema = $plugin->private(); + my $options = $schema->{options}->{$type}; + for my $k (PVE::Tools::split_list($delete)) { + my $d = $options->{$k} || die "no such option '$k'\n"; + die "unable to delete required option '$k'\n" if !$d->{optional}; - delete $cfg->{ids}->{$id}->{$k}; - } - } + delete $cfg->{ids}->{$id}->{$k}; + } + } - my $opts = $plugin->check_config($id, $param, 0, 1); - for my $k (sort keys %$opts) { - $plugin_cfg->{$k} = $opts->{$k}; - } + my $opts = $plugin->check_config($id, $param, 0, 1); + for my $k (sort keys %$opts) { + $plugin_cfg->{$k} = $opts->{$k}; + } - cfs_write_file($plugin_config_file, $cfg); - }); - die "$@" if $@; + cfs_write_file($plugin_config_file, $cfg); + }, + ); + die "$@" if $@; - return undef; - } + return undef; + }, }); __PACKAGE__->register_method({ @@ -214,34 +226,38 @@ __PACKAGE__->register_method({ method => 'DELETE', description => "Delete ACME plugin configuration.", permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, protected => 1, parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-acme-pluginid'), - }, + additionalProperties => 0, + properties => { + id => get_standard_option('pve-acme-pluginid'), + }, }, returns => { - type => "null" + type => "null", }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'id'); + my $id = extract_param($param, 'id'); - cfs_lock_file($plugin_config_file, undef, sub { - my $cfg = load_config(); + cfs_lock_file( + $plugin_config_file, + undef, + sub { + my $cfg = load_config(); - delete $cfg->{ids}->{$id}; + delete $cfg->{ids}->{$id}; - cfs_write_file($plugin_config_file, $cfg); - }); - die "$@" if $@; + cfs_write_file($plugin_config_file, $cfg); + }, + ); + die "$@" if $@; - return undef; - } + return undef; + }, }); sub load_config { diff --git a/PVE/API2/APT.pm b/PVE/API2/APT.pm index e9e15db7..b55d1358 100644 --- a/PVE/API2/APT.pm +++ b/PVE/API2/APT.pm @@ -46,70 +46,71 @@ __PACKAGE__->register_method({ method => 'GET', description => "Directory index for apt (Advanced Package Tool).", permissions => { - user => 'all', + user => 'all', }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => "array", - items => { - type => "object", - properties => { - id => { type => 'string' }, - }, - }, - links => [ { rel => 'child', href => "{id}" } ], + type => "array", + items => { + type => "object", + properties => { + id => { type => 'string' }, + }, + }, + links => [{ rel => 'child', href => "{id}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $res = [ - { id => 'changelog' }, - { id => 'repositories' }, - { id => 'update' }, - { id => 'versions' }, - ]; + my $res = [ + { id => 'changelog' }, + { id => 'repositories' }, + { id => 'update' }, + { id => 'versions' }, + ]; - return $res; - }}); + return $res; + }, +}); my $get_pkgfile = sub { - my ($veriter) = @_; + my ($veriter) = @_; - foreach my $verfile (@{$veriter->{FileList}}) { - my $pkgfile = $verfile->{File}; - next if !$pkgfile->{Origin}; - return $pkgfile; + foreach my $verfile (@{ $veriter->{FileList} }) { + my $pkgfile = $verfile->{File}; + next if !$pkgfile->{Origin}; + return $pkgfile; } return undef; }; my $assemble_pkginfo = sub { - my ($pkgname, $info, $current_ver, $candidate_ver) = @_; + my ($pkgname, $info, $current_ver, $candidate_ver) = @_; my $data = { - Package => $info->{Name}, - Title => $info->{ShortDesc}, - Origin => 'unknown', + Package => $info->{Name}, + Title => $info->{ShortDesc}, + Origin => 'unknown', }; if (my $pkgfile = &$get_pkgfile($candidate_ver)) { - $data->{Origin} = $pkgfile->{Origin}; + $data->{Origin} = $pkgfile->{Origin}; } if (my $desc = $info->{LongDesc}) { - $desc =~ s/^.*\n\s?//; # remove first line - $desc =~ s/\n / /g; - $data->{Description} = $desc; + $desc =~ s/^.*\n\s?//; # remove first line + $desc =~ s/\n / /g; + $data->{Description} = $desc; } foreach my $k (qw(Section Arch Priority)) { - $data->{$k} = $candidate_ver->{$k}; + $data->{$k} = $candidate_ver->{$k}; } $data->{Version} = $candidate_ver->{VerStr}; @@ -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; }; @@ -139,56 +142,56 @@ my $update_pve_pkgstatus = sub { my $pkgrecords = $cache->packages(); foreach my $pkgname (keys %$cache) { - my $p = $cache->{$pkgname}; - next if !$p->{SelectedState} || ($p->{SelectedState} ne 'Install'); - my $current_ver = $p->{CurrentVer} || next; - my $candidate_ver = $policy->candidate($p) || next; - next if $current_ver->{VerStr} eq $candidate_ver->{VerStr}; + my $p = $cache->{$pkgname}; + next if !$p->{SelectedState} || ($p->{SelectedState} ne 'Install'); + my $current_ver = $p->{CurrentVer} || next; + my $candidate_ver = $policy->candidate($p) || next; + next if $current_ver->{VerStr} eq $candidate_ver->{VerStr}; - my $info = $pkgrecords->lookup($pkgname); - my $res = &$assemble_pkginfo($pkgname, $info, $current_ver, $candidate_ver); - push @$pkglist, $res; + my $info = $pkgrecords->lookup($pkgname); + my $res = &$assemble_pkginfo($pkgname, $info, $current_ver, $candidate_ver); + push @$pkglist, $res; - # also check if we need any new package - # Note: this is just a quick hack (not recursive as it should be), because - # I found no way to get that info from AptPkg - my $deps = $candidate_ver->{DependsList} || next; + # also check if we need any new package + # Note: this is just a quick hack (not recursive as it should be), because + # I found no way to get that info from AptPkg + my $deps = $candidate_ver->{DependsList} || next; - my ($found, $req); - for my $d (@$deps) { - if ($d->{DepType} eq 'Depends') { - $found = $d->{TargetPkg}->{SelectedState} eq 'Install' if !$found; - # need to check ProvidesList for virtual packages - if (!$found && (my $provides = $d->{TargetPkg}->{ProvidesList})) { - for my $provide ($provides->@*) { - $found = $provide->{OwnerPkg}->{SelectedState} eq 'Install'; - last if $found; - } - } - $req = $d->{TargetPkg} if !$req; + my ($found, $req); + for my $d (@$deps) { + if ($d->{DepType} eq 'Depends') { + $found = $d->{TargetPkg}->{SelectedState} eq 'Install' if !$found; + # need to check ProvidesList for virtual packages + if (!$found && (my $provides = $d->{TargetPkg}->{ProvidesList})) { + for my $provide ($provides->@*) { + $found = $provide->{OwnerPkg}->{SelectedState} eq 'Install'; + last if $found; + } + } + $req = $d->{TargetPkg} if !$req; - if (!($d->{CompType} & AptPkg::Dep::Or)) { - if (!$found && $req) { # New required Package - my $tpname = $req->{Name}; - my $tpinfo = $pkgrecords->lookup($tpname); - my $tpcv = $policy->candidate($req); - if ($tpinfo && $tpcv) { - my $res = &$assemble_pkginfo($tpname, $tpinfo, undef, $tpcv); - push @$pkglist, $res; - } - } - undef $found; - undef $req; - } - } - } + if (!($d->{CompType} & AptPkg::Dep::Or)) { + if (!$found && $req) { # New required Package + my $tpname = $req->{Name}; + my $tpinfo = $pkgrecords->lookup($tpname); + my $tpcv = $policy->candidate($req); + if ($tpinfo && $tpcv) { + my $res = &$assemble_pkginfo($tpname, $tpinfo, undef, $tpcv); + push @$pkglist, $res; + } + } + undef $found; + undef $req; + } + } + } } # keep notification status (avoid sending mails abou new packages more than once) foreach my $pi (@$pkglist) { - if (my $ns = $notify_status->{$pi->{Package}}) { - $pi->{NotifyStatus} = $ns if $ns eq $pi->{Version}; - } + if (my $ns = $notify_status->{ $pi->{Package} }) { + $pi->{NotifyStatus} = $ns if $ns eq $pi->{Version}; + } } PVE::Tools::file_set_contents($pve_pkgstatus_fn, encode_json($pkglist)); @@ -202,171 +205,172 @@ __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', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => "array", - items => { - type => "object", - properties => {}, - }, + type => "array", + items => { + type => "object", + properties => {}, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - if (my $st1 = File::stat::stat($pve_pkgstatus_fn)) { - my $st2 = File::stat::stat("/var/cache/apt/pkgcache.bin"); - my $st3 = File::stat::stat("/var/lib/dpkg/status"); + if (my $st1 = File::stat::stat($pve_pkgstatus_fn)) { + my $st2 = File::stat::stat("/var/cache/apt/pkgcache.bin"); + my $st3 = File::stat::stat("/var/lib/dpkg/status"); - if ($st2 && $st3 && $st2->mtime <= $st1->mtime && $st3->mtime <= $st1->mtime) { - if (my $data = &$read_cached_pkgstatus()) { - return $data; - } - } - } + if ($st2 && $st3 && $st2->mtime <= $st1->mtime && $st3->mtime <= $st1->mtime) { + if (my $data = &$read_cached_pkgstatus()) { + return $data; + } + } + } - my $pkglist = &$update_pve_pkgstatus(); + my $pkglist = &$update_pve_pkgstatus(); - return $pkglist; - }}); + 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', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - notify => { - type => 'boolean', - description => "Send notification about new packages.", - optional => 1, - default => 0, - }, - quiet => { - type => 'boolean', - description => "Only produces output suitable for logging, omitting progress indicators.", - optional => 1, - default => 0, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + notify => { + type => 'boolean', + description => "Send notification about new packages.", + optional => 1, + default => 0, + }, + quiet => { + type => 'boolean', + description => + "Only produces output suitable for logging, omitting progress indicators.", + optional => 1, + default => 0, + }, + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $dcconf = PVE::Cluster::cfs_read_file('datacenter.cfg'); + my $rpcenv = PVE::RPCEnvironment::get(); + my $dcconf = PVE::Cluster::cfs_read_file('datacenter.cfg'); - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); - my $realcmd = sub { - my $upid = shift; + my $realcmd = sub { + my $upid = shift; - # setup proxy for apt + # setup proxy for apt - my $aptconf = "// no proxy configured\n"; - if ($dcconf->{http_proxy}) { - $aptconf = "Acquire::http::Proxy \"$dcconf->{http_proxy}\";\n"; - } - my $aptcfn = "/etc/apt/apt.conf.d/76pveproxy"; - PVE::Tools::file_set_contents($aptcfn, $aptconf); + my $aptconf = "// no proxy configured\n"; + if ($dcconf->{http_proxy}) { + $aptconf = "Acquire::http::Proxy \"$dcconf->{http_proxy}\";\n"; + } + my $aptcfn = "/etc/apt/apt.conf.d/76pveproxy"; + PVE::Tools::file_set_contents($aptcfn, $aptconf); - my $cmd = ['apt-get', 'update']; + my $cmd = ['apt-get', 'update']; - print "starting apt-get update\n" if !$param->{quiet}; + print "starting apt-get update\n" if !$param->{quiet}; - if ($param->{quiet}) { - PVE::Tools::run_command($cmd, outfunc => sub {}, errfunc => sub {}); - } else { - PVE::Tools::run_command($cmd); - } + if ($param->{quiet}) { + PVE::Tools::run_command($cmd, outfunc => sub { }, errfunc => sub { }); + } else { + PVE::Tools::run_command($cmd); + } - my $pkglist = &$update_pve_pkgstatus(); + my $pkglist = &$update_pve_pkgstatus(); - if ($param->{notify} && scalar(@$pkglist)) { - my $updates_table = { - schema => { - columns => [ - { - label => "Package Name", - id => "package-name", - }, - { - label => "Installed Version", - id => "installed-version", - }, - { - label => "Available Version", - id => "available-version", - } - ] - }, - data => [] - }; + if ($param->{notify} && scalar(@$pkglist)) { + my $updates_table = { + schema => { + columns => [ + { + label => "Package Name", + id => "package-name", + }, + { + label => "Installed Version", + id => "installed-version", + }, + { + label => "Available Version", + id => "available-version", + }, + ], + }, + data => [], + }; - my $count = 0; - foreach my $p (sort {$a->{Package} cmp $b->{Package} } @$pkglist) { - next if $p->{NotifyStatus} && $p->{NotifyStatus} eq $p->{Version}; - $count++; + my $count = 0; + foreach my $p (sort { $a->{Package} cmp $b->{Package} } @$pkglist) { + next if $p->{NotifyStatus} && $p->{NotifyStatus} eq $p->{Version}; + $count++; - push @{$updates_table->{data}}, { - "package-name" => $p->{Package}, - "installed-version" => $p->{OldVersion}, - "available-version" => $p->{Version} - }; - } + push @{ $updates_table->{data} }, + { + "package-name" => $p->{Package}, + "installed-version" => $p->{OldVersion}, + "available-version" => $p->{Version}, + }; + } - return if !$count; + return if !$count; - my $template_data = PVE::Notify::common_template_data(); - $template_data->{"available-updates"} = $updates_table; + my $template_data = PVE::Notify::common_template_data(); + $template_data->{"available-updates"} = $updates_table; - # Additional metadata fields that can be used in notification - # matchers. - my $metadata_fields = { - type => 'package-updates', - # Hostname (without domain part) - hostname => PVE::INotify::nodename(), - }; + # Additional metadata fields that can be used in notification + # matchers. + my $metadata_fields = { + type => 'package-updates', + # Hostname (without domain part) + hostname => PVE::INotify::nodename(), + }; - PVE::Notify::info( - "package-updates", - $template_data, - $metadata_fields, - ); + PVE::Notify::info( + "package-updates", $template_data, $metadata_fields, + ); - foreach my $pi (@$pkglist) { - $pi->{NotifyStatus} = $pi->{Version}; - } - PVE::Tools::file_set_contents($pve_pkgstatus_fn, encode_json($pkglist)); - } + foreach my $pi (@$pkglist) { + $pi->{NotifyStatus} = $pi->{Version}; + } + PVE::Tools::file_set_contents($pve_pkgstatus_fn, encode_json($pkglist)); + } - return; - }; - - return $rpcenv->fork_worker('aptupdate', undef, $authuser, $realcmd); - - }}); + return; + }; + return $rpcenv->fork_worker('aptupdate', undef, $authuser, $realcmd); + }, +}); __PACKAGE__->register_method({ name => 'changelog', @@ -374,55 +378,56 @@ __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 => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - name => { - description => "Package name.", - type => 'string', - }, - version => { - description => "Package version.", - type => 'string', - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + name => { + description => "Package name.", + type => 'string', + }, + version => { + description => "Package version.", + type => 'string', + optional => 1, + }, + }, }, returns => { - type => "string", + type => "string", }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $pkgname = $param->{name}; + my $pkgname = $param->{name}; - my $cmd = ['apt-get', 'changelog', '-qq']; - if (my $version = $param->{version}) { - push @$cmd, "$pkgname=$version"; - } else { - push @$cmd, "$pkgname"; - } + my $cmd = ['apt-get', 'changelog', '-qq']; + if (my $version = $param->{version}) { + push @$cmd, "$pkgname=$version"; + } else { + push @$cmd, "$pkgname"; + } - my $output = ""; + my $output = ""; - my $rc = PVE::Tools::run_command( - $cmd, - timeout => 10, - logfunc => sub { - my $line = shift; - $output .= "$line\n"; - }, - noerr => 1, - ); + my $rc = PVE::Tools::run_command( + $cmd, + timeout => 10, + logfunc => sub { + my $line = shift; + $output .= "$line\n"; + }, + noerr => 1, + ); - $output .= "RC: $rc" if $rc != 0; + $output .= "RC: $rc" if $rc != 0; - return $output; - }}); + return $output; + }, +}); __PACKAGE__->register_method({ name => 'repositories', @@ -431,196 +436,199 @@ __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, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => "object", - description => "Result from parsing the APT repository files in /etc/apt/.", - properties => { - files => { - type => "array", - description => "List of parsed repository files.", - items => { - type => "object", - properties => { - path => { - type => "string", - description => "Path to the problematic file.", - }, - 'file-type' => { - type => "string", - enum => [ 'list', 'sources' ], - description => "Format of the file.", - }, - repositories => { - type => "array", - description => "The parsed repositories.", - items => { - type => "object", - properties => { - Types => { - type => "array", - description => "List of package types.", - items => { - type => "string", - enum => [ 'deb', 'deb-src' ], - }, - }, - URIs => { - description => "List of repository URIs.", - type => "array", - items => { - type => "string", - }, - }, - Suites => { - type => "array", - description => "List of package distribuitions", - items => { - type => "string", - }, - }, - Components => { - type => "array", - description => "List of repository components", - optional => 1, # not present if suite is absolute - items => { - type => "string", - }, - }, - Options => { - type => "array", - description => "Additional options", - optional => 1, - items => { - type => "object", - properties => { - Key => { - type => "string", - }, - Values => { - type => "array", - items => { - type => "string", - }, - }, - }, - }, - }, - Comment => { - type => "string", - description => "Associated comment", - optional => 1, - }, - FileType => { - type => "string", - enum => [ 'list', 'sources' ], - description => "Format of the defining file.", - }, - Enabled => { - type => "boolean", - description => "Whether the repository is enabled or not", - }, - }, - }, - }, - digest => { - type => "array", - description => "Digest of the file as bytes.", - items => { - type => "integer", - }, - }, - }, - }, - }, - errors => { - type => "array", - description => "List of problematic repository files.", - items => { - type => "object", - properties => { - path => { - type => "string", - description => "Path to the problematic file.", - }, - error => { - type => "string", - description => "The error message", - }, - }, - }, - }, - digest => { - type => "string", - description => "Common digest of all files.", - }, - infos => { - type => "array", - description => "Additional information/warnings for APT repositories.", - items => { - type => "object", - properties => { - path => { - type => "string", - description => "Path to the associated file.", - }, - index => { - type => "string", - description => "Index of the associated repository within the file.", - }, - property => { - type => "string", - description => "Property from which the info originates.", - optional => 1, - }, - kind => { - type => "string", - description => "Kind of the information (e.g. warning).", - }, - message => { - type => "string", - description => "Information message.", - } - }, - }, - }, - 'standard-repos' => { - type => "array", - description => "List of standard repositories and their configuration status", - items => { - type => "object", - properties => { - handle => { - type => "string", - description => "Handle to identify the repository.", - }, - name => { - type => "string", - description => "Full name of the repository.", - }, - status => { - type => "boolean", - optional => 1, - description => "Indicating enabled/disabled status, if the " . - "repository is configured.", - }, - }, - }, - }, - }, + type => "object", + description => "Result from parsing the APT repository files in /etc/apt/.", + properties => { + files => { + type => "array", + description => "List of parsed repository files.", + items => { + type => "object", + properties => { + path => { + type => "string", + description => "Path to the problematic file.", + }, + 'file-type' => { + type => "string", + enum => ['list', 'sources'], + description => "Format of the file.", + }, + repositories => { + type => "array", + description => "The parsed repositories.", + items => { + type => "object", + properties => { + Types => { + type => "array", + description => "List of package types.", + items => { + type => "string", + enum => ['deb', 'deb-src'], + }, + }, + URIs => { + description => "List of repository URIs.", + type => "array", + items => { + type => "string", + }, + }, + Suites => { + type => "array", + description => "List of package distribuitions", + items => { + type => "string", + }, + }, + Components => { + type => "array", + description => "List of repository components", + optional => 1, # not present if suite is absolute + items => { + type => "string", + }, + }, + Options => { + type => "array", + description => "Additional options", + optional => 1, + items => { + type => "object", + properties => { + Key => { + type => "string", + }, + Values => { + type => "array", + items => { + type => "string", + }, + }, + }, + }, + }, + Comment => { + type => "string", + description => "Associated comment", + optional => 1, + }, + FileType => { + type => "string", + enum => ['list', 'sources'], + description => "Format of the defining file.", + }, + Enabled => { + type => "boolean", + description => + "Whether the repository is enabled or not", + }, + }, + }, + }, + digest => { + type => "array", + description => "Digest of the file as bytes.", + items => { + type => "integer", + }, + }, + }, + }, + }, + errors => { + type => "array", + description => "List of problematic repository files.", + items => { + type => "object", + properties => { + path => { + type => "string", + description => "Path to the problematic file.", + }, + error => { + type => "string", + description => "The error message", + }, + }, + }, + }, + digest => { + type => "string", + description => "Common digest of all files.", + }, + infos => { + type => "array", + description => "Additional information/warnings for APT repositories.", + items => { + type => "object", + properties => { + path => { + type => "string", + description => "Path to the associated file.", + }, + index => { + type => "string", + description => + "Index of the associated repository within the file.", + }, + property => { + type => "string", + description => "Property from which the info originates.", + optional => 1, + }, + kind => { + type => "string", + description => "Kind of the information (e.g. warning).", + }, + message => { + type => "string", + description => "Information message.", + }, + }, + }, + }, + 'standard-repos' => { + type => "array", + description => "List of standard repositories and their configuration status", + items => { + type => "object", + properties => { + handle => { + type => "string", + description => "Handle to identify the repository.", + }, + name => { + type => "string", + description => "Full name of the repository.", + }, + status => { + type => "boolean", + optional => 1, + description => "Indicating enabled/disabled status, if the " + . "repository is configured.", + }, + }, + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - return Proxmox::RS::APT::Repositories::repositories("pve"); - }}); + return Proxmox::RS::APT::Repositories::repositories("pve"); + }, +}); __PACKAGE__->register_method({ name => 'add_repository', @@ -628,88 +636,93 @@ __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', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - handle => { - type => 'string', - description => "Handle that identifies a repository.", - }, - digest => { - type => "string", - description => "Digest to detect modifications.", - maxLength => 80, - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + handle => { + type => 'string', + description => "Handle that identifies a repository.", + }, + digest => { + type => "string", + description => "Digest to detect modifications.", + maxLength => 80, + optional => 1, + }, + }, }, returns => { - type => 'null', + type => 'null', }, code => sub { - my ($param) = @_; + 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', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - path => { - type => 'string', - description => "Path to the containing file.", - }, - index => { - type => 'integer', - description => "Index within the file (starting from 0).", - }, - enabled => { - type => 'boolean', - description => "Whether the repository should be enabled or not.", - optional => 1, - }, - digest => { - type => "string", - description => "Digest to detect modifications.", - maxLength => 80, - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + path => { + type => 'string', + description => "Path to the containing file.", + }, + index => { + type => 'integer', + description => "Index within the file (starting from 0).", + }, + enabled => { + type => 'boolean', + description => "Whether the repository should be enabled or not.", + optional => 1, + }, + digest => { + type => "string", + description => "Digest to detect modifications.", + maxLength => 80, + optional => 1, + }, + }, }, returns => { - type => 'null', + type => 'null', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $options = {}; + my $options = {}; - my $enabled = $param->{enabled}; - $options->{enabled} = int($enabled) if defined($enabled); + my $enabled = $param->{enabled}; + $options->{enabled} = int($enabled) if defined($enabled); - Proxmox::RS::APT::Repositories::change_repository( - $param->{path}, - int($param->{index}), - $options, - $param->{digest} - ); - }}); + Proxmox::RS::APT::Repositories::change_repository( + $param->{path}, + int($param->{index}), + $options, + $param->{digest}, + ); + }, +}); __PACKAGE__->register_method({ name => 'versions', @@ -718,141 +731,152 @@ __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, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => "array", - items => { - type => "object", - properties => {}, - }, + type => "array", + items => { + type => "object", + properties => {}, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $cache = &$get_apt_cache(); - my $policy = $cache->policy; - my $pkgrecords = $cache->packages(); + my $cache = &$get_apt_cache(); + my $policy = $cache->policy; + my $pkgrecords = $cache->packages(); - # order most important things first - my @list = qw(proxmox-ve pve-manager); + # order most important things first + 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 $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 @opt_pack = qw( - amd64-microcode - ceph - criu - dnsmasq - frr-pythontools - gfs2-utils - ifupdown - ifupdown2 - intel-microcode - ksm-control-daemon - ksmtuned - libpve-apiclient-perl - libpve-network-perl - openvswitch-switch - proxmox-backup-file-restore - proxmox-firewall - proxmox-kernel-helper - proxmox-offline-mirror-helper - pve-esxi-import-tools - pve-zsync - zfsutils-linux - ); + amd64-microcode + ceph + criu + dnsmasq + frr-pythontools + gfs2-utils + ifupdown + ifupdown2 + intel-microcode + ksm-control-daemon + ksmtuned + libpve-apiclient-perl + libpve-network-perl + openvswitch-switch + proxmox-backup-file-restore + proxmox-firewall + proxmox-kernel-helper + proxmox-offline-mirror-helper + pve-esxi-import-tools + pve-zsync + zfsutils-linux + ); - my @pkgs = qw( - ceph-fuse - corosync - glusterfs-client - libjs-extjs - libknet1 - libproxmox-acme-perl - libproxmox-backup-qemu0 - libproxmox-rs-perl - libpve-access-control - libpve-cluster-api-perl - libpve-cluster-perl - libpve-common-perl - libpve-guest-common-perl - libpve-http-server-perl - livpve-notify-perl - libpve-rs-perl - libpve-storage-perl - libqb0 - libspice-server1 - lvm2 - lxc-pve - lxcfs - novnc-pve - proxmox-backup-client - proxmox-backup-restore-image - proxmox-mail-forward - proxmox-mini-journalreader - proxmox-widget-toolkit - pve-cluster - pve-container - pve-docs - pve-edk2-firmware - pve-firewall - pve-firmware - pve-ha-manager - pve-i18n - pve-qemu-kvm - pve-xtermjs - qemu-server - smartmontools - spiceterm - swtpm - vncterm - ); + my @pkgs = qw( + ceph-fuse + corosync + glusterfs-client + libjs-extjs + libknet1 + libproxmox-acme-perl + libproxmox-backup-qemu0 + libproxmox-rs-perl + libpve-access-control + libpve-cluster-api-perl + libpve-cluster-perl + libpve-common-perl + libpve-guest-common-perl + libpve-http-server-perl + livpve-notify-perl + libpve-rs-perl + libpve-storage-perl + libqb0 + libspice-server1 + lvm2 + lxc-pve + lxcfs + novnc-pve + proxmox-backup-client + proxmox-backup-restore-image + proxmox-mail-forward + proxmox-mini-journalreader + proxmox-widget-toolkit + pve-cluster + pve-container + pve-docs + pve-edk2-firmware + pve-firewall + pve-firmware + pve-ha-manager + pve-i18n + pve-qemu-kvm + pve-xtermjs + qemu-server + smartmontools + spiceterm + swtpm + vncterm + ); - # add the rest ordered by name, easier to find for humans - push @list, (sort @pkgs, @opt_pack); + # add the rest ordered by name, easier to find for humans + push @list, (sort @pkgs, @opt_pack); - my (undef, undef, $kernel_release) = POSIX::uname(); - my $pvever = PVE::pvecfg::version_text(); + my (undef, undef, $kernel_release) = POSIX::uname(); + my $pvever = PVE::pvecfg::version_text(); - my $pkglist = []; - foreach my $pkgname (@list) { - my $p = $cache->{$pkgname}; - my $info = $pkgrecords->lookup($pkgname); - 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); - } elsif ($candidate_ver) { - $res = $assemble_pkginfo->($pkgname, $info, $candidate_ver, $candidate_ver); - delete $res->{OldVersion}; - } else { - next; - } - $res->{CurrentState} = $p->{CurrentState}; + my $pkglist = []; + foreach my $pkgname (@list) { + my $p = $cache->{$pkgname}; + my $info = $pkgrecords->lookup($pkgname); + 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, + ); + } elsif ($candidate_ver) { + $res = $assemble_pkginfo->($pkgname, $info, $candidate_ver, $candidate_ver); + delete $res->{OldVersion}; + } else { + next; + } + $res->{CurrentState} = $p->{CurrentState}; - # hack: add some useful information (used by 'pveversion -v') - if ($pkgname eq 'pve-manager') { - $res->{ManagerVersion} = $pvever; - } elsif ($pkgname eq 'proxmox-ve') { - $res->{RunningKernel} = $kernel_release; - } - if (grep( /^$pkgname$/, @opt_pack)) { - next if $res->{CurrentState} eq 'NotInstalled'; - } + # hack: add some useful information (used by 'pveversion -v') + if ($pkgname eq 'pve-manager') { + $res->{ManagerVersion} = $pvever; + } elsif ($pkgname eq 'proxmox-ve') { + $res->{RunningKernel} = $kernel_release; + } + if (grep(/^$pkgname$/, @opt_pack)) { + next if $res->{CurrentState} eq 'NotInstalled'; + } - push @$pkglist, $res; - } + push @$pkglist, $res; + } - return $pkglist; - }}); + return $pkglist; + }, +}); 1; diff --git a/PVE/API2/Backup.pm b/PVE/API2/Backup.pm index f37e2393..42e9c418 100644 --- a/PVE/API2/Backup.pm +++ b/PVE/API2/Backup.pm @@ -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,17 +41,17 @@ 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' ]) - if $fleecing->{storage}; + my $fleecing = PVE::VZDump::parse_fleecing($param) // {}; + $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,19 +74,19 @@ 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 if ($current->{dumpdir}) { - die "only root\@pam may edit jobs with a 'dumpdir' option."; + 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' ]); - } + if (my $storeid = PVE::VZDump::get_storage_param($current)) { + $rpcenv->check($user, "/storage/$storeid", ['Datastore.Allocate']); + } } } @@ -105,14 +106,14 @@ my $convert_to_schedule = sub { my $schedule_param_check = sub { my ($param, $required) = @_; if (defined($param->{schedule})) { - if (defined($param->{starttime})) { - raise_param_exc({ starttime => "'starttime' and 'schedule' cannot both be set" }); - } + if (defined($param->{starttime})) { + raise_param_exc({ starttime => "'starttime' and 'schedule' cannot both be set" }); + } } elsif (!defined($param->{starttime})) { - raise_param_exc({ schedule => "neither 'starttime' nor 'schedule' were set" }) - if $required; + raise_param_exc({ schedule => "neither 'starttime' nor 'schedule' were set" }) + if $required; } else { - $param->{schedule} = $convert_to_schedule->($param); + $param->{schedule} = $convert_to_schedule->($param); } delete $param->{starttime}; @@ -125,60 +126,61 @@ __PACKAGE__->register_method({ method => 'GET', description => "List vzdump backup schedule.", permissions => { - check => ['perm', '/', ['Sys.Audit']], + check => ['perm', '/', ['Sys.Audit']], }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - id => get_standard_option('pve-backup-jobid'), - }, - }, - links => [ { rel => 'child', href => "{id}" } ], + type => 'array', + items => { + type => "object", + properties => { + id => get_standard_option('pve-backup-jobid'), + }, + }, + links => [{ rel => 'child', href => "{id}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); - my $data = cfs_read_file('vzdump.cron'); - my $jobs_data = cfs_read_file('jobs.cfg'); - my $order = $jobs_data->{order}; - my $jobs = $jobs_data->{ids}; + my $data = cfs_read_file('vzdump.cron'); + my $jobs_data = cfs_read_file('jobs.cfg'); + my $order = $jobs_data->{order}; + my $jobs = $jobs_data->{ids}; - my $res = $data->{jobs} || []; - foreach my $job (@$res) { - $job->{schedule} = $convert_to_schedule->($job); - } + my $res = $data->{jobs} || []; + foreach my $job (@$res) { + $job->{schedule} = $convert_to_schedule->($job); + } - foreach my $jobid (sort { $order->{$a} <=> $order->{$b} } keys %$jobs) { - my $job = $jobs->{$jobid}; - next if $job->{type} ne 'vzdump'; + foreach my $jobid (sort { $order->{$a} <=> $order->{$b} } keys %$jobs) { + my $job = $jobs->{$jobid}; + next if $job->{type} ne 'vzdump'; - if (my $schedule = $job->{schedule}) { - # vzdump jobs are cluster wide, there maybe was no local run - # so simply calculate from now - my $last_run = time(); - my $calspec = Proxmox::RS::CalendarEvent->new($schedule); - my $next_run = $calspec->compute_next_event($last_run); - $job->{'next-run'} = $next_run if defined($next_run); - } + if (my $schedule = $job->{schedule}) { + # vzdump jobs are cluster wide, there maybe was no local run + # so simply calculate from now + my $last_run = time(); + my $calspec = Proxmox::RS::CalendarEvent->new($schedule); + my $next_run = $calspec->compute_next_event($last_run); + $job->{'next-run'} = $next_run if defined($next_run); + } - # FIXME remove in PVE 8.0? - # backwards compat: before moving the job registry to pve-common, id was auto-injected - $job->{id} = $jobid; + # FIXME remove in PVE 8.0? + # backwards compat: before moving the job registry to pve-common, id was auto-injected + $job->{id} = $jobid; - push @$res, $job; - } + push @$res, $job; + } - return $res; - }}); + return $res; + }, +}); __PACKAGE__->register_method({ name => 'create_job', @@ -187,99 +189,109 @@ __PACKAGE__->register_method({ protected => 1, 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.", + check => ['perm', '/', ['Sys.Modify']], + 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 => { - type => 'string', - description => "Job ID (will be autogenerated).", - format => 'pve-configid', - 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', - maxLength => 128, - optional => 1, - }, - starttime => { - type => 'string', - description => "Job Start time.", - pattern => '\d{1,2}:\d{1,2}', - typetext => 'HH:MM', - optional => 1, - }, - dow => { - type => 'string', format => 'pve-day-of-week-list', - optional => 1, - description => "Day of week selection.", - requires => 'starttime', - default => ALL_DAYS, - }, - enabled => { - type => 'boolean', - optional => 1, - description => "Enable or disable the job.", - default => '1', - }, - '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.", - default => 0, - }, - comment => { - optional => 1, - type => 'string', - description => "Description for the Job.", - maxLength => 512, - }, - }), + additionalProperties => 0, + properties => PVE::VZDump::Common::json_config_properties({ + id => { + type => 'string', + description => "Job ID (will be autogenerated).", + format => 'pve-configid', + 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', + maxLength => 128, + optional => 1, + }, + starttime => { + type => 'string', + description => "Job Start time.", + pattern => '\d{1,2}:\d{1,2}', + typetext => 'HH:MM', + optional => 1, + }, + dow => { + type => 'string', + format => 'pve-day-of-week-list', + optional => 1, + description => "Day of week selection.", + requires => 'starttime', + default => ALL_DAYS, + }, + enabled => { + type => 'boolean', + optional => 1, + description => "Enable or disable the job.", + default => '1', + }, + '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.", + default => 0, + }, + comment => { + optional => 1, + type => 'string', + description => "Description for the Job.", + maxLength => 512, + }, + }), }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); - assert_param_permission_create($rpcenv, $user, $param); + assert_param_permission_create($rpcenv, $user, $param); - if (my $pool = $param->{pool}) { - $rpcenv->check_pool_exist($pool); - $rpcenv->check($user, "/pool/$pool", ['VM.Backup']); - } + if (my $pool = $param->{pool}) { + $rpcenv->check_pool_exist($pool); + $rpcenv->check($user, "/pool/$pool", ['VM.Backup']); + } - $schedule_param_check->($param, 1); + $schedule_param_check->($param, 1); - $param->{enabled} = 1 if !defined($param->{enabled}); + $param->{enabled} = 1 if !defined($param->{enabled}); - # autogenerate id for api compatibility FIXME remove with 8.0 - my $id = extract_param($param, 'id') // UUID::uuid(); + # 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 { - my $data = cfs_read_file('jobs.cfg'); + cfs_lock_file( + 'jobs.cfg', + undef, + sub { + my $data = cfs_read_file('jobs.cfg'); - die "Job '$id' already exists\n" - if $data->{ids}->{$id}; + die "Job '$id' already exists\n" + if $data->{ids}->{$id}; - PVE::VZDump::verify_vzdump_parameters($param, 1); - my $opts = PVE::VZDump::JobBase->check_config($id, $param, 1, 1); + PVE::VZDump::verify_vzdump_parameters($param, 1); + my $opts = PVE::VZDump::JobBase->check_config($id, $param, 1, 1); - $data->{ids}->{$id} = $opts; + $data->{ids}->{$id} = $opts; - PVE::Jobs::create_job($id, 'vzdump', $opts); + PVE::Jobs::create_job($id, 'vzdump', $opts); - cfs_write_file('jobs.cfg', $data); - }); - die "$@" if ($@); + cfs_write_file('jobs.cfg', $data); + }, + ); + die "$@" if ($@); - return undef; - }}); + return undef; + }, +}); __PACKAGE__->register_method({ name => 'read_job', @@ -287,46 +299,47 @@ __PACKAGE__->register_method({ method => 'GET', description => "Read vzdump backup job definition.", permissions => { - check => ['perm', '/', ['Sys.Audit']], + check => ['perm', '/', ['Sys.Audit']], }, parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-backup-jobid'), - }, + additionalProperties => 0, + properties => { + id => get_standard_option('pve-backup-jobid'), + }, }, returns => { - type => 'object', + type => 'object', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); - my $data = cfs_read_file('vzdump.cron'); + my $data = cfs_read_file('vzdump.cron'); - my $jobs = $data->{jobs} || []; + my $jobs = $data->{jobs} || []; - foreach my $job (@$jobs) { - if ($job->{id} eq $param->{id}) { - $job->{schedule} = $convert_to_schedule->($job); - return $job; - } - } + foreach my $job (@$jobs) { + if ($job->{id} eq $param->{id}) { + $job->{schedule} = $convert_to_schedule->($job); + return $job; + } + } - my $jobs_data = cfs_read_file('jobs.cfg'); - 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 - $job->{id} = $param->{id}; - return $job; - } + my $jobs_data = cfs_read_file('jobs.cfg'); + 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 + $job->{id} = $param->{id}; + return $job; + } - raise_param_exc({ id => "No such job '$param->{id}'" }); + raise_param_exc({ id => "No such job '$param->{id}'" }); - }}); + }, +}); __PACKAGE__->register_method({ name => 'delete_job', @@ -334,64 +347,69 @@ __PACKAGE__->register_method({ method => 'DELETE', description => "Delete vzdump backup job definition.", permissions => { - check => ['perm', '/', ['Sys.Modify']], + check => ['perm', '/', ['Sys.Modify']], }, protected => 1, parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-backup-jobid'), - }, + additionalProperties => 0, + properties => { + id => get_standard_option('pve-backup-jobid'), + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); - my $id = $param->{id}; + my $id = $param->{id}; - my $delete_job = sub { - my $data = cfs_read_file('vzdump.cron'); + my $delete_job = sub { + my $data = cfs_read_file('vzdump.cron'); - my $jobs = $data->{jobs} || []; - my $newjobs = []; + my $jobs = $data->{jobs} || []; + my $newjobs = []; - my $found; - foreach my $job (@$jobs) { - if ($job->{id} eq $id) { - $found = 1; - } else { - push @$newjobs, $job; - } - } + my $found; + foreach my $job (@$jobs) { + if ($job->{id} eq $id) { + $found = 1; + } else { + push @$newjobs, $job; + } + } - if (!$found) { - cfs_lock_file('jobs.cfg', undef, sub { - my $jobs_data = cfs_read_file('jobs.cfg'); + if (!$found) { + cfs_lock_file( + 'jobs.cfg', + undef, + sub { + my $jobs_data = cfs_read_file('jobs.cfg'); - if (!defined($jobs_data->{ids}->{$id})) { - raise_param_exc({ id => "No such job '$id'" }); - } - delete $jobs_data->{ids}->{$id}; + if (!defined($jobs_data->{ids}->{$id})) { + raise_param_exc({ id => "No such job '$id'" }); + } + delete $jobs_data->{ids}->{$id}; - PVE::Jobs::remove_job($id, 'vzdump'); + PVE::Jobs::remove_job($id, 'vzdump'); - cfs_write_file('jobs.cfg', $jobs_data); - }); - die "$@" if $@; - } else { - $data->{jobs} = $newjobs; + cfs_write_file('jobs.cfg', $jobs_data); + }, + ); + die "$@" if $@; + } else { + $data->{jobs} = $newjobs; - cfs_write_file('vzdump.cron', $data); - } - }; - cfs_lock_file('vzdump.cron', undef, $delete_job); - die "$@" if ($@); + cfs_write_file('vzdump.cron', $data); + } + }; + cfs_lock_file('vzdump.cron', undef, $delete_job); + die "$@" if ($@); - return undef; - }}); + return undef; + }, +}); __PACKAGE__->register_method({ name => 'update_job', @@ -400,337 +418,363 @@ __PACKAGE__->register_method({ protected => 1, 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.", + check => ['perm', '/', ['Sys.Modify']], + 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', - maxLength => 128, - optional => 1, - }, - starttime => { - type => 'string', - description => "Job Start time.", - pattern => '\d{1,2}:\d{1,2}', - typetext => 'HH:MM', - optional => 1, - }, - dow => { - type => 'string', format => 'pve-day-of-week-list', - optional => 1, - requires => 'starttime', - description => "Day of week selection.", - }, - delete => { - type => 'string', format => 'pve-configid-list', - description => "A list of settings you want to delete.", - optional => 1, - }, - enabled => { - type => 'boolean', - optional => 1, - description => "Enable or disable the job.", - default => '1', - }, - '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.", - default => 0, - }, - comment => { - optional => 1, - type => 'string', - description => "Description for the Job.", - maxLength => 512, - }, - }), + 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', + maxLength => 128, + optional => 1, + }, + starttime => { + type => 'string', + description => "Job Start time.", + pattern => '\d{1,2}:\d{1,2}', + typetext => 'HH:MM', + optional => 1, + }, + dow => { + type => 'string', + format => 'pve-day-of-week-list', + optional => 1, + requires => 'starttime', + description => "Day of week selection.", + }, + delete => { + type => 'string', + format => 'pve-configid-list', + description => "A list of settings you want to delete.", + optional => 1, + }, + enabled => { + type => 'boolean', + optional => 1, + description => "Enable or disable the job.", + default => '1', + }, + '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.", + default => 0, + }, + comment => { + optional => 1, + type => 'string', + description => "Description for the Job.", + maxLength => 512, + }, + }), }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); - my $id = extract_param($param, 'id'); - my $delete = extract_param($param, 'delete'); - $delete = { map { $_ => 1 } PVE::Tools::split_list($delete) } if $delete; + my $id = extract_param($param, 'id'); + my $delete = extract_param($param, 'delete'); + $delete = { map { $_ => 1 } PVE::Tools::split_list($delete) } if $delete; - assert_param_permission_update($rpcenv, $user, $param, $delete); + assert_param_permission_update($rpcenv, $user, $param, $delete); - if (my $pool = $param->{pool}) { - $rpcenv->check_pool_exist($pool); - $rpcenv->check($user, "/pool/$pool", ['VM.Backup']); - } + if (my $pool = $param->{pool}) { + $rpcenv->check_pool_exist($pool); + $rpcenv->check($user, "/pool/$pool", ['VM.Backup']); + } - $schedule_param_check->($param); + $schedule_param_check->($param); - my $update_job = sub { - my $data = cfs_read_file('vzdump.cron'); - my $jobs_data = cfs_read_file('jobs.cfg'); + my $update_job = sub { + my $data = cfs_read_file('vzdump.cron'); + my $jobs_data = cfs_read_file('jobs.cfg'); - my $jobs = $data->{jobs} || []; + 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); + PVE::VZDump::verify_vzdump_parameters($param); + my $opts = PVE::VZDump::JobBase->check_config($id, $param, 0, 1); - # try to find it in old vzdump.cron and convert it to a job - my ($idx) = grep { $jobs->[$_]->{id} eq $id } (0 .. scalar(@$jobs) - 1); + # try to find it in old vzdump.cron and convert it to a job + my ($idx) = grep { $jobs->[$_]->{id} eq $id } (0 .. scalar(@$jobs) - 1); - my $job; - if (defined($idx)) { - $job = splice @$jobs, $idx, 1; - $job->{schedule} = $convert_to_schedule->($job); - delete $job->{starttime}; - delete $job->{dow}; - delete $job->{id}; - $job->{type} = 'vzdump'; - $jobs_data->{ids}->{$id} = $job; - } else { - $job = $jobs_data->{ids}->{$id}; - die "no such vzdump job\n" if !$job || $job->{type} ne 'vzdump'; - } + my $job; + if (defined($idx)) { + $job = splice @$jobs, $idx, 1; + $job->{schedule} = $convert_to_schedule->($job); + delete $job->{starttime}; + delete $job->{dow}; + delete $job->{id}; + $job->{type} = 'vzdump'; + $jobs_data->{ids}->{$id} = $job; + } else { + $job = $jobs_data->{ids}->{$id}; + die "no such vzdump job\n" if !$job || $job->{type} ne 'vzdump'; + } - assert_param_permission_update($rpcenv, $user, $param, $delete, $job); + assert_param_permission_update($rpcenv, $user, $param, $delete, $job); - my $deletable = { - comment => 1, - 'repeat-missed' => 1, - }; + my $deletable = { + comment => 1, + 'repeat-missed' => 1, + }; - for my $k (keys $delete->%*) { - if (!PVE::VZDump::option_exists($k) && !$deletable->{$k}) { - raise_param_exc({ delete => "unknown option '$k'" }); - } + for my $k (keys $delete->%*) { + if (!PVE::VZDump::option_exists($k) && !$deletable->{$k}) { + raise_param_exc({ delete => "unknown option '$k'" }); + } - delete $job->{$k}; - } + delete $job->{$k}; + } - foreach my $k (keys %$param) { - $job->{$k} = $param->{$k}; - } + foreach my $k (keys %$param) { + $job->{$k} = $param->{$k}; + } - $job->{all} = 1 if (defined($job->{exclude}) && !defined($job->{pool})); + $job->{all} = 1 if (defined($job->{exclude}) && !defined($job->{pool})); - if (defined($param->{vmid})) { - delete $job->{all}; - delete $job->{exclude}; - delete $job->{pool}; - } elsif ($param->{all}) { - delete $job->{vmid}; - delete $job->{pool}; - } elsif ($job->{pool}) { - delete $job->{vmid}; - delete $job->{all}; - delete $job->{exclude}; - } + if (defined($param->{vmid})) { + delete $job->{all}; + delete $job->{exclude}; + delete $job->{pool}; + } elsif ($param->{all}) { + delete $job->{vmid}; + delete $job->{pool}; + } elsif ($job->{pool}) { + delete $job->{vmid}; + delete $job->{all}; + delete $job->{exclude}; + } - PVE::VZDump::verify_vzdump_parameters($job, 1); + PVE::VZDump::verify_vzdump_parameters($job, 1); - if (defined($idx)) { - cfs_write_file('vzdump.cron', $data); - } - cfs_write_file('jobs.cfg', $jobs_data); + if (defined($idx)) { + cfs_write_file('vzdump.cron', $data); + } + cfs_write_file('jobs.cfg', $jobs_data); - PVE::Jobs::detect_changed_runtime_props($id, 'vzdump', $job); + PVE::Jobs::detect_changed_runtime_props($id, 'vzdump', $job); - return; - }; - cfs_lock_file('vzdump.cron', undef, sub { - cfs_lock_file('jobs.cfg', undef, $update_job); - die "$@" if ($@); - }); - die "$@" if ($@); - }}); + return; + }; + 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']], + check => ['perm', '/', ['Sys.Audit']], }, parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-backup-jobid'), - }, + additionalProperties => 0, + properties => { + id => get_standard_option('pve-backup-jobid'), + }, }, returns => { - type => 'object', - description => 'Root node of the tree object. Children represent guests, grandchildren represent volumes of that guest.', - properties => { - children => { - type => 'array', - items => { - type => 'object', - properties => { - id => { - type => 'integer', - description => 'VMID of the guest.', - }, - name => { - type => 'string', - description => 'Name of the guest', - optional => 1, - }, - type => { - type => 'string', - description => 'Type of the guest, 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.', - items => { - type => 'object', - properties => { - id => { - type => 'string', - description => 'Configuration key of the volume.', - }, - name => { - type => 'string', - description => 'Name of the volume.', - }, - included => { - type => 'boolean', - description => 'Whether the volume is included in the backup or not.', - }, - reason => { - type => 'string', - description => 'The reason why the volume is included (or excluded).', - }, - }, - }, - }, - }, - }, - }, - }, + type => 'object', + description => + 'Root node of the tree object. Children represent guests, grandchildren represent volumes of that guest.', + properties => { + children => { + type => 'array', + items => { + type => 'object', + properties => { + id => { + type => 'integer', + description => 'VMID of the guest.', + }, + name => { + type => 'string', + description => 'Name of the guest', + optional => 1, + }, + type => { + type => 'string', + description => + 'Type of the guest, 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.', + items => { + type => 'object', + properties => { + id => { + type => 'string', + description => 'Configuration key of the volume.', + }, + name => { + type => 'string', + description => 'Name of the volume.', + }, + included => { + type => 'boolean', + description => + 'Whether the volume is included in the backup or not.', + }, + reason => { + type => 'string', + description => + 'The reason why the volume is included (or excluded).', + }, + }, + }, + }, + }, + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $user = $rpcenv->get_user(); - my $vzconf = cfs_read_file('vzdump.cron'); - my $all_jobs = $vzconf->{jobs} || []; - my $job; - my $rrd = PVE::Cluster::rrd_dump(); + my $vzconf = cfs_read_file('vzdump.cron'); + my $all_jobs = $vzconf->{jobs} || []; + my $job; + my $rrd = PVE::Cluster::rrd_dump(); - for my $j (@$all_jobs) { - if ($j->{id} eq $param->{id}) { - $job = $j; - last; - } - } - if (!$job) { - my $jobs_data = cfs_read_file('jobs.cfg'); - my $j = $jobs_data->{ids}->{$param->{id}}; - if ($j && $j->{type} eq 'vzdump') { - $job = $j; - } - } - raise_param_exc({ id => "No such job '$param->{id}'" }) if !$job; + for my $j (@$all_jobs) { + if ($j->{id} eq $param->{id}) { + $job = $j; + last; + } + } + if (!$job) { + my $jobs_data = cfs_read_file('jobs.cfg'); + my $j = $jobs_data->{ids}->{ $param->{id} }; + if ($j && $j->{type} eq 'vzdump') { + $job = $j; + } + } + raise_param_exc({ id => "No such job '$param->{id}'" }) if !$job; - my $vmlist = PVE::Cluster::get_vmlist(); + my $vmlist = PVE::Cluster::get_vmlist(); - my @job_vmids; + my @job_vmids; - my $included_guests = PVE::VZDump::get_included_guests($job); + my $included_guests = PVE::VZDump::get_included_guests($job); - for my $node (keys %{$included_guests}) { - my $node_vmids = $included_guests->{$node}; - push(@job_vmids, @{$node_vmids}); - } + for my $node (keys %{$included_guests}) { + my $node_vmids = $included_guests->{$node}; + push(@job_vmids, @{$node_vmids}); + } - # 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); - } @job_vmids; + # 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); + } @job_vmids; - my $result = { - children => [], - }; + my $result = { + children => [], + }; - for my $vmid (@allowed_vmids) { + for my $vmid (@allowed_vmids) { - my $children = []; + my $children = []; - # It's possible that a job has VMIDs configured that are not in - # vmlist. This could be because a guest was removed but not purged. - # Since there is no more data available we can only deliver the VMID - # and no volumes. - if (!defined $vmlist->{ids}->{$vmid}) { - push(@{$result->{children}}, { - id => int($vmid), - type => 'unknown', - leaf => 1, - }); - next; - } + # It's possible that a job has VMIDs configured that are not in + # vmlist. This could be because a guest was removed but not purged. + # Since there is no more data available we can only deliver the VMID + # and no volumes. + if (!defined $vmlist->{ids}->{$vmid}) { + push( + @{ $result->{children} }, + { + id => int($vmid), + type => 'unknown', + leaf => 1, + }, + ); + next; + } - my $type = $vmlist->{ids}->{$vmid}->{type}; - my $node = $vmlist->{ids}->{$vmid}->{node}; + my $type = $vmlist->{ids}->{$vmid}->{type}; + my $node = $vmlist->{ids}->{$vmid}->{node}; - my $conf; - my $volumes; - my $name = ""; + my $conf; + my $volumes; + my $name = ""; - if ($type eq 'qemu') { - $conf = PVE::QemuConfig->load_config($vmid, $node); - $volumes = PVE::QemuConfig->get_backup_volumes($conf); - $name = $conf->{name}; - } elsif ($type eq 'lxc') { - $conf = PVE::LXC::Config->load_config($vmid, $node); - $volumes = PVE::LXC::Config->get_backup_volumes($conf); - $name = $conf->{hostname}; - } else { - die "VMID $vmid is neither Qemu nor LXC guest\n"; - } + if ($type eq 'qemu') { + $conf = PVE::QemuConfig->load_config($vmid, $node); + $volumes = PVE::QemuConfig->get_backup_volumes($conf); + $name = $conf->{name}; + } elsif ($type eq 'lxc') { + $conf = PVE::LXC::Config->load_config($vmid, $node); + $volumes = PVE::LXC::Config->get_backup_volumes($conf); + $name = $conf->{hostname}; + } else { + die "VMID $vmid is neither Qemu nor LXC guest\n"; + } - foreach my $volume (@$volumes) { - 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}, - reason => $volume->{reason}, - leaf => 1, - }; - push(@{$children}, $disk); - } + foreach my $volume (@$volumes) { + 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}, + reason => $volume->{reason}, + leaf => 1, + }; + push(@{$children}, $disk); + } - my $leaf = 0; - # it's possible for a guest to have no volumes configured - $leaf = 1 if !@{$children}; + my $leaf = 0; + # it's possible for a guest to have no volumes configured + $leaf = 1 if !@{$children}; - push(@{$result->{children}}, { - id => int($vmid), - type => $type, - name => $name, - children => $children, - leaf => $leaf, - }); - } + push( + @{ $result->{children} }, + { + id => int($vmid), + type => $type, + name => $name, + children => $children, + leaf => $leaf, + }, + ); + } - return $result; - }}); + return $result; + }, +}); 1; diff --git a/PVE/API2/Capabilities.pm b/PVE/API2/Capabilities.pm index c88c6c46..a58e7b1a 100644 --- a/PVE/API2/Capabilities.pm +++ b/PVE/API2/Capabilities.pm @@ -10,79 +10,76 @@ use PVE::API2::Qemu::Machine; use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Qemu::CPU", path => 'qemu/cpu', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Qemu::Machine", path => 'qemu/machines', }); - -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', permissions => { user => 'all' }, description => "Node capabilities index.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $result = [ - { name => 'qemu' }, - ]; + my $result = [ + { name => 'qemu' }, + ]; - return $result; - } + return $result; + }, }); - -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'qemu_caps_index', path => 'qemu', method => 'GET', permissions => { user => 'all' }, description => "QEMU capabilities index.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $result = [ - { name => 'cpu' }, - { name => 'machines' }, - ]; + my $result = [ + { name => 'cpu' }, { name => 'machines' }, + ]; - return $result; - } + return $result; + }, }); 1; diff --git a/PVE/API2/Ceph.pm b/PVE/API2/Ceph.pm index 7fedb87a..e9fdcd37 100644 --- a/PVE/API2/Ceph.pm +++ b/PVE/API2/Ceph.pm @@ -29,92 +29,93 @@ 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, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $result = [ - { name => 'cmd-safety' }, - { name => 'cfg' }, - { name => 'crush' }, - { name => 'fs' }, - { name => 'init' }, - { name => 'log' }, - { name => 'mds' }, - { name => 'mgr' }, - { name => 'mon' }, - { name => 'osd' }, - { name => 'pool' }, - { name => 'restart' }, - { name => 'rules' }, - { name => 'start' }, - { name => 'status' }, - { name => 'stop' }, - ]; + my $result = [ + { name => 'cmd-safety' }, + { name => 'cfg' }, + { name => 'crush' }, + { name => 'fs' }, + { name => 'init' }, + { name => 'log' }, + { name => 'mds' }, + { name => 'mgr' }, + { name => 'mon' }, + { name => 'osd' }, + { name => 'pool' }, + { name => 'restart' }, + { name => 'rules' }, + { name => 'start' }, + { name => 'status' }, + { name => 'stop' }, + ]; - return $result; - }}); + return $result; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'init', path => 'init', method => 'POST', @@ -122,136 +123,143 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - network => { - description => "Use specific network for all ceph related traffic", - 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', - requires => 'network', - optional => 1, - maxLength => 128, - }, - size => { - description => 'Targeted number of replicas per object', - type => 'integer', - default => 3, - optional => 1, - minimum => 1, - maximum => 7, - }, - min_size => { - description => 'Minimum number of available replicas per object to allow I/O', - type => 'integer', - default => 2, - optional => 1, - minimum => 1, - maximum => 7, - }, - # 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.", - type => 'integer', - default => 6, - optional => 1, - minimum => 6, - 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!", - type => 'boolean', - optional => 1, - default => 0, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + network => { + description => "Use specific network for all ceph related traffic", + 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', + requires => 'network', + optional => 1, + maxLength => 128, + }, + size => { + description => 'Targeted number of replicas per object', + type => 'integer', + default => 3, + optional => 1, + minimum => 1, + maximum => 7, + }, + min_size => { + description => 'Minimum number of available replicas per object to allow I/O', + type => 'integer', + default => 2, + optional => 1, + minimum => 1, + maximum => 7, + }, + # 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.", + type => 'integer', + default => 6, + optional => 1, + minimum => 6, + 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!", + type => 'boolean', + optional => 1, + default => 0, + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $version = PVE::Ceph::Tools::get_local_version(1); + my $version = PVE::Ceph::Tools::get_local_version(1); - if (!$version || $version < 14) { - die "Ceph Nautilus required - please run 'pveceph install'\n"; - } else { - PVE::Ceph::Tools::check_ceph_installed('ceph_bin'); - } + if (!$version || $version < 14) { + die "Ceph Nautilus required - please run 'pveceph install'\n"; + } else { + PVE::Ceph::Tools::check_ceph_installed('ceph_bin'); + } - my $pve_ceph_cfgdir = PVE::Ceph::Tools::get_config('pve_ceph_cfgdir'); - if (! -d $pve_ceph_cfgdir) { - File::Path::make_path($pve_ceph_cfgdir); - } + my $pve_ceph_cfgdir = PVE::Ceph::Tools::get_config('pve_ceph_cfgdir'); + if (!-d $pve_ceph_cfgdir) { + File::Path::make_path($pve_ceph_cfgdir); + } - my $auth = $param->{disable_cephx} ? 'none' : 'cephx'; + my $auth = $param->{disable_cephx} ? 'none' : 'cephx'; - # simply load old config if it already exists - PVE::Cluster::cfs_lock_file('ceph.conf', undef, sub { - my $cfg = cfs_read_file('ceph.conf'); + # simply load old config if it already exists + PVE::Cluster::cfs_lock_file( + 'ceph.conf', + undef, + sub { + my $cfg = cfs_read_file('ceph.conf'); - if (!$cfg->{global}) { + if (!$cfg->{global}) { - my $fsid; - my $uuid; + my $fsid; + my $uuid; - UUID::generate($uuid); - UUID::unparse($uuid, $fsid); + UUID::generate($uuid); + UUID::unparse($uuid, $fsid); - $cfg->{global} = { - 'fsid' => $fsid, - 'auth_cluster_required' => $auth, - 'auth_service_required' => $auth, - 'auth_client_required' => $auth, - 'osd_pool_default_size' => $param->{size} // 3, - 'osd_pool_default_min_size' => $param->{min_size} // 2, - 'mon_allow_pool_delete' => 'true', - }; + $cfg->{global} = { + 'fsid' => $fsid, + 'auth_cluster_required' => $auth, + 'auth_service_required' => $auth, + 'auth_client_required' => $auth, + 'osd_pool_default_size' => $param->{size} // 3, + 'osd_pool_default_min_size' => $param->{min_size} // 2, + 'mon_allow_pool_delete' => 'true', + }; - # this does not work for default pools - #'osd pool default pg num' => $pg_num, - #'osd pool default pgp num' => $pg_num, - } + # this does not work for default pools + #'osd pool default pg num' => $pg_num, + #'osd pool default pgp num' => $pg_num, + } - if ($auth eq 'cephx') { - $cfg->{client}->{keyring} = '/etc/pve/priv/$cluster.$name.keyring'; - } + if ($auth eq 'cephx') { + $cfg->{client}->{keyring} = '/etc/pve/priv/$cluster.$name.keyring'; + } - if ($param->{network}) { - $cfg->{global}->{'public_network'} = $param->{network}; - $cfg->{global}->{'cluster_network'} = $param->{network}; - } + if ($param->{network}) { + $cfg->{global}->{'public_network'} = $param->{network}; + $cfg->{global}->{'cluster_network'} = $param->{network}; + } - if ($param->{'cluster-network'}) { - $cfg->{global}->{'cluster_network'} = $param->{'cluster-network'}; - } + if ($param->{'cluster-network'}) { + $cfg->{global}->{'cluster_network'} = $param->{'cluster-network'}; + } - cfs_write_file('ceph.conf', $cfg); + cfs_write_file('ceph.conf', $cfg); - if ($auth eq 'cephx') { - PVE::Ceph::Tools::get_or_create_admin_keyring(); - } - PVE::Ceph::Tools::setup_pve_symlinks(); - }); - die $@ if $@; + if ($auth eq 'cephx') { + PVE::Ceph::Tools::get_or_create_admin_keyring(); + } + PVE::Ceph::Tools::setup_pve_symlinks(); + }, + ); + die $@ if $@; - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'stop', path => 'stop', method => 'POST', @@ -259,50 +267,51 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - service => { - description => 'Ceph service name.', - type => 'string', - optional => 1, - default => 'ceph.target', - pattern => '(ceph|mon|mds|osd|mgr)(\.'.PVE::Ceph::Services::SERVICE_REGEX.')?', - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + service => { + description => 'Ceph service name.', + type => 'string', + optional => 1, + default => 'ceph.target', + pattern => '(ceph|mon|mds|osd|mgr)(\.' + . PVE::Ceph::Services::SERVICE_REGEX . ')?', + }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $cfg = cfs_read_file('ceph.conf'); - scalar(keys %$cfg) || die "no configuration\n"; + my $cfg = cfs_read_file('ceph.conf'); + scalar(keys %$cfg) || die "no configuration\n"; - my $worker = sub { - my $upid = shift; + my $worker = sub { + my $upid = shift; - my $cmd = ['stop']; - if ($param->{service}) { - push @$cmd, $param->{service}; - } + my $cmd = ['stop']; + if ($param->{service}) { + push @$cmd, $param->{service}; + } - PVE::Ceph::Services::ceph_service_cmd(@$cmd); - }; + 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,50 +319,52 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - service => { - description => 'Ceph service name.', - type => 'string', - optional => 1, - default => 'ceph.target', - pattern => '(ceph|mon|mds|osd|mgr)(\.'.PVE::Ceph::Services::SERVICE_REGEX.')?', - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + service => { + description => 'Ceph service name.', + type => 'string', + optional => 1, + default => 'ceph.target', + pattern => '(ceph|mon|mds|osd|mgr)(\.' + . PVE::Ceph::Services::SERVICE_REGEX . ')?', + }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $cfg = cfs_read_file('ceph.conf'); - scalar(keys %$cfg) || die "no configuration\n"; + my $cfg = cfs_read_file('ceph.conf'); + scalar(keys %$cfg) || die "no configuration\n"; - my $worker = sub { - my $upid = shift; + my $worker = sub { + my $upid = shift; - my $cmd = ['start']; - if ($param->{service}) { - push @$cmd, $param->{service}; - } + my $cmd = ['start']; + if ($param->{service}) { + push @$cmd, $param->{service}; + } - PVE::Ceph::Services::ceph_service_cmd(@$cmd); - }; + PVE::Ceph::Services::ceph_service_cmd(@$cmd); + }; - return $rpcenv->fork_worker('srvstart', $param->{service} || 'ceph', - $authuser, $worker); - }}); + return $rpcenv->fork_worker('srvstart', $param->{service} || 'ceph', + $authuser, $worker); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'restart', path => 'restart', method => 'POST', @@ -361,50 +372,51 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - service => { - description => 'Ceph service name.', - type => 'string', - optional => 1, - default => 'ceph.target', - pattern => '(mon|mds|osd|mgr)(\.'.PVE::Ceph::Services::SERVICE_REGEX.')?', - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + service => { + description => 'Ceph service name.', + type => 'string', + optional => 1, + default => 'ceph.target', + pattern => '(mon|mds|osd|mgr)(\.' . PVE::Ceph::Services::SERVICE_REGEX . ')?', + }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $cfg = cfs_read_file('ceph.conf'); - scalar(keys %$cfg) || die "no configuration\n"; + my $cfg = cfs_read_file('ceph.conf'); + scalar(keys %$cfg) || die "no configuration\n"; - my $worker = sub { - my $upid = shift; + my $worker = sub { + my $upid = shift; - my $cmd = ['restart']; - if ($param->{service}) { - push @$cmd, $param->{service}; - } + my $cmd = ['restart']; + if ($param->{service}) { + push @$cmd, $param->{service}; + } - PVE::Ceph::Services::ceph_service_cmd(@$cmd); - }; + PVE::Ceph::Services::ceph_service_cmd(@$cmd); + }; - return $rpcenv->fork_worker('srvrestart', $param->{service} || 'ceph', - $authuser, $worker); - }}); + return $rpcenv->fork_worker('srvrestart', $param->{service} || 'ceph', + $authuser, $worker); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'status', path => 'status', method => 'GET', @@ -412,25 +424,25 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1], + check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { type => 'object' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - return PVE::Ceph::Tools::ceph_cluster_status(); - }}); + return PVE::Ceph::Tools::ceph_cluster_status(); + }, +}); - -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'crush', path => 'crush', method => 'GET', @@ -438,45 +450,47 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1], + check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - # this produces JSON (difficult to read for the user) - # my $txt = &$run_ceph_cmd_text(['osd', 'crush', 'dump'], quiet => 1); + # this produces JSON (difficult to read for the user) + # my $txt = &$run_ceph_cmd_text(['osd', 'crush', 'dump'], quiet => 1); - my $txt = ''; + my $txt = ''; - my $mapfile = "/var/tmp/ceph-crush.map.$$"; - my $mapdata = "/var/tmp/ceph-crush.txt.$$"; + my $mapfile = "/var/tmp/ceph-crush.map.$$"; + my $mapdata = "/var/tmp/ceph-crush.txt.$$"; - my $rados = PVE::RADOS->new(); + my $rados = PVE::RADOS->new(); - eval { - 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); - }; - my $err = $@; + eval { + 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); + }; + my $err = $@; - unlink $mapfile; - unlink $mapdata; + unlink $mapfile; + unlink $mapdata; - die $err if $err; + die $err if $err; - return $txt; - }}); + return $txt; + }, +}); __PACKAGE__->register_method({ name => 'log', @@ -485,59 +499,61 @@ __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 => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - start => { - type => 'integer', - minimum => 0, - optional => 1, - }, - limit => { - type => 'integer', - minimum => 0, - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + start => { + type => 'integer', + minimum => 0, + optional => 1, + }, + limit => { + type => 'integer', + minimum => 0, + optional => 1, + }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - n => { - description=> "Line number", - type=> 'integer', - }, - t => { - description=> "Line text", - type => 'string', - } - } - } + type => 'array', + items => { + type => "object", + properties => { + n => { + description => "Line number", + type => 'integer', + }, + t => { + description => "Line text", + type => 'string', + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); - my $node = $param->{node}; + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); + my $node = $param->{node}; - my $logfile = "/var/log/ceph/ceph.log"; - my ($count, $lines) = PVE::Tools::dump_logfile($logfile, $param->{start}, $param->{limit}); + my $logfile = "/var/log/ceph/ceph.log"; + my ($count, $lines) = + PVE::Tools::dump_logfile($logfile, $param->{start}, $param->{limit}); - $rpcenv->set_result_attrib('total', $count); + $rpcenv->set_result_attrib('total', $count); - return $lines; - }}); + return $lines; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'rules', path => 'rules', method => 'GET', @@ -545,46 +561,47 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1], + check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - name => { - description => "Name of the CRUSH rule.", - type => "string", - } - }, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => { + name => { + description => "Name of the CRUSH rule.", + type => "string", + }, + }, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $rados = PVE::RADOS->new(); + my $rados = PVE::RADOS->new(); - my $rules = $rados->mon_command({ prefix => 'osd crush rule ls' }); + my $rules = $rados->mon_command({ prefix => 'osd crush rule ls' }); - my $res = []; + my $res = []; - foreach my $rule (@$rules) { - push @$res, { name => $rule }; - } + foreach my $rule (@$rules) { + push @$res, { name => $rule }; + } - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'cmd_safety', path => 'cmd-safety', method => 'GET', @@ -592,92 +609,93 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit' ]], + check => ['perm', '/', ['Sys.Audit']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - service => { - description => 'Service type', - type => 'string', - enum => ['osd', 'mon', 'mds'], - }, - id => { - description => 'ID of the service', - type => 'string', - }, - action => { - description => 'Action to check', - type => 'string', - enum => ['stop', 'destroy'], - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + service => { + description => 'Service type', + type => 'string', + enum => ['osd', 'mon', 'mds'], + }, + id => { + description => 'ID of the service', + type => 'string', + }, + action => { + description => 'Action to check', + type => 'string', + enum => ['stop', 'destroy'], + }, + }, }, returns => { - type => 'object', - properties => { - safe => { - type => 'boolean', - description => 'If it is safe to run the command.', - }, - status => { - type => 'string', - optional => 1, - description => 'Status message given by Ceph.' - }, - }, + type => 'object', + properties => { + safe => { + type => 'boolean', + description => 'If it is safe to run the command.', + }, + status => { + type => 'string', + optional => 1, + description => 'Status message given by Ceph.', + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $id = $param->{id}; - my $service = $param->{service}; - my $action = $param->{action}; + my $id = $param->{id}; + my $service = $param->{service}; + my $action = $param->{action}; - my $rados = PVE::RADOS->new(); + my $rados = PVE::RADOS->new(); - my $supported_actions = { - osd => { - stop => 'ok-to-stop', - destroy => 'safe-to-destroy', - }, - mon => { - stop => 'ok-to-stop', - destroy => 'ok-to-rm', - }, - mds => { - stop => 'ok-to-stop', - }, - }; + my $supported_actions = { + osd => { + stop => 'ok-to-stop', + destroy => 'safe-to-destroy', + }, + mon => { + stop => 'ok-to-stop', + destroy => 'ok-to-rm', + }, + mds => { + stop => 'ok-to-stop', + }, + }; - die "Service does not support this action: ${service}: ${action}\n" - if !$supported_actions->{$service}->{$action}; + die "Service does not support this action: ${service}: ${action}\n" + if !$supported_actions->{$service}->{$action}; - my $result = { - safe => 0, - status => '', - }; + my $result = { + safe => 0, + status => '', + }; - my $params = { - prefix => "${service} $supported_actions->{$service}->{$action}", - format => 'plain', - }; - if ($service eq 'mon' && $action eq 'destroy') { - $params->{id} = $id; - } else { - $params->{ids} = [ $id ]; - } + my $params = { + prefix => "${service} $supported_actions->{$service}->{$action}", + format => 'plain', + }; + if ($service eq 'mon' && $action eq 'destroy') { + $params->{id} = $id; + } else { + $params->{ids} = [$id]; + } - $result = $rados->mon_cmd($params, 1); - die $@ if $@; + $result = $rados->mon_cmd($params, 1); + die $@ if $@; - $result->{safe} = $result->{return_code} == 0 ? 1 : 0; - $result->{status} = $result->{status_message}; + $result->{safe} = $result->{return_code} == 0 ? 1 : 0; + $result->{status} = $result->{status_message}; - return $result; - }}); + return $result; + }, +}); 1; diff --git a/PVE/API2/Ceph/Cfg.pm b/PVE/API2/Ceph/Cfg.pm index f06c42f4..9d16caf2 100644 --- a/PVE/API2/Ceph/Cfg.pm +++ b/PVE/API2/Ceph/Cfg.pm @@ -11,184 +11,185 @@ use PVE::Tools qw(file_get_contents); use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', description => "Directory index.", permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $result = [ - { name => 'raw' }, - { name => 'db' }, - { name => 'value' }, - ]; + my $result = [ + { name => 'raw' }, { name => 'db' }, { name => 'value' }, + ]; - return $result; - }}); + return $result; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'raw', path => 'raw', method => 'GET', proxyto => 'node', permissions => { - check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1], + check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1], }, description => "Get the Ceph configuration file.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $path = PVE::Ceph::Tools::get_config('pve_ceph_cfgpath'); - return file_get_contents($path); + my $path = PVE::Ceph::Tools::get_config('pve_ceph_cfgpath'); + return file_get_contents($path); - }}); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'db', path => 'db', method => 'GET', proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1], + check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1], }, description => "Get the Ceph configuration database.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => 'object', - properties => { - section => { type => "string", }, - name => { type => "string", }, - value => { type => "string", }, - level => { type => "string", }, - 'can_update_at_runtime' => { type => "boolean", }, - mask => { type => "string" }, - }, - }, + type => 'array', + items => { + type => 'object', + properties => { + section => { type => "string" }, + name => { type => "string" }, + value => { type => "string" }, + level => { type => "string" }, + 'can_update_at_runtime' => { type => "boolean" }, + mask => { type => "string" }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $rados = PVE::RADOS->new(); - my $res = $rados->mon_command( { prefix => 'config dump', format => 'json' }); - foreach my $entry (@$res) { - $entry->{can_update_at_runtime} = $entry->{can_update_at_runtime}? 1 : 0; # JSON::true/false -> 1/0 - } - - return $res; - }}); + my $rados = PVE::RADOS->new(); + my $res = $rados->mon_command({ prefix => 'config dump', format => 'json' }); + foreach my $entry (@$res) { + $entry->{can_update_at_runtime} = $entry->{can_update_at_runtime} ? 1 : 0; # JSON::true/false -> 1/0 + } + return $res; + }, +}); my $SINGLE_CONFIGKEY_RE = qr/[0-9a-z\-_\.]+:[0-9a-zA-Z\-_]+/i; my $CONFIGKEYS_RE = qr/^(:?${SINGLE_CONFIGKEY_RE})(:?[;, ]${SINGLE_CONFIGKEY_RE})*$/; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'value', path => 'value', method => 'GET', proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit' ]], + check => ['perm', '/', ['Sys.Audit']], }, description => "Get configured values from either the config file or config DB.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - 'config-keys' => { - type => "string", - typetext => "
:[;
:]", - pattern => $CONFIGKEYS_RE, - description => "List of
: items.", - } - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + 'config-keys' => { + type => "string", + typetext => "
:[;
:]", + pattern => $CONFIGKEYS_RE, + description => "List of
: items.", + }, + }, }, returns => { - type => 'object', - description => "Contains {section}->{key} children with the values", + type => 'object', + description => "Contains {section}->{key} children with the values", }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - # Ceph treats '-' and '_' the same in parameter names, stick with '-' - my $normalize = sub { - my $t = shift; - $t =~ s/_/-/g; - return $t; - }; + # Ceph treats '-' and '_' the same in parameter names, stick with '-' + my $normalize = sub { + my $t = shift; + $t =~ s/_/-/g; + return $t; + }; - my $requested_keys = {}; - for my $pair (PVE::Tools::split_list($param->{'config-keys'})) { - my ($section, $key) = split(":", $pair); - $section = $normalize->($section); - $key = $normalize->($key); + my $requested_keys = {}; + for my $pair (PVE::Tools::split_list($param->{'config-keys'})) { + my ($section, $key) = split(":", $pair); + $section = $normalize->($section); + $key = $normalize->($key); - $requested_keys->{$section}->{$key} = 1; - } + $requested_keys->{$section}->{$key} = 1; + } - my $config = {}; + my $config = {}; - my $rados = PVE::RADOS->new(); - my $configdb = $rados->mon_command( { prefix => 'config dump', format => 'json' }); - for my $s (@{$configdb}) { - my ($section, $name, $value) = $s->@{'section', 'name', 'value'}; - my $n_section = $normalize->($section); - my $n_name = $normalize->($name); + my $rados = PVE::RADOS->new(); + my $configdb = $rados->mon_command({ prefix => 'config dump', format => 'json' }); + for my $s (@{$configdb}) { + my ($section, $name, $value) = $s->@{ 'section', 'name', 'value' }; + my $n_section = $normalize->($section); + my $n_name = $normalize->($name); - $config->{$n_section}->{$n_name} = $value - if defined $requested_keys->{$n_section} && $n_name eq $n_name; - } + $config->{$n_section}->{$n_name} = $value + if defined $requested_keys->{$n_section} && $n_name eq $n_name; + } - # read ceph.conf after config db as it has priority if settings are present in both - my $config_file = cfs_read_file('ceph.conf'); # cfs_read_file to get it parsed - for my $section (keys %{$config_file}) { - my $n_section = $normalize->($section); - next if !defined $requested_keys->{$n_section}; + # read ceph.conf after config db as it has priority if settings are present in both + my $config_file = cfs_read_file('ceph.conf'); # cfs_read_file to get it parsed + for my $section (keys %{$config_file}) { + my $n_section = $normalize->($section); + next if !defined $requested_keys->{$n_section}; - for my $key (keys %{$config_file->{$section}}) { - my $n_key = $normalize->($key); - $config->{$n_section}->{$n_key} = $config_file->{$section}->{$key} - if $requested_keys->{$n_section}->{$n_key}; - } - } + for my $key (keys %{ $config_file->{$section} }) { + my $n_key = $normalize->($key); + $config->{$n_section}->{$n_key} = $config_file->{$section}->{$key} + if $requested_keys->{$n_section}->{$n_key}; + } + } - return $config; - }}); + return $config; + }, +}); diff --git a/PVE/API2/Ceph/FS.pm b/PVE/API2/Ceph/FS.pm index 8e02ef5a..dd4d1d3f 100644 --- a/PVE/API2/Ceph/FS.pm +++ b/PVE/API2/Ceph/FS.pm @@ -15,65 +15,65 @@ use PVE::API2::Storage::Config; use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', proxyto => 'node', description => "Directory index.", permissions => { - check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1], + check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1], }, protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - name => { - description => "The ceph filesystem name.", - type => 'string', - }, - metadata_pool => { - description => "The name of the metadata pool.", - type => 'string', - }, - data_pool => { - description => "The name of the data pool.", - type => 'string', - }, - }, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => { + name => { + description => "The ceph filesystem name.", + type => 'string', + }, + metadata_pool => { + description => "The name of the metadata pool.", + type => 'string', + }, + data_pool => { + description => "The name of the data pool.", + type => 'string', + }, + }, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $rados = PVE::RADOS->new(); + my $rados = PVE::RADOS->new(); - my $cephfs_list = PVE::Ceph::Tools::ls_fs($rados); + my $cephfs_list = PVE::Ceph::Tools::ls_fs($rados); - my $res = [ - map {{ - name => $_->{name}, - metadata_pool => $_->{metadata_pool}, - data_pool => $_->{data_pools}->[0], - }} @$cephfs_list - ]; + my $res = [ + map { { + name => $_->{name}, + metadata_pool => $_->{metadata_pool}, + data_pool => $_->{data_pools}->[0], + } } @$cephfs_list + ]; - return $res; - } + return $res; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'createfs', path => '{name}', method => 'POST', @@ -81,141 +81,143 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - name => { - description => "The ceph filesystem name.", - type => 'string', - default => 'cephfs', - optional => 1, - pattern => qr|^[^:/\s]+$|, - }, - pg_num => { - description => "Number of placement groups for the backing data pool. The metadata pool will use a quarter of this.", - type => 'integer', - default => 128, - optional => 1, - minimum => 8, - maximum => 32768, - }, - 'add-storage' => { - description => "Configure the created CephFS as storage for this cluster.", - type => 'boolean', - optional => 1, - default => 0, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + name => { + description => "The ceph filesystem name.", + type => 'string', + default => 'cephfs', + optional => 1, + pattern => qr|^[^:/\s]+$|, + }, + pg_num => { + description => + "Number of placement groups for the backing data pool. The metadata pool will use a quarter of this.", + type => 'integer', + default => 128, + optional => 1, + minimum => 8, + maximum => 32768, + }, + 'add-storage' => { + description => "Configure the created CephFS as storage for this cluster.", + type => 'boolean', + optional => 1, + default => 0, + }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_configured(); + PVE::Ceph::Tools::check_ceph_configured(); - my $fs_name = $param->{name} // 'cephfs'; - my $pg_num = $param->{pg_num} // 128; + my $fs_name = $param->{name} // 'cephfs'; + my $pg_num = $param->{pg_num} // 128; - my $pool_data = "${fs_name}_data"; - my $pool_metadata = "${fs_name}_metadata"; + my $pool_data = "${fs_name}_data"; + my $pool_metadata = "${fs_name}_metadata"; - my $rados = PVE::RADOS->new(); - my $ls_pools = PVE::Ceph::Tools::ls_pools(); - my $existing_pools = { map { $_->{poolname} => 1 } @$ls_pools }; + my $rados = PVE::RADOS->new(); + my $ls_pools = PVE::Ceph::Tools::ls_pools(); + my $existing_pools = { map { $_->{poolname} => 1 } @$ls_pools }; - die "ceph pools '$pool_data' and/or '$pool_metadata' already exist\n" - if $existing_pools->{$pool_data} || $existing_pools->{$pool_metadata}; + die "ceph pools '$pool_data' and/or '$pool_metadata' already exist\n" + if $existing_pools->{$pool_data} || $existing_pools->{$pool_metadata}; - my $fs = PVE::Ceph::Tools::ls_fs($rados); - die "ceph fs '$fs_name' already exists\n" - if (grep { $_->{name} eq $fs_name } @$fs); + my $fs = PVE::Ceph::Tools::ls_fs($rados); + die "ceph fs '$fs_name' already exists\n" + if (grep { $_->{name} eq $fs_name } @$fs); - my $running_mds = PVE::Ceph::Services::get_cluster_mds_state($rados); - die "no running Metadata Server (MDS) found!\n" if !scalar(keys %$running_mds); - die "no standby Metadata Server (MDS) found!\n" - if !grep { $_->{state} eq 'up:standby' } values(%$running_mds); + my $running_mds = PVE::Ceph::Services::get_cluster_mds_state($rados); + die "no running Metadata Server (MDS) found!\n" if !scalar(keys %$running_mds); + die "no standby Metadata Server (MDS) found!\n" + if !grep { $_->{state} eq 'up:standby' } values(%$running_mds); - PVE::Storage::assert_sid_unused($fs_name) if $param->{add_storage}; + PVE::Storage::assert_sid_unused($fs_name) if $param->{add_storage}; - my $worker = sub { - $rados = PVE::RADOS->new(); + my $worker = sub { + $rados = PVE::RADOS->new(); - my $pool_param = { - application => 'cephfs', - pg_num => $pg_num, - }; + my $pool_param = { + application => 'cephfs', + pg_num => $pg_num, + }; - my @created_pools = (); - eval { - print "creating data pool '$pool_data'...\n"; - PVE::Ceph::Tools::create_pool($pool_data, $pool_param, $rados); - push @created_pools, $pool_data; + my @created_pools = (); + eval { + print "creating data pool '$pool_data'...\n"; + PVE::Ceph::Tools::create_pool($pool_data, $pool_param, $rados); + push @created_pools, $pool_data; - print "creating metadata pool '$pool_metadata'...\n"; - $pool_param->{pg_num} = $pg_num >= 32 ? $pg_num / 4 : 8; - PVE::Ceph::Tools::create_pool($pool_metadata, $pool_param, $rados); - push @created_pools, $pool_metadata; + print "creating metadata pool '$pool_metadata'...\n"; + $pool_param->{pg_num} = $pg_num >= 32 ? $pg_num / 4 : 8; + PVE::Ceph::Tools::create_pool($pool_metadata, $pool_param, $rados); + push @created_pools, $pool_metadata; - print "configuring new CephFS '$fs_name'\n"; - my $param = { - pool_metadata => $pool_metadata, - pool_data => $pool_data, - }; - PVE::Ceph::Tools::create_fs($fs_name, $param, $rados); - }; - if (my $err = $@) { - $@ = undef; + print "configuring new CephFS '$fs_name'\n"; + my $param = { + pool_metadata => $pool_metadata, + pool_data => $pool_data, + }; + PVE::Ceph::Tools::create_fs($fs_name, $param, $rados); + }; + if (my $err = $@) { + $@ = undef; - if (@created_pools > 0) { - warn "Encountered error after creating at least one pool\n"; - # our old connection is very likely broken now, recreate - $rados = PVE::RADOS->new(); - foreach my $pool (@created_pools) { - warn "cleaning up left over pool '$pool'\n"; - eval { PVE::Ceph::Tools::destroy_pool($pool, $rados) }; - warn "$@\n" if $@; - } - } + if (@created_pools > 0) { + warn "Encountered error after creating at least one pool\n"; + # our old connection is very likely broken now, recreate + $rados = PVE::RADOS->new(); + foreach my $pool (@created_pools) { + warn "cleaning up left over pool '$pool'\n"; + eval { PVE::Ceph::Tools::destroy_pool($pool, $rados) }; + warn "$@\n" if $@; + } + } - die "$err\n"; - } - print "Successfully create CephFS '$fs_name'\n"; + die "$err\n"; + } + print "Successfully create CephFS '$fs_name'\n"; - if ($param->{'add-storage'}) { - print "Adding '$fs_name' to storage configuration...\n"; + if ($param->{'add-storage'}) { + print "Adding '$fs_name' to storage configuration...\n"; - my $waittime = 0; - while (!PVE::Ceph::Services::is_mds_active($rados, $fs_name)) { - if ($waittime >= 10) { - die "Need MDS to add storage, but none got active!\n"; - } + my $waittime = 0; + while (!PVE::Ceph::Services::is_mds_active($rados, $fs_name)) { + if ($waittime >= 10) { + die "Need MDS to add storage, but none got active!\n"; + } - print "Waiting for an MDS to become active\n"; - sleep(1); - $waittime++; - } + print "Waiting for an MDS to become active\n"; + sleep(1); + $waittime++; + } - eval { - PVE::API2::Storage::Config->create({ - type => 'cephfs', - storage => $fs_name, - content => 'backup,iso,vztmpl', - 'fs-name' => $fs_name, - }) - }; - die "adding storage for CephFS '$fs_name' failed, check log ". - "and add manually!\n$@\n" if $@; - } - }; + eval { + PVE::API2::Storage::Config->create({ + type => 'cephfs', + storage => $fs_name, + content => 'backup,iso,vztmpl', + 'fs-name' => $fs_name, + }); + }; + die "adding storage for CephFS '$fs_name' failed, check log " + . "and add manually!\n$@\n" + if $@; + } + }; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); - return $rpcenv->fork_worker('cephfscreate', $fs_name, $user, $worker); - } + return $rpcenv->fork_worker('cephfscreate', $fs_name, $user, $worker); + }, }); 1; diff --git a/PVE/API2/Ceph/MDS.pm b/PVE/API2/Ceph/MDS.pm index d99eebe6..629e8a87 100644 --- a/PVE/API2/Ceph/MDS.pm +++ b/PVE/API2/Ceph/MDS.pm @@ -14,79 +14,80 @@ use PVE::RPCEnvironment; use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', description => "MDS directory index.", permissions => { - check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1], + check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1], }, proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - name => { - description => "The name (ID) for the MDS", - }, - addr => { - type => 'string', - optional => 1, - }, - host => { - type => 'string', - optional => 1, - }, - state => { - type => 'string', - description => 'State of the MDS', - }, - standby_replay => { - type => 'boolean', - optional => 1, - description => 'If true, the standby MDS is polling the active MDS for faster recovery (hot standby).', - }, - rank => { - type => 'integer', - optional => 1, - }, - }, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => { + name => { + description => "The name (ID) for the MDS", + }, + addr => { + type => 'string', + optional => 1, + }, + host => { + type => 'string', + optional => 1, + }, + state => { + type => 'string', + description => 'State of the MDS', + }, + standby_replay => { + type => 'boolean', + optional => 1, + description => + 'If true, the standby MDS is polling the active MDS for faster recovery (hot standby).', + }, + rank => { + type => 'integer', + optional => 1, + }, + }, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $res = []; + my $res = []; - my $cfg = cfs_read_file('ceph.conf'); - my $rados = PVE::RADOS->new(); + my $cfg = cfs_read_file('ceph.conf'); + my $rados = PVE::RADOS->new(); - my $mds_hash = PVE::Ceph::Services::get_services_info("mds", $cfg, $rados); + my $mds_hash = PVE::Ceph::Services::get_services_info("mds", $cfg, $rados); - my $mds_state = PVE::Ceph::Services::get_cluster_mds_state($rados); - foreach my $name (keys %$mds_state) { - my $d = $mds_state->{$name}; - # just overwrite, this always provides more info - $mds_hash->{$name}->{$_} = $d->{$_} for keys %$d; - } + my $mds_state = PVE::Ceph::Services::get_cluster_mds_state($rados); + foreach my $name (keys %$mds_state) { + my $d = $mds_state->{$name}; + # just overwrite, this always provides more info + $mds_hash->{$name}->{$_} = $d->{$_} for keys %$d; + } - return PVE::RESTHandler::hash_to_array($mds_hash, 'name'); - } + return PVE::RESTHandler::hash_to_array($mds_hash, 'name'); + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'createmds', path => '{name}', method => 'POST', @@ -94,94 +95,95 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - name => { - type => 'string', - optional => 1, - default => 'nodename', - pattern => PVE::Ceph::Services::SERVICE_REGEX, - maxLength => 200, - description => "The ID for the mds, when omitted the same as the nodename", - }, - hotstandby => { - type => 'boolean', - optional => 1, - default => '0', - description => "Determines whether a ceph-mds daemon should poll and replay the log of an active MDS. ". - "Faster switch on MDS failure, but needs more idle resources.", - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + name => { + type => 'string', + optional => 1, + default => 'nodename', + pattern => PVE::Ceph::Services::SERVICE_REGEX, + maxLength => 200, + description => "The ID for the mds, when omitted the same as the nodename", + }, + hotstandby => { + type => 'boolean', + optional => 1, + default => '0', + description => + "Determines whether a ceph-mds daemon should poll and replay the log of an active MDS. " + . "Faster switch on MDS failure, but needs more idle resources.", + }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_installed('ceph_mds'); + PVE::Ceph::Tools::check_ceph_installed('ceph_mds'); - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $nodename = $param->{node}; - $nodename = INotify::nodename() if $nodename eq 'localhost'; + my $nodename = $param->{node}; + $nodename = INotify::nodename() if $nodename eq 'localhost'; - my $mds_id = $param->{name} // $nodename; + my $mds_id = $param->{name} // $nodename; - die "ID of the MDS cannot start with a number!\n" if ($mds_id =~ /^[0-9]/); + die "ID of the MDS cannot start with a number!\n" if ($mds_id =~ /^[0-9]/); - my $worker = sub { - my $timeout = PVE::Ceph::Tools::get_config('long_rados_timeout'); - my $rados = PVE::RADOS->new(timeout => $timeout); + my $worker = sub { + my $timeout = PVE::Ceph::Tools::get_config('long_rados_timeout'); + my $rados = PVE::RADOS->new(timeout => $timeout); - my $cfg = cfs_read_file('ceph.conf'); + my $cfg = cfs_read_file('ceph.conf'); - my $section = "mds.$mds_id"; + my $section = "mds.$mds_id"; - if (defined($cfg->{$section})) { - die "MDS '$mds_id' already referenced in ceph config, abort!\n" - } + if (defined($cfg->{$section})) { + die "MDS '$mds_id' already referenced in ceph config, abort!\n"; + } - if (!defined($cfg->{mds}->{keyring})) { - # $id isn't a perl variable but a ceph metavariable - my $keyring = '/var/lib/ceph/mds/ceph-$id/keyring'; + if (!defined($cfg->{mds}->{keyring})) { + # $id isn't a perl variable but a ceph metavariable + my $keyring = '/var/lib/ceph/mds/ceph-$id/keyring'; - $cfg->{mds}->{keyring} = $keyring; - } + $cfg->{mds}->{keyring} = $keyring; + } - $cfg->{$section}->{host} = $nodename; - $cfg->{$section}->{'mds_standby_for_name'} = 'pve'; + $cfg->{$section}->{host} = $nodename; + $cfg->{$section}->{'mds_standby_for_name'} = 'pve'; - if ($param->{hotstandby}) { - $cfg->{$section}->{'mds_standby_replay'} = 'true'; - } + if ($param->{hotstandby}) { + $cfg->{$section}->{'mds_standby_replay'} = 'true'; + } - cfs_write_file('ceph.conf', $cfg); + cfs_write_file('ceph.conf', $cfg); - eval { PVE::Ceph::Services::create_mds($mds_id, $rados) }; - if (my $err = $@) { - # we abort early if the section is defined, so we know that we - # wrote it at this point. Do not auto remove the service, could - # do real harm for previously manual setup MDS - warn "Encountered error, remove '$section' from ceph.conf\n"; - my $cfg = cfs_read_file('ceph.conf'); - delete $cfg->{$section}; - cfs_write_file('ceph.conf', $cfg); + eval { PVE::Ceph::Services::create_mds($mds_id, $rados) }; + if (my $err = $@) { + # we abort early if the section is defined, so we know that we + # wrote it at this point. Do not auto remove the service, could + # do real harm for previously manual setup MDS + warn "Encountered error, remove '$section' from ceph.conf\n"; + my $cfg = cfs_read_file('ceph.conf'); + delete $cfg->{$section}; + cfs_write_file('ceph.conf', $cfg); - die "$err\n"; - } - }; + die "$err\n"; + } + }; - return $rpcenv->fork_worker('cephcreatemds', "mds.$mds_id", $authuser, $worker); - } + return $rpcenv->fork_worker('cephcreatemds', "mds.$mds_id", $authuser, $worker); + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'destroymds', path => '{name}', method => 'DELETE', @@ -189,47 +191,47 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - name => { - description => 'The name (ID) of the mds', - type => 'string', - pattern => PVE::Ceph::Services::SERVICE_REGEX, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + name => { + description => 'The name (ID) of the mds', + type => 'string', + pattern => PVE::Ceph::Services::SERVICE_REGEX, + }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $mds_id = $param->{name}; + my $mds_id = $param->{name}; - my $worker = sub { - my $timeout = PVE::Ceph::Tools::get_config('long_rados_timeout'); - my $rados = PVE::RADOS->new(timeout => $timeout); + my $worker = sub { + my $timeout = PVE::Ceph::Tools::get_config('long_rados_timeout'); + my $rados = PVE::RADOS->new(timeout => $timeout); - my $cfg = cfs_read_file('ceph.conf'); + my $cfg = cfs_read_file('ceph.conf'); - if (defined($cfg->{"mds.$mds_id"})) { - delete $cfg->{"mds.$mds_id"}; - cfs_write_file('ceph.conf', $cfg); - } + if (defined($cfg->{"mds.$mds_id"})) { + delete $cfg->{"mds.$mds_id"}; + cfs_write_file('ceph.conf', $cfg); + } - PVE::Ceph::Services::destroy_mds($mds_id, $rados); - }; + PVE::Ceph::Services::destroy_mds($mds_id, $rados); + }; - return $rpcenv->fork_worker('cephdestroymds', "mds.$mds_id", $authuser, $worker); - } + return $rpcenv->fork_worker('cephdestroymds', "mds.$mds_id", $authuser, $worker); + }, }); 1; diff --git a/PVE/API2/Ceph/MGR.pm b/PVE/API2/Ceph/MGR.pm index 2dc679ef..1371c0cc 100644 --- a/PVE/API2/Ceph/MGR.pm +++ b/PVE/API2/Ceph/MGR.pm @@ -15,72 +15,72 @@ use PVE::Tools qw(run_command); use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', description => "MGR directory index.", permissions => { - check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1], + check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1], }, proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - name => { - description => "The name (ID) for the MGR", - }, - addr => { - type => 'string', - optional => 1, - }, - host => { - type => 'string', - optional => 1, - }, - state => { - type => 'string', - description => 'State of the MGR', - }, - }, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => { + name => { + description => "The name (ID) for the MGR", + }, + addr => { + type => 'string', + optional => 1, + }, + host => { + type => 'string', + optional => 1, + }, + state => { + type => 'string', + description => 'State of the MGR', + }, + }, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $res = []; + my $res = []; - my $cfg = cfs_read_file('ceph.conf'); - my $rados = PVE::RADOS->new(); + my $cfg = cfs_read_file('ceph.conf'); + my $rados = PVE::RADOS->new(); - my $mgr_hash = PVE::Ceph::Services::get_services_info("mgr", $cfg, $rados); + my $mgr_hash = PVE::Ceph::Services::get_services_info("mgr", $cfg, $rados); - my $mgr_dump = $rados->mon_command({ prefix => 'mgr dump' }); + my $mgr_dump = $rados->mon_command({ prefix => 'mgr dump' }); - my $active_name = $mgr_dump->{active_name}; - $mgr_hash->{$active_name}->{state} = 'active' if $active_name; + my $active_name = $mgr_dump->{active_name}; + $mgr_hash->{$active_name}->{state} = 'active' if $active_name; - foreach my $mgr (@{$mgr_dump->{standbys}}) { - $mgr_hash->{$mgr->{name}}->{state} = 'standby'; - } + foreach my $mgr (@{ $mgr_dump->{standbys} }) { + $mgr_hash->{ $mgr->{name} }->{state} = 'standby'; + } - return PVE::RESTHandler::hash_to_array($mgr_hash, 'name'); - } + return PVE::RESTHandler::hash_to_array($mgr_hash, 'name'); + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'createmgr', path => '{id}', method => 'POST', @@ -88,47 +88,48 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - id => { - type => 'string', - optional => 1, - pattern => PVE::Ceph::Services::SERVICE_REGEX, - maxLength => 200, - description => "The ID for the manager, when omitted the same as the nodename", - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + id => { + type => 'string', + optional => 1, + pattern => PVE::Ceph::Services::SERVICE_REGEX, + maxLength => 200, + description => "The ID for the manager, when omitted the same as the nodename", + }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_installed('ceph_mgr'); - PVE::Ceph::Tools::check_ceph_inited(); - PVE::Ceph::Tools::setup_pve_symlinks(); + PVE::Ceph::Tools::check_ceph_installed('ceph_mgr'); + PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::setup_pve_symlinks(); - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $mgrid = $param->{id} // $param->{node}; + my $mgrid = $param->{id} // $param->{node}; - my $worker = sub { - my $upid = shift; + my $worker = sub { + my $upid = shift; - my $rados_timeout = PVE::Ceph::Tools::get_config('long_rados_timeout'); - my $rados = PVE::RADOS->new(timeout => $rados_timeout); + my $rados_timeout = PVE::Ceph::Tools::get_config('long_rados_timeout'); + my $rados = PVE::RADOS->new(timeout => $rados_timeout); - PVE::Ceph::Services::create_mgr($mgrid, $rados); - }; + PVE::Ceph::Services::create_mgr($mgrid, $rados); + }; - return $rpcenv->fork_worker('cephcreatemgr', "mgr.$mgrid", $authuser, $worker); - }}); + return $rpcenv->fork_worker('cephcreatemgr', "mgr.$mgrid", $authuser, $worker); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'destroymgr', path => '{id}', method => 'DELETE', @@ -136,36 +137,37 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - id => { - description => 'The ID of the manager', - type => 'string', - pattern => PVE::Ceph::Services::SERVICE_REGEX, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + id => { + description => 'The ID of the manager', + type => 'string', + pattern => PVE::Ceph::Services::SERVICE_REGEX, + }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $mgrid = $param->{id}; + my $mgrid = $param->{id}; - my $worker = sub { - my $upid = shift; + my $worker = sub { + my $upid = shift; - PVE::Ceph::Services::destroy_mgr($mgrid); - }; + PVE::Ceph::Services::destroy_mgr($mgrid); + }; - return $rpcenv->fork_worker('cephdestroymgr', "mgr.$mgrid", $authuser, $worker); - }}); + return $rpcenv->fork_worker('cephdestroymgr', "mgr.$mgrid", $authuser, $worker); + }, +}); diff --git a/PVE/API2/Ceph/MON.pm b/PVE/API2/Ceph/MON.pm index 60c8d8dd..70fc158d 100644 --- a/PVE/API2/Ceph/MON.pm +++ b/PVE/API2/Ceph/MON.pm @@ -23,67 +23,73 @@ use base qw(PVE::RESTHandler); my $find_mon_ips = sub { my ($cfg, $rados, $node, $mon_address) = @_; - my $overwrite_ips = [ PVE::Tools::split_list($mon_address) ]; + my $overwrite_ips = [PVE::Tools::split_list($mon_address)]; $overwrite_ips = PVE::Network::unique_ips($overwrite_ips); my $pubnet; if ($rados) { - $pubnet = $rados->mon_command({ prefix => "config get" , who => "mon.", - key => "public_network", format => 'plain' }); - # if not defined in the db, the result is empty, it is also always - # followed by a newline - ($pubnet) = $pubnet =~ m/^(\S+)$/; + $pubnet = $rados->mon_command({ + prefix => "config get", + who => "mon.", + key => "public_network", + format => 'plain', + }); + # if not defined in the db, the result is empty, it is also always + # followed by a newline + ($pubnet) = $pubnet =~ m/^(\S+)$/; } $pubnet //= $cfg->{global}->{public_network}; if (!$pubnet) { - if (scalar(@{$overwrite_ips})) { - return $overwrite_ips; - } else { - # don't refactor into '[ PVE::Cluster::remote... ]' as it uses wantarray - my $ip = PVE::Cluster::remote_node_ip($node); - return [ $ip ]; - } + if (scalar(@{$overwrite_ips})) { + return $overwrite_ips; + } else { + # don't refactor into '[ PVE::Cluster::remote... ]' as it uses wantarray + my $ip = PVE::Cluster::remote_node_ip($node); + return [$ip]; + } } - my $public_nets = [ PVE::Tools::split_list($pubnet) ]; + my $public_nets = [PVE::Tools::split_list($pubnet)]; if (scalar(@{$public_nets}) > 1) { - warn "Multiple Ceph public networks detected on $node: $pubnet\n"; - warn "Networks must be capable of routing to each other.\n"; + warn "Multiple Ceph public networks detected on $node: $pubnet\n"; + warn "Networks must be capable of routing to each other.\n"; } my $res = []; if (!scalar(@{$overwrite_ips})) { # auto-select one address for each public network - for my $net (@{$public_nets}) { - my $allowed_ips = PVE::Network::get_local_ip_from_cidr($net); - $allowed_ips = PVE::Network::unique_ips($allowed_ips); + for my $net (@{$public_nets}) { + my $allowed_ips = PVE::Network::get_local_ip_from_cidr($net); + $allowed_ips = PVE::Network::unique_ips($allowed_ips); - die "No active IP found for the requested ceph public network '$net' on node '$node'\n" - if scalar(@$allowed_ips) < 1; + die + "No active IP found for the requested ceph public network '$net' on node '$node'\n" + if scalar(@$allowed_ips) < 1; - if (scalar(@$allowed_ips) == 1) { - push @{$res}, $allowed_ips->[0]; - } else { - die "Multiple IPs for ceph public network '$net' detected on $node:\n". - join("\n", @$allowed_ips) ."\nuse 'mon-address' to specify one of them.\n"; - } - } + if (scalar(@$allowed_ips) == 1) { + push @{$res}, $allowed_ips->[0]; + } else { + die "Multiple IPs for ceph public network '$net' detected on $node:\n" + . join("\n", @$allowed_ips) + . "\nuse 'mon-address' to specify one of them.\n"; + } + } } else { # check if overwrite IPs are active and in any of the public networks - my $allowed_list = []; + my $allowed_list = []; - for my $net (@{$public_nets}) { - push @{$allowed_list}, @{PVE::Network::get_local_ip_from_cidr($net)}; - } + for my $net (@{$public_nets}) { + push @{$allowed_list}, @{ PVE::Network::get_local_ip_from_cidr($net) }; + } - my $allowed_ips = PVE::Network::unique_ips($allowed_list); + my $allowed_ips = PVE::Network::unique_ips($allowed_list); - for my $overwrite_ip (@{$overwrite_ips}) { - die "Specified monitor IP '$overwrite_ip' not configured or up on $node!\n" - if !grep { $_ eq $overwrite_ip } @{$allowed_ips}; + for my $overwrite_ip (@{$overwrite_ips}) { + die "Specified monitor IP '$overwrite_ip' not configured or up on $node!\n" + if !grep { $_ eq $overwrite_ip } @{$allowed_ips}; - push @{$res}, $overwrite_ip; - } + push @{$res}, $overwrite_ip; + } } return $res; @@ -97,17 +103,17 @@ my $ips_from_mon_host = sub { my @hosts = PVE::Tools::split_list($mon_host); for my $host (@hosts) { - $host =~ s|^\[?v\d+\:||; # remove beginning of vector - $host =~ s|/\d+\]?||; # remove end of vector + $host =~ s|^\[?v\d+\:||; # remove beginning of vector + $host =~ s|/\d+\]?||; # remove end of vector - ($host) = PVE::Tools::parse_host_and_port($host); - next if !defined($host); + ($host) = PVE::Tools::parse_host_and_port($host); + next if !defined($host); - # filter out hostnames - my $ip = PVE::JSONSchema::pve_verify_ip($host, 1); - next if !defined($ip); + # filter out hostnames + my $ip = PVE::JSONSchema::pve_verify_ip($host, 1); + next if !defined($ip); - push @{$ips}, $ip; + push @{$ips}, $ip; } return $ips; @@ -121,26 +127,26 @@ my $assert_mon_prerequisites = sub { my $mon_host_ips = $ips_from_mon_host->($cfg->{global}->{mon_host}); for my $mon_host_ip (@{$mon_host_ips}) { - my $ip = PVE::Network::canonical_ip($mon_host_ip); - $used_ips->{$ip} = 1; + my $ip = PVE::Network::canonical_ip($mon_host_ip); + $used_ips->{$ip} = 1; } for my $mon (values %{$monhash}) { - next if !defined($mon->{addr}); + next if !defined($mon->{addr}); - for my $ip ($ips_from_mon_host->($mon->{addr})->@*) { - $ip = PVE::Network::canonical_ip($ip); - $used_ips->{$ip} = 1; - } + for my $ip ($ips_from_mon_host->($mon->{addr})->@*) { + $ip = PVE::Network::canonical_ip($ip); + $used_ips->{$ip} = 1; + } } for my $monip (@{$monips}) { - $monip = PVE::Network::canonical_ip($monip); - die "monitor address '$monip' already in use\n" if $used_ips->{$monip}; + $monip = PVE::Network::canonical_ip($monip); + die "monitor address '$monip' already in use\n" if $used_ips->{$monip}; } if (defined($monhash->{$monid})) { - die "monitor '$monid' already exists\n"; + die "monitor '$monid' already exists\n"; } }; @@ -148,13 +154,15 @@ my $assert_mon_can_remove = sub { my ($monhash, $monlist, $monid, $mondir) = @_; if ( - !defined($monhash->{$monid} || - grep { defined($_->{name}) && $_->{name} eq $monid } $monlist->@*) + !defined( + $monhash->{$monid} + || grep { defined($_->{name}) && $_->{name} eq $monid } $monlist->@* + ) ) { - die "no such monitor id '$monid'\n" + die "no such monitor id '$monid'\n"; } - die "monitor filesystem '$mondir' does not exist on this node\n" if ! -d $mondir; + die "monitor filesystem '$mondir' does not exist on this node\n" if !-d $mondir; die "can't remove last monitor\n" if scalar($monlist->@*) <= 1; }; @@ -178,8 +186,8 @@ my $remove_addr_from_mon_host = sub { # ipv6 only without brackets if ($addr =~ m/^\[?(.*?:.*?)\]?$/) { - $addr = $1; - $monhost =~ s/(^|[ ,;]+)\Q$addr\E(?:[ ,;]+|$)/$1/; + $addr = $1; + $monhost =~ s/(^|[ ,;]+)\Q$addr\E(?:[ ,;]+|$)/$1/; } # remove trailing separators @@ -188,7 +196,7 @@ my $remove_addr_from_mon_host = sub { return $monhost; }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'listmon', path => '', method => 'GET', @@ -196,73 +204,74 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1], + check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - addr => { type => 'string', optional => 1 }, - ceph_version => { type => 'string', optional => 1 }, - ceph_version_short => { type => 'string', optional => 1 }, - direxists => { type => 'string', optional => 1 }, - host => { type => 'boolean', optional => 1 }, - name => { type => 'string' }, - quorum => { type => 'boolean', optional => 1 }, - rank => { type => 'integer', optional => 1 }, - service => { type => 'integer', optional => 1 }, - state => { type => 'string', optional => 1 }, - }, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => { + addr => { type => 'string', optional => 1 }, + ceph_version => { type => 'string', optional => 1 }, + ceph_version_short => { type => 'string', optional => 1 }, + direxists => { type => 'string', optional => 1 }, + host => { type => 'boolean', optional => 1 }, + name => { type => 'string' }, + quorum => { type => 'boolean', optional => 1 }, + rank => { type => 'integer', optional => 1 }, + service => { type => 'integer', optional => 1 }, + state => { type => 'string', optional => 1 }, + }, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $res = []; + my $res = []; - my $cfg = cfs_read_file('ceph.conf'); + my $cfg = cfs_read_file('ceph.conf'); - my $rados = eval { PVE::RADOS->new() }; - warn $@ if $@; - my $monhash = PVE::Ceph::Services::get_services_info("mon", $cfg, $rados); + my $rados = eval { PVE::RADOS->new() }; + warn $@ if $@; + my $monhash = PVE::Ceph::Services::get_services_info("mon", $cfg, $rados); - if ($rados) { - my $monstat = $rados->mon_command({ prefix => 'quorum_status' }); + if ($rados) { + my $monstat = $rados->mon_command({ prefix => 'quorum_status' }); - my $mons = $monstat->{monmap}->{mons}; - foreach my $d (@$mons) { - next if !defined($d->{name}); - my $name = $d->{name}; - $monhash->{$name}->{rank} = $d->{rank}; - $monhash->{$name}->{addr} = $d->{addr}; - if (grep { $_ eq $d->{rank} } @{$monstat->{quorum}}) { - $monhash->{$name}->{quorum} = 1; - $monhash->{$name}->{state} = 'running'; - } - } + my $mons = $monstat->{monmap}->{mons}; + foreach my $d (@$mons) { + next if !defined($d->{name}); + my $name = $d->{name}; + $monhash->{$name}->{rank} = $d->{rank}; + $monhash->{$name}->{addr} = $d->{addr}; + if (grep { $_ eq $d->{rank} } @{ $monstat->{quorum} }) { + $monhash->{$name}->{quorum} = 1; + $monhash->{$name}->{state} = 'running'; + } + } - } else { - # we cannot check the status if we do not have a RADOS - # object, so set the state to unknown - foreach my $monid (sort keys %$monhash) { - $monhash->{$monid}->{state} = 'unknown'; - } - } + } else { + # we cannot check the status if we do not have a RADOS + # object, so set the state to unknown + foreach my $monid (sort keys %$monhash) { + $monhash->{$monid}->{state} = 'unknown'; + } + } - return PVE::RESTHandler::hash_to_array($monhash, 'name'); - }}); + return PVE::RESTHandler::hash_to_array($monhash, 'name'); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'createmon', path => '{monid}', method => 'POST', @@ -270,216 +279,230 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - monid => { - type => 'string', - optional => 1, - pattern => PVE::Ceph::Services::SERVICE_REGEX, - maxLength => 200, - description => "The ID for the monitor, when omitted the same as the nodename", - }, - 'mon-address' => { - description => 'Overwrites autodetected monitor IP address(es). ' . - 'Must be in the public network(s) of Ceph.', - type => 'string', format => 'ip-list', - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + monid => { + type => 'string', + optional => 1, + pattern => PVE::Ceph::Services::SERVICE_REGEX, + maxLength => 200, + description => "The ID for the monitor, when omitted the same as the nodename", + }, + 'mon-address' => { + description => 'Overwrites autodetected monitor IP address(es). ' + . 'Must be in the public network(s) of Ceph.', + type => 'string', + format => 'ip-list', + optional => 1, + }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_installed('ceph_mon'); - PVE::Ceph::Tools::check_ceph_inited(); - PVE::Ceph::Tools::setup_pve_symlinks(); + PVE::Ceph::Tools::check_ceph_installed('ceph_mon'); + PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::setup_pve_symlinks(); - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $cfg = cfs_read_file('ceph.conf'); - my $rados = eval { PVE::RADOS->new() }; # try a rados connection, fails for first monitor - my $monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados); + my $cfg = cfs_read_file('ceph.conf'); + my $rados = eval { PVE::RADOS->new() }; # try a rados connection, fails for first monitor + my $monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados); - my $is_first_monitor = !(scalar(keys %$monhash) || $cfg->{global}->{mon_host}); + my $is_first_monitor = !(scalar(keys %$monhash) || $cfg->{global}->{mon_host}); - if (!defined($rados) && !$is_first_monitor) { - die "Could not connect to ceph cluster despite configured monitors\n"; - } + if (!defined($rados) && !$is_first_monitor) { + die "Could not connect to ceph cluster despite configured monitors\n"; + } - my $monid = $param->{monid} // $param->{node}; - my $monsection = "mon.$monid"; - my $ips = $find_mon_ips->($cfg, $rados, $param->{node}, $param->{'mon-address'}); + my $monid = $param->{monid} // $param->{node}; + my $monsection = "mon.$monid"; + my $ips = $find_mon_ips->($cfg, $rados, $param->{node}, $param->{'mon-address'}); - $assert_mon_prerequisites->($cfg, $monhash, $monid, $ips); + $assert_mon_prerequisites->($cfg, $monhash, $monid, $ips); - my $worker = sub { - my $upid = shift; + my $worker = sub { + my $upid = shift; - PVE::Cluster::cfs_lock_file('ceph.conf', undef, sub { - # update cfg content and reassert prereqs inside the lock - $cfg = cfs_read_file('ceph.conf'); - # reopen with longer timeout - if (defined($rados)) { - $rados = PVE::RADOS->new(timeout => PVE::Ceph::Tools::get_config('long_rados_timeout')); - } - $monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados); - $assert_mon_prerequisites->($cfg, $monhash, $monid, $ips); + PVE::Cluster::cfs_lock_file( + 'ceph.conf', + undef, + sub { + # update cfg content and reassert prereqs inside the lock + $cfg = cfs_read_file('ceph.conf'); + # reopen with longer timeout + if (defined($rados)) { + $rados = PVE::RADOS->new( + timeout => PVE::Ceph::Tools::get_config('long_rados_timeout')); + } + $monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados); + $assert_mon_prerequisites->($cfg, $monhash, $monid, $ips); - my $client_keyring = PVE::Ceph::Tools::get_or_create_admin_keyring(); - my $mon_keyring = PVE::Ceph::Tools::get_config('pve_mon_key_path'); + my $client_keyring = PVE::Ceph::Tools::get_or_create_admin_keyring(); + my $mon_keyring = PVE::Ceph::Tools::get_config('pve_mon_key_path'); - if (! -f $mon_keyring) { - print "creating new monitor keyring\n"; - run_command([ - 'ceph-authtool', - '--create-keyring', - $mon_keyring, - '--gen-key', - '-n', - 'mon.', - '--cap', - 'mon', - 'allow *', - ]); - run_command([ - 'ceph-authtool', - $mon_keyring, - '--import-keyring', - $client_keyring, - ]); - } + if (!-f $mon_keyring) { + print "creating new monitor keyring\n"; + run_command([ + 'ceph-authtool', + '--create-keyring', + $mon_keyring, + '--gen-key', + '-n', + 'mon.', + '--cap', + 'mon', + 'allow *', + ]); + run_command([ + 'ceph-authtool', $mon_keyring, '--import-keyring', $client_keyring, + ]); + } - my $ccname = PVE::Ceph::Tools::get_config('ccname'); - my $mondir = "/var/lib/ceph/mon/$ccname-$monid"; - -d $mondir && die "monitor filesystem '$mondir' already exist\n"; + my $ccname = PVE::Ceph::Tools::get_config('ccname'); + my $mondir = "/var/lib/ceph/mon/$ccname-$monid"; + -d $mondir && die "monitor filesystem '$mondir' already exist\n"; - my $monmap = "/tmp/monmap"; + my $monmap = "/tmp/monmap"; - eval { - mkdir $mondir; + eval { + mkdir $mondir; - run_command(['chown', 'ceph:ceph', $mondir]); + run_command(['chown', 'ceph:ceph', $mondir]); - my $is_first_address = !defined($rados); + my $is_first_address = !defined($rados); - my $monaddrs = []; + my $monaddrs = []; - for my $ip (@{$ips}) { - if (Net::IP::ip_is_ipv6($ip)) { - $cfg->{global}->{ms_bind_ipv6} = 'true'; - $cfg->{global}->{ms_bind_ipv4} = 'false' if $is_first_address; - } else { - $cfg->{global}->{ms_bind_ipv4} = 'true'; - $cfg->{global}->{ms_bind_ipv6} = 'false' if $is_first_address; - } + for my $ip (@{$ips}) { + if (Net::IP::ip_is_ipv6($ip)) { + $cfg->{global}->{ms_bind_ipv6} = 'true'; + $cfg->{global}->{ms_bind_ipv4} = 'false' if $is_first_address; + } else { + $cfg->{global}->{ms_bind_ipv4} = 'true'; + $cfg->{global}->{ms_bind_ipv6} = 'false' if $is_first_address; + } - my $monaddr = Net::IP::ip_is_ipv6($ip) ? "[$ip]" : $ip; - push @{$monaddrs}, "v2:$monaddr:3300"; - push @{$monaddrs}, "v1:$monaddr:6789"; + my $monaddr = Net::IP::ip_is_ipv6($ip) ? "[$ip]" : $ip; + push @{$monaddrs}, "v2:$monaddr:3300"; + push @{$monaddrs}, "v1:$monaddr:6789"; - $is_first_address = 0; - } + $is_first_address = 0; + } - my $monmaptool_cmd = [ - 'monmaptool', - '--clobber', - '--addv', - $monid, - "[" . join(',', @{$monaddrs}) . "]", - '--print', - $monmap, - ]; + my $monmaptool_cmd = [ + 'monmaptool', + '--clobber', + '--addv', + $monid, + "[" . join(',', @{$monaddrs}) . "]", + '--print', + $monmap, + ]; - if (defined($rados)) { # we can only have a RADOS object if we have a monitor - my $mapdata = $rados->mon_command({ prefix => 'mon getmap', format => 'plain' }); - file_set_contents($monmap, $mapdata); - run_command($monmaptool_cmd); - } else { # we need to create a monmap for the first monitor - push @{$monmaptool_cmd}, '--create'; - run_command($monmaptool_cmd); - } + if (defined($rados)) { # we can only have a RADOS object if we have a monitor + my $mapdata = + $rados->mon_command({ prefix => 'mon getmap', format => 'plain' }); + file_set_contents($monmap, $mapdata); + run_command($monmaptool_cmd); + } else { # we need to create a monmap for the first monitor + push @{$monmaptool_cmd}, '--create'; + run_command($monmaptool_cmd); + } - run_command([ - 'ceph-mon', - '--mkfs', - '-i', - $monid, - '--monmap', - $monmap, - '--keyring', - $mon_keyring, - ]); - run_command(['chown', 'ceph:ceph', '-R', $mondir]); - }; - my $err = $@; - unlink $monmap; - if ($err) { - File::Path::remove_tree($mondir); - die $err; - } + run_command([ + 'ceph-mon', + '--mkfs', + '-i', + $monid, + '--monmap', + $monmap, + '--keyring', + $mon_keyring, + ]); + run_command(['chown', 'ceph:ceph', '-R', $mondir]); + }; + my $err = $@; + unlink $monmap; + if ($err) { + File::Path::remove_tree($mondir); + die $err; + } - # update ceph.conf - my $monhost = $cfg->{global}->{mon_host} // ""; - # add all known monitor ips to mon_host if it does not exist - if (!defined($cfg->{global}->{mon_host})) { - for my $mon (sort keys %$monhash) { - $monhost .= " " . $monhash->{$mon}->{addr}; - } - } - $monhost .= " " . join(' ', @{$ips}); - $cfg->{global}->{mon_host} = $monhost; - # The IP is needed in the ceph.conf for the first boot - $cfg->{$monsection}->{public_addr} = $ips->[0]; + # update ceph.conf + my $monhost = $cfg->{global}->{mon_host} // ""; + # add all known monitor ips to mon_host if it does not exist + if (!defined($cfg->{global}->{mon_host})) { + for my $mon (sort keys %$monhash) { + $monhost .= " " . $monhash->{$mon}->{addr}; + } + } + $monhost .= " " . join(' ', @{$ips}); + $cfg->{global}->{mon_host} = $monhost; + # The IP is needed in the ceph.conf for the first boot + $cfg->{$monsection}->{public_addr} = $ips->[0]; - cfs_write_file('ceph.conf', $cfg); + cfs_write_file('ceph.conf', $cfg); - PVE::Ceph::Services::ceph_service_cmd('start', $monsection); + PVE::Ceph::Services::ceph_service_cmd('start', $monsection); - if ($is_first_monitor) { - print "created the first monitor, assume it's safe to disable insecure global" - ." ID reclaim for new setup\n"; - eval { - run_command( - ['ceph', 'config', 'set', 'mon', 'auth_allow_insecure_global_id_reclaim', 'false'], - errfunc => sub { print STDERR "$_[0]\n" }, - ) - }; - warn "$@" if $@; + if ($is_first_monitor) { + print + "created the first monitor, assume it's safe to disable insecure global" + . " ID reclaim for new setup\n"; + eval { + run_command( + [ + 'ceph', + 'config', + 'set', + 'mon', + 'auth_allow_insecure_global_id_reclaim', + 'false', + ], + errfunc => sub { print STDERR "$_[0]\n" }, + ); + }; + warn "$@" if $@; - print "Configuring keyring for ceph-crash.service\n"; - eval { - PVE::Ceph::Tools::create_or_update_crash_keyring_file(); - $cfg->{'client.crash'}->{keyring} = '/etc/pve/ceph/$cluster.$name.keyring'; - cfs_write_file('ceph.conf', $cfg); - }; - warn "Unable to configure keyring for ceph-crash.service: $@" if $@; - } + print "Configuring keyring for ceph-crash.service\n"; + eval { + PVE::Ceph::Tools::create_or_update_crash_keyring_file(); + $cfg->{'client.crash'}->{keyring} = + '/etc/pve/ceph/$cluster.$name.keyring'; + cfs_write_file('ceph.conf', $cfg); + }; + warn "Unable to configure keyring for ceph-crash.service: $@" if $@; + } - eval { PVE::Ceph::Services::ceph_service_cmd('enable', $monsection) }; - warn "Enable ceph-mon\@${monid}.service failed, do manually: $@\n" if $@; + eval { PVE::Ceph::Services::ceph_service_cmd('enable', $monsection) }; + warn "Enable ceph-mon\@${monid}.service failed, do manually: $@\n" if $@; - PVE::Ceph::Services::broadcast_ceph_services(); - }); - die $@ if $@; - # automatically create manager after the first monitor is created - if ($is_first_monitor) { - PVE::API2::Ceph::MGR->createmgr({ - node => $param->{node}, - id => $param->{node} - }) - } - }; + PVE::Ceph::Services::broadcast_ceph_services(); + }, + ); + die $@ if $@; + # automatically create manager after the first monitor is created + if ($is_first_monitor) { + PVE::API2::Ceph::MGR->createmgr({ + node => $param->{node}, + id => $param->{node}, + }); + } + }; - return $rpcenv->fork_worker('cephcreatemon', $monsection, $authuser, $worker); - }}); + return $rpcenv->fork_worker('cephcreatemon', $monsection, $authuser, $worker); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'destroymon', path => '{monid}', method => 'DELETE', @@ -487,122 +510,130 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - monid => { - description => 'Monitor ID', - type => 'string', - pattern => PVE::Ceph::Services::SERVICE_REGEX, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + monid => { + description => 'Monitor ID', + type => 'string', + pattern => PVE::Ceph::Services::SERVICE_REGEX, + }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $cfg = cfs_read_file('ceph.conf'); + my $cfg = cfs_read_file('ceph.conf'); - my $monid = $param->{monid}; - my $monsection = "mon.$monid"; + my $monid = $param->{monid}; + my $monsection = "mon.$monid"; - my $rados = PVE::RADOS->new(); - my $monstat = $rados->mon_command({ prefix => 'quorum_status' }); - my $monlist = $monstat->{monmap}->{mons}; - my $monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados); + my $rados = PVE::RADOS->new(); + my $monstat = $rados->mon_command({ prefix => 'quorum_status' }); + my $monlist = $monstat->{monmap}->{mons}; + my $monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados); - my $ccname = PVE::Ceph::Tools::get_config('ccname'); - my $mondir = "/var/lib/ceph/mon/$ccname-$monid"; + my $ccname = PVE::Ceph::Tools::get_config('ccname'); + my $mondir = "/var/lib/ceph/mon/$ccname-$monid"; - $assert_mon_can_remove->($monhash, $monlist, $monid, $mondir); + $assert_mon_can_remove->($monhash, $monlist, $monid, $mondir); - my $worker = sub { - my $upid = shift; - PVE::Cluster::cfs_lock_file('ceph.conf', undef, sub { - # reload info and recheck - $cfg = cfs_read_file('ceph.conf'); + my $worker = sub { + my $upid = shift; + PVE::Cluster::cfs_lock_file( + 'ceph.conf', + undef, + sub { + # reload info and recheck + $cfg = cfs_read_file('ceph.conf'); - # reopen with longer timeout - $rados = PVE::RADOS->new(timeout => PVE::Ceph::Tools::get_config('long_rados_timeout')); - $monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados); - $monstat = $rados->mon_command({ prefix => 'quorum_status' }); - $monlist = $monstat->{monmap}->{mons}; + # reopen with longer timeout + $rados = PVE::RADOS->new( + timeout => PVE::Ceph::Tools::get_config('long_rados_timeout')); + $monhash = PVE::Ceph::Services::get_services_info('mon', $cfg, $rados); + $monstat = $rados->mon_command({ prefix => 'quorum_status' }); + $monlist = $monstat->{monmap}->{mons}; - my $addrs = []; + my $addrs = []; - my $add_addr = sub { - my ($addr) = @_; + my $add_addr = sub { + my ($addr) = @_; - # extract the ip without port and nonce (if present) - ($addr) = $addr =~ m|^(.*):\d+(/\d+)?$|; - ($addr) = $addr =~ m|^\[?(.*?)\]?$|; # remove brackets - push @{$addrs}, $addr; - }; + # extract the ip without port and nonce (if present) + ($addr) = $addr =~ m|^(.*):\d+(/\d+)?$|; + ($addr) = $addr =~ m|^\[?(.*?)\]?$|; # remove brackets + push @{$addrs}, $addr; + }; - for my $mon (@$monlist) { - if ($mon->{name} eq $monid) { - if ($mon->{public_addrs} && $mon->{public_addrs}->{addrvec}) { - my $addrvec = $mon->{public_addrs}->{addrvec}; - for my $addr (@{$addrvec}) { - $add_addr->($addr->{addr}); - } - } else { - $add_addr->($mon->{public_addr} // $mon->{addr}); - } - last; - } - } + for my $mon (@$monlist) { + if ($mon->{name} eq $monid) { + if ($mon->{public_addrs} && $mon->{public_addrs}->{addrvec}) { + my $addrvec = $mon->{public_addrs}->{addrvec}; + for my $addr (@{$addrvec}) { + $add_addr->($addr->{addr}); + } + } else { + $add_addr->($mon->{public_addr} // $mon->{addr}); + } + last; + } + } - $assert_mon_can_remove->($monhash, $monlist, $monid, $mondir); + $assert_mon_can_remove->($monhash, $monlist, $monid, $mondir); - # this also stops the service - $rados->mon_command({ prefix => "mon remove", name => $monid, format => 'plain' }); + # this also stops the service + $rados->mon_command( + { prefix => "mon remove", name => $monid, format => 'plain' }); - # delete section - delete $cfg->{$monsection}; + # delete section + delete $cfg->{$monsection}; - # delete from mon_host - if (my $monhost = $cfg->{global}->{mon_host}) { - my $mon_host_ips = $ips_from_mon_host->($cfg->{global}->{mon_host}); + # delete from mon_host + if (my $monhost = $cfg->{global}->{mon_host}) { + my $mon_host_ips = $ips_from_mon_host->($cfg->{global}->{mon_host}); - for my $addr (@{$addrs}) { - $monhost = $remove_addr_from_mon_host->($monhost, $addr); + for my $addr (@{$addrs}) { + $monhost = $remove_addr_from_mon_host->($monhost, $addr); - # also remove matching IPs that differ syntactically - if (PVE::JSONSchema::pve_verify_ip($addr, 1)) { - $addr = PVE::Network::canonical_ip($addr); + # also remove matching IPs that differ syntactically + if (PVE::JSONSchema::pve_verify_ip($addr, 1)) { + $addr = PVE::Network::canonical_ip($addr); - for my $mon_host_ip (@{$mon_host_ips}) { - # match canonical addresses, but remove as present in mon_host - if (PVE::Network::canonical_ip($mon_host_ip) eq $addr) { - $monhost = $remove_addr_from_mon_host->($monhost, $mon_host_ip); - } - } - } - } - $cfg->{global}->{mon_host} = $monhost; - } + for my $mon_host_ip (@{$mon_host_ips}) { + # match canonical addresses, but remove as present in mon_host + if (PVE::Network::canonical_ip($mon_host_ip) eq $addr) { + $monhost = + $remove_addr_from_mon_host->($monhost, $mon_host_ip); + } + } + } + } + $cfg->{global}->{mon_host} = $monhost; + } - cfs_write_file('ceph.conf', $cfg); - File::Path::remove_tree($mondir); - eval { PVE::Ceph::Services::ceph_service_cmd('disable', $monsection) }; - warn $@ if $@; - PVE::Ceph::Services::broadcast_ceph_services(); - }); + cfs_write_file('ceph.conf', $cfg); + File::Path::remove_tree($mondir); + eval { PVE::Ceph::Services::ceph_service_cmd('disable', $monsection) }; + warn $@ if $@; + PVE::Ceph::Services::broadcast_ceph_services(); + }, + ); - die $@ if $@; - }; + die $@ if $@; + }; - return $rpcenv->fork_worker('cephdestroymon', $monsection, $authuser, $worker); - }}); + return $rpcenv->fork_worker('cephdestroymon', $monsection, $authuser, $worker); + }, +}); 1; diff --git a/PVE/API2/Ceph/OSD.pm b/PVE/API2/Ceph/OSD.pm index 5e39eed7..23e187ce 100644 --- a/PVE/API2/Ceph/OSD.pm +++ b/PVE/API2/Ceph/OSD.pm @@ -39,11 +39,11 @@ 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}; - return $osdstat->{$osdid}; + die "no such OSD '$osdid'\n" if !$osdstat->{$osdid}; + return $osdstat->{$osdid}; } return wantarray ? ($osdstat, $flags) : $osdstat; @@ -52,19 +52,19 @@ 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 []; + warn "got unknown result format for 'pg dump osds' command\n"; + return []; } if (ref($osdlist) eq "HASH") { # since nautilus - $osdlist = $osdlist->{osd_stats}; + $osdlist = $osdlist->{osd_stats}; } 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,21 +74,20 @@ my sub get_proc_pss_from_pid { my ($pid) = @_; return if !defined($pid) || $pid <= 1; - open (my $SMAPS_FH, '<', "/proc/$pid/smaps_rollup") - or die "failed to open PSS memory-stat from process - $!\n"; + open(my $SMAPS_FH, '<', "/proc/$pid/smaps_rollup") + or die "failed to open PSS memory-stat from process - $!\n"; while (my $line = <$SMAPS_FH>) { - if ($line =~ m/^Pss:\s+([0-9]+) kB$/) { # using PSS avoids bias with many OSDs - close $SMAPS_FH; - return int($1) * 1024; - } + if ($line =~ m/^Pss:\s+([0-9]+) kB$/) { # using PSS avoids bias with many OSDs + close $SMAPS_FH; + return int($1) * 1024; + } } close $SMAPS_FH; 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,142 +95,143 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1], + check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, # fixme: return a list instead of extjs tree format ? returns => { - type => "object", - items => { - type => "object", - properties => { - flags => { type => "string" }, - root => { - type => "object", - description => "Tree with OSDs in the CRUSH map structure.", - }, - }, - }, + type => "object", + items => { + type => "object", + properties => { + flags => { type => "string" }, + root => { + type => "object", + description => "Tree with OSDs in the CRUSH map structure.", + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $rados = PVE::RADOS->new(); - my $res = $rados->mon_command({ prefix => 'osd df', output_method => 'tree', }); + my $rados = PVE::RADOS->new(); + my $res = $rados->mon_command({ prefix => 'osd df', output_method => 'tree' }); die "no tree nodes found\n" if !($res && $res->{nodes}); - my ($osdhash, $flags) = $get_osd_status->($rados); + my ($osdhash, $flags) = $get_osd_status->($rados); - my $osd_usage = $get_osd_usage->($rados); + my $osd_usage = $get_osd_usage->($rados); - my $osdmetadata_res = $rados->mon_command({ prefix => 'osd metadata' }); - my $osdmetadata = { map { $_->{id} => $_ } @$osdmetadata_res }; + my $osdmetadata_res = $rados->mon_command({ prefix => 'osd metadata' }); + my $osdmetadata = { map { $_->{id} => $_ } @$osdmetadata_res }; - my $hostversions = PVE::Ceph::Services::get_ceph_versions(); + my $hostversions = PVE::Ceph::Services::get_ceph_versions(); - my $nodes = {}; - my $newnodes = {}; - foreach my $e (@{$res->{nodes}}) { - my ($id, $name) = $e->@{qw(id name)}; + my $nodes = {}; + my $newnodes = {}; + foreach my $e (@{ $res->{nodes} }) { + my ($id, $name) = $e->@{qw(id name)}; - $nodes->{$id} = $e; + $nodes->{$id} = $e; - my $new = { - id => $id, - name => $name, - type => $e->{type} - }; + my $new = { + id => $id, + name => $name, + type => $e->{type}, + }; - foreach my $opt (qw(status crush_weight reweight device_class pgs)) { - $new->{$opt} = $e->{$opt} if defined($e->{$opt}); - } + foreach my $opt (qw(status crush_weight reweight device_class pgs)) { + $new->{$opt} = $e->{$opt} if defined($e->{$opt}); + } - if (my $stat = $osdhash->{$id}) { - $new->{in} = $stat->{in} if defined($stat->{in}); - } + if (my $stat = $osdhash->{$id}) { + $new->{in} = $stat->{in} if defined($stat->{in}); + } - 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}; - if (my $d = $stat->{perf_stat}) { - $new->{commit_latency_ms} = $d->{commit_latency_ms}; - $new->{apply_latency_ms} = $d->{apply_latency_ms}; - } - } + 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}; + if (my $d = $stat->{perf_stat}) { + $new->{commit_latency_ms} = $d->{commit_latency_ms}; + $new->{apply_latency_ms} = $d->{apply_latency_ms}; + } + } - my $osdmd = $osdmetadata->{$id}; - if ($e->{type} eq 'osd' && $osdmd) { - if ($osdmd->{bluefs}) { - $new->{osdtype} = 'bluestore'; - $new->{blfsdev} = $osdmd->{bluestore_bdev_dev_node}; - $new->{dbdev} = $osdmd->{bluefs_db_dev_node}; - $new->{waldev} = $osdmd->{bluefs_wal_dev_node}; - } else { - $new->{osdtype} = 'filestore'; - } - for my $field (qw(ceph_version ceph_version_short)) { - $new->{$field} = $osdmd->{$field} if $osdmd->{$field}; - } - } + my $osdmd = $osdmetadata->{$id}; + if ($e->{type} eq 'osd' && $osdmd) { + if ($osdmd->{bluefs}) { + $new->{osdtype} = 'bluestore'; + $new->{blfsdev} = $osdmd->{bluestore_bdev_dev_node}; + $new->{dbdev} = $osdmd->{bluefs_db_dev_node}; + $new->{waldev} = $osdmd->{bluefs_wal_dev_node}; + } else { + $new->{osdtype} = 'filestore'; + } + for my $field (qw(ceph_version ceph_version_short)) { + $new->{$field} = $osdmd->{$field} if $osdmd->{$field}; + } + } - $newnodes->{$id} = $new; - } + $newnodes->{$id} = $new; + } - foreach my $e (@{$res->{nodes}}) { - my ($id, $name) = $e->@{qw(id name)}; - my $new = $newnodes->{$id}; + foreach my $e (@{ $res->{nodes} }) { + my ($id, $name) = $e->@{qw(id name)}; + my $new = $newnodes->{$id}; - if ($e->{children} && scalar(@{$e->{children}})) { - $new->{children} = []; - $new->{leaf} = 0; - 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}; - } - } else { - $new->{leaf} = ($id >= 0) ? 1 : 0; - } + if ($e->{children} && scalar(@{ $e->{children} })) { + $new->{children} = []; + $new->{leaf} = 0; + 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}; + } + } else { + $new->{leaf} = ($id >= 0) ? 1 : 0; + } - if ($name && $e->{type} eq 'host') { - $new->{version} = $hostversions->{$name}->{version}->{str}; - } - } + if ($name && $e->{type} eq 'host') { + $new->{version} = $hostversions->{$name}->{version}->{str}; + } + } - my $realroots = []; - foreach my $e (@{$res->{nodes}}) { - my $id = $e->{id}; - if (!$nodes->{$id}->{parent}) { - push @$realroots, $newnodes->{$id}; - } - } + my $realroots = []; + foreach my $e (@{ $res->{nodes} }) { + my $id = $e->{id}; + if (!$nodes->{$id}->{parent}) { + push @$realroots, $newnodes->{$id}; + } + } - die "no root node\n" if scalar(@$realroots) < 1; + die "no root node\n" if scalar(@$realroots) < 1; - my $data = { - root => { - leaf => 0, - children => $realroots - }, - }; + my $data = { + root => { + leaf => 0, + children => $realroots, + }, + }; - $data->{flags} = $flags if $flags; # we want this for the noout flag + $data->{flags} = $flags if $flags; # we want this for the noout flag - return $data; - }}); + return $data; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'createosd', path => '', method => 'POST', @@ -239,399 +239,410 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - dev => { - description => "Block device name.", - type => 'string', - }, - db_dev => { - description => "Block device name for block.db.", - optional => 1, - type => 'string', - }, - 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.", - optional => 1, - type => 'number', - default => 'bluestore_block_db_size or 10% of OSD size', - requires => 'db_dev', - minimum => 1.0, - }, - wal_dev => { - description => "Block device name for block.wal.", - optional => 1, - type => 'string', - }, - 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.", - optional => 1, - minimum => 0.5, - default => 'bluestore_block_wal_size or 1% of OSD size', - requires => 'wal_dev', - type => 'number', - }, - encrypted => { - type => 'boolean', - optional => 1, - default => 0, - description => "Enables encryption of the OSD." - }, - 'crush-device-class' => { - optional => 1, - type => 'string', - 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" + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + dev => { + description => "Block device name.", + type => 'string', + }, + db_dev => { + description => "Block device name for block.db.", + optional => 1, + type => 'string', + }, + 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.", + optional => 1, + type => 'number', + default => 'bluestore_block_db_size or 10% of OSD size', + requires => 'db_dev', + minimum => 1.0, + }, + wal_dev => { + description => "Block device name for block.wal.", + optional => 1, + type => 'string', + }, + 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.", + optional => 1, + minimum => 0.5, + default => 'bluestore_block_wal_size or 1% of OSD size', + requires => 'wal_dev', + type => 'number', + }, + encrypted => { + type => 'boolean', + optional => 1, + default => 0, + description => "Enables encryption of the OSD.", + }, + 'crush-device-class' => { + optional => 1, + type => 'string', + 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" ." to utilize their performance better.', - }, - }, + }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); - # test basic requirements - PVE::Ceph::Tools::check_ceph_inited(); - PVE::Ceph::Tools::setup_pve_symlinks(); - PVE::Ceph::Tools::check_ceph_installed('ceph_osd'); - PVE::Ceph::Tools::check_ceph_installed('ceph_volume'); + # test basic requirements + PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::setup_pve_symlinks(); + PVE::Ceph::Tools::check_ceph_installed('ceph_osd'); + PVE::Ceph::Tools::check_ceph_installed('ceph_volume'); - # extract parameter info and fail if a device is set more than once - my $devs = {}; + # extract parameter info and fail if a device is set more than once + my $devs = {}; - # 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}'" }) - if $param->{$type}; - } - } + # 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}'" }) + if $param->{$type}; + } + } - my $ceph_conf = cfs_read_file('ceph.conf'); + my $ceph_conf = cfs_read_file('ceph.conf'); - my $osd_network = $ceph_conf->{global}->{cluster_network}; - $osd_network //= $ceph_conf->{global}->{public_network}; # fallback + my $osd_network = $ceph_conf->{global}->{cluster_network}; + $osd_network //= $ceph_conf->{global}->{public_network}; # fallback - if ($osd_network) { # check only if something is configured - my $cluster_net_ips = PVE::Network::get_local_ip_from_cidr($osd_network); - if (scalar(@$cluster_net_ips) < 1) { - my $osd_net_obj = PVE::Network::IP_from_cidr($osd_network); - my $osd_base_cidr = $osd_net_obj->{ip} . "/" . $osd_net_obj->{prefixlen}; + if ($osd_network) { # check only if something is configured + my $cluster_net_ips = PVE::Network::get_local_ip_from_cidr($osd_network); + if (scalar(@$cluster_net_ips) < 1) { + 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) ) { - next if !$param->{$type}; + for my $type (qw(dev db_dev wal_dev)) { + next if !$param->{$type}; - my $type_dev = PVE::Diskmanage::verify_blockdev_path($param->{$type}); - (my $type_devname = $type_dev) =~ s|/dev/||; + my $type_dev = PVE::Diskmanage::verify_blockdev_path($param->{$type}); + (my $type_devname = $type_dev) =~ s|/dev/||; - raise_param_exc({ $type => "cannot chose '$type_dev' for more than one type." }) - if grep { $_->{name} eq $type_devname } values %$devs; + raise_param_exc({ $type => "cannot chose '$type_dev' for more than one type." }) + if grep { $_->{name} eq $type_devname } values %$devs; - $devs->{$type} = { - dev => $type_dev, - name => $type_devname, - }; + $devs->{$type} = { + dev => $type_dev, + name => $type_devname, + }; - if (my $size = $param->{"${type}_size"}) { - $devs->{$type}->{size} = PVE::Tools::convert_size($size, 'gb' => 'b') ; - } - } + if (my $size = $param->{"${type}_size"}) { + $devs->{$type}->{size} = PVE::Tools::convert_size($size, 'gb' => 'b'); + } + } - my $test_disk_requirements = sub { - my ($disklist) = @_; + my $test_disk_requirements = sub { + my ($disklist) = @_; - my $dev = $devs->{dev}->{dev}; - my $devname = $devs->{dev}->{name}; - die "unable to get device info for '$dev'\n" if !$disklist->{$devname}; - die "device '$dev' is already in use\n" if $disklist->{$devname}->{used}; + my $dev = $devs->{dev}->{dev}; + my $devname = $devs->{dev}->{name}; + 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) ) { - 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}; - if (my $usage = $info->{used}) { - if ($usage eq 'partitions') { - die "device '$d->{dev}' is not GPT partitioned\n" if !$info->{gpt}; - } elsif ($usage ne 'LVM') { - die "device '$d->{dev}' is already in use and has no LVM on it\n"; - } - } - } - }; + 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}; + if (my $usage = $info->{used}) { + if ($usage eq 'partitions') { + die "device '$d->{dev}' is not GPT partitioned\n" if !$info->{gpt}; + } elsif ($usage ne 'LVM') { + die "device '$d->{dev}' is already in use and has no LVM on it\n"; + } + } + } + }; + # test disk requirements early + my $devlist = [map { $_->{name} } values %$devs]; + my $disklist = PVE::Diskmanage::get_disks($devlist, 1, 1); + $test_disk_requirements->($disklist); - # test disk requirements early - my $devlist = [ map { $_->{name} } values %$devs ]; - my $disklist = PVE::Diskmanage::get_disks($devlist, 1, 1); - $test_disk_requirements->($disklist); + # get necessary ceph infos + my $rados = PVE::RADOS->new(); + my $monstat = $rados->mon_command({ prefix => 'quorum_status' }); - # get necessary ceph infos - 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' + ) { + my $bindata = $rados->mon_command({ + prefix => 'auth get-or-create', + entity => 'client.bootstrap-osd', + caps => [ + 'mon' => 'allow profile bootstrap-osd', + ], + format => 'plain', + }); + file_set_contents($ceph_bootstrap_osd_keyring, $bindata); + } - 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' - ], - format => 'plain', - }); - file_set_contents($ceph_bootstrap_osd_keyring, $bindata); - }; + # See FIXME below + my @udev_trigger_devs = (); - # See FIXME below - my @udev_trigger_devs = (); + my $create_part_or_lv = sub { + my ($dev, $size, $type) = @_; - my $create_part_or_lv = sub { - my ($dev, $size, $type) = @_; + $size =~ m/^(\d+)$/ or die "invalid size '$size'\n"; + $size = $1; - $size =~ m/^(\d+)$/ or die "invalid size '$size'\n"; - $size = $1; + die "'$dev->{devpath}' is smaller than requested size '$size' bytes\n" + if $dev->{size} < $size; - die "'$dev->{devpath}' is smaller than requested size '$size' bytes\n" - if $dev->{size} < $size; + # sgdisk and lvcreate can only sizes divisible by 512b + # so we round down to the nearest kb + $size = PVE::Tools::convert_size($size, 'b' => 'kb', 1); - # sgdisk and lvcreate can only sizes divisible by 512b - # so we round down to the nearest kb - $size = PVE::Tools::convert_size($size, 'b' => 'kb', 1); + if (!$dev->{used}) { + # create pv,vg,lv - if (!$dev->{used}) { - # create pv,vg,lv + my $vg = "ceph-" . UUID::uuid(); + my $lv = $type . "-" . UUID::uuid(); - my $vg = "ceph-" . UUID::uuid(); - my $lv = $type . "-" . UUID::uuid(); + PVE::Storage::LVMPlugin::lvm_create_volume_group($dev->{devpath}, $vg); + PVE::Storage::LVMPlugin::lvcreate($vg, $lv, "${size}k"); - PVE::Storage::LVMPlugin::lvm_create_volume_group($dev->{devpath}, $vg); - PVE::Storage::LVMPlugin::lvcreate($vg, $lv, "${size}k"); + if (PVE::Diskmanage::is_partition($dev->{devpath})) { + eval { PVE::Diskmanage::change_parttype($dev->{devpath}, '8E00'); }; + warn $@ if $@; + } - if (PVE::Diskmanage::is_partition($dev->{devpath})) { - eval { PVE::Diskmanage::change_parttype($dev->{devpath}, '8E00'); }; - warn $@ if $@; - } + push @udev_trigger_devs, $dev->{devpath}; - push @udev_trigger_devs, $dev->{devpath}; + return "$vg/$lv"; - return "$vg/$lv"; + } elsif ($dev->{used} eq 'LVM') { + # check pv/vg and create lv - } elsif ($dev->{used} eq 'LVM') { - # check pv/vg and create lv + my $vgs = PVE::Storage::LVMPlugin::lvm_vgs(1); + my $vg; + for my $vgname (sort keys %$vgs) { + next if $vgname !~ /^ceph-/; - my $vgs = PVE::Storage::LVMPlugin::lvm_vgs(1); - my $vg; - for my $vgname ( sort keys %$vgs ) { - next if $vgname !~ /^ceph-/; + for my $pv (@{ $vgs->{$vgname}->{pvs} }) { + next if $pv->{name} ne $dev->{devpath}; + $vg = $vgname; + last; + } + last if $vg; + } - for my $pv ( @{$vgs->{$vgname}->{pvs}} ) { - next if $pv->{name} ne $dev->{devpath}; - $vg = $vgname; - last; - } - last if $vg; - } + die "no ceph vg found on '$dev->{devpath}'\n" if !$vg; + die "vg '$vg' has not enough free space\n" if $vgs->{$vg}->{free} < $size; - die "no ceph vg found on '$dev->{devpath}'\n" if !$vg; - die "vg '$vg' has not enough free space\n" if $vgs->{$vg}->{free} < $size; + my $lv = $type . "-" . UUID::uuid(); - my $lv = $type . "-" . UUID::uuid(); + PVE::Storage::LVMPlugin::lvcreate($vg, $lv, "${size}k"); - PVE::Storage::LVMPlugin::lvcreate($vg, $lv, "${size}k"); + return "$vg/$lv"; - return "$vg/$lv"; + } elsif ($dev->{used} eq 'partitions' && $dev->{gpt}) { + # create new partition at the end + my $parttypes = { + 'osd-db' => '30CD0809-C2B2-499C-8879-2D6B78529876', + 'osd-wal' => '5CE17FCE-4087-4169-B7FF-056CC58473F9', + }; - } elsif ($dev->{used} eq 'partitions' && $dev->{gpt}) { - # create new partition at the end - my $parttypes = { - 'osd-db' => '30CD0809-C2B2-499C-8879-2D6B78529876', - 'osd-wal' => '5CE17FCE-4087-4169-B7FF-056CC58473F9', - }; + my $part = PVE::Diskmanage::append_partition($dev->{devpath}, $size * 1024); - my $part = PVE::Diskmanage::append_partition($dev->{devpath}, $size * 1024); + if (my $parttype = $parttypes->{$type}) { + eval { PVE::Diskmanage::change_parttype($part, $parttype); }; + warn $@ if $@; + } - if (my $parttype = $parttypes->{$type}) { - eval { PVE::Diskmanage::change_parttype($part, $parttype); }; - warn $@ if $@; - } + push @udev_trigger_devs, $part; + return $part; + } - push @udev_trigger_devs, $part; - return $part; - } + die "cannot use '$dev->{devpath}' for '$type'\n"; + }; - die "cannot use '$dev->{devpath}' for '$type'\n"; - }; + my $worker = sub { + my $upid = shift; - my $worker = sub { - my $upid = shift; + PVE::Diskmanage::locked_disk_action(sub { + # update disklist and re-test requirements + $disklist = PVE::Diskmanage::get_disks($devlist, 1, 1); + $test_disk_requirements->($disklist); - PVE::Diskmanage::locked_disk_action(sub { - # update disklist and re-test requirements - $disklist = PVE::Diskmanage::get_disks($devlist, 1, 1); - $test_disk_requirements->($disklist); + my $dev_class = $param->{'crush-device-class'}; + # 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]; + push @$cmd, '--crush-device-class', $dev_class if $dev_class; - my $dev_class = $param->{'crush-device-class'}; - # 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 ]; - push @$cmd, '--crush-device-class', $dev_class if $dev_class; + my $devname = $devs->{dev}->{name}; + my $devpath = $disklist->{$devname}->{devpath}; + print "create OSD on $devpath (bluestore)\n"; - my $devname = $devs->{dev}->{name}; - my $devpath = $disklist->{$devname}->{devpath}; - print "create OSD on $devpath (bluestore)\n"; + push @udev_trigger_devs, $devpath; - push @udev_trigger_devs, $devpath; + my $osd_size = $disklist->{$devname}->{size}; + my $size_map = { + db => int($osd_size / 10), # 10% of OSD + wal => int($osd_size / 100), # 1% of OSD + }; - my $osd_size = $disklist->{$devname}->{size}; - my $size_map = { - db => int($osd_size / 10), # 10% of OSD - wal => int($osd_size / 100), # 1% of OSD - }; + my $sizes; + foreach my $type (qw(db wal)) { + my $fallback_size = $size_map->{$type}; + my $d = $devs->{"${type}_dev"}; + next if !$d; - my $sizes; - foreach my $type ( qw(db wal) ) { - my $fallback_size = $size_map->{$type}; - my $d = $devs->{"${type}_dev"}; - next if !$d; + # size was not set via api, getting from config/fallback + if (!defined($d->{size})) { + $sizes = PVE::Ceph::Tools::get_db_wal_sizes() if !$sizes; + $d->{size} = $sizes->{$type} // $fallback_size; + } + 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"); - # size was not set via api, getting from config/fallback - if (!defined($d->{size})) { - $sizes = PVE::Ceph::Tools::get_db_wal_sizes() if !$sizes; - $d->{size} = $sizes->{$type} // $fallback_size; - } - 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"); + print "using '$part_or_lv' for block.$type\n"; + push @$cmd, "--block.$type", $part_or_lv; + } - print "using '$part_or_lv' for block.$type\n"; - push @$cmd, "--block.$type", $part_or_lv; - } + push @$cmd, '--data', $devpath if $create_mode eq 'create'; + push @$cmd, '--dmcrypt' if $param->{encrypted}; - push @$cmd, '--data', $devpath if $create_mode eq 'create'; - push @$cmd, '--dmcrypt' if $param->{encrypted}; + if ($create_mode eq 'batch') { + push @$cmd, + '--osds-per-device', $param->{'osds-per-device'}, + '--yes', + '--no-auto', + '--', + $devpath; + } + PVE::Diskmanage::wipe_blockdev($devpath); - if ($create_mode eq 'batch') { - push @$cmd, - '--osds-per-device', $param->{'osds-per-device'}, - '--yes', - '--no-auto', - '--', - $devpath; - } - PVE::Diskmanage::wipe_blockdev($devpath); + if (PVE::Diskmanage::is_partition($devpath)) { + eval { PVE::Diskmanage::change_parttype($devpath, '8E00'); }; + warn $@ if $@; + } - if (PVE::Diskmanage::is_partition($devpath)) { - eval { PVE::Diskmanage::change_parttype($devpath, '8E00'); }; - warn $@ if $@; - } + run_command($cmd); - run_command($cmd); + # FIXME: Remove once we depend on systemd >= v249. + # Work around udev bug https://github.com/systemd/systemd/issues/18525 to ensure the + # udev database is updated. + eval { run_command(['udevadm', 'trigger', @udev_trigger_devs]); }; + warn $@ if $@; + }); + }; - # FIXME: Remove once we depend on systemd >= v249. - # Work around udev bug https://github.com/systemd/systemd/issues/18525 to ensure the - # udev database is updated. - eval { run_command(['udevadm', 'trigger', @udev_trigger_devs]); }; - warn $@ if $@; - }); - }; - - return $rpcenv->fork_worker('cephcreateosd', $devs->{dev}->{name}, $authuser, $worker); - }}); + return $rpcenv->fork_worker('cephcreateosd', $devs->{dev}->{name}, $authuser, $worker); + }, +}); my $OSD_DEV_RETURN_PROPS = { device => { - type => 'string', - enum => ['block', 'db', 'wal'], - description => 'Kind of OSD device', + type => 'string', + enum => ['block', 'db', 'wal'], + description => 'Kind of OSD device', }, dev_node => { - type => 'string', - description => 'Device node', + type => 'string', + description => 'Device node', }, devices => { - type => 'string', - description => 'Physical disks used', + type => 'string', + description => 'Physical disks used', }, size => { - type => 'integer', - description => 'Size in bytes', + type => 'integer', + description => 'Size in bytes', }, support_discard => { - type => 'boolean', - description => 'Discard support of the physical device', + type => 'boolean', + description => 'Discard support of the physical device', }, type => { - type => 'string', - description => 'Type of device. For example, hdd or ssd', + type => 'string', + description => 'Type of device. For example, hdd or ssd', }, }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'osdindex', path => '{osdid}', method => 'GET', permissions => { user => 'all' }, description => "OSD index.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - osdid => { - description => 'OSD ID', - type => 'integer', - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + osdid => { + description => 'OSD ID', + type => 'integer', + }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $result = [ - { name => 'metadata' }, - { name => 'lv-info' }, - ]; + my $result = [ + { name => 'metadata' }, { name => 'lv-info' }, + ]; - return $result; - }}); + return $result; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'osddetails', path => '{osdid}/metadata', method => 'GET', @@ -639,154 +650,155 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit' ], any => 1], + check => ['perm', '/', ['Sys.Audit'], any => 1], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - osdid => { - description => 'OSD ID', - type => 'integer', - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + osdid => { + description => 'OSD ID', + type => 'integer', + }, + }, }, returns => { - type => 'object', - properties => { - osd => { - type => 'object', - description => 'General information about the OSD', - properties => { - hostname => { - type => 'string', - description => 'Name of the host containing the OSD.', - }, - id => { - type => 'integer', - description => 'ID of the OSD.', - }, - mem_usage => { - type => 'integer', - description => 'Memory usage of the OSD service.', - }, - osd_data => { - type => 'string', - description => "Path to the OSD's data directory.", - }, - osd_objectstore => { - type => 'string', - description => 'The type of object store used.', - }, - pid => { - type => 'integer', - description => 'OSD process ID.', - }, - version => { - type => 'string', - description => 'Ceph version of the OSD service.', - }, - front_addr => { - type => 'string', - description => 'Address and port used to talk to clients and monitors.', - }, - back_addr => { - type => 'string', - description => 'Address and port used to talk to other OSDs.', - }, - hb_front_addr => { - type => 'string', - description => 'Heartbeat address and port for clients and monitors.', - }, - hb_back_addr => { - type => 'string', - description => 'Heartbeat address and port for other OSDs.', - }, - }, - }, - devices => { - type => 'array', - description => 'Array containing data about devices', - items => { - type => "object", - properties => $OSD_DEV_RETURN_PROPS, - }, - } - } + type => 'object', + properties => { + osd => { + type => 'object', + description => 'General information about the OSD', + properties => { + hostname => { + type => 'string', + description => 'Name of the host containing the OSD.', + }, + id => { + type => 'integer', + description => 'ID of the OSD.', + }, + mem_usage => { + type => 'integer', + description => 'Memory usage of the OSD service.', + }, + osd_data => { + type => 'string', + description => "Path to the OSD's data directory.", + }, + osd_objectstore => { + type => 'string', + description => 'The type of object store used.', + }, + pid => { + type => 'integer', + description => 'OSD process ID.', + }, + version => { + type => 'string', + description => 'Ceph version of the OSD service.', + }, + front_addr => { + type => 'string', + description => 'Address and port used to talk to clients and monitors.', + }, + back_addr => { + type => 'string', + description => 'Address and port used to talk to other OSDs.', + }, + hb_front_addr => { + type => 'string', + description => 'Heartbeat address and port for clients and monitors.', + }, + hb_back_addr => { + type => 'string', + description => 'Heartbeat address and port for other OSDs.', + }, + }, + }, + devices => { + type => 'array', + description => 'Array containing data about devices', + items => { + type => "object", + properties => $OSD_DEV_RETURN_PROPS, + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $osdid = $param->{osdid}; - my $rados = PVE::RADOS->new(); - my $metadata = $rados->mon_command({ prefix => 'osd metadata', id => int($osdid) }); + my $osdid = $param->{osdid}; + my $rados = PVE::RADOS->new(); + my $metadata = $rados->mon_command({ prefix => 'osd metadata', id => int($osdid) }); - die "OSD '${osdid}' does not exists on host '${nodename}'\n" - if $nodename ne $metadata->{hostname}; + die "OSD '${osdid}' does not exists on host '${nodename}'\n" + if $nodename ne $metadata->{hostname}; - my $pid; - my $parser = sub { - my $line = shift; - if ($line =~ m/^MainPID=([0-9]*)$/) { - $pid = int($1); - } - }; + my $pid; + my $parser = sub { + my $line = shift; + if ($line =~ m/^MainPID=([0-9]*)$/) { + $pid = int($1); + } + }; - my $cmd = [ - '/bin/systemctl', - 'show', - "ceph-osd\@${osdid}.service", - '--property', - 'MainPID', - ]; - run_command($cmd, errmsg => 'fetching OSD PID and memory usage failed', outfunc => $parser); + my $cmd = [ + '/bin/systemctl', 'show', "ceph-osd\@${osdid}.service", '--property', 'MainPID', + ]; + 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 $@; + my $osd_pss_memory = eval { get_proc_pss_from_pid($pid) } // 0; + warn $@ if $@; - my $data = { - osd => { - hostname => $metadata->{hostname}, - id => $metadata->{id}, - mem_usage => $osd_pss_memory, - osd_data => $metadata->{osd_data}, - osd_objectstore => $metadata->{osd_objectstore}, - pid => $pid, - version => "$metadata->{ceph_version_short} ($metadata->{ceph_release})", - front_addr => $metadata->{front_addr}, - back_addr => $metadata->{back_addr}, - hb_front_addr => $metadata->{hb_front_addr}, - hb_back_addr => $metadata->{hb_back_addr}, - }, - }; + my $data = { + osd => { + hostname => $metadata->{hostname}, + id => $metadata->{id}, + mem_usage => $osd_pss_memory, + osd_data => $metadata->{osd_data}, + osd_objectstore => $metadata->{osd_objectstore}, + pid => $pid, + version => "$metadata->{ceph_version_short} ($metadata->{ceph_release})", + front_addr => $metadata->{front_addr}, + back_addr => $metadata->{back_addr}, + hb_front_addr => $metadata->{hb_front_addr}, + hb_back_addr => $metadata->{hb_back_addr}, + }, + }; - $data->{devices} = []; + $data->{devices} = []; - my $get_data = sub { - my ($dev, $prefix, $device) = @_; - push ( - @{$data->{devices}}, - { - dev_node => $metadata->{"${prefix}_${dev}_dev_node"}, - physical_device => $metadata->{"${prefix}_${dev}_devices"}, - size => int($metadata->{"${prefix}_${dev}_size"}), - support_discard => int($metadata->{"${prefix}_${dev}_support_discard"}), - type => $metadata->{"${prefix}_${dev}_type"}, - device => $device, - } - ); - }; + my $get_data = sub { + my ($dev, $prefix, $device) = @_; + push( + @{ $data->{devices} }, + { + dev_node => $metadata->{"${prefix}_${dev}_dev_node"}, + physical_device => $metadata->{"${prefix}_${dev}_devices"}, + size => int($metadata->{"${prefix}_${dev}_size"}), + support_discard => int($metadata->{"${prefix}_${dev}_support_discard"}), + type => $metadata->{"${prefix}_${dev}_type"}, + device => $device, + }, + ); + }; - $get_data->("bdev", "bluestore", "block"); - $get_data->("db", "bluefs", "db") if $metadata->{bluefs_dedicated_db}; - $get_data->("wal", "bluefs", "wal") if $metadata->{bluefs_dedicated_wal}; + $get_data->("bdev", "bluestore", "block"); + $get_data->("db", "bluefs", "db") if $metadata->{bluefs_dedicated_db}; + $get_data->("wal", "bluefs", "wal") if $metadata->{bluefs_dedicated_wal}; - return $data; - }}); + return $data; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'osdvolume', path => '{osdid}/lv-info', method => 'GET', @@ -794,98 +806,100 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit' ], any => 1], + check => ['perm', '/', ['Sys.Audit'], any => 1], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - osdid => { - description => 'OSD ID', - type => 'integer', - }, - type => { - description => 'OSD device type', - type => 'string', - enum => ['block', 'db', 'wal'], - default => 'block', - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + osdid => { + description => 'OSD ID', + type => 'integer', + }, + type => { + description => 'OSD device type', + type => 'string', + enum => ['block', 'db', 'wal'], + default => 'block', + optional => 1, + }, + }, }, returns => { - type => 'object', - properties => { - creation_time => { - type => 'string', - description => "Creation time as reported by `lvs`.", - }, - lv_name => { - type => 'string', - description => 'Name of the logical volume (LV).', - }, - lv_path => { - type => 'string', - description => 'Path to the logical volume (LV).', - }, - lv_size => { - type => 'integer', - description => 'Size of the logical volume (LV).', - }, - lv_uuid => { - type => 'string', - description => 'UUID of the logical volume (LV).', - }, - vg_name => { - type => 'string', - description => 'Name of the volume group (VG).', - }, - }, + type => 'object', + properties => { + creation_time => { + type => 'string', + description => "Creation time as reported by `lvs`.", + }, + lv_name => { + type => 'string', + description => 'Name of the logical volume (LV).', + }, + lv_path => { + type => 'string', + description => 'Path to the logical volume (LV).', + }, + lv_size => { + type => 'integer', + description => 'Size of the logical volume (LV).', + }, + lv_uuid => { + type => 'string', + description => 'UUID of the logical volume (LV).', + }, + vg_name => { + type => 'string', + description => 'Name of the volume group (VG).', + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $osdid = $param->{osdid}; - my $type = $param->{type} // 'block'; + my $osdid = $param->{osdid}; + my $type = $param->{type} // 'block'; - my $raw = ''; - my $parser = sub { $raw .= shift }; - my $cmd = ['/usr/sbin/ceph-volume', 'lvm', 'list', $osdid, '--format', 'json']; - run_command($cmd, errmsg => 'listing Ceph LVM volumes failed', outfunc => $parser); + my $raw = ''; + my $parser = sub { $raw .= shift }; + my $cmd = ['/usr/sbin/ceph-volume', 'lvm', 'list', $osdid, '--format', 'json']; + run_command($cmd, errmsg => 'listing Ceph LVM volumes failed', outfunc => $parser); - my $result; - if ($raw =~ m/^(\{.*\})$/s) { #untaint - $result = JSON::decode_json($1); - } else { - die "got unexpected data from ceph-volume: '${raw}'\n"; - } - 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"; - } + my $result; + if ($raw =~ m/^(\{.*\})$/s) { #untaint + $result = JSON::decode_json($1); + } else { + die "got unexpected data from ceph-volume: '${raw}'\n"; + } + 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"; + } - 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']; - run_command($cmd, errmsg => 'listing logical volumes failed', outfunc => $parser); + $raw = ''; + $cmd = ['/sbin/lvs', $volume->{lv_path}, '--reportformat', 'json', '-o', 'lv_time']; + run_command($cmd, errmsg => 'listing logical volumes failed', outfunc => $parser); - if ($raw =~ m/(\{.*\})$/s) { #untaint, lvs has whitespace at beginning - $result = JSON::decode_json($1); - } else { - die "got unexpected data from lvs: '${raw}'\n"; - } + if ($raw =~ m/(\{.*\})$/s) { #untaint, lvs has whitespace at beginning + $result = JSON::decode_json($1); + } else { + die "got unexpected data from lvs: '${raw}'\n"; + } - my $data = { map { $_ => $volume->{$_} } qw(lv_name lv_path lv_uuid vg_name) }; - $data->{lv_size} = int($volume->{lv_size}); + 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; - }}); + return $data; + }, +}); # Check if $osdid belongs to $nodename # $tree ... rados osd tree (passing the tree makes it easy to test) @@ -894,10 +908,10 @@ 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}}) { - my $name = $el->{name}; - die "internal error: duplicate host name found '$name'\n" if $node_map->{$name}; - $node_map->{$name} = $el; + 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; } my $osds = $node_map->{$nodename}->{children}; @@ -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', @@ -914,170 +928,190 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - osdid => { - description => 'OSD ID', - type => 'integer', - }, - cleanup => { - description => "If set, we remove partition table entries.", - type => 'boolean', - optional => 1, - default => 0, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + osdid => { + description => 'OSD ID', + type => 'integer', + }, + cleanup => { + description => "If set, we remove partition table entries.", + type => 'boolean', + optional => 1, + default => 0, + }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $osdid = $param->{osdid}; - my $cleanup = $param->{cleanup}; + my $osdid = $param->{osdid}; + my $cleanup = $param->{cleanup}; - my $rados = PVE::RADOS->new(); + my $rados = PVE::RADOS->new(); - my $osd_belongs_to_node = osd_belongs_to_node( - $rados->mon_command({ prefix => 'osd tree' }), - $param->{node}, - $osdid, - ); - die "OSD osd.$osdid does not belong to node $param->{node}!" - if !$osd_belongs_to_node; + my $osd_belongs_to_node = osd_belongs_to_node( + $rados->mon_command({ prefix => 'osd tree' }), + $param->{node}, + $osdid, + ); + die "OSD osd.$osdid does not belong to node $param->{node}!" + if !$osd_belongs_to_node; - # dies if osdid is unknown - my $osdstat = $get_osd_status->($rados, $osdid); + # dies if osdid is unknown + my $osdstat = $get_osd_status->($rados, $osdid); - die "osd is in use (in == 1)\n" if $osdstat->{in}; - #&$run_ceph_cmd(['osd', 'out', $osdid]); + die "osd is in use (in == 1)\n" if $osdstat->{in}; + #&$run_ceph_cmd(['osd', 'out', $osdid]); - die "osd is still running (up == 1)\n" if $osdstat->{up}; + die "osd is still running (up == 1)\n" if $osdstat->{up}; - my $osdsection = "osd.$osdid"; + my $osdsection = "osd.$osdid"; - my $worker = sub { - my $upid = shift; + my $worker = sub { + my $upid = shift; - # reopen with longer timeout - $rados = PVE::RADOS->new(timeout => PVE::Ceph::Tools::get_config('long_rados_timeout')); + # reopen with longer timeout + $rados = + PVE::RADOS->new(timeout => PVE::Ceph::Tools::get_config('long_rados_timeout')); - print "destroy OSD $osdsection\n"; + print "destroy OSD $osdsection\n"; - eval { - PVE::Ceph::Services::ceph_service_cmd('stop', $osdsection); - PVE::Ceph::Services::ceph_service_cmd('disable', $osdsection); - }; - warn $@ if $@; + eval { + PVE::Ceph::Services::ceph_service_cmd('stop', $osdsection); + PVE::Ceph::Services::ceph_service_cmd('disable', $osdsection); + }; + warn $@ if $@; - print "Remove $osdsection from the CRUSH map\n"; - $rados->mon_command({ prefix => "osd crush remove", name => $osdsection, format => 'plain' }); + print "Remove $osdsection from the CRUSH map\n"; + $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' }); + print "Remove the $osdsection authentication key.\n"; + $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' }); + print "Remove OSD $osdsection\n"; + $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' }); + 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', + }, + ); - # try to unmount from standard mount point - my $mountpoint = "/var/lib/ceph/osd/ceph-$osdid"; + # try to unmount from standard mount point + my $mountpoint = "/var/lib/ceph/osd/ceph-$osdid"; - # See FIXME below - my $udev_trigger_devs = {}; + # See FIXME below + my $udev_trigger_devs = {}; - my $remove_partition = sub { - my ($part) = @_; + my $remove_partition = sub { + my ($part) = @_; - return if !$part || (! -b $part ); - my $partnum = PVE::Diskmanage::get_partnum($part); - my $devpath = PVE::Diskmanage::get_blockdev($part); + return if !$part || (!-b $part); + my $partnum = PVE::Diskmanage::get_partnum($part); + my $devpath = PVE::Diskmanage::get_blockdev($part); - $udev_trigger_devs->{$devpath} = 1; + $udev_trigger_devs->{$devpath} = 1; - PVE::Diskmanage::wipe_blockdev($part); - print "remove partition $part (disk '${devpath}', partnum $partnum)\n"; - eval { run_command(['/sbin/sgdisk', '-d', $partnum, "${devpath}"]); }; - warn $@ if $@; - }; + PVE::Diskmanage::wipe_blockdev($part); + print "remove partition $part (disk '${devpath}', partnum $partnum)\n"; + eval { run_command(['/sbin/sgdisk', '-d', $partnum, "${devpath}"]); }; + warn $@ if $@; + }; - my $osd_list = PVE::Ceph::Tools::ceph_volume_list(); + my $osd_list = PVE::Ceph::Tools::ceph_volume_list(); - if ($osd_list->{$osdid}) { # ceph-volume managed + if ($osd_list->{$osdid}) { # ceph-volume managed - eval { PVE::Ceph::Tools::ceph_volume_zap($osdid, $cleanup) }; - warn $@ if $@; + eval { PVE::Ceph::Tools::ceph_volume_zap($osdid, $cleanup) }; + warn $@ if $@; - 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}}) { - ($dev) = ($dev =~ m|^(/dev/[-_.a-zA-Z0-9\/]+)$|); #untaint + 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} }) { + ($dev) = ($dev =~ m|^(/dev/[-_.a-zA-Z0-9\/]+)$|); #untaint - eval { run_command(['/sbin/pvremove', $dev], errfunc => sub {}) }; - warn $@ if $@; + eval { + run_command(['/sbin/pvremove', $dev], errfunc => sub { }); + }; + warn $@ if $@; - $udev_trigger_devs->{$dev} = 1; - } - } - } - } else { - my $partitions_to_remove = []; - if ($cleanup) { - if (my $mp = PVE::ProcFSTools::parse_proc_mounts()) { - foreach my $line (@$mp) { - my ($dev, $path, $fstype) = @$line; - next if !($dev && $path && $fstype); - next if $dev !~ m|^/dev/|; + $udev_trigger_devs->{$dev} = 1; + } + } + } + } else { + my $partitions_to_remove = []; + if ($cleanup) { + if (my $mp = PVE::ProcFSTools::parse_proc_mounts()) { + foreach my $line (@$mp) { + my ($dev, $path, $fstype) = @$line; + next if !($dev && $path && $fstype); + next if $dev !~ m|^/dev/|; - if ($path eq $mountpoint) { - abs_path($dev) =~ m|^(/.+)| or die "invalid dev: $dev\n"; - push @$partitions_to_remove, $1; - last; - } - } - } + if ($path eq $mountpoint) { + abs_path($dev) =~ m|^(/.+)| or die "invalid dev: $dev\n"; + push @$partitions_to_remove, $1; + last; + } + } + } - foreach my $path (qw(journal block block.db block.wal)) { - abs_path("$mountpoint/$path") =~ m|^(/.+)| or die "invalid path: $path\n"; - push @$partitions_to_remove, $1; - } - } + foreach my $path (qw(journal block block.db block.wal)) { + abs_path("$mountpoint/$path") =~ m|^(/.+)| + or die "invalid path: $path\n"; + push @$partitions_to_remove, $1; + } + } - print "Unmount OSD $osdsection from $mountpoint\n"; - eval { run_command(['/bin/umount', $mountpoint]); }; - if (my $err = $@) { - warn $err; - } elsif ($cleanup) { - #be aware of the ceph udev rules which can remount. - foreach my $part (@$partitions_to_remove) { - $remove_partition->($part); - } - } - } + print "Unmount OSD $osdsection from $mountpoint\n"; + eval { run_command(['/bin/umount', $mountpoint]); }; + if (my $err = $@) { + warn $err; + } elsif ($cleanup) { + #be aware of the ceph udev rules which can remount. + foreach my $part (@$partitions_to_remove) { + $remove_partition->($part); + } + } + } - # FIXME: Remove once we depend on systemd >= v249. - # Work around udev bug https://github.com/systemd/systemd/issues/18525 to ensure the - # udev database is updated. - if ($cleanup) { - eval { run_command(['udevadm', 'trigger', keys $udev_trigger_devs->%*]); }; - warn $@ if $@; - } - }; + # FIXME: Remove once we depend on systemd >= v249. + # Work around udev bug https://github.com/systemd/systemd/issues/18525 to ensure the + # udev database is updated. + if ($cleanup) { + eval { run_command(['udevadm', 'trigger', keys $udev_trigger_devs->%*]); }; + warn $@ if $@; + } + }; - return $rpcenv->fork_worker('cephdestroyosd', $osdsection, $authuser, $worker); - }}); + return $rpcenv->fork_worker('cephdestroyosd', $osdsection, $authuser, $worker); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'in', path => '{osdid}/in', method => 'POST', @@ -1085,38 +1119,39 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - osdid => { - description => 'OSD ID', - type => 'integer', - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + osdid => { + description => 'OSD ID', + type => 'integer', + }, + }, }, returns => { type => "null" }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $osdid = $param->{osdid}; + my $osdid = $param->{osdid}; - my $rados = PVE::RADOS->new(); + my $rados = PVE::RADOS->new(); - $get_osd_status->($rados, $osdid); # osd exists? + $get_osd_status->($rados, $osdid); # osd exists? - my $osdsection = "osd.$osdid"; + 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; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'out', path => '{osdid}/out', method => 'POST', @@ -1124,38 +1159,39 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - osdid => { - description => 'OSD ID', - type => 'integer', - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + osdid => { + description => 'OSD ID', + type => 'integer', + }, + }, }, returns => { type => "null" }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $osdid = $param->{osdid}; + my $osdid = $param->{osdid}; - my $rados = PVE::RADOS->new(); + my $rados = PVE::RADOS->new(); - $get_osd_status->($rados, $osdid); # osd exists? + $get_osd_status->($rados, $osdid); # osd exists? - my $osdsection = "osd.$osdid"; + 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; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'scrub', path => '{osdid}/scrub', method => 'POST', @@ -1163,41 +1199,42 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - osdid => { - description => 'OSD ID', - type => 'integer', - }, - deep => { - description => 'If set, instructs a deep scrub instead of a normal one.', - type => 'boolean', - optional => 1, - default => 0, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + osdid => { + description => 'OSD ID', + type => 'integer', + }, + deep => { + description => 'If set, instructs a deep scrub instead of a normal one.', + type => 'boolean', + optional => 1, + default => 0, + }, + }, }, returns => { type => "null" }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $osdid = $param->{osdid}; - my $deep = $param->{deep} // 0; + my $osdid = $param->{osdid}; + my $deep = $param->{deep} // 0; - my $rados = PVE::RADOS->new(); + my $rados = PVE::RADOS->new(); - $get_osd_status->($rados, $osdid); # osd exists? + $get_osd_status->($rados, $osdid); # osd exists? - my $prefix = $deep ? 'osd deep-scrub' : 'osd scrub'; - $rados->mon_command({ prefix => $prefix, who => $osdid }); + my $prefix = $deep ? 'osd deep-scrub' : 'osd scrub'; + $rados->mon_command({ prefix => $prefix, who => $osdid }); - return undef; - }}); + return undef; + }, +}); 1; diff --git a/PVE/API2/Ceph/Pool.pm b/PVE/API2/Ceph/Pool.pm index b890c633..4df6f2ca 100644 --- a/PVE/API2/Ceph/Pool.pm +++ b/PVE/API2/Ceph/Pool.pm @@ -21,296 +21,293 @@ 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, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - pool => { - type => 'integer', - title => 'ID', - }, - pool_name => { - type => 'string', - title => 'Name', - }, - size => { - type => 'integer', - title => 'Size', - }, - type => { - type => 'string', - title => 'Type', - enum => ['replicated', 'erasure', 'unknown'], - }, - min_size => { - type => 'integer', - title => 'Min Size', - }, - pg_num => { - type => 'integer', - title => 'PG Num', - }, - pg_num_min => { - type => 'integer', - title => 'min. PG Num', - optional => 1, - }, - pg_num_final => { - type => 'integer', - title => 'Optimal PG Num', - optional => 1, - }, - pg_autoscale_mode => { - type => 'string', - title => 'PG Autoscale Mode', - optional => 1, - }, - crush_rule => { - type => 'integer', - title => 'Crush Rule', - }, - crush_rule_name => { - type => 'string', - title => 'Crush Rule Name', - }, - percent_used => { - type => 'number', - title => '%-Used', - }, - bytes_used => { - type => 'integer', - title => 'Used', - }, - target_size => { - type => 'integer', - title => 'PG Autoscale Target Size', - optional => 1, - }, - target_size_ratio => { - type => 'number', - title => 'PG Autoscale Target Ratio', - optional => 1, - }, - autoscale_status => { - type => 'object', - title => 'Autoscale Status', - optional => 1, - }, - application_metadata => { - type => 'object', - title => 'Associated Applications', - optional => 1, - }, - }, - }, - links => [ { rel => 'child', href => "{pool_name}" } ], + type => 'array', + items => { + type => "object", + properties => { + pool => { + type => 'integer', + title => 'ID', + }, + pool_name => { + type => 'string', + title => 'Name', + }, + size => { + type => 'integer', + title => 'Size', + }, + type => { + type => 'string', + title => 'Type', + enum => ['replicated', 'erasure', 'unknown'], + }, + min_size => { + type => 'integer', + title => 'Min Size', + }, + pg_num => { + type => 'integer', + title => 'PG Num', + }, + pg_num_min => { + type => 'integer', + title => 'min. PG Num', + optional => 1, + }, + pg_num_final => { + type => 'integer', + title => 'Optimal PG Num', + optional => 1, + }, + pg_autoscale_mode => { + type => 'string', + title => 'PG Autoscale Mode', + optional => 1, + }, + crush_rule => { + type => 'integer', + title => 'Crush Rule', + }, + crush_rule_name => { + type => 'string', + title => 'Crush Rule Name', + }, + percent_used => { + type => 'number', + title => '%-Used', + }, + bytes_used => { + type => 'integer', + title => 'Used', + }, + target_size => { + type => 'integer', + title => 'PG Autoscale Target Size', + optional => 1, + }, + target_size_ratio => { + type => 'number', + title => 'PG Autoscale Target Ratio', + optional => 1, + }, + autoscale_status => { + type => 'object', + title => 'Autoscale Status', + optional => 1, + }, + application_metadata => { + type => 'object', + title => 'Associated Applications', + optional => 1, + }, + }, + }, + links => [{ rel => 'child', href => "{pool_name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $rados = PVE::RADOS->new(); + my $rados = PVE::RADOS->new(); - my $stats = {}; - my $res = $rados->mon_command({ prefix => 'df' }); + my $stats = {}; + my $res = $rados->mon_command({ prefix => 'df' }); - foreach my $d (@{$res->{pools}}) { - next if !$d->{stats}; - next if !defined($d->{id}); - $stats->{$d->{id}} = $d->{stats}; - } + foreach my $d (@{ $res->{pools} }) { + next if !$d->{stats}; + next if !defined($d->{id}); + $stats->{ $d->{id} } = $d->{stats}; + } - $res = $rados->mon_command({ prefix => 'osd dump' }); - my $rulestmp = $rados->mon_command({ prefix => 'osd crush rule dump'}); + $res = $rados->mon_command({ prefix => 'osd dump' }); + my $rulestmp = $rados->mon_command({ prefix => 'osd crush rule dump' }); - my $rules = {}; - for my $rule (@$rulestmp) { - $rules->{$rule->{rule_id}} = $rule->{rule_name}; - } + my $rules = {}; + for my $rule (@$rulestmp) { + $rules->{ $rule->{rule_id} } = $rule->{rule_name}; + } - my $data = []; - my $attr_list = [ - 'pool', - 'pool_name', - 'size', - 'min_size', - 'pg_num', - 'crush_rule', - 'pg_autoscale_mode', - 'application_metadata', - ]; + my $data = []; + my $attr_list = [ + 'pool', + 'pool_name', + 'size', + 'min_size', + 'pg_num', + 'crush_rule', + 'pg_autoscale_mode', + 'application_metadata', + ]; - # pg_autoscaler module is not enabled in Nautilus - my $autoscale = eval { $get_autoscale_status->($rados) }; + # pg_autoscaler module is not enabled in Nautilus + my $autoscale = eval { $get_autoscale_status->($rados) }; - foreach my $e (@{$res->{pools}}) { - my $d = {}; - foreach my $attr (@$attr_list) { - $d->{$attr} = $e->{$attr} if defined($e->{$attr}); - } + 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->{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}; - $d->{target_size} = $e->{options}->{target_size_bytes}; - $d->{target_size_ratio} = $e->{options}->{target_size_ratio}; - } + if ($autoscale) { + $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}; + $d->{target_size} = $e->{options}->{target_size_bytes}; + $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}}) { - $d->{bytes_used} = $s->{bytes_used}; - $d->{percent_used} = $s->{percent_used}; - } + if (my $s = $stats->{ $d->{pool} }) { + $d->{bytes_used} = $s->{bytes_used}; + $d->{percent_used} = $s->{percent_used}; + } - # Cephs numerical pool types are barely documented. Found the following in the Ceph - # codebase: https://github.com/ceph/ceph/blob/ff144995a849407c258bcb763daa3e03cfce5059/src/osd/osd_types.h#L1221-L1233 - if ($e->{type} == 1) { - $d->{type} = 'replicated'; - } elsif ($e->{type} == 3) { - $d->{type} = 'erasure'; - } else { - # we should never get here, but better be safe - $d->{type} = 'unknown'; - } - push @$data, $d; - } - - - return $data; - }}); + # Cephs numerical pool types are barely documented. Found the following in the Ceph + # codebase: https://github.com/ceph/ceph/blob/ff144995a849407c258bcb763daa3e03cfce5059/src/osd/osd_types.h#L1221-L1233 + if ($e->{type} == 1) { + $d->{type} = 'replicated'; + } elsif ($e->{type} == 3) { + $d->{type} = 'erasure'; + } else { + # we should never get here, but better be safe + $d->{type} = 'unknown'; + } + push @$data, $d; + } + return $data; + }, +}); my $ceph_pool_common_options = sub { my ($nodefault) = shift; my $options = { - name => { - title => 'Name', - description => "The name of the pool. It must be unique.", - type => 'string', - pattern => qr|^[^:/\s]+$|, - }, - size => { - title => 'Size', - description => 'Number of replicas per object', - type => 'integer', - default => 3, - optional => 1, - minimum => 1, - maximum => 7, - }, - min_size => { - title => 'Min Size', - description => 'Minimum number of replicas per object', - type => 'integer', - default => 2, - optional => 1, - minimum => 1, - maximum => 7, - }, - pg_num => { - title => 'PG Num', - description => "Number of placement groups.", - type => 'integer', - default => 128, - optional => 1, - minimum => 1, - maximum => 32768, - }, - pg_num_min => { - title => 'min. PG Num', - description => "Minimal number of placement groups.", - type => 'integer', - optional => 1, - maximum => 32768, - }, - crush_rule => { - title => 'Crush Rule Name', - description => "The rule to use for mapping object placement in the cluster.", - type => 'string', - optional => 1, - }, - application => { - title => 'Application', - description => "The application of the pool.", - default => 'rbd', - type => 'string', - enum => ['rbd', 'cephfs', 'rgw'], - optional => 1, - }, - pg_autoscale_mode => { - title => 'PG Autoscale Mode', - description => "The automatic PG scaling mode of the pool.", - type => 'string', - enum => ['on', 'off', 'warn'], - default => 'warn', - optional => 1, - }, - target_size => { - description => "The estimated target size of the pool for the PG autoscaler.", - title => 'PG Autoscale Target Size', - type => 'string', - pattern => '^(\d+(\.\d+)?)([KMGT])?$', - optional => 1, - }, - target_size_ratio => { - description => "The estimated target ratio of the pool for the PG autoscaler.", - title => 'PG Autoscale Target Ratio', - type => 'number', - optional => 1, - }, + name => { + title => 'Name', + description => "The name of the pool. It must be unique.", + type => 'string', + pattern => qr|^[^:/\s]+$|, + }, + size => { + title => 'Size', + description => 'Number of replicas per object', + type => 'integer', + default => 3, + optional => 1, + minimum => 1, + maximum => 7, + }, + min_size => { + title => 'Min Size', + description => 'Minimum number of replicas per object', + type => 'integer', + default => 2, + optional => 1, + minimum => 1, + maximum => 7, + }, + pg_num => { + title => 'PG Num', + description => "Number of placement groups.", + type => 'integer', + default => 128, + optional => 1, + minimum => 1, + maximum => 32768, + }, + pg_num_min => { + title => 'min. PG Num', + description => "Minimal number of placement groups.", + type => 'integer', + optional => 1, + maximum => 32768, + }, + crush_rule => { + title => 'Crush Rule Name', + description => "The rule to use for mapping object placement in the cluster.", + type => 'string', + optional => 1, + }, + application => { + title => 'Application', + description => "The application of the pool.", + default => 'rbd', + type => 'string', + enum => ['rbd', 'cephfs', 'rgw'], + optional => 1, + }, + pg_autoscale_mode => { + title => 'PG Autoscale Mode', + description => "The automatic PG scaling mode of the pool.", + type => 'string', + enum => ['on', 'off', 'warn'], + default => 'warn', + optional => 1, + }, + target_size => { + description => "The estimated target size of the pool for the PG autoscaler.", + title => 'PG Autoscale Target Size', + type => 'string', + pattern => '^(\d+(\.\d+)?)([KMGT])?$', + optional => 1, + }, + target_size_ratio => { + description => "The estimated target ratio of the pool for the PG autoscaler.", + title => 'PG Autoscale Target Ratio', + type => 'number', + optional => 1, + }, }; if ($nodefault) { - delete $options->{$_}->{default} for keys %$options; + delete $options->{$_}->{default} for keys %$options; } return $options; }; - my $add_storage = sub { my ($pool, $storeid, $ec_data_pool) = @_; my $storage_params = { - type => 'rbd', - pool => $pool, - storage => $storeid, - krbd => 0, - content => 'rootdir,images', + type => 'rbd', + pool => $pool, + storage => $storeid, + krbd => 0, + content => 'rootdir,images', }; $storage_params->{'data-pool'} = $ec_data_pool if $ec_data_pool; @@ -326,15 +323,15 @@ my $get_storages = sub { my $storages = $cfg->{ids}; my $res = {}; foreach my $storeid (keys %$storages) { - my $curr = $storages->{$storeid}; - 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'}) - ) { - $res->{$storeid} = $storages->{$storeid}; - } + my $curr = $storages->{$storeid}; + 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'}) + ) { + $res->{$storeid} = $storages->{$storeid}; + } } return $res; @@ -342,38 +339,38 @@ my $get_storages = sub { my $ec_format = { k => { - type => 'integer', - description => "Number of data chunks. Will create an erasure coded pool plus a" - ." replicated pool for metadata.", - minimum => 2, + type => 'integer', + description => "Number of data chunks. Will create an erasure coded pool plus a" + . " 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.", - minimum => 1, + type => 'integer', + description => "Number of coding chunks. Will create an erasure coded pool plus a" + . " 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.", - format_description => 'domain', - optional => 1, - default => 'host', + type => 'string', + description => "CRUSH failure domain. Default is 'host'. Will create an erasure" + . " coded pool plus a replicated pool for metadata.", + format_description => 'domain', + optional => 1, + default => 'host', }, 'device-class' => { - type => 'string', - description => "CRUSH device class. Will create an erasure coded pool plus a" - ." replicated pool for metadata.", - format_description => 'class', - optional => 1, + type => 'string', + description => "CRUSH device class. Will create an erasure coded pool plus a" + . " 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.", - type => 'string', - format_description => 'profile', - optional => 1, + description => "Override the erasure code (EC) profile to use. Will create an" + . " erasure coded pool plus a replicated pool for metadata.", + type => 'string', + format_description => 'profile', + optional => 1, }, }; @@ -384,13 +381,12 @@ sub ec_parse_and_check { my $ec = parse_property_string($ec_format, $property); die "Erasure code profile '$ec->{profile}' does not exist.\n" - if $ec->{profile} && !PVE::Ceph::Tools::ecprofile_exists($ec->{profile}, $rados); + if $ec->{profile} && !PVE::Ceph::Tools::ecprofile_exists($ec->{profile}, $rados); return $ec; } - -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'createpool', path => '', method => 'POST', @@ -398,115 +394,116 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - add_storages => { - description => "Configure VM and CT storage using the new pool.", - type => 'boolean', - optional => 1, - default => "0; for erasure coded pools: 1", - }, - '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.", - type => 'string', - format => $ec_format, - optional => 1, - }, - %{ $ceph_pool_common_options->() }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + add_storages => { + description => "Configure VM and CT storage using the new pool.", + type => 'boolean', + optional => 1, + default => "0; for erasure coded pools: 1", + }, + '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.", + type => 'string', + format => $ec_format, + optional => 1, + }, + %{ $ceph_pool_common_options->() }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Cluster::check_cfs_quorum(); - PVE::Ceph::Tools::check_ceph_configured(); + PVE::Cluster::check_cfs_quorum(); + PVE::Ceph::Tools::check_ceph_configured(); - my $pool = my $name = extract_param($param, 'name'); - my $node = extract_param($param, 'node'); - my $add_storages = extract_param($param, 'add_storages'); + my $pool = my $name = extract_param($param, 'name'); + my $node = extract_param($param, 'node'); + my $add_storages = extract_param($param, 'add_storages'); - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); - # Ceph uses target_size_bytes - if (defined($param->{'target_size'})) { - my $target_sizestr = extract_param($param, 'target_size'); - $param->{target_size_bytes} = PVE::JSONSchema::parse_size($target_sizestr); - } + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); + # Ceph uses target_size_bytes + if (defined($param->{'target_size'})) { + my $target_sizestr = extract_param($param, 'target_size'); + $param->{target_size_bytes} = PVE::JSONSchema::parse_size($target_sizestr); + } - my $rados = PVE::RADOS->new(); - my $ec = ec_parse_and_check(extract_param($param, 'erasure-coding'), $rados); - $add_storages = 1 if $ec && !defined($add_storages); + my $rados = PVE::RADOS->new(); + my $ec = ec_parse_and_check(extract_param($param, 'erasure-coding'), $rados); + $add_storages = 1 if $ec && !defined($add_storages); - if ($add_storages) { - $rpcenv->check($user, '/storage', ['Datastore.Allocate']); - die "pool name contains characters which are illegal for storage naming\n" - if !PVE::JSONSchema::parse_storage_id($pool); - } + if ($add_storages) { + $rpcenv->check($user, '/storage', ['Datastore.Allocate']); + die "pool name contains characters which are illegal for storage naming\n" + if !PVE::JSONSchema::parse_storage_id($pool); + } - # pool defaults - $param->{pg_num} //= 128; - $param->{size} //= 3; - $param->{min_size} //= 2; - $param->{application} //= 'rbd'; - $param->{pg_autoscale_mode} //= 'warn'; + # pool defaults + $param->{pg_num} //= 128; + $param->{size} //= 3; + $param->{min_size} //= 2; + $param->{application} //= 'rbd'; + $param->{pg_autoscale_mode} //= 'warn'; - my $worker = sub { - # reopen with longer timeout - $rados = PVE::RADOS->new(timeout => PVE::Ceph::Tools::get_config('long_rados_timeout')); + my $worker = sub { + # reopen with longer 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'}, - $rados, - ); - }; - die "could not create erasure code profile '$ec->{profile}': $@\n" if $@; - print "created new erasure code profile '$ec->{profile}'\n"; - } + 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' }, + $rados, + ); + }; + die "could not create erasure code profile '$ec->{profile}': $@\n" if $@; + print "created new erasure code profile '$ec->{profile}'\n"; + } - my $ec_data_param = {}; - # copy all params, should be a flat hash - $ec_data_param = { map { $_ => $param->{$_} } keys %$param }; + my $ec_data_param = {}; + # copy all params, should be a flat hash + $ec_data_param = { map { $_ => $param->{$_} } keys %$param }; - $ec_data_param->{pool_type} = 'erasure'; - $ec_data_param->{allow_ec_overwrites} = 'true'; - $ec_data_param->{erasure_code_profile} = $ec->{profile}; - delete $ec_data_param->{size}; - delete $ec_data_param->{min_size}; - delete $ec_data_param->{crush_rule}; + $ec_data_param->{pool_type} = 'erasure'; + $ec_data_param->{allow_ec_overwrites} = 'true'; + $ec_data_param->{erasure_code_profile} = $ec->{profile}; + delete $ec_data_param->{size}; + delete $ec_data_param->{min_size}; + delete $ec_data_param->{crush_rule}; - # metadata pool should be ok with 32 PGs - $param->{pg_num} = 32; + # metadata pool should be ok with 32 PGs + $param->{pg_num} = 32; - $pool = "${name}-metadata"; - $ec->{data_pool} = "${name}-data"; + $pool = "${name}-metadata"; + $ec->{data_pool} = "${name}-data"; - PVE::Ceph::Tools::create_pool($ec->{data_pool}, $ec_data_param, $rados); - } + PVE::Ceph::Tools::create_pool($ec->{data_pool}, $ec_data_param, $rados); + } - PVE::Ceph::Tools::create_pool($pool, $param, $rados); + PVE::Ceph::Tools::create_pool($pool, $param, $rados); - if ($add_storages) { - eval { $add_storage->($pool, "${name}", $ec->{data_pool}) }; - die "adding PVE storage for ceph pool '$name' failed: $@\n" if $@; - } - }; + if ($add_storages) { + eval { $add_storage->($pool, "${name}", $ec->{data_pool}) }; + die "adding PVE storage for ceph pool '$name' failed: $@\n" if $@; + } + }; - return $rpcenv->fork_worker('cephcreatepool', $pool, $user, $worker); - }}); + return $rpcenv->fork_worker('cephcreatepool', $pool, $user, $worker); + }, +}); - -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'destroypool', path => '{name}', method => 'DELETE', @@ -514,105 +511,106 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - name => { - description => "The name of the pool. It must be unique.", - type => 'string', - }, - force => { - description => "If true, destroys pool even if in use", - type => 'boolean', - optional => 1, - default => 0, - }, - remove_storages => { - description => "Remove all pveceph-managed storages configured for this pool", - type => 'boolean', - optional => 1, - default => 0, - }, - remove_ecprofile => { - description => "Remove the erasure code profile. Defaults to true, if applicable.", - type => 'boolean', - optional => 1, - default => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + name => { + description => "The name of the pool. It must be unique.", + type => 'string', + }, + force => { + description => "If true, destroys pool even if in use", + type => 'boolean', + optional => 1, + default => 0, + }, + remove_storages => { + description => "Remove all pveceph-managed storages configured for this pool", + type => 'boolean', + optional => 1, + default => 0, + }, + remove_ecprofile => { + description => + "Remove the erasure code profile. Defaults to true, if applicable.", + type => 'boolean', + optional => 1, + default => 1, + }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); - $rpcenv->check($user, '/storage', ['Datastore.Allocate']) - if $param->{remove_storages}; + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); + $rpcenv->check($user, '/storage', ['Datastore.Allocate']) + if $param->{remove_storages}; - my $pool = $param->{name}; + my $pool = $param->{name}; - my $worker = sub { - my $storages = $get_storages->($pool); + my $worker = sub { + my $storages = $get_storages->($pool); - # if not forced, destroy ceph pool only when no - # vm disks are on it anymore - if (!$param->{force}) { - my $storagecfg = PVE::Storage::config(); - foreach my $storeid (keys %$storages) { - my $storage = $storages->{$storeid}; + # if not forced, destroy ceph pool only when no + # vm disks are on it anymore + if (!$param->{force}) { + my $storagecfg = PVE::Storage::config(); + foreach my $storeid (keys %$storages) { + my $storage = $storages->{$storeid}; - # check if any vm disks are on the pool - 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; - } - } - my $rados = PVE::RADOS->new(); + # check if any vm disks are on the pool + 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; + } + } + my $rados = PVE::RADOS->new(); - my $pool_properties = PVE::Ceph::Tools::get_pool_properties($pool, $rados); + my $pool_properties = PVE::Ceph::Tools::get_pool_properties($pool, $rados); - PVE::Ceph::Tools::destroy_pool($pool, $rados); + PVE::Ceph::Tools::destroy_pool($pool, $rados); - if (my $ecprofile = $pool_properties->{erasure_code_profile}) { - print "found erasure coded profile '$ecprofile', destroying its CRUSH rule\n"; - my $crush_rule = $pool_properties->{crush_rule}; - eval { PVE::Ceph::Tools::destroy_crush_rule($crush_rule, $rados); }; - warn "removing crush rule '${crush_rule}' failed: $@\n" if $@; + if (my $ecprofile = $pool_properties->{erasure_code_profile}) { + print "found erasure coded profile '$ecprofile', destroying its CRUSH rule\n"; + my $crush_rule = $pool_properties->{crush_rule}; + eval { PVE::Ceph::Tools::destroy_crush_rule($crush_rule, $rados); }; + warn "removing crush rule '${crush_rule}' failed: $@\n" if $@; - if ($param->{remove_ecprofile} // 1) { - print "destroying erasure coded profile '$ecprofile'\n"; - eval { PVE::Ceph::Tools::destroy_ecprofile($ecprofile, $rados) }; - warn "removing EC profile '${ecprofile}' failed: $@\n" if $@; - } - } + if ($param->{remove_ecprofile} // 1) { + print "destroying erasure coded profile '$ecprofile'\n"; + eval { PVE::Ceph::Tools::destroy_ecprofile($ecprofile, $rados) }; + warn "removing EC profile '${ecprofile}' failed: $@\n" if $@; + } + } - if ($param->{remove_storages}) { - my $err; - 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}) }; - if ($@) { - warn "failed to remove storage '$storeid': $@\n"; - $err = 1; - } - } - die "failed to remove (some) storages - check log and remove manually!\n" - if $err; - } - }; - return $rpcenv->fork_worker('cephdestroypool', $pool, $user, $worker); - }}); + if ($param->{remove_storages}) { + my $err; + 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 }) }; + if ($@) { + warn "failed to remove storage '$storeid': $@\n"; + $err = 1; + } + } + die "failed to remove (some) storages - check log and remove manually!\n" + if $err; + } + }; + return $rpcenv->fork_worker('cephdestroypool', $pool, $user, $worker); + }, +}); - -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'setpool', path => '{name}', method => 'PUT', @@ -620,78 +618,79 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - %{ $ceph_pool_common_options->('nodefault') }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + %{ $ceph_pool_common_options->('nodefault') }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_configured(); + PVE::Ceph::Tools::check_ceph_configured(); - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $pool = extract_param($param, 'name'); - my $node = extract_param($param, 'node'); + my $pool = extract_param($param, 'name'); + my $node = extract_param($param, 'node'); - # Ceph uses target_size_bytes - if (defined($param->{'target_size'})) { - my $target_sizestr = extract_param($param, 'target_size'); - $param->{target_size_bytes} = PVE::JSONSchema::parse_size($target_sizestr); - } + # Ceph uses target_size_bytes + if (defined($param->{'target_size'})) { + my $target_sizestr = extract_param($param, 'target_size'); + $param->{target_size_bytes} = PVE::JSONSchema::parse_size($target_sizestr); + } - my $worker = sub { - PVE::Ceph::Tools::set_pool($pool, $param); - }; + my $worker = sub { + PVE::Ceph::Tools::set_pool($pool, $param); + }; - return $rpcenv->fork_worker('cephsetpool', $pool, $authuser, $worker); - }}); + 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 => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - name => { - description => 'The name of the pool.', - type => 'string', - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + name => { + description => 'The name of the pool.', + type => 'string', + }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $result = [ - { name => 'status' }, - ]; + my $result = [ + { name => 'status' }, + ]; - return $result; - }}); + return $result; + }, +}); - -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'getpool', path => '{name}/status', method => 'GET', @@ -699,104 +698,105 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1], + check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - name => { - description => "The name of the pool. It must be unique.", - type => 'string', - }, - verbose => { - type => 'boolean', - default => 0, - optional => 1, - description => "If enabled, will display additional data". - "(eg. statistics).", - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + name => { + description => "The name of the pool. It must be unique.", + type => 'string', + }, + verbose => { + type => 'boolean', + default => 0, + optional => 1, + description => "If enabled, will display additional data" . "(eg. statistics).", + }, + }, }, returns => { - type => "object", - properties => { - id => { type => 'integer', title => 'ID' }, - pgp_num => { type => 'integer', title => 'PGP num' }, - noscrub => { type => 'boolean', title => 'noscrub' }, - 'nodeep-scrub' => { type => 'boolean', title => 'nodeep-scrub' }, - nodelete => { type => 'boolean', title => 'nodelete' }, - nopgchange => { type => 'boolean', title => 'nopgchange' }, - nosizechange => { type => 'boolean', title => 'nosizechange' }, - write_fadvise_dontneed => { type => 'boolean', title => 'write_fadvise_dontneed' }, - hashpspool => { type => 'boolean', title => 'hashpspool' }, - use_gmt_hitset => { type => 'boolean', title => 'use_gmt_hitset' }, - 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 }, - %{ $ceph_pool_common_options->() }, - }, + type => "object", + properties => { + id => { type => 'integer', title => 'ID' }, + pgp_num => { type => 'integer', title => 'PGP num' }, + noscrub => { type => 'boolean', title => 'noscrub' }, + 'nodeep-scrub' => { type => 'boolean', title => 'nodeep-scrub' }, + nodelete => { type => 'boolean', title => 'nodelete' }, + nopgchange => { type => 'boolean', title => 'nopgchange' }, + nosizechange => { type => 'boolean', title => 'nosizechange' }, + write_fadvise_dontneed => { type => 'boolean', title => 'write_fadvise_dontneed' }, + hashpspool => { type => 'boolean', title => 'hashpspool' }, + use_gmt_hitset => { type => 'boolean', title => 'use_gmt_hitset' }, + 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 }, + %{ $ceph_pool_common_options->() }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $verbose = $param->{verbose}; - my $pool = $param->{name}; + my $verbose = $param->{verbose}; + my $pool = $param->{name}; - my $rados = PVE::RADOS->new(); - my $res = $rados->mon_command({ - prefix => 'osd pool get', - pool => "$pool", - var => 'all', - }); + my $rados = PVE::RADOS->new(); + my $res = $rados->mon_command({ + prefix => 'osd pool get', + pool => "$pool", + var => 'all', + }); - my $data = { - id => $res->{pool_id}, - name => $pool, - size => $res->{size}, - min_size => $res->{min_size}, - pg_num => $res->{pg_num}, - pg_num_min => $res->{pg_num_min}, - pgp_num => $res->{pgp_num}, - crush_rule => $res->{crush_rule}, - pg_autoscale_mode => $res->{pg_autoscale_mode}, - noscrub => "$res->{noscrub}", - 'nodeep-scrub' => "$res->{'nodeep-scrub'}", - nodelete => "$res->{nodelete}", - nopgchange => "$res->{nopgchange}", - nosizechange => "$res->{nosizechange}", - write_fadvise_dontneed => "$res->{write_fadvise_dontneed}", - hashpspool => "$res->{hashpspool}", - use_gmt_hitset => "$res->{use_gmt_hitset}", - fast_read => "$res->{fast_read}", - target_size => $res->{target_size_bytes}, - target_size_ratio => $res->{target_size_ratio}, - }; + my $data = { + id => $res->{pool_id}, + name => $pool, + size => $res->{size}, + min_size => $res->{min_size}, + pg_num => $res->{pg_num}, + pg_num_min => $res->{pg_num_min}, + pgp_num => $res->{pgp_num}, + crush_rule => $res->{crush_rule}, + pg_autoscale_mode => $res->{pg_autoscale_mode}, + noscrub => "$res->{noscrub}", + 'nodeep-scrub' => "$res->{'nodeep-scrub'}", + nodelete => "$res->{nodelete}", + nopgchange => "$res->{nopgchange}", + nosizechange => "$res->{nosizechange}", + write_fadvise_dontneed => "$res->{write_fadvise_dontneed}", + hashpspool => "$res->{hashpspool}", + use_gmt_hitset => "$res->{use_gmt_hitset}", + fast_read => "$res->{fast_read}", + target_size => $res->{target_size_bytes}, + target_size_ratio => $res->{target_size_ratio}, + }; - if ($verbose) { - my $stats; - my $res = $rados->mon_command({ prefix => 'df' }); + if ($verbose) { + my $stats; + my $res = $rados->mon_command({ prefix => 'df' }); - # pg_autoscaler module is not enabled in Nautilus - # avoid partial read further down, use new rados instance - my $autoscale_status = eval { $get_autoscale_status->() }; - $data->{autoscale_status} = $autoscale_status->{$pool}; + # pg_autoscaler module is not enabled in Nautilus + # avoid partial read further down, use new rados instance + my $autoscale_status = eval { $get_autoscale_status->() }; + $data->{autoscale_status} = $autoscale_status->{$pool}; - foreach my $d (@{$res->{pools}}) { - next if !$d->{stats}; - next if !defined($d->{name}) && !$d->{name} ne "$pool"; - $data->{statistics} = $d->{stats}; - } + 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 ]; - } - - return $data; - }}); + my $apps = + $rados->mon_command({ prefix => "osd pool application get", pool => "$pool" }); + $data->{application_list} = [keys %$apps]; + } + return $data; + }, +}); 1; diff --git a/PVE/API2/Certificates.pm b/PVE/API2/Certificates.pm index d22e203e..de8762c5 100644 --- a/PVE/API2/Certificates.pm +++ b/PVE/API2/Certificates.pm @@ -5,51 +5,48 @@ use warnings; use PVE::API2::ACME; use PVE::Certificate; -use PVE::CertHelpers;; +use PVE::CertHelpers; use PVE::Exception qw(raise_param_exc); use PVE::JSONSchema qw(get_standard_option); use PVE::Tools qw(extract_param file_get_contents file_set_contents); use base qw(PVE::RESTHandler); - -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::ACME", path => 'acme', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', permissions => { user => 'all' }, description => "Node index.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - return [ - { name => 'acme' }, - { name => 'custom' }, - { name => 'info' }, - ]; + return [ + { name => 'acme' }, { name => 'custom' }, { name => 'info' }, + ]; }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'info', path => 'info', method => 'GET', @@ -57,157 +54,159 @@ __PACKAGE__->register_method ({ proxyto => 'node', description => "Get information about node's certificates.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => get_standard_option('pve-certificate-info'), + type => 'array', + items => get_standard_option('pve-certificate-info'), }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $node_path = "/etc/pve/nodes/$param->{node}"; + my $node_path = "/etc/pve/nodes/$param->{node}"; - my $res = []; - my $cert_paths = [ - '/etc/pve/pve-root-ca.pem', - "$node_path/pve-ssl.pem", - "$node_path/pveproxy-ssl.pem", - ]; - for my $path (@$cert_paths) { - eval { - my $info = PVE::Certificate::get_certificate_info($path); - push @$res, $info if $info; - }; - } - return $res; + my $res = []; + my $cert_paths = [ + '/etc/pve/pve-root-ca.pem', "$node_path/pve-ssl.pem", "$node_path/pveproxy-ssl.pem", + ]; + for my $path (@$cert_paths) { + eval { + my $info = PVE::Certificate::get_certificate_info($path); + push @$res, $info if $info; + }; + } + return $res; }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'upload_custom_cert', path => 'custom', method => 'POST', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, description => 'Upload or update custom certificate chain and key.', protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - certificates => { - type => 'string', - format => 'pem-certificate-chain', - description => 'PEM encoded certificate (chain).', - }, - key => { - type => 'string', - description => 'PEM encoded private key.', - format => 'pem-string', - optional => 1, - }, - force => { - type => 'boolean', - description => 'Overwrite existing custom or ACME certificate files.', - optional => 1, - default => 0, - }, - restart => { - type => 'boolean', - description => 'Restart pveproxy.', - optional => 1, - default => 0, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + certificates => { + type => 'string', + format => 'pem-certificate-chain', + description => 'PEM encoded certificate (chain).', + }, + key => { + type => 'string', + description => 'PEM encoded private key.', + format => 'pem-string', + optional => 1, + }, + force => { + type => 'boolean', + description => 'Overwrite existing custom or ACME certificate files.', + optional => 1, + default => 0, + }, + restart => { + type => 'boolean', + description => 'Restart pveproxy.', + optional => 1, + default => 0, + }, + }, }, returns => get_standard_option('pve-certificate-info'), code => sub { - my ($param) = @_; + my ($param) = @_; - my $node = extract_param($param, 'node'); - my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node); + my $node = extract_param($param, 'node'); + my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node); - my $certs = extract_param($param, 'certificates'); - $certs = PVE::Certificate::strip_leading_text($certs); + my $certs = extract_param($param, 'certificates'); + $certs = PVE::Certificate::strip_leading_text($certs); - my $key = extract_param($param, 'key'); - if ($key) { - $key = PVE::Certificate::strip_leading_text($key); - } else { - raise_param_exc({'key' => "Attempted to upload custom certificate without (existing) key."}) - if ! -e "${cert_prefix}.key"; - } + my $key = extract_param($param, 'key'); + if ($key) { + $key = PVE::Certificate::strip_leading_text($key); + } else { + raise_param_exc( + { 'key' => "Attempted to upload custom certificate without (existing) key." }) + if !-e "${cert_prefix}.key"; + } - my $info; + my $info; - my $code = sub { - print "Setting custom certificate files\n"; - $info = PVE::CertHelpers::set_cert_files($certs, $key, $cert_prefix, $param->{force}); + my $code = sub { + print "Setting custom certificate files\n"; + $info = + PVE::CertHelpers::set_cert_files($certs, $key, $cert_prefix, $param->{force}); - if ($param->{restart}) { - print "Restarting pveproxy\n"; - PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']); - } - }; + if ($param->{restart}) { + print "Restarting pveproxy\n"; + PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']); + } + }; - PVE::CertHelpers::cert_lock(10, $code); - die "$@\n" if $@; + PVE::CertHelpers::cert_lock(10, $code); + die "$@\n" if $@; - return $info; - }}); + return $info; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'remove_custom_cert', path => 'custom', method => 'DELETE', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, description => 'DELETE custom certificate chain and key.', protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - restart => { - type => 'boolean', - description => 'Restart pveproxy.', - optional => 1, - default => 0, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + restart => { + type => 'boolean', + description => 'Restart pveproxy.', + optional => 1, + default => 0, + }, + }, }, returns => { - type => 'null', + type => 'null', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $node = extract_param($param, 'node'); - my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node); + my $node = extract_param($param, 'node'); + my $cert_prefix = PVE::CertHelpers::cert_path_prefix($node); - my $code = sub { - print "Deleting custom certificate files\n"; - unlink "${cert_prefix}.pem"; - unlink "${cert_prefix}.key"; + my $code = sub { + print "Deleting custom certificate files\n"; + unlink "${cert_prefix}.pem"; + unlink "${cert_prefix}.key"; - if ($param->{restart}) { - print "Restarting pveproxy\n"; - PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']); - } - }; + if ($param->{restart}) { + print "Restarting pveproxy\n"; + PVE::Tools::run_command(['systemctl', 'reload-or-restart', 'pveproxy']); + } + }; - PVE::CertHelpers::cert_lock(10, $code); - die "$@\n" if $@; + PVE::CertHelpers::cert_lock(10, $code); + die "$@\n" if $@; - return undef; - }}); + return undef; + }, +}); 1; diff --git a/PVE/API2/Cluster.pm b/PVE/API2/Cluster.pm index a0e5c11b..a025d264 100644 --- a/PVE/API2/Cluster.pm +++ b/PVE/API2/Cluster.pm @@ -43,133 +43,135 @@ 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 ({ - subclass => "PVE::API2::Network::SDN", - path => 'sdn', + __PACKAGE__->register_method({ + subclass => "PVE::API2::Network::SDN", + path => 'sdn', }); } my $dc_schema = PVE::DataCenterConfig::get_datacenter_schema(); my $dc_properties = { delete => { - type => 'string', format => 'pve-configid-list', - description => "A list of settings you want to delete.", - optional => 1, - } + type => 'string', + format => 'pve-configid-list', + description => "A list of settings you want to delete.", + optional => 1, + }, }; -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', description => "Cluster index.", permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $result = [ - { name => 'acme' }, - { name => 'backup' }, - { name => 'backup-info' }, - { name => 'ceph' }, - { name => 'config' }, - { name => 'firewall' }, - { name => 'ha' }, - { name => 'jobs' }, - { name => 'log' }, - { name => 'mapping' }, - { name => 'metrics' }, - { name => 'notifications' }, - { name => 'nextid' }, - { name => 'options' }, - { name => 'replication' }, - { name => 'resources' }, - { name => 'status' }, - { name => 'tasks' }, - ]; + my $result = [ + { name => 'acme' }, + { name => 'backup' }, + { name => 'backup-info' }, + { name => 'ceph' }, + { name => 'config' }, + { name => 'firewall' }, + { name => 'ha' }, + { name => 'jobs' }, + { name => 'log' }, + { name => 'mapping' }, + { name => 'metrics' }, + { name => 'notifications' }, + { name => 'nextid' }, + { name => 'options' }, + { name => 'replication' }, + { name => 'resources' }, + { name => 'status' }, + { name => 'tasks' }, + ]; - if ($have_sdn) { - push(@{$result}, { name => 'sdn' }); - } + if ($have_sdn) { + push(@{$result}, { name => 'sdn' }); + } - return $result; - }}); + return $result; + }, +}); __PACKAGE__->register_method({ name => 'log', @@ -178,43 +180,44 @@ __PACKAGE__->register_method({ description => "Read cluster log", permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => { - max => { - type => 'integer', - description => "Maximum number of entries.", - optional => 1, - minimum => 1, - } - }, + additionalProperties => 0, + properties => { + max => { + type => 'integer', + description => "Maximum number of entries.", + optional => 1, + minimum => 1, + }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, + type => 'array', + items => { + type => "object", + properties => {}, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $max = $param->{max} || 0; - my $user = $rpcenv->get_user(); + 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 $loguser = $admin ? '' : $user; - my $res = decode_json(PVE::Cluster::get_cluster_log($loguser, $max)); + my $res = decode_json(PVE::Cluster::get_cluster_log($loguser, $max)); - foreach my $entry (@{$res->{data}}) { - $entry->{id} = "$entry->{uid}:$entry->{node}"; - } + foreach my $entry (@{ $res->{data} }) { + $entry->{id} = "$entry->{uid}:$entry->{node}"; + } - return $res->{data}; - }}); + return $res->{data}; + }, +}); __PACKAGE__->register_method({ name => 'resources', @@ -223,365 +226,387 @@ __PACKAGE__->register_method({ description => "Resources index (cluster wide).", permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => { - type => { - type => 'string', - description => 'Resource type.', - optional => 1, - enum => ['vm', 'storage', 'node', 'sdn'], - }, - }, + additionalProperties => 0, + properties => { + type => { + type => 'string', + description => 'Resource type.', + optional => 1, + enum => ['vm', 'storage', 'node', 'sdn'], + }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - id => { - description => "Resource id.", - type => 'string', - }, - type => { - description => "Resource type.", - type => 'string', - enum => ['node', 'storage', 'pool', 'qemu', 'lxc', 'openvz', 'sdn'], - }, - status => { - description => "Resource type dependent status.", - type => 'string', - optional => 1, - }, - name => { - description => "Name of the resource.", - type => 'string', - optional => 1, - }, - node => get_standard_option('pve-node', { - description => "The cluster node name" - ." (for types 'node', 'storage', 'qemu', and 'lxc').", - optional => 1, - }), - 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', - optional => 1, - }, - cpu => { - description => "CPU utilization (for types 'node', 'qemu' and 'lxc').", - type => 'number', - optional => 1, - minimum => 0, - renderer => 'fraction_as_percentage', - }, - maxcpu => { - description => "Number of available CPUs (for types 'node', 'qemu' and 'lxc').", - type => 'number', - optional => 1, - minimum => 0, - }, - mem => { - description => "Used memory in bytes (for types 'node', 'qemu' and 'lxc').", - type => 'integer', - optional => 1, - renderer => 'bytes', - minimum => 0, - }, - maxmem => { - description => "Number of available memory in bytes" - ." (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')", - 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')", - type => 'integer', - optional => 1, - renderer => 'bytes', - }, - level => { - description => "Support level (for type 'node').", - type => 'string', - optional => 1, - }, - lock => { - 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').", - type => 'integer', - optional => 1, - renderer => 'duration', - }, - hastate => { - description => "HA service status (for HA managed VMs).", - type => 'string', - optional => 1, - }, - disk => { - description => "Used disk space in bytes (for type 'storage')," - ." used root image space for VMs (for types 'qemu' and 'lxc').", - type => 'integer', - optional => 1, - renderer => 'bytes', - minimum => 0, - }, - maxdisk => { - description => "Storage size in bytes (for type 'storage')," - ." 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')", - 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')", - type => 'integer', - optional => 1, - renderer => 'bytes', - }, - content => { - description => "Allowed storage content types (for type 'storage').", - type => 'string', - format => 'pve-storage-content-list', - optional => 1, - }, - plugintype => { - description => "More specific type, if available.", - type => 'string', - optional => 1, - }, - 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', - optional => 1, - }, - tags => { - description => "The guest's tags (for types 'qemu' and 'lxc')", - type => "string", - optional => 1, - }, - template => { - description => "Determines if the guest is a template." - ." (for types 'qemu' and 'lxc')", - type => 'boolean', - optional => 1, - default => 0, - }, - }, - }, + type => 'array', + items => { + type => "object", + properties => { + id => { + description => "Resource id.", + type => 'string', + }, + type => { + description => "Resource type.", + type => 'string', + enum => ['node', 'storage', 'pool', 'qemu', 'lxc', 'openvz', 'sdn'], + }, + status => { + description => "Resource type dependent status.", + type => 'string', + optional => 1, + }, + name => { + description => "Name of the resource.", + type => 'string', + optional => 1, + }, + node => get_standard_option( + 'pve-node', + { + description => "The cluster node name" + . " (for types 'node', 'storage', 'qemu', and 'lxc').", + optional => 1, + }, + ), + 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', + optional => 1, + }, + cpu => { + description => "CPU utilization (for types 'node', 'qemu' and 'lxc').", + type => 'number', + optional => 1, + minimum => 0, + renderer => 'fraction_as_percentage', + }, + maxcpu => { + description => + "Number of available CPUs (for types 'node', 'qemu' and 'lxc').", + type => 'number', + optional => 1, + minimum => 0, + }, + mem => { + description => "Used memory in bytes (for types 'node', 'qemu' and 'lxc').", + type => 'integer', + optional => 1, + renderer => 'bytes', + minimum => 0, + }, + maxmem => { + description => "Number of available memory in bytes" + . " (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')", + 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')", + type => 'integer', + optional => 1, + renderer => 'bytes', + }, + level => { + description => "Support level (for type 'node').", + type => 'string', + optional => 1, + }, + lock => { + 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').", + type => 'integer', + optional => 1, + renderer => 'duration', + }, + hastate => { + description => "HA service status (for HA managed VMs).", + type => 'string', + optional => 1, + }, + disk => { + description => "Used disk space in bytes (for type 'storage')," + . " used root image space for VMs (for types 'qemu' and 'lxc').", + type => 'integer', + optional => 1, + renderer => 'bytes', + minimum => 0, + }, + maxdisk => { + description => "Storage size in bytes (for type 'storage')," + . " 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')", + 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')", + type => 'integer', + optional => 1, + renderer => 'bytes', + }, + content => { + description => "Allowed storage content types (for type 'storage').", + type => 'string', + format => 'pve-storage-content-list', + optional => 1, + }, + plugintype => { + description => "More specific type, if available.", + type => 'string', + optional => 1, + }, + 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', + optional => 1, + }, + tags => { + description => "The guest's tags (for types 'qemu' and 'lxc')", + type => "string", + optional => 1, + }, + template => { + description => "Determines if the guest is a template." + . " (for types 'qemu' and 'lxc')", + type => 'boolean', + optional => 1, + default => 0, + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); - my $usercfg = $rpcenv->{user_cfg}; + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + my $usercfg = $rpcenv->{user_cfg}; - my $res = []; + my $res = []; - my $nodelist = PVE::Cluster::get_nodelist(); - my $members = PVE::Cluster::get_members(); + my $nodelist = PVE::Cluster::get_nodelist(); + my $members = PVE::Cluster::get_members(); - my $rrd = PVE::Cluster::rrd_dump(); + my $rrd = PVE::Cluster::rrd_dump(); - my $vmlist = PVE::Cluster::get_vmlist() || {}; - my $idlist = $vmlist->{ids} || {}; + my $vmlist = PVE::Cluster::get_vmlist() || {}; + my $idlist = $vmlist->{ids} || {}; - my $hastatus = PVE::HA::Config::read_manager_status(); - my $haresources = PVE::HA::Config::read_resources_config(); - my $hatypemap = { - 'qemu' => 'vm', - 'lxc' => 'ct' - }; + my $hastatus = PVE::HA::Config::read_manager_status(); + my $haresources = PVE::HA::Config::read_resources_config(); + my $hatypemap = { + 'qemu' => 'vm', + 'lxc' => 'ct', + }; - my $pooldata = {}; - if (!$param->{type} || $param->{type} eq 'pool') { - for my $pool (sort keys %{$usercfg->{pools}}) { - my $d = $usercfg->{pools}->{$pool}; + my $pooldata = {}; + if (!$param->{type} || $param->{type} eq 'pool') { + 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", - pool => $pool, - type => 'pool', - }; + my $entry = { + id => "/pool/$pool", + pool => $pool, + type => 'pool', + }; - $pooldata->{$pool} = $entry; + $pooldata->{$pool} = $entry; - push @$res, $entry; - } - } + push @$res, $entry; + } + } - # we try to generate 'numbers' by using "$X + 0" - if (!$param->{type} || $param->{type} eq 'vm') { - my $prop_list = [qw(lock tags)]; - my $props = PVE::Cluster::get_guest_config_properties($prop_list); + # we try to generate 'numbers' by using "$X + 0" + if (!$param->{type} || $param->{type} eq 'vm') { + my $prop_list = [qw(lock tags)]; + my $props = PVE::Cluster::get_guest_config_properties($prop_list); - for my $vmid (sort keys %$idlist) { + for my $vmid (sort keys %$idlist) { - my $data = $idlist->{$vmid}; - my $entry = PVE::API2Tools::extract_vm_stats($vmid, $data, $rrd); + my $data = $idlist->{$vmid}; + my $entry = PVE::API2Tools::extract_vm_stats($vmid, $data, $rrd); - if (my $pool = $usercfg->{vms}->{$vmid}) { - $entry->{pool} = $pool; - if (my $pe = $pooldata->{$pool}) { - if ($entry->{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}; - $pe->{maxmem} += $entry->{maxmem}; - $pe->{cpu} = 0 if !$pe->{cpu}; - $pe->{maxcpu} = 0 if !$pe->{maxcpu}; - # explanation: - # we do not know how much cpus there are in the cluster at this moment - # 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->{maxcpu} += $entry->{maxcpu}; - } - } - } + if (my $pool = $usercfg->{vms}->{$vmid}) { + $entry->{pool} = $pool; + if (my $pe = $pooldata->{$pool}) { + if ($entry->{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}; + $pe->{maxmem} += $entry->{maxmem}; + $pe->{cpu} = 0 if !$pe->{cpu}; + $pe->{maxcpu} = 0 if !$pe->{maxcpu}; + # explanation: + # we do not know how much cpus there are in the cluster at this moment + # 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->{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); + # 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); - for my $prop (@$prop_list) { - if (defined(my $value = $props->{$vmid}->{$prop})) { - $entry->{$prop} = $value; - } - } + for my $prop (@$prop_list) { + if (defined(my $value = $props->{$vmid}->{$prop})) { + $entry->{$prop} = $value; + } + } - if (defined($entry->{pool}) && - !$rpcenv->check($authuser, "/pool/$entry->{pool}", ['Pool.Audit'], 1)) { - delete $entry->{pool}; - } + 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}}) { - my $sid = "$hatype:$vmid"; - my $service; - if ($service = $hastatus->{service_status}->{$sid}) { - $entry->{hastate} = $service->{state}; - } elsif ($service = $haresources->{ids}->{$sid}) { - $entry->{hastate} = $service->{state}; - } - } + # get ha status + if (my $hatype = $hatypemap->{ $entry->{type} }) { + my $sid = "$hatype:$vmid"; + my $service; + if ($service = $hastatus->{service_status}->{$sid}) { + $entry->{hastate} = $service->{state}; + } elsif ($service = $haresources->{ids}->{$sid}) { + $entry->{hastate} = $service->{state}; + } + } - push @$res, $entry; - } - } + push @$res, $entry; + } + } - my $static_node_info = PVE::Cluster::get_node_kv("static-info"); + my $static_node_info = PVE::Cluster::get_node_kv("static-info"); - 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); + 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 $info = eval { decode_json($static_node_info->{$node}); }; - if (defined(my $mode = $info->{'cgroup-mode'})) { - $entry->{'cgroup-mode'} = int($mode); - } - if (defined(my $status = $hastatus->{node_status}->{$node})) { - $entry->{'hastate'} = $status; - } + my $info = eval { decode_json($static_node_info->{$node}); }; + if (defined(my $mode = $info->{'cgroup-mode'})) { + $entry->{'cgroup-mode'} = int($mode); + } + if (defined(my $status = $hastatus->{node_status}->{$node})) { + $entry->{'hastate'} = $status; + } - push @$res, $entry; - } - } + push @$res, $entry; + } + } - if (!$param->{type} || $param->{type} eq 'storage') { + if (!$param->{type} || $param->{type} eq 'storage') { - my $cfg = PVE::Storage::config(); - my @sids = PVE::Storage::storage_ids ($cfg); + my $cfg = PVE::Storage::config(); + my @sids = PVE::Storage::storage_ids($cfg); - foreach my $storeid (@sids) { - next if !$rpcenv->check($authuser, "/storage/$storeid", [ 'Datastore.Audit' ], 1); + foreach my $storeid (@sids) { + 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 $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); - push @$res, $entry; - } - } - } + my $entry = + PVE::API2Tools::extract_storage_stats($storeid, $scfg, $node, $rrd); + push @$res, $entry; + } + } + } - if (!$param->{type} || $param->{type} eq 'sdn') { - #add default "localnetwork" zone - if ($rpcenv->check($authuser, "/sdn/zones/localnetwork", [ 'SDN.Audit' ], 1)) { - foreach my $node (@$nodelist) { - my $local_sdn = { - id => "sdn/$node/localnetwork", - sdn => 'localnetwork', - node => $node, - type => 'sdn', - status => 'ok', - }; - push @$res, $local_sdn; - } - } + if (!$param->{type} || $param->{type} eq 'sdn') { + #add default "localnetwork" zone + if ($rpcenv->check($authuser, "/sdn/zones/localnetwork", ['SDN.Audit'], 1)) { + foreach my $node (@$nodelist) { + my $local_sdn = { + id => "sdn/$node/localnetwork", + sdn => 'localnetwork', + node => $node, + type => 'sdn', + status => 'ok', + }; + push @$res, $local_sdn; + } + } - if ($have_sdn) { - my $nodes = PVE::Cluster::get_node_kv("sdn"); + if ($have_sdn) { + my $nodes = PVE::Cluster::get_node_kv("sdn"); - for my $node (sort keys %{$nodes}) { - my $sdns = decode_json($nodes->{$node}); + for my $node (sort keys %{$nodes}) { + my $sdns = decode_json($nodes->{$node}); - for my $id (sort keys %{$sdns}) { - next if !$rpcenv->check($authuser, "/sdn/zones/$id", [ 'SDN.Audit' ], 1); - my $sdn = $sdns->{$id}; - my $entry = { - id => "sdn/$node/$id", - sdn => $id, - node => $node, - type => 'sdn', - status => $sdn->{'status'}, - }; - push @$res, $entry; - } - } - } - } + for my $id (sort keys %{$sdns}) { + next if !$rpcenv->check($authuser, "/sdn/zones/$id", ['SDN.Audit'], 1); + my $sdn = $sdns->{$id}; + my $entry = { + id => "sdn/$node/$id", + sdn => $id, + node => $node, + type => 'sdn', + status => $sdn->{'status'}, + }; + push @$res, $entry; + } + } + } + } - return $res; - }}); + return $res; + }, +}); __PACKAGE__->register_method({ name => 'tasks', @@ -590,80 +615,84 @@ __PACKAGE__->register_method({ description => "List recent tasks (cluster wide).", permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - upid => { type => 'string' }, - }, - }, + type => 'array', + items => { + type => "object", + properties => { + upid => { type => 'string' }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $tlist = PVE::Cluster::get_tasklist(); - return [] if !$tlist; + 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}); - } - push @$res, $task if $all || ($task->{user} eq $authuser); - } + 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}); + } + push @$res, $task if $all || ($task->{user} eq $authuser); + } - return $res; - }}); + 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' ]], + user => 'all', + check => ['perm', '/', ['Sys.Audit']], }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => "object", - properties => {}, + type => "object", + properties => {}, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $res = {}; + my $res = {}; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $datacenter_config = eval { PVE::Cluster::cfs_read_file('datacenter.cfg') } // {}; + my $datacenter_config = eval { PVE::Cluster::cfs_read_file('datacenter.cfg') } // {}; - if ($rpcenv->check($authuser, '/', ['Sys.Audit'], 1)) { - $res = $datacenter_config; - } else { - for my $k (qw(console tag-style)) { - $res->{$k} = $datacenter_config->{$k} if exists $datacenter_config->{$k}; - } - } + if ($rpcenv->check($authuser, '/', ['Sys.Audit'], 1)) { + $res = $datacenter_config; + } else { + for my $k (qw(console tag-style)) { + $res->{$k} = $datacenter_config->{$k} if exists $datacenter_config->{$k}; + } + } - my $tags = PVE::GuestHelpers::get_allowed_tags($rpcenv, $authuser); - $res->{'allowed-tags'} = [sort keys $tags->%*]; + my $tags = PVE::GuestHelpers::get_allowed_tags($rpcenv, $authuser); + $res->{'allowed-tags'} = [sort keys $tags->%*]; - return $res; - }}); + return $res; + }, +}); __PACKAGE__->register_method({ name => 'set_options', @@ -671,32 +700,37 @@ __PACKAGE__->register_method({ method => 'PUT', description => "Set datacenter options.", permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, protected => 1, parameters => { - additionalProperties => 0, - properties => $dc_properties, + additionalProperties => 0, + properties => $dc_properties, }, returns => { type => "null" }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $delete = extract_param($param, 'delete'); + my $delete = extract_param($param, 'delete'); - cfs_lock_file('datacenter.cfg', undef, sub { - my $conf = cfs_read_file('datacenter.cfg'); + cfs_lock_file( + 'datacenter.cfg', + undef, + sub { + my $conf = cfs_read_file('datacenter.cfg'); - $conf->{$_} = $param->{$_} for keys $param->%*; + $conf->{$_} = $param->{$_} for keys $param->%*; - delete $conf->{$_} for PVE::Tools::split_list($delete); + delete $conf->{$_} for PVE::Tools::split_list($delete); - cfs_write_file('datacenter.cfg', $conf); - }); - die $@ if $@; + cfs_write_file('datacenter.cfg', $conf); + }, + ); + die $@ if $@; - return undef; - }}); + return undef; + }, +}); __PACKAGE__->register_method({ name => 'get_status', @@ -704,180 +738,188 @@ __PACKAGE__->register_method({ method => 'GET', description => "Get cluster status information.", permissions => { - check => ['perm', '/', [ 'Sys.Audit' ]], + check => ['perm', '/', ['Sys.Audit']], }, protected => 1, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - 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.' - }, - id => { - type => 'string', - }, - name => { - type => 'string', - }, - nodes => { - type => 'integer', - optional => 1, - description => '[cluster] Nodes count, including offline nodes.', - }, - version => { - type => 'integer', - optional => 1, - 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', - }, - nodeid => { - type => 'integer', - optional => 1, - description => '[node] ID of the node from the corosync configuration.', - }, - ip => { - type => 'string', - optional => 1, - description => '[node] IP of the resolved nodename.', - }, - 'local' => { - type => 'boolean', - optional => 1, - description => '[node] Indicates if this is the responding node.', - }, - online => { - type => 'boolean', - optional => 1, - description => '[node] Indicates if the node is online or offline.', - }, - 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.', - } - }, - }, + type => 'array', + items => { + type => "object", + properties => { + 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.', + }, + id => { + type => 'string', + }, + name => { + type => 'string', + }, + nodes => { + type => 'integer', + optional => 1, + description => '[cluster] Nodes count, including offline nodes.', + }, + version => { + type => 'integer', + optional => 1, + 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', + }, + nodeid => { + type => 'integer', + optional => 1, + description => '[node] ID of the node from the corosync configuration.', + }, + ip => { + type => 'string', + optional => 1, + description => '[node] IP of the resolved nodename.', + }, + 'local' => { + type => 'boolean', + optional => 1, + description => '[node] Indicates if this is the responding node.', + }, + online => { + type => 'boolean', + optional => 1, + description => '[node] Indicates if the node is online or offline.', + }, + 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.', + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - # make sure we get current info - PVE::Cluster::cfs_update(); + # make sure we get current info + PVE::Cluster::cfs_update(); - # we also add info from pmxcfs - my $clinfo = PVE::Cluster::get_clinfo(); - my $members = PVE::Cluster::get_members(); - my $nodename = PVE::INotify::nodename(); - my $rrd = PVE::Cluster::rrd_dump(); + # we also add info from pmxcfs + my $clinfo = PVE::Cluster::get_clinfo(); + my $members = PVE::Cluster::get_members(); + my $nodename = PVE::INotify::nodename(); + my $rrd = PVE::Cluster::rrd_dump(); - if ($members) { - my $res = []; + if ($members) { + my $res = []; - if (my $d = $clinfo->{cluster}) { - push @$res, { - type => 'cluster', - id => 'cluster', - nodes => $d->{nodes}, - version => $d->{version}, - name => $d->{name}, - quorate => $d->{quorate}, - }; - } + if (my $d = $clinfo->{cluster}) { + push @$res, + { + type => 'cluster', + id => 'cluster', + nodes => $d->{nodes}, + version => $d->{version}, + name => $d->{name}, + quorate => $d->{quorate}, + }; + } - foreach my $node (keys %$members) { - my $d = $members->{$node}; - my $entry = { - type => 'node', - id => "node/$node", - name => $node, - nodeid => $d->{id}, - 'local' => ($node eq $nodename) ? 1 : 0, - online => $d->{online}, - }; + foreach my $node (keys %$members) { + my $d = $members->{$node}; + my $entry = { + type => 'node', + id => "node/$node", + name => $node, + nodeid => $d->{id}, + 'local' => ($node eq $nodename) ? 1 : 0, + online => $d->{online}, + }; - if (defined($d->{ip})) { - $entry->{ip} = $d->{ip}; - } + if (defined($d->{ip})) { + $entry->{ip} = $d->{ip}; + } - if (my $d = PVE::API2Tools::extract_node_stats($node, $members, $rrd)) { - $entry->{level} = $d->{level} || ''; - } + if (my $d = PVE::API2Tools::extract_node_stats($node, $members, $rrd)) { + $entry->{level} = $d->{level} || ''; + } - push @$res, $entry; - } - return $res; - } else { - # fake entry for local node if no cluster defined - my $pmxcfs = ($clinfo && $clinfo->{version}) ? 1 : 0; # pmxcfs online ? + push @$res, $entry; + } + return $res; + } else { + # fake entry for local node if no cluster defined + my $pmxcfs = ($clinfo && $clinfo->{version}) ? 1 : 0; # pmxcfs online ? - my $subinfo = PVE::API2::Subscription::read_etc_subscription(); - my $sublevel = $subinfo->{level} || ''; + my $subinfo = PVE::API2::Subscription::read_etc_subscription(); + my $sublevel = $subinfo->{level} || ''; - return [{ - type => 'node', - id => "node/$nodename", - name => $nodename, - ip => scalar(PVE::Cluster::remote_node_ip($nodename)), - 'local' => 1, - nodeid => 0, - online => 1, - level => $sublevel, - }]; - } - }}); + return [{ + type => 'node', + id => "node/$nodename", + name => $nodename, + ip => scalar(PVE::Cluster::remote_node_ip($nodename)), + 'local' => 1, + nodeid => 0, + online => 1, + 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, - properties => { - vmid => get_standard_option('pve-vmid', { - optional => 1, - }), - }, + additionalProperties => 0, + properties => { + vmid => get_standard_option('pve-vmid', { + optional => 1, + }), + }, }, returns => { - type => 'integer', - description => "The next free VMID.", + type => 'integer', + description => "The next free VMID.", }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $vmlist = PVE::Cluster::get_vmlist() || {}; - my $idlist = $vmlist->{ids} || {}; + my $vmlist = PVE::Cluster::get_vmlist() || {}; + my $idlist = $vmlist->{ids} || {}; - if (my $vmid = $param->{vmid}) { - return $vmid if !defined($idlist->{$vmid}); - raise_param_exc({ vmid => "VM $vmid already exists" }); - } + if (my $vmid = $param->{vmid}) { + return $vmid if !defined($idlist->{$vmid}); + raise_param_exc({ vmid => "VM $vmid already exists" }); + } - my $dc_conf = PVE::Cluster::cfs_read_file('datacenter.cfg'); - my $next_id = $dc_conf->{'next-id'} // {}; + my $dc_conf = PVE::Cluster::cfs_read_file('datacenter.cfg'); + my $next_id = $dc_conf->{'next-id'} // {}; - my $lower = $next_id->{lower} // 100; - my $upper = $next_id->{upper} // (1000 * 1000); # note, lower than the schema-maximum + my $lower = $next_id->{lower} // 100; + my $upper = $next_id->{upper} // (1000 * 1000); # note, lower than the schema-maximum - for (my $i = $lower; $i < $upper; $i++) { - return $i if !defined($idlist->{$i}); - } + for (my $i = $lower; $i < $upper; $i++) { + return $i if !defined($idlist->{$i}); + } - die "unable to get any free VMID in range [$lower, $upper]\n"; - }}); + die "unable to get any free VMID in range [$lower, $upper]\n"; + }, +}); 1; diff --git a/PVE/API2/Cluster/BackupInfo.pm b/PVE/API2/Cluster/BackupInfo.pm index e17a2efe..4ef59ac3 100644 --- a/PVE/API2/Cluster/BackupInfo.pm +++ b/PVE/API2/Cluster/BackupInfo.pm @@ -25,8 +25,8 @@ sub get_included_vmids { my $all_vmids = {}; for my $job ($legacy_jobs->@*, grep { $_->{type} eq 'vzdump' } values $jobs->{ids}->%*) { - my $job_included_guests = PVE::VZDump::get_included_guests($job); - $all_vmids->{$_} = 1 for map { $_->@* } values %{$job_included_guests}; + my $job_included_guests = PVE::VZDump::get_included_guests($job); + $all_vmids->{$_} = 1 for map { $_->@* } values %{$job_included_guests}; } return $all_vmids; @@ -38,28 +38,29 @@ __PACKAGE__->register_method({ method => 'GET', description => "Index for backup info related endpoints", parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - description => 'Directory index.', - items => { - type => "object", - properties => { - subdir => { - type => 'string', - description => 'API sub-directory endpoint', - }, - }, - }, - links => [ { rel => 'child', href => "{subdir}" } ], + type => 'array', + description => 'Directory index.', + items => { + type => "object", + properties => { + subdir => { + type => 'string', + description => 'API sub-directory endpoint', + }, + }, + }, + links => [{ rel => 'child', href => "{subdir}" }], }, code => sub { - return [ - { subdir => 'not-backed-up' }, - ]; - }}); + return [ + { subdir => 'not-backed-up' }, + ]; + }, +}); __PACKAGE__->register_method({ name => 'get_guests_not_in_backup', @@ -68,72 +69,75 @@ __PACKAGE__->register_method({ protected => 1, description => "Shows all guests which are not covered by any backup job.", permissions => { - check => ['perm', '/', ['Sys.Audit']], + check => ['perm', '/', ['Sys.Audit']], }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - description => 'Contains the guest objects.', - items => { - type => 'object', - properties => { - vmid => { - type => 'integer', - description => 'VMID of the guest.', - }, - name => { - type => 'string', - description => 'Name of the guest', - optional => 1, - }, - type => { - type => 'string', - description => 'Type of the guest.', - enum => ['qemu', 'lxc'], - }, - }, - }, + type => 'array', + description => 'Contains the guest objects.', + items => { + type => 'object', + properties => { + vmid => { + type => 'integer', + description => 'VMID of the guest.', + }, + name => { + type => 'string', + description => 'Name of the guest', + optional => 1, + }, + type => { + type => 'string', + description => 'Type of the guest.', + enum => ['qemu', 'lxc'], + }, + }, + }, }, code => sub { - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); - my $included_vmids = get_included_vmids(); - my $vmlist = PVE::Cluster::get_vmlist(); + my $included_vmids = get_included_vmids(); + my $vmlist = PVE::Cluster::get_vmlist(); - # remove VMIDs to which the user has no permission to not leak infos like the guest name - my @allowed_vmids = grep { $rpcenv->check($user, "/vms/$_", [ 'VM.Audit' ], 1) } keys $vmlist->{ids}->%*; + # remove VMIDs to which the user has no permission to not leak infos like the guest name + my @allowed_vmids = + grep { $rpcenv->check($user, "/vms/$_", ['VM.Audit'], 1) } keys $vmlist->{ids}->%*; - my $result = []; - for my $vmid (@allowed_vmids) { - next if $included_vmids->{$vmid}; + my $result = []; + for my $vmid (@allowed_vmids) { + next if $included_vmids->{$vmid}; - my ($type, $node) = $vmlist->{ids}->{$vmid}->@{'type', 'node'}; + my ($type, $node) = $vmlist->{ids}->{$vmid}->@{ 'type', 'node' }; - my ($conf, $name); - if ($type eq 'qemu') { - $conf = PVE::QemuConfig->load_config($vmid, $node); - $name = $conf->{name}; - } elsif ($type eq 'lxc') { - $conf = PVE::LXC::Config->load_config($vmid, $node); - $name = $conf->{hostname}; - } else { - die "Unexpected error: unknown guest type for VMID $vmid, neither QEMU nor LXC\n"; - } + my ($conf, $name); + if ($type eq 'qemu') { + $conf = PVE::QemuConfig->load_config($vmid, $node); + $name = $conf->{name}; + } elsif ($type eq 'lxc') { + $conf = PVE::LXC::Config->load_config($vmid, $node); + $name = $conf->{hostname}; + } else { + die + "Unexpected error: unknown guest type for VMID $vmid, neither QEMU nor LXC\n"; + } - my $entry = { - vmid => int($vmid), - type => $type, - }; - $entry->{name} = $name if defined($name); + my $entry = { + vmid => int($vmid), + type => $type, + }; + $entry->{name} = $name if defined($name); - push @{$result}, $entry; - } + push @{$result}, $entry; + } - return $result; - }}); + return $result; + }, +}); 1; diff --git a/PVE/API2/Cluster/BulkActions.pm b/PVE/API2/Cluster/BulkActions.pm new file mode 100644 index 00000000..f35342ad --- /dev/null +++ b/PVE/API2/Cluster/BulkActions.pm @@ -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/' 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; diff --git a/PVE/API2/Cluster/Ceph.pm b/PVE/API2/Cluster/Ceph.pm index 6778772d..399460fb 100644 --- a/PVE/API2/Cluster/Ceph.pm +++ b/PVE/API2/Cluster/Ceph.pm @@ -17,296 +17,297 @@ use PVE::Tools qw(extract_param); use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'cephindex', path => '', method => 'GET', description => "Cluster ceph index.", permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $result = [ - { name => 'metadata' }, - { name => 'status' }, - { name => 'flags' }, - ]; + my $result = [ + { name => 'metadata' }, { name => 'status' }, { name => 'flags' }, + ]; - return $result; - } + return $result; + }, }); my $metadata_common_props = { hostname => { - type => "string", - description => "Hostname on which the service is running.", + type => "string", + description => "Hostname on which the service is running.", }, ceph_release => { - type => "string", - description => "Ceph release codename currently used.", + type => "string", + description => "Ceph release codename currently used.", }, ceph_version => { - type => "string", - description => "Version info currently used by the service.", + type => "string", + description => "Version info currently used by the service.", }, ceph_version_short => { - type => "string", - description => "Short version (numerical) info currently used by the service.", + type => "string", + description => "Short version (numerical) info currently used by the service.", }, mem_total_kb => { - type => "integer", - description => "Memory consumption of the service.", + type => "integer", + description => "Memory consumption of the service.", }, mem_swap_kb => { - type => "integer", - description => "Memory of the service currently in swap.", + type => "integer", + description => "Memory of the service currently in swap.", }, }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'metadata', path => 'metadata', method => 'GET', description => "Get ceph metadata.", protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1], + check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1], }, parameters => { - additionalProperties => 0, - properties => { - scope => { - type => 'string', - optional => 1, - default => 'all', - enum => ['all', 'versions', ], - }, - }, + additionalProperties => 0, + properties => { + scope => { + type => 'string', + optional => 1, + default => 'all', + enum => ['all', 'versions'], + }, + }, }, returns => { - type => 'object', - description => "Items for each type of service containing objects for each instance.", - properties => { - mds => { - type => "object", - description => "Metadata servers configured in the cluster and their properties.", - properties => { - "{id}" => { - type => "object", - description => "Useful properties are listed, but not the full list.", - properties => { - addr => { - type => "string", - description => "Bind addresses and ports.", - }, - name => { - type => "string", - description => "Name of the service instance.", - }, - %{$metadata_common_props}, - }, - }, - }, - }, - mgr => { - type => "object", - description => "Managers configured in the cluster and their properties.", - properties => { - "{id}" => { - type => "object", - description => "Useful properties are listed, but not the full list.", - properties => { - addr => { - type => "string", - description => "Bind address", - }, - name => { - type => "string", - description => "Name of the service instance.", - }, - %{$metadata_common_props}, - }, - }, - }, - }, - mon => { - type => "object", - description => "Monitors configured in the cluster and their properties.", - properties => { - "{id}" => { - type => "object", - description => "Useful properties are listed, but not the full list.", - properties => { - addrs => { - type => "string", - description => "Bind addresses and ports.", - }, - name => { - type => "string", - description => "Name of the service instance.", - }, - %{$metadata_common_props}, - }, - }, - }, - }, - node => { - type => "object", - description => "Ceph version installed on the nodes.", - properties => { - "{node}" => { - type => "object", - properties => { - buildcommit => { - type => "string", - description => "GIT commit used for the build.", - }, - version => { - type => "object", - description => "Version info.", - properties => { - str => { - type => "string", - description => "Version as single string.", - }, - parts => { - type => "array", - description => "major, minor & patch", - }, - }, - }, - }, - }, - }, - }, - osd => { - type => "array", - description => "OSDs configured in the cluster and their properties.", - properties => { - "{id}" => { - type => "object", - description => "Useful properties are listed, but not the full list.", - properties => { - id => { - type => "integer", - description => "OSD ID.", - }, - front_addr => { - type => "string", - description => "Bind addresses and ports for frontend traffic to OSDs.", - }, - back_addr => { - type => "string", - description => "Bind addresses and ports for backend inter OSD traffic.", - }, - device_id => { - type => "string", - description => "Devices used by the OSD.", - }, - osd_data => { - type => "string", - description => "Path to the OSD data directory.", - }, - osd_objectstore => { - type => "string", - description => "OSD objectstore type.", - }, - %{$metadata_common_props}, - }, - }, - }, - }, - }, + type => 'object', + description => "Items for each type of service containing objects for each instance.", + properties => { + mds => { + type => "object", + description => + "Metadata servers configured in the cluster and their properties.", + properties => { + "{id}" => { + type => "object", + description => "Useful properties are listed, but not the full list.", + properties => { + addr => { + type => "string", + description => "Bind addresses and ports.", + }, + name => { + type => "string", + description => "Name of the service instance.", + }, + %{$metadata_common_props}, + }, + }, + }, + }, + mgr => { + type => "object", + description => "Managers configured in the cluster and their properties.", + properties => { + "{id}" => { + type => "object", + description => "Useful properties are listed, but not the full list.", + properties => { + addr => { + type => "string", + description => "Bind address", + }, + name => { + type => "string", + description => "Name of the service instance.", + }, + %{$metadata_common_props}, + }, + }, + }, + }, + mon => { + type => "object", + description => "Monitors configured in the cluster and their properties.", + properties => { + "{id}" => { + type => "object", + description => "Useful properties are listed, but not the full list.", + properties => { + addrs => { + type => "string", + description => "Bind addresses and ports.", + }, + name => { + type => "string", + description => "Name of the service instance.", + }, + %{$metadata_common_props}, + }, + }, + }, + }, + node => { + type => "object", + description => "Ceph version installed on the nodes.", + properties => { + "{node}" => { + type => "object", + properties => { + buildcommit => { + type => "string", + description => "GIT commit used for the build.", + }, + version => { + type => "object", + description => "Version info.", + properties => { + str => { + type => "string", + description => "Version as single string.", + }, + parts => { + type => "array", + description => "major, minor & patch", + }, + }, + }, + }, + }, + }, + }, + osd => { + type => "array", + description => "OSDs configured in the cluster and their properties.", + properties => { + "{id}" => { + type => "object", + description => "Useful properties are listed, but not the full list.", + properties => { + id => { + type => "integer", + description => "OSD ID.", + }, + front_addr => { + type => "string", + description => + "Bind addresses and ports for frontend traffic to OSDs.", + }, + back_addr => { + type => "string", + description => + "Bind addresses and ports for backend inter OSD traffic.", + }, + device_id => { + type => "string", + description => "Devices used by the OSD.", + }, + osd_data => { + type => "string", + description => "Path to the OSD data directory.", + }, + osd_objectstore => { + type => "string", + description => "OSD objectstore type.", + }, + %{$metadata_common_props}, + }, + }, + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $scope = $param->{scope} // 'all'; + my $scope = $param->{scope} // 'all'; - my $res = {}; + my $res = {}; - if (defined(my $versions = PVE::Ceph::Services::get_ceph_versions())) { - $res->{node} = $versions; - } + if (defined(my $versions = PVE::Ceph::Services::get_ceph_versions())) { + $res->{node} = $versions; + } - return $res if ($scope eq 'versions'); + return $res if ($scope eq 'versions'); - # only check now, we want to allow calls with scope 'versions' on non-ceph nodes too! - PVE::Ceph::Tools::check_ceph_inited(); - my $rados = PVE::RADOS->new(); + # only check now, we want to allow calls with scope 'versions' on non-ceph nodes too! + PVE::Ceph::Tools::check_ceph_inited(); + my $rados = PVE::RADOS->new(); - for my $type ( qw(mon mgr mds) ) { - my $typedata = PVE::Ceph::Services::get_cluster_service($type); - my $data = {}; - for my $host (sort keys %$typedata) { - for my $service (sort keys %{$typedata->{$host}}) { - $data->{"$service\@$host"} = $typedata->{$host}->{$service}; - } - } + for my $type (qw(mon mgr mds)) { + my $typedata = PVE::Ceph::Services::get_cluster_service($type); + my $data = {}; + for my $host (sort keys %$typedata) { + for my $service (sort keys %{ $typedata->{$host} }) { + $data->{"$service\@$host"} = $typedata->{$host}->{$service}; + } + } - # get data from metadata call and merge 'our' data - my $services = $rados->mon_command({ prefix => "$type metadata" }); - for my $service ( @$services ) { - my $hostname = $service->{hostname}; - next if !defined($hostname); # can happen if node is dead + # get data from metadata call and merge 'our' data + my $services = $rados->mon_command({ prefix => "$type metadata" }); + for my $service (@$services) { + my $hostname = $service->{hostname}; + next if !defined($hostname); # can happen if node is dead - my $servicename = $service->{name} // $service->{id}; - my $id = "$servicename\@$hostname"; + my $servicename = $service->{name} // $service->{id}; + my $id = "$servicename\@$hostname"; - if ($data->{$id}) { # copy values over to the metadata hash - for my $k (keys %{$data->{$id}}) { - $service->{$k} = $data->{$id}->{$k}; - } - } - $data->{$id} = $service; - } + if ($data->{$id}) { # copy values over to the metadata hash + for my $k (keys %{ $data->{$id} }) { + $service->{$k} = $data->{$id}->{$k}; + } + } + $data->{$id} = $service; + } - $res->{$type} = $data; - } + $res->{$type} = $data; + } - $res->{osd} = $rados->mon_command({ prefix => "osd metadata" }); + $res->{osd} = $rados->mon_command({ prefix => "osd metadata" }); - return $res; - } + return $res; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'status', path => 'status', method => 'GET', description => "Get ceph status.", protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1], + check => ['perm', '/', ['Sys.Audit', 'Datastore.Audit'], any => 1], }, parameters => { - additionalProperties => 0, - properties => { }, + additionalProperties => 0, + properties => {}, }, returns => { type => 'object' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - return PVE::Ceph::Tools::ceph_cluster_status(); - } + return PVE::Ceph::Tools::ceph_cluster_status(); + }, }); my $possible_flags = PVE::Ceph::Tools::get_possible_osd_flags(); -my $possible_flags_list = [ sort keys %$possible_flags ]; +my $possible_flags_list = [sort keys %$possible_flags]; my $get_current_set_flags = sub { my $rados = shift; @@ -318,198 +319,200 @@ my $get_current_set_flags = sub { return { map { $_ => 1 } PVE::Tools::split_list($setflags) }; }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_all_flags', path => 'flags', method => 'GET', description => "get the status of all ceph flags", protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit' ]], + check => ['perm', '/', ['Sys.Audit']], }, parameters => { - additionalProperties => 0, - properties => { - }, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - additionalProperties => 1, - properties => { - name => { - description => "Flag name.", - type => 'string', enum => $possible_flags_list, - }, - description => { - description => "Flag description.", - type => 'string', - }, - value => { - description => "Flag value.", - type => 'boolean', - }, - }, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + additionalProperties => 1, + properties => { + name => { + description => "Flag name.", + type => 'string', + enum => $possible_flags_list, + }, + description => { + description => "Flag description.", + type => 'string', + }, + value => { + description => "Flag value.", + type => 'boolean', + }, + }, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_configured(); + PVE::Ceph::Tools::check_ceph_configured(); - my $setflags = $get_current_set_flags->(); + my $setflags = $get_current_set_flags->(); - my $res = []; - foreach my $flag (@$possible_flags_list) { - my $el = { - name => $flag, - description => $possible_flags->{$flag}->{description}, - value => 0, - }; + my $res = []; + foreach my $flag (@$possible_flags_list) { + my $el = { + name => $flag, + description => $possible_flags->{$flag}->{description}, + value => 0, + }; - my $realflag = PVE::Ceph::Tools::get_real_flag_name($flag); - if ($setflags->{$realflag}) { - $el->{value} = 1; - } + my $realflag = PVE::Ceph::Tools::get_real_flag_name($flag); + if ($setflags->{$realflag}) { + $el->{value} = 1; + } - push @$res, $el; - } + push @$res, $el; + } - return $res; - } + return $res; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'set_flags', path => 'flags', method => 'PUT', description => "Set/Unset multiple ceph flags at once.", protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => $possible_flags, + additionalProperties => 0, + properties => $possible_flags, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); - PVE::Ceph::Tools::check_ceph_configured(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); + PVE::Ceph::Tools::check_ceph_configured(); - my $worker = sub { - my $rados = PVE::RADOS->new(); # (re-)open for forked worker + my $worker = sub { + my $rados = PVE::RADOS->new(); # (re-)open for forked worker - my $setflags = $get_current_set_flags->($rados); + my $setflags = $get_current_set_flags->($rados); - my $errors = 0; - foreach my $flag (@$possible_flags_list) { - next if !defined($param->{$flag}); - my $val = $param->{$flag}; - my $realflag = PVE::Ceph::Tools::get_real_flag_name($flag); + my $errors = 0; + foreach my $flag (@$possible_flags_list) { + next if !defined($param->{$flag}); + my $val = $param->{$flag}; + my $realflag = PVE::Ceph::Tools::get_real_flag_name($flag); - next if !$val == !$setflags->{$realflag}; # we do not set/unset flags to the same state + next if !$val == !$setflags->{$realflag}; # we do not set/unset flags to the same state - my $prefix = $val ? 'set' : 'unset'; - eval { - print "$prefix $flag\n"; - $rados->mon_command({ prefix => "osd $prefix", key => $flag, }); - }; - if (my $err = $@) { - warn "error with $flag: '$err'\n"; - $errors++; - } - } + my $prefix = $val ? 'set' : 'unset'; + eval { + print "$prefix $flag\n"; + $rados->mon_command({ prefix => "osd $prefix", key => $flag }); + }; + if (my $err = $@) { + warn "error with $flag: '$err'\n"; + $errors++; + } + } - if ($errors) { - die "could not set/unset $errors flags\n"; - } - }; + if ($errors) { + die "could not set/unset $errors flags\n"; + } + }; - return $rpcenv->fork_worker('cephsetflags', undef, $user, $worker); - }}); + return $rpcenv->fork_worker('cephsetflags', undef, $user, $worker); + }, +}); - -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_flag', path => 'flags/{flag}', method => 'GET', description => "Get the status of a specific ceph flag.", protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit' ]], + check => ['perm', '/', ['Sys.Audit']], }, parameters => { - additionalProperties => 0, - properties => { - flag => { - description => "The name of the flag name to get.", - type => 'string', enum => $possible_flags_list, - }, - }, + additionalProperties => 0, + properties => { + flag => { + description => "The name of the flag name to get.", + type => 'string', + enum => $possible_flags_list, + }, + }, }, returns => { - type => 'boolean', + type => 'boolean', }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_configured(); + PVE::Ceph::Tools::check_ceph_configured(); - my $realflag = PVE::Ceph::Tools::get_real_flag_name($param->{flag}); + my $realflag = PVE::Ceph::Tools::get_real_flag_name($param->{flag}); - my $setflags = $get_current_set_flags->(); - if ($setflags->{$realflag}) { - return 1; - } + my $setflags = $get_current_set_flags->(); + if ($setflags->{$realflag}) { + return 1; + } - return 0; - }}); + return 0; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update_flag', path => 'flags/{flag}', method => 'PUT', description => "Set or clear (unset) a specific ceph flag", protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - flag => { - description => 'The ceph flag to update', - type => 'string', - enum => $possible_flags_list, - }, - value => { - description => 'The new value of the flag', - type => 'boolean', - }, - }, + additionalProperties => 0, + properties => { + flag => { + description => 'The ceph flag to update', + type => 'string', + enum => $possible_flags_list, + }, + value => { + description => 'The new value of the flag', + type => 'boolean', + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_configured(); + PVE::Ceph::Tools::check_ceph_configured(); - my $cmd = $param->{value} ? 'set' : 'unset'; + my $cmd = $param->{value} ? 'set' : 'unset'; - my $rados = PVE::RADOS->new(); - $rados->mon_command({ - prefix => "osd $cmd", - key => $param->{flag}, - }); - - return undef; - }}); + my $rados = PVE::RADOS->new(); + $rados->mon_command({ + prefix => "osd $cmd", + key => $param->{flag}, + }); + return undef; + }, +}); 1; diff --git a/PVE/API2/Cluster/Jobs.pm b/PVE/API2/Cluster/Jobs.pm index 0b003e70..e02eed9e 100644 --- a/PVE/API2/Cluster/Jobs.pm +++ b/PVE/API2/Cluster/Jobs.pm @@ -10,7 +10,7 @@ use PVE::API2::Jobs::RealmSync; use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Jobs::RealmSync", path => 'realm-sync', }); @@ -22,29 +22,29 @@ __PACKAGE__->register_method({ description => "Index for jobs related endpoints.", permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - description => 'Directory index.', - items => { - type => "object", - properties => { - subdir => { - type => 'string', - description => 'API sub-directory endpoint', - }, - }, - }, - links => [ { rel => 'child', href => "{subdir}" } ], + type => 'array', + description => 'Directory index.', + items => { + type => "object", + properties => { + subdir => { + type => 'string', + description => 'API sub-directory endpoint', + }, + }, + }, + links => [{ rel => 'child', href => "{subdir}" }], }, code => sub { - return [ - { subdir => 'schedule-analyze' }, - { subdir => 'realm-sync' }, - ]; - }}); + return [ + { subdir => 'schedule-analyze' }, { subdir => 'realm-sync' }, + ]; + }, +}); __PACKAGE__->register_method({ name => 'schedule-analyze', @@ -53,67 +53,72 @@ __PACKAGE__->register_method({ description => "Returns a list of future schedule runtimes.", permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => { - schedule => { - description => "Job schedule. The format is a subset of `systemd` calendar events.", - type => 'string', format => 'pve-calendar-event', - maxLength => 128, - }, - starttime => { - description => "UNIX timestamp to start the calculation from. Defaults to the current time.", - optional => 1, - type => 'integer', - }, - iterations => { - description => "Number of event-iteration to simulate and return.", - optional => 1, - type => 'integer', - minimum => 1, - maximum => 100, - default => 10, - }, - }, + additionalProperties => 0, + properties => { + schedule => { + description => + "Job schedule. The format is a subset of `systemd` calendar events.", + type => 'string', + format => 'pve-calendar-event', + maxLength => 128, + }, + starttime => { + description => + "UNIX timestamp to start the calculation from. Defaults to the current time.", + optional => 1, + type => 'integer', + }, + iterations => { + description => "Number of event-iteration to simulate and return.", + optional => 1, + type => 'integer', + minimum => 1, + maximum => 100, + default => 10, + }, + }, }, returns => { - type => 'array', - description => 'An array of the next events since .', - items => { - type => 'object', - properties => { - timestamp => { - type => 'integer', - description => 'UNIX timestamp for the run.', - }, - utc => { - type => 'string', - description => "UTC timestamp for the run.", - }, - }, - }, + type => 'array', + description => 'An array of the next events since .', + items => { + type => 'object', + properties => { + timestamp => { + type => 'integer', + description => 'UNIX timestamp for the run.', + }, + utc => { + type => 'string', + description => "UTC timestamp for the run.", + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $starttime = $param->{starttime} // time(); - my $iterations = $param->{iterations} // 10; - my $schedule = $param->{schedule}; + my $starttime = $param->{starttime} // time(); + my $iterations = $param->{iterations} // 10; + my $schedule = $param->{schedule}; - my $result = []; + my $result = []; - my $event = PVE::CalendarEvent::parse_calendar_event($schedule); + my $event = PVE::CalendarEvent::parse_calendar_event($schedule); - for (my $count = 0; $count < $iterations; $count++) { - my $next = PVE::CalendarEvent::compute_next_event($event, $starttime); - last if !defined($next); - push @$result, { - timestamp => $next, - utc => scalar(gmtime($next)), - }; - $starttime = $next; - } + for (my $count = 0; $count < $iterations; $count++) { + my $next = PVE::CalendarEvent::compute_next_event($event, $starttime); + last if !defined($next); + push @$result, + { + timestamp => $next, + utc => scalar(gmtime($next)), + }; + $starttime = $next; + } - return $result; - }}); + return $result; + }, +}); 1; diff --git a/PVE/API2/Cluster/Mapping.pm b/PVE/API2/Cluster/Mapping.pm index 9f0dcd2b..e39a1683 100644 --- a/PVE/API2/Cluster/Mapping.pm +++ b/PVE/API2/Cluster/Mapping.pm @@ -9,50 +9,49 @@ use PVE::API2::Cluster::Mapping::USB; use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Cluster::Mapping::Dir", path => 'dir', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Cluster::Mapping::PCI", path => 'pci', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Cluster::Mapping::USB", path => 'usb', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', description => "List resource types.", permissions => { - user => 'all', + user => 'all', }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $result = [ - { name => 'dir' }, - { name => 'pci' }, - { name => 'usb' }, - ]; + my $result = [ + { name => 'dir' }, { name => 'pci' }, { name => 'usb' }, + ]; - return $result; - }}); + return $result; + }, +}); 1; diff --git a/PVE/API2/Cluster/Mapping/Dir.pm b/PVE/API2/Cluster/Mapping/Dir.pm index f905cef3..ceaa1129 100644 --- a/PVE/API2/Cluster/Mapping/Dir.pm +++ b/PVE/API2/Cluster/Mapping/Dir.pm @@ -14,295 +14,314 @@ use PVE::Tools qw(extract_param); use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', # only proxy if we give the 'check-node' parameter proxyto_callback => sub { - my ($rpcenv, $proxyto, $param) = @_; - return $param->{'check-node'} // 'localhost'; + my ($rpcenv, $proxyto, $param) = @_; + return $param->{'check-node'} // 'localhost'; }, description => "List directory mapping", permissions => { - description => "Only lists entries where you have 'Mapping.Modify', 'Mapping.Use' or" - ." 'Mapping.Audit' permissions on '/mapping/dir/'.", - user => 'all', + description => "Only lists entries where you have 'Mapping.Modify', 'Mapping.Use' or" + . " 'Mapping.Audit' permissions on '/mapping/dir/'.", + user => 'all', }, parameters => { - additionalProperties => 0, - properties => { - 'check-node' => get_standard_option('pve-node', { - description => "If given, checks the configurations on the given node for" - ." correctness, and adds relevant diagnostics for the directory to the response.", - optional => 1, - }), - }, + additionalProperties => 0, + properties => { + 'check-node' => get_standard_option( + 'pve-node', + { + description => "If given, checks the configurations on the given node for" + . " correctness, and adds relevant diagnostics for the directory to the response.", + optional => 1, + }, + ), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - id => { - type => 'string', - description => "The logical ID of the mapping." - }, - map => { - type => 'array', - description => "The entries of the mapping.", - items => { - type => 'string', - description => "A mapping for a node.", - }, - }, - description => { - type => 'string', - description => "A description of the logical mapping.", - }, - checks => { - type => "array", - optional => 1, - description => "A list of checks, only present if 'check-node' is set.", - items => { - type => 'object', - properties => { - severity => { - type => "string", - enum => ['warning', 'error'], - description => "The severity of the error", - }, - message => { - type => "string", - description => "The message of the error", - }, - }, - } - }, - }, - }, - links => [ { rel => 'child', href => "{id}" } ], + type => 'array', + items => { + type => "object", + properties => { + id => { + type => 'string', + description => "The logical ID of the mapping.", + }, + map => { + type => 'array', + description => "The entries of the mapping.", + items => { + type => 'string', + description => "A mapping for a node.", + }, + }, + description => { + type => 'string', + description => "A description of the logical mapping.", + }, + checks => { + type => "array", + optional => 1, + description => "A list of checks, only present if 'check-node' is set.", + items => { + type => 'object', + properties => { + severity => { + type => "string", + enum => ['warning', 'error'], + description => "The severity of the error", + }, + message => { + type => "string", + description => "The message of the error", + }, + }, + }, + }, + }, + }, + links => [{ rel => 'child', href => "{id}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $check_node = $param->{'check-node'}; - my $local_node = PVE::INotify::nodename(); + my $check_node = $param->{'check-node'}; + my $local_node = PVE::INotify::nodename(); - die "wrong node to check - $check_node != $local_node\n" - if defined($check_node) && $check_node ne 'localhost' && $check_node ne $local_node; + die "wrong node to check - $check_node != $local_node\n" + if defined($check_node) && $check_node ne 'localhost' && $check_node ne $local_node; - my $cfg = PVE::Mapping::Dir::config(); + my $cfg = PVE::Mapping::Dir::config(); - my $can_see_mapping_privs = ['Mapping.Modify', 'Mapping.Use', 'Mapping.Audit']; + my $can_see_mapping_privs = ['Mapping.Modify', 'Mapping.Use', 'Mapping.Audit']; - my $res = []; - for my $id (keys $cfg->{ids}->%*) { - next if !$rpcenv->check_any($authuser, "/mapping/dir/$id", $can_see_mapping_privs, 1); - next if !$cfg->{ids}->{$id}; + my $res = []; + for my $id (keys $cfg->{ids}->%*) { + next + if !$rpcenv->check_any($authuser, "/mapping/dir/$id", $can_see_mapping_privs, 1); + next if !$cfg->{ids}->{$id}; - my $entry = dclone($cfg->{ids}->{$id}); - $entry->{id} = $id; - $entry->{digest} = $cfg->{digest}; + my $entry = dclone($cfg->{ids}->{$id}); + $entry->{id} = $id; + $entry->{digest} = $cfg->{digest}; - if (defined($check_node)) { - $entry->{checks} = []; - if (my $mappings = PVE::Mapping::Dir::get_node_mapping($cfg, $id, $check_node)) { - if (!scalar($mappings->@*)) { - push $entry->{checks}->@*, { - severity => 'warning', - message => "No mapping for node $check_node.", - }; - } - for my $mapping ($mappings->@*) { - eval { PVE::Mapping::Dir::assert_valid($mapping) }; - if (my $err = $@) { - push $entry->{checks}->@*, { - severity => 'error', - message => "Invalid configuration: $err", - }; - } - } - } - } + if (defined($check_node)) { + $entry->{checks} = []; + if ( + my $mappings = PVE::Mapping::Dir::get_node_mapping($cfg, $id, $check_node) + ) { + if (!scalar($mappings->@*)) { + push $entry->{checks}->@*, + { + severity => 'warning', + message => "No mapping for node $check_node.", + }; + } + for my $mapping ($mappings->@*) { + eval { PVE::Mapping::Dir::assert_valid($mapping) }; + if (my $err = $@) { + push $entry->{checks}->@*, + { + severity => 'error', + message => "Invalid configuration: $err", + }; + } + } + } + } - push @$res, $entry; - } + push @$res, $entry; + } - return $res; + return $res; }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get', protected => 1, path => '{id}', method => 'GET', description => "Get directory mapping.", permissions => { - check =>['or', - ['perm', '/mapping/dir/{id}', ['Mapping.Use']], - ['perm', '/mapping/dir/{id}', ['Mapping.Modify']], - ['perm', '/mapping/dir/{id}', ['Mapping.Audit']], - ], + check => [ + 'or', + ['perm', '/mapping/dir/{id}', ['Mapping.Use']], + ['perm', '/mapping/dir/{id}', ['Mapping.Modify']], + ['perm', '/mapping/dir/{id}', ['Mapping.Audit']], + ], }, parameters => { - additionalProperties => 0, - properties => { - id => { - type => 'string', - format => 'pve-configid', - }, - } + additionalProperties => 0, + properties => { + id => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { type => 'object' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $cfg = PVE::Mapping::Dir::config(); - my $id = $param->{id}; + my $cfg = PVE::Mapping::Dir::config(); + my $id = $param->{id}; - my $entry = $cfg->{ids}->{$id}; - die "mapping '$param->{id}' not found\n" if !defined($entry); + my $entry = $cfg->{ids}->{$id}; + die "mapping '$param->{id}' not found\n" if !defined($entry); - my $data = dclone($entry); + my $data = dclone($entry); - $data->{digest} = $cfg->{digest}; + $data->{digest} = $cfg->{digest}; - return $data; - }}); + return $data; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create', protected => 1, path => '', method => 'POST', description => "Create a new directory mapping.", permissions => { - check => ['perm', '/mapping/dir', ['Mapping.Modify']], + check => ['perm', '/mapping/dir', ['Mapping.Modify']], }, parameters => PVE::Mapping::Dir->createSchema(1), returns => { - type => 'null', + type => 'null', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'id'); + my $id = extract_param($param, 'id'); - my $plugin = PVE::Mapping::Dir->lookup('dir'); - my $opts = $plugin->check_config($id, $param, 1, 1); + my $plugin = PVE::Mapping::Dir->lookup('dir'); + my $opts = $plugin->check_config($id, $param, 1, 1); - my $map_list = $opts->{map}; - PVE::Mapping::Dir::assert_valid_map_list($map_list); + my $map_list = $opts->{map}; + PVE::Mapping::Dir::assert_valid_map_list($map_list); - PVE::Mapping::Dir::lock_dir_config(sub { - my $cfg = PVE::Mapping::Dir::config(); + PVE::Mapping::Dir::lock_dir_config( + sub { + my $cfg = PVE::Mapping::Dir::config(); - die "dir ID '$id' already defined\n" if defined($cfg->{ids}->{$id}); + die "dir ID '$id' already defined\n" if defined($cfg->{ids}->{$id}); - $cfg->{ids}->{$id} = $opts; + $cfg->{ids}->{$id} = $opts; - PVE::Mapping::Dir::write_dir_config($cfg); + PVE::Mapping::Dir::write_dir_config($cfg); - }, "create directory mapping failed"); + }, + "create directory mapping failed", + ); - return; + return; }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update', protected => 1, path => '{id}', method => 'PUT', description => "Update a directory mapping.", permissions => { - check => ['perm', '/mapping/dir/{id}', ['Mapping.Modify']], + check => ['perm', '/mapping/dir/{id}', ['Mapping.Modify']], }, parameters => PVE::Mapping::Dir->updateSchema(), returns => { - type => 'null', + type => 'null', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $digest = extract_param($param, 'digest'); - my $delete = extract_param($param, 'delete'); - my $id = extract_param($param, 'id'); + my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); + my $id = extract_param($param, 'id'); - if ($delete) { - $delete = [ PVE::Tools::split_list($delete) ]; - } + if ($delete) { + $delete = [PVE::Tools::split_list($delete)]; + } - PVE::Mapping::Dir::lock_dir_config(sub { - my $cfg = PVE::Mapping::Dir::config(); + PVE::Mapping::Dir::lock_dir_config( + sub { + my $cfg = PVE::Mapping::Dir::config(); - PVE::Tools::assert_if_modified($cfg->{digest}, $digest) if defined($digest); + PVE::Tools::assert_if_modified($cfg->{digest}, $digest) if defined($digest); - die "dir ID '$id' does not exist\n" if !defined($cfg->{ids}->{$id}); + die "dir ID '$id' does not exist\n" if !defined($cfg->{ids}->{$id}); - my $plugin = PVE::Mapping::Dir->lookup('dir'); - my $opts = $plugin->check_config($id, $param, 1, 1); + my $plugin = PVE::Mapping::Dir->lookup('dir'); + my $opts = $plugin->check_config($id, $param, 1, 1); - my $map_list = $opts->{map}; - PVE::Mapping::Dir::assert_valid_map_list($map_list); + my $map_list = $opts->{map}; + PVE::Mapping::Dir::assert_valid_map_list($map_list); - my $data = $cfg->{ids}->{$id}; + my $data = $cfg->{ids}->{$id}; - my $options = $plugin->private()->{options}->{dir}; - PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete); + my $options = $plugin->private()->{options}->{dir}; + PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete); - $data->{$_} = $opts->{$_} for keys $opts->%*; + $data->{$_} = $opts->{$_} for keys $opts->%*; - PVE::Mapping::Dir::write_dir_config($cfg); + PVE::Mapping::Dir::write_dir_config($cfg); - }, "update directory mapping failed"); + }, + "update directory mapping failed", + ); - return; + return; }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete', protected => 1, path => '{id}', method => 'DELETE', description => "Remove directory mapping.", permissions => { - check => [ 'perm', '/mapping/dir', ['Mapping.Modify']], + check => ['perm', '/mapping/dir', ['Mapping.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - id => { - type => 'string', - format => 'pve-configid', - }, - } + additionalProperties => 0, + properties => { + id => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = $param->{id}; + my $id = $param->{id}; - PVE::Mapping::Dir::lock_dir_config(sub { - my $cfg = PVE::Mapping::Dir::config(); + PVE::Mapping::Dir::lock_dir_config( + sub { + my $cfg = PVE::Mapping::Dir::config(); - if ($cfg->{ids}->{$id}) { - delete $cfg->{ids}->{$id}; - } + if ($cfg->{ids}->{$id}) { + delete $cfg->{ids}->{$id}; + } - PVE::Mapping::Dir::write_dir_config($cfg); + PVE::Mapping::Dir::write_dir_config($cfg); - }, "delete dir mapping failed"); + }, + "delete dir mapping failed", + ); - return; - } + return; + }, }); 1; diff --git a/PVE/API2/Cluster/Mapping/PCI.pm b/PVE/API2/Cluster/Mapping/PCI.pm index f85623bb..c39e53ee 100644 --- a/PVE/API2/Cluster/Mapping/PCI.pm +++ b/PVE/API2/Cluster/Mapping/PCI.pm @@ -11,289 +11,308 @@ use PVE::Tools qw(extract_param); use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', # only proxy if we give the 'check-node' parameter proxyto_callback => sub { - my ($rpcenv, $proxyto, $param) = @_; - return $param->{'check-node'} // 'localhost'; + my ($rpcenv, $proxyto, $param) = @_; + return $param->{'check-node'} // 'localhost'; }, description => "List PCI Hardware Mapping", permissions => { - description => "Only lists entries where you have 'Mapping.Modify', 'Mapping.Use' or". - " 'Mapping.Audit' permissions on '/mapping/pci/'.", - user => 'all', + description => "Only lists entries where you have 'Mapping.Modify', 'Mapping.Use' or" + . " 'Mapping.Audit' permissions on '/mapping/pci/'.", + user => 'all', }, parameters => { - additionalProperties => 0, - properties => { - 'check-node' => get_standard_option('pve-node', { - description => "If given, checks the configurations on the given node for ". - "correctness, and adds relevant diagnostics for the devices to the response.", - optional => 1, - }), - }, + additionalProperties => 0, + properties => { + 'check-node' => get_standard_option( + 'pve-node', + { + description => "If given, checks the configurations on the given node for " + . "correctness, and adds relevant diagnostics for the devices to the response.", + optional => 1, + }, + ), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - id => { - type => 'string', - description => "The logical ID of the mapping." - }, - map => { - type => 'array', - description => "The entries of the mapping.", - items => { - type => 'string', - description => "A mapping for a node.", - }, - }, - description => { - type => 'string', - description => "A description of the logical mapping.", - }, - checks => { - type => "array", - optional => 1, - description => "A list of checks, only present if 'check_node' is set.", - items => { - type => 'object', - properties => { - severity => { - type => "string", - enum => ['warning', 'error'], - description => "The severity of the error", - }, - message => { - type => "string", - description => "The message of the error", - }, - }, - } - }, - }, - }, - links => [ { rel => 'child', href => "{id}" } ], + type => 'array', + items => { + type => "object", + properties => { + id => { + type => 'string', + description => "The logical ID of the mapping.", + }, + map => { + type => 'array', + description => "The entries of the mapping.", + items => { + type => 'string', + description => "A mapping for a node.", + }, + }, + description => { + type => 'string', + description => "A description of the logical mapping.", + }, + checks => { + type => "array", + optional => 1, + description => "A list of checks, only present if 'check_node' is set.", + items => { + type => 'object', + properties => { + severity => { + type => "string", + enum => ['warning', 'error'], + description => "The severity of the error", + }, + message => { + type => "string", + description => "The message of the error", + }, + }, + }, + }, + }, + }, + links => [{ rel => 'child', href => "{id}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $check_node = $param->{'check-node'}; - my $local_node = PVE::INotify::nodename(); + my $check_node = $param->{'check-node'}; + my $local_node = PVE::INotify::nodename(); - die "wrong node to check - $check_node != $local_node\n" - if defined($check_node) && $check_node ne 'localhost' && $check_node ne $local_node; + die "wrong node to check - $check_node != $local_node\n" + if defined($check_node) && $check_node ne 'localhost' && $check_node ne $local_node; - my $cfg = PVE::Mapping::PCI::config(); + my $cfg = PVE::Mapping::PCI::config(); - my $can_see_mapping_privs = ['Mapping.Modify', 'Mapping.Use', 'Mapping.Audit']; + my $can_see_mapping_privs = ['Mapping.Modify', 'Mapping.Use', 'Mapping.Audit']; - my $res = []; - for my $id (keys $cfg->{ids}->%*) { - next if !$rpcenv->check_any($authuser, "/mapping/pci/$id", $can_see_mapping_privs, 1); - next if !$cfg->{ids}->{$id}; + my $res = []; + for my $id (keys $cfg->{ids}->%*) { + next + if !$rpcenv->check_any($authuser, "/mapping/pci/$id", $can_see_mapping_privs, 1); + next if !$cfg->{ids}->{$id}; - my $entry = dclone($cfg->{ids}->{$id}); - $entry->{id} = $id; - $entry->{digest} = $cfg->{digest}; + my $entry = dclone($cfg->{ids}->{$id}); + $entry->{id} = $id; + $entry->{digest} = $cfg->{digest}; - if (defined($check_node)) { - $entry->{checks} = []; - if (my $mappings = PVE::Mapping::PCI::get_node_mapping($cfg, $id, $check_node)) { - if (!scalar($mappings->@*)) { - push $entry->{checks}->@*, { - severity => 'warning', - message => "No mapping for node $check_node.", - }; - } - for my $mapping ($mappings->@*) { - eval { PVE::Mapping::PCI::assert_valid($id, $mapping, $entry) }; - if (my $err = $@) { - push $entry->{checks}->@*, { - severity => 'error', - message => "Invalid configuration: $err", - }; - } - } - } - } + if (defined($check_node)) { + $entry->{checks} = []; + if ( + my $mappings = PVE::Mapping::PCI::get_node_mapping($cfg, $id, $check_node) + ) { + if (!scalar($mappings->@*)) { + push $entry->{checks}->@*, + { + severity => 'warning', + message => "No mapping for node $check_node.", + }; + } + for my $mapping ($mappings->@*) { + eval { PVE::Mapping::PCI::assert_valid($id, $mapping, $entry) }; + if (my $err = $@) { + push $entry->{checks}->@*, + { + severity => 'error', + message => "Invalid configuration: $err", + }; + } + } + } + } - push @$res, $entry; - } + push @$res, $entry; + } - return $res; + return $res; }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get', protected => 1, path => '{id}', method => 'GET', description => "Get PCI Mapping.", permissions => { - check =>['or', - ['perm', '/mapping/pci/{id}', ['Mapping.Use']], - ['perm', '/mapping/pci/{id}', ['Mapping.Modify']], - ['perm', '/mapping/pci/{id}', ['Mapping.Audit']], - ], + check => [ + 'or', + ['perm', '/mapping/pci/{id}', ['Mapping.Use']], + ['perm', '/mapping/pci/{id}', ['Mapping.Modify']], + ['perm', '/mapping/pci/{id}', ['Mapping.Audit']], + ], }, parameters => { - additionalProperties => 0, - properties => { - id => { - type => 'string', - format => 'pve-configid', - }, - } + additionalProperties => 0, + properties => { + id => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { type => 'object' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $cfg = PVE::Mapping::PCI::config(); - my $id = $param->{id}; + my $cfg = PVE::Mapping::PCI::config(); + my $id = $param->{id}; - my $entry = $cfg->{ids}->{$id}; - die "mapping '$param->{id}' not found\n" if !defined($entry); + my $entry = $cfg->{ids}->{$id}; + die "mapping '$param->{id}' not found\n" if !defined($entry); - my $data = dclone($entry); + my $data = dclone($entry); - $data->{digest} = $cfg->{digest}; + $data->{digest} = $cfg->{digest}; - return $data; - }}); + return $data; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create', protected => 1, path => '', method => 'POST', description => "Create a new hardware mapping.", permissions => { - check => ['perm', '/mapping/pci', ['Mapping.Modify']], + check => ['perm', '/mapping/pci', ['Mapping.Modify']], }, parameters => PVE::Mapping::PCI->createSchema(1), returns => { - type => 'null', + type => 'null', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'id'); + my $id = extract_param($param, 'id'); - my $plugin = PVE::Mapping::PCI->lookup('pci'); - my $opts = $plugin->check_config($id, $param, 1, 1); + my $plugin = PVE::Mapping::PCI->lookup('pci'); + my $opts = $plugin->check_config($id, $param, 1, 1); - PVE::Mapping::PCI::lock_pci_config(sub { - my $cfg = PVE::Mapping::PCI::config(); + PVE::Mapping::PCI::lock_pci_config( + sub { + my $cfg = PVE::Mapping::PCI::config(); - die "pci ID '$id' already defined\n" if defined($cfg->{ids}->{$id}); + die "pci ID '$id' already defined\n" if defined($cfg->{ids}->{$id}); - $cfg->{ids}->{$id} = $opts; + $cfg->{ids}->{$id} = $opts; - PVE::Mapping::PCI::write_pci_config($cfg); + PVE::Mapping::PCI::write_pci_config($cfg); - }, "create hardware mapping failed"); + }, + "create hardware mapping failed", + ); - return; + return; }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update', protected => 1, path => '{id}', method => 'PUT', description => "Update a hardware mapping.", permissions => { - check => ['perm', '/mapping/pci/{id}', ['Mapping.Modify']], + check => ['perm', '/mapping/pci/{id}', ['Mapping.Modify']], }, parameters => PVE::Mapping::PCI->updateSchema(), returns => { - type => 'null', + type => 'null', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $digest = extract_param($param, 'digest'); - my $delete = extract_param($param, 'delete'); - my $id = extract_param($param, 'id'); + my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); + my $id = extract_param($param, 'id'); - if ($delete) { - $delete = [ PVE::Tools::split_list($delete) ]; - } + if ($delete) { + $delete = [PVE::Tools::split_list($delete)]; + } - PVE::Mapping::PCI::lock_pci_config(sub { - my $cfg = PVE::Mapping::PCI::config(); + PVE::Mapping::PCI::lock_pci_config( + sub { + my $cfg = PVE::Mapping::PCI::config(); - PVE::Tools::assert_if_modified($cfg->{digest}, $digest) if defined($digest); + PVE::Tools::assert_if_modified($cfg->{digest}, $digest) if defined($digest); - die "pci ID '$id' does not exist\n" if !defined($cfg->{ids}->{$id}); + die "pci ID '$id' does not exist\n" if !defined($cfg->{ids}->{$id}); - my $plugin = PVE::Mapping::PCI->lookup('pci'); - my $opts = $plugin->check_config($id, $param, 1, 1); + my $plugin = PVE::Mapping::PCI->lookup('pci'); + my $opts = $plugin->check_config($id, $param, 1, 1); - my $data = $cfg->{ids}->{$id}; + my $data = $cfg->{ids}->{$id}; - my $options = $plugin->private()->{options}->{pci}; - PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete); + my $options = $plugin->private()->{options}->{pci}; + PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete); - $data->{$_} = $opts->{$_} for keys $opts->%*; + $data->{$_} = $opts->{$_} for keys $opts->%*; - PVE::Mapping::PCI::write_pci_config($cfg); + PVE::Mapping::PCI::write_pci_config($cfg); - }, "update hardware mapping failed"); + }, + "update hardware mapping failed", + ); - return; + return; }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete', protected => 1, path => '{id}', method => 'DELETE', description => "Remove Hardware Mapping.", permissions => { - check => [ 'perm', '/mapping/pci', ['Mapping.Modify']], + check => ['perm', '/mapping/pci', ['Mapping.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - id => { - type => 'string', - format => 'pve-configid', - }, - } + additionalProperties => 0, + properties => { + id => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = $param->{id}; + my $id = $param->{id}; - PVE::Mapping::PCI::lock_pci_config(sub { - my $cfg = PVE::Mapping::PCI::config(); + PVE::Mapping::PCI::lock_pci_config( + sub { + my $cfg = PVE::Mapping::PCI::config(); - if ($cfg->{ids}->{$id}) { - delete $cfg->{ids}->{$id}; - } + if ($cfg->{ids}->{$id}) { + delete $cfg->{ids}->{$id}; + } - PVE::Mapping::PCI::write_pci_config($cfg); + PVE::Mapping::PCI::write_pci_config($cfg); - }, "delete pci mapping failed"); + }, + "delete pci mapping failed", + ); - return; - } + return; + }, }); 1; diff --git a/PVE/API2/Cluster/Mapping/USB.pm b/PVE/API2/Cluster/Mapping/USB.pm index 763d5c2b..9a2a16a9 100644 --- a/PVE/API2/Cluster/Mapping/USB.pm +++ b/PVE/API2/Cluster/Mapping/USB.pm @@ -11,287 +11,301 @@ use PVE::Tools qw(extract_param); use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', # only proxy if we give the 'check-node' parameter proxyto_callback => sub { - my ($rpcenv, $proxyto, $param) = @_; - return $param->{'check-node'} // 'localhost'; + my ($rpcenv, $proxyto, $param) = @_; + return $param->{'check-node'} // 'localhost'; }, description => "List USB Hardware Mappings", permissions => { - description => "Only lists entries where you have 'Mapping.Modify', 'Mapping.Use' or". - " 'Mapping.Audit' permissions on '/mapping/usb/'.", - user => 'all', + description => "Only lists entries where you have 'Mapping.Modify', 'Mapping.Use' or" + . " 'Mapping.Audit' permissions on '/mapping/usb/'.", + user => 'all', }, parameters => { - additionalProperties => 0, - properties => { - 'check-node' => get_standard_option('pve-node', { - description => "If given, checks the configurations on the given node for ". - "correctness, and adds relevant errors to the devices.", - optional => 1, - }), - }, + additionalProperties => 0, + properties => { + 'check-node' => get_standard_option( + 'pve-node', + { + description => "If given, checks the configurations on the given node for " + . "correctness, and adds relevant errors to the devices.", + optional => 1, + }, + ), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - id => { - type => 'string', - description => "The logical ID of the mapping." - }, - map => { - type => 'array', - description => "The entries of the mapping.", - items => { - type => 'string', - description => "A mapping for a node.", - }, - }, - description => { - type => 'string', - description => "A description of the logical mapping.", - }, - error => { - description => "A list of errors when 'check_node' is given.", - items => { - type => 'object', - properties => { - severity => { - type => "string", - description => "The severity of the error", - }, - message => { - type => "string", - description => "The message of the error", - }, - }, - } - }, - }, - }, - links => [ { rel => 'child', href => "{id}" } ], + type => 'array', + items => { + type => "object", + properties => { + id => { + type => 'string', + description => "The logical ID of the mapping.", + }, + map => { + type => 'array', + description => "The entries of the mapping.", + items => { + type => 'string', + description => "A mapping for a node.", + }, + }, + description => { + type => 'string', + description => "A description of the logical mapping.", + }, + error => { + description => "A list of errors when 'check_node' is given.", + items => { + type => 'object', + properties => { + severity => { + type => "string", + description => "The severity of the error", + }, + message => { + type => "string", + description => "The message of the error", + }, + }, + }, + }, + }, + }, + links => [{ rel => 'child', href => "{id}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); - my $node = $param->{'check-node'}; + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + my $node = $param->{'check-node'}; - die "Wrong node to check\n" - if defined($node) && $node ne 'localhost' && $node ne PVE::INotify::nodename(); + die "Wrong node to check\n" + if defined($node) && $node ne 'localhost' && $node ne PVE::INotify::nodename(); - my $cfg = PVE::Mapping::USB::config(); + my $cfg = PVE::Mapping::USB::config(); - my $res = []; + my $res = []; - my $privs = ['Mapping.Modify', 'Mapping.Use', 'Mapping.Audit']; + my $privs = ['Mapping.Modify', 'Mapping.Use', 'Mapping.Audit']; - for my $id (keys $cfg->{ids}->%*) { - next if !$rpcenv->check_full($authuser, "/mapping/usb/$id", $privs, 1, 1); - next if !$cfg->{ids}->{$id}; + for my $id (keys $cfg->{ids}->%*) { + next if !$rpcenv->check_full($authuser, "/mapping/usb/$id", $privs, 1, 1); + next if !$cfg->{ids}->{$id}; - my $entry = dclone($cfg->{ids}->{$id}); - $entry->{id} = $id; - $entry->{digest} = $cfg->{digest}; + my $entry = dclone($cfg->{ids}->{$id}); + $entry->{id} = $id; + $entry->{digest} = $cfg->{digest}; - if (defined($node)) { - $entry->{errors} = []; - if (my $mappings = PVE::Mapping::USB::get_node_mapping($cfg, $id, $node)) { - if (!scalar($mappings->@*)) { - push $entry->{errors}->@*, { - severity => 'warning', - message => "No mapping for node $node.", - }; - } - for my $mapping ($mappings->@*) { - eval { - PVE::Mapping::USB::assert_valid($id, $mapping); - }; - if (my $err = $@) { - push $entry->{errors}->@*, { - severity => 'error', - message => "Invalid configuration: $err", - }; - } - } - } - } + if (defined($node)) { + $entry->{errors} = []; + if (my $mappings = PVE::Mapping::USB::get_node_mapping($cfg, $id, $node)) { + if (!scalar($mappings->@*)) { + push $entry->{errors}->@*, + { + severity => 'warning', + message => "No mapping for node $node.", + }; + } + for my $mapping ($mappings->@*) { + eval { PVE::Mapping::USB::assert_valid($id, $mapping); }; + if (my $err = $@) { + push $entry->{errors}->@*, + { + severity => 'error', + message => "Invalid configuration: $err", + }; + } + } + } + } - push @$res, $entry; - } + push @$res, $entry; + } - return $res; + return $res; }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get', protected => 1, path => '{id}', method => 'GET', description => "Get USB Mapping.", permissions => { - check =>['or', - ['perm', '/mapping/usb/{id}', ['Mapping.Audit']], - ['perm', '/mapping/usb/{id}', ['Mapping.Use']], - ['perm', '/mapping/usb/{id}', ['Mapping.Modify']], - ], + check => [ + 'or', + ['perm', '/mapping/usb/{id}', ['Mapping.Audit']], + ['perm', '/mapping/usb/{id}', ['Mapping.Use']], + ['perm', '/mapping/usb/{id}', ['Mapping.Modify']], + ], }, parameters => { - additionalProperties => 0, - properties => { - id => { - type => 'string', - format => 'pve-configid', - }, - } + additionalProperties => 0, + properties => { + id => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { type => 'object' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $cfg = PVE::Mapping::USB::config(); - my $id = $param->{id}; + my $cfg = PVE::Mapping::USB::config(); + my $id = $param->{id}; - my $entry = $cfg->{ids}->{$id}; - die "mapping '$param->{id}' not found\n" if !defined($entry); + my $entry = $cfg->{ids}->{$id}; + die "mapping '$param->{id}' not found\n" if !defined($entry); - my $data = dclone($entry); + my $data = dclone($entry); - $data->{digest} = $cfg->{digest}; + $data->{digest} = $cfg->{digest}; - return $data; - }}); + return $data; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create', protected => 1, path => '', method => 'POST', description => "Create a new hardware mapping.", permissions => { - check => ['perm', '/mapping/usb', ['Mapping.Modify']], + check => ['perm', '/mapping/usb', ['Mapping.Modify']], }, parameters => PVE::Mapping::USB->createSchema(1), returns => { - type => 'null', + type => 'null', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'id'); + my $id = extract_param($param, 'id'); - my $plugin = PVE::Mapping::USB->lookup('usb'); - my $opts = $plugin->check_config($id, $param, 1, 1); + my $plugin = PVE::Mapping::USB->lookup('usb'); + my $opts = $plugin->check_config($id, $param, 1, 1); - PVE::Mapping::USB::lock_usb_config(sub { - my $cfg = PVE::Mapping::USB::config(); + PVE::Mapping::USB::lock_usb_config( + sub { + my $cfg = PVE::Mapping::USB::config(); - die "usb ID '$id' already defined\n" if defined($cfg->{ids}->{$id}); + die "usb ID '$id' already defined\n" if defined($cfg->{ids}->{$id}); - $cfg->{ids}->{$id} = $opts; + $cfg->{ids}->{$id} = $opts; - PVE::Mapping::USB::write_usb_config($cfg); + PVE::Mapping::USB::write_usb_config($cfg); - }, "create hardware mapping failed"); + }, + "create hardware mapping failed", + ); - return; + return; }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update', protected => 1, path => '{id}', method => 'PUT', description => "Update a hardware mapping.", permissions => { - check => ['perm', '/mapping/usb/{id}', ['Mapping.Modify']], + check => ['perm', '/mapping/usb/{id}', ['Mapping.Modify']], }, parameters => PVE::Mapping::USB->updateSchema(), returns => { - type => 'null', + type => 'null', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $digest = extract_param($param, 'digest'); - my $delete = extract_param($param, 'delete'); - my $id = extract_param($param, 'id'); + my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); + my $id = extract_param($param, 'id'); - if ($delete) { - $delete = [ PVE::Tools::split_list($delete) ]; - } + if ($delete) { + $delete = [PVE::Tools::split_list($delete)]; + } - PVE::Mapping::USB::lock_usb_config(sub { - my $cfg = PVE::Mapping::USB::config(); + PVE::Mapping::USB::lock_usb_config( + sub { + my $cfg = PVE::Mapping::USB::config(); - PVE::Tools::assert_if_modified($cfg->{digest}, $digest) if defined($digest); + PVE::Tools::assert_if_modified($cfg->{digest}, $digest) if defined($digest); - die "usb ID '$id' does not exist\n" if !defined($cfg->{ids}->{$id}); + die "usb ID '$id' does not exist\n" if !defined($cfg->{ids}->{$id}); - my $plugin = PVE::Mapping::USB->lookup('usb'); - my $opts = $plugin->check_config($id, $param, 1, 1); + my $plugin = PVE::Mapping::USB->lookup('usb'); + my $opts = $plugin->check_config($id, $param, 1, 1); - my $data = $cfg->{ids}->{$id}; + my $data = $cfg->{ids}->{$id}; - my $options = $plugin->private()->{options}->{usb}; - PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete); + my $options = $plugin->private()->{options}->{usb}; + PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete); - $data->{$_} = $opts->{$_} for keys $opts->%*; + $data->{$_} = $opts->{$_} for keys $opts->%*; - PVE::Mapping::USB::write_usb_config($cfg); + PVE::Mapping::USB::write_usb_config($cfg); - }, "update hardware mapping failed"); + }, + "update hardware mapping failed", + ); - return; + return; }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete', protected => 1, path => '{id}', method => 'DELETE', description => "Remove Hardware Mapping.", permissions => { - check => [ 'perm', '/mapping/usb', ['Mapping.Modify']], + check => ['perm', '/mapping/usb', ['Mapping.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - id => { - type => 'string', - format => 'pve-configid', - }, - } + additionalProperties => 0, + properties => { + id => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = $param->{id}; + my $id = $param->{id}; - PVE::Mapping::USB::lock_usb_config(sub { - my $cfg = PVE::Mapping::USB::config(); + PVE::Mapping::USB::lock_usb_config( + sub { + my $cfg = PVE::Mapping::USB::config(); - if ($cfg->{ids}->{$id}) { - delete $cfg->{ids}->{$id}; - } + if ($cfg->{ids}->{$id}) { + delete $cfg->{ids}->{$id}; + } - PVE::Mapping::USB::write_usb_config($cfg); + PVE::Mapping::USB::write_usb_config($cfg); - }, "delete usb mapping failed"); + }, + "delete usb mapping failed", + ); - return; - } + return; + }, }); 1; diff --git a/PVE/API2/Cluster/MetricServer.pm b/PVE/API2/Cluster/MetricServer.pm index 209b92a7..73c30ede 100644 --- a/PVE/API2/Cluster/MetricServer.pm +++ b/PVE/API2/Cluster/MetricServer.pm @@ -16,458 +16,474 @@ use PVE::RESTHandler; use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', description => "Metrics index.", permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $result = [ - { name => 'server' }, - ]; + my $result = [ + { name => 'server' }, + ]; - return $result; - } + return $result; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'server_index', path => 'server', method => 'GET', description => "List configured metric servers.", permissions => { - check => ['perm', '/', ['Sys.Audit']], + check => ['perm', '/', ['Sys.Audit']], }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - id => { - description => "The ID of the entry.", - type => 'string' - }, - disable => { - description => "Flag to disable the plugin.", - type => 'boolean', - }, - type => { - description => "Plugin type.", - type => 'string', - }, - server => { - description => "Server dns name or IP address", - type => 'string', - }, - port => { - description => "Server network port", - type => 'integer', - }, - }, - }, - links => [ { rel => 'child', href => "{id}" } ], + type => 'array', + items => { + type => "object", + properties => { + id => { + description => "The ID of the entry.", + type => 'string', + }, + disable => { + description => "Flag to disable the plugin.", + type => 'boolean', + }, + type => { + description => "Plugin type.", + type => 'string', + }, + server => { + description => "Server dns name or IP address", + type => 'string', + }, + port => { + description => "Server network port", + type => 'integer', + }, + }, + }, + links => [{ rel => 'child', href => "{id}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $res = []; - my $status_cfg = PVE::Cluster::cfs_read_file('status.cfg'); + my $res = []; + my $status_cfg = PVE::Cluster::cfs_read_file('status.cfg'); - for my $id (sort keys %{$status_cfg->{ids}}) { - my $plugin_config = $status_cfg->{ids}->{$id}; - push @$res, { - id => $id, - disable => $plugin_config->{disable} // 0, - type => $plugin_config->{type}, - server => $plugin_config->{server}, - port => $plugin_config->{port}, - }; - } + for my $id (sort keys %{ $status_cfg->{ids} }) { + my $plugin_config = $status_cfg->{ids}->{$id}; + push @$res, + { + id => $id, + disable => $plugin_config->{disable} // 0, + type => $plugin_config->{type}, + server => $plugin_config->{server}, + port => $plugin_config->{port}, + }; + } - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'read', path => 'server/{id}', method => 'GET', description => "Read metric server configuration.", permissions => { - check => ['perm', '/', ['Sys.Audit']], + check => ['perm', '/', ['Sys.Audit']], }, parameters => { - additionalProperties => 0, - properties => { - id => { - type => 'string', - format => 'pve-configid', - }, - }, + additionalProperties => 0, + properties => { + id => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { type => 'object' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $status_cfg = PVE::Cluster::cfs_read_file('status.cfg'); - my $id = $param->{id}; + my $status_cfg = PVE::Cluster::cfs_read_file('status.cfg'); + my $id = $param->{id}; - if (!defined($status_cfg->{ids}->{$id})) { - die "status server entry '$id' does not exist\n"; - } + if (!defined($status_cfg->{ids}->{$id})) { + die "status server entry '$id' does not exist\n"; + } - return $status_cfg->{ids}->{$id}; - }}); + return $status_cfg->{ids}->{$id}; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create', path => 'server/{id}', protected => 1, method => 'POST', description => "Create a new external metric server config", permissions => { - check => ['perm', '/', ['Sys.Modify']], + check => ['perm', '/', ['Sys.Modify']], }, parameters => PVE::Status::Plugin->createSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $type = extract_param($param, 'type'); - my $plugin = PVE::Status::Plugin->lookup($type); - my $id = extract_param($param, 'id'); + my $type = extract_param($param, 'type'); + my $plugin = PVE::Status::Plugin->lookup($type); + my $id = extract_param($param, 'id'); - my $sensitive_params = extract_sensitive_params($param, ['token'], []); + my $sensitive_params = extract_sensitive_params($param, ['token'], []); - PVE::Cluster::cfs_lock_file('status.cfg', undef, sub { - my $cfg = PVE::Cluster::cfs_read_file('status.cfg'); + PVE::Cluster::cfs_lock_file( + 'status.cfg', + undef, + sub { + my $cfg = PVE::Cluster::cfs_read_file('status.cfg'); - die "Metric server '$id' already exists\n" - if $cfg->{ids}->{$id}; + die "Metric server '$id' already exists\n" + if $cfg->{ids}->{$id}; - my $opts = $plugin->check_config($id, $param, 1, 1); + my $opts = $plugin->check_config($id, $param, 1, 1); - $cfg->{ids}->{$id} = $opts; + $cfg->{ids}->{$id} = $opts; - $plugin->on_add_hook($id, $opts, $sensitive_params); + $plugin->on_add_hook($id, $opts, $sensitive_params); - eval { - $plugin->test_connection($opts, $id); - }; + eval { $plugin->test_connection($opts, $id); }; - if (my $err = $@) { - eval { $plugin->on_delete_hook($id, $opts) }; - warn "$@\n" if $@; - die $err; - } + if (my $err = $@) { + eval { $plugin->on_delete_hook($id, $opts) }; + warn "$@\n" if $@; + die $err; + } - PVE::Cluster::cfs_write_file('status.cfg', $cfg); - }); - die $@ if $@; + PVE::Cluster::cfs_write_file('status.cfg', $cfg); + }, + ); + die $@ if $@; - return; - }}); + return; + }, +}); - -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update', protected => 1, path => 'server/{id}', method => 'PUT', description => "Update metric server configuration.", permissions => { - check => ['perm', '/', ['Sys.Modify']], + check => ['perm', '/', ['Sys.Modify']], }, parameters => PVE::Status::Plugin->updateSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'id'); - my $digest = extract_param($param, 'digest'); - my $delete = extract_param($param, 'delete'); + my $id = extract_param($param, 'id'); + my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); - if ($delete) { - $delete = [PVE::Tools::split_list($delete)]; - } + if ($delete) { + $delete = [PVE::Tools::split_list($delete)]; + } - my $sensitive_params = extract_sensitive_params($param, ['token'], $delete); + my $sensitive_params = extract_sensitive_params($param, ['token'], $delete); - PVE::Cluster::cfs_lock_file('status.cfg', undef, sub { - my $cfg = PVE::Cluster::cfs_read_file('status.cfg'); + PVE::Cluster::cfs_lock_file( + 'status.cfg', + undef, + sub { + my $cfg = PVE::Cluster::cfs_read_file('status.cfg'); - PVE::SectionConfig::assert_if_modified($cfg, $digest); + PVE::SectionConfig::assert_if_modified($cfg, $digest); - my $data = $cfg->{ids}->{$id}; - die "no such server '$id'\n" if !$data; + my $data = $cfg->{ids}->{$id}; + die "no such server '$id'\n" if !$data; - my $plugin = PVE::Status::Plugin->lookup($data->{type}); - my $opts = $plugin->check_config($id, $param, 0, 1); + my $plugin = PVE::Status::Plugin->lookup($data->{type}); + my $opts = $plugin->check_config($id, $param, 0, 1); - for my $k (keys %$opts) { - $data->{$k} = $opts->{$k}; - } + for my $k (keys %$opts) { + $data->{$k} = $opts->{$k}; + } - if ($delete) { - my $options = $plugin->private()->{options}->{$data->{type}}; - for my $k (@$delete) { - my $d = $options->{$k} || die "no such option '$k'\n"; - die "unable to delete required option '$k'\n" if !$d->{optional}; - die "unable to delete fixed option '$k'\n" if $d->{fixed}; - die "cannot set and delete property '$k' at the same time!\n" - if defined($opts->{$k}); + if ($delete) { + my $options = $plugin->private()->{options}->{ $data->{type} }; + for my $k (@$delete) { + my $d = $options->{$k} || die "no such option '$k'\n"; + die "unable to delete required option '$k'\n" if !$d->{optional}; + die "unable to delete fixed option '$k'\n" if $d->{fixed}; + die "cannot set and delete property '$k' at the same time!\n" + if defined($opts->{$k}); - delete $data->{$k}; - } - } + delete $data->{$k}; + } + } - $plugin->on_update_hook($id, $data, $sensitive_params); + $plugin->on_update_hook($id, $data, $sensitive_params); - $plugin->test_connection($data, $id); + $plugin->test_connection($data, $id); - PVE::Cluster::cfs_write_file('status.cfg', $cfg); - }); - die $@ if $@; + PVE::Cluster::cfs_write_file('status.cfg', $cfg); + }, + ); + die $@ if $@; - return; - }}); + return; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete', protected => 1, path => 'server/{id}', method => 'DELETE', description => "Remove Metric server.", permissions => { - check => ['perm', '/', ['Sys.Modify']], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - id => { - type => 'string', - format => 'pve-configid', - }, - } + additionalProperties => 0, + properties => { + id => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Cluster::cfs_lock_file('status.cfg', undef, sub { - my $cfg = PVE::Cluster::cfs_read_file('status.cfg'); + PVE::Cluster::cfs_lock_file( + 'status.cfg', + undef, + sub { + my $cfg = PVE::Cluster::cfs_read_file('status.cfg'); - my $id = $param->{id}; + my $id = $param->{id}; - my $plugin_cfg = $cfg->{ids}->{$id}; + my $plugin_cfg = $cfg->{ids}->{$id}; - my $plugin = PVE::Status::Plugin->lookup($plugin_cfg->{type}); + my $plugin = PVE::Status::Plugin->lookup($plugin_cfg->{type}); - $plugin->on_delete_hook($id, $plugin_cfg); + $plugin->on_delete_hook($id, $plugin_cfg); - delete $cfg->{ids}->{$id}; - PVE::Cluster::cfs_write_file('status.cfg', $cfg); - }); - die $@ if $@; + delete $cfg->{ids}->{$id}; + PVE::Cluster::cfs_write_file('status.cfg', $cfg); + }, + ); + die $@ if $@; - return; - }}); + return; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'export', path => 'export', method => 'GET', protected => 1, description => "Retrieve metrics of the cluster.", permissions => { - check => ['perm', '/', ['Sys.Audit']], + check => ['perm', '/', ['Sys.Audit']], }, parameters => { - additionalProperties => 0, - properties => { - 'local-only' => { - type => 'boolean', - description => - 'Only return metrics for the current node instead of the whole cluster', - optional => 1, - default => 0, - }, - 'start-time' => { - type => 'integer', - description => 'Only include metrics with a timestamp > start-time.', - optional => 1, - default => 0, - }, - 'history' => { - type => 'boolean', - description => 'Also return historic values.' - . ' Returns full available metric history unless `start-time` is also set', - optional => 1, - default => 0, - }, - }, + additionalProperties => 0, + properties => { + 'local-only' => { + type => 'boolean', + description => + 'Only return metrics for the current node instead of the whole cluster', + optional => 1, + default => 0, + }, + 'start-time' => { + type => 'integer', + description => 'Only include metrics with a timestamp > start-time.', + optional => 1, + default => 0, + }, + 'history' => { + type => 'boolean', + description => 'Also return historic values.' + . ' Returns full available metric history unless `start-time` is also set', + optional => 1, + default => 0, + }, + }, }, returns => { - type => 'object', - additionalProperties => 0, - properties => { - data => { - type => 'array', - description => 'Array of system metrics. Metrics are sorted by their timestamp.', - items => { - type => 'object', - additionalProperties => 0, - properties => { - timestamp => { - type => 'integer', - description => 'Time at which this metric was observed', - }, - id => { - type => 'string', - description => "Unique identifier for this metric object," - . " for instance 'node/' or" - . " 'qemu/'." - }, - metric => { - type => 'string', - description => "Name of the metric.", - }, - value => { - type => 'number', - description => 'Metric value.', - }, - type => { - type => 'string', - description => 'Type of the metric.', - enum => [qw(gauge counter derive)], - } - } - }, + type => 'object', + additionalProperties => 0, + properties => { + data => { + type => 'array', + description => + 'Array of system metrics. Metrics are sorted by their timestamp.', + items => { + type => 'object', + additionalProperties => 0, + properties => { + timestamp => { + type => 'integer', + description => 'Time at which this metric was observed', + }, + id => { + type => 'string', + description => "Unique identifier for this metric object," + . " for instance 'node/' or" + . " 'qemu/'.", + }, + metric => { + type => 'string', + description => "Name of the metric.", + }, + value => { + type => 'number', + description => 'Metric value.', + }, + type => { + type => 'string', + description => 'Type of the metric.', + enum => [qw(gauge counter derive)], + }, + }, + }, - }, + }, - } + }, }, code => sub { - my ($param) = @_; - my $local_only = $param->{'local-only'} // 0; - my $start = $param->{'start-time'}; - my $history = $param->{'history'} // 0; + my ($param) = @_; + my $local_only = $param->{'local-only'} // 0; + my $start = $param->{'start-time'}; + my $history = $param->{'history'} // 0; - my $now = time(); + my $now = time(); - my $generations; - if ($history) { - # Assuming update loop time of pvestatd of 10 seconds. - if (defined($start)) { - my $delta = $now - $start; - $generations = int($delta / 10); - } else { - $generations = PVE::PullMetric::max_generations(); - } + my $generations; + if ($history) { + # Assuming update loop time of pvestatd of 10 seconds. + if (defined($start)) { + my $delta = $now - $start; + $generations = int($delta / 10); + } else { + $generations = PVE::PullMetric::max_generations(); + } - } else { - $generations = 0; - }; + } else { + $generations = 0; + } - my @metrics = @{PVE::PullMetric::get_local_metrics($generations)}; + my @metrics = @{ PVE::PullMetric::get_local_metrics($generations) }; - if (defined($start)) { - @metrics = grep { - $_->{timestamp} > ($start) - } @metrics; - } + if (defined($start)) { + @metrics = grep { + $_->{timestamp} > ($start) + } @metrics; + } - my $nodename = PVE::INotify::nodename(); + my $nodename = PVE::INotify::nodename(); - # Fan out to cluster members - # Do NOT remove this check - if (!$local_only) { - my $members = PVE::Cluster::get_members(); + # Fan out to cluster members + # Do NOT remove this check + if (!$local_only) { + my $members = PVE::Cluster::get_members(); - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my ($user, undef) = PVE::AccessControl::split_tokenid($authuser, 1); + my ($user, undef) = PVE::AccessControl::split_tokenid($authuser, 1); - my $ticket; - if ($user) { - # Theoretically, we might now bypass token privilege separation, since - # we use the regular user instead of the token, but - # since we already passed the permission check for this handler, - # this should be fine. - $ticket = PVE::AccessControl::assemble_ticket($user); - } else { - $ticket = PVE::AccessControl::assemble_ticket($authuser); - } + my $ticket; + if ($user) { + # Theoretically, we might now bypass token privilege separation, since + # we use the regular user instead of the token, but + # since we already passed the permission check for this handler, + # this should be fine. + $ticket = PVE::AccessControl::assemble_ticket($user); + } else { + $ticket = PVE::AccessControl::assemble_ticket($authuser); + } - for my $name (keys %$members) { - if ($name eq $nodename) { - # Skip own node, for that one we already have the metrics - next; - } + for my $name (keys %$members) { + if ($name eq $nodename) { + # Skip own node, for that one we already have the metrics + next; + } - if (!$members->{$name}->{online}) { - next; - } + if (!$members->{$name}->{online}) { + next; + } - my $status = eval { - my $fingerprint = PVE::Cluster::get_node_fingerprint($name); - my $ip = scalar(PVE::Cluster::remote_node_ip($name)); + my $status = eval { + my $fingerprint = PVE::Cluster::get_node_fingerprint($name); + my $ip = scalar(PVE::Cluster::remote_node_ip($name)); - my $conn_args = { - protocol => 'https', - host => $ip, - port => 8006, - ticket => $ticket, - timeout => 5, - }; + my $conn_args = { + protocol => 'https', + host => $ip, + port => 8006, + ticket => $ticket, + timeout => 5, + }; - $conn_args->{cached_fingerprints} = { $fingerprint => 1 }; + $conn_args->{cached_fingerprints} = { $fingerprint => 1 }; - my $api_client = PVE::APIClient::LWP->new(%$conn_args); + my $api_client = PVE::APIClient::LWP->new(%$conn_args); - my $params = { - # Do NOT remove 'local-only' - potential for request recursion! - 'local-only' => 1, - history => $history, - }; - $params->{'start-time'} = $start if defined($start); + my $params = { + # Do NOT remove 'local-only' - potential for request recursion! + 'local-only' => 1, + history => $history, + }; + $params->{'start-time'} = $start if defined($start); - $api_client->get('/cluster/metrics/export', $params); - }; + $api_client->get('/cluster/metrics/export', $params); + }; - if ($@) { - syslog('warning', "could not fetch metrics from $name: $@"); - } else { - push @metrics, $status->{data}->@*; - } - } - } + if ($@) { + syslog('warning', "could not fetch metrics from $name: $@"); + } else { + push @metrics, $status->{data}->@*; + } + } + } - my @sorted = sort {$a->{timestamp} <=> $b->{timestamp}} @metrics; + my @sorted = sort { $a->{timestamp} <=> $b->{timestamp} } @metrics; - return { - data => \@sorted, - }; + return { + data => \@sorted, + }; }, }); diff --git a/PVE/API2/Cluster/Notifications.pm b/PVE/API2/Cluster/Notifications.pm index a61ab839..8b455227 100644 --- a/PVE/API2/Cluster/Notifications.pm +++ b/PVE/API2/Cluster/Notifications.pm @@ -19,7 +19,7 @@ sub make_properties_optional { $properties = dclone($properties); for my $key (keys %$properties) { - $properties->{$key}->{optional} = 1 if $key ne 'name'; + $properties->{$key}->{optional} = 1 if $key ne 'name'; } return $properties; @@ -30,9 +30,9 @@ sub remove_protected_properties { $properties = dclone($properties); for my $key (keys %$properties) { - if (grep /^$key$/, @$to_remove) { - delete $properties->{$key}; - } + if (grep /^$key$/, @$to_remove) { + delete $properties->{$key}; + } } return $properties; @@ -42,7 +42,7 @@ sub raise_api_error { my ($api_error) = @_; if (!(ref($api_error) eq 'HASH' && $api_error->{message} && $api_error->{code})) { - die $api_error; + die $api_error; } my $msg = "$api_error->{message}\n"; @@ -56,1643 +56,1638 @@ sub raise_api_error { die $exc; } -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', description => 'Index for notification-related API endpoints.', permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => 'object', - properties => {}, - }, - links => [ { rel => 'child', href => '{name}' } ], + type => 'array', + items => { + type => 'object', + properties => {}, + }, + links => [{ rel => 'child', href => '{name}' }], }, code => sub { - my $result = [ - { name => 'endpoints' }, - { name => 'matchers' }, - { name => 'targets' }, - { name => 'matcher-fields' }, - { name => 'matcher-field-values' }, - ]; + my $result = [ + { name => 'endpoints' }, + { name => 'matchers' }, + { name => 'targets' }, + { name => 'matcher-fields' }, + { name => 'matcher-field-values' }, + ]; - return $result; - } + return $result; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_matcher_fields', path => 'matcher-fields', method => 'GET', description => 'Returns known notification metadata fields', permissions => { - check => ['or', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['perm', '/mapping/notifications', ['Mapping.Audit']], - ], + check => [ + 'or', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + ['perm', '/mapping/notifications', ['Mapping.Audit']], + ], }, protected => 0, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => 'object', - properties => { - name => { - description => 'Name of the field.', - type => 'string', - }, - }, - }, - links => [ { rel => 'child', href => '{name}' } ], + type => 'array', + items => { + type => 'object', + properties => { + name => { + description => 'Name of the field.', + type => 'string', + }, + }, + }, + links => [{ rel => 'child', href => '{name}' }], }, code => sub { - # TODO: Adapt this API handler once we have a 'notification registry' + # TODO: Adapt this API handler once we have a 'notification registry' - my $result = [ - { name => 'type' }, - { name => 'hostname' }, - { name => 'job-id' }, - ]; + my $result = [ + { name => 'type' }, { name => 'hostname' }, { name => 'job-id' }, + ]; - return $result; - } + return $result; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_matcher_field_values', path => 'matcher-field-values', method => 'GET', description => 'Returns known notification metadata fields and their known values', permissions => { - check => ['or', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['perm', '/mapping/notifications', ['Mapping.Audit']], - ], + check => [ + 'or', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + ['perm', '/mapping/notifications', ['Mapping.Audit']], + ], }, protected => 1, parameters => { - additionalProperties => 0, + additionalProperties => 0, }, returns => { - type => 'array', - items => { - type => 'object', - properties => { - 'value' => { - description => 'Notification metadata value known by the system.', - type => 'string' - }, - 'comment' => { - description => 'Additional comment for this value.', - type => 'string', - optional => 1, - }, - 'field' => { - description => 'Field this value belongs to.', - type => 'string', - }, - }, - }, + type => 'array', + items => { + type => 'object', + properties => { + 'value' => { + description => 'Notification metadata value known by the system.', + type => 'string', + }, + 'comment' => { + description => 'Additional comment for this value.', + type => 'string', + optional => 1, + }, + 'field' => { + description => 'Field this value belongs to.', + type => 'string', + }, + }, + }, }, code => sub { - # TODO: Adapt this API handler once we have a 'notification registry' - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + # TODO: Adapt this API handler once we have a 'notification registry' + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); - my $values = [ - { - value => 'package-updates', - field => 'type', - }, - { - value => 'fencing', - field => 'type', - }, - { - value => 'replication', - field => 'type', - }, - { - value => 'vzdump', - field => 'type', - }, - { - value => 'system-mail', - field => 'type', - }, - ]; + my $values = [ + { + value => 'package-updates', + field => 'type', + }, + { + value => 'fencing', + field => 'type', + }, + { + value => 'replication', + field => 'type', + }, + { + value => 'vzdump', + field => 'type', + }, + { + value => 'system-mail', + field => 'type', + }, + ]; - # Here we need a manual permission check. - if ($rpcenv->check($user, "/", ["Sys.Audit"], 1)) { - for my $backup_job (@{PVE::API2::Backup->index({})}) { - push @$values, { - value => $backup_job->{id}, - comment => $backup_job->{comment}, - field => 'job-id' - }; - } - } - # The API call returns only returns jobs for which the user - # has adequate permissions. - for my $sync_job (@{PVE::API2::ReplicationConfig->index({})}) { - push @$values, { - value => $sync_job->{id}, - comment => $sync_job->{comment}, - field => 'job-id' - }; - } + # Here we need a manual permission check. + if ($rpcenv->check($user, "/", ["Sys.Audit"], 1)) { + for my $backup_job (@{ PVE::API2::Backup->index({}) }) { + push @$values, + { + value => $backup_job->{id}, + comment => $backup_job->{comment}, + field => 'job-id', + }; + } + } + # The API call returns only returns jobs for which the user + # has adequate permissions. + for my $sync_job (@{ PVE::API2::ReplicationConfig->index({}) }) { + push @$values, + { + value => $sync_job->{id}, + comment => $sync_job->{comment}, + field => 'job-id', + }; + } - for my $node (@{PVE::Cluster::get_nodelist()}) { - push @$values, { - value => $node, - field => 'hostname', - } - } + for my $node (@{ PVE::Cluster::get_nodelist() }) { + push @$values, + { + value => $node, + field => 'hostname', + }; + } - return $values; - } + return $values; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'endpoints_index', path => 'endpoints', method => 'GET', description => 'Index for all available endpoint types.', permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => 'object', - properties => {}, - }, - links => [ { rel => 'child', href => '{name}' } ], + type => 'array', + items => { + type => 'object', + properties => {}, + }, + links => [{ rel => 'child', href => '{name}' }], }, code => sub { - my $result = [ - { name => 'gotify' }, - { name => 'sendmail' }, - { name => 'smtp' }, - { name => 'webhook' }, - ]; + my $result = [ + { name => 'gotify' }, + { name => 'sendmail' }, + { name => 'smtp' }, + { name => 'webhook' }, + ]; - return $result; - } + return $result; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_all_targets', path => 'targets', method => 'GET', description => 'Returns a list of all entities that can be used as notification targets.', permissions => { - check => ['or', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['perm', '/mapping/notifications', ['Mapping.Audit']], - ['perm', '/mapping/notifications', ['Mapping.Use']], - ], + check => [ + 'or', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + ['perm', '/mapping/notifications', ['Mapping.Audit']], + ['perm', '/mapping/notifications', ['Mapping.Use']], + ], }, protected => 1, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => 'object', - properties => { - name => { - description => 'Name of the target.', - type => 'string', - format => 'pve-configid', - }, - 'type' => { - description => 'Type of the target.', - type => 'string', - enum => [qw(sendmail gotify smtp webhook)], - }, - 'comment' => { - description => 'Comment', - type => 'string', - optional => 1, - }, - 'disable' => { - description => 'Show if this target is disabled', - type => 'boolean', - optional => 1, - default => 0, - }, - 'origin' => { - description => 'Show if this entry was created by a user or was built-in', - type => 'string', - enum => [qw(user-created builtin modified-builtin)], - }, - }, - }, - links => [ { rel => 'child', href => '{name}' } ], + type => 'array', + items => { + type => 'object', + properties => { + name => { + description => 'Name of the target.', + type => 'string', + format => 'pve-configid', + }, + 'type' => { + description => 'Type of the target.', + type => 'string', + enum => [qw(sendmail gotify smtp webhook)], + }, + 'comment' => { + description => 'Comment', + type => 'string', + optional => 1, + }, + 'disable' => { + description => 'Show if this target is disabled', + type => 'boolean', + optional => 1, + default => 0, + }, + 'origin' => { + description => 'Show if this entry was created by a user or was built-in', + type => 'string', + enum => [qw(user-created builtin modified-builtin)], + }, + }, + }, + links => [{ rel => 'child', href => '{name}' }], }, code => sub { - my $config = PVE::Notify::read_config(); + my $config = PVE::Notify::read_config(); - my $targets = eval { - $config->get_targets(); - }; + my $targets = eval { $config->get_targets(); }; - raise_api_error($@) if $@; + raise_api_error($@) if $@; - return $targets; - } + return $targets; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'test_target', path => 'targets/{name}/test', protected => 1, method => 'POST', description => 'Send a test notification to a provided target.', permissions => { - check => ['or', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['perm', '/mapping/notifications', ['Mapping.Audit']], - ['perm', '/mapping/notifications', ['Mapping.Use']], - ], + check => [ + 'or', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + ['perm', '/mapping/notifications', ['Mapping.Audit']], + ['perm', '/mapping/notifications', ['Mapping.Use']], + ], }, parameters => { - additionalProperties => 0, - properties => { - name => { - description => 'Name of the target.', - type => 'string', - format => 'pve-configid' - }, - }, + additionalProperties => 0, + properties => { + name => { + description => 'Name of the target.', + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; - my $name = extract_param($param, 'name'); + my ($param) = @_; + my $name = extract_param($param, 'name'); - eval { - my $config = PVE::Notify::read_config(); - $config->test_target($name); - }; + eval { + my $config = PVE::Notify::read_config(); + $config->test_target($name); + }; - raise_api_error($@) if $@; + raise_api_error($@) if $@; - return; - } + return; + }, }); my $sendmail_properties = { name => { - description => 'The name of the endpoint.', - type => 'string', - format => 'pve-configid', + description => 'The name of the endpoint.', + type => 'string', + format => 'pve-configid', }, mailto => { - type => 'array', - items => { - type => 'string', - format => 'email-or-username', - }, - description => 'List of email recipients', - optional => 1, + type => 'array', + items => { + type => 'string', + format => 'email-or-username', + }, + description => 'List of email recipients', + optional => 1, }, 'mailto-user' => { - type => 'array', - items => { - type => 'string', - format => 'pve-userid', - }, - description => 'List of users', - optional => 1, + type => 'array', + items => { + type => 'string', + format => 'pve-userid', + }, + description => 'List of users', + optional => 1, }, 'from-address' => { - description => '`From` address for the mail', - type => 'string', - optional => 1, + description => '`From` address for the mail', + type => 'string', + optional => 1, }, author => { - description => 'Author of the mail', - type => 'string', - optional => 1, + description => 'Author of the mail', + type => 'string', + optional => 1, }, 'comment' => { - description => 'Comment', - type => 'string', - optional => 1, + description => 'Comment', + type => 'string', + optional => 1, }, 'disable' => { - description => 'Disable this target', - type => 'boolean', - optional => 1, - default => 0, + description => 'Disable this target', + type => 'boolean', + optional => 1, + default => 0, }, }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_sendmail_endpoints', path => 'endpoints/sendmail', method => 'GET', description => 'Returns a list of all sendmail endpoints', permissions => { - check => ['or', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['perm', '/mapping/notifications', ['Mapping.Audit']], - ], + check => [ + 'or', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + ['perm', '/mapping/notifications', ['Mapping.Audit']], + ], }, protected => 1, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => 'object', - properties => { - %$sendmail_properties, - 'origin' => { - description => 'Show if this entry was created by a user or was built-in', - type => 'string', - enum => [qw(user-created builtin modified-builtin)], - }, - }, - }, - links => [ { rel => 'child', href => '{name}' } ], + type => 'array', + items => { + type => 'object', + properties => { + %$sendmail_properties, + 'origin' => { + description => 'Show if this entry was created by a user or was built-in', + type => 'string', + enum => [qw(user-created builtin modified-builtin)], + }, + }, + }, + links => [{ rel => 'child', href => '{name}' }], }, code => sub { - my $config = PVE::Notify::read_config(); + my $config = PVE::Notify::read_config(); - my $entities = eval { - $config->get_sendmail_endpoints(); - }; - raise_api_error($@) if $@; + my $entities = eval { $config->get_sendmail_endpoints(); }; + raise_api_error($@) if $@; - return $entities; - } + return $entities; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_sendmail_endpoint', path => 'endpoints/sendmail/{name}', method => 'GET', description => 'Return a specific sendmail endpoint', permissions => { - check => ['or', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['perm', '/mapping/notifications', ['Mapping.Audit']], - ], + check => [ + 'or', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + ['perm', '/mapping/notifications', ['Mapping.Audit']], + ], }, protected => 1, parameters => { - additionalProperties => 0, - properties => { - name => { - type => 'string', - format => 'pve-configid', - }, - } + additionalProperties => 0, + properties => { + name => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { - type => 'object', - properties => { - %$sendmail_properties, - digest => get_standard_option('pve-config-digest'), - } + type => 'object', + properties => { + %$sendmail_properties, digest => get_standard_option('pve-config-digest'), + }, }, code => sub { - my ($param) = @_; - my $name = extract_param($param, 'name'); + my ($param) = @_; + my $name = extract_param($param, 'name'); - my $config = PVE::Notify::read_config(); - my $endpoint = eval { - $config->get_sendmail_endpoint($name) - }; + my $config = PVE::Notify::read_config(); + my $endpoint = eval { $config->get_sendmail_endpoint($name) }; - raise_api_error($@) if $@; - $endpoint->{digest} = $config->digest(); + raise_api_error($@) if $@; + $endpoint->{digest} = $config->digest(); - return $endpoint; - } + return $endpoint; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create_sendmail_endpoint', path => 'endpoints/sendmail', protected => 1, method => 'POST', description => 'Create a new sendmail endpoint', permissions => { - check => ['and', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['or', - ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]], - ['perm', '/', [ 'Sys.AccessNetwork' ]], - ], - ], + check => [ + 'and', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + [ + 'or', + ['perm', '/', ['Sys.Audit', 'Sys.Modify']], + ['perm', '/', ['Sys.AccessNetwork']], + ], + ], }, parameters => { - additionalProperties => 0, - properties => $sendmail_properties, + additionalProperties => 0, + properties => $sendmail_properties, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $name = extract_param($param, 'name'); - my $mailto = extract_param($param, 'mailto'); - my $mailto_user = extract_param($param, 'mailto-user'); - my $from_address = extract_param($param, 'from-address'); - my $author = extract_param($param, 'author'); - my $comment = extract_param($param, 'comment'); - my $disable = extract_param($param, 'disable'); + my $name = extract_param($param, 'name'); + my $mailto = extract_param($param, 'mailto'); + my $mailto_user = extract_param($param, 'mailto-user'); + my $from_address = extract_param($param, 'from-address'); + my $author = extract_param($param, 'author'); + my $comment = extract_param($param, 'comment'); + my $disable = extract_param($param, 'disable'); - eval { - PVE::Notify::lock_config(sub { - my $config = PVE::Notify::read_config(); + eval { + PVE::Notify::lock_config(sub { + my $config = PVE::Notify::read_config(); - $config->add_sendmail_endpoint( - $name, - $mailto, - $mailto_user, - $from_address, - $author, - $comment, - $disable, - ); + $config->add_sendmail_endpoint( + $name, $mailto, $mailto_user, $from_address, $author, $comment, $disable, + ); - PVE::Notify::write_config($config); - }); - }; + PVE::Notify::write_config($config); + }); + }; - raise_api_error($@) if $@; - return; - } + raise_api_error($@) if $@; + return; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update_sendmail_endpoint', path => 'endpoints/sendmail/{name}', protected => 1, method => 'PUT', description => 'Update existing sendmail endpoint', permissions => { - check => ['and', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['or', - ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]], - ['perm', '/', [ 'Sys.AccessNetwork' ]], - ], - ], + check => [ + 'and', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + [ + 'or', + ['perm', '/', ['Sys.Audit', 'Sys.Modify']], + ['perm', '/', ['Sys.AccessNetwork']], + ], + ], }, parameters => { - additionalProperties => 0, - properties => { - %{ make_properties_optional($sendmail_properties) }, - delete => { - type => 'array', - items => { - type => 'string', - format => 'pve-configid', - }, - optional => 1, - description => 'A list of settings you want to delete.', - }, - digest => get_standard_option('pve-config-digest'), + additionalProperties => 0, + properties => { + %{ make_properties_optional($sendmail_properties) }, + delete => { + type => 'array', + items => { + type => 'string', + format => 'pve-configid', + }, + optional => 1, + description => 'A list of settings you want to delete.', + }, + digest => get_standard_option('pve-config-digest'), - } + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $name = extract_param($param, 'name'); - my $mailto = extract_param($param, 'mailto'); - my $mailto_user = extract_param($param, 'mailto-user'); - my $from_address = extract_param($param, 'from-address'); - my $author = extract_param($param, 'author'); - my $comment = extract_param($param, 'comment'); - my $disable = extract_param($param, 'disable'); + my $name = extract_param($param, 'name'); + my $mailto = extract_param($param, 'mailto'); + my $mailto_user = extract_param($param, 'mailto-user'); + my $from_address = extract_param($param, 'from-address'); + my $author = extract_param($param, 'author'); + my $comment = extract_param($param, 'comment'); + my $disable = extract_param($param, 'disable'); - my $delete = extract_param($param, 'delete'); - my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); + my $digest = extract_param($param, 'digest'); - eval { - PVE::Notify::lock_config(sub { - my $config = PVE::Notify::read_config(); + eval { + PVE::Notify::lock_config(sub { + my $config = PVE::Notify::read_config(); - $config->update_sendmail_endpoint( - $name, - $mailto, - $mailto_user, - $from_address, - $author, - $comment, - $disable, - $delete, - $digest, - ); + $config->update_sendmail_endpoint( + $name, + $mailto, + $mailto_user, + $from_address, + $author, + $comment, + $disable, + $delete, + $digest, + ); - PVE::Notify::write_config($config); - }); - }; + PVE::Notify::write_config($config); + }); + }; - raise_api_error($@) if $@; - return; - } + raise_api_error($@) if $@; + return; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete_sendmail_endpoint', protected => 1, path => 'endpoints/sendmail/{name}', method => 'DELETE', description => 'Remove sendmail endpoint', permissions => { - check => ['perm', '/mapping/notifications', ['Mapping.Modify']], + check => ['perm', '/mapping/notifications', ['Mapping.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - name => { - type => 'string', - format => 'pve-configid', - }, - } + additionalProperties => 0, + properties => { + name => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; - my $name = extract_param($param, 'name'); + my ($param) = @_; + my $name = extract_param($param, 'name'); - eval { - PVE::Notify::lock_config(sub { - my $config = PVE::Notify::read_config(); - $config->delete_sendmail_endpoint($name); - PVE::Notify::write_config($config); - }); - }; + eval { + PVE::Notify::lock_config(sub { + my $config = PVE::Notify::read_config(); + $config->delete_sendmail_endpoint($name); + PVE::Notify::write_config($config); + }); + }; - raise_api_error($@) if ($@); - return; - } + raise_api_error($@) if ($@); + return; + }, }); my $gotify_properties = { name => { - description => 'The name of the endpoint.', - type => 'string', - format => 'pve-configid', + description => 'The name of the endpoint.', + type => 'string', + format => 'pve-configid', }, 'server' => { - description => 'Server URL', - type => 'string', + description => 'Server URL', + type => 'string', }, 'token' => { - description => 'Secret token', - type => 'string', + description => 'Secret token', + type => 'string', }, 'comment' => { - description => 'Comment', - type => 'string', - optional => 1, + description => 'Comment', + type => 'string', + optional => 1, }, 'disable' => { - description => 'Disable this target', - type => 'boolean', - optional => 1, - default => 0, + description => 'Disable this target', + type => 'boolean', + optional => 1, + default => 0, }, }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_gotify_endpoints', path => 'endpoints/gotify', method => 'GET', description => 'Returns a list of all gotify endpoints', protected => 1, permissions => { - check => ['perm', '/mapping/notifications', ['Mapping.Modify']], - check => ['perm', '/mapping/notifications', ['Mapping.Audit']], + check => ['perm', '/mapping/notifications', ['Mapping.Modify']], + check => ['perm', '/mapping/notifications', ['Mapping.Audit']], }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => 'object', - properties => { - % {remove_protected_properties($gotify_properties, ['token'])}, - 'origin' => { - description => 'Show if this entry was created by a user or was built-in', - type => 'string', - enum => [qw(user-created builtin modified-builtin)], - }, - }, - }, - links => [ { rel => 'child', href => '{name}' } ], + type => 'array', + items => { + type => 'object', + properties => { + %{ remove_protected_properties($gotify_properties, ['token']) }, + 'origin' => { + description => 'Show if this entry was created by a user or was built-in', + type => 'string', + enum => [qw(user-created builtin modified-builtin)], + }, + }, + }, + links => [{ rel => 'child', href => '{name}' }], }, code => sub { - my $config = PVE::Notify::read_config(); - my $rpcenv = PVE::RPCEnvironment::get(); + my $config = PVE::Notify::read_config(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $entities = eval { - $config->get_gotify_endpoints(); - }; - raise_api_error($@) if $@; + my $entities = eval { $config->get_gotify_endpoints(); }; + raise_api_error($@) if $@; - return $entities; - } + return $entities; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_gotify_endpoint', path => 'endpoints/gotify/{name}', method => 'GET', description => 'Return a specific gotify endpoint', protected => 1, permissions => { - check => ['or', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['perm', '/mapping/notifications', ['Mapping.Audit']], - ], + check => [ + 'or', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + ['perm', '/mapping/notifications', ['Mapping.Audit']], + ], }, parameters => { - additionalProperties => 0, - properties => { - name => { - type => 'string', - format => 'pve-configid', - description => 'Name of the endpoint.' - }, - } + additionalProperties => 0, + properties => { + name => { + type => 'string', + format => 'pve-configid', + description => 'Name of the endpoint.', + }, + }, }, returns => { - type => 'object', - properties => { - %{ remove_protected_properties($gotify_properties, ['token']) }, - digest => get_standard_option('pve-config-digest'), - } + type => 'object', + properties => { + %{ remove_protected_properties($gotify_properties, ['token']) }, + digest => get_standard_option('pve-config-digest'), + }, }, code => sub { - my ($param) = @_; - my $name = extract_param($param, 'name'); + my ($param) = @_; + my $name = extract_param($param, 'name'); - my $config = PVE::Notify::read_config(); - my $endpoint = eval { - $config->get_gotify_endpoint($name) - }; + my $config = PVE::Notify::read_config(); + my $endpoint = eval { $config->get_gotify_endpoint($name) }; - raise_api_error($@) if $@; - $endpoint->{digest} = $config->digest(); + raise_api_error($@) if $@; + $endpoint->{digest} = $config->digest(); - return $endpoint; - } + return $endpoint; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create_gotify_endpoint', path => 'endpoints/gotify', protected => 1, method => 'POST', description => 'Create a new gotify endpoint', permissions => { - check => ['and', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['or', - ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]], - ['perm', '/', [ 'Sys.AccessNetwork' ]], - ], - ], + check => [ + 'and', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + [ + 'or', + ['perm', '/', ['Sys.Audit', 'Sys.Modify']], + ['perm', '/', ['Sys.AccessNetwork']], + ], + ], }, parameters => { - additionalProperties => 0, - properties => $gotify_properties, + additionalProperties => 0, + properties => $gotify_properties, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $name = extract_param($param, 'name'); - my $server = extract_param($param, 'server'); - my $token = extract_param($param, 'token'); - my $comment = extract_param($param, 'comment'); - my $disable = extract_param($param, 'disable'); + my $name = extract_param($param, 'name'); + my $server = extract_param($param, 'server'); + my $token = extract_param($param, 'token'); + my $comment = extract_param($param, 'comment'); + my $disable = extract_param($param, 'disable'); - eval { - PVE::Notify::lock_config(sub { - my $config = PVE::Notify::read_config(); + eval { + PVE::Notify::lock_config(sub { + my $config = PVE::Notify::read_config(); - $config->add_gotify_endpoint( - $name, - $server, - $token, - $comment, - $disable, - ); + $config->add_gotify_endpoint( + $name, $server, $token, $comment, $disable, + ); - PVE::Notify::write_config($config); - }); - }; + PVE::Notify::write_config($config); + }); + }; - raise_api_error($@) if $@; - return; - } + raise_api_error($@) if $@; + return; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update_gotify_endpoint', path => 'endpoints/gotify/{name}', protected => 1, method => 'PUT', description => 'Update existing gotify endpoint', permissions => { - check => ['and', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['or', - ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]], - ['perm', '/', [ 'Sys.AccessNetwork' ]], - ], - ], + check => [ + 'and', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + [ + 'or', + ['perm', '/', ['Sys.Audit', 'Sys.Modify']], + ['perm', '/', ['Sys.AccessNetwork']], + ], + ], }, parameters => { - additionalProperties => 0, - properties => { - %{ make_properties_optional($gotify_properties) }, - delete => { - type => 'array', - items => { - type => 'string', - format => 'pve-configid', - }, - optional => 1, - description => 'A list of settings you want to delete.', - }, - digest => get_standard_option('pve-config-digest'), - } + additionalProperties => 0, + properties => { + %{ make_properties_optional($gotify_properties) }, + delete => { + type => 'array', + items => { + type => 'string', + format => 'pve-configid', + }, + optional => 1, + description => 'A list of settings you want to delete.', + }, + digest => get_standard_option('pve-config-digest'), + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $name = extract_param($param, 'name'); - my $server = extract_param($param, 'server'); - my $token = extract_param($param, 'token'); - my $comment = extract_param($param, 'comment'); - my $disable = extract_param($param, 'disable'); + my $name = extract_param($param, 'name'); + my $server = extract_param($param, 'server'); + my $token = extract_param($param, 'token'); + my $comment = extract_param($param, 'comment'); + my $disable = extract_param($param, 'disable'); - my $delete = extract_param($param, 'delete'); - my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); + my $digest = extract_param($param, 'digest'); - eval { - PVE::Notify::lock_config(sub { - my $config = PVE::Notify::read_config(); + eval { + PVE::Notify::lock_config(sub { + my $config = PVE::Notify::read_config(); - $config->update_gotify_endpoint( - $name, - $server, - $token, - $comment, - $disable, - $delete, - $digest, - ); + $config->update_gotify_endpoint( + $name, + $server, + $token, + $comment, + $disable, + $delete, + $digest, + ); - PVE::Notify::write_config($config); - }); - }; + PVE::Notify::write_config($config); + }); + }; - raise_api_error($@) if $@; - return; - } + raise_api_error($@) if $@; + return; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete_gotify_endpoint', protected => 1, path => 'endpoints/gotify/{name}', method => 'DELETE', description => 'Remove gotify endpoint', permissions => { - check => ['perm', '/mapping/notifications', ['Mapping.Modify']], + check => ['perm', '/mapping/notifications', ['Mapping.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - name => { - type => 'string', - format => 'pve-configid', - }, - } + additionalProperties => 0, + properties => { + name => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; - my $name = extract_param($param, 'name'); + my ($param) = @_; + my $name = extract_param($param, 'name'); - eval { - PVE::Notify::lock_config(sub { - my $config = PVE::Notify::read_config(); - $config->delete_gotify_endpoint($name); - PVE::Notify::write_config($config); - }); - }; + eval { + PVE::Notify::lock_config(sub { + my $config = PVE::Notify::read_config(); + $config->delete_gotify_endpoint($name); + PVE::Notify::write_config($config); + }); + }; - raise_api_error($@) if $@; - return; - } + raise_api_error($@) if $@; + return; + }, }); -my $smtp_properties= { +my $smtp_properties = { name => { - description => 'The name of the endpoint.', - type => 'string', - format => 'pve-configid', + description => 'The name of the endpoint.', + type => 'string', + format => 'pve-configid', }, server => { - description => 'The address of the SMTP server.', - type => 'string', + description => 'The address of the SMTP server.', + type => 'string', }, port => { - description => 'The port to be used. Defaults to 465 for TLS based connections,' - . ' 587 for STARTTLS based connections and port 25 for insecure plain-text' - . ' connections.', - type => 'integer', - optional => 1, + description => 'The port to be used. Defaults to 465 for TLS based connections,' + . ' 587 for STARTTLS based connections and port 25 for insecure plain-text' + . ' connections.', + type => 'integer', + optional => 1, }, mode => { - description => 'Determine which encryption method shall be used for the connection.', - type => 'string', - enum => [ qw(insecure starttls tls) ], - default => 'tls', - optional => 1, + description => 'Determine which encryption method shall be used for the connection.', + type => 'string', + enum => [qw(insecure starttls tls)], + default => 'tls', + optional => 1, }, username => { - description => 'Username for SMTP authentication', - type => 'string', - optional => 1, + description => 'Username for SMTP authentication', + type => 'string', + optional => 1, }, password => { - description => 'Password for SMTP authentication', - type => 'string', - optional => 1, + description => 'Password for SMTP authentication', + type => 'string', + optional => 1, }, mailto => { - type => 'array', - items => { - type => 'string', - format => 'email-or-username', - }, - description => 'List of email recipients', - optional => 1, + type => 'array', + items => { + type => 'string', + format => 'email-or-username', + }, + description => 'List of email recipients', + optional => 1, }, 'mailto-user' => { - type => 'array', - items => { - type => 'string', - format => 'pve-userid', - }, - description => 'List of users', - optional => 1, + type => 'array', + items => { + type => 'string', + format => 'pve-userid', + }, + description => 'List of users', + optional => 1, }, 'from-address' => { - description => '`From` address for the mail', - type => 'string', + description => '`From` address for the mail', + type => 'string', }, author => { - description => 'Author of the mail. Defaults to \'Proxmox VE\'.', - type => 'string', - optional => 1, + description => 'Author of the mail. Defaults to \'Proxmox VE\'.', + type => 'string', + optional => 1, }, 'comment' => { - description => 'Comment', - type => 'string', - optional => 1, + description => 'Comment', + type => 'string', + optional => 1, }, 'disable' => { - description => 'Disable this target', - type => 'boolean', - optional => 1, - default => 0, + description => 'Disable this target', + type => 'boolean', + optional => 1, + default => 0, }, }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_smtp_endpoints', path => 'endpoints/smtp', method => 'GET', description => 'Returns a list of all smtp endpoints', permissions => { - check => ['or', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['perm', '/mapping/notifications', ['Mapping.Audit']], - ], + check => [ + 'or', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + ['perm', '/mapping/notifications', ['Mapping.Audit']], + ], }, protected => 1, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => 'object', - properties => { - %{ remove_protected_properties($smtp_properties, ['password']) }, - 'origin' => { - description => 'Show if this entry was created by a user or was built-in', - type => 'string', - enum => [qw(user-created builtin modified-builtin)], - }, - }, - }, - links => [ { rel => 'child', href => '{name}' } ], + type => 'array', + items => { + type => 'object', + properties => { + %{ remove_protected_properties($smtp_properties, ['password']) }, + 'origin' => { + description => 'Show if this entry was created by a user or was built-in', + type => 'string', + enum => [qw(user-created builtin modified-builtin)], + }, + }, + }, + links => [{ rel => 'child', href => '{name}' }], }, code => sub { - my $config = PVE::Notify::read_config(); + my $config = PVE::Notify::read_config(); - my $entities = eval { - $config->get_smtp_endpoints(); - }; - raise_api_error($@) if $@; + my $entities = eval { $config->get_smtp_endpoints(); }; + raise_api_error($@) if $@; - return $entities; - } + return $entities; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_smtp_endpoint', path => 'endpoints/smtp/{name}', method => 'GET', description => 'Return a specific smtp endpoint', permissions => { - check => ['or', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['perm', '/mapping/notifications', ['Mapping.Audit']], - ], + check => [ + 'or', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + ['perm', '/mapping/notifications', ['Mapping.Audit']], + ], }, protected => 1, parameters => { - additionalProperties => 0, - properties => { - name => { - type => 'string', - format => 'pve-configid', - }, - } + additionalProperties => 0, + properties => { + name => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { - type => 'object', - properties => { - %{ remove_protected_properties($smtp_properties, ['password']) }, - digest => get_standard_option('pve-config-digest'), - } + type => 'object', + properties => { + %{ remove_protected_properties($smtp_properties, ['password']) }, + digest => get_standard_option('pve-config-digest'), + }, }, code => sub { - my ($param) = @_; - my $name = extract_param($param, 'name'); + my ($param) = @_; + my $name = extract_param($param, 'name'); - my $config = PVE::Notify::read_config(); - my $endpoint = eval { - $config->get_smtp_endpoint($name) - }; + my $config = PVE::Notify::read_config(); + my $endpoint = eval { $config->get_smtp_endpoint($name) }; - raise_api_error($@) if $@; - $endpoint->{digest} = $config->digest(); + raise_api_error($@) if $@; + $endpoint->{digest} = $config->digest(); - return $endpoint; - } + return $endpoint; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create_smtp_endpoint', path => 'endpoints/smtp', protected => 1, method => 'POST', description => 'Create a new smtp endpoint', permissions => { - check => ['and', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['or', - ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]], - ['perm', '/', [ 'Sys.AccessNetwork' ]], - ], - ], + check => [ + 'and', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + [ + 'or', + ['perm', '/', ['Sys.Audit', 'Sys.Modify']], + ['perm', '/', ['Sys.AccessNetwork']], + ], + ], }, parameters => { - additionalProperties => 0, - properties => $smtp_properties, + additionalProperties => 0, + properties => $smtp_properties, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $name = extract_param($param, 'name'); - my $server = extract_param($param, 'server'); - my $port = extract_param($param, 'port'); - my $mode = extract_param($param, 'mode'); - my $username = extract_param($param, 'username'); - my $password = extract_param($param, 'password'); - my $mailto = extract_param($param, 'mailto'); - my $mailto_user = extract_param($param, 'mailto-user'); - my $from_address = extract_param($param, 'from-address'); - my $author = extract_param($param, 'author'); - my $comment = extract_param($param, 'comment'); - my $disable = extract_param($param, 'disable'); + my $name = extract_param($param, 'name'); + my $server = extract_param($param, 'server'); + my $port = extract_param($param, 'port'); + my $mode = extract_param($param, 'mode'); + my $username = extract_param($param, 'username'); + my $password = extract_param($param, 'password'); + my $mailto = extract_param($param, 'mailto'); + my $mailto_user = extract_param($param, 'mailto-user'); + my $from_address = extract_param($param, 'from-address'); + my $author = extract_param($param, 'author'); + my $comment = extract_param($param, 'comment'); + my $disable = extract_param($param, 'disable'); - eval { - PVE::Notify::lock_config(sub { - my $config = PVE::Notify::read_config(); + eval { + PVE::Notify::lock_config(sub { + my $config = PVE::Notify::read_config(); - $config->add_smtp_endpoint( - $name, - $server, - $port, - $mode, - $username, - $password, - $mailto, - $mailto_user, - $from_address, - $author, - $comment, - $disable, - ); + $config->add_smtp_endpoint( + $name, + $server, + $port, + $mode, + $username, + $password, + $mailto, + $mailto_user, + $from_address, + $author, + $comment, + $disable, + ); - PVE::Notify::write_config($config); - }); - }; + PVE::Notify::write_config($config); + }); + }; - raise_api_error($@) if $@; - return; - } + raise_api_error($@) if $@; + return; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update_smtp_endpoint', path => 'endpoints/smtp/{name}', protected => 1, method => 'PUT', description => 'Update existing smtp endpoint', permissions => { - check => ['and', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['or', - ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]], - ['perm', '/', [ 'Sys.AccessNetwork' ]], - ], - ], + check => [ + 'and', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + [ + 'or', + ['perm', '/', ['Sys.Audit', 'Sys.Modify']], + ['perm', '/', ['Sys.AccessNetwork']], + ], + ], }, parameters => { - additionalProperties => 0, - properties => { - %{ make_properties_optional($smtp_properties) }, - delete => { - type => 'array', - items => { - type => 'string', - format => 'pve-configid', - }, - optional => 1, - description => 'A list of settings you want to delete.', - }, - digest => get_standard_option('pve-config-digest'), + additionalProperties => 0, + properties => { + %{ make_properties_optional($smtp_properties) }, + delete => { + type => 'array', + items => { + type => 'string', + format => 'pve-configid', + }, + optional => 1, + description => 'A list of settings you want to delete.', + }, + digest => get_standard_option('pve-config-digest'), - } + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $name = extract_param($param, 'name'); - my $server = extract_param($param, 'server'); - my $port = extract_param($param, 'port'); - my $mode = extract_param($param, 'mode'); - my $username = extract_param($param, 'username'); - my $password = extract_param($param, 'password'); - my $mailto = extract_param($param, 'mailto'); - my $mailto_user = extract_param($param, 'mailto-user'); - my $from_address = extract_param($param, 'from-address'); - my $author = extract_param($param, 'author'); - my $comment = extract_param($param, 'comment'); - my $disable = extract_param($param, 'disable'); + my $name = extract_param($param, 'name'); + my $server = extract_param($param, 'server'); + my $port = extract_param($param, 'port'); + my $mode = extract_param($param, 'mode'); + my $username = extract_param($param, 'username'); + my $password = extract_param($param, 'password'); + my $mailto = extract_param($param, 'mailto'); + my $mailto_user = extract_param($param, 'mailto-user'); + my $from_address = extract_param($param, 'from-address'); + my $author = extract_param($param, 'author'); + my $comment = extract_param($param, 'comment'); + my $disable = extract_param($param, 'disable'); - my $delete = extract_param($param, 'delete'); - my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); + my $digest = extract_param($param, 'digest'); - eval { - PVE::Notify::lock_config(sub { - my $config = PVE::Notify::read_config(); + eval { + PVE::Notify::lock_config(sub { + my $config = PVE::Notify::read_config(); - $config->update_smtp_endpoint( - $name, - $server, - $port, - $mode, - $username, - $password, - $mailto, - $mailto_user, - $from_address, - $author, - $comment, - $disable, - $delete, - $digest, - ); + $config->update_smtp_endpoint( + $name, + $server, + $port, + $mode, + $username, + $password, + $mailto, + $mailto_user, + $from_address, + $author, + $comment, + $disable, + $delete, + $digest, + ); - PVE::Notify::write_config($config); - }); - }; + PVE::Notify::write_config($config); + }); + }; - raise_api_error($@) if $@; - return; - } + raise_api_error($@) if $@; + return; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete_smtp_endpoint', protected => 1, path => 'endpoints/smtp/{name}', method => 'DELETE', description => 'Remove smtp endpoint', permissions => { - check => ['perm', '/mapping/notifications', ['Mapping.Modify']], + check => ['perm', '/mapping/notifications', ['Mapping.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - name => { - type => 'string', - format => 'pve-configid', - }, - } + additionalProperties => 0, + properties => { + name => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; - my $name = extract_param($param, 'name'); + my ($param) = @_; + my $name = extract_param($param, 'name'); - eval { - PVE::Notify::lock_config(sub { - my $config = PVE::Notify::read_config(); - $config->delete_smtp_endpoint($name); - PVE::Notify::write_config($config); - }); - }; + eval { + PVE::Notify::lock_config(sub { + my $config = PVE::Notify::read_config(); + $config->delete_smtp_endpoint($name); + PVE::Notify::write_config($config); + }); + }; - raise_api_error($@) if ($@); - return; - } + raise_api_error($@) if ($@); + return; + }, }); my $webhook_properties = { name => { - description => 'The name of the endpoint.', - type => 'string', - format => 'pve-configid', + description => 'The name of the endpoint.', + type => 'string', + format => 'pve-configid', }, url => { - description => 'Server URL', - type => 'string', + description => 'Server URL', + type => 'string', }, method => { - description => 'HTTP method', - type => 'string', - enum => [qw(post put get)], + description => 'HTTP method', + type => 'string', + enum => [qw(post put get)], }, header => { - description => 'HTTP headers to set. These have to be formatted as' - . ' a property string in the format name=,value=', - type => 'array', - items => { - type => 'string', - }, - optional => 1, + description => 'HTTP headers to set. These have to be formatted as' + . ' a property string in the format name=,value=', + type => 'array', + items => { + type => 'string', + }, + optional => 1, }, body => { - description => 'HTTP body, base64 encoded', - type => 'string', - optional => 1, + description => 'HTTP body, base64 encoded', + type => 'string', + optional => 1, }, secret => { - description => 'Secrets to set. These have to be formatted as' - . ' a property string in the format name=,value=', - type => 'array', - items => { - type => 'string', - }, - optional => 1, + description => 'Secrets to set. These have to be formatted as' + . ' a property string in the format name=,value=', + type => 'array', + items => { + type => 'string', + }, + optional => 1, }, comment => { - description => 'Comment', - type => 'string', - optional => 1, + description => 'Comment', + type => 'string', + optional => 1, }, disable => { - description => 'Disable this target', - type => 'boolean', - optional => 1, - default => 0, + description => 'Disable this target', + type => 'boolean', + optional => 1, + default => 0, }, }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_webhook_endpoints', path => 'endpoints/webhook', method => 'GET', description => 'Returns a list of all webhook endpoints', protected => 1, permissions => { - check => ['perm', '/mapping/notifications', ['Mapping.Modify']], - check => ['perm', '/mapping/notifications', ['Mapping.Audit']], + check => ['perm', '/mapping/notifications', ['Mapping.Modify']], + check => ['perm', '/mapping/notifications', ['Mapping.Audit']], }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => 'object', - properties => { - %$webhook_properties, - 'origin' => { - description => 'Show if this entry was created by a user or was built-in', - type => 'string', - enum => [qw(user-created builtin modified-builtin)], - }, - }, - }, - links => [ { rel => 'child', href => '{name}' } ], + type => 'array', + items => { + type => 'object', + properties => { + %$webhook_properties, + 'origin' => { + description => 'Show if this entry was created by a user or was built-in', + type => 'string', + enum => [qw(user-created builtin modified-builtin)], + }, + }, + }, + links => [{ rel => 'child', href => '{name}' }], }, code => sub { - my $config = PVE::Notify::read_config(); - my $rpcenv = PVE::RPCEnvironment::get(); + my $config = PVE::Notify::read_config(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $entities = eval { - $config->get_webhook_endpoints(); - }; - raise_api_error($@) if $@; + my $entities = eval { $config->get_webhook_endpoints(); }; + raise_api_error($@) if $@; - return $entities; - } + return $entities; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_webhook_endpoint', path => 'endpoints/webhook/{name}', method => 'GET', description => 'Return a specific webhook endpoint', protected => 1, permissions => { - check => ['or', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['perm', '/mapping/notifications', ['Mapping.Audit']], - ], + check => [ + 'or', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + ['perm', '/mapping/notifications', ['Mapping.Audit']], + ], }, parameters => { - additionalProperties => 0, - properties => { - name => { - type => 'string', - format => 'pve-configid', - description => 'Name of the endpoint.' - }, - } + additionalProperties => 0, + properties => { + name => { + type => 'string', + format => 'pve-configid', + description => 'Name of the endpoint.', + }, + }, }, returns => { - type => 'object', - properties => { - %$webhook_properties, - digest => get_standard_option('pve-config-digest'), - } + type => 'object', + properties => { + %$webhook_properties, digest => get_standard_option('pve-config-digest'), + }, }, code => sub { - my ($param) = @_; - my $name = extract_param($param, 'name'); + my ($param) = @_; + my $name = extract_param($param, 'name'); - my $config = PVE::Notify::read_config(); - my $endpoint = eval { - $config->get_webhook_endpoint($name) - }; + my $config = PVE::Notify::read_config(); + my $endpoint = eval { $config->get_webhook_endpoint($name) }; - raise_api_error($@) if $@; - $endpoint->{digest} = $config->digest(); + raise_api_error($@) if $@; + $endpoint->{digest} = $config->digest(); - return $endpoint; - } + return $endpoint; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create_webhook_endpoint', path => 'endpoints/webhook', protected => 1, method => 'POST', description => 'Create a new webhook endpoint', permissions => { - check => ['and', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['or', - ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]], - ['perm', '/', [ 'Sys.AccessNetwork' ]], - ], - ], + check => [ + 'and', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + [ + 'or', + ['perm', '/', ['Sys.Audit', 'Sys.Modify']], + ['perm', '/', ['Sys.AccessNetwork']], + ], + ], }, parameters => { - additionalProperties => 0, - properties => $webhook_properties, + additionalProperties => 0, + properties => $webhook_properties, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; - eval { - PVE::Notify::lock_config(sub { - my $config = PVE::Notify::read_config(); + my ($param) = @_; + eval { + PVE::Notify::lock_config(sub { + my $config = PVE::Notify::read_config(); - $config->add_webhook_endpoint( - $param, - ); + $config->add_webhook_endpoint( + $param, + ); - PVE::Notify::write_config($config); - }); - }; + PVE::Notify::write_config($config); + }); + }; - raise_api_error($@) if $@; - return; - } + raise_api_error($@) if $@; + return; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update_webhook_endpoint', path => 'endpoints/webhook/{name}', protected => 1, method => 'PUT', description => 'Update existing webhook endpoint', permissions => { - check => ['and', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['or', - ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]], - ['perm', '/', [ 'Sys.AccessNetwork' ]], - ], - ], + check => [ + 'and', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + [ + 'or', + ['perm', '/', ['Sys.Audit', 'Sys.Modify']], + ['perm', '/', ['Sys.AccessNetwork']], + ], + ], }, parameters => { - additionalProperties => 0, - properties => { - %{ make_properties_optional($webhook_properties) }, - delete => { - type => 'array', - items => { - type => 'string', - format => 'pve-configid', - }, - optional => 1, - description => 'A list of settings you want to delete.', - }, - digest => get_standard_option('pve-config-digest'), - } + additionalProperties => 0, + properties => { + %{ make_properties_optional($webhook_properties) }, + delete => { + type => 'array', + items => { + type => 'string', + format => 'pve-configid', + }, + optional => 1, + description => 'A list of settings you want to delete.', + }, + digest => get_standard_option('pve-config-digest'), + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $name = extract_param($param, 'name'); - my $delete = extract_param($param, 'delete'); - my $digest = extract_param($param, 'digest'); + my $name = extract_param($param, 'name'); + my $delete = extract_param($param, 'delete'); + my $digest = extract_param($param, 'digest'); - eval { - PVE::Notify::lock_config(sub { - my $config = PVE::Notify::read_config(); + eval { + PVE::Notify::lock_config(sub { + my $config = PVE::Notify::read_config(); - $config->update_webhook_endpoint( - $name, - $param, # Config updater - $delete, - $digest, - ); + $config->update_webhook_endpoint( + $name, + $param, # Config updater + $delete, + $digest, + ); - PVE::Notify::write_config($config); - }); - }; + PVE::Notify::write_config($config); + }); + }; - raise_api_error($@) if $@; - return; - } + raise_api_error($@) if $@; + return; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete_webhook_endpoint', protected => 1, path => 'endpoints/webhook/{name}', method => 'DELETE', description => 'Remove webhook endpoint', permissions => { - check => ['perm', '/mapping/notifications', ['Mapping.Modify']], + check => ['perm', '/mapping/notifications', ['Mapping.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - name => { - type => 'string', - format => 'pve-configid', - }, - } + additionalProperties => 0, + properties => { + name => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; - my $name = extract_param($param, 'name'); + my ($param) = @_; + my $name = extract_param($param, 'name'); - eval { - PVE::Notify::lock_config(sub { - my $config = PVE::Notify::read_config(); - $config->delete_webhook_endpoint($name); - PVE::Notify::write_config($config); - }); - }; + eval { + PVE::Notify::lock_config(sub { + my $config = PVE::Notify::read_config(); + $config->delete_webhook_endpoint($name); + PVE::Notify::write_config($config); + }); + }; - raise_api_error($@) if $@; - return; - } + raise_api_error($@) if $@; + return; + }, }); my $matcher_properties = { name => { - description => 'Name of the matcher.', - type => 'string', - format => 'pve-configid', + description => 'Name of the matcher.', + type => 'string', + format => 'pve-configid', }, 'match-field' => { - type => 'array', - items => { - type => 'string', - }, - optional => 1, - description => 'Metadata fields to match (regex or exact match).' - . ' Must be in the form (regex|exact):=', + type => 'array', + items => { + type => 'string', + }, + optional => 1, + description => 'Metadata fields to match (regex or exact match).' + . ' Must be in the form (regex|exact):=', }, 'match-severity' => { - type => 'array', - items => { - type => 'string', - }, - optional => 1, - description => 'Notification severities to match', + type => 'array', + items => { + type => 'string', + }, + optional => 1, + description => 'Notification severities to match', }, 'match-calendar' => { - type => 'array', - items => { - type => 'string', - }, - optional => 1, - description => 'Match notification timestamp', + type => 'array', + items => { + type => 'string', + }, + optional => 1, + description => 'Match notification timestamp', }, 'target' => { - type => 'array', - items => { - type => 'string', - format => 'pve-configid', - }, - optional => 1, - description => 'Targets to notify on match', + type => 'array', + items => { + type => 'string', + format => 'pve-configid', + }, + optional => 1, + description => 'Targets to notify on match', }, mode => { - type => 'string', - description => "Choose between 'all' and 'any' for when multiple properties are specified", - optional => 1, - enum => [qw(all any)], - default => 'all', + type => 'string', + description => + "Choose between 'all' and 'any' for when multiple properties are specified", + optional => 1, + enum => [qw(all any)], + default => 'all', }, 'invert-match' => { - type => 'boolean', - description => 'Invert match of the whole matcher', - optional => 1, + type => 'boolean', + description => 'Invert match of the whole matcher', + optional => 1, }, 'comment' => { - description => 'Comment', - type => 'string', - optional => 1, + description => 'Comment', + type => 'string', + optional => 1, }, 'disable' => { - description => 'Disable this matcher', - type => 'boolean', - optional => 1, - default => 0, + description => 'Disable this matcher', + type => 'boolean', + optional => 1, + default => 0, }, }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_matchers', path => 'matchers', method => 'GET', description => 'Returns a list of all matchers', protected => 1, permissions => { - check => ['or', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['perm', '/mapping/notifications', ['Mapping.Audit']], - ['perm', '/mapping/notifications', ['Mapping.Use']], - ], + check => [ + 'or', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + ['perm', '/mapping/notifications', ['Mapping.Audit']], + ['perm', '/mapping/notifications', ['Mapping.Use']], + ], }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => 'object', - properties => { - %$matcher_properties, - 'origin' => { - description => 'Show if this entry was created by a user or was built-in', - type => 'string', - enum => [qw(user-created builtin modified-builtin)], - }, - } - }, - links => [ { rel => 'child', href => '{name}' } ], + type => 'array', + items => { + type => 'object', + properties => { + %$matcher_properties, + 'origin' => { + description => 'Show if this entry was created by a user or was built-in', + type => 'string', + enum => [qw(user-created builtin modified-builtin)], + }, + }, + }, + links => [{ rel => 'child', href => '{name}' }], }, code => sub { - my $config = PVE::Notify::read_config(); + my $config = PVE::Notify::read_config(); - my $entities = eval { - $config->get_matchers(); - }; - raise_api_error($@) if $@; + my $entities = eval { $config->get_matchers(); }; + raise_api_error($@) if $@; - return $entities; - } + return $entities; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_matcher', path => 'matchers/{name}', method => 'GET', description => 'Return a specific matcher', protected => 1, permissions => { - check => ['or', - ['perm', '/mapping/notifications', ['Mapping.Modify']], - ['perm', '/mapping/notifications', ['Mapping.Audit']], - ], + check => [ + 'or', + ['perm', '/mapping/notifications', ['Mapping.Modify']], + ['perm', '/mapping/notifications', ['Mapping.Audit']], + ], }, parameters => { - additionalProperties => 0, - properties => { - name => { - type => 'string', - format => 'pve-configid', - }, - } + additionalProperties => 0, + properties => { + name => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { - type => 'object', - properties => { - %$matcher_properties, - digest => get_standard_option('pve-config-digest'), - }, + type => 'object', + properties => { + %$matcher_properties, digest => get_standard_option('pve-config-digest'), + }, }, code => sub { - my ($param) = @_; - my $name = extract_param($param, 'name'); + my ($param) = @_; + my $name = extract_param($param, 'name'); - my $config = PVE::Notify::read_config(); + my $config = PVE::Notify::read_config(); - my $matcher = eval { - $config->get_matcher($name) - }; + my $matcher = eval { $config->get_matcher($name) }; - raise_api_error($@) if $@; - $matcher->{digest} = $config->digest(); + raise_api_error($@) if $@; + $matcher->{digest} = $config->digest(); - return $matcher; - } + return $matcher; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create_matcher', path => 'matchers', protected => 1, @@ -1700,153 +1695,153 @@ __PACKAGE__->register_method ({ description => 'Create a new matcher', protected => 1, permissions => { - check => ['perm', '/mapping/notifications', ['Mapping.Modify']], + check => ['perm', '/mapping/notifications', ['Mapping.Modify']], }, parameters => { - additionalProperties => 0, - properties => $matcher_properties, + additionalProperties => 0, + properties => $matcher_properties, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $name = extract_param($param, 'name'); - my $match_severity = extract_param($param, 'match-severity'); - my $match_field = extract_param($param, 'match-field'); - my $match_calendar = extract_param($param, 'match-calendar'); - my $target = extract_param($param, 'target'); - my $mode = extract_param($param, 'mode'); - my $invert_match = extract_param($param, 'invert-match'); - my $comment = extract_param($param, 'comment'); - my $disable = extract_param($param, 'disable'); + my $name = extract_param($param, 'name'); + my $match_severity = extract_param($param, 'match-severity'); + my $match_field = extract_param($param, 'match-field'); + my $match_calendar = extract_param($param, 'match-calendar'); + my $target = extract_param($param, 'target'); + my $mode = extract_param($param, 'mode'); + my $invert_match = extract_param($param, 'invert-match'); + my $comment = extract_param($param, 'comment'); + my $disable = extract_param($param, 'disable'); - eval { - PVE::Notify::lock_config(sub { - my $config = PVE::Notify::read_config(); + eval { + PVE::Notify::lock_config(sub { + my $config = PVE::Notify::read_config(); - $config->add_matcher( - $name, - $target, - $match_severity, - $match_field, - $match_calendar, - $mode, - $invert_match, - $comment, - $disable, - ); + $config->add_matcher( + $name, + $target, + $match_severity, + $match_field, + $match_calendar, + $mode, + $invert_match, + $comment, + $disable, + ); - PVE::Notify::write_config($config); - }); - }; + PVE::Notify::write_config($config); + }); + }; - raise_api_error($@) if $@; - return; - } + raise_api_error($@) if $@; + return; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update_matcher', path => 'matchers/{name}', protected => 1, method => 'PUT', description => 'Update existing matcher', permissions => { - check => ['perm', '/mapping/notifications', ['Mapping.Modify']], + check => ['perm', '/mapping/notifications', ['Mapping.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - %{ make_properties_optional($matcher_properties) }, - delete => { - type => 'array', - items => { - type => 'string', - format => 'pve-configid', - }, - optional => 1, - description => 'A list of settings you want to delete.', - }, - digest => get_standard_option('pve-config-digest'), - }, + additionalProperties => 0, + properties => { + %{ make_properties_optional($matcher_properties) }, + delete => { + type => 'array', + items => { + type => 'string', + format => 'pve-configid', + }, + optional => 1, + description => 'A list of settings you want to delete.', + }, + digest => get_standard_option('pve-config-digest'), + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $name = extract_param($param, 'name'); - my $match_severity = extract_param($param, 'match-severity'); - my $match_field = extract_param($param, 'match-field'); - my $match_calendar = extract_param($param, 'match-calendar'); - my $target = extract_param($param, 'target'); - my $mode = extract_param($param, 'mode'); - my $invert_match = extract_param($param, 'invert-match'); - my $comment = extract_param($param, 'comment'); - my $disable = extract_param($param, 'disable'); - my $digest = extract_param($param, 'digest'); - my $delete = extract_param($param, 'delete'); + my $name = extract_param($param, 'name'); + my $match_severity = extract_param($param, 'match-severity'); + my $match_field = extract_param($param, 'match-field'); + my $match_calendar = extract_param($param, 'match-calendar'); + my $target = extract_param($param, 'target'); + my $mode = extract_param($param, 'mode'); + my $invert_match = extract_param($param, 'invert-match'); + my $comment = extract_param($param, 'comment'); + my $disable = extract_param($param, 'disable'); + my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); - eval { - PVE::Notify::lock_config(sub { - my $config = PVE::Notify::read_config(); + eval { + PVE::Notify::lock_config(sub { + my $config = PVE::Notify::read_config(); - $config->update_matcher( - $name, - $target, - $match_severity, - $match_field, - $match_calendar, - $mode, - $invert_match, - $comment, - $disable, - $delete, - $digest, - ); + $config->update_matcher( + $name, + $target, + $match_severity, + $match_field, + $match_calendar, + $mode, + $invert_match, + $comment, + $disable, + $delete, + $digest, + ); - PVE::Notify::write_config($config); - }); - }; + PVE::Notify::write_config($config); + }); + }; - raise_api_error($@) if $@; - return; - } + raise_api_error($@) if $@; + return; + }, }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete_matcher', protected => 1, path => 'matchers/{name}', method => 'DELETE', description => 'Remove matcher', permissions => { - check => ['perm', '/mapping/notifications', ['Mapping.Modify']], + check => ['perm', '/mapping/notifications', ['Mapping.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - name => { - type => 'string', - format => 'pve-configid', - }, - } + additionalProperties => 0, + properties => { + name => { + type => 'string', + format => 'pve-configid', + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; - my $name = extract_param($param, 'name'); + my ($param) = @_; + my $name = extract_param($param, 'name'); - eval { - PVE::Notify::lock_config(sub { - my $config = PVE::Notify::read_config(); - $config->delete_matcher($name); - PVE::Notify::write_config($config); - }); - }; + eval { + PVE::Notify::lock_config(sub { + my $config = PVE::Notify::read_config(); + $config->delete_matcher($name); + PVE::Notify::write_config($config); + }); + }; - raise_api_error($@) if $@; - return; - } + raise_api_error($@) if $@; + return; + }, }); 1; diff --git a/PVE/API2/HAConfig.pm b/PVE/API2/HAConfig.pm index 828c5a87..35f49cbb 100644 --- a/PVE/API2/HAConfig.pm +++ b/PVE/API2/HAConfig.pm @@ -16,54 +16,52 @@ use PVE::API2::HA::Status; use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ - subclass => "PVE::API2::HA::Resources", +__PACKAGE__->register_method({ + subclass => "PVE::API2::HA::Resources", path => 'resources', - }); +}); -__PACKAGE__->register_method ({ - subclass => "PVE::API2::HA::Groups", +__PACKAGE__->register_method({ + subclass => "PVE::API2::HA::Groups", path => 'groups', - }); +}); -__PACKAGE__->register_method ({ - subclass => "PVE::API2::HA::Status", +__PACKAGE__->register_method({ + subclass => "PVE::API2::HA::Status", path => 'status', }); __PACKAGE__->register_method({ - name => 'index', - path => '', + name => 'index', + path => '', method => 'GET', description => "Directory index.", permissions => { - check => ['perm', '/', [ 'Sys.Audit' ]], + check => ['perm', '/', ['Sys.Audit']], }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - id => { type => 'string' }, - }, - }, - links => [ { rel => 'child', href => "{id}" } ], + type => 'array', + items => { + type => "object", + properties => { + id => { type => 'string' }, + }, + }, + links => [{ rel => 'child', href => "{id}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $res = [ - { id => 'status' }, - { id => 'resources' }, - { id => 'groups' }, - ]; - - return $res; - }}); + my $res = [ + { id => 'status' }, { id => 'resources' }, { id => 'groups' }, + ]; + return $res; + }, +}); 1; diff --git a/PVE/API2/Hardware.pm b/PVE/API2/Hardware.pm index 1c6fd8f5..48b0765a 100644 --- a/PVE/API2/Hardware.pm +++ b/PVE/API2/Hardware.pm @@ -11,47 +11,46 @@ use PVE::API2::Hardware::USB; use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Hardware::PCI", path => 'pci', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Hardware::USB", path => 'usb', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', description => "Index of hardware types", permissions => { - user => 'all', + user => 'all', }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { type => { type => 'string'} }, - }, - links => [ { rel => 'child', href => "{type}" } ], + type => 'array', + items => { + type => "object", + properties => { type => { type => 'string' } }, + }, + links => [{ rel => 'child', href => "{type}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $res = [ - { type => 'pci' }, - { type => 'usb' }, - ]; + my $res = [ + { type => 'pci' }, { type => 'usb' }, + ]; - return $res; - } + return $res; + }, }); diff --git a/PVE/API2/Hardware/PCI.pm b/PVE/API2/Hardware/PCI.pm index 0b9d05c7..984bb2ee 100644 --- a/PVE/API2/Hardware/PCI.pm +++ b/PVE/API2/Hardware/PCI.pm @@ -12,7 +12,7 @@ use base qw(PVE::RESTHandler); my $default_class_blacklist = "05;06;0b"; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'pci_scan', path => '', method => 'GET', @@ -20,154 +20,159 @@ __PACKAGE__->register_method ({ protected => 1, proxyto => "node", permissions => { - check => ['perm', '/', ['Sys.Audit', 'Sys.Modify'], any => 1], + check => ['perm', '/', ['Sys.Audit', 'Sys.Modify'], any => 1], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - 'pci-class-blacklist' => { - type => 'string', - format => 'string-list', - default => $default_class_blacklist, - optional => 1, - description => "A list of blacklisted PCI classes, which will not be returned." - ." Following are filtered by default: Memory Controller (05), Bridge (06) and" - ." Processor (0b).", - }, - verbose => { - type => 'boolean', - default => 1, - optional => 1, - description => "If disabled, does only print the PCI IDs. Otherwise, additional" - ." information like vendor and device will be returned.", - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + 'pci-class-blacklist' => { + type => 'string', + format => 'string-list', + default => $default_class_blacklist, + optional => 1, + description => "A list of blacklisted PCI classes, which will not be returned." + . " Following are filtered by default: Memory Controller (05), Bridge (06) and" + . " Processor (0b).", + }, + verbose => { + type => 'boolean', + default => 1, + optional => 1, + description => "If disabled, does only print the PCI IDs. Otherwise, additional" + . " information like vendor and device will be returned.", + }, + }, }, returns => { - links => [ { rel => 'child', href => "{id}" } ], - type => 'array', - items => { - type => "object", - properties => { - id => { - type => 'string', - description => "The PCI ID.", - }, - class => { - type => 'string', - description => 'The PCI Class of the device.', - }, - vendor => { - type => 'string', - description => 'The Vendor ID.', - }, - vendor_name => { - type => 'string', - optional => 1, - }, - device => { - type => 'string', - description => 'The Device ID.', - }, - device_name => { - type => 'string', - optional => 1, - }, - subsystem_vendor => { - type => 'string', - description => 'The Subsystem Vendor ID.', - optional => 1, - }, - subsystem_vendor_name => { - type => 'string', - optional => 1, - }, - subsystem_device => { - type => 'string', - description => 'The Subsystem Device ID.', - optional => 1, - }, - subsystem_device_name => { - type => 'string', - optional => 1, - }, - iommugroup => { - type => 'integer', - description => "The IOMMU group in which the device is in. If no IOMMU group is" - ." detected, it is set to -1.", - }, - mdev => { - type => 'boolean', - optional => 1, - description => "If set, marks that the device is capable of creating mediated" - ." devices.", - }, - }, - }, + links => [{ rel => 'child', href => "{id}" }], + type => 'array', + items => { + type => "object", + properties => { + id => { + type => 'string', + description => "The PCI ID.", + }, + class => { + type => 'string', + description => 'The PCI Class of the device.', + }, + vendor => { + type => 'string', + description => 'The Vendor ID.', + }, + vendor_name => { + type => 'string', + optional => 1, + }, + device => { + type => 'string', + description => 'The Device ID.', + }, + device_name => { + type => 'string', + optional => 1, + }, + subsystem_vendor => { + type => 'string', + description => 'The Subsystem Vendor ID.', + optional => 1, + }, + subsystem_vendor_name => { + type => 'string', + optional => 1, + }, + subsystem_device => { + type => 'string', + description => 'The Subsystem Device ID.', + optional => 1, + }, + subsystem_device_name => { + type => 'string', + optional => 1, + }, + iommugroup => { + type => 'integer', + description => + "The IOMMU group in which the device is in. If no IOMMU group is" + . " detected, it is set to -1.", + }, + mdev => { + type => 'boolean', + optional => 1, + description => + "If set, marks that the device is capable of creating mediated" + . " devices.", + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $blacklist = $param->{'pci-class-blacklist'} // $default_class_blacklist; - my $class_regex = join('|', PVE::Tools::split_list($blacklist)); + my $blacklist = $param->{'pci-class-blacklist'} // $default_class_blacklist; + my $class_regex = join('|', PVE::Tools::split_list($blacklist)); - my $filter; + my $filter; - if ($class_regex ne '') { - $filter = sub { - my ($pcidevice) = @_; + if ($class_regex ne '') { + $filter = sub { + my ($pcidevice) = @_; - if ($pcidevice->{class} =~ m/^0x(?:$class_regex)/) { - return 0; - } + if ($pcidevice->{class} =~ m/^0x(?:$class_regex)/) { + return 0; + } - return 1; - }; - } + return 1; + }; + } - my $verbose = $param->{verbose} // 1; + my $verbose = $param->{verbose} // 1; - return PVE::SysFSTools::lspci($filter, $verbose); - }}); + return PVE::SysFSTools::lspci($filter, $verbose); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'pci_index', path => '{pci-id-or-mapping}', method => 'GET', description => "Index of available pci methods", permissions => { - user => 'all', + user => 'all', }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - 'pci-id-or-mapping' => { - type => 'string', - pattern => '(?:(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F])|([a-zA-Z][a-zA-Z0-9_-]+)', - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + 'pci-id-or-mapping' => { + type => 'string', + pattern => + '(?:(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F])|([a-zA-Z][a-zA-Z0-9_-]+)', + }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { method => { type => 'string'} }, - }, - links => [ { rel => 'child', href => "{method}" } ], + type => 'array', + items => { + type => "object", + properties => { method => { type => 'string' } }, + }, + links => [{ rel => 'child', href => "{method}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $res = [ - { method => 'mdev' }, - ]; + my $res = [ + { method => 'mdev' }, + ]; - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'mdevscan', path => '{pci-id-or-mapping}/mdev', method => 'GET', @@ -175,69 +180,73 @@ __PACKAGE__->register_method ({ protected => 1, proxyto => "node", permissions => { - check => ['perm', '/', ['Sys.Audit', 'Sys.Modify'], any => 1], + check => ['perm', '/', ['Sys.Audit', 'Sys.Modify'], any => 1], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - 'pci-id-or-mapping' => { - type => 'string', - pattern => '(?:(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F])|([a-zA-Z][a-zA-Z0-9_-]+)', - description => "The PCI ID or mapping to list the mdev types for." - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + 'pci-id-or-mapping' => { + type => 'string', + pattern => + '(?:(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F])|([a-zA-Z][a-zA-Z0-9_-]+)', + description => "The PCI ID or mapping to list the mdev types for.", + }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - type => { - type => 'string', - description => "The name of the mdev type.", - }, - available => { - type => 'integer', - description => "The number of still available instances of this type.", - }, - description => { - type => 'string', - description => "Additional description of the type." - }, - name => { - type => 'string', - optional => 1, - description => 'A human readable name for the type.', - }, - }, - }, + type => 'array', + items => { + type => "object", + properties => { + type => { + type => 'string', + description => "The name of the mdev type.", + }, + available => { + type => 'integer', + description => "The number of still available instances of this type.", + }, + description => { + type => 'string', + description => "Additional description of the type.", + }, + name => { + type => 'string', + optional => 1, + description => 'A human readable name for the type.', + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - if ($param->{'pci-id-or-mapping'} =~ m/^(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$/) { - return PVE::SysFSTools::get_mdev_types($param->{'pci-id-or-mapping'}); # PCI ID - } else { - my $mapping = $param->{'pci-id-or-mapping'}; + if ($param->{'pci-id-or-mapping'} =~ + m/^(?:[0-9a-fA-F]{4}:)?[0-9a-fA-F]{2}:[0-9a-fA-F]{2}\.[0-9a-fA-F]$/ + ) { + return PVE::SysFSTools::get_mdev_types($param->{'pci-id-or-mapping'}); # PCI ID + } else { + my $mapping = $param->{'pci-id-or-mapping'}; - my $types = {}; - my $devices = PVE::Mapping::PCI::find_on_current_node($mapping); - for my $device ($devices->@*) { - my $id = $device->{path}; - next if $id =~ m/;/; # mdev not supported for multifunction devices + my $types = {}; + my $devices = PVE::Mapping::PCI::find_on_current_node($mapping); + for my $device ($devices->@*) { + my $id = $device->{path}; + next if $id =~ m/;/; # mdev not supported for multifunction devices - my $device_types = PVE::SysFSTools::get_mdev_types($id); + my $device_types = PVE::SysFSTools::get_mdev_types($id); - for my $type_definition ($device_types->@*) { - my $type = $type_definition->{type}; - if (!defined($types->{$type})) { - $types->{$type} = $type_definition; - } - } - } + for my $type_definition ($device_types->@*) { + my $type = $type_definition->{type}; + if (!defined($types->{$type})) { + $types->{$type} = $type_definition; + } + } + } - return [sort { $a->{type} cmp $b->{type} } values($types->%*)]; - } + return [sort { $a->{type} cmp $b->{type} } values($types->%*)]; + } - }}); + }, +}); diff --git a/PVE/API2/Hardware/USB.pm b/PVE/API2/Hardware/USB.pm index d7cb6607..49ca49ed 100644 --- a/PVE/API2/Hardware/USB.pm +++ b/PVE/API2/Hardware/USB.pm @@ -17,37 +17,37 @@ __PACKAGE__->register_method({ protected => 1, proxyto => "node", permissions => { - check => ['perm', '/', ['Sys.Modify']], + check => ['perm', '/', ['Sys.Modify']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - busnum => { type => 'integer'}, - class => { type => 'integer'}, - devnum => { type => 'integer'}, - level => { type => 'integer'}, - manufacturer => { type => 'string', optional => 1 }, - port => { type => 'integer'}, - prodid => { type => 'string'}, - product => { type => 'string', optional => 1 }, - serial => { type => 'string', optional => 1 }, - speed => { type => 'string'}, - usbpath => { type => 'string', optional => 1}, - vendid => { type => 'string'}, - }, - }, + type => 'array', + items => { + type => "object", + properties => { + busnum => { type => 'integer' }, + class => { type => 'integer' }, + devnum => { type => 'integer' }, + level => { type => 'integer' }, + manufacturer => { type => 'string', optional => 1 }, + port => { type => 'integer' }, + prodid => { type => 'string' }, + product => { type => 'string', optional => 1 }, + serial => { type => 'string', optional => 1 }, + speed => { type => 'string' }, + usbpath => { type => 'string', optional => 1 }, + vendid => { type => 'string' }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - return PVE::SysFSTools::scan_usb(); - } + return PVE::SysFSTools::scan_usb(); + }, }); diff --git a/PVE/API2/Network.pm b/PVE/API2/Network.pm index 12ee6cca..a8cd7649 100644 --- a/PVE/API2/Network.pm +++ b/PVE/API2/Network.pm @@ -32,161 +32,192 @@ my $bond_mode_enum = [ '802.3ad', 'balance-tlb', 'balance-alb', - 'balance-slb', # OVS + '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 => { - description => "Network interface type", - type => 'string', - enum => [@$network_type_enum, 'unknown'], + description => "Network interface type", + type => 'string', + enum => [@$network_type_enum, 'unknown'], }, comments => { - description => "Comments", - type => 'string', - optional => 1, + description => "Comments", + type => 'string', + optional => 1, }, comments6 => { - description => "Comments", - type => 'string', - optional => 1, + description => "Comments", + type => 'string', + optional => 1, }, autostart => { - description => "Automatically start interface on boot.", - type => 'boolean', - optional => 1, + description => "Automatically start interface on boot.", + type => 'boolean', + optional => 1, }, bridge_vlan_aware => { - description => "Enable bridge vlan support.", - type => 'boolean', - optional => 1, + description => "Enable bridge vlan support.", + type => 'boolean', + optional => 1, }, bridge_vids => { - 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', + 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', }, bridge_ports => { - description => "Specify the interfaces you want to add to your bridge.", - optional => 1, - type => 'string', format => 'pve-iface-list', + description => "Specify the interfaces you want to add to your bridge.", + optional => 1, + 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', + description => "Specify the interfaces you want to add to your bridge.", + optional => 1, + type => 'string', + format => 'pve-iface-list', }, ovs_tag => { - description => "Specify a VLan tag (used by OVSPort, OVSIntPort, OVSBond)", - optional => 1, - type => 'integer', - minimum => 1, - maximum => 4094, + description => "Specify a VLan tag (used by OVSPort, OVSIntPort, OVSBond)", + optional => 1, + type => 'integer', + minimum => 1, + maximum => 4094, }, ovs_options => { - description => "OVS interface options.", - optional => 1, - type => 'string', - maxLength => 1024, + description => "OVS interface options.", + optional => 1, + type => 'string', + maxLength => 1024, }, ovs_bridge => { - 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', + 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', }, slaves => { - description => "Specify the interfaces used by the bonding device.", - optional => 1, - type => 'string', format => 'pve-iface-list', + description => "Specify the interfaces used by the bonding device.", + optional => 1, + 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', + description => "Specify the interfaces used by the bonding device.", + optional => 1, + type => 'string', + format => 'pve-iface-list', }, bond_mode => { - description => "Bonding mode.", - optional => 1, - type => 'string', enum => $bond_mode_enum, + description => "Bonding mode.", + optional => 1, + type => 'string', + enum => $bond_mode_enum, }, 'bond-primary' => { - description => "Specify the primary interface for active-backup bond.", - optional => 1, - type => 'string', format => 'pve-iface', + description => "Specify the primary interface for active-backup bond.", + optional => 1, + 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.", - optional => 1, - type => 'string', - enum => ['layer2', 'layer2+3', 'layer3+4' ], + 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'], }, 'vlan-raw-device' => { - description => "Specify the raw interface for the vlan interface.", - optional => 1, - type => 'string', format => 'pve-iface', + description => "Specify the raw interface for the vlan interface.", + optional => 1, + type => 'string', + format => 'pve-iface', }, 'vlan-id' => { - description => "vlan-id for a custom named vlan interface (ifupdown2 only).", - optional => 1, - type => 'integer', - minimum => 1, - maximum => 4094, + description => "vlan-id for a custom named vlan interface (ifupdown2 only).", + optional => 1, + type => 'integer', + minimum => 1, + maximum => 4094, }, gateway => { - description => 'Default gateway address.', - type => 'string', format => 'ipv4', - optional => 1, + description => 'Default gateway address.', + type => 'string', + format => 'ipv4', + optional => 1, }, netmask => { - description => 'Network mask.', - type => 'string', format => 'ipv4mask', - optional => 1, - requires => 'address', + description => 'Network mask.', + type => 'string', + format => 'ipv4mask', + optional => 1, + requires => 'address', }, address => { - description => 'IP address.', - type => 'string', format => 'ipv4', - optional => 1, - requires => 'netmask', + description => 'IP address.', + type => 'string', + format => 'ipv4', + optional => 1, + requires => 'netmask', }, cidr => { - description => 'IPv4 CIDR.', - type => 'string', format => 'CIDRv4', - optional => 1, + description => 'IPv4 CIDR.', + type => 'string', + format => 'CIDRv4', + optional => 1, }, mtu => { - description => 'MTU.', - optional => 1, - type => 'integer', - minimum => 1280, - maximum => 65520, + description => 'MTU.', + optional => 1, + type => 'integer', + minimum => 1280, + maximum => 65520, }, gateway6 => { - description => 'Default ipv6 gateway address.', - type => 'string', format => 'ipv6', - optional => 1, + description => 'Default ipv6 gateway address.', + type => 'string', + format => 'ipv6', + optional => 1, }, netmask6 => { - description => 'Network mask.', - type => 'integer', minimum => 0, maximum => 128, - optional => 1, - requires => 'address6', + description => 'Network mask.', + type => 'integer', + minimum => 0, + maximum => 128, + optional => 1, + requires => 'address6', }, address6 => { - description => 'IP address.', - type => 'string', format => 'ipv6', - optional => 1, - requires => 'netmask6', + description => 'IP address.', + type => 'string', + format => 'ipv6', + optional => 1, + requires => 'netmask6', }, cidr6 => { - description => 'IPv6 CIDR.', - type => 'string', format => 'CIDRv6', - optional => 1, + description => 'IPv6 CIDR.', + type => 'string', + format => 'CIDRv6', + optional => 1, }, }; @@ -194,237 +225,243 @@ sub json_config_properties { my $prop = shift; foreach my $opt (keys %$confdesc) { - $prop->{$opt} = $confdesc->{$opt}; + $prop->{$opt} = $confdesc->{$opt}; } return $prop; } __PACKAGE__->register_method({ - name => 'index', - path => '', + name => 'index', + path => '', method => 'GET', permissions => { user => 'all' }, description => "List available networks", proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - type => { - description => "Only list specific interface types.", - type => 'string', - enum => [ @$network_type_enum, 'any_bridge', 'any_local_bridge' ], - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + type => { + description => "Only list specific interface types.", + type => 'string', + enum => [@$network_type_enum, 'any_bridge', 'any_local_bridge'], + optional => 1, + }, + }, }, returns => { - type => "array", - items => { - type => "object", - properties => json_config_properties({ - iface => get_standard_option('pve-iface'), - active => { - type => 'boolean', - optional => 1, - description => "Set to true if the interface is active.", - }, - 'bridge-access' => { - type => 'integer', - optional => 1, - description => "The bridge port access VLAN.", - }, - 'bridge-learning' => { - type => 'boolean', - optional => 1, - description => "Bridge port learning flag.", - }, - 'bridge-arp-nd-suppress' => { - type => 'boolean', - optional => 1, - description => "Bridge port ARP/ND suppress flag.", - }, - 'bridge-unicast-flood' => { - type => 'boolean', - optional => 1, - description => "Bridge port unicast flood flag.", - }, - 'bridge-multicast-flood' => { - type => 'boolean', - optional => 1, - description => "Bridge port multicast flood flag.", - }, - exists => { - type => 'boolean', - optional => 1, - description => "Set to true if the interface physically exists.", - }, - families => { - type => "array", - description => "The network families.", - items => { - type => "string", - description => "A network family.", - enum => ["inet", "inet6"], - }, - optional => 1, - }, - 'link-type' => { - type => 'string', - optional => 1, - description => "The link type.", - }, - method => { - type => "string", - description => "The network configuration method for IPv4.", - enum => ["loopback", "dhcp", "manual", "static", "auto"], - optional => 1, - }, - method6 => { - type => "string", - description => "The network configuration method for IPv6.", - enum => ["loopback", "dhcp", "manual", "static", "auto"], - optional => 1, - }, - options => { - type => 'array', - optional => 1, - description => "A list of additional interface options for IPv4.", - items => { - type => "string", - description => "An interface property.", - }, - }, - options6 => { - type => 'array', - optional => 1, - description => "A list of additional interface options for IPv6.", - items => { - type => "string", - description => "An interface property.", - }, - }, - priority => { - type => 'integer', - description => "The order of the interface.", - optional => 1, - }, - 'uplink-id' => { - type => 'string', - optional => 1, - description => "The uplink ID.", - }, - 'vlan-protocol' => { - type => 'string', - optional => 1, - enum => [qw(802.1ad 802.1q)], - description => "The VLAN protocol.", - }, - 'vxlan-id' => { - type => 'integer', - optional => 1, - description => "The VXLAN ID.", - }, - 'vxlan-svcnodeip' => { - type => 'string', - optional => 1, - description => "The VXLAN SVC node IP.", - }, - 'vxlan-physdev' => { - type => 'string', - optional => 1, - description => "The physical device for the VXLAN tunnel.", - }, - 'vxlan-local-tunnelip' => { - type => 'string', - optional => 1, - description => "The VXLAN local tunnel IP.", - }, - }), - }, - links => [ { rel => 'child', href => "{iface}" } ], + type => "array", + items => { + type => "object", + properties => json_config_properties({ + iface => get_standard_option('pve-iface'), + active => { + type => 'boolean', + optional => 1, + description => "Set to true if the interface is active.", + }, + 'bridge-access' => { + type => 'integer', + optional => 1, + description => "The bridge port access VLAN.", + }, + 'bridge-learning' => { + type => 'boolean', + optional => 1, + description => "Bridge port learning flag.", + }, + 'bridge-arp-nd-suppress' => { + type => 'boolean', + optional => 1, + description => "Bridge port ARP/ND suppress flag.", + }, + 'bridge-unicast-flood' => { + type => 'boolean', + optional => 1, + description => "Bridge port unicast flood flag.", + }, + 'bridge-multicast-flood' => { + type => 'boolean', + optional => 1, + description => "Bridge port multicast flood flag.", + }, + exists => { + type => 'boolean', + optional => 1, + description => "Set to true if the interface physically exists.", + }, + families => { + type => "array", + description => "The network families.", + items => { + type => "string", + description => "A network family.", + enum => ["inet", "inet6"], + }, + optional => 1, + }, + 'link-type' => { + type => 'string', + optional => 1, + description => "The link type.", + }, + method => { + type => "string", + description => "The network configuration method for IPv4.", + enum => ["loopback", "dhcp", "manual", "static", "auto"], + optional => 1, + }, + method6 => { + type => "string", + description => "The network configuration method for IPv6.", + enum => ["loopback", "dhcp", "manual", "static", "auto"], + optional => 1, + }, + options => { + type => 'array', + optional => 1, + description => "A list of additional interface options for IPv4.", + items => { + type => "string", + description => "An interface property.", + }, + }, + options6 => { + type => 'array', + optional => 1, + description => "A list of additional interface options for IPv6.", + items => { + type => "string", + description => "An interface property.", + }, + }, + priority => { + type => 'integer', + description => "The order of the interface.", + optional => 1, + }, + 'uplink-id' => { + type => 'string', + optional => 1, + description => "The uplink ID.", + }, + 'vlan-protocol' => { + type => 'string', + optional => 1, + enum => [qw(802.1ad 802.1q)], + description => "The VLAN protocol.", + }, + 'vxlan-id' => { + type => 'integer', + optional => 1, + description => "The VXLAN ID.", + }, + 'vxlan-svcnodeip' => { + type => 'string', + optional => 1, + description => "The VXLAN SVC node IP.", + }, + 'vxlan-physdev' => { + type => 'string', + optional => 1, + description => "The physical device for the VXLAN tunnel.", + }, + 'vxlan-local-tunnelip' => { + type => 'string', + optional => 1, + description => "The VXLAN local tunnel IP.", + }, + }), + }, + links => [{ rel => 'child', href => "{iface}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $tmp = PVE::INotify::read_file('interfaces', 1); - my $config = $tmp->{data}; - my $changes = $tmp->{changes}; + my $tmp = PVE::INotify::read_file('interfaces', 1); + my $config = $tmp->{data}; + my $changes = $tmp->{changes}; - $rpcenv->set_result_attrib('changes', $changes) if $changes; + $rpcenv->set_result_attrib('changes', $changes) if $changes; - my $ifaces = $config->{ifaces}; + my $ifaces = $config->{ifaces}; - delete $ifaces->{lo}; # do not list the loopback device + delete $ifaces->{lo}; # do not list the loopback device - if (my $tfilter = $param->{type}) { - my $vnets; + if (my $tfilter = $param->{type}) { + my $vnets; - if ($have_sdn && $tfilter eq 'any_bridge') { - $vnets = PVE::Network::SDN::get_local_vnets(); # returns already access-filtered - } + if ($have_sdn && $tfilter eq 'any_bridge') { + $vnets = PVE::Network::SDN::get_local_vnets(); # returns already access-filtered + } - for my $k (sort keys $ifaces->%*) { - my $type = $ifaces->{$k}->{type}; - my $is_bridge = $type eq 'bridge' || $type eq 'OVSBridge'; - my $bridge_match = $is_bridge && $tfilter =~ /^any(_local)?_bridge$/; - my $match = $tfilter eq $type || $bridge_match; - delete $ifaces->{$k} if !$match; - } + for my $k (sort keys $ifaces->%*) { + my $type = $ifaces->{$k}->{type}; + my $is_bridge = $type eq 'bridge' || $type eq 'OVSBridge'; + my $bridge_match = $is_bridge && $tfilter =~ /^any(_local)?_bridge$/; + my $match = $tfilter eq $type || $bridge_match; + delete $ifaces->{$k} if !$match; + } - if (defined($vnets)) { - $ifaces->{$_} = $vnets->{$_} for keys $vnets->%* - } - } + if (defined($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); - }; - 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); - } + #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, + ); + }; + 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); + } - return PVE::RESTHandler::hash_to_array($ifaces, 'iface'); - }}); + return PVE::RESTHandler::hash_to_array($ifaces, 'iface'); + }, +}); __PACKAGE__->register_method({ - name => 'revert_network_changes', - path => '', + 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.", proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { type => "null" }, code => sub { - my ($param) = @_; + my ($param) = @_; - unlink "/etc/network/interfaces.new"; + unlink "/etc/network/interfaces.new"; - return undef; - }}); + return undef; + }, +}); my $check_duplicate = sub { my ($config, $newiface, $key, $name) = @_; foreach my $iface (keys %$config) { - raise_param_exc({ $key => "$name already exists on interface '$iface'." }) - if ($newiface ne $iface) && $config->{$iface}->{$key}; + raise_param_exc({ $key => "$name already exists on interface '$iface'." }) + if ($newiface ne $iface) && $config->{$iface}->{$key}; } }; @@ -443,31 +480,31 @@ my $check_duplicate_ports = sub { my $param_name; my $get_portlist = sub { - my ($param) = @_; - my $ports = ''; - for my $k (qw(bridge_ports ovs_ports slaves ovs_bonds)) { - if ($param->{$k}) { - $ports .= " $param->{$k}"; - $param_name //= $k; - } - } - return PVE::Tools::split_list($ports); + my ($param) = @_; + my $ports = ''; + for my $k (qw(bridge_ports ovs_ports slaves ovs_bonds)) { + if ($param->{$k}) { + $ports .= " $param->{$k}"; + $param_name //= $k; + } + } + return PVE::Tools::split_list($ports); }; my $new_ports = {}; for my $p ($get_portlist->($newparam)) { - $new_ports->{$p} = 1; + $new_ports->{$p} = 1; } return if !(keys %$new_ports); for my $iface (keys %$config) { - next if $iface eq $newiface; + next if $iface eq $newiface; - my $d = $config->{$iface}; - for my $p ($get_portlist->($d)) { - raise_param_exc({ $param_name => "$p is already used on interface '$iface'." }) - if $new_ports->{$p}; - } + my $d = $config->{$iface}; + for my $p ($get_portlist->($d)) { + raise_param_exc({ $param_name => "$p is already used on interface '$iface'." }) + if $new_ports->{$p}; + } } }; @@ -479,10 +516,10 @@ my $check_ipv6_settings = sub { my ($address, $netmask) = @_; raise_param_exc({ netmask => "$netmask is not a valid subnet length for ipv6" }) - if $netmask < 0 || $netmask > 128; + if $netmask < 0 || $netmask > 128; raise_param_exc({ address => "$address is not a valid host IPv6 address." }) - if !Net::IP::ip_is_ipv6($address); + if !Net::IP::ip_is_ipv6($address); my $binip = ipv6_tobin($address); my $binmask = Net::IP::ip_get_mask($netmask, 6); @@ -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." }); } }; @@ -498,253 +536,265 @@ my $map_cidr_to_address_netmask = sub { my ($param) = @_; if ($param->{cidr}) { - raise_param_exc({ address => "address conflicts with cidr" }) - if $param->{address}; - raise_param_exc({ netmask => "netmask conflicts with cidr" }) - if $param->{netmask}; + raise_param_exc({ address => "address conflicts with cidr" }) + if $param->{address}; + raise_param_exc({ netmask => "netmask conflicts with cidr" }) + if $param->{netmask}; - my ($address, $netmask) = $param->{cidr} =~ m!^(.*)/(\d+)$!; - $param->{address} = $address; - $param->{netmask} = $netmask; - delete $param->{cidr}; + my ($address, $netmask) = $param->{cidr} =~ m!^(.*)/(\d+)$!; + $param->{address} = $address; + $param->{netmask} = $netmask; + delete $param->{cidr}; } if ($param->{cidr6}) { - raise_param_exc({ address6 => "address6 conflicts with cidr6" }) - if $param->{address6}; - raise_param_exc({ netmask6 => "netmask6 conflicts with cidr6" }) - if $param->{netmask6}; + raise_param_exc({ address6 => "address6 conflicts with cidr6" }) + if $param->{address6}; + raise_param_exc({ netmask6 => "netmask6 conflicts with cidr6" }) + if $param->{netmask6}; - my ($address, $netmask) = $param->{cidr6} =~ m!^(.*)/(\d+)$!; - $param->{address6} = $address; - $param->{netmask6} = $netmask; - delete $param->{cidr6}; + my ($address, $netmask) = $param->{cidr6} =~ m!^(.*)/(\d+)$!; + $param->{address6} = $address; + $param->{netmask6} = $netmask; + delete $param->{cidr6}; } }; __PACKAGE__->register_method({ - name => 'create_network', - path => '', + name => 'create_network', + path => '', method => 'POST', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, description => "Create network device configuration", protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => json_config_properties({ - node => get_standard_option('pve-node'), - iface => get_standard_option('pve-iface')}), + additionalProperties => 0, + properties => json_config_properties({ + node => get_standard_option('pve-node'), + iface => get_standard_option('pve-iface'), + }), }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $node = extract_param($param, 'node'); - my $iface = extract_param($param, 'iface'); + my $node = extract_param($param, 'node'); + my $iface = extract_param($param, 'iface'); - my $code = sub { - my $config = PVE::INotify::read_file('interfaces'); - my $ifaces = $config->{ifaces}; + my $code = sub { + my $config = PVE::INotify::read_file('interfaces'); + my $ifaces = $config->{ifaces}; - raise_param_exc({ iface => "interface already exists" }) - if $ifaces->{$iface}; + raise_param_exc({ iface => "interface already exists" }) + if $ifaces->{$iface}; - &$check_duplicate_gateway($ifaces, $iface) - if $param->{gateway}; - &$check_duplicate_gateway6($ifaces, $iface) - if $param->{gateway6}; + &$check_duplicate_gateway($ifaces, $iface) + if $param->{gateway}; + &$check_duplicate_gateway6($ifaces, $iface) + if $param->{gateway6}; - $check_duplicate_ports->($ifaces, $iface, $param); + $check_duplicate_ports->($ifaces, $iface, $param); - $map_cidr_to_address_netmask->($param); + $map_cidr_to_address_netmask->($param); - &$check_ipv6_settings($param->{address6}, int($param->{netmask6})) - if $param->{address6}; + &$check_ipv6_settings($param->{address6}, int($param->{netmask6})) + if $param->{address6}; - my $families = $param->{families} = []; - push @$families, 'inet' - if $param->{address} && !grep(/^inet$/, @$families); - push @$families, 'inet6' - if $param->{address6} && !grep(/^inet6$/, @$families); - @$families = ('inet') if !scalar(@$families); + my $families = $param->{families} = []; + push @$families, 'inet' + if $param->{address} && !grep(/^inet$/, @$families); + push @$families, 'inet6' + if $param->{address6} && !grep(/^inet6$/, @$families); + @$families = ('inet') if !scalar(@$families); - $param->{method} = $param->{address} ? 'static' : 'manual'; - $param->{method6} = $param->{address6} ? 'static' : 'manual'; + $param->{method} = $param->{address} ? 'static' : 'manual'; + $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"; - } + if ($param->{type} =~ m/^OVS/) { + -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') { - my $brname = $param->{ovs_bridge}; - raise_param_exc({ ovs_bridge => "parameter is required" }) if !$brname; - my $br = $ifaces->{$brname}; - raise_param_exc({ ovs_bridge => "bridge '$brname' does not exist" }) if !$br; - raise_param_exc({ ovs_bridge => "interface '$brname' is no OVS bridge" }) - if $br->{type} ne 'OVSBridge'; + if ($param->{type} eq 'OVSIntPort' || $param->{type} eq 'OVSBond') { + my $brname = $param->{ovs_bridge}; + raise_param_exc({ ovs_bridge => "parameter is required" }) if !$brname; + my $br = $ifaces->{$brname}; + raise_param_exc({ ovs_bridge => "bridge '$brname' does not exist" }) if !$br; + raise_param_exc({ ovs_bridge => "interface '$brname' is no OVS bridge" }) + if $br->{type} ne 'OVSBridge'; - my @ports = split (/\s+/, $br->{ovs_ports} || ''); - $br->{ovs_ports} = join(' ', @ports, $iface) - if ! grep { $_ eq $iface } @ports; - } + my @ports = split(/\s+/, $br->{ovs_ports} || ''); + $br->{ovs_ports} = join(' ', @ports, $iface) + if !grep { $_ eq $iface } @ports; + } - 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 - # network configuration - $param->{bridge_vids} = join(" ", PVE::Tools::split_list($param->{bridge_vids})) - if $param->{bridge_vids}; + 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 + # network configuration + $param->{bridge_vids} = join(" ", PVE::Tools::split_list($param->{bridge_vids})) + if $param->{bridge_vids}; - $ifaces->{$iface} = $param; + $ifaces->{$iface} = $param; - PVE::INotify::write_file('interfaces', $config); - }; + PVE::INotify::write_file('interfaces', $config); + }; - PVE::Tools::lock_file($iflockfn, 10, $code); - die $@ if $@; + PVE::Tools::lock_file($iflockfn, 10, $code); + die $@ if $@; - return undef; - }}); + return undef; + }, +}); __PACKAGE__->register_method({ - name => 'update_network', - path => '{iface}', + 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, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => json_config_properties({ - node => get_standard_option('pve-node'), - iface => get_standard_option('pve-iface'), - delete => { - type => 'string', format => 'pve-configid-list', - description => "A list of settings you want to delete.", - optional => 1, - }}), + additionalProperties => 0, + properties => json_config_properties({ + node => get_standard_option('pve-node'), + iface => get_standard_option('pve-iface'), + delete => { + type => 'string', + format => 'pve-configid-list', + description => "A list of settings you want to delete.", + optional => 1, + }, + }), }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $node = extract_param($param, 'node'); - my $iface = extract_param($param, 'iface'); - my $delete = extract_param($param, 'delete'); + my $node = extract_param($param, 'node'); + my $iface = extract_param($param, 'iface'); + my $delete = extract_param($param, 'delete'); - my $code = sub { - my $config = PVE::INotify::read_file('interfaces'); - my $ifaces = $config->{ifaces}; + my $code = sub { + my $config = PVE::INotify::read_file('interfaces'); + my $ifaces = $config->{ifaces}; - raise_param_exc({ iface => "interface does not exist" }) - if !$ifaces->{$iface}; + raise_param_exc({ iface => "interface does not exist" }) + if !$ifaces->{$iface}; - my $families = ($param->{families} ||= []); - foreach my $k (PVE::Tools::split_list($delete)) { - delete $ifaces->{$iface}->{$k}; - @$families = grep(!/^inet$/, @$families) if $k eq 'address'; - @$families = grep(!/^inet6$/, @$families) if $k eq 'address6'; - if ($k eq 'cidr') { - delete $ifaces->{$iface}->{netmask}; - delete $ifaces->{$iface}->{address}; - } elsif ($k eq 'cidr6') { - delete $ifaces->{$iface}->{netmask6}; - delete $ifaces->{$iface}->{address6}; - } - } + my $families = ($param->{families} ||= []); + foreach my $k (PVE::Tools::split_list($delete)) { + delete $ifaces->{$iface}->{$k}; + @$families = grep(!/^inet$/, @$families) if $k eq 'address'; + @$families = grep(!/^inet6$/, @$families) if $k eq 'address6'; + if ($k eq 'cidr') { + delete $ifaces->{$iface}->{netmask}; + delete $ifaces->{$iface}->{address}; + } elsif ($k eq 'cidr6') { + delete $ifaces->{$iface}->{netmask6}; + delete $ifaces->{$iface}->{address6}; + } + } - $map_cidr_to_address_netmask->($param); + $map_cidr_to_address_netmask->($param); - &$check_duplicate_gateway($ifaces, $iface) - if $param->{gateway}; - &$check_duplicate_gateway6($ifaces, $iface) - if $param->{gateway6}; + &$check_duplicate_gateway($ifaces, $iface) + if $param->{gateway}; + &$check_duplicate_gateway6($ifaces, $iface) + if $param->{gateway6}; - $check_duplicate_ports->($ifaces, $iface, $param); + $check_duplicate_ports->($ifaces, $iface, $param); - if ($param->{address}) { - push @$families, 'inet' if !grep(/^inet$/, @$families); - } else { - @$families = grep(!/^inet$/, @$families); - } - if ($param->{address6}) { - &$check_ipv6_settings($param->{address6}, int($param->{netmask6})); - push @$families, 'inet6' if !grep(/^inet6$/, @$families); - } else { - @$families = grep(!/^inet6$/, @$families); - } - @$families = ('inet') if !scalar(@$families); + if ($param->{address}) { + push @$families, 'inet' if !grep(/^inet$/, @$families); + } else { + @$families = grep(!/^inet$/, @$families); + } + if ($param->{address6}) { + &$check_ipv6_settings($param->{address6}, int($param->{netmask6})); + push @$families, 'inet6' if !grep(/^inet6$/, @$families); + } else { + @$families = grep(!/^inet6$/, @$families); + } + @$families = ('inet') if !scalar(@$families); - $param->{method} = $param->{address} ? 'static' : 'manual'; - $param->{method6} = $param->{address6} ? 'static' : 'manual'; + $param->{method} = $param->{address} ? 'static' : 'manual'; + $param->{method6} = $param->{address6} ? 'static' : 'manual'; - foreach my $k (keys %$param) { - $ifaces->{$iface}->{$k} = $param->{$k}; - } + foreach my $k (keys %$param) { + $ifaces->{$iface}->{$k} = $param->{$k}; + } - 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 - # network configuration - $param->{bridge_vids} = join(" ", PVE::Tools::split_list($param->{bridge_vids})) - if $param->{bridge_vids}; + 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 + # network configuration + $param->{bridge_vids} = join(" ", PVE::Tools::split_list($param->{bridge_vids})) + if $param->{bridge_vids}; - PVE::INotify::write_file('interfaces', $config); - }; + PVE::INotify::write_file('interfaces', $config); + }; - PVE::Tools::lock_file($iflockfn, 10, $code); - die $@ if $@; + PVE::Tools::lock_file($iflockfn, 10, $code); + die $@ if $@; - return undef; - }}); + return undef; + }, +}); __PACKAGE__->register_method({ - name => 'network_config', - path => '{iface}', + 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', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - iface => get_standard_option('pve-iface'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + iface => get_standard_option('pve-iface'), + }, }, returns => { - type => "object", - properties => { - type => { - type => 'string', - }, - method => { - type => 'string', - }, - }, + type => "object", + properties => { + type => { + type => 'string', + }, + method => { + type => 'string', + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $config = PVE::INotify::read_file('interfaces'); - my $ifaces = $config->{ifaces}; + my $config = PVE::INotify::read_file('interfaces'); + my $ifaces = $config->{ifaces}; - raise_param_exc({ iface => "interface does not exist" }) - if !$ifaces->{$param->{iface}}; + raise_param_exc({ iface => "interface does not exist" }) + 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,103 +820,105 @@ __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, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; my $rpcenv = PVE::RPCEnvironment::get(); my $authuser = $rpcenv->get_user(); - my $current_config_file = "/etc/network/interfaces"; - my $new_config_file = "/etc/network/interfaces.new"; + my $current_config_file = "/etc/network/interfaces"; + my $new_config_file = "/etc/network/interfaces.new"; - assert_ifupdown2_installed(); + assert_ifupdown2_installed(); - my $worker = sub { + my $worker = sub { - rename($new_config_file, $current_config_file) if -e $new_config_file; + rename($new_config_file, $current_config_file) if -e $new_config_file; - if ($have_sdn) { - PVE::Network::SDN::generate_zone_config(); - PVE::Network::SDN::generate_dhcp_config(); - } + if ($have_sdn) { + PVE::Network::SDN::generate_zone_config(); + PVE::Network::SDN::generate_dhcp_config(); + } - my $err = sub { - my $line = shift; - if ($line =~ /(warning|error): (\S+):/) { - print "$2 : $line \n"; - } - }; - PVE::Tools::run_command(['ifreload', '-a'], errfunc => $err); + my $err = sub { + my $line = shift; + if ($line =~ /(warning|error): (\S+):/) { + print "$2 : $line \n"; + } + }; + PVE::Tools::run_command(['ifreload', '-a'], errfunc => $err); - if ($have_sdn) { - PVE::Network::SDN::generate_controller_config(1); - } - }; - return $rpcenv->fork_worker('srvreload', 'networking', $authuser, $worker); - }}); + if ($have_sdn) { + PVE::Network::SDN::generate_controller_config(1); + } + }; + return $rpcenv->fork_worker('srvreload', 'networking', $authuser, $worker); + }, +}); __PACKAGE__->register_method({ - name => 'delete_network', - path => '{iface}', + 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, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - iface => get_standard_option('pve-iface'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + iface => get_standard_option('pve-iface'), + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $code = sub { - my $config = PVE::INotify::read_file('interfaces'); - my $ifaces = $config->{ifaces}; + my $code = sub { + my $config = PVE::INotify::read_file('interfaces'); + my $ifaces = $config->{ifaces}; - raise_param_exc({ iface => "interface does not exist" }) - if !$ifaces->{$param->{iface}}; + raise_param_exc({ iface => "interface does not exist" }) + if !$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 @new = grep { $_ ne $param->{iface} } @ports; - $br->{ovs_ports} = join(' ', @new); - } - } - } - } + 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 @new = grep { $_ ne $param->{iface} } @ports; + $br->{ovs_ports} = join(' ', @new); + } + } + } + } - delete $ifaces->{$param->{iface}}; + delete $ifaces->{ $param->{iface} }; - PVE::INotify::write_file('interfaces', $config); - }; + PVE::INotify::write_file('interfaces', $config); + }; - PVE::Tools::lock_file($iflockfn, 10, $code); - die $@ if $@; + PVE::Tools::lock_file($iflockfn, 10, $code); + die $@ if $@; - return undef; - }}); + return undef; + }, +}); diff --git a/PVE/API2/NodeConfig.pm b/PVE/API2/NodeConfig.pm index 961cd345..cc726dac 100644 --- a/PVE/API2/NodeConfig.pm +++ b/PVE/API2/NodeConfig.pm @@ -10,21 +10,23 @@ use PVE::Tools qw(extract_param); use base qw(PVE::RESTHandler); my $node_config_schema = PVE::NodeConfig::get_nodeconfig_schema(); -my $node_config_keys = [ sort keys %$node_config_schema ]; +my $node_config_keys = [sort keys %$node_config_schema]; my $node_config_return_properties = { digest => { - type => 'string', - description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.', - maxLength => 40, - optional => 1, + type => 'string', + description => + 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.', + maxLength => 40, + optional => 1, }, %$node_config_schema, }; my $node_config_properties = { delete => { - type => 'string', format => 'pve-configid-list', - description => "A list of settings you want to delete.", - optional => 1, + type => 'string', + format => 'pve-configid-list', + description => "A list of settings you want to delete.", + optional => 1, }, node => get_standard_option('pve-node'), %$node_config_return_properties, @@ -36,38 +38,39 @@ __PACKAGE__->register_method({ method => 'GET', description => "Get node configuration options.", permissions => { - check => ['perm', '/', [ 'Sys.Audit' ]], + check => ['perm', '/', ['Sys.Audit']], }, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - property => { - type => 'string', - description => 'Return only a specific property from the node configuration.', - enum => $node_config_keys, - optional => 1, - default => 'all', - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + property => { + type => 'string', + description => 'Return only a specific property from the node configuration.', + enum => $node_config_keys, + optional => 1, + default => 'all', + }, + }, }, returns => { - type => "object", - properties => $node_config_return_properties, + type => "object", + properties => $node_config_return_properties, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $config = PVE::NodeConfig::load_config($param->{node}); + my $config = PVE::NodeConfig::load_config($param->{node}); - if (defined (my $prop = $param->{property})) { - return {} if !exists $config->{$prop}; - return { $prop => $config->{$prop} }; - } + if (defined(my $prop = $param->{property})) { + return {} if !exists $config->{$prop}; + return { $prop => $config->{$prop} }; + } - return $config; - }}); + return $config; + }, +}); __PACKAGE__->register_method({ name => 'set_options', @@ -75,43 +78,44 @@ __PACKAGE__->register_method({ method => 'PUT', description => "Set node configuration options.", permissions => { - check => ['perm', '/', [ 'Sys.Modify' ]], + check => ['perm', '/', ['Sys.Modify']], }, protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => $node_config_properties, + additionalProperties => 0, + properties => $node_config_properties, }, returns => { type => "null" }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $delete = extract_param($param, 'delete'); - my $node = extract_param($param, 'node'); - my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); + my $node = extract_param($param, 'node'); + my $digest = extract_param($param, 'digest'); - my $code = sub { - my $conf = PVE::NodeConfig::load_config($node); + my $code = sub { + my $conf = PVE::NodeConfig::load_config($node); - PVE::Tools::assert_if_modified($digest, $conf->{digest}); + PVE::Tools::assert_if_modified($digest, $conf->{digest}); - foreach my $opt (sort keys %$param) { - $conf->{$opt} = $param->{$opt}; - } + foreach my $opt (sort keys %$param) { + $conf->{$opt} = $param->{$opt}; + } - foreach my $opt (PVE::Tools::split_list($delete)) { - delete $conf->{$opt}; - }; + foreach my $opt (PVE::Tools::split_list($delete)) { + delete $conf->{$opt}; + } - PVE::NodeConfig::verify_conf($conf); - PVE::NodeConfig::write_config($node, $conf); - }; + PVE::NodeConfig::verify_conf($conf); + PVE::NodeConfig::write_config($node, $conf); + }; - PVE::NodeConfig::lock_config($node, $code); - die $@ if $@; + PVE::NodeConfig::lock_config($node, $code); + die $@ if $@; - return undef; - }}); + return undef; + }, +}); 1; diff --git a/PVE/API2/Nodes.pm b/PVE/API2/Nodes.pm index 791d2dec..9ad72e17 100644 --- a/PVE/API2/Nodes.pm +++ b/PVE/API2/Nodes.pm @@ -72,29 +72,30 @@ my $verify_command_item_desc = { description => "An array of objects describing endpoints, methods and arguments.", type => "array", items => { - type => "object", - properties => { - path => { - description => "A relative path to an API endpoint on this node.", - type => "string", - optional => 0, - }, - method => { - description => "A method related to the API endpoint (GET, POST etc.).", - type => "string", - pattern => "(GET|POST|PUT|DELETE)", - optional => 0, - }, - args => { - description => "A set of parameter names and their values.", - type => "object", - optional => 1, - }, - }, - } + type => "object", + properties => { + path => { + description => "A relative path to an API endpoint on this node.", + type => "string", + optional => 0, + }, + method => { + description => "A method related to the API endpoint (GET, POST etc.).", + type => "string", + pattern => "(GET|POST|PUT|DELETE)", + optional => 0, + }, + args => { + description => "A set of parameter names and their values.", + type => "object", + optional => 1, + }, + }, + }, }; PVE::JSONSchema::register_format('pve-command-batch', \&verify_command_batch); + sub verify_command_batch { my ($value, $noerr) = @_; my $commands = eval { decode_json($value); }; @@ -110,204 +111,205 @@ sub verify_command_batch { die "commands is not a valid array of commands: $@"; } -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Qemu", path => 'qemu', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::LXC", path => 'lxc', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Ceph", path => 'ceph', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::VZDump", path => 'vzdump', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Services", path => 'services', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Subscription", path => 'subscription', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Network", path => 'network', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Tasks", path => 'tasks', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Storage::Scan", path => 'scan', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Hardware", path => 'hardware', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Capabilities", path => 'capabilities', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Storage::Status", path => 'storage', }); -__PACKAGE__->register_method ({ - subclass => "PVE::API2::Disks", - path => 'disks', +__PACKAGE__->register_method({ + subclass => "PVE::API2::Disks", + path => 'disks', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::APT", path => 'apt', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Firewall::Host", path => 'firewall', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Replication", path => 'replication', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Certificates", path => 'certificates', }); - -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::NodeConfig", path => 'config', }); if ($have_sdn) { - __PACKAGE__->register_method ({ - subclass => "PVE::API2::Network::SDN::Zones::Status", - path => 'sdn/zones', + __PACKAGE__->register_method({ + subclass => "PVE::API2::Network::SDN::Zones::Status", + path => 'sdn/zones', }); -__PACKAGE__->register_method ({ - name => 'sdnindex', - path => 'sdn', - method => 'GET', - permissions => { user => 'all' }, - description => "SDN index.", - parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, - }, - returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], - }, - code => sub { - my ($param) = @_; + __PACKAGE__->register_method({ + name => 'sdnindex', + path => 'sdn', + method => 'GET', + permissions => { user => 'all' }, + description => "SDN index.", + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, + }, + returns => { + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], + }, + code => sub { + my ($param) = @_; - my $result = [ - { name => 'zones' }, - ]; - return $result; - }}); + my $result = [ + { name => 'zones' }, + ]; + return $result; + }, + }); } -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', permissions => { user => 'all' }, description => "Node index.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $result = [ - { name => 'aplinfo' }, - { name => 'apt' }, - { name => 'capabilities' }, - { name => 'ceph' }, - { name => 'certificates' }, - { name => 'config' }, - { name => 'disks' }, - { name => 'dns' }, - { name => 'firewall' }, - { name => 'hardware' }, - { name => 'hosts' }, - { name => 'journal' }, - { name => 'lxc' }, - { name => 'migrateall' }, - { name => 'netstat' }, - { name => 'network' }, - { name => 'qemu' }, - { name => 'query-url-metadata' }, - { name => 'replication' }, - { name => 'report' }, - { name => 'rrd' }, # fixme: remove? - { name => 'rrddata' },# fixme: remove? - { name => 'scan' }, - { name => 'services' }, - { name => 'spiceshell' }, - { name => 'startall' }, - { name => 'status' }, - { name => 'stopall' }, - { name => 'storage' }, - { name => 'subscription' }, - { name => 'suspendall' }, - { name => 'syslog' }, - { name => 'tasks' }, - { name => 'termproxy' }, - { name => 'time' }, - { name => 'version' }, - { name => 'vncshell' }, - { name => 'vzdump' }, - { name => 'wakeonlan' }, - ]; + my $result = [ + { name => 'aplinfo' }, + { name => 'apt' }, + { name => 'capabilities' }, + { name => 'ceph' }, + { name => 'certificates' }, + { name => 'config' }, + { name => 'disks' }, + { name => 'dns' }, + { name => 'firewall' }, + { name => 'hardware' }, + { name => 'hosts' }, + { name => 'journal' }, + { name => 'lxc' }, + { name => 'migrateall' }, + { name => 'netstat' }, + { name => 'network' }, + { name => 'qemu' }, + { name => 'query-url-metadata' }, + { name => 'replication' }, + { name => 'report' }, + { name => 'rrd' }, # fixme: remove? + { name => 'rrddata' }, # fixme: remove? + { name => 'scan' }, + { name => 'services' }, + { name => 'spiceshell' }, + { name => 'startall' }, + { name => 'status' }, + { name => 'stopall' }, + { name => 'storage' }, + { name => 'subscription' }, + { name => 'suspendall' }, + { name => 'syslog' }, + { name => 'tasks' }, + { name => 'termproxy' }, + { name => 'time' }, + { name => 'version' }, + { name => 'vncshell' }, + { name => 'vzdump' }, + { name => 'wakeonlan' }, + ]; - push @$result, { name => 'sdn' } if $have_sdn; + push @$result, { name => 'sdn' } if $have_sdn; - return $result; - }}); + return $result; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'version', path => 'version', method => 'GET', @@ -315,43 +317,44 @@ __PACKAGE__->register_method ({ permissions => { user => 'all' }, description => "API version details", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => "object", - properties => { - version => { - type => 'string', - description => 'The current installed pve-manager package version', - }, - release => { - type => 'string', - description => 'The current installed Proxmox VE Release', - }, - repoid => { - type => 'string', - description => 'The short git commit hash ID from which this version was build', - }, - }, + type => "object", + properties => { + version => { + type => 'string', + description => 'The current installed pve-manager package version', + }, + release => { + type => 'string', + description => 'The current installed Proxmox VE Release', + }, + repoid => { + type => 'string', + description => 'The short git commit hash ID from which this version was build', + }, + }, }, code => sub { - my ($resp, $param) = @_; + my ($resp, $param) = @_; - return PVE::pvecfg::version_info(); - }}); + return PVE::pvecfg::version_info(); + }, +}); my sub get_current_kernel_info { my ($sysname, $nodename, $release, $version, $machine) = POSIX::uname(); my $kernel_version_string = "$sysname $release $version"; # for legacy compat my $current_kernel = { - sysname => $sysname, - release => $release, - version => $version, - machine => $machine, + sysname => $sysname, + release => $release, + version => $version, + machine => $machine, }; return ($current_kernel, $kernel_version_string); } @@ -363,19 +366,19 @@ my sub get_boot_mode_info { my $is_efi_booted = -d "/sys/firmware/efi"; $boot_mode_info_cache = { - mode => $is_efi_booted ? 'efi' : 'legacy-bios', + mode => $is_efi_booted ? 'efi' : 'legacy-bios', }; my $efi_var = "/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c"; if ($is_efi_booted && -e $efi_var) { - my $efi_var_sec_boot_entry = eval { file_get_contents($efi_var) }; - if ($@) { - warn "Failed to read secure boot state: $@\n"; - } else { - my @secureboot = unpack("CCCCC", $efi_var_sec_boot_entry); - $boot_mode_info_cache->{secureboot} = $secureboot[4] == 1 ? 1 : 0; - } + my $efi_var_sec_boot_entry = eval { file_get_contents($efi_var) }; + if ($@) { + warn "Failed to read secure boot state: $@\n"; + } else { + my @secureboot = unpack("CCCCC", $efi_var_sec_boot_entry); + $boot_mode_info_cache->{secureboot} = $secureboot[4] == 1 ? 1 : 0; + } } return $boot_mode_info_cache; } @@ -385,236 +388,240 @@ __PACKAGE__->register_method({ path => 'status', method => 'GET', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], + check => ['perm', '/nodes/{node}', ['Sys.Audit']], }, description => "Read node status", proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => "object", - additionalProperties => 1, - properties => { - # TODO: document remaining ones - 'boot-info' => { - description => "Meta-information about the boot mode.", - type => 'object', - properties => { - mode => { - description => 'Through which firmware the system got booted.', - type => 'string', - enum => [qw(efi legacy-bios)], - }, - secureboot => { - description => 'System is booted in secure mode, only applicable for the "efi" mode.', - type => 'boolean', - optional => 1, - }, - }, - }, - 'current-kernel' => { - description => "Meta-information about the currently booted kernel of this node.", - type => 'object', - properties => { - sysname => { - description => 'OS kernel name (e.g., "Linux")', - type => 'string', - }, - release => { - description => 'OS kernel release (e.g., "6.8.0")', - type => 'string', - }, - version => { - description => 'OS kernel version with build info', - type => 'string', - }, - machine => { - description => 'Hardware (architecture) type', - type => 'string', - }, - }, - }, - cpu => { - type => "number", - description => "The current cpu usage.", - }, - cpuinfo => { - type => "object", - properties => { - cores => { - type => "integer", - description => "The number of physical cores of the CPU.", - }, - cpus => { - type => "integer", - description => "The number of logical threads of the CPU.", - }, - model => { - type => "string", - description => "The CPU model", - }, - sockets => { - type => "integer", - description => "The number of logical threads of the CPU.", - }, - }, - }, - loadavg => { - type => 'array', - description => "An array of load avg for 1, 5 and 15 minutes respectively.", - items => { - type => 'string', - description => "The value of the load.", - } - }, - memory => { - type => "object", - properties => { - free => { - type => "integer", - description => "The free memory in bytes.", - }, - total => { - type => "integer", - description => "The total memory in bytes.", - }, - used => { - type => "integer", - description => "The used memory in bytes.", - }, - }, - }, - pveversion => { - type => 'string', - description => "The PVE version string.", - }, - rootfs => { - type => "object", - properties => { - free => { - type => "integer", - description => "The free bytes on the root filesystem.", - }, - total => { - type => "integer", - description => "The total size of the root filesystem in bytes.", - }, - used => { - type => "integer", - description => "The used bytes in the root filesystem.", - }, - avail => { - type => "integer", - description => "The available bytes in the root filesystem.", - }, - }, - } - }, + type => "object", + additionalProperties => 1, + properties => { + # TODO: document remaining ones + 'boot-info' => { + description => "Meta-information about the boot mode.", + type => 'object', + properties => { + mode => { + description => 'Through which firmware the system got booted.', + type => 'string', + enum => [qw(efi legacy-bios)], + }, + secureboot => { + description => + 'System is booted in secure mode, only applicable for the "efi" mode.', + type => 'boolean', + optional => 1, + }, + }, + }, + 'current-kernel' => { + description => + "Meta-information about the currently booted kernel of this node.", + type => 'object', + properties => { + sysname => { + description => 'OS kernel name (e.g., "Linux")', + type => 'string', + }, + release => { + description => 'OS kernel release (e.g., "6.8.0")', + type => 'string', + }, + version => { + description => 'OS kernel version with build info', + type => 'string', + }, + machine => { + description => 'Hardware (architecture) type', + type => 'string', + }, + }, + }, + cpu => { + type => "number", + description => "The current cpu usage.", + }, + cpuinfo => { + type => "object", + properties => { + cores => { + type => "integer", + description => "The number of physical cores of the CPU.", + }, + cpus => { + type => "integer", + description => "The number of logical threads of the CPU.", + }, + model => { + type => "string", + description => "The CPU model", + }, + sockets => { + type => "integer", + description => "The number of logical threads of the CPU.", + }, + }, + }, + loadavg => { + type => 'array', + description => "An array of load avg for 1, 5 and 15 minutes respectively.", + items => { + type => 'string', + description => "The value of the load.", + }, + }, + memory => { + type => "object", + properties => { + free => { + type => "integer", + description => "The free memory in bytes.", + }, + total => { + type => "integer", + description => "The total memory in bytes.", + }, + used => { + type => "integer", + description => "The used memory in bytes.", + }, + }, + }, + pveversion => { + type => 'string', + description => "The PVE version string.", + }, + rootfs => { + type => "object", + properties => { + free => { + type => "integer", + description => "The free bytes on the root filesystem.", + }, + total => { + type => "integer", + description => "The total size of the root filesystem in bytes.", + }, + used => { + type => "integer", + description => "The used bytes in the root filesystem.", + }, + avail => { + type => "integer", + description => "The available bytes in the root filesystem.", + }, + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $res = { - uptime => 0, - idle => 0, - }; + my $res = { + uptime => 0, + idle => 0, + }; - my ($uptime, $idle) = PVE::ProcFSTools::read_proc_uptime(); - $res->{uptime} = $uptime; + my ($uptime, $idle) = PVE::ProcFSTools::read_proc_uptime(); + $res->{uptime} = $uptime; - my ($avg1, $avg5, $avg15) = PVE::ProcFSTools::read_loadavg(); - $res->{loadavg} = [ $avg1, $avg5, $avg15]; + my ($avg1, $avg5, $avg15) = PVE::ProcFSTools::read_loadavg(); + $res->{loadavg} = [$avg1, $avg5, $avg15]; - my ($current_kernel_info, $kversion_string) = get_current_kernel_info(); - $res->{kversion} = $kversion_string; - $res->{'current-kernel'} = $current_kernel_info; + my ($current_kernel_info, $kversion_string) = get_current_kernel_info(); + $res->{kversion} = $kversion_string; + $res->{'current-kernel'} = $current_kernel_info; - $res->{'boot-info'} = get_boot_mode_info(); + $res->{'boot-info'} = get_boot_mode_info(); - $res->{cpuinfo} = PVE::ProcFSTools::read_cpuinfo(); + $res->{cpuinfo} = PVE::ProcFSTools::read_cpuinfo(); - my $stat = PVE::ProcFSTools::read_proc_stat(); - $res->{cpu} = $stat->{cpu}; - $res->{wait} = $stat->{wait}; + my $stat = PVE::ProcFSTools::read_proc_stat(); + $res->{cpu} = $stat->{cpu}; + $res->{wait} = $stat->{wait}; - my $meminfo = PVE::ProcFSTools::read_meminfo(); - $res->{memory} = { - free => $meminfo->{memfree}, - total => $meminfo->{memtotal}, - used => $meminfo->{memused}, - }; + my $meminfo = PVE::ProcFSTools::read_meminfo(); + $res->{memory} = { + free => $meminfo->{memfree}, + total => $meminfo->{memtotal}, + used => $meminfo->{memused}, + }; - $res->{ksm} = { - shared => $meminfo->{memshared}, - }; + $res->{ksm} = { + shared => $meminfo->{memshared}, + }; - $res->{swap} = { - free => $meminfo->{swapfree}, - total => $meminfo->{swaptotal}, - used => $meminfo->{swapused}, - }; + $res->{swap} = { + free => $meminfo->{swapfree}, + total => $meminfo->{swaptotal}, + used => $meminfo->{swapused}, + }; - $res->{pveversion} = PVE::pvecfg::package() . "/" . - PVE::pvecfg::version_text(); + $res->{pveversion} = PVE::pvecfg::package() . "/" . PVE::pvecfg::version_text(); - my $dinfo = df('/', 1); # output is bytes + my $dinfo = df('/', 1); # output is bytes - $res->{rootfs} = { - total => $dinfo->{blocks}, - avail => $dinfo->{bavail}, - used => $dinfo->{used}, - free => $dinfo->{blocks} - $dinfo->{used}, - }; + $res->{rootfs} = { + total => $dinfo->{blocks}, + avail => $dinfo->{bavail}, + used => $dinfo->{used}, + free => $dinfo->{blocks} - $dinfo->{used}, + }; - return $res; - }}); + return $res; + }, +}); __PACKAGE__->register_method({ name => 'netstat', path => 'netstat', method => 'GET', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], + check => ['perm', '/nodes/{node}', ['Sys.Audit']], }, description => "Read tap/vm network device interface counters", proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => "array", - items => { - type => "object", - properties => {}, - }, + type => "array", + items => { + type => "object", + properties => {}, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $res = [ ]; + my $res = []; - my $netdev = PVE::ProcFSTools::read_proc_net_dev(); - foreach my $dev (sort keys %$netdev) { - next if $dev !~ m/^(?:tap|veth)([1-9]\d*)i(\d+)$/; - my ($vmid, $netid) = ($1, $2); + my $netdev = PVE::ProcFSTools::read_proc_net_dev(); + foreach my $dev (sort keys %$netdev) { + next if $dev !~ m/^(?:tap|veth)([1-9]\d*)i(\d+)$/; + my ($vmid, $netid) = ($1, $2); - push @$res, { - vmid => $vmid, - dev => "net$netid", - in => $netdev->{$dev}->{transmit}, - out => $netdev->{$dev}->{receive}, - }; - } + push @$res, + { + vmid => $vmid, + dev => "net$netid", + in => $netdev->{$dev}->{transmit}, + out => $netdev->{$dev}->{receive}, + }; + } - return $res; - }}); + return $res; + }, +}); __PACKAGE__->register_method({ name => 'execute', @@ -624,183 +631,194 @@ __PACKAGE__->register_method({ proxyto => 'node', protected => 1, # avoid problems with proxy code parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - commands => { - description => "JSON encoded array of commands.", - type => "string", - verbose_description => "JSON encoded array of commands, where each command is an object with the following properties:\n" - . PVE::RESTHandler::dump_properties($verify_command_item_desc->{items}->{properties}, 'full'), - format => "pve-command-batch", - } - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + commands => { + description => "JSON encoded array of commands.", + type => "string", + verbose_description => + "JSON encoded array of commands, where each command is an object with the following properties:\n" + . PVE::RESTHandler::dump_properties( + $verify_command_item_desc->{items}->{properties}, 'full', + ), + format => "pve-command-batch", + }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, + type => 'array', + items => { + type => "object", + properties => {}, + }, }, code => sub { - my ($param) = @_; - my $res = []; + my ($param) = @_; + my $res = []; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); - # just parse the json again, it should already be validated - my $commands = eval { decode_json($param->{commands}); }; + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); + # just parse the json again, it should already be validated + my $commands = eval { decode_json($param->{commands}); }; - foreach my $cmd (@$commands) { - eval { - $cmd->{args} //= {}; + foreach my $cmd (@$commands) { + eval { + $cmd->{args} //= {}; - my $path = "nodes/$param->{node}/$cmd->{path}"; + my $path = "nodes/$param->{node}/$cmd->{path}"; - my $uri_param = {}; - my ($handler, $info) = PVE::API2->find_handler($cmd->{method}, $path, $uri_param); - if (!$handler || !$info) { - die "no handler for '$path'\n"; - } + my $uri_param = {}; + my ($handler, $info) = + PVE::API2->find_handler($cmd->{method}, $path, $uri_param); + if (!$handler || !$info) { + die "no handler for '$path'\n"; + } - foreach my $p (keys %{$cmd->{args}}) { - raise_param_exc({ $p => "duplicate parameter" }) if defined($uri_param->{$p}); - $uri_param->{$p} = $cmd->{args}->{$p}; - } + foreach my $p (keys %{ $cmd->{args} }) { + raise_param_exc({ $p => "duplicate parameter" }) + if defined($uri_param->{$p}); + $uri_param->{$p} = $cmd->{args}->{$p}; + } - # check access permissions - $rpcenv->check_api2_permissions($info->{permissions}, $user, $uri_param); + # check access permissions + $rpcenv->check_api2_permissions($info->{permissions}, $user, $uri_param); - push @$res, { - status => HTTP_OK, - data => $handler->handle($info, $uri_param), - }; - }; - if (my $err = $@) { - my $resp = { status => HTTP_INTERNAL_SERVER_ERROR }; - if (ref($err) eq "PVE::Exception") { - $resp->{status} = $err->{code} if $err->{code}; - $resp->{errors} = $err->{errors} if $err->{errors}; - $resp->{message} = $err->{msg}; - } else { - $resp->{message} = $err; - } - push @$res, $resp; - } - } - - return $res; - }}); + push @$res, + { + status => HTTP_OK, + data => $handler->handle($info, $uri_param), + }; + }; + if (my $err = $@) { + my $resp = { status => HTTP_INTERNAL_SERVER_ERROR }; + if (ref($err) eq "PVE::Exception") { + $resp->{status} = $err->{code} if $err->{code}; + $resp->{errors} = $err->{errors} if $err->{errors}; + $resp->{message} = $err->{msg}; + } else { + $resp->{message} = $err; + } + push @$res, $resp; + } + } + return $res; + }, +}); __PACKAGE__->register_method({ name => 'node_cmd', path => 'status', method => 'POST', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]], + check => ['perm', '/nodes/{node}', ['Sys.PowerMgmt']], }, protected => 1, description => "Reboot or shutdown a node.", proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - command => { - description => "Specify the command.", - type => 'string', - enum => [qw(reboot shutdown)], - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + command => { + description => "Specify the command.", + type => 'string', + enum => [qw(reboot shutdown)], + }, + }, }, returns => { type => "null" }, code => sub { - my ($param) = @_; + my ($param) = @_; - if ($param->{command} eq 'reboot') { - system ("(sleep 2;/sbin/reboot)&"); - } elsif ($param->{command} eq 'shutdown') { - system ("(sleep 2;/sbin/poweroff)&"); - } + if ($param->{command} eq 'reboot') { + system("(sleep 2;/sbin/reboot)&"); + } elsif ($param->{command} eq 'shutdown') { + system("(sleep 2;/sbin/poweroff)&"); + } - return; - }}); + return; + }, +}); __PACKAGE__->register_method({ name => 'wakeonlan', path => 'wakeonlan', method => 'POST', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.PowerMgmt' ]], + check => ['perm', '/nodes/{node}', ['Sys.PowerMgmt']], }, protected => 1, description => "Try to wake a node via 'wake on LAN' network packet.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node', { - description => 'target node for wake on LAN packet', - completion => sub { - my $members = PVE::Cluster::get_members(); - return [ grep { !$members->{$_}->{online} } keys %$members ]; - } - }), - }, + additionalProperties => 0, + properties => { + node => get_standard_option( + 'pve-node', + { + description => 'target node for wake on LAN packet', + completion => sub { + my $members = PVE::Cluster::get_members(); + return [grep { !$members->{$_}->{online} } keys %$members]; + }, + }, + ), + }, }, returns => { - type => 'string', - format => 'mac-addr', - description => 'MAC address used to assemble the WoL magic packet.', + type => 'string', + format => 'mac-addr', + description => 'MAC address used to assemble the WoL magic packet.', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $node = $param->{node}; - my $local_node = PVE::INotify::nodename(); + my $node = $param->{node}; + my $local_node = PVE::INotify::nodename(); - die "'$node' is local node, cannot wake my self!\n" - if $node eq 'localhost' || $node eq $local_node; + die "'$node' is local node, cannot wake my self!\n" + if $node eq 'localhost' || $node eq $local_node; - PVE::Cluster::check_node_exists($node); + PVE::Cluster::check_node_exists($node); - my $config = PVE::NodeConfig::load_config($node); - my $wol_config = PVE::NodeConfig::get_wakeonlan_config($config); - my $mac_addr = $wol_config->{mac}; - if (!defined($mac_addr)) { - die "No wake on LAN MAC address defined for '$node'!\n"; - } + my $config = PVE::NodeConfig::load_config($node); + my $wol_config = PVE::NodeConfig::get_wakeonlan_config($config); + my $mac_addr = $wol_config->{mac}; + if (!defined($mac_addr)) { + die "No wake on LAN MAC address defined for '$node'!\n"; + } - my $local_config = PVE::NodeConfig::load_config($local_node); - my $local_wol_config = PVE::NodeConfig::get_wakeonlan_config($local_config); - my $broadcast_addr = $local_wol_config->{'broadcast-address'} // '255.255.255.255'; + my $local_config = PVE::NodeConfig::load_config($local_node); + my $local_wol_config = PVE::NodeConfig::get_wakeonlan_config($local_config); + my $broadcast_addr = $local_wol_config->{'broadcast-address'} // '255.255.255.255'; - $mac_addr =~ s/://g; - my $packet = chr(0xff) x 6 . pack('H*', $mac_addr) x 16; + $mac_addr =~ s/://g; + my $packet = chr(0xff) x 6 . pack('H*', $mac_addr) x 16; - my $addr = gethostbyname($broadcast_addr); - my $port = getservbyname('discard', 'udp'); - my $to = Socket::pack_sockaddr_in($port, $addr); + my $addr = gethostbyname($broadcast_addr); + my $port = getservbyname('discard', 'udp'); + my $to = Socket::pack_sockaddr_in($port, $addr); - socket(my $sock, Socket::AF_INET, Socket::SOCK_DGRAM, Socket::IPPROTO_UDP) - || die "Unable to open socket: $!\n"; - setsockopt($sock, Socket::SOL_SOCKET, Socket::SO_BROADCAST, 1) - || die "Unable to set socket option: $!\n"; + socket(my $sock, Socket::AF_INET, Socket::SOCK_DGRAM, Socket::IPPROTO_UDP) + || die "Unable to open socket: $!\n"; + setsockopt($sock, Socket::SOL_SOCKET, Socket::SO_BROADCAST, 1) + || die "Unable to set socket option: $!\n"; - if (defined(my $bind_iface = $local_wol_config->{'bind-interface'})) { - my $bind_iface_raw = pack('Z*', $bind_iface); # Null terminated interface name - setsockopt($sock, Socket::SOL_SOCKET, Socket::SO_BINDTODEVICE, $bind_iface_raw) - || die "Unable to bind socket to interface '$bind_iface': $!\n"; - } + if (defined(my $bind_iface = $local_wol_config->{'bind-interface'})) { + my $bind_iface_raw = pack('Z*', $bind_iface); # Null terminated interface name + setsockopt($sock, Socket::SOL_SOCKET, Socket::SO_BINDTODEVICE, $bind_iface_raw) + || die "Unable to bind socket to interface '$bind_iface': $!\n"; + } - send($sock, $packet, 0, $to) - || die "Unable to send packet: $!\n"; + send($sock, $packet, 0, $to) + || die "Unable to send packet: $!\n"; - close($sock); + close($sock); - return $wol_config->{mac}; - }}); + return $wol_config->{mac}; + }, +}); __PACKAGE__->register_method({ name => 'rrd', @@ -808,44 +826,46 @@ __PACKAGE__->register_method({ method => 'GET', protected => 1, # fixme: can we avoid that? permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], + check => ['perm', '/nodes/{node}', ['Sys.Audit']], }, description => "Read node RRD statistics (returns PNG)", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - timeframe => { - description => "Specify the time frame you are interested in.", - type => 'string', - enum => [ 'hour', 'day', 'week', 'month', 'year' ], - }, - ds => { - description => "The list of datasources you want to display.", - type => 'string', format => 'pve-configid-list', - }, - cf => { - description => "The RRD consolidation function", - type => 'string', - enum => [ 'AVERAGE', 'MAX' ], - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + timeframe => { + description => "Specify the time frame you are interested in.", + type => 'string', + enum => ['hour', 'day', 'week', 'month', 'year'], + }, + ds => { + description => "The list of datasources you want to display.", + type => 'string', + format => 'pve-configid-list', + }, + cf => { + description => "The RRD consolidation function", + type => 'string', + enum => ['AVERAGE', 'MAX'], + optional => 1, + }, + }, }, returns => { - type => "object", - properties => { - filename => { type => 'string' }, - }, + type => "object", + properties => { + filename => { type => 'string' }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - return PVE::RRD::create_rrd_graph( - "pve2-node/$param->{node}", $param->{timeframe}, - $param->{ds}, $param->{cf}); + return PVE::RRD::create_rrd_graph( + "pve2-node/$param->{node}", $param->{timeframe}, $param->{ds}, $param->{cf}, + ); - }}); + }, +}); __PACKAGE__->register_method({ name => 'rrddata', @@ -853,39 +873,40 @@ __PACKAGE__->register_method({ method => 'GET', protected => 1, # fixme: can we avoid that? permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], + check => ['perm', '/nodes/{node}', ['Sys.Audit']], }, description => "Read node RRD statistics", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - timeframe => { - description => "Specify the time frame you are interested in.", - type => 'string', - enum => [ 'hour', 'day', 'week', 'month', 'year' ], - }, - cf => { - description => "The RRD consolidation function", - type => 'string', - enum => [ 'AVERAGE', 'MAX' ], - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + timeframe => { + description => "Specify the time frame you are interested in.", + type => 'string', + enum => ['hour', 'day', 'week', 'month', 'year'], + }, + cf => { + description => "The RRD consolidation function", + type => 'string', + enum => ['AVERAGE', 'MAX'], + optional => 1, + }, + }, }, returns => { - type => "array", - items => { - type => "object", - properties => {}, - }, + type => "array", + items => { + type => "object", + properties => {}, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - return PVE::RRD::create_rrd_data( - "pve2-node/$param->{node}", $param->{timeframe}, $param->{cf}); - }}); + return PVE::RRD::create_rrd_data("pve2-node/$param->{node}", $param->{timeframe}, + $param->{cf}); + }, +}); __PACKAGE__->register_method({ name => 'syslog', @@ -894,82 +915,88 @@ __PACKAGE__->register_method({ description => "Read system log", proxyto => 'node', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]], + check => ['perm', '/nodes/{node}', ['Sys.Syslog']], }, protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - start => { - type => 'integer', - minimum => 0, - optional => 1, - }, - limit => { - type => 'integer', - minimum => 0, - optional => 1, - }, - since => { - type=> 'string', - pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$', - description => "Display all log since this date-time string.", - optional => 1, - }, - until => { - type=> 'string', - pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$', - description => "Display all log until this date-time string.", - optional => 1, - }, - service => { - description => "Service ID", - type => 'string', - maxLength => 128, - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + start => { + type => 'integer', + minimum => 0, + optional => 1, + }, + limit => { + type => 'integer', + minimum => 0, + optional => 1, + }, + since => { + type => 'string', + pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$', + description => "Display all log since this date-time string.", + optional => 1, + }, + until => { + type => 'string', + pattern => '^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}(:\d{2})?)?$', + description => "Display all log until this date-time string.", + optional => 1, + }, + service => { + description => "Service ID", + type => 'string', + maxLength => 128, + optional => 1, + }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - n => { - description=> "Line number", - type=> 'integer', - }, - t => { - description=> "Line text", - type => 'string', - } - } - } + type => 'array', + items => { + type => "object", + properties => { + n => { + description => "Line number", + type => 'integer', + }, + t => { + description => "Line text", + type => 'string', + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); - my $node = $param->{node}; - my $service; + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); + my $node = $param->{node}; + my $service; - if ($param->{service}) { - my $service_aliases = { - 'postfix' => 'postfix@-', - }; + if ($param->{service}) { + my $service_aliases = { + 'postfix' => 'postfix@-', + }; - $service = $service_aliases->{$param->{service}} // $param->{service}; - } + $service = $service_aliases->{ $param->{service} } // $param->{service}; + } - my ($count, $lines) = PVE::Tools::dump_journal($param->{start}, $param->{limit}, - $param->{since}, $param->{until}, $service); + my ($count, $lines) = PVE::Tools::dump_journal( + $param->{start}, + $param->{limit}, + $param->{since}, + $param->{until}, + $service, + ); - $rpcenv->set_result_attrib('total', $count); + $rpcenv->set_result_attrib('total', $count); - return $lines; - }}); + return $lines; + }, +}); __PACKAGE__->register_method({ name => 'journal', @@ -978,109 +1005,114 @@ __PACKAGE__->register_method({ description => "Read Journal", proxyto => 'node', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]], + check => ['perm', '/nodes/{node}', ['Sys.Syslog']], }, protected => 1, download_allowed => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - since => { - type=> 'integer', - minimum => 0, - description => "Display all log since this UNIX epoch. Conflicts with 'startcursor'.", - optional => 1, - }, - until => { - type=> 'integer', - minimum => 0, - description => "Display all log until this UNIX epoch. Conflicts with 'endcursor'.", - optional => 1, - }, - lastentries => { - description => "Limit to the last X lines. Conflicts with a range.", - type => 'integer', - minimum => 0, - optional => 1, - }, - startcursor => { - description => "Start after the given Cursor. Conflicts with 'since'", - type => 'string', - optional => 1, - }, - endcursor => { - description => "End before the given Cursor. Conflicts with 'until'", - type => 'string', - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + since => { + type => 'integer', + minimum => 0, + description => + "Display all log since this UNIX epoch. Conflicts with 'startcursor'.", + optional => 1, + }, + until => { + type => 'integer', + minimum => 0, + description => + "Display all log until this UNIX epoch. Conflicts with 'endcursor'.", + optional => 1, + }, + lastentries => { + description => "Limit to the last X lines. Conflicts with a range.", + type => 'integer', + minimum => 0, + optional => 1, + }, + startcursor => { + description => "Start after the given Cursor. Conflicts with 'since'", + type => 'string', + optional => 1, + }, + endcursor => { + description => "End before the given Cursor. Conflicts with 'until'", + type => 'string', + optional => 1, + }, + }, }, returns => { - type => 'array', - items => { - type => "string", - } + type => 'array', + items => { + type => "string", + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); - my $cmd = ["/usr/bin/mini-journalreader", "-j"]; - push @$cmd, '-n', $param->{lastentries} if $param->{lastentries}; - push @$cmd, '-b', $param->{since} if $param->{since}; - push @$cmd, '-e', $param->{until} if $param->{until}; - push @$cmd, '-f', PVE::Tools::shellquote($param->{startcursor}) if $param->{startcursor}; - push @$cmd, '-t', PVE::Tools::shellquote($param->{endcursor}) if $param->{endcursor}; - push @$cmd, ' | gzip '; + my $cmd = ["/usr/bin/mini-journalreader", "-j"]; + push @$cmd, '-n', $param->{lastentries} if $param->{lastentries}; + push @$cmd, '-b', $param->{since} if $param->{since}; + push @$cmd, '-e', $param->{until} if $param->{until}; + push @$cmd, '-f', PVE::Tools::shellquote($param->{startcursor}) + if $param->{startcursor}; + push @$cmd, '-t', PVE::Tools::shellquote($param->{endcursor}) if $param->{endcursor}; + push @$cmd, ' | gzip '; - open(my $fh, "-|", join(' ', @$cmd)) - or die "could not start mini-journalreader"; + open(my $fh, "-|", join(' ', @$cmd)) + or die "could not start mini-journalreader"; - return { - download => { - fh => $fh, - stream => 1, - 'content-type' => 'application/json', - 'content-encoding' => 'gzip', - }, - }, - }}); + return { + download => { + fh => $fh, + stream => 1, + 'content-type' => 'application/json', + 'content-encoding' => 'gzip', + }, + }, + ; + }, +}); my $sslcert; my $shell_cmd_map = { 'login' => { - cmd => [ '/bin/login', '-f', 'root' ], + cmd => ['/bin/login', '-f', 'root'], }, 'upgrade' => { - cmd => [ '/usr/bin/pveupgrade', '--shell' ], + cmd => ['/usr/bin/pveupgrade', '--shell'], }, 'ceph_install' => { - cmd => [ '/usr/bin/pveceph', 'install' ], - allow_args => 1, + cmd => ['/usr/bin/pveceph', 'install'], + allow_args => 1, }, }; -sub get_shell_command { +sub get_shell_command { my ($user, $shellcmd, $args) = @_; my $cmd; if ($user eq 'root@pam') { - if (defined($shellcmd) && exists($shell_cmd_map->{$shellcmd})) { - my $def = $shell_cmd_map->{$shellcmd}; - $cmd = [ @{$def->{cmd}} ]; # clone - if (defined($args) && $def->{allow_args}) { - push @$cmd, split("\0", $args); - } - } else { - $cmd = [ '/bin/login', '-f', 'root' ]; - } + if (defined($shellcmd) && exists($shell_cmd_map->{$shellcmd})) { + my $def = $shell_cmd_map->{$shellcmd}; + $cmd = [@{ $def->{cmd} }]; # clone + if (defined($args) && $def->{allow_args}) { + push @$cmd, split("\0", $args); + } + } else { + $cmd = ['/bin/login', '-f', 'root']; + } } else { - # non-root must always login for now, we do not have a superuser role! - $cmd = [ '/bin/login' ]; + # non-root must always login for now, we do not have a superuser role! + $cmd = ['/bin/login']; } return $cmd; } @@ -1092,389 +1124,407 @@ my $get_vnc_connection_info = sub { my ($remip, $family); if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) { - ($remip, $family) = PVE::Cluster::remote_node_ip($node); - $remote_cmd = PVE::SSHInfo::ssh_info_to_command({ ip => $remip, name => $node }, ('-t')); - push @$remote_cmd, '--'; + ($remip, $family) = PVE::Cluster::remote_node_ip($node); + $remote_cmd = + PVE::SSHInfo::ssh_info_to_command({ ip => $remip, name => $node }, ('-t')); + push @$remote_cmd, '--'; } else { - $family = PVE::Tools::get_host_address_family($node); + $family = PVE::Tools::get_host_address_family($node); } my $port = PVE::Tools::next_vnc_port($family); return ($port, $remote_cmd); }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'vncshell', path => 'vncshell', method => 'POST', protected => 1, permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]], + check => ['perm', '/nodes/{node}', ['Sys.Console']], }, description => "Creates a VNC Shell proxy.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - cmd => { - type => 'string', - description => "Run specific command or default to login (requires 'root\@pam')", - enum => [keys %$shell_cmd_map], - optional => 1, - default => 'login', - }, - 'cmd-opts' => { - type => 'string', - description => "Add parameters to a command. Encoded as null terminated strings.", - requires => 'cmd', - optional => 1, - default => '', - }, - websocket => { - optional => 1, - type => 'boolean', - description => "use websocket instead of standard vnc.", - }, - width => { - optional => 1, - description => "sets the width of the console in pixels.", - type => 'integer', - minimum => 16, - maximum => 4096, - }, - height => { - optional => 1, - description => "sets the height of the console in pixels.", - type => 'integer', - minimum => 16, - maximum => 2160, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + cmd => { + type => 'string', + description => + "Run specific command or default to login (requires 'root\@pam')", + enum => [keys %$shell_cmd_map], + optional => 1, + default => 'login', + }, + 'cmd-opts' => { + type => 'string', + description => + "Add parameters to a command. Encoded as null terminated strings.", + requires => 'cmd', + optional => 1, + default => '', + }, + websocket => { + optional => 1, + type => 'boolean', + description => "use websocket instead of standard vnc.", + }, + width => { + optional => 1, + description => "sets the width of the console in pixels.", + type => 'integer', + minimum => 16, + maximum => 4096, + }, + height => { + optional => 1, + description => "sets the height of the console in pixels.", + type => 'integer', + minimum => 16, + maximum => 2160, + }, + }, }, returns => { - additionalProperties => 0, - properties => { - user => { type => 'string' }, - ticket => { type => 'string' }, - cert => { type => 'string' }, - port => { type => 'integer' }, - upid => { type => 'string' }, - }, + additionalProperties => 0, + properties => { + user => { type => 'string' }, + ticket => { type => 'string' }, + cert => { type => 'string' }, + port => { type => 'integer' }, + upid => { type => 'string' }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user()); + my $rpcenv = PVE::RPCEnvironment::get(); + my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user()); + if (defined($param->{cmd}) && $param->{cmd} ne 'login' && $user ne 'root@pam') { + raise_perm_exc('user != root@pam'); + } - if (defined($param->{cmd}) && $param->{cmd} ne 'login' && $user ne 'root@pam') { - raise_perm_exc('user != root@pam'); - } + my $node = $param->{node}; - my $node = $param->{node}; + my $authpath = "/nodes/$node"; + my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath); - my $authpath = "/nodes/$node"; - my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath); + $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192) + if !$sslcert; - $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192) - if !$sslcert; + my ($port, $remcmd) = $get_vnc_connection_info->($node); - my ($port, $remcmd) = $get_vnc_connection_info->($node); + my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'}); - my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'}); + my $timeout = 10; - my $timeout = 10; + my $cmd = [ + '/usr/bin/vncterm', + '-rfbport', + $port, + '-timeout', + $timeout, + '-authpath', + $authpath, + '-perm', + 'Sys.Console', + ]; - my $cmd = ['/usr/bin/vncterm', - '-rfbport', $port, - '-timeout', $timeout, - '-authpath', $authpath, - '-perm', 'Sys.Console', - ]; + push @$cmd, '-width', $param->{width} if $param->{width}; + push @$cmd, '-height', $param->{height} if $param->{height}; - push @$cmd, '-width', $param->{width} if $param->{width}; - push @$cmd, '-height', $param->{height} if $param->{height}; + if ($param->{websocket}) { + $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm + push @$cmd, '-notls', '-listen', 'localhost'; + } - if ($param->{websocket}) { - $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm - push @$cmd, '-notls', '-listen', 'localhost'; - } + push @$cmd, '-c', @$remcmd, @$shcmd; - push @$cmd, '-c', @$remcmd, @$shcmd; + my $realcmd = sub { + my $upid = shift; - my $realcmd = sub { - my $upid = shift; + syslog('info', "starting vnc proxy $upid\n"); - syslog ('info', "starting vnc proxy $upid\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 'PVE_VNC_TICKET'; + next + if $k eq 'PATH' + || $k eq 'TERM' + || $k eq 'USER' + || $k eq 'HOME' + || $k eq 'LANG' + || $k eq 'LANGUAGE'; + delete $ENV{$k}; + } + $ENV{PWD} = '/'; - eval { - foreach my $k (keys %ENV) { - next if $k eq 'PVE_VNC_TICKET'; - next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME' || $k eq 'LANG' || $k eq 'LANGUAGE'; - delete $ENV{$k}; - } - $ENV{PWD} = '/'; + PVE::Tools::run_command($cmd, errmsg => "vncterm failed", keeplocale => 1); + }; + if (my $err = $@) { + syslog('err', $err); + } - PVE::Tools::run_command($cmd, errmsg => "vncterm failed", keeplocale => 1); - }; - if (my $err = $@) { - syslog ('err', $err); - } + return; + }; - return; - }; + my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd); - my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd); + PVE::Tools::wait_for_vnc_port($port); - PVE::Tools::wait_for_vnc_port($port); + return { + user => $user, + ticket => $ticket, + port => $port, + upid => $upid, + cert => $sslcert, + }; + }, +}); - return { - user => $user, - ticket => $ticket, - port => $port, - upid => $upid, - cert => $sslcert, - }; - }}); - -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'termproxy', path => 'termproxy', method => 'POST', protected => 1, permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]], + check => ['perm', '/nodes/{node}', ['Sys.Console']], }, description => "Creates a VNC Shell proxy.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - cmd => { - type => 'string', - description => "Run specific command or default to login (requires 'root\@pam')", - enum => [keys %$shell_cmd_map], - optional => 1, - default => 'login', - }, - 'cmd-opts' => { - type => 'string', - description => "Add parameters to a command. Encoded as null terminated strings.", - requires => 'cmd', - optional => 1, - default => '', - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + cmd => { + type => 'string', + description => + "Run specific command or default to login (requires 'root\@pam')", + enum => [keys %$shell_cmd_map], + optional => 1, + default => 'login', + }, + 'cmd-opts' => { + type => 'string', + description => + "Add parameters to a command. Encoded as null terminated strings.", + requires => 'cmd', + optional => 1, + default => '', + }, + }, }, returns => { - additionalProperties => 0, - properties => { - user => { type => 'string' }, - ticket => { type => 'string' }, - port => { type => 'integer' }, - upid => { type => 'string' }, - }, + additionalProperties => 0, + properties => { + user => { type => 'string' }, + ticket => { type => 'string' }, + port => { type => 'integer' }, + upid => { type => 'string' }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user()); + my $rpcenv = PVE::RPCEnvironment::get(); + my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user()); - my $node = $param->{node}; - my $authpath = "/nodes/$node"; - my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath); + my $node = $param->{node}; + my $authpath = "/nodes/$node"; + my $ticket = PVE::AccessControl::assemble_vnc_ticket($user, $authpath); - my ($port, $remcmd) = $get_vnc_connection_info->($node); + my ($port, $remcmd) = $get_vnc_connection_info->($node); - my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'}); + my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'}); - my $realcmd = sub { - my $upid = shift; + my $realcmd = sub { + my $upid = shift; - syslog ('info', "starting termproxy $upid\n"); + syslog('info', "starting termproxy $upid\n"); - my $cmd = [ - '/usr/bin/termproxy', - $port, - '--path', $authpath, - '--perm', 'Sys.Console', - '--' - ]; - push @$cmd, @$remcmd, @$shcmd; + my $cmd = [ + '/usr/bin/termproxy', $port, '--path', $authpath, '--perm', 'Sys.Console', '--', + ]; + push @$cmd, @$remcmd, @$shcmd; - PVE::Tools::run_command($cmd); - }; - my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd); + PVE::Tools::run_command($cmd); + }; + my $upid = $rpcenv->fork_worker('vncshell', "", $user, $realcmd); - PVE::Tools::wait_for_vnc_port($port); + PVE::Tools::wait_for_vnc_port($port); - return { - user => $user, - ticket => $ticket, - port => $port, - upid => $upid, - }; - }}); + return { + user => $user, + ticket => $ticket, + port => $port, + upid => $upid, + }; + }, +}); __PACKAGE__->register_method({ name => 'vncwebsocket', path => 'vncwebsocket', method => 'GET', permissions => { - description => "You also need to pass a valid ticket (vncticket).", - check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]], + description => "You also need to pass a valid ticket (vncticket).", + check => ['perm', '/nodes/{node}', ['Sys.Console']], }, description => "Opens a websocket for VNC traffic.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - vncticket => { - description => "Ticket from previous call to vncproxy.", - type => 'string', - maxLength => 512, - }, - port => { - description => "Port number returned by previous vncproxy call.", - type => 'integer', - minimum => 5900, - maximum => 5999, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + vncticket => { + description => "Ticket from previous call to vncproxy.", + type => 'string', + maxLength => 512, + }, + port => { + description => "Port number returned by previous vncproxy call.", + type => 'integer', + minimum => 5900, + maximum => 5999, + }, + }, }, returns => { - type => "object", - properties => { - port => { type => 'string' }, - }, + type => "object", + properties => { + port => { type => 'string' }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user()); + my ($user, undef, $realm) = PVE::AccessControl::verify_username($rpcenv->get_user()); - my $authpath = "/nodes/$param->{node}"; + my $authpath = "/nodes/$param->{node}"; - PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $user, $authpath); + PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $user, $authpath); - my $port = $param->{port}; + my $port = $param->{port}; - return { port => $port }; - }}); + return { port => $port }; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'spiceshell', path => 'spiceshell', method => 'POST', protected => 1, proxyto => 'node', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Console' ]], + check => ['perm', '/nodes/{node}', ['Sys.Console']], }, description => "Creates a SPICE shell.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - proxy => get_standard_option('spice-proxy', { optional => 1 }), - cmd => { - type => 'string', - description => "Run specific command or default to login (requires 'root\@pam')", - enum => [keys %$shell_cmd_map], - optional => 1, - default => 'login', - }, - 'cmd-opts' => { - type => 'string', - description => "Add parameters to a command. Encoded as null terminated strings.", - requires => 'cmd', - optional => 1, - default => '', - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + proxy => get_standard_option('spice-proxy', { optional => 1 }), + cmd => { + type => 'string', + description => + "Run specific command or default to login (requires 'root\@pam')", + enum => [keys %$shell_cmd_map], + optional => 1, + default => 'login', + }, + 'cmd-opts' => { + type => 'string', + description => + "Add parameters to a command. Encoded as null terminated strings.", + requires => 'cmd', + optional => 1, + default => '', + }, + }, }, returns => get_standard_option('remote-viewer-config'), code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my ($user, undef, $realm) = PVE::AccessControl::verify_username($authuser); + my ($user, undef, $realm) = PVE::AccessControl::verify_username($authuser); + if (defined($param->{cmd}) && $param->{cmd} ne 'login' && $user ne 'root@pam') { + raise_perm_exc('user != root@pam'); + } - if (defined($param->{cmd}) && $param->{cmd} ne 'login' && $user ne 'root@pam') { - raise_perm_exc('user != root@pam'); - } + my $node = $param->{node}; + my $proxy = $param->{proxy}; - my $node = $param->{node}; - my $proxy = $param->{proxy}; + my $authpath = "/nodes/$node"; + my $permissions = 'Sys.Console'; - my $authpath = "/nodes/$node"; - my $permissions = 'Sys.Console'; + my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'}); - my $shcmd = get_shell_command($user, $param->{cmd}, $param->{'cmd-opts'}); + my $title = "Shell on '$node'"; - my $title = "Shell on '$node'"; - - return PVE::API2Tools::run_spiceterm($authpath, $permissions, 0, $node, $proxy, $title, $shcmd); - }}); + return PVE::API2Tools::run_spiceterm($authpath, $permissions, 0, $node, $proxy, $title, + $shcmd); + }, +}); __PACKAGE__->register_method({ name => 'dns', path => 'dns', method => 'GET', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], + check => ['perm', '/nodes/{node}', ['Sys.Audit']], }, description => "Read DNS settings.", proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => "object", - additionalProperties => 0, - properties => { - search => { - description => "Search domain for host-name lookup.", - type => 'string', - optional => 1, - }, - dns1 => { - description => 'First name server IP address.', - type => 'string', - optional => 1, - }, - dns2 => { - description => 'Second name server IP address.', - type => 'string', - optional => 1, - }, - dns3 => { - description => 'Third name server IP address.', - type => 'string', - optional => 1, - }, - }, + type => "object", + additionalProperties => 0, + properties => { + search => { + description => "Search domain for host-name lookup.", + type => 'string', + optional => 1, + }, + dns1 => { + description => 'First name server IP address.', + type => 'string', + optional => 1, + }, + dns2 => { + description => 'Second name server IP address.', + type => 'string', + optional => 1, + }, + dns3 => { + description => 'Third name server IP address.', + type => 'string', + optional => 1, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $res = PVE::INotify::read_file('resolvconf'); + my $res = PVE::INotify::read_file('resolvconf'); - return $res; - }}); + return $res; + }, +}); __PACKAGE__->register_method({ name => 'update_dns', @@ -1482,94 +1532,99 @@ __PACKAGE__->register_method({ method => 'PUT', description => "Write DNS settings.", permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - search => { - description => "Search domain for host-name lookup.", - type => 'string', - }, - dns1 => { - description => 'First name server IP address.', - type => 'string', format => 'ip', - optional => 1, - }, - dns2 => { - description => 'Second name server IP address.', - type => 'string', format => 'ip', - optional => 1, - }, - dns3 => { - description => 'Third name server IP address.', - type => 'string', format => 'ip', - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + search => { + description => "Search domain for host-name lookup.", + type => 'string', + }, + dns1 => { + description => 'First name server IP address.', + type => 'string', + format => 'ip', + optional => 1, + }, + dns2 => { + description => 'Second name server IP address.', + type => 'string', + format => 'ip', + optional => 1, + }, + dns3 => { + description => 'Third name server IP address.', + type => 'string', + format => 'ip', + optional => 1, + }, + }, }, returns => { type => "null" }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::INotify::update_file('resolvconf', $param); + PVE::INotify::update_file('resolvconf', $param); - return; - }}); + return; + }, +}); __PACKAGE__->register_method({ name => 'time', path => 'time', method => 'GET', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], - }, + check => ['perm', '/nodes/{node}', ['Sys.Audit']], + }, description => "Read server time and time zone settings.", proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => "object", - additionalProperties => 0, - properties => { - timezone => { - description => "Time zone", - type => 'string', - }, - time => { - description => "Seconds since 1970-01-01 00:00:00 UTC.", - type => 'integer', - minimum => 1297163644, - renderer => 'timestamp', - }, - localtime => { - description => "Seconds since 1970-01-01 00:00:00 (local time)", - type => 'integer', - minimum => 1297163644, - renderer => 'timestamp_gmt', - }, - }, + type => "object", + additionalProperties => 0, + properties => { + timezone => { + description => "Time zone", + type => 'string', + }, + time => { + description => "Seconds since 1970-01-01 00:00:00 UTC.", + type => 'integer', + minimum => 1297163644, + renderer => 'timestamp', + }, + localtime => { + description => "Seconds since 1970-01-01 00:00:00 (local time)", + type => 'integer', + minimum => 1297163644, + renderer => 'timestamp_gmt', + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $ctime = time(); - my $ltime = timegm_nocheck(localtime($ctime)); - my $res = { - timezone => PVE::INotify::read_file('timezone'), - time => $ctime, - localtime => $ltime, - }; + my $ctime = time(); + my $ltime = timegm_nocheck(localtime($ctime)); + my $res = { + timezone => PVE::INotify::read_file('timezone'), + time => $ctime, + localtime => $ltime, + }; - return $res; - }}); + return $res; + }, +}); __PACKAGE__->register_method({ name => 'set_timezone', @@ -1577,131 +1632,142 @@ __PACKAGE__->register_method({ method => 'PUT', description => "Set time zone.", permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - timezone => { - description => "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.", - type => 'string', - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + timezone => { + description => + "Time zone. The file '/usr/share/zoneinfo/zone.tab' contains the list of valid names.", + type => 'string', + }, + }, }, returns => { type => "null" }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::INotify::write_file('timezone', $param->{timezone}); + PVE::INotify::write_file('timezone', $param->{timezone}); - return; - }}); + return; + }, +}); __PACKAGE__->register_method({ name => 'aplinfo', path => 'aplinfo', method => 'GET', permissions => { - user => 'all', + user => 'all', }, description => "Get list of appliances.", proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, + type => 'array', + items => { + type => "object", + properties => {}, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $list = PVE::APLInfo::load_data(); + my $list = PVE::APLInfo::load_data(); - my $res = []; - for my $appliance (values %{$list->{all}}) { - next if $appliance->{'package'} eq 'pve-web-news'; - push @$res, $appliance; - } + my $res = []; + for my $appliance (values %{ $list->{all} }) { + next if $appliance->{'package'} eq 'pve-web-news'; + push @$res, $appliance; + } - return $res; - }}); + return $res; + }, +}); __PACKAGE__->register_method({ name => 'apl_download', path => 'aplinfo', method => 'POST', permissions => { - check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']], + check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']], }, description => "Download appliance templates.", proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - storage => get_standard_option('pve-storage-id', { - description => "The storage where the template will be stored", - completion => \&PVE::Storage::complete_storage_enabled, - }), - template => { - type => 'string', - description => "The template which will downloaded", - maxLength => 255, - completion => \&complete_templet_repo, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + storage => get_standard_option( + 'pve-storage-id', + { + description => "The storage where the template will be stored", + completion => \&PVE::Storage::complete_storage_enabled, + }, + ), + template => { + type => 'string', + description => "The template which will downloaded", + maxLength => 255, + completion => \&complete_templet_repo, + }, + }, }, returns => { type => "string" }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); - my $node = $param->{node}; - my $template = $param->{template}; + my $node = $param->{node}; + my $template = $param->{template}; - my $list = PVE::APLInfo::load_data(); - my $appliance = $list->{all}->{$template}; - raise_param_exc({ template => "no such template"}) if !$appliance; + my $list = PVE::APLInfo::load_data(); + my $appliance = $list->{all}->{$template}; + raise_param_exc({ template => "no such template" }) if !$appliance; - my $cfg = PVE::Storage::config(); - my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node); + my $cfg = PVE::Storage::config(); + my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node); - die "unknown template type '$appliance->{type}'\n" - if !($appliance->{type} eq 'openvz' || $appliance->{type} eq 'lxc'); + die "unknown template type '$appliance->{type}'\n" + if !($appliance->{type} eq 'openvz' || $appliance->{type} eq 'lxc'); - die "storage '$param->{storage}' does not support templates\n" - if !$scfg->{content}->{vztmpl}; + die "storage '$param->{storage}' does not support templates\n" + if !$scfg->{content}->{vztmpl}; - my $tmpldir = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage}); + my $tmpldir = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage}); - my $worker = sub { - my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg'); + my $worker = sub { + my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg'); - PVE::Tools::download_file_from_url("$tmpldir/$template", $appliance->{location}, { - hash_required => 1, - sha512sum => $appliance->{sha512sum}, - md5sum => $appliance->{md5sum}, - http_proxy => $dccfg->{http_proxy}, - }); - }; + PVE::Tools::download_file_from_url( + "$tmpldir/$template", + $appliance->{location}, + { + hash_required => 1, + sha512sum => $appliance->{sha512sum}, + md5sum => $appliance->{md5sum}, + http_proxy => $dccfg->{http_proxy}, + }, + ); + }; - my $upid = $rpcenv->fork_worker('download', $template, $user, $worker); + my $upid = $rpcenv->fork_worker('download', $template, $user, $worker); - return $upid; - }}); + return $upid; + }, +}); __PACKAGE__->register_method({ name => 'query_url_metadata', @@ -1710,119 +1776,125 @@ __PACKAGE__->register_method({ description => "Query metadata of an URL: file size, file name and mime type.", proxyto => 'node', permissions => { - check => ['or', - ['perm', '/', [ 'Sys.Audit', 'Sys.Modify' ]], - ['perm', '/nodes/{node}', [ 'Sys.AccessNetwork' ]], - ], + check => [ + 'or', + ['perm', '/', ['Sys.Audit', 'Sys.Modify']], + ['perm', '/nodes/{node}', ['Sys.AccessNetwork']], + ], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - url => { - description => "The URL to query the metadata from.", - type => 'string', - pattern => 'https?://.*', - }, - 'verify-certificates' => { - description => "If false, no SSL/TLS certificates will be verified.", - type => 'boolean', - optional => 1, - default => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + url => { + description => "The URL to query the metadata from.", + type => 'string', + pattern => 'https?://.*', + }, + 'verify-certificates' => { + description => "If false, no SSL/TLS certificates will be verified.", + type => 'boolean', + optional => 1, + default => 1, + }, + }, }, returns => { - type => "object", - properties => { - filename => { - type => 'string', - optional => 1, - }, - size => { - type => 'integer', - renderer => 'bytes', - optional => 1, - }, - mimetype => { - type => 'string', - optional => 1, - }, - }, + type => "object", + properties => { + filename => { + type => 'string', + optional => 1, + }, + size => { + type => 'integer', + renderer => 'bytes', + optional => 1, + }, + mimetype => { + type => 'string', + optional => 1, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $url = $param->{url}; + my $url = $param->{url}; - my $ua = LWP::UserAgent->new(); - $ua->agent("Proxmox VE"); + my $ua = LWP::UserAgent->new(); + $ua->agent("Proxmox VE"); - my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg'); - if ($dccfg->{http_proxy}) { - $ua->proxy(['http', 'https'], $dccfg->{http_proxy}); - } + my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg'); + if ($dccfg->{http_proxy}) { + $ua->proxy(['http', 'https'], $dccfg->{http_proxy}); + } - my $verify = $param->{'verify-certificates'} // 1; - if (!$verify) { - $ua->ssl_opts( - verify_hostname => 0, - SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE, - ); - } + my $verify = $param->{'verify-certificates'} // 1; + if (!$verify) { + $ua->ssl_opts( + verify_hostname => 0, + SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE, + ); + } - my $req = HTTP::Request->new(HEAD => $url); - my $res = $ua->request($req); + my $req = HTTP::Request->new(HEAD => $url); + my $res = $ua->request($req); - die "invalid server response: '" . $res->status_line() . "'\n" if ($res->code() != 200); + die "invalid server response: '" . $res->status_line() . "'\n" if ($res->code() != 200); - my $size = $res->header("Content-Length"); - my $disposition = $res->header("Content-Disposition"); - my $type = $res->header("Content-Type"); + my $size = $res->header("Content-Length"); + my $disposition = $res->header("Content-Disposition"); + my $type = $res->header("Content-Type"); - my $filename; + my $filename; - if ($disposition && ($disposition =~ m/filename="([^"]*)"/ || $disposition =~ m/filename=([^;]*)/)) { - $filename = $1; - } elsif ($url =~ m!^[^?]+/([^?/]*)(?:\?.*)?$!) { - $filename = $1; - } + if ( + $disposition + && ($disposition =~ m/filename="([^"]*)"/ || $disposition =~ m/filename=([^;]*)/) + ) { + $filename = $1; + } elsif ($url =~ m!^[^?]+/([^?/]*)(?:\?.*)?$!) { + $filename = $1; + } - # Content-Type: text/html; charset=utf-8 - if ($type && $type =~ m/^([^;]+);/) { - $type = $1; - } + # Content-Type: text/html; charset=utf-8 + if ($type && $type =~ m/^([^;]+);/) { + $type = $1; + } - my $ret = {}; - $ret->{filename} = $filename if $filename; - $ret->{size} = $size + 0 if $size; - $ret->{mimetype} = $type if $type; + my $ret = {}; + $ret->{filename} = $filename if $filename; + $ret->{size} = $size + 0 if $size; + $ret->{mimetype} = $type if $type; - return $ret; - }}); + return $ret; + }, +}); __PACKAGE__->register_method({ name => 'report', path => 'report', method => 'GET', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], + check => ['perm', '/nodes/{node}', ['Sys.Audit']], }, protected => 1, description => "Gather various systems information about a node", proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - return PVE::Report::generate(); - }}); + return PVE::Report::generate(); + }, +}); # returns a list of VMIDs, those can be filtered by # * current parent node @@ -1836,35 +1908,35 @@ my $get_filtered_vmlist = sub { my $vms_allowed; if (defined($vmfilter)) { - $vms_allowed = { map { $_ => 1 } PVE::Tools::split_list($vmfilter) }; + $vms_allowed = { map { $_ => 1 } PVE::Tools::split_list($vmfilter) }; } my $res = {}; - foreach my $vmid (keys %{$vmlist->{ids}}) { - next if defined($vms_allowed) && !$vms_allowed->{$vmid}; + foreach my $vmid (keys %{ $vmlist->{ids} }) { + next if defined($vms_allowed) && !$vms_allowed->{$vmid}; - my $d = $vmlist->{ids}->{$vmid}; - next if $nodename && $d->{node} ne $nodename; + my $d = $vmlist->{ids}->{$vmid}; + next if $nodename && $d->{node} ne $nodename; - eval { - my $class; - if ($d->{type} eq 'lxc') { - $class = 'PVE::LXC::Config'; - } elsif ($d->{type} eq 'qemu') { - $class = 'PVE::QemuConfig'; - } else { - die "unknown virtual guest type '$d->{type}'\n"; - } + eval { + my $class; + if ($d->{type} eq 'lxc') { + $class = 'PVE::LXC::Config'; + } elsif ($d->{type} eq 'qemu') { + $class = 'PVE::QemuConfig'; + } else { + die "unknown virtual guest type '$d->{type}'\n"; + } - my $conf = $class->load_config($vmid); - return if !$templates && $class->is_template($conf); - return if !$ha_managed && PVE::HA::Config::vm_is_ha_managed($vmid); + my $conf = $class->load_config($vmid); + return if !$templates && $class->is_template($conf); + return if !$ha_managed && PVE::HA::Config::vm_is_ha_managed($vmid); - $res->{$vmid}->{conf} = $conf; - $res->{$vmid}->{type} = $d->{type}; - $res->{$vmid}->{class} = $class; - }; - warn $@ if $@; + $res->{$vmid}->{conf} = $conf; + $res->{$vmid}->{type} = $d->{type}; + $res->{$vmid}->{class} = $class; + }; + warn $@ if $@; } return $res; @@ -1881,14 +1953,15 @@ my $get_start_stop_list = sub { my $resList = {}; foreach my $vmid (keys %$vmlist) { - my $conf = $vmlist->{$vmid}->{conf}; - next if $autostart && !$conf->{onboot}; + my $conf = $vmlist->{$vmid}->{conf}; + next if $autostart && !$conf->{onboot}; - my $startup = $conf->{startup} ? PVE::JSONSchema::pve_parse_startup_order($conf->{startup}) : {}; - my $order = $startup->{order} = $startup->{order} // LONG_MAX; + my $startup = + $conf->{startup} ? PVE::JSONSchema::pve_parse_startup_order($conf->{startup}) : {}; + my $order = $startup->{order} = $startup->{order} // LONG_MAX; - $resList->{$order}->{$vmid} = $startup; - $resList->{$order}->{$vmid}->{type} = $vmlist->{$vmid}->{type}; + $resList->{$order}->{$vmid} = $startup; + $resList->{$order}->{$vmid}->{type} = $vmlist->{$vmid}->{type}; } return $resList; @@ -1900,472 +1973,487 @@ my $remove_locks_on_startup = sub { my $vmlist = &$get_filtered_vmlist($nodename, undef, undef, 1); foreach my $vmid (keys %$vmlist) { - my $conf = $vmlist->{$vmid}->{conf}; - my $class = $vmlist->{$vmid}->{class}; + my $conf = $vmlist->{$vmid}->{conf}; + my $class = $vmlist->{$vmid}->{class}; - eval { - if ($class->has_lock($conf, 'backup')) { - $class->remove_lock($vmid, 'backup'); - my $msg = "removed left over backup lock from '$vmid'!"; - warn "$msg\n"; # prints to task log - syslog('warning', $msg); - } - }; warn $@ if $@; + eval { + if ($class->has_lock($conf, 'backup')) { + $class->remove_lock($vmid, 'backup'); + my $msg = "removed left over backup lock from '$vmid'!"; + warn "$msg\n"; # prints to task log + syslog('warning', $msg); + } + }; + warn $@ if $@; } }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'startall', path => 'startall', method => 'POST', protected => 1, permissions => { - description => "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/' for " - ."each ID passed via the 'vms' parameter.", - user => 'all', + description => "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/' for " + . "each ID passed via the 'vms' parameter.", + user => 'all', }, proxyto => 'node', - description => "Start all VMs and containers located on this node (by default only those with onboot=1).", + description => + "Start all VMs and containers located on this node (by default only those with onboot=1).", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - force => { - optional => 1, - type => 'boolean', - default => 'off', - description => "Issue start command even if virtual guest have 'onboot' not set or set to off.", - }, - vms => { - description => "Only consider guests from this comma separated list of VMIDs.", - type => 'string', format => 'pve-vmid-list', - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + force => { + optional => 1, + type => 'boolean', + default => 'off', + description => + "Issue start command even if virtual guest have 'onboot' not set or set to off.", + }, + vms => { + description => "Only consider guests from this comma separated list of VMIDs.", + type => 'string', + format => 'pve-vmid-list', + optional => 1, + }, + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - if (!$rpcenv->check($authuser, "/", [ 'VM.PowerMgmt' ], 1)) { - my @vms = PVE::Tools::split_list($param->{vms}); - if (scalar(@vms) > 0) { - $rpcenv->check($authuser, "/vms/$_", [ 'VM.PowerMgmt' ]) for @vms; - } else { - raise_perm_exc("/, VM.PowerMgmt"); - } - } + if (!$rpcenv->check($authuser, "/", ['VM.PowerMgmt'], 1)) { + my @vms = PVE::Tools::split_list($param->{vms}); + if (scalar(@vms) > 0) { + $rpcenv->check($authuser, "/vms/$_", ['VM.PowerMgmt']) for @vms; + } else { + raise_perm_exc("/, VM.PowerMgmt"); + } + } - my $nodename = $param->{node}; - $nodename = PVE::INotify::nodename() if $nodename eq 'localhost'; + my $nodename = $param->{node}; + $nodename = PVE::INotify::nodename() if $nodename eq 'localhost'; - my $force = $param->{force}; + my $force = $param->{force}; - my $code = sub { - $rpcenv->{type} = 'priv'; # to start tasks in background + my $code = sub { + $rpcenv->{type} = 'priv'; # to start tasks in background - if (!PVE::Cluster::check_cfs_quorum(1)) { - print "waiting for quorum ...\n"; - do { - sleep(1); - } while (!PVE::Cluster::check_cfs_quorum(1)); - print "got quorum\n"; - } + if (!PVE::Cluster::check_cfs_quorum(1)) { + print "waiting for quorum ...\n"; + do { + sleep(1); + } while (!PVE::Cluster::check_cfs_quorum(1)); + print "got quorum\n"; + } - eval { # remove backup locks, but avoid running into a scheduled backup job - PVE::Tools::lock_file('/var/run/vzdump.lock', 10, $remove_locks_on_startup, $nodename); - }; - warn $@ if $@; + eval { # remove backup locks, but avoid running into a scheduled backup job + PVE::Tools::lock_file( + '/var/run/vzdump.lock', 10, $remove_locks_on_startup, $nodename, + ); + }; + warn $@ if $@; - my $autostart = $force ? undef : 1; - my $startList = $get_start_stop_list->($nodename, $autostart, $param->{vms}); + my $autostart = $force ? undef : 1; + my $startList = $get_start_stop_list->($nodename, $autostart, $param->{vms}); - # Note: use numeric sorting with <=> - for my $order (sort {$a <=> $b} keys %$startList) { - my $vmlist = $startList->{$order}; + # Note: use numeric sorting with <=> + for my $order (sort { $a <=> $b } keys %$startList) { + my $vmlist = $startList->{$order}; - for my $vmid (sort {$a <=> $b} keys %$vmlist) { - my $d = $vmlist->{$vmid}; + for my $vmid (sort { $a <=> $b } keys %$vmlist) { + my $d = $vmlist->{$vmid}; - PVE::Cluster::check_cfs_quorum(); # abort when we loose quorum + PVE::Cluster::check_cfs_quorum(); # abort when we loose quorum - eval { - my $default_delay = 0; - my $upid; + eval { + my $default_delay = 0; + my $upid; - if ($d->{type} eq 'lxc') { - return if PVE::LXC::check_running($vmid); - print STDERR "Starting CT $vmid\n"; - $upid = PVE::API2::LXC::Status->vm_start({node => $nodename, vmid => $vmid }); - } elsif ($d->{type} eq 'qemu') { - $default_delay = 3; # to reduce load - return if PVE::QemuServer::check_running($vmid, 1); - print STDERR "Starting VM $vmid\n"; - $upid = PVE::API2::Qemu->vm_start({node => $nodename, vmid => $vmid }); - } else { - die "unknown VM type '$d->{type}'\n"; - } + if ($d->{type} eq 'lxc') { + return if PVE::LXC::check_running($vmid); + print STDERR "Starting CT $vmid\n"; + $upid = PVE::API2::LXC::Status->vm_start( + { node => $nodename, vmid => $vmid }); + } elsif ($d->{type} eq 'qemu') { + $default_delay = 3; # to reduce load + return if PVE::QemuServer::check_running($vmid, 1); + print STDERR "Starting VM $vmid\n"; + $upid = + PVE::API2::Qemu->vm_start({ node => $nodename, vmid => $vmid }); + } else { + die "unknown VM type '$d->{type}'\n"; + } - my $task = PVE::Tools::upid_decode($upid); - while (PVE::ProcFSTools::check_process_running($task->{pid})) { - sleep(1); - } + my $task = PVE::Tools::upid_decode($upid); + while (PVE::ProcFSTools::check_process_running($task->{pid})) { + sleep(1); + } - my $status = PVE::Tools::upid_read_status($upid); - if (!PVE::Tools::upid_status_is_error($status)) { - # use default delay to reduce load - my $delay = defined($d->{up}) ? int($d->{up}) : $default_delay; - if ($delay > 0) { - print STDERR "Waiting for $delay seconds (startup delay)\n" if $d->{up}; - for (my $i = 0; $i < $delay; $i++) { - sleep(1); - } - } - } else { - my $rendered_type = $d->{type} eq 'lxc' ? 'CT' : 'VM'; - print STDERR "Starting $rendered_type $vmid failed: $status\n"; - } - }; - warn $@ if $@; - } - } - return; - }; + my $status = PVE::Tools::upid_read_status($upid); + if (!PVE::Tools::upid_status_is_error($status)) { + # use default delay to reduce load + my $delay = defined($d->{up}) ? int($d->{up}) : $default_delay; + if ($delay > 0) { + print STDERR "Waiting for $delay seconds (startup delay)\n" + if $d->{up}; + for (my $i = 0; $i < $delay; $i++) { + sleep(1); + } + } + } else { + my $rendered_type = $d->{type} eq 'lxc' ? 'CT' : 'VM'; + print STDERR "Starting $rendered_type $vmid failed: $status\n"; + } + }; + warn $@ if $@; + } + } + return; + }; - return $rpcenv->fork_worker('startall', undef, $authuser, $code); - }}); + return $rpcenv->fork_worker('startall', undef, $authuser, $code); + }, +}); my $create_stop_worker = sub { my ($nodename, $type, $vmid, $timeout, $force_stop) = @_; if ($type eq 'lxc') { - return if !PVE::LXC::check_running($vmid); - print STDERR "Stopping CT $vmid (timeout = $timeout seconds)\n"; - return PVE::API2::LXC::Status->vm_shutdown( - { node => $nodename, vmid => $vmid, timeout => $timeout, forceStop => $force_stop } - ); + return if !PVE::LXC::check_running($vmid); + print STDERR "Stopping CT $vmid (timeout = $timeout seconds)\n"; + return PVE::API2::LXC::Status->vm_shutdown( + { node => $nodename, vmid => $vmid, timeout => $timeout, forceStop => $force_stop } + ); } elsif ($type eq 'qemu') { - return if !PVE::QemuServer::check_running($vmid, 1); - print STDERR "Stopping VM $vmid (timeout = $timeout seconds)\n"; - return PVE::API2::Qemu->vm_shutdown( - { node => $nodename, vmid => $vmid, timeout => $timeout, forceStop => $force_stop } - ); + return if !PVE::QemuServer::check_running($vmid, 1); + print STDERR "Stopping VM $vmid (timeout = $timeout seconds)\n"; + return PVE::API2::Qemu->vm_shutdown( + { node => $nodename, vmid => $vmid, timeout => $timeout, forceStop => $force_stop } + ); } else { - die "unknown VM type '$type'\n"; + die "unknown VM type '$type'\n"; } }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'stopall', path => 'stopall', method => 'POST', protected => 1, permissions => { - description => "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/' for " - ."each ID passed via the 'vms' parameter.", - user => 'all', + description => "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/' for " + . "each ID passed via the 'vms' parameter.", + user => 'all', }, proxyto => 'node', description => "Stop all VMs and Containers.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - vms => { - description => "Only consider Guests with these IDs.", - type => 'string', format => 'pve-vmid-list', - optional => 1, - }, - 'force-stop' => { - description => 'Force a hard-stop after the timeout.', - type => 'boolean', - default => 1, - optional => 1, - }, - 'timeout' => { - description => 'Timeout for each guest shutdown task. Depending on `force-stop`,' - .' the shutdown gets then simply aborted or a hard-stop is forced.', - type => 'integer', - optional => 1, - default => 180, - minimum => 0, - maximum => 2 * 3600, # mostly arbitrary, but we do not want to high timeouts - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + vms => { + description => "Only consider Guests with these IDs.", + type => 'string', + format => 'pve-vmid-list', + optional => 1, + }, + 'force-stop' => { + description => 'Force a hard-stop after the timeout.', + type => 'boolean', + default => 1, + optional => 1, + }, + 'timeout' => { + description => + 'Timeout for each guest shutdown task. Depending on `force-stop`,' + . ' the shutdown gets then simply aborted or a hard-stop is forced.', + type => 'integer', + optional => 1, + default => 180, + minimum => 0, + maximum => 2 * 3600, # mostly arbitrary, but we do not want to high timeouts + }, + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - if (!$rpcenv->check($authuser, "/", [ 'VM.PowerMgmt' ], 1)) { - my @vms = PVE::Tools::split_list($param->{vms}); - if (scalar(@vms) > 0) { - $rpcenv->check($authuser, "/vms/$_", [ 'VM.PowerMgmt' ]) for @vms; - } else { - raise_perm_exc("/, VM.PowerMgmt"); - } - } + if (!$rpcenv->check($authuser, "/", ['VM.PowerMgmt'], 1)) { + my @vms = PVE::Tools::split_list($param->{vms}); + if (scalar(@vms) > 0) { + $rpcenv->check($authuser, "/vms/$_", ['VM.PowerMgmt']) for @vms; + } else { + raise_perm_exc("/, VM.PowerMgmt"); + } + } - my $nodename = $param->{node}; - $nodename = PVE::INotify::nodename() if $nodename eq 'localhost'; + my $nodename = $param->{node}; + $nodename = PVE::INotify::nodename() if $nodename eq 'localhost'; - my $code = sub { + my $code = sub { - $rpcenv->{type} = 'priv'; # to start tasks in background + $rpcenv->{type} = 'priv'; # to start tasks in background - my $stopList = $get_start_stop_list->($nodename, undef, $param->{vms}); + my $stopList = $get_start_stop_list->($nodename, undef, $param->{vms}); - my $cpuinfo = PVE::ProcFSTools::read_cpuinfo(); - my $datacenterconfig = cfs_read_file('datacenter.cfg'); - # if not set by user spawn max cpu count number of workers - my $maxWorkers = $datacenterconfig->{max_workers} || $cpuinfo->{cpus}; + my $cpuinfo = PVE::ProcFSTools::read_cpuinfo(); + my $datacenterconfig = cfs_read_file('datacenter.cfg'); + # if not set by user spawn max cpu count number of workers + my $maxWorkers = $datacenterconfig->{max_workers} || $cpuinfo->{cpus}; - for my $order (sort {$b <=> $a} keys %$stopList) { - my $vmlist = $stopList->{$order}; - my $workers = {}; + for my $order (sort { $b <=> $a } keys %$stopList) { + my $vmlist = $stopList->{$order}; + my $workers = {}; - my $finish_worker = sub { - my $pid = shift; - my $worker = delete $workers->{$pid} || return; + my $finish_worker = sub { + my $pid = shift; + my $worker = delete $workers->{$pid} || return; - syslog('info', "end task $worker->{upid}"); - }; + syslog('info', "end task $worker->{upid}"); + }; - for my $vmid (sort {$b <=> $a} keys %$vmlist) { - my $d = $vmlist->{$vmid}; - my $timeout = int($d->{down} // $param->{timeout} // 180); - my $upid = eval { - $create_stop_worker->( - $nodename, $d->{type}, $vmid, $timeout, $param->{'force-stop'} // 1) - }; - warn $@ if $@; - next if !$upid; + for my $vmid (sort { $b <=> $a } keys %$vmlist) { + my $d = $vmlist->{$vmid}; + my $timeout = int($d->{down} // $param->{timeout} // 180); + my $upid = eval { + $create_stop_worker->( + $nodename, $d->{type}, $vmid, $timeout, $param->{'force-stop'} // 1, + ); + }; + warn $@ if $@; + next if !$upid; - my $task = PVE::Tools::upid_decode($upid, 1); - next if !$task; + my $task = PVE::Tools::upid_decode($upid, 1); + next if !$task; - my $pid = $task->{pid}; + my $pid = $task->{pid}; - $workers->{$pid} = { type => $d->{type}, upid => $upid, vmid => $vmid }; - while (scalar(keys %$workers) >= $maxWorkers) { - foreach my $p (keys %$workers) { - if (!PVE::ProcFSTools::check_process_running($p)) { - $finish_worker->($p); - } - } - sleep(1); - } - } - while (scalar(keys %$workers)) { - for my $p (keys %$workers) { - if (!PVE::ProcFSTools::check_process_running($p)) { - $finish_worker->($p); - } - } - sleep(1); - } - } + $workers->{$pid} = { type => $d->{type}, upid => $upid, vmid => $vmid }; + while (scalar(keys %$workers) >= $maxWorkers) { + foreach my $p (keys %$workers) { + if (!PVE::ProcFSTools::check_process_running($p)) { + $finish_worker->($p); + } + } + sleep(1); + } + } + while (scalar(keys %$workers)) { + for my $p (keys %$workers) { + if (!PVE::ProcFSTools::check_process_running($p)) { + $finish_worker->($p); + } + } + sleep(1); + } + } - syslog('info', "all VMs and CTs stopped"); + syslog('info', "all VMs and CTs stopped"); - return; - }; + return; + }; - return $rpcenv->fork_worker('stopall', undef, $authuser, $code); - }}); + return $rpcenv->fork_worker('stopall', undef, $authuser, $code); + }, +}); my $create_suspend_worker = sub { my ($nodename, $vmid) = @_; if (!PVE::QemuServer::check_running($vmid, 1)) { - print "VM $vmid not running, skipping suspension\n"; - return; + print "VM $vmid not running, skipping suspension\n"; + return; } print STDERR "Suspending VM $vmid\n"; - return PVE::API2::Qemu->vm_suspend( - { node => $nodename, vmid => $vmid, todisk => 1 } - ); + return PVE::API2::Qemu->vm_suspend({ node => $nodename, vmid => $vmid, todisk => 1 }); }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'suspendall', path => 'suspendall', method => 'POST', protected => 1, permissions => { - description => "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/' for each" - ." ID passed via the 'vms' parameter. Additionally, you need 'VM.Config.Disk' on the" - ." '/vms/{vmid}' path and 'Datastore.AllocateSpace' for the configured state-storage(s)", - user => 'all', + description => + "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/' for each" + . " ID passed via the 'vms' parameter. Additionally, you need 'VM.Config.Disk' on the" + . " '/vms/{vmid}' path and 'Datastore.AllocateSpace' for the configured state-storage(s)", + user => 'all', }, proxyto => 'node', description => "Suspend all VMs.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - vms => { - description => "Only consider Guests with these IDs.", - type => 'string', format => 'pve-vmid-list', - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + vms => { + description => "Only consider Guests with these IDs.", + type => 'string', + format => 'pve-vmid-list', + optional => 1, + }, + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - # we cannot really check access to the state-storage here, that's happening per worker. - if (!$rpcenv->check($authuser, "/", [ 'VM.PowerMgmt', 'VM.Config.Disk' ], 1)) { - my @vms = PVE::Tools::split_list($param->{vms}); - if (scalar(@vms) > 0) { - $rpcenv->check($authuser, "/vms/$_", [ 'VM.PowerMgmt' ]) for @vms; - } else { - raise_perm_exc("/, VM.PowerMgmt && VM.Config.Disk"); - } - } + # we cannot really check access to the state-storage here, that's happening per worker. + if (!$rpcenv->check($authuser, "/", ['VM.PowerMgmt', 'VM.Config.Disk'], 1)) { + my @vms = PVE::Tools::split_list($param->{vms}); + if (scalar(@vms) > 0) { + $rpcenv->check($authuser, "/vms/$_", ['VM.PowerMgmt']) for @vms; + } else { + raise_perm_exc("/, VM.PowerMgmt && VM.Config.Disk"); + } + } - my $nodename = $param->{node}; - $nodename = PVE::INotify::nodename() if $nodename eq 'localhost'; + my $nodename = $param->{node}; + $nodename = PVE::INotify::nodename() if $nodename eq 'localhost'; - my $code = sub { + my $code = sub { - $rpcenv->{type} = 'priv'; # to start tasks in background + $rpcenv->{type} = 'priv'; # to start tasks in background - my $toSuspendList = $get_start_stop_list->($nodename, undef, $param->{vms}); + my $toSuspendList = $get_start_stop_list->($nodename, undef, $param->{vms}); - my $cpuinfo = PVE::ProcFSTools::read_cpuinfo(); - my $datacenterconfig = cfs_read_file('datacenter.cfg'); - # if not set by user spawn max cpu count number of workers - my $maxWorkers = $datacenterconfig->{max_workers} || $cpuinfo->{cpus}; + my $cpuinfo = PVE::ProcFSTools::read_cpuinfo(); + my $datacenterconfig = cfs_read_file('datacenter.cfg'); + # if not set by user spawn max cpu count number of workers + my $maxWorkers = $datacenterconfig->{max_workers} || $cpuinfo->{cpus}; - for my $order (sort {$b <=> $a} keys %$toSuspendList) { - my $vmlist = $toSuspendList->{$order}; - my $workers = {}; + for my $order (sort { $b <=> $a } keys %$toSuspendList) { + my $vmlist = $toSuspendList->{$order}; + my $workers = {}; - my $finish_worker = sub { - my $pid = shift; - my $worker = delete $workers->{$pid} || return; + my $finish_worker = sub { + my $pid = shift; + my $worker = delete $workers->{$pid} || return; - syslog('info', "end task $worker->{upid}"); - }; + syslog('info', "end task $worker->{upid}"); + }; - for my $vmid (sort {$b <=> $a} keys %$vmlist) { - my $d = $vmlist->{$vmid}; - if ($d->{type} ne 'qemu') { - log_warn("skipping $vmid, only VMs can be suspended"); - next; - } - my $upid = eval { - $create_suspend_worker->($nodename, $vmid) - }; - warn $@ if $@; - next if !$upid; + for my $vmid (sort { $b <=> $a } keys %$vmlist) { + my $d = $vmlist->{$vmid}; + if ($d->{type} ne 'qemu') { + log_warn("skipping $vmid, only VMs can be suspended"); + next; + } + my $upid = eval { $create_suspend_worker->($nodename, $vmid) }; + warn $@ if $@; + next if !$upid; - my $task = PVE::Tools::upid_decode($upid, 1); - next if !$task; + my $task = PVE::Tools::upid_decode($upid, 1); + next if !$task; - my $pid = $task->{pid}; - $workers->{$pid} = { type => $d->{type}, upid => $upid, vmid => $vmid }; + my $pid = $task->{pid}; + $workers->{$pid} = { type => $d->{type}, upid => $upid, vmid => $vmid }; - while (scalar(keys %$workers) >= $maxWorkers) { - for my $p (keys %$workers) { - if (!PVE::ProcFSTools::check_process_running($p)) { - $finish_worker->($p); - } - } - sleep(1); - } - } - while (scalar(keys %$workers)) { - for my $p (keys %$workers) { - if (!PVE::ProcFSTools::check_process_running($p)) { - $finish_worker->($p); - } - } - sleep(1); - } - } + while (scalar(keys %$workers) >= $maxWorkers) { + for my $p (keys %$workers) { + if (!PVE::ProcFSTools::check_process_running($p)) { + $finish_worker->($p); + } + } + sleep(1); + } + } + while (scalar(keys %$workers)) { + for my $p (keys %$workers) { + if (!PVE::ProcFSTools::check_process_running($p)) { + $finish_worker->($p); + } + } + sleep(1); + } + } - syslog('info', "all VMs suspended"); + syslog('info', "all VMs suspended"); - return; - }; - - return $rpcenv->fork_worker('suspendall', undef, $authuser, $code); - }}); + return; + }; + return $rpcenv->fork_worker('suspendall', undef, $authuser, $code); + }, +}); my $create_migrate_worker = sub { my ($nodename, $type, $vmid, $target, $with_local_disks) = @_; my $upid; if ($type eq 'lxc') { - my $online = PVE::LXC::check_running($vmid) ? 1 : 0; - print STDERR "Migrating CT $vmid\n"; - $upid = PVE::API2::LXC->migrate_vm( - { node => $nodename, vmid => $vmid, target => $target, restart => $online }); + my $online = PVE::LXC::check_running($vmid) ? 1 : 0; + print STDERR "Migrating CT $vmid\n"; + $upid = PVE::API2::LXC->migrate_vm( + { node => $nodename, vmid => $vmid, target => $target, restart => $online }); } elsif ($type eq 'qemu') { - print STDERR "Check VM $vmid: "; - *STDERR->flush(); - my $online = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0; - my $preconditions = PVE::API2::Qemu->migrate_vm_precondition( - {node => $nodename, vmid => $vmid, target => $target}); - my $invalidConditions = ''; - if ($online && !$with_local_disks && scalar @{$preconditions->{local_disks}}) { - $invalidConditions .= "\n Has local disks: "; - $invalidConditions .= join(', ', map { $_->{volid} } @{$preconditions->{local_disks}}); - } + print STDERR "Check VM $vmid: "; + *STDERR->flush(); + my $online = PVE::QemuServer::check_running($vmid, 1) ? 1 : 0; + my $preconditions = PVE::API2::Qemu->migrate_vm_precondition( + { node => $nodename, vmid => $vmid, target => $target }); + my $invalidConditions = ''; + if ($online && !$with_local_disks && scalar @{ $preconditions->{local_disks} }) { + $invalidConditions .= "\n Has local disks: "; + $invalidConditions .= + join(', ', map { $_->{volid} } @{ $preconditions->{local_disks} }); + } - if (@{$preconditions->{local_resources}}) { - $invalidConditions .= "\n Has local resources: "; - $invalidConditions .= join(', ', @{$preconditions->{local_resources}}); - } + if (@{ $preconditions->{local_resources} }) { + $invalidConditions .= "\n Has local resources: "; + $invalidConditions .= join(', ', @{ $preconditions->{local_resources} }); + } - if (my $not_allowed_nodes = $preconditions->{not_allowed_nodes}) { - if (my $unavail_storages = $not_allowed_nodes->{$target}->{unavailable_storages}) { - $invalidConditions .= "\n Has unavailable storages: "; - $invalidConditions .= join(', ', $unavail_storages->@*); - } + if (my $not_allowed_nodes = $preconditions->{not_allowed_nodes}) { + if (my $unavail_storages = $not_allowed_nodes->{$target}->{unavailable_storages}) { + $invalidConditions .= "\n Has unavailable storages: "; + $invalidConditions .= join(', ', $unavail_storages->@*); + } - if (my $unavail_resources = $not_allowed_nodes->{$target}->{'unavailable-resources'}) { - $invalidConditions .= "\n Has unavailable resources: "; - $invalidConditions .= join(', ', $unavail_resources->@*); - } - } + if ( + my $unavail_resources = $not_allowed_nodes->{$target}->{'unavailable-resources'} + ) { + $invalidConditions .= "\n Has unavailable resources: "; + $invalidConditions .= join(', ', $unavail_resources->@*); + } + } - if ($invalidConditions && $invalidConditions ne '') { - print STDERR "skip VM $vmid - precondition check failed:"; - die "$invalidConditions\n"; - } - print STDERR "precondition check passed\n"; - print STDERR "Migrating VM $vmid\n"; + if ($invalidConditions && $invalidConditions ne '') { + print STDERR "skip VM $vmid - precondition check failed:"; + die "$invalidConditions\n"; + } + print STDERR "precondition check passed\n"; + print STDERR "Migrating VM $vmid\n"; - my $params = { - node => $nodename, - vmid => $vmid, - target => $target, - online => $online, - }; - $params->{'with-local-disks'} = $with_local_disks if defined($with_local_disks); + my $params = { + node => $nodename, + vmid => $vmid, + target => $target, + online => $online, + }; + $params->{'with-local-disks'} = $with_local_disks if defined($with_local_disks); - $upid = PVE::API2::Qemu->migrate_vm($params); + $upid = PVE::API2::Qemu->migrate_vm($params); } else { - die "unknown VM type '$type'\n"; + die "unknown VM type '$type'\n"; } my $task = PVE::Tools::upid_decode($upid); @@ -2373,196 +2461,211 @@ my $create_migrate_worker = sub { return $task->{pid}; }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'migrateall', path => 'migrateall', method => 'POST', proxyto => 'node', protected => 1, permissions => { - description => "The 'VM.Migrate' permission is required on '/' or on '/vms/' for each " - ."ID passed via the 'vms' parameter.", - user => 'all', + description => + "The 'VM.Migrate' permission is required on '/' or on '/vms/' for each " + . "ID passed via the 'vms' parameter.", + user => 'all', }, description => "Migrate all VMs and Containers.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - target => get_standard_option('pve-node', { description => "Target node." }), - maxworkers => { - description => "Maximal number of parallel migration job. If not set, uses" - ."'max_workers' from datacenter.cfg. One of both must be set!", - optional => 1, - type => 'integer', - minimum => 1 - }, - vms => { - description => "Only consider Guests with these IDs.", - type => 'string', format => 'pve-vmid-list', - optional => 1, - }, - "with-local-disks" => { - type => 'boolean', - description => "Enable live storage migration for local disk", - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + target => get_standard_option('pve-node', { description => "Target node." }), + maxworkers => { + description => "Maximal number of parallel migration job. If not set, uses" + . "'max_workers' from datacenter.cfg. One of both must be set!", + optional => 1, + type => 'integer', + minimum => 1, + }, + vms => { + description => "Only consider Guests with these IDs.", + type => 'string', + format => 'pve-vmid-list', + optional => 1, + }, + "with-local-disks" => { + type => 'boolean', + description => "Enable live storage migration for local disk", + optional => 1, + }, + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - if (!$rpcenv->check($authuser, "/", [ 'VM.Migrate' ], 1)) { - my @vms = PVE::Tools::split_list($param->{vms}); - if (scalar(@vms) > 0) { - $rpcenv->check($authuser, "/vms/$_", [ 'VM.Migrate' ]) for @vms; - } else { - raise_perm_exc("/, VM.Migrate"); - } - } + if (!$rpcenv->check($authuser, "/", ['VM.Migrate'], 1)) { + my @vms = PVE::Tools::split_list($param->{vms}); + if (scalar(@vms) > 0) { + $rpcenv->check($authuser, "/vms/$_", ['VM.Migrate']) for @vms; + } else { + raise_perm_exc("/, VM.Migrate"); + } + } - my $nodename = $param->{node}; - $nodename = PVE::INotify::nodename() if $nodename eq 'localhost'; + my $nodename = $param->{node}; + $nodename = PVE::INotify::nodename() if $nodename eq 'localhost'; - my $target = $param->{target}; - my $with_local_disks = $param->{'with-local-disks'}; - raise_param_exc({ target => "target is local node."}) if $target eq $nodename; + my $target = $param->{target}; + my $with_local_disks = $param->{'with-local-disks'}; + raise_param_exc({ target => "target is local node." }) if $target eq $nodename; - PVE::Cluster::check_cfs_quorum(); + PVE::Cluster::check_cfs_quorum(); - PVE::Cluster::check_node_exists($target); + PVE::Cluster::check_node_exists($target); - my $datacenterconfig = cfs_read_file('datacenter.cfg'); - # prefer parameter over datacenter cfg settings - my $maxWorkers = $param->{maxworkers} || $datacenterconfig->{max_workers} || - die "either 'maxworkers' parameter or max_workers in datacenter.cfg must be set!\n"; + my $datacenterconfig = cfs_read_file('datacenter.cfg'); + # prefer parameter over datacenter cfg settings + my $maxWorkers = + $param->{maxworkers} + || $datacenterconfig->{max_workers} + || die "either 'maxworkers' parameter or max_workers in datacenter.cfg must be set!\n"; - my $code = sub { - $rpcenv->{type} = 'priv'; # to start tasks in background + my $code = sub { + $rpcenv->{type} = 'priv'; # to start tasks in background - my $vmlist = &$get_filtered_vmlist($nodename, $param->{vms}, 1, 1); - if (!scalar(keys %$vmlist)) { - warn "no virtual guests matched, nothing to do..\n"; - return; - } + my $vmlist = &$get_filtered_vmlist($nodename, $param->{vms}, 1, 1); + if (!scalar(keys %$vmlist)) { + warn "no virtual guests matched, nothing to do..\n"; + return; + } - my $workers = {}; - my $workers_started = 0; - foreach my $vmid (sort keys %$vmlist) { - my $d = $vmlist->{$vmid}; - my $pid; - eval { $pid = &$create_migrate_worker($nodename, $d->{type}, $vmid, $target, $with_local_disks); }; - warn $@ if $@; - next if !$pid; + my $workers = {}; + my $workers_started = 0; + foreach my $vmid (sort keys %$vmlist) { + my $d = $vmlist->{$vmid}; + my $pid; + eval { + $pid = &$create_migrate_worker( + $nodename, $d->{type}, $vmid, $target, $with_local_disks, + ); + }; + warn $@ if $@; + next if !$pid; - $workers_started++; - $workers->{$pid} = 1; - while (scalar(keys %$workers) >= $maxWorkers) { - foreach my $p (keys %$workers) { - if (!PVE::ProcFSTools::check_process_running($p)) { - delete $workers->{$p}; - } - } - sleep(1); - } - } - while (scalar(keys %$workers)) { - foreach my $p (keys %$workers) { - # FIXME: what about PID re-use ?!?! - if (!PVE::ProcFSTools::check_process_running($p)) { - delete $workers->{$p}; - } - } - sleep(1); - } - if ($workers_started <= 0) { - die "no migrations worker started...\n"; - } - print STDERR "All jobs finished, used $workers_started workers in total.\n"; - return; - }; + $workers_started++; + $workers->{$pid} = 1; + while (scalar(keys %$workers) >= $maxWorkers) { + foreach my $p (keys %$workers) { + if (!PVE::ProcFSTools::check_process_running($p)) { + delete $workers->{$p}; + } + } + sleep(1); + } + } + while (scalar(keys %$workers)) { + foreach my $p (keys %$workers) { + # FIXME: what about PID re-use ?!?! + if (!PVE::ProcFSTools::check_process_running($p)) { + delete $workers->{$p}; + } + } + sleep(1); + } + if ($workers_started <= 0) { + die "no migrations worker started...\n"; + } + print STDERR "All jobs finished, used $workers_started workers in total.\n"; + return; + }; - return $rpcenv->fork_worker('migrateall', undef, $authuser, $code); + return $rpcenv->fork_worker('migrateall', undef, $authuser, $code); - }}); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get_etc_hosts', path => 'hosts', method => 'GET', proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/', [ 'Sys.Audit' ]], + check => ['perm', '/', ['Sys.Audit']], }, description => "Get the content of /etc/hosts.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'object', - properties => { - digest => get_standard_option('pve-config-digest'), - data => { - type => 'string', - description => 'The content of /etc/hosts.' - }, - }, + type => 'object', + properties => { + digest => get_standard_option('pve-config-digest'), + data => { + type => 'string', + description => 'The content of /etc/hosts.', + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - return PVE::INotify::read_file('etchosts'); + return PVE::INotify::read_file('etchosts'); - }}); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'write_etc_hosts', path => 'hosts', method => 'POST', proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, description => "Write /etc/hosts.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - digest => get_standard_option('pve-config-digest'), - data => { - type => 'string', - description => 'The target content of /etc/hosts.' - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + digest => get_standard_option('pve-config-digest'), + data => { + type => 'string', + description => 'The target content of /etc/hosts.', + }, + }, }, returns => { - type => 'null', + type => 'null', }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Tools::lock_file('/var/lock/pve-etchosts.lck', undef, sub { - if ($param->{digest}) { - my $hosts = PVE::INotify::read_file('etchosts'); - PVE::Tools::assert_if_modified($hosts->{digest}, $param->{digest}); - } - PVE::INotify::write_file('etchosts', $param->{data}); - }); - die $@ if $@; + PVE::Tools::lock_file( + '/var/lock/pve-etchosts.lck', + undef, + sub { + if ($param->{digest}) { + my $hosts = PVE::INotify::read_file('etchosts'); + PVE::Tools::assert_if_modified($hosts->{digest}, $param->{digest}); + } + PVE::INotify::write_file('etchosts', $param->{data}); + }, + ); + die $@ if $@; - return; - }}); + return; + }, +}); # bash completion helper @@ -2571,9 +2674,9 @@ sub complete_templet_repo { my $repo = PVE::APLInfo::load_data(); my $res = []; - foreach my $templ (keys %{$repo->{all}}) { - next if $templ !~ m/^$cvalue/; - push @$res, $templ; + foreach my $templ (keys %{ $repo->{all} }) { + next if $templ !~ m/^$cvalue/; + push @$res, $templ; } return $res; @@ -2593,99 +2696,100 @@ use PVE::JSONSchema qw(get_standard_option); use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ subclass => "PVE::API2::Nodes::Nodeinfo", path => '{node}', }); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', permissions => { user => 'all' }, description => "Cluster node index.", parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - node => get_standard_option('pve-node'), - status => { - description => "Node status.", - type => 'string', - enum => ['unknown', 'online', 'offline'], - }, - cpu => { - description => "CPU utilization.", - type => 'number', - optional => 1, - renderer => 'fraction_as_percentage', - }, - maxcpu => { - description => "Number of available CPUs.", - type => 'integer', - optional => 1, - }, - mem => { - description => "Used memory in bytes.", - type => 'integer', - optional => 1, - renderer => 'bytes', - }, - maxmem => { - description => "Number of available memory in bytes.", - type => 'integer', - optional => 1, - renderer => 'bytes', - }, - level => { - description => "Support level.", - type => 'string', - optional => 1, - }, - uptime => { - description => "Node uptime in seconds.", - type => 'integer', - optional => 1, - renderer => 'duration', - }, - ssl_fingerprint => { - description => "The SSL fingerprint for the node certificate.", - type => 'string', - optional => 1, - }, - }, - }, - links => [ { rel => 'child', href => "{node}" } ], + type => 'array', + items => { + type => "object", + properties => { + node => get_standard_option('pve-node'), + status => { + description => "Node status.", + type => 'string', + enum => ['unknown', 'online', 'offline'], + }, + cpu => { + description => "CPU utilization.", + type => 'number', + optional => 1, + renderer => 'fraction_as_percentage', + }, + maxcpu => { + description => "Number of available CPUs.", + type => 'integer', + optional => 1, + }, + mem => { + description => "Used memory in bytes.", + type => 'integer', + optional => 1, + renderer => 'bytes', + }, + maxmem => { + description => "Number of available memory in bytes.", + type => 'integer', + optional => 1, + renderer => 'bytes', + }, + level => { + description => "Support level.", + type => 'string', + optional => 1, + }, + uptime => { + description => "Node uptime in seconds.", + type => 'integer', + optional => 1, + renderer => 'duration', + }, + ssl_fingerprint => { + description => "The SSL fingerprint for the node certificate.", + type => 'string', + optional => 1, + }, + }, + }, + links => [{ rel => 'child', href => "{node}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $clinfo = PVE::Cluster::get_clinfo(); - my $res = []; + my $clinfo = PVE::Cluster::get_clinfo(); + my $res = []; - my $nodelist = PVE::Cluster::get_nodelist(); - my $members = PVE::Cluster::get_members(); - my $rrd = PVE::Cluster::rrd_dump(); + my $nodelist = PVE::Cluster::get_nodelist(); + my $members = PVE::Cluster::get_members(); + my $rrd = PVE::Cluster::rrd_dump(); - foreach my $node (@$nodelist) { - my $can_audit = $rpcenv->check($authuser, "/nodes/$node", [ 'Sys.Audit' ], 1); - my $entry = PVE::API2Tools::extract_node_stats($node, $members, $rrd, !$can_audit); + foreach my $node (@$nodelist) { + my $can_audit = $rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'], 1); + my $entry = PVE::API2Tools::extract_node_stats($node, $members, $rrd, !$can_audit); - $entry->{ssl_fingerprint} = eval { PVE::Cluster::get_node_fingerprint($node) }; - warn "$@" if $@; + $entry->{ssl_fingerprint} = eval { PVE::Cluster::get_node_fingerprint($node) }; + warn "$@" if $@; - push @$res, $entry; - } + push @$res, $entry; + } - return $res; - }}); + return $res; + }, +}); 1; diff --git a/PVE/API2/Pool.pm b/PVE/API2/Pool.pm index 54e74455..138b50a6 100644 --- a/PVE/API2/Pool.pm +++ b/PVE/API2/Pool.pm @@ -16,487 +16,521 @@ use PVE::RESTHandler; use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', description => "List pools or get pool configuration.", permissions => { - description => "List all pools where you have Pool.Audit permissions on /pool/, or the pool specific with {poolid}", - user => 'all', + description => + "List all pools where you have Pool.Audit permissions on /pool/, or the pool specific with {poolid}", + user => 'all', }, parameters => { - additionalProperties => 0, - properties => { - poolid => { - type => 'string', - format => 'pve-poolid', - optional => 1, - }, - type => { - type => 'string', - enum => [ 'qemu', 'lxc', 'storage' ], - optional => 1, - requires => 'poolid', - }, - }, + additionalProperties => 0, + properties => { + poolid => { + type => 'string', + format => 'pve-poolid', + optional => 1, + }, + type => { + type => 'string', + enum => ['qemu', 'lxc', 'storage'], + optional => 1, + requires => 'poolid', + }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - poolid => { type => 'string' }, - comment => { - type => 'string', - optional => 1, - }, - members => { - type => 'array', - optional => 1, - items => { - type => "object", - additionalProperties => 1, - properties => { - type => { - type => 'string', - enum => [ 'qemu', 'lxc', 'openvz', 'storage' ], - }, - id => { - type => 'string', - }, - node => { - type => 'string', - }, - vmid => { - type => 'integer', - optional => 1, - }, - storage => { - type => 'string', - optional => 1, - }, - }, - }, - }, - }, - }, - links => [ { rel => 'child', href => "{poolid}" } ], + type => 'array', + items => { + type => "object", + properties => { + poolid => { type => 'string' }, + comment => { + type => 'string', + optional => 1, + }, + members => { + type => 'array', + optional => 1, + items => { + type => "object", + additionalProperties => 1, + properties => { + type => { + type => 'string', + enum => ['qemu', 'lxc', 'openvz', 'storage'], + }, + id => { + type => 'string', + }, + node => { + type => 'string', + }, + vmid => { + type => 'integer', + optional => 1, + }, + storage => { + type => 'string', + optional => 1, + }, + }, + }, + }, + }, + }, + links => [{ rel => 'child', href => "{poolid}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $usercfg = $rpcenv->{user_cfg}; + my $usercfg = $rpcenv->{user_cfg}; - my $res = []; - if (my $poolid = $param->{poolid}) { - $rpcenv->check($authuser, "/pool/$poolid", [ 'Pool.Audit' ], 1); + my $res = []; + if (my $poolid = $param->{poolid}) { + $rpcenv->check($authuser, "/pool/$poolid", ['Pool.Audit'], 1); - my $vmlist = PVE::Cluster::get_vmlist() || {}; - my $idlist = $vmlist->{ids} || {}; + my $vmlist = PVE::Cluster::get_vmlist() || {}; + my $idlist = $vmlist->{ids} || {}; - my $rrd = PVE::Cluster::rrd_dump(); + my $rrd = PVE::Cluster::rrd_dump(); - my $pool_config = $usercfg->{pools}->{$poolid}; + my $pool_config = $usercfg->{pools}->{$poolid}; - die "pool '$poolid' does not exist\n" if !$pool_config; + die "pool '$poolid' does not exist\n" if !$pool_config; - my $members = []; - for my $vmid (sort keys %{$pool_config->{vms}}) { - my $vmdata = $idlist->{$vmid}; - next if !$vmdata || defined($param->{type}) && $param->{type} ne $vmdata->{type}; - my $entry = PVE::API2Tools::extract_vm_stats($vmid, $vmdata, $rrd); - push @$members, $entry; - } + my $members = []; + for my $vmid (sort keys %{ $pool_config->{vms} }) { + my $vmdata = $idlist->{$vmid}; + next + if !$vmdata || defined($param->{type}) && $param->{type} ne $vmdata->{type}; + my $entry = PVE::API2Tools::extract_vm_stats($vmid, $vmdata, $rrd); + push @$members, $entry; + } - my $nodename = PVE::INotify::nodename(); - my $cfg = PVE::Storage::config(); - if (!defined($param->{type}) || $param->{type} eq 'storage') { - for my $storeid (sort keys %{$pool_config->{storage}}) { - my $scfg = PVE::Storage::storage_config ($cfg, $storeid, 1); - next if !$scfg; + my $nodename = PVE::INotify::nodename(); + my $cfg = PVE::Storage::config(); + if (!defined($param->{type}) || $param->{type} eq 'storage') { + for my $storeid (sort keys %{ $pool_config->{storage} }) { + my $scfg = PVE::Storage::storage_config($cfg, $storeid, 1); + next if !$scfg; - my $storage_node = $nodename; # prefer local node - if ($scfg->{nodes} && !$scfg->{nodes}->{$storage_node}) { - for my $node (sort keys(%{$scfg->{nodes}})) { - $storage_node = $node; - last; - } - } + my $storage_node = $nodename; # prefer local node + if ($scfg->{nodes} && !$scfg->{nodes}->{$storage_node}) { + for my $node (sort keys(%{ $scfg->{nodes} })) { + $storage_node = $node; + last; + } + } - my $entry = PVE::API2Tools::extract_storage_stats($storeid, $scfg, $storage_node, $rrd); - push @$members, $entry; - } - } + my $entry = + PVE::API2Tools::extract_storage_stats($storeid, $scfg, $storage_node, $rrd); + push @$members, $entry; + } + } - my $pool_info = { - members => $members, - }; - $pool_info->{comment} = $pool_config->{comment} if defined($pool_config->{comment}); - $pool_info->{poolid} = $poolid; + my $pool_info = { + members => $members, + }; + $pool_info->{comment} = $pool_config->{comment} if defined($pool_config->{comment}); + $pool_info->{poolid} = $poolid; - push @$res, $pool_info; - } else { - for my $pool (sort keys %{$usercfg->{pools}}) { - next if !$rpcenv->check($authuser, "/pool/$pool", [ 'Pool.Audit' ], 1); + push @$res, $pool_info; + } else { + for my $pool (sort keys %{ $usercfg->{pools} }) { + next if !$rpcenv->check($authuser, "/pool/$pool", ['Pool.Audit'], 1); - my $entry = { poolid => $pool }; - my $pool_config = $usercfg->{pools}->{$pool}; - $entry->{comment} = $pool_config->{comment} if defined($pool_config->{comment}); - push @$res, $entry; - } - } + my $entry = { poolid => $pool }; + my $pool_config = $usercfg->{pools}->{$pool}; + $entry->{comment} = $pool_config->{comment} if defined($pool_config->{comment}); + push @$res, $entry; + } + } - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create_pool', protected => 1, path => '', method => 'POST', permissions => { - check => ['perm', '/pool/{poolid}', ['Pool.Allocate']], + check => ['perm', '/pool/{poolid}', ['Pool.Allocate']], }, description => "Create new pool.", parameters => { - additionalProperties => 0, - properties => { - poolid => { - type => 'string', - format => 'pve-poolid', - }, - comment => { - type => 'string', - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + poolid => { + type => 'string', + format => 'pve-poolid', + }, + comment => { + type => 'string', + optional => 1, + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::AccessControl::lock_user_config(sub { - my $usercfg = cfs_read_file("user.cfg"); - my $pool = $param->{poolid}; + PVE::AccessControl::lock_user_config( + sub { + my $usercfg = cfs_read_file("user.cfg"); + my $pool = $param->{poolid}; - die "pool '$pool' already exists\n" if $usercfg->{pools}->{$pool}; - if ($pool =~ m!^(.*)/[^/]+$!) { - my $parent = $1; - die "parent '$parent' of pool '$pool' does not exist\n" - if !defined($usercfg->{pools}->{$parent}); - } + die "pool '$pool' already exists\n" if $usercfg->{pools}->{$pool}; + if ($pool =~ m!^(.*)/[^/]+$!) { + my $parent = $1; + die "parent '$parent' of pool '$pool' does not exist\n" + if !defined($usercfg->{pools}->{$parent}); + } - $usercfg->{pools}->{$pool} = { - vms => {}, - storage => {}, - }; + $usercfg->{pools}->{$pool} = { + vms => {}, + storage => {}, + }; - $usercfg->{pools}->{$pool}->{comment} = $param->{comment} if $param->{comment}; + $usercfg->{pools}->{$pool}->{comment} = $param->{comment} if $param->{comment}; - cfs_write_file("user.cfg", $usercfg); - }, "create pool failed"); + cfs_write_file("user.cfg", $usercfg); + }, + "create pool failed", + ); - return; - }}); + return; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update_pool_deprecated', protected => 1, path => '{poolid}', method => 'PUT', permissions => { - description => "You also need the right to modify permissions on any object you add/delete.", - check => ['perm', '/pool/{poolid}', ['Pool.Allocate']], + description => + "You also need the right to modify permissions on any object you add/delete.", + check => ['perm', '/pool/{poolid}', ['Pool.Allocate']], }, - description => "Update pool data (deprecated, no support for nested pools - use 'PUT /pools/?poolid={poolid}' instead).", + description => + "Update pool data (deprecated, no support for nested pools - use 'PUT /pools/?poolid={poolid}' instead).", parameters => { - additionalProperties => 0, - properties => { - poolid => { type => 'string', format => 'pve-poolid' }, - comment => { type => 'string', optional => 1 }, - vms => { - description => 'List of guest VMIDs to add or remove from this pool.', - type => 'string', format => 'pve-vmid-list', - optional => 1, - }, - storage => { - description => 'List of storage IDs to add or remove from this pool.', - type => 'string', format => 'pve-storage-id-list', - optional => 1, - }, - 'allow-move' => { - description => 'Allow adding a guest even if already in another pool.' - .' The guest will be removed from its current pool and added to this one.', - type => 'boolean', - optional => 1, - default => 0, - }, - delete => { - description => 'Remove the passed VMIDs and/or storage IDs instead of adding them.', - type => 'boolean', - optional => 1, - default => 0, - }, - }, + additionalProperties => 0, + properties => { + poolid => { type => 'string', format => 'pve-poolid' }, + comment => { type => 'string', optional => 1 }, + vms => { + description => 'List of guest VMIDs to add or remove from this pool.', + type => 'string', + format => 'pve-vmid-list', + optional => 1, + }, + storage => { + description => 'List of storage IDs to add or remove from this pool.', + type => 'string', + format => 'pve-storage-id-list', + optional => 1, + }, + 'allow-move' => { + description => 'Allow adding a guest even if already in another pool.' + . ' The guest will be removed from its current pool and added to this one.', + type => 'boolean', + optional => 1, + default => 0, + }, + delete => { + description => + 'Remove the passed VMIDs and/or storage IDs instead of adding them.', + type => 'boolean', + optional => 1, + default => 0, + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - return __PACKAGE__->update_pool($param); - }}); + return __PACKAGE__->update_pool($param); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update_pool', protected => 1, path => '', method => 'PUT', permissions => { - description => "You also need the right to modify permissions on any object you add/delete.", - check => ['perm', '/pool/{poolid}', ['Pool.Allocate']], + description => + "You also need the right to modify permissions on any object you add/delete.", + check => ['perm', '/pool/{poolid}', ['Pool.Allocate']], }, description => "Update pool.", parameters => { - additionalProperties => 0, - properties => { - poolid => { type => 'string', format => 'pve-poolid' }, - comment => { type => 'string', optional => 1 }, - vms => { - description => 'List of guest VMIDs to add or remove from this pool.', - type => 'string', format => 'pve-vmid-list', - optional => 1, - }, - storage => { - description => 'List of storage IDs to add or remove from this pool.', - type => 'string', format => 'pve-storage-id-list', - optional => 1, - }, - 'allow-move' => { - description => 'Allow adding a guest even if already in another pool.' - .' The guest will be removed from its current pool and added to this one.', - type => 'boolean', - optional => 1, - default => 0, - }, - delete => { - description => 'Remove the passed VMIDs and/or storage IDs instead of adding them.', - type => 'boolean', - optional => 1, - default => 0, - }, - }, + additionalProperties => 0, + properties => { + poolid => { type => 'string', format => 'pve-poolid' }, + comment => { type => 'string', optional => 1 }, + vms => { + description => 'List of guest VMIDs to add or remove from this pool.', + type => 'string', + format => 'pve-vmid-list', + optional => 1, + }, + storage => { + description => 'List of storage IDs to add or remove from this pool.', + type => 'string', + format => 'pve-storage-id-list', + optional => 1, + }, + 'allow-move' => { + description => 'Allow adding a guest even if already in another pool.' + . ' The guest will be removed from its current pool and added to this one.', + type => 'boolean', + optional => 1, + default => 0, + }, + delete => { + description => + 'Remove the passed VMIDs and/or storage IDs instead of adding them.', + type => 'boolean', + optional => 1, + default => 0, + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - PVE::AccessControl::lock_user_config(sub { - my $usercfg = cfs_read_file("user.cfg"); - my $pool = $param->{poolid}; - my $pool_config = $usercfg->{pools}->{$pool}; + PVE::AccessControl::lock_user_config( + sub { + my $usercfg = cfs_read_file("user.cfg"); + my $pool = $param->{poolid}; + my $pool_config = $usercfg->{pools}->{$pool}; - die "pool '$pool' does not exist\n" if !$pool_config; + die "pool '$pool' does not exist\n" if !$pool_config; - $pool_config->{comment} = $param->{comment} if defined($param->{comment}); + $pool_config->{comment} = $param->{comment} if defined($param->{comment}); - if (defined($param->{vms})) { - for my $vmid (PVE::Tools::split_list($param->{vms})) { - $rpcenv->check_perm_modify($authuser, "/vms/$vmid"); + if (defined($param->{vms})) { + for my $vmid (PVE::Tools::split_list($param->{vms})) { + $rpcenv->check_perm_modify($authuser, "/vms/$vmid"); - if ($param->{delete}) { - die "VM $vmid is not a pool member\n" if !$pool_config->{vms}->{$vmid}; - delete $pool_config->{vms}->{$vmid}; - delete $usercfg->{vms}->{$vmid}; - } else { - die "VM $vmid is already a pool member\n" if $pool_config->{vms}->{$vmid}; - if (defined(my $existing_pool = $usercfg->{vms}->{$vmid})) { - die "VM $vmid belongs already to pool '$existing_pool' and 'allow-move' is not set\n" - if !$param->{'allow-move'}; + if ($param->{delete}) { + die "VM $vmid is not a pool member\n" + if !$pool_config->{vms}->{$vmid}; + delete $pool_config->{vms}->{$vmid}; + delete $usercfg->{vms}->{$vmid}; + } else { + die "VM $vmid is already a pool member\n" + if $pool_config->{vms}->{$vmid}; + if (defined(my $existing_pool = $usercfg->{vms}->{$vmid})) { + die + "VM $vmid belongs already to pool '$existing_pool' and 'allow-move' is not set\n" + if !$param->{'allow-move'}; - $rpcenv->check($authuser, "/pool/$existing_pool", ['Pool.Allocate']); - delete $usercfg->{pools}->{$existing_pool}->{vms}->{$vmid}; - } - $pool_config->{vms}->{$vmid} = 1; - $usercfg->{vms}->{$vmid} = $pool; - } - } - } + $rpcenv->check( + $authuser, "/pool/$existing_pool", ['Pool.Allocate'], + ); + delete $usercfg->{pools}->{$existing_pool}->{vms}->{$vmid}; + } + $pool_config->{vms}->{$vmid} = 1; + $usercfg->{vms}->{$vmid} = $pool; + } + } + } - if (defined($param->{storage})) { - for my $storeid (PVE::Tools::split_list($param->{storage})) { - $rpcenv->check_perm_modify($authuser, "/storage/$storeid"); + if (defined($param->{storage})) { + for my $storeid (PVE::Tools::split_list($param->{storage})) { + $rpcenv->check_perm_modify($authuser, "/storage/$storeid"); - if ($param->{delete}) { - die "Storage '$storeid' is not a pool member\n" - if !$pool_config->{storage}->{$storeid}; - delete $pool_config->{storage}->{$storeid}; - } else { - die "Storage '$storeid' is already a pool member\n" - if $pool_config->{storage}->{$storeid}; + if ($param->{delete}) { + die "Storage '$storeid' is not a pool member\n" + if !$pool_config->{storage}->{$storeid}; + delete $pool_config->{storage}->{$storeid}; + } else { + die "Storage '$storeid' is already a pool member\n" + if $pool_config->{storage}->{$storeid}; - $pool_config->{storage}->{$storeid} = 1; - } - } - } + $pool_config->{storage}->{$storeid} = 1; + } + } + } - cfs_write_file("user.cfg", $usercfg); - }, "update pools failed"); + cfs_write_file("user.cfg", $usercfg); + }, + "update pools failed", + ); - return; - }}); + return; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'read_pool', path => '{poolid}', method => 'GET', permissions => { - check => ['perm', '/pool/{poolid}', ['Pool.Audit']], + check => ['perm', '/pool/{poolid}', ['Pool.Audit']], }, - description => "Get pool configuration (deprecated, no support for nested pools, use 'GET /pools/?poolid={poolid}').", + description => + "Get pool configuration (deprecated, no support for nested pools, use 'GET /pools/?poolid={poolid}').", parameters => { - additionalProperties => 0, - properties => { - poolid => { - type => 'string', - format => 'pve-poolid', - }, - type => { - type => 'string', - enum => [ 'qemu', 'lxc', 'storage' ], - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + poolid => { + type => 'string', + format => 'pve-poolid', + }, + type => { + type => 'string', + enum => ['qemu', 'lxc', 'storage'], + optional => 1, + }, + }, }, returns => { - type => "object", - additionalProperties => 0, - properties => { - comment => { - type => 'string', - optional => 1, - }, - members => { - type => 'array', - items => { - type => "object", - additionalProperties => 1, - properties => { - type => { - type => 'string', - enum => [ 'qemu', 'lxc', 'openvz', 'storage' ], - }, - id => { - type => 'string', - }, - node => { - type => 'string', - }, - vmid => { - type => 'integer', - optional => 1, - }, - storage => { - type => 'string', - optional => 1, - }, - }, - }, - }, - }, + type => "object", + additionalProperties => 0, + properties => { + comment => { + type => 'string', + optional => 1, + }, + members => { + type => 'array', + items => { + type => "object", + additionalProperties => 1, + properties => { + type => { + type => 'string', + enum => ['qemu', 'lxc', 'openvz', 'storage'], + }, + id => { + type => 'string', + }, + node => { + type => 'string', + }, + vmid => { + type => 'integer', + optional => 1, + }, + storage => { + type => 'string', + optional => 1, + }, + }, + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $pool_info = __PACKAGE__->index($param); - return $pool_info->[0]; - }}); + my $pool_info = __PACKAGE__->index($param); + return $pool_info->[0]; + }, +}); - -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete_pool_deprecated', protected => 1, path => '{poolid}', method => 'DELETE', permissions => { - description => "You can only delete empty pools (no members).", - check => ['perm', '/pool/{poolid}', ['Pool.Allocate']], + description => "You can only delete empty pools (no members).", + check => ['perm', '/pool/{poolid}', ['Pool.Allocate']], }, - description => "Delete pool (deprecated, no support for nested pools, use 'DELETE /pools/?poolid={poolid}').", + description => + "Delete pool (deprecated, no support for nested pools, use 'DELETE /pools/?poolid={poolid}').", parameters => { - additionalProperties => 0, - properties => { - poolid => { type => 'string', format => 'pve-poolid' }, - } + additionalProperties => 0, + properties => { + poolid => { type => 'string', format => 'pve-poolid' }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - return __PACKAGE__->delete_pool($param); - }}); + return __PACKAGE__->delete_pool($param); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete_pool', protected => 1, path => '', method => 'DELETE', permissions => { - description => "You can only delete empty pools (no members).", - check => ['perm', '/pool/{poolid}', ['Pool.Allocate']], + description => "You can only delete empty pools (no members).", + check => ['perm', '/pool/{poolid}', ['Pool.Allocate']], }, description => "Delete pool.", parameters => { - additionalProperties => 0, - properties => { - poolid => { type => 'string', format => 'pve-poolid' }, - } + additionalProperties => 0, + properties => { + poolid => { type => 'string', format => 'pve-poolid' }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - PVE::AccessControl::lock_user_config(sub { - my $vmlist = PVE::Cluster::get_vmlist() || {}; - my $idlist = $vmlist->{ids} || {}; + PVE::AccessControl::lock_user_config( + sub { + my $vmlist = PVE::Cluster::get_vmlist() || {}; + my $idlist = $vmlist->{ids} || {}; - my $storecfg = PVE::Storage::config(); - my $usercfg = cfs_read_file("user.cfg"); + my $storecfg = PVE::Storage::config(); + my $usercfg = cfs_read_file("user.cfg"); - my $pool = $param->{poolid}; + my $pool = $param->{poolid}; - my $pool_config = $usercfg->{pools}->{$pool}; - die "pool '$pool' does not exist\n" if !$pool_config; - for my $subpool (sort keys %{$pool_config->{pools}}) { - die "pool '$pool' is not empty (contains pool '$subpool')\n"; - } + my $pool_config = $usercfg->{pools}->{$pool}; + die "pool '$pool' does not exist\n" if !$pool_config; + for my $subpool (sort keys %{ $pool_config->{pools} }) { + die "pool '$pool' is not empty (contains pool '$subpool')\n"; + } - for my $vmid (sort keys %{$pool_config->{vms}}) { - next if !$idlist->{$vmid}; # ignore destroyed guests - die "pool '$pool' is not empty (contains VM $vmid)\n"; - } + for my $vmid (sort keys %{ $pool_config->{vms} }) { + next if !$idlist->{$vmid}; # ignore destroyed guests + die "pool '$pool' is not empty (contains VM $vmid)\n"; + } - for my $storeid (sort keys %{$pool_config->{storage}}) { - next if !PVE::Storage::storage_config ($storecfg, $storeid, 1); - die "pool '$pool' is not empty (contains storage '$storeid')\n"; - } + for my $storeid (sort keys %{ $pool_config->{storage} }) { + next if !PVE::Storage::storage_config($storecfg, $storeid, 1); + die "pool '$pool' is not empty (contains storage '$storeid')\n"; + } - delete ($usercfg->{pools}->{$pool}); - PVE::AccessControl::delete_pool_acl($pool, $usercfg); + delete($usercfg->{pools}->{$pool}); + PVE::AccessControl::delete_pool_acl($pool, $usercfg); - cfs_write_file("user.cfg", $usercfg); - }, "delete pool failed"); + cfs_write_file("user.cfg", $usercfg); + }, + "delete pool failed", + ); - return; - }}); + return; + }, +}); 1; diff --git a/PVE/API2/Replication.pm b/PVE/API2/Replication.pm index 3a2be73a..4d45ff84 100644 --- a/PVE/API2/Replication.pm +++ b/PVE/API2/Replication.pm @@ -27,11 +27,11 @@ our $lookup_guest_class = sub { my ($vmtype) = @_; if ($vmtype eq 'qemu') { - return 'PVE::QemuConfig'; + return 'PVE::QemuConfig'; } elsif ($vmtype eq 'lxc') { - return 'PVE::LXC::Config'; + return 'PVE::LXC::Config'; } else { - die "unknown guest type '$vmtype' - internal error"; + die "unknown guest type '$vmtype' - internal error"; } }; @@ -42,38 +42,37 @@ sub run_single_job { my $local_node = PVE::INotify::nodename(); my $code = sub { - $now //= time(); + $now //= time(); - my $cfg = PVE::ReplicationConfig->new(); + my $cfg = PVE::ReplicationConfig->new(); - my $jobcfg = $cfg->{ids}->{$jobid}; - die "no such job '$jobid'\n" if !$jobcfg; + my $jobcfg = $cfg->{ids}->{$jobid}; + die "no such job '$jobid'\n" if !$jobcfg; - die "internal error - not implemented" if $jobcfg->{type} ne 'local'; + die "internal error - not implemented" if $jobcfg->{type} ne 'local'; - die "job '$jobid' is disabled\n" if $jobcfg->{disable}; + die "job '$jobid' is disabled\n" if $jobcfg->{disable}; - my $vms = PVE::Cluster::get_vmlist(); - my $vmid = $jobcfg->{guest}; + my $vms = PVE::Cluster::get_vmlist(); + my $vmid = $jobcfg->{guest}; - die "no such guest '$vmid'\n" if !$vms->{ids}->{$vmid}; + die "no such guest '$vmid'\n" if !$vms->{ids}->{$vmid}; - die "guest '$vmid' is not on local node\n" - if $vms->{ids}->{$vmid}->{node} ne $local_node; + die "guest '$vmid' is not on local node\n" + if $vms->{ids}->{$vmid}->{node} ne $local_node; - die "unable to sync to local node\n" if $jobcfg->{target} eq $local_node; + die "unable to sync to local node\n" if $jobcfg->{target} eq $local_node; - my $vmtype = $vms->{ids}->{$vmid}->{type}; + my $vmtype = $vms->{ids}->{$vmid}->{type}; - my $guest_class = $lookup_guest_class->($vmtype); - PVE::Replication::run_replication($guest_class, $jobcfg, $now, $now, $logfunc); + my $guest_class = $lookup_guest_class->($vmtype); + PVE::Replication::run_replication($guest_class, $jobcfg, $now, $now, $logfunc); }; my $res = PVE::Tools::lock_file($pvesr_lock_path, 60, $code); die $@ if $@; } - # TODO: below two should probably part of the general job framework/plugin system my sub _should_mail_at_failcount { my ($fail_count) = @_; @@ -87,11 +86,10 @@ my sub _should_mail_at_failcount { # failing job is re-tried every half hour, try to send one mail after 1, 2, 4, 8, etc. days my $i = 1; while ($i * 48 < $fail_count) { - $i = $i * 2; + $i = $i * 2; } return $i * 48 == $fail_count; -}; - +} my sub _handle_job_err { my ($job, $err, $mail) = @_; @@ -121,18 +119,16 @@ my sub _handle_job_err { $template_data->{"error"} = $err; my $metadata_fields = { - type => "replication", - "job-id" => $job->{id}, - # Hostname (without domain part) - hostname => PVE::INotify::nodename(), + type => "replication", + "job-id" => $job->{id}, + # Hostname (without domain part) + hostname => PVE::INotify::nodename(), }; eval { - PVE::Notify::error( - "replication", - $template_data, - $metadata_fields - ); + PVE::Notify::error( + "replication", $template_data, $metadata_fields, + ); }; warn ": $@" if $@; @@ -145,22 +141,24 @@ sub run_jobs { my $iteration = $now // time(); my $code = sub { - my $start_time = $now // time(); + my $start_time = $now // time(); - PVE::ReplicationState::purge_old_states(); + PVE::ReplicationState::purge_old_states(); - while (my $jobcfg = PVE::ReplicationState::get_next_job($iteration, $start_time)) { - my $guest_class = $lookup_guest_class->($jobcfg->{vmtype}); + while (my $jobcfg = PVE::ReplicationState::get_next_job($iteration, $start_time)) { + my $guest_class = $lookup_guest_class->($jobcfg->{vmtype}); - eval { - PVE::Replication::run_replication($guest_class, $jobcfg, $iteration, $start_time, $logfunc, $verbose); - }; - if (my $err = $@) { - _handle_job_err($jobcfg, $err, $mail); - } + eval { + PVE::Replication::run_replication( + $guest_class, $jobcfg, $iteration, $start_time, $logfunc, $verbose, + ); + }; + if (my $err = $@) { + _handle_job_err($jobcfg, $err, $mail); + } - $start_time = $now // time(); - } + $start_time = $now // time(); + } }; my $res = PVE::Tools::lock_file($pvesr_lock_path, 60, $code); @@ -177,215 +175,224 @@ my $extract_job_status = sub { $data->{id} = $jobid; foreach my $k (qw(last_sync last_try fail_count error duration)) { - $data->{$k} = $state->{$k} if defined($state->{$k}); + $data->{$k} = $state->{$k} if defined($state->{$k}); } if ($state->{pid} && $state->{ptime}) { - if (PVE::ProcFSTools::check_process_running($state->{pid}, $state->{ptime})) { - $data->{pid} = $state->{pid}; - } + if (PVE::ProcFSTools::check_process_running($state->{pid}, $state->{ptime})) { + $data->{pid} = $state->{pid}; + } } return $data; }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'status', path => '', method => 'GET', description => "List status of all replication jobs on this node.", permissions => { - description => "Requires the VM.Audit permission on /vms/.", - user => 'all', + description => "Requires the VM.Audit permission on /vms/.", + user => 'all', }, protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - guest => get_standard_option('pve-vmid', { - optional => 1, - description => "Only list replication jobs for this guest.", - }), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + guest => get_standard_option( + 'pve-vmid', + { + optional => 1, + description => "Only list replication jobs for this guest.", + }, + ), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - id => { type => 'string' }, - }, - }, - links => [ { rel => 'child', href => "{id}" } ], + type => 'array', + items => { + type => "object", + properties => { + id => { type => 'string' }, + }, + }, + links => [{ rel => 'child', href => "{id}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $jobs = PVE::ReplicationState::job_status(1); + my $jobs = PVE::ReplicationState::job_status(1); - my $res = []; - foreach my $id (sort keys %$jobs) { - my $data = $extract_job_status->($jobs->{$id}, $id); - my $guest = $data->{guest}; - next if defined($param->{guest}) && $guest != $param->{guest}; - next if !$rpcenv->check($authuser, "/vms/$guest", [ 'VM.Audit' ]); - push @$res, $data; - } + my $res = []; + foreach my $id (sort keys %$jobs) { + my $data = $extract_job_status->($jobs->{$id}, $id); + my $guest = $data->{guest}; + next if defined($param->{guest}) && $guest != $param->{guest}; + next if !$rpcenv->check($authuser, "/vms/$guest", ['VM.Audit']); + push @$res, $data; + } - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '{id}', method => 'GET', permissions => { user => 'all' }, description => "Directory index.", parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-replication-id'), - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + id => get_standard_option('pve-replication-id'), + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - return [ - { name => 'schedule_now' }, - { name => 'log' }, - { name => 'status' }, - ]; - }}); + return [ + { name => 'schedule_now' }, { name => 'log' }, { name => 'status' }, + ]; + }, +}); - -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'job_status', path => '{id}/status', method => 'GET', description => "Get replication job status.", permissions => { - description => "Requires the VM.Audit permission on /vms/.", - user => 'all', + description => "Requires the VM.Audit permission on /vms/.", + user => 'all', }, protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-replication-id'), - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + id => get_standard_option('pve-replication-id'), + node => get_standard_option('pve-node'), + }, }, returns => { - type => "object", - properties => {}, + type => "object", + properties => {}, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $jobs = PVE::ReplicationState::job_status(1); - my $jobid = $param->{id}; - my $jobcfg = $jobs->{$jobid}; + my $jobs = PVE::ReplicationState::job_status(1); + my $jobid = $param->{id}; + my $jobcfg = $jobs->{$jobid}; - die "no such replication job '$jobid'\n" if !defined($jobcfg); + die "no such replication job '$jobid'\n" if !defined($jobcfg); - my $data = $extract_job_status->($jobcfg, $jobid); - my $guest = $data->{guest}; + my $data = $extract_job_status->($jobcfg, $jobid); + my $guest = $data->{guest}; - raise_perm_exc() if !$rpcenv->check($authuser, "/vms/$guest", [ 'VM.Audit' ]); + raise_perm_exc() if !$rpcenv->check($authuser, "/vms/$guest", ['VM.Audit']); - return $data; - }}); + return $data; + }, +}); __PACKAGE__->register_method({ name => 'read_job_log', path => '{id}/log', method => 'GET', permissions => { - description => "Requires the VM.Audit permission on /vms/, or 'Sys.Audit' on '/nodes/'", - user => 'all', + description => + "Requires the VM.Audit permission on /vms/, or 'Sys.Audit' on '/nodes/'", + user => 'all', }, protected => 1, description => "Read replication job log.", proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-replication-id'), - node => get_standard_option('pve-node'), - start => { - type => 'integer', - minimum => 0, - optional => 1, - }, - limit => { - type => 'integer', - minimum => 0, - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + id => get_standard_option('pve-replication-id'), + node => get_standard_option('pve-node'), + start => { + type => 'integer', + minimum => 0, + optional => 1, + }, + limit => { + type => 'integer', + minimum => 0, + optional => 1, + }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - n => { - description=> "Line number", - type=> 'integer', - }, - t => { - description=> "Line text", - type => 'string', - } - } - } + type => 'array', + items => { + type => "object", + properties => { + n => { + description => "Line number", + type => 'integer', + }, + t => { + description => "Line text", + type => 'string', + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $jobid = $param->{id}; - my $filename = PVE::ReplicationState::job_logfile_name($jobid); + my $jobid = $param->{id}; + my $filename = PVE::ReplicationState::job_logfile_name($jobid); - my $cfg = PVE::ReplicationConfig->new(); - my $data = $cfg->{ids}->{$jobid}; + my $cfg = PVE::ReplicationConfig->new(); + my $data = $cfg->{ids}->{$jobid}; - die "no such replication job '$jobid'\n" if !defined($data); + die "no such replication job '$jobid'\n" if !defined($data); - my $node = $param->{node}; + my $node = $param->{node}; - my $vmid = $data->{guest}; - raise_perm_exc() if (!($rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ]) || - $rpcenv->check($authuser, "/nodes/$node", [ 'Sys.Audit' ]))); + my $vmid = $data->{guest}; + raise_perm_exc() + if (!( + $rpcenv->check($authuser, "/vms/$vmid", ['VM.Audit']) + || $rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit']) + )); - my ($count, $lines) = PVE::Tools::dump_logfile($filename, $param->{start}, $param->{limit}); + my ($count, $lines) = + PVE::Tools::dump_logfile($filename, $param->{start}, $param->{limit}); - $rpcenv->set_result_attrib('total', $count); + $rpcenv->set_result_attrib('total', $count); - return $lines; - }}); + return $lines; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'schedule_now', path => '{id}/schedule_now', method => 'POST', @@ -393,30 +400,31 @@ __PACKAGE__->register_method ({ proxyto => 'node', protected => 1, permissions => { - check => ['perm', '/storage', ['Datastore.Allocate']], + check => ['perm', '/storage', ['Datastore.Allocate']], }, parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-replication-id'), - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + id => get_standard_option('pve-replication-id'), + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $jobid = $param->{id}; + my $jobid = $param->{id}; - my $cfg = PVE::ReplicationConfig->new(); - my $jobcfg = $cfg->{ids}->{$jobid}; + my $cfg = PVE::ReplicationConfig->new(); + my $jobcfg = $cfg->{ids}->{$jobid}; - die "no such replication job '$jobid'\n" if !defined($jobcfg); + die "no such replication job '$jobid'\n" if !defined($jobcfg); - PVE::ReplicationState::schedule_job_now($jobcfg); + PVE::ReplicationState::schedule_job_now($jobcfg); - }}); + }, +}); 1; diff --git a/PVE/API2/ReplicationConfig.pm b/PVE/API2/ReplicationConfig.pm index d0e8a49e..d8732a76 100644 --- a/PVE/API2/ReplicationConfig.pm +++ b/PVE/API2/ReplicationConfig.pm @@ -14,268 +14,276 @@ use PVE::RESTHandler; use base qw(PVE::RESTHandler); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', description => "List replication jobs.", permissions => { - description => "Will only return replication jobs for which the calling user has" - . " VM.Audit permission on /vms/.", - user => 'all', + description => "Will only return replication jobs for which the calling user has" + . " VM.Audit permission on /vms/.", + user => 'all', }, parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{id}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{id}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $cfg = PVE::ReplicationConfig->new(); + my $cfg = PVE::ReplicationConfig->new(); - my $res = []; - foreach my $id (sort keys %{$cfg->{ids}}) { - my $d = $cfg->{ids}->{$id}; - my $vmid = $d->{guest}; - next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1); - $d->{id} = $id; - push @$res, $d; - } + my $res = []; + foreach my $id (sort keys %{ $cfg->{ids} }) { + my $d = $cfg->{ids}->{$id}; + my $vmid = $d->{guest}; + next if !$rpcenv->check($authuser, "/vms/$vmid", ['VM.Audit'], 1); + $d->{id} = $id; + push @$res, $d; + } - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'read', path => '{id}', method => 'GET', description => "Read replication job configuration.", permissions => { - description => "Requires the VM.Audit permission on /vms/.", - user => 'all', + description => "Requires the VM.Audit permission on /vms/.", + user => 'all', }, parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-replication-id'), - }, + additionalProperties => 0, + properties => { + id => get_standard_option('pve-replication-id'), + }, }, returns => { type => 'object' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $cfg = PVE::ReplicationConfig->new(); + my $cfg = PVE::ReplicationConfig->new(); - my $data = $cfg->{ids}->{$param->{id}}; + my $data = $cfg->{ids}->{ $param->{id} }; - die "no such replication job '$param->{id}'\n" if !defined($data); + die "no such replication job '$param->{id}'\n" if !defined($data); - my $vmid = $data->{guest}; + my $vmid = $data->{guest}; - raise_perm_exc() if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ]); + raise_perm_exc() if !$rpcenv->check($authuser, "/vms/$vmid", ['VM.Audit']); - $data->{id} = $param->{id}; + $data->{id} = $param->{id}; - $data->{digest} = $cfg->{digest}; + $data->{digest} = $cfg->{digest}; - return $data; - }}); + return $data; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create', path => '', protected => 1, method => 'POST', description => "Create a new replication job", permissions => { - check => ['perm', '/storage', ['Datastore.Allocate']], + check => ['perm', '/storage', ['Datastore.Allocate']], }, parameters => PVE::ReplicationConfig->createSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $type = extract_param($param, 'type'); - my $plugin = PVE::ReplicationConfig->lookup($type); - my $id = extract_param($param, 'id'); + my $type = extract_param($param, 'type'); + my $plugin = PVE::ReplicationConfig->lookup($type); + my $id = extract_param($param, 'id'); - # extract guest ID from job ID - my ($guest) = PVE::ReplicationConfig::parse_replication_job_id($id); + # extract guest ID from job ID + my ($guest) = PVE::ReplicationConfig::parse_replication_job_id($id); - my $nodelist = PVE::Cluster::get_members(); - my $vmlist = PVE::Cluster::get_vmlist(); + my $nodelist = PVE::Cluster::get_members(); + my $vmlist = PVE::Cluster::get_vmlist(); - my $guest_info = $vmlist->{ids}->{$guest}; + my $guest_info = $vmlist->{ids}->{$guest}; - die "Guest '$guest' does not exist.\n" - if !defined($guest_info); - die "Target '$param->{target}' does not exist.\n" - if !defined($nodelist->{$param->{target}}); + die "Guest '$guest' does not exist.\n" + if !defined($guest_info); + die "Target '$param->{target}' does not exist.\n" + if !defined($nodelist->{ $param->{target} }); - my $source = $guest_info->{node}; - die "Source '$param->{source}' does not match current node of guest '$guest' ($source)\n" - if defined($param->{source}) && $param->{source} ne $source; + my $source = $guest_info->{node}; + die + "Source '$param->{source}' does not match current node of guest '$guest' ($source)\n" + if defined($param->{source}) && $param->{source} ne $source; - $param->{source} //= $source; + $param->{source} //= $source; - die "Source and target must not be identical\n" - if $param->{target} eq $source; + die "Source and target must not be identical\n" + if $param->{target} eq $source; - my $guest_class = $PVE::API2::Replication::lookup_guest_class->($guest_info->{type}); - my $guest_conf = $guest_class->load_config($guest, $source); - my $rep_volumes = $guest_class->get_replicatable_volumes(PVE::Storage::config(), $guest, $guest_conf, 0, 0); - die "No replicatable volumes found\n" if !%$rep_volumes; + my $guest_class = $PVE::API2::Replication::lookup_guest_class->($guest_info->{type}); + my $guest_conf = $guest_class->load_config($guest, $source); + my $rep_volumes = $guest_class->get_replicatable_volumes( + PVE::Storage::config(), $guest, $guest_conf, 0, 0, + ); + die "No replicatable volumes found\n" if !%$rep_volumes; - my $code = sub { - my $cfg = PVE::ReplicationConfig->new(); + my $code = sub { + my $cfg = PVE::ReplicationConfig->new(); - die "replication job '$id' already exists\n" - if $cfg->{ids}->{$id}; + die "replication job '$id' already exists\n" + if $cfg->{ids}->{$id}; - my $opts = $plugin->check_config($id, $param, 1, 1); + my $opts = $plugin->check_config($id, $param, 1, 1); - $opts->{guest} = $guest; + $opts->{guest} = $guest; - $cfg->{ids}->{$id} = $opts; + $cfg->{ids}->{$id} = $opts; - $cfg->write(); - }; + $cfg->write(); + }; - PVE::ReplicationConfig::lock($code); + PVE::ReplicationConfig::lock($code); - return undef; - }}); + return undef; + }, +}); - -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update', protected => 1, path => '{id}', method => 'PUT', description => "Update replication job configuration.", permissions => { - check => ['perm', '/storage', ['Datastore.Allocate']], + check => ['perm', '/storage', ['Datastore.Allocate']], }, parameters => PVE::ReplicationConfig->updateSchema(), returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = extract_param($param, 'id'); - my $digest = extract_param($param, 'digest'); - my $delete = extract_param($param, 'delete'); + my $id = extract_param($param, 'id'); + my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete'); - my $code = sub { - my $cfg = PVE::ReplicationConfig->new(); + my $code = sub { + my $cfg = PVE::ReplicationConfig->new(); - PVE::SectionConfig::assert_if_modified($cfg, $digest); + PVE::SectionConfig::assert_if_modified($cfg, $digest); - my $data = $cfg->{ids}->{$id}; - die "no such job '$id'\n" if !$data; + my $data = $cfg->{ids}->{$id}; + die "no such job '$id'\n" if !$data; - my $plugin = PVE::ReplicationConfig->lookup($data->{type}); - my $opts = $plugin->check_config($id, $param, 0, 1); + my $plugin = PVE::ReplicationConfig->lookup($data->{type}); + my $opts = $plugin->check_config($id, $param, 0, 1); - foreach my $k (keys %$opts) { - $data->{$k} = $opts->{$k}; - } + foreach my $k (keys %$opts) { + $data->{$k} = $opts->{$k}; + } - if ($delete) { - my $options = $plugin->private()->{options}->{$data->{type}}; - foreach my $k (PVE::Tools::split_list($delete)) { - my $d = $options->{$k} || - die "no such option '$k'\n"; - die "unable to delete required option '$k'\n" - if !$d->{optional}; - die "unable to delete fixed option '$k'\n" - if $d->{fixed}; - delete $data->{$k}; - } - } + if ($delete) { + my $options = $plugin->private()->{options}->{ $data->{type} }; + foreach my $k (PVE::Tools::split_list($delete)) { + my $d = $options->{$k} + || die "no such option '$k'\n"; + die "unable to delete required option '$k'\n" + if !$d->{optional}; + die "unable to delete fixed option '$k'\n" + if $d->{fixed}; + delete $data->{$k}; + } + } - $cfg->write(); - }; + $cfg->write(); + }; - PVE::ReplicationConfig::lock($code); + PVE::ReplicationConfig::lock($code); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete', protected => 1, path => '{id}', method => 'DELETE', description => "Mark replication job for removal.", permissions => { - check => ['perm', '/storage', ['Datastore.Allocate']], + check => ['perm', '/storage', ['Datastore.Allocate']], }, parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-replication-id'), - keep => { - description => "Keep replicated data at target (do not remove).", - type => 'boolean', - optional => 1, - default => 0, - }, - force => { - description => "Will remove the jobconfig entry, but will not cleanup.", - type => 'boolean', - optional => 1, - default => 0, - }, - } + additionalProperties => 0, + properties => { + id => get_standard_option('pve-replication-id'), + keep => { + description => "Keep replicated data at target (do not remove).", + type => 'boolean', + optional => 1, + default => 0, + }, + force => { + description => "Will remove the jobconfig entry, but will not cleanup.", + type => 'boolean', + optional => 1, + default => 0, + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $code = sub { - my $cfg = PVE::ReplicationConfig->new(); + my $code = sub { + my $cfg = PVE::ReplicationConfig->new(); - my $id = $param->{id}; - if ($param->{force}) { - raise_param_exc({ 'keep' => "conflicts with parameter 'force'" }) if $param->{keep}; - delete $cfg->{ids}->{$id}; - } else { - my $jobcfg = $cfg->{ids}->{$id}; - die "no such job '$id'\n" if !$jobcfg; + my $id = $param->{id}; + if ($param->{force}) { + raise_param_exc({ 'keep' => "conflicts with parameter 'force'" }) + if $param->{keep}; + delete $cfg->{ids}->{$id}; + } else { + my $jobcfg = $cfg->{ids}->{$id}; + die "no such job '$id'\n" if !$jobcfg; - if (!$param->{keep} && $jobcfg->{type} eq 'local') { - # remove local snapshots and remote volumes - $jobcfg->{remove_job} = 'full'; - } else { - # only remove local snapshots - $jobcfg->{remove_job} = 'local'; - } + if (!$param->{keep} && $jobcfg->{type} eq 'local') { + # remove local snapshots and remote volumes + $jobcfg->{remove_job} = 'full'; + } else { + # only remove local snapshots + $jobcfg->{remove_job} = 'local'; + } - warn "Replication job removal is a background task and will take some time.\n" - if $rpcenv->{type} eq 'cli'; - } - $cfg->write(); - }; + warn "Replication job removal is a background task and will take some time.\n" + if $rpcenv->{type} eq 'cli'; + } + $cfg->write(); + }; - PVE::ReplicationConfig::lock($code); + PVE::ReplicationConfig::lock($code); - return undef; - }}); + return undef; + }, +}); 1; diff --git a/PVE/API2/Services.pm b/PVE/API2/Services.pm index 40f7504a..b67d10c8 100644 --- a/PVE/API2/Services.pm +++ b/PVE/API2/Services.pm @@ -47,7 +47,7 @@ my $essential_services = { # manage subinstances, of which the default is called "-". # This is where we look for the daemon status my $unit_extra_names = { - postfix => 'postfix@-' + postfix => 'postfix@-', }; my $get_full_service_state = sub { @@ -56,10 +56,10 @@ my $get_full_service_state = sub { my $res; my $parser = sub { - my $line = shift; - if ($line =~ m/^([^=\s]+)=(.*)$/) { - $res->{$1} = $2; - } + my $line = shift; + if ($line =~ m/^([^=\s]+)=(.*)$/) { + $res->{$1} = $2; + } }; PVE::Tools::run_command(['systemctl', 'show', $service], outfunc => $parser); @@ -75,11 +75,11 @@ sub get_service_list { my $list = {}; foreach my $name (@$service_name_list) { - my $ss = eval { $get_full_service_state->($name) }; - warn $@ if $@; - next if !$ss; - next if !defined($ss->{Description}); - $list->{$name} = { name => $name, desc => $ss->{Description} }; + my $ss = eval { $get_full_service_state->($name) }; + warn $@ if $@; + next if !$ss; + next if !defined($ss->{Description}); + $list->{$name} = { name => $name, desc => $ss->{Description} }; } $static_service_list = $list; @@ -87,7 +87,6 @@ sub get_service_list { return $static_service_list; } - my $service_prop_desc = { description => "Service ID", type => 'string', @@ -99,10 +98,11 @@ my $service_cmd = sub { my $initd_cmd; - die "unknown service command '$cmd'\n" if $cmd !~ m/^(start|stop|restart|reload|try-reload-or-restart)$/; + die "unknown service command '$cmd'\n" + if $cmd !~ m/^(start|stop|restart|reload|try-reload-or-restart)$/; if ($essential_services->{$service} && $cmd eq 'stop') { - die "invalid service cmd '$service $cmd': refusing to stop essential service!\n"; + die "invalid service cmd '$service $cmd': refusing to stop essential service!\n"; } PVE::Tools::run_command(['systemctl', $cmd, $service]); @@ -115,67 +115,69 @@ my $service_state = sub { my $ss = eval { $get_full_service_state->($service) }; if (my $err = $@) { - return $res; + return $res; } my $state = $ss->{SubState} || 'unknown'; if ($state eq 'dead' && $ss->{Type} && $ss->{Type} eq 'oneshot' && $ss->{Result}) { - $res->{state} = $ss->{Result}; + $res->{state} = $ss->{Result}; } else { - $res->{state} = $ss->{SubState} || 'unknown'; + $res->{state} = $ss->{SubState} || 'unknown'; } if ($ss->{LoadState} eq 'not-found') { - $res->{'unit-state'} = 'not-found'; # not installed + $res->{'unit-state'} = 'not-found'; # not installed } else { - $res->{'unit-state'} = $ss->{UnitFileState} || 'unknown'; + $res->{'unit-state'} = $ss->{UnitFileState} || 'unknown'; } $res->{'active-state'} = $ss->{ActiveState} || 'unknown'; return $res; }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => '', method => 'GET', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], + check => ['perm', '/nodes/{node}', ['Sys.Audit']], }, description => "Service list.", proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{service}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{service}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $service_list = get_service_list(); + my $service_list = get_service_list(); - my $res = []; - for my $id (sort keys %{$service_list}) { - my $state = $service_state->($id); - push @$res, { - service => $id, - name => $service_list->{$id}->{name}, - desc => $service_list->{$id}->{desc}, - %$state, - }; - } + my $res = []; + for my $id (sort keys %{$service_list}) { + my $state = $service_state->($id); + push @$res, + { + service => $id, + name => $service_list->{$id}->{name}, + desc => $service_list->{$id}->{desc}, + %$state, + }; + } - return $res; - }}); + return $res; + }, +}); __PACKAGE__->register_method({ name => 'srvcmdidx', @@ -183,226 +185,232 @@ __PACKAGE__->register_method({ method => 'GET', description => "Directory index", permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], + check => ['perm', '/nodes/{node}', ['Sys.Audit']], }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - service => $service_prop_desc, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + service => $service_prop_desc, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - subdir => { type => 'string' }, - }, - }, - links => [ { rel => 'child', href => "{subdir}" } ], + type => 'array', + items => { + type => "object", + properties => { + subdir => { type => 'string' }, + }, + }, + links => [{ rel => 'child', href => "{subdir}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $res = [ - { subdir => 'state' }, - { subdir => 'start' }, - { subdir => 'stop' }, - { subdir => 'restart' }, - { subdir => 'reload' }, - ]; + my $res = [ + { subdir => 'state' }, + { subdir => 'start' }, + { subdir => 'stop' }, + { subdir => 'restart' }, + { subdir => 'reload' }, + ]; - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'service_state', path => '{service}/state', method => 'GET', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Audit' ]], + check => ['perm', '/nodes/{node}', ['Sys.Audit']], }, description => "Read service properties", proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - service => $service_prop_desc, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + service => $service_prop_desc, + }, }, returns => { - type => "object", - properties => {}, + type => "object", + properties => {}, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $id = $param->{service}; + my $id = $param->{service}; - my $service_list = get_service_list(); + my $service_list = get_service_list(); - my $si = $service_list->{$id}; + my $si = $service_list->{$id}; - my $state = $service_state->($id); + my $state = $service_state->($id); - return { - service => $param->{service}, - name => $si->{name}, - desc => $si->{desc}, - %$state, - }; - }}); + return { + service => $param->{service}, + name => $si->{name}, + desc => $si->{desc}, + %$state, + }; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'service_start', path => '{service}/start', method => 'POST', description => "Start service.", permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - service => $service_prop_desc, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + service => $service_prop_desc, + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $user = $rpcenv->get_user(); - my $realcmd = sub { - my $upid = shift; + my $realcmd = sub { + my $upid = shift; - syslog('info', "starting service $param->{service}: $upid\n"); + syslog('info', "starting service $param->{service}: $upid\n"); - $service_cmd->($param->{service}, 'start'); + $service_cmd->($param->{service}, 'start'); - }; + }; - return $rpcenv->fork_worker('srvstart', $param->{service}, $user, $realcmd); - }}); + return $rpcenv->fork_worker('srvstart', $param->{service}, $user, $realcmd); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'service_stop', path => '{service}/stop', method => 'POST', description => "Stop service.", permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - service => $service_prop_desc, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + service => $service_prop_desc, + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $user = $rpcenv->get_user(); - my $realcmd = sub { - my $upid = shift; + my $realcmd = sub { + my $upid = shift; - syslog('info', "stopping service $param->{service}: $upid\n"); + syslog('info', "stopping service $param->{service}: $upid\n"); - $service_cmd->($param->{service}, 'stop'); + $service_cmd->($param->{service}, 'stop'); - }; + }; - return $rpcenv->fork_worker('srvstop', $param->{service}, $user, $realcmd); - }}); + return $rpcenv->fork_worker('srvstop', $param->{service}, $user, $realcmd); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'service_restart', path => '{service}/restart', method => 'POST', description => "Hard restart service. Use reload if you want to reduce interruptions.", permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - service => $service_prop_desc, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + service => $service_prop_desc, + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); - my $realcmd = sub { - my $upid = shift; - syslog('info', "re-starting service $param->{service}: $upid\n"); + my $realcmd = sub { + my $upid = shift; + syslog('info', "re-starting service $param->{service}: $upid\n"); - $service_cmd->($param->{service}, 'restart'); - }; + $service_cmd->($param->{service}, 'restart'); + }; - return $rpcenv->fork_worker('srvrestart', $param->{service}, $user, $realcmd); - }}); + return $rpcenv->fork_worker('srvrestart', $param->{service}, $user, $realcmd); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'service_reload', path => '{service}/reload', method => 'POST', description => "Reload service. Falls back to restart if service cannot be reloaded.", permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - service => $service_prop_desc, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + service => $service_prop_desc, + }, }, returns => { - type => 'string', + type => 'string', }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); - my $realcmd = sub { - my $upid = shift; - syslog('info', "reloading service $param->{service}: $upid\n"); + my $realcmd = sub { + my $upid = shift; + syslog('info', "reloading service $param->{service}: $upid\n"); - $service_cmd->($param->{service}, 'try-reload-or-restart'); + $service_cmd->($param->{service}, 'try-reload-or-restart'); - }; + }; - return $rpcenv->fork_worker('srvreload', $param->{service}, $user, $realcmd); - }}); + return $rpcenv->fork_worker('srvreload', $param->{service}, $user, $realcmd); + }, +}); diff --git a/PVE/API2/Subscription.pm b/PVE/API2/Subscription.pm index 77a42434..59e7fe74 100644 --- a/PVE/API2/Subscription.pm +++ b/PVE/API2/Subscription.pm @@ -39,7 +39,7 @@ sub parse_key { my ($key, $noerr) = @_; if ($key =~ m/^${subscription_pattern}$/) { - return wantarray ? ($1, $2) : $1; # number of sockets, level + return wantarray ? ($1, $2) : $1; # number of sockets, level } return undef if $noerr; @@ -51,7 +51,7 @@ sub check_key { my ($sockets, $level) = parse_key($key); if ($sockets < $req_sockets) { - die "wrong number of sockets ($sockets < $req_sockets)\n"; + die "wrong number of sockets ($sockets < $req_sockets)\n"; } return ($sockets, $level); } @@ -67,11 +67,11 @@ sub read_etc_subscription { my ($sockets, $level); eval { ($sockets, $level) = check_key($info->{key}, $req_sockets); }; if (my $err = $@) { - chomp $err; - $info->{status} = 'invalid'; - $info->{message} = $err; + chomp $err; + $info->{status} = 'invalid'; + $info->{message} = $err; } else { - $info->{level} = $level; + $info->{level} = $level; } return $info; @@ -83,7 +83,7 @@ my sub cache_is_valid { return if !$info || $info->{status} ne 'active'; my $checked_info = Proxmox::RS::Subscription::check_age($info, 1); - return $checked_info->{status} eq 'active' + return $checked_info->{status} eq 'active'; } sub write_etc_subscription { @@ -92,25 +92,32 @@ sub write_etc_subscription { my $server_id = PVE::API2Tools::get_hwaddress(); mkdir "/etc/apt/auth.conf.d"; Proxmox::RS::Subscription::write_subscription( - $filename, "/etc/apt/auth.conf.d/pve.conf", "enterprise.proxmox.com/debian/pve", $info); + $filename, + "/etc/apt/auth.conf.d/pve.conf", + "enterprise.proxmox.com/debian/pve", + $info, + ); if (!(defined($info->{key}) && defined($info->{serverid}))) { - unlink "/etc/apt/auth.conf.d/ceph.conf" or $!{ENOENT} or die "failed to remove apt auth ceph.conf - $!"; + unlink "/etc/apt/auth.conf.d/ceph.conf" + or $!{ENOENT} + or die "failed to remove apt auth ceph.conf - $!"; } else { - my $supported_ceph_releases = PVE::Ceph::Releases::get_available_ceph_release_codenames(1); - my $ceph_auth = ''; - for my $ceph_release ($supported_ceph_releases->@*) { - $ceph_auth .= "machine enterprise.proxmox.com/debian/ceph-${ceph_release}" - ." login $info->{key} password $info->{serverid}\n" - } - # add a generic one to handle the case where a new ceph release was made available while - # the subscription info was not yet updated, and as per APT_AUTH.CONF(5) start-with matches. - $ceph_auth .= "machine enterprise.proxmox.com/debian/ceph login $info->{key} password $info->{serverid}\n"; - PVE::Tools::file_set_contents("/etc/apt/auth.conf.d/ceph.conf", $ceph_auth, 0600); + my $supported_ceph_releases = PVE::Ceph::Releases::get_available_ceph_release_codenames(1); + my $ceph_auth = ''; + for my $ceph_release ($supported_ceph_releases->@*) { + $ceph_auth .= "machine enterprise.proxmox.com/debian/ceph-${ceph_release}" + . " login $info->{key} password $info->{serverid}\n"; + } + # add a generic one to handle the case where a new ceph release was made available while + # the subscription info was not yet updated, and as per APT_AUTH.CONF(5) start-with matches. + $ceph_auth .= + "machine enterprise.proxmox.com/debian/ceph login $info->{key} password $info->{serverid}\n"; + PVE::Tools::file_set_contents("/etc/apt/auth.conf.d/ceph.conf", $ceph_auth, 0600); } } -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get', path => '', method => 'GET', @@ -118,239 +125,247 @@ __PACKAGE__->register_method ({ proxyto => 'node', permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, returns => { - type => 'object', - additionalProperties => 0, - properties => { - status => { - type => 'string', - enum => [qw(new notfound active invalid expired suspended)], - description => "The current subscription status.", - }, - checktime => { - type => 'integer', - description => 'Timestamp of the last check done.', - optional => 1, - }, - key => { - type => 'string', - description => 'The subscription key, if set and permitted to access.', - optional => 1, - }, - level => { - type => 'string', - description => 'A short code for the subscription level.', - optional => 1, - }, - message => { - type => 'string', - description => 'A more human readable status message.', - optional => 1, - }, - nextduedate => { - type => 'string', - description => 'Next due date of the set subscription.', - optional => 1, - }, - productname => { - type => 'string', - description => 'Human readable productname of the set subscription.', - optional => 1, - }, - regdate => { - type => 'string', - description => 'Register date of the set subscription.', - optional => 1, - }, - serverid => { - type => 'string', - description => 'The server ID, if permitted to access.', - optional => 1, - }, - signature => { - type => 'string', - description => 'Signature for offline keys', - optional => 1, - }, - sockets => { - type => 'integer', - description => 'The number of sockets for this host.', - optional => 1, - }, - url => { - type => 'string', - description => 'URL to the web shop.', - optional => 1, - }, - }, + type => 'object', + additionalProperties => 0, + properties => { + status => { + type => 'string', + enum => [qw(new notfound active invalid expired suspended)], + description => "The current subscription status.", + }, + checktime => { + type => 'integer', + description => 'Timestamp of the last check done.', + optional => 1, + }, + key => { + type => 'string', + description => 'The subscription key, if set and permitted to access.', + optional => 1, + }, + level => { + type => 'string', + description => 'A short code for the subscription level.', + optional => 1, + }, + message => { + type => 'string', + description => 'A more human readable status message.', + optional => 1, + }, + nextduedate => { + type => 'string', + description => 'Next due date of the set subscription.', + optional => 1, + }, + productname => { + type => 'string', + description => 'Human readable productname of the set subscription.', + optional => 1, + }, + regdate => { + type => 'string', + description => 'Register date of the set subscription.', + optional => 1, + }, + serverid => { + type => 'string', + description => 'The server ID, if permitted to access.', + optional => 1, + }, + signature => { + type => 'string', + description => 'Signature for offline keys', + optional => 1, + }, + sockets => { + type => 'integer', + description => 'The number of sockets for this host.', + optional => 1, + }, + url => { + type => 'string', + description => 'URL to the web shop.', + optional => 1, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $node = $param->{node}; + my $node = $param->{node}; - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); - my $has_permission = $rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'], 1); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + my $has_permission = $rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'], 1); - my $server_id = PVE::API2Tools::get_hwaddress(); - my $url = "https://www.proxmox.com/en/proxmox-virtual-environment/pricing"; + my $server_id = PVE::API2Tools::get_hwaddress(); + my $url = "https://www.proxmox.com/en/proxmox-virtual-environment/pricing"; - my $info = read_etc_subscription(); - if (!$info) { - my $no_subscription_info = { - status => "notfound", - message => "There is no subscription key", - url => $url, - }; - $no_subscription_info->{serverid} = $server_id if $has_permission; - return $no_subscription_info; - } + my $info = read_etc_subscription(); + if (!$info) { + my $no_subscription_info = { + status => "notfound", + message => "There is no subscription key", + url => $url, + }; + $no_subscription_info->{serverid} = $server_id if $has_permission; + return $no_subscription_info; + } - if (!$has_permission) { - return { - status => $info->{status}, - message => $info->{message}, - url => $url, - } - } + if (!$has_permission) { + return { + status => $info->{status}, + message => $info->{message}, + url => $url, + }; + } - $info->{serverid} = $server_id; - $info->{sockets} = get_sockets(); - $info->{url} = $url; + $info->{serverid} = $server_id; + $info->{sockets} = get_sockets(); + $info->{url} = $url; - return $info - }}); + return $info; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update', path => '', method => 'POST', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, description => "Update subscription info.", proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - force => { - description => "Always connect to server, even if local cache is still valid.", - type => 'boolean', - optional => 1, - default => 0 - } - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + force => { + description => "Always connect to server, even if local cache is still valid.", + type => 'boolean', + optional => 1, + default => 0, + }, + }, }, - returns => { type => 'null'}, + returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $info = read_etc_subscription(); - return undef if !$info; + my $info = read_etc_subscription(); + return undef if !$info; - my $server_id = PVE::API2Tools::get_hwaddress(); - my $key = $info->{key}; + my $server_id = PVE::API2Tools::get_hwaddress(); + my $key = $info->{key}; - die "Updating offline key not possible - please remove and re-add subscription key to switch to online key.\n" - if $info->{signature}; + die + "Updating offline key not possible - please remove and re-add subscription key to switch to online key.\n" + if $info->{signature}; - return undef if !$param->{force} && cache_is_valid($info); # key has been recently checked + return undef if !$param->{force} && cache_is_valid($info); # key has been recently checked - my $req_sockets = get_sockets(); - check_key($key, $req_sockets); + my $req_sockets = get_sockets(); + check_key($key, $req_sockets); - my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg'); - my $proxy = $dccfg->{http_proxy}; + my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg'); + my $proxy = $dccfg->{http_proxy}; - $info = Proxmox::RS::Subscription::check_subscription($key, $server_id, "", "Proxmox VE", $proxy); + $info = Proxmox::RS::Subscription::check_subscription( + $key, $server_id, "", "Proxmox VE", $proxy, + ); - write_etc_subscription($info); + write_etc_subscription($info); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'set', path => '', method => 'PUT', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, description => "Set subscription key.", proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - key => { - description => "Proxmox VE subscription key", - type => 'string', - pattern => "\\s*${subscription_pattern}\\s*", - maxLength => 32, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + key => { + description => "Proxmox VE subscription key", + type => 'string', + pattern => "\\s*${subscription_pattern}\\s*", + maxLength => 32, + }, + }, }, - returns => { type => 'null'}, + returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $key = PVE::Tools::trim($param->{key}); + my $key = PVE::Tools::trim($param->{key}); - my $new_info = { - status => 'New', - key => $key, - checktime => time(), - }; + my $new_info = { + status => 'New', + key => $key, + checktime => time(), + }; - my $req_sockets = get_sockets(); - my $server_id = PVE::API2Tools::get_hwaddress(); + my $req_sockets = get_sockets(); + my $server_id = PVE::API2Tools::get_hwaddress(); - check_key($key, $req_sockets); + check_key($key, $req_sockets); - write_etc_subscription($new_info); + write_etc_subscription($new_info); - my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg'); - my $proxy = $dccfg->{http_proxy}; + my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg'); + my $proxy = $dccfg->{http_proxy}; - my $checked_info = Proxmox::RS::Subscription::check_subscription( - $key, $server_id, "", "Proxmox VE", $proxy); + my $checked_info = Proxmox::RS::Subscription::check_subscription( + $key, $server_id, "", "Proxmox VE", $proxy, + ); - write_etc_subscription($checked_info); + write_etc_subscription($checked_info); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete', path => '', method => 'DELETE', permissions => { - check => ['perm', '/nodes/{node}', [ 'Sys.Modify' ]], + check => ['perm', '/nodes/{node}', ['Sys.Modify']], }, description => "Delete subscription key of this node.", proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, }, - returns => { type => 'null'}, + returns => { type => 'null' }, code => sub { - my $subscription_file = '/etc/subscription'; - return if ! -e $subscription_file; - unlink($subscription_file) or die "cannot delete subscription key: $!"; - return undef; - }}); + my $subscription_file = '/etc/subscription'; + return if !-e $subscription_file; + unlink($subscription_file) or die "cannot delete subscription key: $!"; + return undef; + }, +}); 1; diff --git a/PVE/API2/Tasks.pm b/PVE/API2/Tasks.pm index ae60d846..9da5b6bd 100644 --- a/PVE/API2/Tasks.pm +++ b/PVE/API2/Tasks.pm @@ -20,7 +20,7 @@ my $convert_token_task = sub { my ($task) = @_; if (PVE::AccessControl::pve_verify_tokenid($task->{user}, 1)) { - ($task->{user}, $task->{tokenid}) = PVE::AccessControl::split_tokenid($task->{user}); + ($task->{user}, $task->{tokenid}) = PVE::AccessControl::split_tokenid($task->{user}); } }; @@ -28,11 +28,11 @@ my $check_task_user = sub { my ($task, $user) = @_; if ($task->{tokenid}) { - my $fulltoken = PVE::AccessControl::join_tokenid($task->{user}, $task->{tokenid}); - # token only sees token tasks, user sees user + token tasks - return $user eq $fulltoken || $user eq $task->{user}; + my $fulltoken = PVE::AccessControl::join_tokenid($task->{user}, $task->{tokenid}); + # token only sees token tasks, user sees user + token tasks + return $user eq $fulltoken || $user eq $task->{user}; } else { - return $user eq $task->{user}; + return $user eq $task->{user}; } }; @@ -41,211 +41,216 @@ __PACKAGE__->register_method({ path => '', method => 'GET', permissions => { - description => "List task associated with the current user, or all task the user has 'Sys.Audit' permissions on /nodes/ (the 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/ (the the task runs on).", + user => 'all', }, description => "Read task list for one node (finished tasks).", proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - start => { - type => 'integer', - minimum => 0, - default => 0, - optional => 1, - description => "List tasks beginning from this offset.", - }, - limit => { - type => 'integer', - minimum => 0, - default => 50, - optional => 1, - description => "Only list this amount of tasks.", - }, - userfilter => { - type => 'string', - optional => 1, - description => "Only list tasks from this user.", - }, - typefilter => { - type => 'string', - optional => 1, - description => 'Only list tasks of this type (e.g., vzstart, vzdump).', - }, - vmid => get_standard_option('pve-vmid', { - description => "Only list tasks for this VM.", - optional => 1, - }), - errors => { - type => 'boolean', - default => 0, - optional => 1, - description => 'Only list tasks with a status of ERROR.', - }, - source => { - type => 'string', - enum => ['archive', 'active', 'all'], - default => 'archive', - optional => 1, - description => 'List archived, active or all tasks.', - }, - since => { - type => 'integer', - description => "Only list tasks since this UNIX epoch.", - optional => 1, - }, - until => { - type => 'integer', - description => "Only list tasks until this UNIX epoch.", - optional => 1, - }, - statusfilter => { - type => 'string', - format => 'pve-task-status-type-list', - optional => 1, - description => 'List of Task States that should be returned.', - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + start => { + type => 'integer', + minimum => 0, + default => 0, + optional => 1, + description => "List tasks beginning from this offset.", + }, + limit => { + type => 'integer', + minimum => 0, + default => 50, + optional => 1, + description => "Only list this amount of tasks.", + }, + userfilter => { + type => 'string', + optional => 1, + description => "Only list tasks from this user.", + }, + typefilter => { + type => 'string', + optional => 1, + description => 'Only list tasks of this type (e.g., vzstart, vzdump).', + }, + vmid => get_standard_option( + 'pve-vmid', + { + description => "Only list tasks for this VM.", + optional => 1, + }, + ), + errors => { + type => 'boolean', + default => 0, + optional => 1, + description => 'Only list tasks with a status of ERROR.', + }, + source => { + type => 'string', + enum => ['archive', 'active', 'all'], + default => 'archive', + optional => 1, + description => 'List archived, active or all tasks.', + }, + since => { + type => 'integer', + description => "Only list tasks since this UNIX epoch.", + optional => 1, + }, + until => { + type => 'integer', + description => "Only list tasks until this UNIX epoch.", + optional => 1, + }, + statusfilter => { + type => 'string', + format => 'pve-task-status-type-list', + optional => 1, + description => 'List of Task States that should be returned.', + }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - upid => { type => 'string', title => 'UPID', }, - node => { type => 'string', title => 'Node', }, - pid => { type => 'integer', title => 'PID', }, - pstart => { type => 'integer', }, - starttime => { type => 'integer', title => 'Starttime', }, - type => { type => 'string', title => 'Type', }, - id => { type => 'string', title => 'ID', }, - user => { type => 'string', title => 'User', }, - endtime => { type => 'integer', optional => 1, title => 'Endtime', }, - status => { type => 'string', optional => 1, title => 'Status', }, - }, - }, - links => [ { rel => 'child', href => "{upid}" } ], + type => 'array', + items => { + type => "object", + properties => { + upid => { type => 'string', title => 'UPID' }, + node => { type => 'string', title => 'Node' }, + pid => { type => 'integer', title => 'PID' }, + pstart => { type => 'integer' }, + starttime => { type => 'integer', title => 'Starttime' }, + type => { type => 'string', title => 'Type' }, + id => { type => 'string', title => 'ID' }, + user => { type => 'string', title => 'User' }, + endtime => { type => 'integer', optional => 1, title => 'Endtime' }, + status => { type => 'string', optional => 1, title => 'Status' }, + }, + }, + links => [{ rel => 'child', href => "{upid}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); - my $res = []; + my $res = []; - my $filename = "/var/log/pve/tasks/index"; + my $filename = "/var/log/pve/tasks/index"; - my $node = $param->{node}; - my $start = $param->{start} // 0; - my $limit = $param->{limit} // 50; - my $userfilter = $param->{userfilter}; - my $typefilter = $param->{typefilter}; - my $errors = $param->{errors} // 0; - my $source = $param->{source} // 'archive'; - my $since = $param->{since}; - my $until = $param->{until}; - my $statusfilter = { - ok => 1, - warning => 1, - error => 1, - unknown => 1, - }; + my $node = $param->{node}; + my $start = $param->{start} // 0; + my $limit = $param->{limit} // 50; + my $userfilter = $param->{userfilter}; + my $typefilter = $param->{typefilter}; + my $errors = $param->{errors} // 0; + my $source = $param->{source} // 'archive'; + my $since = $param->{since}; + my $until = $param->{until}; + my $statusfilter = { + ok => 1, + warning => 1, + error => 1, + unknown => 1, + }; - if (defined($param->{statusfilter}) && !$errors) { - $statusfilter = { - ok => 0, - warning => 0, - error => 0, - unknown => 0, - }; - for my $filter (PVE::Tools::split_list($param->{statusfilter})) { - $statusfilter->{lc($filter)} = 1 ; - } - } elsif ($errors) { - $statusfilter->{ok} = 0; - } + if (defined($param->{statusfilter}) && !$errors) { + $statusfilter = { + ok => 0, + warning => 0, + error => 0, + unknown => 0, + }; + for my $filter (PVE::Tools::split_list($param->{statusfilter})) { + $statusfilter->{ lc($filter) } = 1; + } + } elsif ($errors) { + $statusfilter->{ok} = 0; + } - my $count = 0; - my $line; + my $count = 0; + my $line; - my $auditor = $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ], 1); + my $auditor = $rpcenv->check($user, "/nodes/$node", ['Sys.Audit'], 1); - my $filter_task = sub { - my $task = shift; + my $filter_task = sub { + my $task = shift; - return 1 if $userfilter && $task->{user} !~ m/\Q$userfilter\E/i; - return 1 if !($auditor || $check_task_user->($task, $user)); + return 1 if $userfilter && $task->{user} !~ m/\Q$userfilter\E/i; + return 1 if !($auditor || $check_task_user->($task, $user)); - return 1 if $typefilter && $task->{type} ne $typefilter; + return 1 if $typefilter && $task->{type} ne $typefilter; - return 1 if $param->{vmid} && (!$task->{id} || $task->{id} ne $param->{vmid}); + return 1 if $param->{vmid} && (!$task->{id} || $task->{id} ne $param->{vmid}); - return 1 if defined($since) && $task->{starttime} < $since; - return 1 if defined($until) && $task->{starttime} > $until; + return 1 if defined($since) && $task->{starttime} < $since; + return 1 if defined($until) && $task->{starttime} > $until; - my $type = PVE::Tools::upid_normalize_status_type($task->{status}); - return 1 if !$statusfilter->{$type}; + my $type = PVE::Tools::upid_normalize_status_type($task->{status}); + return 1 if !$statusfilter->{$type}; - return 1 if $count++ < $start; - return 1 if $limit <= 0; + return 1 if $count++ < $start; + return 1 if $limit <= 0; - return 0; - }; + return 0; + }; - my $parse_line = sub { - if ($line =~ m/^(\S+)(\s([0-9A-Za-z]{8})(\s(\S.*))?)?$/) { - my $upid = $1; - my $endtime = $3; - my $status = $5; - if ((my $task = PVE::Tools::upid_decode($upid, 1))) { + my $parse_line = sub { + if ($line =~ m/^(\S+)(\s([0-9A-Za-z]{8})(\s(\S.*))?)?$/) { + my $upid = $1; + my $endtime = $3; + my $status = $5; + if ((my $task = PVE::Tools::upid_decode($upid, 1))) { - $task->{upid} = $upid; - $task->{endtime} = hex($endtime) if $endtime; - $task->{status} = $status if $status; + $task->{upid} = $upid; + $task->{endtime} = hex($endtime) if $endtime; + $task->{status} = $status if $status; - $convert_token_task->($task); - if (!$filter_task->($task)) { - push @$res, $task; - $limit--; - } - } - } - }; + $convert_token_task->($task); + if (!$filter_task->($task)) { + push @$res, $task; + $limit--; + } + } + } + }; - if ($source eq 'active' || $source eq 'all') { - my $recent_tasks = PVE::INotify::read_file('active'); - for my $task (@$recent_tasks) { - next if $task->{saved}; # archived task, already in index(.1) - if (!$filter_task->($task)) { - $task->{status} = 'RUNNING' if !$task->{status}; # otherwise it would be archived - push @$res, $task; - $limit--; - } - } - } + if ($source eq 'active' || $source eq 'all') { + my $recent_tasks = PVE::INotify::read_file('active'); + for my $task (@$recent_tasks) { + next if $task->{saved}; # archived task, already in index(.1) + if (!$filter_task->($task)) { + $task->{status} = 'RUNNING' if !$task->{status}; # otherwise it would be archived + push @$res, $task; + $limit--; + } + } + } - if ($source ne 'active') { - if (my $bw = File::ReadBackwards->new($filename)) { - while (defined ($line = $bw->readline)) { - &$parse_line(); - } - $bw->close(); - } - if (my $bw = File::ReadBackwards->new("$filename.1")) { - while (defined ($line = $bw->readline)) { - &$parse_line(); - } - $bw->close(); - } - } + if ($source ne 'active') { + if (my $bw = File::ReadBackwards->new($filename)) { + while (defined($line = $bw->readline)) { + &$parse_line(); + } + $bw->close(); + } + if (my $bw = File::ReadBackwards->new("$filename.1")) { + while (defined($line = $bw->readline)) { + &$parse_line(); + } + $bw->close(); + } + } - $rpcenv->set_result_attrib('total', $count); + $rpcenv->set_result_attrib('total', $count); - return $res; - }}); + return $res; + }, +}); __PACKAGE__->register_method({ name => 'upid_index', @@ -254,28 +259,28 @@ __PACKAGE__->register_method({ description => '', # index helper permissions => { user => 'all' }, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - upid => { type => 'string' }, - } + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + upid => { type => 'string' }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, - links => [ { rel => 'child', href => "{name}" } ], + type => 'array', + items => { + type => "object", + properties => {}, + }, + links => [{ rel => 'child', href => "{name}" }], }, code => sub { - my ($param) = @_; + my ($param) = @_; - return [ - { name => 'log' }, - { name => 'status' } - ]; - }}); + return [ + { name => 'log' }, { name => 'status' }, + ]; + }, +}); __PACKAGE__->register_method({ name => 'stop_task', @@ -283,153 +288,159 @@ __PACKAGE__->register_method({ method => 'DELETE', description => 'Stop a task.', permissions => { - description => "The user needs 'Sys.Modify' permissions on '/nodes/' if they aren't the owner of the task.", - user => 'all', + description => + "The user needs 'Sys.Modify' permissions on '/nodes/' if they aren't the owner of the task.", + user => 'all', }, protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - upid => { type => 'string' }, - } + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + upid => { type => 'string' }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1); - raise_param_exc({ upid => "unable to parse worker upid" }) if !$task; - raise_param_exc({ upid => "no such task" }) if ! -f $filename; + my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1); + raise_param_exc({ upid => "unable to parse worker upid" }) if !$task; + raise_param_exc({ upid => "no such task" }) if !-f $filename; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); - my $node = $param->{node}; + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); + my $node = $param->{node}; - $convert_token_task->($task); + $convert_token_task->($task); - if (!$check_task_user->($task, $user)) { - $rpcenv->check($user, "/nodes/$node", [ 'Sys.Modify' ]); - } + if (!$check_task_user->($task, $user)) { + $rpcenv->check($user, "/nodes/$node", ['Sys.Modify']); + } - PVE::RPCEnvironment->check_worker($param->{upid}, 1); + PVE::RPCEnvironment->check_worker($param->{upid}, 1); - return undef; - }}); + return undef; + }, +}); __PACKAGE__->register_method({ name => 'read_task_log', path => '{upid}/log', method => 'GET', permissions => { - description => "The user needs 'Sys.Audit' permissions on '/nodes/' if they aren't the owner of the task.", - user => 'all', + description => + "The user needs 'Sys.Audit' permissions on '/nodes/' if they aren't the owner of the task.", + user => 'all', }, protected => 1, download_allowed => 1, description => "Read task log.", proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - upid => { - type => 'string', - description => "The task's unique ID.", - }, - start => { - type => 'integer', - minimum => 0, - default => 0, - optional => 1, - description => "Start at this line when reading the tasklog", - }, - limit => { - type => 'integer', - minimum => 0, - default => 50, - optional => 1, - description => "The amount of lines to read from the tasklog.", - }, - download => { - type => 'boolean', - optional => 1, - description => "Whether the tasklog file should be downloaded. This parameter can't be used in conjunction with other parameters", - } - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + upid => { + type => 'string', + description => "The task's unique ID.", + }, + start => { + type => 'integer', + minimum => 0, + default => 0, + optional => 1, + description => "Start at this line when reading the tasklog", + }, + limit => { + type => 'integer', + minimum => 0, + default => 50, + optional => 1, + description => "The amount of lines to read from the tasklog.", + }, + download => { + type => 'boolean', + optional => 1, + description => + "Whether the tasklog file should be downloaded. This parameter can't be used in conjunction with other parameters", + }, + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => { - n => { - description=> "Line number", - type=> 'integer', - }, - t => { - description=> "Line text", - type => 'string', - } - } - } + type => 'array', + items => { + type => "object", + properties => { + n => { + description => "Line number", + type => 'integer', + }, + t => { + description => "Line text", + type => 'string', + }, + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1); - raise_param_exc({ upid => "unable to parse worker upid" }) if !$task; + my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1); + raise_param_exc({ upid => "unable to parse worker upid" }) if !$task; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); - my $node = $param->{node}; + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); + my $node = $param->{node}; - $convert_token_task->($task); + $convert_token_task->($task); - if (!$check_task_user->($task, $user)) { - $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]); - } + if (!$check_task_user->($task, $user)) { + $rpcenv->check($user, "/nodes/$node", ['Sys.Audit']); + } - if ($param->{download}) { - if (defined($param->{start}) || defined($param->{limit})) { - die "'download' cannot be used together with 'start' or 'limit' parameters\n"; - } - # 1024 is a practical cutoff for the size distribution of our log files. - my $use_compression = ( -s $filename ) > 1024; + if ($param->{download}) { + if (defined($param->{start}) || defined($param->{limit})) { + die "'download' cannot be used together with 'start' or 'limit' parameters\n"; + } + # 1024 is a practical cutoff for the size distribution of our log files. + my $use_compression = (-s $filename) > 1024; - my $fh; - if ($use_compression) { - open($fh, "-|", "/usr/bin/gzip", "-c", "$filename") - or die "Could not create compressed file stream for file '$filename' - $!\n"; - } else { - open($fh, '<', $filename) or die "Could not open file '$filename' - $!\n"; - } + my $fh; + if ($use_compression) { + open($fh, "-|", "/usr/bin/gzip", "-c", "$filename") + or die "Could not create compressed file stream for file '$filename' - $!\n"; + } else { + open($fh, '<', $filename) or die "Could not open file '$filename' - $!\n"; + } - my $task_time = strftime('%FT%TZ', gmtime($task->{starttime})); - my $download_name = 'task-'.$task->{node}.'-'.$task->{type}.'-'.$task_time.'.log'; + my $task_time = strftime('%FT%TZ', gmtime($task->{starttime})); + my $download_name = + 'task-' . $task->{node} . '-' . $task->{type} . '-' . $task_time . '.log'; - return { - download => { - fh => $fh, - stream => 1, - 'content-encoding' => $use_compression ? 'gzip' : undef, - 'content-type' => "text/plain", - 'content-disposition' => "attachment; filename=\"".$download_name."\"", - }, - }, - } else { - my $start = $param->{start} // 0; - my $limit = $param->{limit} // 50; + return { + download => { + fh => $fh, + stream => 1, + 'content-encoding' => $use_compression ? 'gzip' : undef, + 'content-type' => "text/plain", + 'content-disposition' => "attachment; filename=\"" . $download_name . "\"", + }, + }, + ; + } else { + my $start = $param->{start} // 0; + my $limit = $param->{limit} // 50; - my ($count, $lines) = PVE::Tools::dump_logfile($filename, $start, $limit); + my ($count, $lines) = PVE::Tools::dump_logfile($filename, $start, $limit); - $rpcenv->set_result_attrib('total', $count); - - return $lines; - } - }}); + $rpcenv->set_result_attrib('total', $count); + return $lines; + } + }, +}); my $exit_status_cache = {}; @@ -438,92 +449,94 @@ __PACKAGE__->register_method({ path => '{upid}/status', method => 'GET', permissions => { - description => "The user needs 'Sys.Audit' permissions on '/nodes/' if they are not the owner of the task.", - user => 'all', + description => + "The user needs 'Sys.Audit' permissions on '/nodes/' if they are not the owner of the task.", + user => 'all', }, protected => 1, description => "Read task status.", proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - upid => { - type => 'string', - description => "The task's unique ID.", - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + upid => { + type => 'string', + description => "The task's unique ID.", + }, + }, }, returns => { - type => "object", - properties => { - pid => { - type => 'integer' - }, - status => { - type => 'string', enum => ['running', 'stopped'], - }, - type => { - type => 'string', - }, - id => { - type => 'string', - }, - user => { - type => 'string', - }, - exitstatus => { - type => 'string', - optional => 1, - }, - upid => { - type => 'string', - }, - starttime => { - type => 'integer', - }, - pstart => { - type => 'integer', - }, - node => { - type => 'string', - }, - }, + type => "object", + properties => { + pid => { + type => 'integer', + }, + status => { + type => 'string', + enum => ['running', 'stopped'], + }, + type => { + type => 'string', + }, + id => { + type => 'string', + }, + user => { + type => 'string', + }, + exitstatus => { + type => 'string', + optional => 1, + }, + upid => { + type => 'string', + }, + starttime => { + type => 'integer', + }, + pstart => { + type => 'integer', + }, + node => { + type => 'string', + }, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1); - raise_param_exc({ upid => "unable to parse worker upid" }) if !$task; - raise_param_exc({ upid => "no such task" }) if ! -f $filename; + my ($task, $filename) = PVE::Tools::upid_decode($param->{upid}, 1); + raise_param_exc({ upid => "unable to parse worker upid" }) if !$task; + raise_param_exc({ upid => "no such task" }) if !-f $filename; - my $lines = []; + my $lines = []; - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); - my $node = $param->{node}; + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); + my $node = $param->{node}; - $convert_token_task->($task); + $convert_token_task->($task); - if (!$check_task_user->($task, $user)) { - $rpcenv->check($user, "/nodes/$node", [ 'Sys.Audit' ]); - } + if (!$check_task_user->($task, $user)) { + $rpcenv->check($user, "/nodes/$node", ['Sys.Audit']); + } - my $pstart = PVE::ProcFSTools::read_proc_starttime($task->{pid}); - $task->{status} = ($pstart && ($pstart == $task->{pstart})) ? - 'running' : 'stopped'; + my $pstart = PVE::ProcFSTools::read_proc_starttime($task->{pid}); + $task->{status} = ($pstart && ($pstart == $task->{pstart})) ? 'running' : 'stopped'; - $task->{upid} = $param->{upid}; # include upid + $task->{upid} = $param->{upid}; # include upid - if ($task->{status} eq 'stopped') { - if (!defined($exit_status_cache->{$task->{upid}})) { - $exit_status_cache->{$task->{upid}} = - PVE::Tools::upid_read_status($task->{upid}); - } - $task->{exitstatus} = $exit_status_cache->{$task->{upid}}; - } + if ($task->{status} eq 'stopped') { + if (!defined($exit_status_cache->{ $task->{upid} })) { + $exit_status_cache->{ $task->{upid} } = + PVE::Tools::upid_read_status($task->{upid}); + } + $task->{exitstatus} = $exit_status_cache->{ $task->{upid} }; + } - return $task; - }}); + return $task; + }, +}); 1; diff --git a/PVE/API2/VZDump.pm b/PVE/API2/VZDump.pm index 6fcac5ba..de48e108 100644 --- a/PVE/API2/VZDump.pm +++ b/PVE/API2/VZDump.pm @@ -28,282 +28,287 @@ my sub assert_param_permission_vzdump { PVE::API2::Backup::assert_param_permission_common($rpcenv, $user, $param); if (defined($param->{maxfiles}) || defined($param->{'prune-backups'})) { - if (my $storeid = PVE::VZDump::get_storage_param($param)) { - $rpcenv->check($user, "/storage/$storeid", [ 'Datastore.Allocate' ]); - } + if (my $storeid = PVE::VZDump::get_storage_param($param)) { + $rpcenv->check($user, "/storage/$storeid", ['Datastore.Allocate']); + } } } -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'vzdump', path => '', method => 'POST', description => "Create backup.", permissions => { - description => "The user needs 'VM.Backup' permissions on any VM, and " - ."'Datastore.AllocateSpace' on the backup storage (and fleecing storage when fleecing " - ."is used). The 'tmpdir', 'dumpdir', 'script' and 'job-id' parameters are restricted " - ."to the 'root\@pam' user. The 'maxfiles' and 'prune-backups' settings require " - ."'Datastore.Allocate' on the backup storage. The 'bwlimit', 'performance' and " - ."'ionice' parameters require 'Sys.Modify' on '/'.", - user => 'all', + description => "The user needs 'VM.Backup' permissions on any VM, and " + . "'Datastore.AllocateSpace' on the backup storage (and fleecing storage when fleecing " + . "is used). The 'tmpdir', 'dumpdir', 'script' and 'job-id' parameters are restricted " + . "to the 'root\@pam' user. The 'maxfiles' and 'prune-backups' settings require " + . "'Datastore.Allocate' on the backup storage. The 'bwlimit', 'performance' and " + . "'ionice' parameters require 'Sys.Modify' on '/'.", + user => 'all', }, protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => PVE::VZDump::Common::json_config_properties({ - 'job-id' => get_standard_option('pve-backup-jobid', { - description => "The ID of the backup job. If set, the 'backup-job' metadata field" - . " of the backup notification will be set to this value. Only root\@pam" - . " can set this parameter.", - optional => 1, - }), - stdout => { - type => 'boolean', - description => "Write tar to stdout, not to a file.", - optional => 1, - }, + additionalProperties => 0, + properties => PVE::VZDump::Common::json_config_properties({ + 'job-id' => get_standard_option( + 'pve-backup-jobid', + { + description => + "The ID of the backup job. If set, the 'backup-job' metadata field" + . " of the backup notification will be set to this value. Only root\@pam" + . " can set this parameter.", + optional => 1, + }, + ), + stdout => { + type => 'boolean', + description => "Write tar to stdout, not to a file.", + optional => 1, + }, }), }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $user = $rpcenv->get_user(); - my $nodename = PVE::INotify::nodename(); + my $nodename = PVE::INotify::nodename(); - if ($rpcenv->{type} ne 'cli') { - raise_param_exc({ node => "option is only allowed on the command line interface."}) - if $param->{node} && $param->{node} ne $nodename; + if ($rpcenv->{type} ne 'cli') { + raise_param_exc({ node => "option is only allowed on the command line interface." }) + if $param->{node} && $param->{node} ne $nodename; - raise_param_exc({ stdout => "option is only allowed on the command line interface."}) - if $param->{stdout}; - } + raise_param_exc({ + stdout => "option is only allowed on the command line interface." }) + if $param->{stdout}; + } - assert_param_permission_vzdump($rpcenv, $user, $param); + assert_param_permission_vzdump($rpcenv, $user, $param); - PVE::VZDump::verify_vzdump_parameters($param, 1); + PVE::VZDump::verify_vzdump_parameters($param, 1); - # silent exit if we run on wrong node - return 'OK' if $param->{node} && $param->{node} ne $nodename; + # silent exit if we run on wrong node + return 'OK' if $param->{node} && $param->{node} ne $nodename; - my $cmdline = PVE::VZDump::Common::command_line($param); + my $cmdline = PVE::VZDump::Common::command_line($param); - my $vmids_per_node = PVE::VZDump::get_included_guests($param); + my $vmids_per_node = PVE::VZDump::get_included_guests($param); - my $local_vmids = delete $vmids_per_node->{$nodename} // []; + my $local_vmids = delete $vmids_per_node->{$nodename} // []; - # include IDs for deleted guests, and visibly fail later - my $orphaned_vmids = delete $vmids_per_node->{''} // []; - push @{$local_vmids}, @{$orphaned_vmids}; + # include IDs for deleted guests, and visibly fail later + my $orphaned_vmids = delete $vmids_per_node->{''} // []; + push @{$local_vmids}, @{$orphaned_vmids}; - my $skiplist = [ map { @$_ } values $vmids_per_node->%* ]; + my $skiplist = [map { @$_ } values $vmids_per_node->%*]; - if($param->{stop}){ - PVE::VZDump::stop_running_backups(); - return 'OK' if !scalar(@{$local_vmids}); - } + if ($param->{stop}) { + PVE::VZDump::stop_running_backups(); + return 'OK' if !scalar(@{$local_vmids}); + } - # silent exit if specified VMs run on other nodes - return "OK" if !scalar(@{$local_vmids}) && !$param->{all}; + # silent exit if specified VMs run on other nodes + return "OK" if !scalar(@{$local_vmids}) && !$param->{all}; - PVE::VZDump::parse_mailto_exclude_path($param); + PVE::VZDump::parse_mailto_exclude_path($param); - die "you can only backup a single VM with option --stdout\n" - if $param->{stdout} && scalar(@{$local_vmids}) != 1; + die "you can only backup a single VM with option --stdout\n" + if $param->{stdout} && scalar(@{$local_vmids}) != 1; - if (my $storeid = PVE::VZDump::get_storage_param($param)) { - $rpcenv->check($user, "/storage/$storeid", [ 'Datastore.AllocateSpace' ]); - } + if (my $storeid = PVE::VZDump::get_storage_param($param)) { + $rpcenv->check($user, "/storage/$storeid", ['Datastore.AllocateSpace']); + } - my $worker = sub { - my $upid = shift; + my $worker = sub { + my $upid = shift; - $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub { - die "interrupted by signal\n"; - }; + $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub { + die "interrupted by signal\n"; + }; - $param->{vmids} = $local_vmids; - my $vzdump = PVE::VZDump->new($cmdline, $param, $skiplist); + $param->{vmids} = $local_vmids; + my $vzdump = PVE::VZDump->new($cmdline, $param, $skiplist); - my $LOCK_FH = eval { - $vzdump->getlock($upid); # only one process allowed - }; - if (my $err = $@) { - $vzdump->send_notification([], 0, $err); - exit(-1); - } + my $LOCK_FH = eval { + $vzdump->getlock($upid); # only one process allowed + }; + if (my $err = $@) { + $vzdump->send_notification([], 0, $err); + exit(-1); + } - if (defined($param->{ionice})) { - if ($param->{ionice} > 7) { - PVE::VZDump::run_command(undef, "ionice -c3 -p $$"); - } else { - PVE::VZDump::run_command(undef, "ionice -c2 -n$param->{ionice} -p $$"); - } - } - $vzdump->exec_backup($rpcenv, $user); + if (defined($param->{ionice})) { + if ($param->{ionice} > 7) { + PVE::VZDump::run_command(undef, "ionice -c3 -p $$"); + } else { + PVE::VZDump::run_command(undef, "ionice -c2 -n$param->{ionice} -p $$"); + } + } + $vzdump->exec_backup($rpcenv, $user); - close($LOCK_FH); - }; + close($LOCK_FH); + }; - open STDOUT, '>/dev/null' if $param->{quiet} && !$param->{stdout}; - open STDERR, '>/dev/null' if $param->{quiet}; + open STDOUT, '>/dev/null' if $param->{quiet} && !$param->{stdout}; + open STDERR, '>/dev/null' if $param->{quiet}; - if ($rpcenv->{type} eq 'cli') { - if ($param->{stdout}) { + if ($rpcenv->{type} eq 'cli') { + if ($param->{stdout}) { - open my $saved_stdout, ">&STDOUT" - || die "can't dup STDOUT: $!\n"; + open my $saved_stdout, ">&STDOUT" + || die "can't dup STDOUT: $!\n"; - open STDOUT, '>&STDERR' || - die "unable to redirect STDOUT: $!\n"; + open STDOUT, '>&STDERR' + || die "unable to redirect STDOUT: $!\n"; - $param->{stdout} = $saved_stdout; - } - } + $param->{stdout} = $saved_stdout; + } + } - my $taskid; - $taskid = $local_vmids->[0] if scalar(@{$local_vmids}) == 1; + my $taskid; + $taskid = $local_vmids->[0] if scalar(@{$local_vmids}) == 1; - return $rpcenv->fork_worker('vzdump', $taskid, $user, $worker); - }}); + return $rpcenv->fork_worker('vzdump', $taskid, $user, $worker); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'defaults', path => 'defaults', method => 'GET', description => "Get the currently configured vzdump defaults.", permissions => { - description => "The user needs 'Datastore.Audit' or 'Datastore.AllocateSpace' " . - "permissions for the specified storage (or default storage if none specified). Some " . - "properties are only returned when the user has 'Sys.Audit' permissions for the node.", - user => 'all', + description => "The user needs 'Datastore.Audit' or 'Datastore.AllocateSpace' " + . "permissions for the specified storage (or default storage if none specified). Some " + . "properties are only returned when the user has 'Sys.Audit' permissions for the node.", + user => 'all', }, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - storage => get_standard_option('pve-storage-id', { optional => 1 }), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + storage => get_standard_option('pve-storage-id', { optional => 1 }), + }, }, returns => { - type => 'object', - additionalProperties => 0, - properties => PVE::VZDump::Common::json_config_properties(), + type => 'object', + additionalProperties => 0, + properties => PVE::VZDump::Common::json_config_properties(), }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $node = extract_param($param, 'node'); - my $storage = extract_param($param, 'storage'); + my $node = extract_param($param, 'node'); + my $storage = extract_param($param, 'storage'); - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $res = PVE::VZDump::read_vzdump_defaults(); + my $res = PVE::VZDump::read_vzdump_defaults(); - $res->{storage} = $storage if defined($storage); + $res->{storage} = $storage if defined($storage); - if (!defined($res->{dumpdir}) && !defined($res->{storage})) { - $res->{storage} = 'local'; - } + if (!defined($res->{dumpdir}) && !defined($res->{storage})) { + $res->{storage} = 'local'; + } - if (defined($res->{storage})) { - $rpcenv->check_any( - $authuser, - "/storage/$res->{storage}", - ['Datastore.Audit', 'Datastore.AllocateSpace'], - ); + if (defined($res->{storage})) { + $rpcenv->check_any( + $authuser, + "/storage/$res->{storage}", + ['Datastore.Audit', 'Datastore.AllocateSpace'], + ); - my $info = PVE::VZDump::storage_info($res->{storage}); - for my $key (qw(dumpdir prune-backups)) { - $res->{$key} = $info->{$key} if defined($info->{$key}); - } - } + my $info = PVE::VZDump::storage_info($res->{storage}); + for my $key (qw(dumpdir prune-backups)) { + $res->{$key} = $info->{$key} if defined($info->{$key}); + } + } - if (defined($res->{'prune-backups'})) { - $res->{'prune-backups'} = PVE::JSONSchema::print_property_string( - $res->{'prune-backups'}, - 'prune-backups', - ); - } + if (defined($res->{'prune-backups'})) { + $res->{'prune-backups'} = PVE::JSONSchema::print_property_string( + $res->{'prune-backups'}, 'prune-backups', + ); + } - $res->{mailto} = join(",", @{$res->{mailto}}) - if defined($res->{mailto}); + $res->{mailto} = join(",", @{ $res->{mailto} }) + if defined($res->{mailto}); - $res->{'exclude-path'} = join(",", @{$res->{'exclude-path'}}) - if defined($res->{'exclude-path'}); + $res->{'exclude-path'} = join(",", @{ $res->{'exclude-path'} }) + if defined($res->{'exclude-path'}); - # normal backup users don't need to know these - if (!$rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'], 1)) { - delete $res->{mailto}; - delete $res->{tmpdir}; - delete $res->{dumpdir}; - delete $res->{script}; - delete $res->{ionice}; - } + # normal backup users don't need to know these + if (!$rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'], 1)) { + delete $res->{mailto}; + delete $res->{tmpdir}; + delete $res->{dumpdir}; + delete $res->{script}; + delete $res->{ionice}; + } - my $pool = $res->{pool}; - if (defined($pool) && - !$rpcenv->check($authuser, "/pool/$pool", ['Pool.Audit'], 1)) { - delete $res->{pool}; - } + my $pool = $res->{pool}; + if ( + defined($pool) + && !$rpcenv->check($authuser, "/pool/$pool", ['Pool.Audit'], 1) + ) { + delete $res->{pool}; + } - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'extractconfig', path => 'extractconfig', method => 'GET', description => "Extract configuration from vzdump backup archive.", permissions => { - description => "The user needs 'VM.Backup' permissions on the backed up guest ID, and 'Datastore.AllocateSpace' on the backup storage.", - user => 'all', + description => + "The user needs 'VM.Backup' permissions on the backed up guest ID, and 'Datastore.AllocateSpace' on the backup storage.", + user => 'all', }, protected => 1, proxyto => 'node', parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - volume => { - description => "Volume identifier", - type => 'string', - completion => \&PVE::Storage::complete_volume, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + volume => { + description => "Volume identifier", + type => 'string', + completion => \&PVE::Storage::complete_volume, + }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $volume = extract_param($param, 'volume'); + my $volume = extract_param($param, 'volume'); - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); - my $storage_cfg = PVE::Storage::config(); - PVE::Storage::check_volume_access( - $rpcenv, - $authuser, - $storage_cfg, - undef, - $volume, - 'backup', - ); + my $storage_cfg = PVE::Storage::config(); + PVE::Storage::check_volume_access( + $rpcenv, $authuser, $storage_cfg, undef, $volume, 'backup', + ); - if (PVE::Storage::parse_volume_id($volume, 1)) { - my (undef, undef, $ownervm) = PVE::Storage::parse_volname($storage_cfg, $volume); - $rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']); - } + if (PVE::Storage::parse_volume_id($volume, 1)) { + my (undef, undef, $ownervm) = PVE::Storage::parse_volname($storage_cfg, $volume); + $rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']); + } - return PVE::Storage::extract_vzdump_config($storage_cfg, $volume); - }}); + return PVE::Storage::extract_vzdump_config($storage_cfg, $volume); + }, +}); 1; diff --git a/PVE/API2Tools.pm b/PVE/API2Tools.pm index a56eb732..d6154925 100644 --- a/PVE/API2Tools.pm +++ b/PVE/API2Tools.pm @@ -25,16 +25,18 @@ sub get_hwaddress { my $fn = '/etc/ssh/ssh_host_rsa_key.pub'; my $st = stat($fn); - if (defined($hwaddress) - && $hwaddress_st->{mtime} == $st->mtime - && $hwaddress_st->{ino} == $st->ino - && $hwaddress_st->{dev} == $st->dev) { - return $hwaddress; + if ( + defined($hwaddress) + && $hwaddress_st->{mtime} == $st->mtime + && $hwaddress_st->{ino} == $st->ino + && $hwaddress_st->{dev} == $st->dev + ) { + return $hwaddress; } my $sshkey = PVE::Tools::file_get_contents($fn); $hwaddress = uc(md5_hex($sshkey)); - $hwaddress_st->@{'mtime', 'ino', 'dev'} = ($st->mtime, $st->ino, $st->dev); + $hwaddress_st->@{ 'mtime', 'ino', 'dev' } = ($st->mtime, $st->ino, $st->dev); return $hwaddress; } @@ -43,35 +45,40 @@ sub extract_node_stats { my ($node, $members, $rrd, $exclude_stats) = @_; my $entry = { - id => "node/$node", - node => $node, - type => "node", - status => 'unknown', + id => "node/$node", + node => $node, + type => "node", + status => 'unknown', }; if (my $d = $rrd->{"pve2-node/$node"}) { - if (!$members || # no cluster - ($members->{$node} && $members->{$node}->{online})) { - if (!$exclude_stats) { - $entry->{uptime} = ($d->[0] || 0) + 0; - $entry->{cpu} = ($d->[5] || 0) + 0; - $entry->{mem} = ($d->[8] || 0) + 0; - $entry->{disk} = ($d->[12] || 0) + 0; - } - $entry->{status} = 'online'; - } - $entry->{level} = $d->[1]; - if (!$exclude_stats) { - $entry->{maxcpu} = ($d->[4] || 0) + 0; - $entry->{maxmem} = ($d->[7] || 0) + 0; - $entry->{maxdisk} = ($d->[11] || 0) + 0; - } + if ( + !$members || # no cluster + ($members->{$node} && $members->{$node}->{online}) + ) { + if (!$exclude_stats) { + $entry->{uptime} = ($d->[0] || 0) + 0; + $entry->{cpu} = ($d->[5] || 0) + 0; + $entry->{mem} = ($d->[8] || 0) + 0; + $entry->{disk} = ($d->[12] || 0) + 0; + } + $entry->{status} = 'online'; + } + $entry->{level} = $d->[1]; + if (!$exclude_stats) { + $entry->{maxcpu} = ($d->[4] || 0) + 0; + $entry->{maxmem} = ($d->[7] || 0) + 0; + $entry->{maxdisk} = ($d->[11] || 0) + 0; + } } - if ($members && $members->{$node} && - !$members->{$node}->{online}) { - $entry->{status} = 'offline'; + if ( + $members + && $members->{$node} + && !$members->{$node}->{online} + ) { + $entry->{status} = 'offline'; } return $entry; @@ -81,49 +88,49 @@ sub extract_vm_stats { my ($vmid, $data, $rrd) = @_; my $entry = { - id => "$data->{type}/$vmid", - vmid => $vmid + 0, - node => $data->{node}, - type => $data->{type}, - status => 'unknown', + id => "$data->{type}/$vmid", + vmid => $vmid + 0, + node => $data->{node}, + type => $data->{type}, + status => 'unknown', }; my $d; if ($d = $rrd->{"pve2-vm/$vmid"}) { - $entry->{uptime} = ($d->[0] || 0) + 0; - $entry->{name} = $d->[1]; - $entry->{status} = $entry->{uptime} ? 'running' : 'stopped'; - $entry->{maxcpu} = ($d->[3] || 0) + 0; - $entry->{cpu} = ($d->[4] || 0) + 0; - $entry->{maxmem} = ($d->[5] || 0) + 0; - $entry->{mem} = ($d->[6] || 0) + 0; - $entry->{maxdisk} = ($d->[7] || 0) + 0; - $entry->{disk} = ($d->[8] || 0) + 0; - $entry->{netin} = ($d->[9] || 0) + 0; - $entry->{netout} = ($d->[10] || 0) + 0; - $entry->{diskread} = ($d->[11] || 0) + 0; - $entry->{diskwrite} = ($d->[12] || 0) + 0; + $entry->{uptime} = ($d->[0] || 0) + 0; + $entry->{name} = $d->[1]; + $entry->{status} = $entry->{uptime} ? 'running' : 'stopped'; + $entry->{maxcpu} = ($d->[3] || 0) + 0; + $entry->{cpu} = ($d->[4] || 0) + 0; + $entry->{maxmem} = ($d->[5] || 0) + 0; + $entry->{mem} = ($d->[6] || 0) + 0; + $entry->{maxdisk} = ($d->[7] || 0) + 0; + $entry->{disk} = ($d->[8] || 0) + 0; + $entry->{netin} = ($d->[9] || 0) + 0; + $entry->{netout} = ($d->[10] || 0) + 0; + $entry->{diskread} = ($d->[11] || 0) + 0; + $entry->{diskwrite} = ($d->[12] || 0) + 0; } elsif ($d = $rrd->{"pve2.3-vm/$vmid"}) { - $entry->{uptime} = ($d->[0] || 0) + 0; - $entry->{name} = $d->[1]; - $entry->{status} = $d->[2]; - $entry->{template} = $d->[3] + 0; + $entry->{uptime} = ($d->[0] || 0) + 0; + $entry->{name} = $d->[1]; + $entry->{status} = $d->[2]; + $entry->{template} = $d->[3] + 0; - $entry->{maxcpu} = ($d->[5] || 0) + 0; - $entry->{cpu} = ($d->[6] || 0) + 0; - $entry->{maxmem} = ($d->[7] || 0) + 0; - $entry->{mem} = ($d->[8] || 0) + 0; - $entry->{maxdisk} = ($d->[9] || 0) + 0; - $entry->{disk} = ($d->[10] || 0) + 0; - $entry->{netin} = ($d->[11] || 0) + 0; - $entry->{netout} = ($d->[12] || 0) + 0; - $entry->{diskread} = ($d->[13] || 0) + 0; - $entry->{diskwrite} = ($d->[14] || 0) + 0; - }; + $entry->{maxcpu} = ($d->[5] || 0) + 0; + $entry->{cpu} = ($d->[6] || 0) + 0; + $entry->{maxmem} = ($d->[7] || 0) + 0; + $entry->{mem} = ($d->[8] || 0) + 0; + $entry->{maxdisk} = ($d->[9] || 0) + 0; + $entry->{disk} = ($d->[10] || 0) + 0; + $entry->{netin} = ($d->[11] || 0) + 0; + $entry->{netout} = ($d->[12] || 0) + 0; + $entry->{diskread} = ($d->[13] || 0) + 0; + $entry->{diskwrite} = ($d->[14] || 0) + 0; + } return $entry; } @@ -134,20 +141,20 @@ sub extract_storage_stats { my $content = PVE::Storage::Plugin::content_hash_to_string($scfg->{content}); my $entry = { - id => "storage/$node/$storeid", - storage => $storeid, - node => $node, - type => 'storage', - plugintype => $scfg->{type}, - status => 'unknown', - shared => $scfg->{shared} || 0, - content => $content, + id => "storage/$node/$storeid", + storage => $storeid, + node => $node, + type => 'storage', + plugintype => $scfg->{type}, + status => 'unknown', + shared => $scfg->{shared} || 0, + content => $content, }; if (my $d = $rrd->{"pve2-storage/$node/$storeid"}) { - $entry->{maxdisk} = ($d->[1] || 0) + 0; - $entry->{disk} = ($d->[2] || 0) + 0; - $entry->{status} = 'available'; + $entry->{maxdisk} = ($d->[1] || 0) + 0; + $entry->{disk} = ($d->[2] || 0) + 0; + $entry->{status} = 'available'; } return $entry; @@ -165,7 +172,7 @@ sub parse_http_proxy { my ($username, $password); if (defined(my $p_auth = $uri->userinfo())) { - ($username, $password) = map URI::Escape::uri_unescape($_), split(":", $p_auth, 2); + ($username, $password) = map URI::Escape::uri_unescape($_), split(":", $p_auth, 2); } return ("$host:$port", $username, $password); @@ -183,13 +190,23 @@ sub run_spiceterm { my $port = PVE::Tools::next_spice_port($family); my ($ticket, undef, $remote_viewer_config) = - PVE::AccessControl::remote_viewer_config($authuser, $vmid, $node, $proxy, $title, $port); + PVE::AccessControl::remote_viewer_config($authuser, $vmid, $node, $proxy, $title, $port); my $timeout = 40; - my $cmd = ['/usr/bin/spiceterm', '--port', $port, '--addr', 'localhost', - '--timeout', $timeout, '--authpath', $authpath, - '--permissions', $permissions]; + my $cmd = [ + '/usr/bin/spiceterm', + '--port', + $port, + '--addr', + 'localhost', + '--timeout', + $timeout, + '--authpath', + $authpath, + '--permissions', + $permissions, + ]; my $dcconf = PVE::Cluster::cfs_read_file('datacenter.cfg'); push @$cmd, '--keymap', $dcconf->{keyboard} if $dcconf->{keyboard}; @@ -197,34 +214,40 @@ sub run_spiceterm { push @$cmd, '--', @$shcmd; my $realcmd = sub { - my $upid = shift; + my $upid = shift; - syslog ('info', "starting spiceterm $upid - $title\n"); + syslog('info', "starting spiceterm $upid - $title\n"); - my $cmdstr = join (' ', @$cmd); - syslog ('info', "launch command: $cmdstr"); + my $cmdstr = join(' ', @$cmd); + syslog('info', "launch command: $cmdstr"); - eval { - foreach my $k (keys %ENV) { - next if $k eq 'PATH' || $k eq 'TERM' || $k eq 'USER' || $k eq 'HOME' || $k eq 'LANG' || $k eq 'LANGUAGE' ; - delete $ENV{$k}; - } - $ENV{PWD} = '/'; - $ENV{SPICE_TICKET} = $ticket; + eval { + foreach my $k (keys %ENV) { + next + if $k eq 'PATH' + || $k eq 'TERM' + || $k eq 'USER' + || $k eq 'HOME' + || $k eq 'LANG' + || $k eq 'LANGUAGE'; + delete $ENV{$k}; + } + $ENV{PWD} = '/'; + $ENV{SPICE_TICKET} = $ticket; - PVE::Tools::run_command($cmd, errmsg => 'spiceterm failed\n', keeplocale => 1); - }; - if (my $err = $@) { - syslog ('err', $err); - } + PVE::Tools::run_command($cmd, errmsg => 'spiceterm failed\n', keeplocale => 1); + }; + if (my $err = $@) { + syslog('err', $err); + } - return; + return; }; if ($vmid) { - $rpcenv->fork_worker('spiceproxy', $vmid, $authuser, $realcmd); + $rpcenv->fork_worker('spiceproxy', $vmid, $authuser, $realcmd); } else { - $rpcenv->fork_worker('spiceshell', undef, $authuser, $realcmd); + $rpcenv->fork_worker('spiceshell', undef, $authuser, $realcmd); } PVE::Tools::wait_for_vnc_port($port); @@ -237,13 +260,13 @@ sub resolve_proxyto { my $node; if ($proxyto_callback) { - $node = $proxyto_callback->($rpcenv, $proxyto, $uri_param); - die "internal error - proxyto_callback returned nothing\n" - if !$node; + $node = $proxyto_callback->($rpcenv, $proxyto, $uri_param); + die "internal error - proxyto_callback returned nothing\n" + if !$node; } else { - $node = $uri_param->{$proxyto}; - raise_param_exc({ $proxyto => "proxyto parameter does not exist"}) - if !$node; + $node = $uri_param->{$proxyto}; + raise_param_exc({ $proxyto => "proxyto parameter does not exist" }) + if !$node; } return $node; } @@ -260,7 +283,7 @@ sub get_resource_pool_guest_members { die "pool '$pool' does not exist\n" if !$data; - my $pool_members = [ grep { $idlist->{$_} } keys %{$data->{vms}} ]; + my $pool_members = [grep { $idlist->{$_} } keys %{ $data->{vms} }]; return $pool_members; } diff --git a/PVE/APLInfo.pm b/PVE/APLInfo.pm index 9ea20678..4a5e89de 100644 --- a/PVE/APLInfo.pm +++ b/PVE/APLInfo.pm @@ -20,10 +20,10 @@ sub logmsg { chomp $msg; - my $tstr = strftime ("%F %H:%M:%S", localtime); + my $tstr = strftime("%F %H:%M:%S", localtime); - foreach my $line (split (/\n/, $msg)) { - print $logfd "$tstr $line\n"; + foreach my $line (split(/\n/, $msg)) { + print $logfd "$tstr $line\n"; } } @@ -33,84 +33,89 @@ sub read_aplinfo_from_fh { local $/ = ""; while (my $rec = <$fh>) { - chomp $rec; + chomp $rec; - my $res = {}; + my $res = {}; - while ($rec) { + while ($rec) { - if ($rec =~ s/^Description:\s*([^\n]*)(\n\s+.*)*$//si) { - $res->{headline} = $1; - my $long = $2 || ''; - $long =~ s/\n\s+/ /g; - $long =~ s/^\s+//g; - $long =~ s/\s+$//g; - $res->{description} = $long; - } elsif ($rec =~ s/^Version:\s*(.*\S)\s*\n//i) { - my $version = $1; - if ($version =~ m/^(\d[a-zA-Z0-9\.\+\-\:\~]*)(-(\d+))?$/) { - $res->{version} = $version; - } else { - my $msg = "unable to parse appliance record: version = '$version'\n"; - $update ? die $msg : warn $msg; - } - } elsif ($rec =~ s/^Type:\s*(.*\S)\s*\n//i) { - my $type = $1; - if ($type =~ m/^(openvz|lxc)$/) { - $res->{type} = $type; - } else { - my $msg = "unable to parse appliance record: unknown type '$type'\n"; - $update ? die $msg : warn $msg; - } - } elsif ($rec =~ s/^([^:]+):\s*(.*\S)\s*\n//) { - $res->{lc $1} = $2; - } else { - my $msg = "unable to parse appliance record: $rec\n"; - $update ? die $msg : warn $msg; - $res = {}; - last; - } - } + if ($rec =~ s/^Description:\s*([^\n]*)(\n\s+.*)*$//si) { + $res->{headline} = $1; + my $long = $2 || ''; + $long =~ s/\n\s+/ /g; + $long =~ s/^\s+//g; + $long =~ s/\s+$//g; + $res->{description} = $long; + } elsif ($rec =~ s/^Version:\s*(.*\S)\s*\n//i) { + my $version = $1; + if ($version =~ m/^(\d[a-zA-Z0-9\.\+\-\:\~]*)(-(\d+))?$/) { + $res->{version} = $version; + } else { + my $msg = "unable to parse appliance record: version = '$version'\n"; + $update ? die $msg : warn $msg; + } + } elsif ($rec =~ s/^Type:\s*(.*\S)\s*\n//i) { + my $type = $1; + if ($type =~ m/^(openvz|lxc)$/) { + $res->{type} = $type; + } else { + my $msg = "unable to parse appliance record: unknown type '$type'\n"; + $update ? die $msg : warn $msg; + } + } elsif ($rec =~ s/^([^:]+):\s*(.*\S)\s*\n//) { + $res->{ lc $1 } = $2; + } else { + my $msg = "unable to parse appliance record: $rec\n"; + $update ? die $msg : warn $msg; + $res = {}; + last; + } + } - if ($res->{'package'} eq 'pve-web-news' && $res->{description}) { - $list->{'all'}->{$res->{'package'}} = $res; - next; - } + if ($res->{'package'} eq 'pve-web-news' && $res->{description}) { + $list->{'all'}->{ $res->{'package'} } = $res; + next; + } - $res->{section} = 'unknown' if !$res->{section}; + $res->{section} = 'unknown' if !$res->{section}; - if ($res->{'package'} && $res->{type} && $res->{os} && $res->{version} && - $res->{infopage}) { - my $template; - if ($res->{location}) { - $template = $res->{location}; - $template =~ s|.*/([^/]+$PVE::Storage::VZTMPL_EXT_RE_1)$|$1|; - if ($res->{location} !~ m|^([a-zA-Z]+)\://|) { - # relative location (no http:// prefix) - $res->{location} = "$source/$res->{location}"; - } - } else { - my $arch = $res->{architecture} || 'i386'; - $template = "$res->{os}-$res->{package}_$res->{version}_$arch.tar.gz"; - $template =~ s/$res->{os}-$res->{os}-/$res->{os}-/; - $res->{location} = "$source/$res->{section}/$template"; - } - $res->{source} = $source; - $res->{template} = $template; - $list->{$res->{section}}->{$template} = $res; - $list->{'all'}->{$template} = $res; - } else { - my $msg = "found incomplete appliance records\n"; - $update ? die $msg : warn $msg; - } + if ( + $res->{'package'} + && $res->{type} + && $res->{os} + && $res->{version} + && $res->{infopage} + ) { + my $template; + if ($res->{location}) { + $template = $res->{location}; + $template =~ s|.*/([^/]+$PVE::Storage::VZTMPL_EXT_RE_1)$|$1|; + if ($res->{location} !~ m|^([a-zA-Z]+)\://|) { + # relative location (no http:// prefix) + $res->{location} = "$source/$res->{location}"; + } + } else { + my $arch = $res->{architecture} || 'i386'; + $template = "$res->{os}-$res->{package}_$res->{version}_$arch.tar.gz"; + $template =~ s/$res->{os}-$res->{os}-/$res->{os}-/; + $res->{location} = "$source/$res->{section}/$template"; + } + $res->{source} = $source; + $res->{template} = $template; + $list->{ $res->{section} }->{$template} = $res; + $list->{'all'}->{$template} = $res; + } else { + my $msg = "found incomplete appliance records\n"; + $update ? die $msg : warn $msg; + } } } sub read_aplinfo { my ($filename, $list, $source, $update) = @_; - my $fh = IO::File->new("<$filename") || - die "unable to open file '$filename' - $!\n"; + my $fh = IO::File->new("<$filename") + || die "unable to open file '$filename' - $!\n"; eval { read_aplinfo_from_fh($fh, $list, $source, $update); }; my $err = $@; @@ -127,15 +132,15 @@ sub url_get { my $req = HTTP::Request->new(GET => $url); - logmsg ($logfh, "start download $url"); + logmsg($logfh, "start download $url"); my $res = $ua->request($req, $file); if ($res->is_success) { - logmsg ($logfh, "download finished: " . $res->status_line); - return 0; + logmsg($logfh, "download finished: " . $res->status_line); + return 0; } - logmsg ($logfh, "download failed: " . $res->status_line); + logmsg($logfh, "download failed: " . $res->status_line); return 1; } @@ -153,35 +158,33 @@ sub download_aplinfo { eval { - if (url_get($ua, $aplsigurl, $sigfn, $logfd) != 0) { - die "update failed - no signature file '$sigfn'\n"; - } + if (url_get($ua, $aplsigurl, $sigfn, $logfd) != 0) { + die "update failed - no signature file '$sigfn'\n"; + } - if (url_get($ua, $aplsrcurl, $tmpgz, $logfd) != 0) { - die "update failed - no data file '$aplsrcurl'\n"; - } + if (url_get($ua, $aplsrcurl, $tmpgz, $logfd) != 0) { + die "update failed - no data file '$aplsrcurl'\n"; + } - eval { run_command(["gunzip", "-f", $tmpgz]) }; - die "update failed: unable to unpack '$tmpgz'\n" if $@; + eval { run_command(["gunzip", "-f", $tmpgz]) }; + die "update failed: unable to unpack '$tmpgz'\n" if $@; - # verify signature - my $trustedkeyring = "/usr/share/doc/pve-manager/trustedkeys.gpg"; - my $cmd = "/usr/bin/gpgv -q --keyring $trustedkeyring $sigfn $tmp"; + # verify signature + my $trustedkeyring = "/usr/share/doc/pve-manager/trustedkeys.gpg"; + my $cmd = "/usr/bin/gpgv -q --keyring $trustedkeyring $sigfn $tmp"; - my $logfunc = sub { logmsg($logfd, "signature verification: $_[0]"); }; - eval { - run_command($cmd, outfunc => $logfunc, errfunc => $logfunc); - }; - die "unable to verify signature - $@\n" if $@; + my $logfunc = sub { logmsg($logfd, "signature verification: $_[0]"); }; + eval { run_command($cmd, outfunc => $logfunc, errfunc => $logfunc); }; + die "unable to verify signature - $@\n" if $@; - # test syntax - eval { read_aplinfo($tmp, {}, $aplinfo->{url}, 1) }; - die "update failed: $@" if $@; + # test syntax + eval { read_aplinfo($tmp, {}, $aplinfo->{url}, 1) }; + die "update failed: $@" if $@; - rename($tmp, "$APL_INFO_DIRECTORY/$host") or - die "update failed: unable to store data: $!\n"; + rename($tmp, "$APL_INFO_DIRECTORY/$host") + or die "update failed: unable to store data: $!\n"; - logmsg($logfd, "update successful"); + logmsg($logfd, "update successful"); }; my $err = $@; @@ -195,16 +198,16 @@ sub download_aplinfo { sub get_apl_sources { my $sources = [ - { - host => "download.proxmox.com", - url => "http://download.proxmox.com/images", - file => 'aplinfo-pve-8.dat', - }, - { - host => "releases.turnkeylinux.org", - url => "https://releases.turnkeylinux.org/pve", - file => 'aplinfo.dat', - }, + { + host => "download.proxmox.com", + url => "http://download.proxmox.com/images", + file => 'aplinfo-pve-8.dat', + }, + { + host => "releases.turnkeylinux.org", + url => "https://releases.turnkeylinux.org/pve", + file => 'aplinfo.dat', + }, ]; return $sources; @@ -215,9 +218,9 @@ sub update { my $logfile_size = -s $LOGFILE || 0; if ($logfile_size > 1024 * 256) { - rename($LOGFILE, "$LOGFILE.0") or warn "failed to rotate log file $LOGFILE - $!\n"; + rename($LOGFILE, "$LOGFILE.0") or warn "failed to rotate log file $LOGFILE - $!\n"; } - my $logfd = IO::File->new (">>$LOGFILE"); + my $logfd = IO::File->new(">>$LOGFILE"); logmsg($logfd, "starting update"); my $ua = LWP::UserAgent->new; @@ -225,9 +228,9 @@ sub update { $ua->agent("PVE/$release"); if ($proxy) { - $ua->proxy(['http', 'https'], $proxy); + $ua->proxy(['http', 'https'], $proxy); } else { - $ua->env_proxy; + $ua->env_proxy; } my $sources = get_apl_sources(); @@ -236,13 +239,11 @@ sub update { my @dlerr = (); foreach my $info (@$sources) { - eval { - download_aplinfo($ua, $info, $logfd); - }; - if (my $err = $@) { - logmsg ($logfd, $err); - push @dlerr, $info->{url}; - } + eval { download_aplinfo($ua, $info, $logfd); }; + if (my $err = $@) { + logmsg($logfd, $err); + push @dlerr, $info->{url}; + } } close($logfd); @@ -254,15 +255,15 @@ sub update { sub load_data { - my $sources = get_apl_sources(); + my $sources = get_apl_sources(); my $list = {}; foreach my $info (@$sources) { - eval { - my $host = $info->{host}; - read_aplinfo("$APL_INFO_DIRECTORY/$host", $list, $info->{url}); - }; - warn $@ if $@; + eval { + my $host = $info->{host}; + read_aplinfo("$APL_INFO_DIRECTORY/$host", $list, $info->{url}); + }; + warn $@ if $@; } return $list; diff --git a/PVE/AutoBalloon.pm b/PVE/AutoBalloon.pm index 8f4d48a4..436774cf 100644 --- a/PVE/AutoBalloon.pm +++ b/PVE/AutoBalloon.pm @@ -4,144 +4,148 @@ use warnings; use strict; sub compute_alg1 { - my ($vmstatus, $goal, $maxchange, $debug) = @_; + my ($vmstatus, $goal, $maxchange, $debug) = @_; my $log = sub { print @_ if $debug; }; my $change_func = sub { - my ($res, $idlist, $bytes) = @_; + my ($res, $idlist, $bytes) = @_; - my $rest = $bytes; - my $repeat = 1; - my $done_hash = {}; - my $progress = 1; + my $rest = $bytes; + my $repeat = 1; + my $done_hash = {}; + my $progress = 1; - while ($rest && $repeat && $progress) { - $repeat = 0; - $progress = 0; + while ($rest && $repeat && $progress) { + $repeat = 0; + $progress = 0; - my $shares_total = 0; - my $alloc_old = 0; + my $shares_total = 0; + my $alloc_old = 0; - foreach my $vmid (@$idlist) { - next if defined($done_hash->{$vmid}); - my $d = $vmstatus->{$vmid}; - my $balloon = defined($res->{$vmid}) ? $res->{$vmid} : $d->{balloon}; - $alloc_old += $balloon - $d->{balloon_min}; - $shares_total += $d->{shares} || 1000; - } + foreach my $vmid (@$idlist) { + next if defined($done_hash->{$vmid}); + my $d = $vmstatus->{$vmid}; + my $balloon = defined($res->{$vmid}) ? $res->{$vmid} : $d->{balloon}; + $alloc_old += $balloon - $d->{balloon_min}; + $shares_total += $d->{shares} || 1000; + } - my $changes = 0; + my $changes = 0; - my $alloc_new = $alloc_old + $rest; + my $alloc_new = $alloc_old + $rest; - &$log("shares_total: $shares_total $alloc_new\n"); + &$log("shares_total: $shares_total $alloc_new\n"); - foreach my $vmid (@$idlist) { - next if defined($done_hash->{$vmid}); - my $d = $vmstatus->{$vmid}; - my $shares = $d->{shares} || 1000; - my $desired = $d->{balloon_min} + int(($alloc_new/$shares_total)*$shares); + foreach my $vmid (@$idlist) { + next if defined($done_hash->{$vmid}); + my $d = $vmstatus->{$vmid}; + my $shares = $d->{shares} || 1000; + my $desired = $d->{balloon_min} + int(($alloc_new / $shares_total) * $shares); - if ($desired > $d->{maxmem}) { - $desired = $d->{maxmem}; - $repeat = 1; - } elsif ($desired < $d->{balloon_min}) { - $desired = $d->{balloon_min}; - $repeat = 1; - } + if ($desired > $d->{maxmem}) { + $desired = $d->{maxmem}; + $repeat = 1; + } elsif ($desired < $d->{balloon_min}) { + $desired = $d->{balloon_min}; + $repeat = 1; + } - my ($new, $balloon); - if (($bytes > 0) && ($desired - $d->{balloon}) > 0) { # grow - $new = $d->{balloon} + $maxchange; - $balloon = $new > $desired ? $desired : $new; - } elsif (($desired - $d->{balloon}) < 0) { # shrink - $new = $d->{balloon} - $maxchange; - $balloon = $new > $desired ? $new : $desired; - } else { - $done_hash->{$vmid} = 1; - next; - } + my ($new, $balloon); + if (($bytes > 0) && ($desired - $d->{balloon}) > 0) { # grow + $new = $d->{balloon} + $maxchange; + $balloon = $new > $desired ? $desired : $new; + } elsif (($desired - $d->{balloon}) < 0) { # shrink + $new = $d->{balloon} - $maxchange; + $balloon = $new > $desired ? $new : $desired; + } else { + $done_hash->{$vmid} = 1; + next; + } - my $diff = $balloon - $d->{balloon}; - if ($diff != 0) { - my $oldballoon = defined($res->{$vmid}) ? $res->{$vmid} : $d->{balloon}; - $res->{$vmid} = $balloon; - my $change = $balloon - $oldballoon; - if ($change != 0) { - $changes += $change; - my $absdiff = $diff > 0 ? $diff : -$diff; - $progress += $absdiff; - $repeat = 1; - } - &$log("change request for $vmid ($balloon, $diff, $desired, $new, $changes, $progress)\n"); - } - } + my $diff = $balloon - $d->{balloon}; + if ($diff != 0) { + my $oldballoon = defined($res->{$vmid}) ? $res->{$vmid} : $d->{balloon}; + $res->{$vmid} = $balloon; + my $change = $balloon - $oldballoon; + if ($change != 0) { + $changes += $change; + my $absdiff = $diff > 0 ? $diff : -$diff; + $progress += $absdiff; + $repeat = 1; + } + &$log( + "change request for $vmid ($balloon, $diff, $desired, $new, $changes, $progress)\n" + ); + } + } - $rest -= $changes; - } + $rest -= $changes; + } - return $rest; + return $rest; }; - my $idlist = []; # list of VMs with working balloon river my $idlist1 = []; # list of VMs with memory pressure my $idlist2 = []; # list of VMs with enough free memory foreach my $vmid (keys %$vmstatus) { - my $d = $vmstatus->{$vmid}; - next if !$d->{balloon}; # skip if balloon driver not running - next if !$d->{balloon_min}; # skip if balloon value not set in config - next if $d->{lock} && $d->{lock} eq 'migrate'; - next if defined($d->{shares}) && - ($d->{shares} == 0); # skip if shares set to zero + my $d = $vmstatus->{$vmid}; + next if !$d->{balloon}; # skip if balloon driver not running + next if !$d->{balloon_min}; # skip if balloon value not set in config + next if $d->{lock} && $d->{lock} eq 'migrate'; + next + if defined($d->{shares}) + && ($d->{shares} == 0); # skip if shares set to zero - push @$idlist, $vmid; + push @$idlist, $vmid; - if ($d->{freemem} && - ($d->{freemem} > $d->{balloon_min}*0.25) && - ($d->{balloon} >= $d->{balloon_min})) { - push @$idlist2, $vmid; - &$log("idlist2 $vmid $d->{balloon}, $d->{balloon_min}, $d->{freemem}\n"); - } else { - push @$idlist1, $vmid; - &$log("idlist1 $vmid $d->{balloon}, $d->{balloon_min}\n"); - } + if ( + $d->{freemem} + && ($d->{freemem} > $d->{balloon_min} * 0.25) + && ($d->{balloon} >= $d->{balloon_min}) + ) { + push @$idlist2, $vmid; + &$log("idlist2 $vmid $d->{balloon}, $d->{balloon_min}, $d->{freemem}\n"); + } else { + push @$idlist1, $vmid; + &$log("idlist1 $vmid $d->{balloon}, $d->{balloon_min}\n"); + } } my $res = {}; - if ($goal > 10*1024*1024) { - &$log("grow request start $goal\n"); - # priorize VMs with memory pressure - my $rest = &$change_func($res, $idlist1, $goal); - if ($rest >= $goal) { # no progress ==> consider all VMs - &$log("grow request loop $rest\n"); - $rest = &$change_func($res, $idlist, $rest); - } - &$log("grow request end $rest\n"); + if ($goal > 10 * 1024 * 1024) { + &$log("grow request start $goal\n"); + # priorize VMs with memory pressure + my $rest = &$change_func($res, $idlist1, $goal); + if ($rest >= $goal) { # no progress ==> consider all VMs + &$log("grow request loop $rest\n"); + $rest = &$change_func($res, $idlist, $rest); + } + &$log("grow request end $rest\n"); - } elsif ($goal < -10*1024*1024) { - &$log("shrink request $goal\n"); - # priorize VMs with enough free memory - my $rest = &$change_func($res, $idlist2, $goal); - if ($rest <= $goal) { # no progress ==> consider all VMs - &$log("shrink request loop $rest\n"); - $rest = &$change_func($res, $idlist, $rest); - } - &$log("shrink request end $rest\n"); - } else { - &$log("do nothing\n"); - # do nothing - requested change to small + } elsif ($goal < -10 * 1024 * 1024) { + &$log("shrink request $goal\n"); + # priorize VMs with enough free memory + my $rest = &$change_func($res, $idlist2, $goal); + if ($rest <= $goal) { # no progress ==> consider all VMs + &$log("shrink request loop $rest\n"); + $rest = &$change_func($res, $idlist, $rest); + } + &$log("shrink request end $rest\n"); + } else { + &$log("do nothing\n"); + # do nothing - requested change to small } foreach my $vmid (@$idlist) { - next if !$res->{$vmid}; - my $d = $vmstatus->{$vmid}; - my $diff = int($res->{$vmid} - $d->{balloon}); - my $absdiff = $diff < 0 ? -$diff : $diff; - &$log("BALLOON $vmid to $res->{$vmid} ($diff)\n"); + next if !$res->{$vmid}; + my $d = $vmstatus->{$vmid}; + my $diff = int($res->{$vmid} - $d->{balloon}); + my $absdiff = $diff < 0 ? -$diff : $diff; + &$log("BALLOON $vmid to $res->{$vmid} ($diff)\n"); } return $res; } diff --git a/PVE/CLI/pve7to8.pm b/PVE/CLI/pve7to8.pm index 61710178..0c48b106 100644 --- a/PVE/CLI/pve7to8.pm +++ b/PVE/CLI/pve7to8.pm @@ -102,10 +102,11 @@ sub log_pass { $log_line->('pass', @_); } sub log_info { $log_line->('info', @_); } sub log_skip { $log_line->('skip', @_); } sub log_notice { $log_line->('notice', @_); } -sub log_warn { $log_line->('warn', @_); } +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; @@ -118,17 +119,18 @@ my $get_systemd_unit_state = sub { my $state; my $filter_output = sub { - $state = shift; - chomp $state; + $state = shift; + chomp $state; }; 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); - return if !defined($state); - run_command(['systemctl', 'is-active', "$unit"], %extra); + run_command(['systemctl', 'is-enabled', "$unit"], %extra); + return if !defined($state); + run_command(['systemctl', 'is-active', "$unit"], %extra); }; return $state // 'unknown'; @@ -140,11 +142,11 @@ my $log_systemd_unit_state = sub { my $state = $get_systemd_unit_state->($unit); if ($state eq 'active') { - $log_method = \&log_pass; + $log_method = \&log_pass; } elsif ($state eq 'inactive') { - $log_method = $no_fail_on_inactive ? \&log_warn : \&log_fail; + $log_method = $no_fail_on_inactive ? \&log_warn : \&log_fail; } elsif ($state eq 'failed') { - $log_method = \&log_fail; + $log_method = \&log_fail; } $log_method->("systemd unit '$unit' is in state '$state'"); @@ -157,18 +159,18 @@ my $get_pkg = sub { $versions = eval { PVE::API2::APT->versions({ node => $nodename }) } if !defined($versions); if (!defined($versions)) { - my $msg = "unable to retrieve package version information"; - $msg .= "- $@" if $@; - log_fail("$msg"); - return undef; + my $msg = "unable to retrieve package version information"; + $msg .= "- $@" if $@; + log_fail("$msg"); + 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; + log_fail("unable to determine installed $pkg version."); + return undef; } else { - return $pkgs->[0]; + return $pkgs->[0]; } }; @@ -178,90 +180,91 @@ sub check_pve_packages { print "Checking for package updates..\n"; my $updates = eval { PVE::API2::APT->list_updates({ node => $nodename }); }; if (!defined($updates)) { - log_warn("$@") if $@; - log_fail("unable to retrieve list of package updates!"); + log_warn("$@") if $@; + log_fail("unable to retrieve list of package updates!"); } elsif (@$updates > 0) { - my $pkgs = join(', ', map { $_->{Package} } @$updates); - log_warn("updates for the following packages are available:\n $pkgs"); + my $pkgs = join(', ', map { $_->{Package} } @$updates); + log_warn("updates for the following packages are available:\n $pkgs"); } else { - log_pass("all packages up-to-date"); + log_pass("all packages up-to-date"); } print "\nChecking proxmox-ve package version..\n"; if (defined(my $proxmox_ve = $get_pkg->('proxmox-ve'))) { - # TODO: update to native version for pve8to9 - my $min_pve_ver = "$min_pve_major.$min_pve_minor-$min_pve_pkgrel"; + # TODO: update to native version for pve8to9 + my $min_pve_ver = "$min_pve_major.$min_pve_minor-$min_pve_pkgrel"; - my ($maj, $min, $pkgrel) = $proxmox_ve->{OldVersion} =~ m/^(\d+)\.(\d+)[.-](\d+)/; + my ($maj, $min, $pkgrel) = $proxmox_ve->{OldVersion} =~ m/^(\d+)\.(\d+)[.-](\d+)/; - if ($maj > $min_pve_major) { - log_pass("already upgraded to Proxmox VE " . ($min_pve_major + 1)); - $upgraded = 1; - } elsif ($maj >= $min_pve_major && $min >= $min_pve_minor && $pkgrel >= $min_pve_pkgrel) { - log_pass("proxmox-ve package has version >= $min_pve_ver"); - } else { - log_fail("proxmox-ve package is too old, please upgrade to >= $min_pve_ver!"); - } + if ($maj > $min_pve_major) { + log_pass("already upgraded to Proxmox VE " . ($min_pve_major + 1)); + $upgraded = 1; + } elsif ($maj >= $min_pve_major && $min >= $min_pve_minor && $pkgrel >= $min_pve_pkgrel) { + log_pass("proxmox-ve package has version >= $min_pve_ver"); + } else { + log_fail("proxmox-ve package is too old, please upgrade to >= $min_pve_ver!"); + } - # FIXME: better differentiate between 6.2 from bullseye or bookworm - my $kinstalled = 'proxmox-kernel-6.2'; - if (!$upgraded) { - # we got a few that avoided 5.15 in cluster with mixed CPUs, so allow older too - $kinstalled = 'pve-kernel-5.15'; - } + # FIXME: better differentiate between 6.2 from bullseye or bookworm + my $kinstalled = 'proxmox-kernel-6.2'; + if (!$upgraded) { + # we got a few that avoided 5.15 in cluster with mixed CPUs, so allow older too + $kinstalled = 'pve-kernel-5.15'; + } - my $kernel_version_is_expected = sub { - my ($version) = @_; + my $kernel_version_is_expected = sub { + my ($version) = @_; - return $version =~ m/^(?:5\.(?:13|15)|6\.2)/ if !$upgraded; + return $version =~ m/^(?:5\.(?:13|15)|6\.2)/ if !$upgraded; - if ($version =~ m/^6\.(?:2\.(?:[2-9]\d+|1[6-8]|1\d\d+)|5)[^~]*$/) { - return 1; - } elsif ($version =~ m/^(\d+).(\d+)[^~]*-pve$/) { - return $1 >= 6 && $2 >= 2; - } - return 0; - }; + if ($version =~ m/^6\.(?:2\.(?:[2-9]\d+|1[6-8]|1\d\d+)|5)[^~]*$/) { + return 1; + } elsif ($version =~ m/^(\d+).(\d+)[^~]*-pve$/) { + return $1 >= 6 && $2 >= 2; + } + return 0; + }; - print "\nChecking running kernel version..\n"; - my $kernel_ver = $proxmox_ve->{RunningKernel}; - if (!defined($kernel_ver)) { - log_fail("unable to determine running kernel version."); - } elsif ($kernel_version_is_expected->($kernel_ver)) { - if ($upgraded) { - log_pass("running new kernel '$kernel_ver' after upgrade."); - } else { - log_pass("running kernel '$kernel_ver' is considered suitable for upgrade."); - } - } 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?!"); - } else { - log_warn("unexpected running and installed kernel '$kernel_ver'."); - } + print "\nChecking running kernel version..\n"; + my $kernel_ver = $proxmox_ve->{RunningKernel}; + if (!defined($kernel_ver)) { + log_fail("unable to determine running kernel version."); + } elsif ($kernel_version_is_expected->($kernel_ver)) { + if ($upgraded) { + log_pass("running new kernel '$kernel_ver' after upgrade."); + } else { + log_pass("running kernel '$kernel_ver' is considered suitable for upgrade."); + } + } 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?!" + ); + } else { + log_warn("unexpected running and installed kernel '$kernel_ver'."); + } - if ($upgraded && $kernel_version_is_expected->($kernel_ver)) { - my $outdated_kernel_meta_pkgs = []; - for my $kernel_meta_version ('5.4', '5.11', '5.13', '5.15') { - my $pkg = "pve-kernel-${kernel_meta_version}"; - if ($get_pkg->($pkg)) { - push @$outdated_kernel_meta_pkgs, $pkg; - } - } - 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->@*) - ); - } - } + if ($upgraded && $kernel_version_is_expected->($kernel_ver)) { + my $outdated_kernel_meta_pkgs = []; + for my $kernel_meta_version ('5.4', '5.11', '5.13', '5.15') { + my $pkg = "pve-kernel-${kernel_meta_version}"; + if ($get_pkg->($pkg)) { + push @$outdated_kernel_meta_pkgs, $pkg; + } + } + 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->@*)); + } + } } else { - log_fail("proxmox-ve package not found!"); + log_fail("proxmox-ve package not found!"); } } - sub check_storage_health { print_header("CHECKING CONFIGURED STORAGES"); my $cfg = PVE::Storage::config(); @@ -271,16 +274,16 @@ sub check_storage_health { my $info = PVE::Storage::storage_info($cfg); for my $storeid (sort keys %$info) { - my $d = $info->{$storeid}; - if ($d->{enabled}) { - if ($d->{active}) { - log_pass("storage '$storeid' enabled and active."); - } else { - log_warn("storage '$storeid' enabled but not active!"); - } - } else { - log_skip("storage '$storeid' disabled."); - } + my $d = $info->{$storeid}; + if ($d->{enabled}) { + if ($d->{active}) { + log_pass("storage '$storeid' enabled and active."); + } else { + log_warn("storage '$storeid' enabled but not active!"); + } + } else { + log_skip("storage '$storeid' disabled."); + } } check_storage_content(); @@ -292,17 +295,17 @@ sub check_cluster_corosync { print_header("CHECKING CLUSTER HEALTH/SETTINGS"); if (!PVE::Corosync::check_conf_exists(1)) { - log_skip("standalone node."); - return; + log_skip("standalone node."); + return; } $log_systemd_unit_state->('pve-cluster.service'); $log_systemd_unit_state->('corosync.service'); if (PVE::Cluster::check_cfs_quorum(1)) { - log_pass("Cluster Filesystem is quorate."); + log_pass("Cluster Filesystem is quorate."); } else { - log_fail("Cluster Filesystem readonly, lost quorum?!"); + log_fail("Cluster Filesystem readonly, lost quorum?!"); } my $conf = PVE::Cluster::cfs_read_file('corosync.conf'); @@ -311,44 +314,43 @@ sub check_cluster_corosync { print "\nAnalzying quorum settings and state..\n"; if (!defined($conf_nodelist)) { - log_fail("unable to retrieve nodelist from corosync.conf"); + log_fail("unable to retrieve nodelist from corosync.conf"); } 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; + 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; } my ($expected_votes, $total_votes); my $filter_output = sub { - my $line = shift; - ($expected_votes) = $line =~ /^Expected votes:\s*(\d+)\s*$/ - if !defined($expected_votes); - ($total_votes) = $line =~ /^Total votes:\s*(\d+)\s*$/ - if !defined($total_votes); - }; - eval { - run_command(['corosync-quorumtool', '-s'], outfunc => $filter_output, noerr => 1); + my $line = shift; + ($expected_votes) = $line =~ /^Expected votes:\s*(\d+)\s*$/ + if !defined($expected_votes); + ($total_votes) = $line =~ /^Total votes:\s*(\d+)\s*$/ + if !defined($total_votes); }; + 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."); - $expected_votes = 0; + log_fail("unable to get expected number of votes, assuming 0."); + $expected_votes = 0; } if (!defined($total_votes)) { - log_fail("unable to get expected number of votes, assuming 0."); - $total_votes = 0; + log_fail("unable to get expected number of votes, assuming 0."); + $total_votes = 0; } my $cfs_nodelist = PVE::Cluster::get_clinfo()->{nodelist}; my $offline_nodes = grep { $cfs_nodelist->{$_}->{online} != 1 } keys %$cfs_nodelist; if ($offline_nodes > 0) { - log_fail("$offline_nodes nodes are offline!"); + log_fail("$offline_nodes nodes are offline!"); } my $qdevice_votes = 0; if (my $qdevice_setup = $conf->{main}->{quorum}->{device}) { - $qdevice_votes = $qdevice_setup->{votes} // 1; + $qdevice_votes = $qdevice_setup->{votes} // 1; } log_info("configured votes - nodes: $node_votes"); @@ -357,63 +359,63 @@ sub check_cluster_corosync { log_info("current total votes: $total_votes"); log_warn("expected votes set to non-standard value '$expected_votes'.") - if $expected_votes != $node_votes + $qdevice_votes; + if $expected_votes != $node_votes + $qdevice_votes; log_warn("total votes < expected votes: $total_votes/$expected_votes!") - if $total_votes < $expected_votes; + if $total_votes < $expected_votes; my $conf_nodelist_count = scalar(keys %$conf_nodelist); my $cfs_nodelist_count = scalar(keys %$cfs_nodelist); log_warn("cluster consists of less than three quorum-providing nodes!") - if $conf_nodelist_count < 3 && $conf_nodelist_count + $qdevice_votes < 3; + 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; for my $cs_node (sort keys %$conf_nodelist) { - my $entry = $conf_nodelist->{$cs_node}; - if (!defined($entry->{name})) { - $nodelist_pass = 0; - log_fail("$cs_node: no name entry in corosync.conf."); - } - if (!defined($entry->{nodeid})) { - $nodelist_pass = 0; - log_fail("$cs_node: no nodeid configured in corosync.conf."); - } - my $gotLinks = 0; - for my $link (0..7) { - $gotLinks++ if defined($entry->{"ring${link}_addr"}); - } - if ($gotLinks <= 0) { - $nodelist_pass = 0; - log_fail("$cs_node: no ringX_addr (0 <= X <= 7) link defined in corosync.conf."); - } + my $entry = $conf_nodelist->{$cs_node}; + if (!defined($entry->{name})) { + $nodelist_pass = 0; + log_fail("$cs_node: no name entry in corosync.conf."); + } + if (!defined($entry->{nodeid})) { + $nodelist_pass = 0; + log_fail("$cs_node: no nodeid configured in corosync.conf."); + } + my $gotLinks = 0; + for my $link (0 .. 7) { + $gotLinks++ if defined($entry->{"ring${link}_addr"}); + } + if ($gotLinks <= 0) { + $nodelist_pass = 0; + log_fail("$cs_node: no ringX_addr (0 <= X <= 7) link defined in corosync.conf."); + } - 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); - 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." - ); - } - } 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!" - ); - } - } - }; - for my $link (0..7) { - $verify_ring_ip->("ring${link}_addr"); - } + 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); + 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."); + } + } 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!" + ); + } + } + }; + for my $link (0 .. 7) { + $verify_ring_ip->("ring${link}_addr"); + } } log_pass("nodelist settings OK") if $nodelist_pass; @@ -423,19 +425,26 @@ sub check_cluster_corosync { my $transport = $totem->{transport}; if (defined($transport)) { - if ($transport ne 'knet') { - $totem_pass = 0; - log_fail("Corosync transport explicitly set to '$transport' instead of implicit default!"); - } + if ($transport ne 'knet') { + $totem_pass = 0; + 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')) { - $totem_pass = 0; - log_fail("Corosync authentication/encryption is not explicitly enabled (secauth / crypto_cipher / crypto_hash)!"); + 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)!" + ); } 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? + $totem_pass = 0; + 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; @@ -443,11 +452,11 @@ sub check_cluster_corosync { log_info("run 'pvecm status' to get detailed cluster status.."); if (defined(my $corosync = $get_pkg->('corosync'))) { - if ($corosync->{OldVersion} =~ m/^2\./) { - log_fail("\ncorosync 2.x installed, cluster-wide upgrade to 3.x needed!"); - } elsif ($corosync->{OldVersion} !~ m/^3\./) { - log_fail("\nunexpected corosync version installed: $corosync->{OldVersion}!"); - } + if ($corosync->{OldVersion} =~ m/^2\./) { + log_fail("\ncorosync 2.x installed, cluster-wide upgrade to 3.x needed!"); + } elsif ($corosync->{OldVersion} !~ m/^3\./) { + log_fail("\nunexpected corosync version installed: $corosync->{OldVersion}!"); + } } } @@ -455,170 +464,176 @@ sub check_ceph { print_header("CHECKING HYPER-CONVERGED CEPH STATUS"); if (PVE::Ceph::Tools::check_ceph_inited(1)) { - log_info("hyper-converged ceph setup detected!"); + log_info("hyper-converged ceph setup detected!"); } else { - log_skip("no hyper-converged ceph setup detected!"); - return; + log_skip("no hyper-converged ceph setup detected!"); + return; } log_info("getting Ceph status/health information.."); my $ceph_status = eval { PVE::API2::Ceph->status({ node => $nodename }); }; my $noout = eval { PVE::API2::Cluster::Ceph->get_flag({ flag => "noout" }); }; if ($@) { - log_fail("failed to get 'noout' flag status - $@"); + log_fail("failed to get 'noout' flag status - $@"); } my $noout_wanted = 1; if (!$ceph_status || !$ceph_status->{health}) { - log_fail("unable to determine Ceph status!"); + log_fail("unable to determine Ceph status!"); } else { - my $ceph_health = $ceph_status->{health}->{status}; - if (!$ceph_health) { - 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."); - } 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." - ); - } + my $ceph_health = $ceph_status->{health}->{status}; + if (!$ceph_health) { + 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." + ); + } 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."); + } } # TODO: check OSD min-required version, if to low it breaks stuff! log_info("checking local Ceph version.."); 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.") - } 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?!" - ) - } 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" - ." " - ); - } + 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."); + } 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?!"); + } 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" + . " "); + } } else { - log_fail("unable to determine local Ceph version!"); + log_fail("unable to determine local Ceph version!"); } log_info("getting Ceph daemon versions.."); my $ceph_versions = eval { PVE::Ceph::Tools::get_cluster_versions(undef, 1); }; if (!$ceph_versions) { - log_fail("unable to determine Ceph daemon versions!"); + log_fail("unable to determine Ceph daemon versions!"); } else { - my $services = [ - { 'key' => 'mon', 'name' => 'monitor' }, - { 'key' => 'mgr', 'name' => 'manager' }, - { 'key' => 'mds', 'name' => 'MDS' }, - { 'key' => 'osd', 'name' => 'OSD' }, - ]; + my $services = [ + { 'key' => 'mon', 'name' => 'monitor' }, + { 'key' => 'mgr', 'name' => 'manager' }, + { 'key' => 'mds', 'name' => 'MDS' }, + { 'key' => 'osd', 'name' => 'OSD' }, + ]; - my $ceph_versions_simple = {}; - my $ceph_versions_commits = {}; - for my $type (keys %$ceph_versions) { - for my $full_version (keys $ceph_versions->{$type}->%*) { - if ($full_version =~ m/^(.*) \((.*)\).*\(.*\)$/) { - # String is in the form of - # ceph version 17.2.6 (810db68029296377607028a6c6da1ec06f5a2b27) quincy (stable) - # only check the first part, e.g. 'ceph version 17.2.6', the commit hash can - # be different - $ceph_versions_simple->{$type}->{$1} = 1; - $ceph_versions_commits->{$type}->{$2} = 1; - } - } - } + my $ceph_versions_simple = {}; + my $ceph_versions_commits = {}; + for my $type (keys %$ceph_versions) { + for my $full_version (keys $ceph_versions->{$type}->%*) { + if ($full_version =~ m/^(.*) \((.*)\).*\(.*\)$/) { + # String is in the form of + # ceph version 17.2.6 (810db68029296377607028a6c6da1ec06f5a2b27) quincy (stable) + # only check the first part, e.g. 'ceph version 17.2.6', the commit hash can + # be different + $ceph_versions_simple->{$type}->{$1} = 1; + $ceph_versions_commits->{$type}->{$2} = 1; + } + } + } - for my $service (@$services) { - 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."); - } elsif (keys %$service_versions == 1) { - log_pass("single running version detected for daemon type $name."); - } else { - log_warn("multiple running versions detected for daemon type $name!"); - } - } else { - 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; - } + for my $service (@$services) { + 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."); + } elsif (keys %$service_versions == 1) { + log_pass("single running version detected for daemon type $name."); + } else { + log_warn("multiple running versions detected for daemon type $name!"); + } + } else { + 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; + } - my $overall_versions = $ceph_versions->{overall}; - if (!$overall_versions) { - log_warn("unable to determine overall Ceph daemon versions!"); - } elsif (keys %$overall_versions == 1) { - 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!"); - } + my $overall_versions = $ceph_versions->{overall}; + if (!$overall_versions) { + log_warn("unable to determine overall Ceph daemon versions!"); + } elsif (keys %$overall_versions == 1) { + 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!"); + } } if ($noout) { - if ($noout_wanted) { - log_pass("'noout' flag set to prevent rebalancing during cluster-wide upgrades."); - } else { - log_warn("'noout' flag set, Ceph cluster upgrade seems finished."); - } + if ($noout_wanted) { + log_pass("'noout' flag set to prevent rebalancing during cluster-wide upgrades."); + } else { + log_warn("'noout' flag set, Ceph cluster upgrade seems finished."); + } } elsif ($noout_wanted) { - log_warn("'noout' flag not set - recommended to prevent rebalancing during upgrades."); + log_warn("'noout' flag not set - recommended to prevent rebalancing during upgrades."); } log_info("checking Ceph config.."); my $conf = PVE::Cluster::cfs_read_file('ceph.conf'); if (%$conf) { - my $global = $conf->{global}; + my $global = $conf->{global}; - my $global_monhost = $global->{mon_host} // $global->{"mon host"} // $global->{"mon-host"}; - 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." - ); - } + my $global_monhost = $global->{mon_host} // $global->{"mon host"} // $global->{"mon-host"}; + 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."); + } - 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"}; - 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." - ); - } - } + 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"}; + 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." + ); + } + } - 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." - ); - } + 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." + ); + } } else { - log_warn("Empty ceph config found"); + log_warn("Empty ceph config found"); } my $local_ceph_ver = PVE::Ceph::Tools::get_local_version(1); if (defined($local_ceph_ver)) { - if ($local_ceph_ver <= 14) { - log_fail("local Ceph version too low, at least Octopus required.."); - } + if ($local_ceph_ver <= 14) { + log_fail("local Ceph version too low, at least Octopus required.."); + } } else { - log_fail("unable to determine local Ceph version."); + log_fail("unable to determine local Ceph version."); } } @@ -627,53 +642,53 @@ 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(); - # vzdump.conf by itself doesn't need to honor any 'requires' - delete $confdesc->{$_}->{requires} for keys $confdesc->%*; + my $confdesc = PVE::VZDump::Common::get_confdesc(); + # vzdump.conf by itself doesn't need to honor any 'requires' + delete $confdesc->{$_}->{requires} for keys $confdesc->%*; - my $fn = "/etc/vzdump.conf"; - my $raw = PVE::Tools::file_get_contents($fn); + my $fn = "/etc/vzdump.conf"; + my $raw = PVE::Tools::file_get_contents($fn); - my $conf_schema = { type => 'object', properties => $confdesc, }; - my $param = PVE::JSONSchema::parse_config($conf_schema, $fn, $raw); + my $conf_schema = { type => 'object', properties => $confdesc }; + my $param = PVE::JSONSchema::parse_config($conf_schema, $fn, $raw); - if (defined($param->{maxfiles})) { - $pass = 0; - log_warn("$fn - $maxfiles_msg"); - } + if (defined($param->{maxfiles})) { + $pass = 0; + log_warn("$fn - $maxfiles_msg"); + } }; if (my $err = $@) { - $pass = 0; - log_warn("unable to parse node's VZDump configuration - $err"); + $pass = 0; + log_warn("unable to parse node's VZDump configuration - $err"); } my $storage_cfg = PVE::Storage::config(); for my $storeid (keys $storage_cfg->{ids}->%*) { - my $scfg = $storage_cfg->{ids}->{$storeid}; + my $scfg = $storage_cfg->{ids}->{$storeid}; - if (defined($scfg->{maxfiles})) { - $pass = 0; - log_warn("storage '$storeid' - $maxfiles_msg"); - } + if (defined($scfg->{maxfiles})) { + $pass = 0; + log_warn("storage '$storeid' - $maxfiles_msg"); + } } eval { - my $vzdump_cron = PVE::Cluster::cfs_read_file('vzdump.cron'); + my $vzdump_cron = PVE::Cluster::cfs_read_file('vzdump.cron'); - # only warn once, there might be many jobs... - if (scalar(grep { defined($_->{maxfiles}) } $vzdump_cron->{jobs}->@*)) { - $pass = 0; - log_warn("/etc/pve/vzdump.cron - $maxfiles_msg"); - } + # only warn once, there might be many jobs... + if (scalar(grep { defined($_->{maxfiles}) } $vzdump_cron->{jobs}->@*)) { + $pass = 0; + log_warn("/etc/pve/vzdump.cron - $maxfiles_msg"); + } }; if (my $err = $@) { - $pass = 0; - log_warn("unable to parse node's VZDump configuration - $err"); + $pass = 0; + log_warn("unable to parse node's VZDump configuration - $err"); } log_pass("no backup retention problems found.") if $pass; @@ -686,18 +701,20 @@ sub check_cifs_credential_location { my $found; - PVE::Tools::dir_glob_foreach('/etc/pve/priv/', $regex, sub { - my ($filename) = @_; + PVE::Tools::dir_glob_foreach( + '/etc/pve/priv/', + $regex, + sub { + my ($filename) = @_; - my ($basename) = $filename =~ $regex; + 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; - }); + $found = 1; + }, + ); log_pass("no CIFS credentials at outdated location found.") if !$found; } @@ -705,91 +722,97 @@ sub check_cifs_credential_location { sub check_custom_pool_roles { log_info("Checking permission system changes.."); - if (! -f "/etc/pve/user.cfg") { - log_skip("user.cfg does not exist"); - return; + if (!-f "/etc/pve/user.cfg") { + log_skip("user.cfg does not exist"); + return; } my $raw = eval { PVE::Tools::file_get_contents('/etc/pve/user.cfg'); }; if ($@) { - log_fail("Failed to read '/etc/pve/user.cfg' - $@"); - return; + log_fail("Failed to read '/etc/pve/user.cfg' - $@"); + return; } my $roles = {}; while ($raw =~ /^\s*(.+?)\s*$/gm) { - my $line = $1; - my @data; + my $line = $1; + my @data; - for my $d (split (/:/, $line)) { - $d =~ s/^\s+//; - $d =~ s/\s+$//; - push @data, $d - } + for my $d (split(/:/, $line)) { + $d =~ s/^\s+//; + $d =~ s/\s+$//; + push @data, $d; + } - my $et = shift @data; - if ($et eq 'role') { - my ($role, $privlist) = @data; - if (!PVE::AccessControl::verify_rolename($role, 1)) { - warn "user config - ignore role '$role' - invalid characters in role name\n"; - next; - } + my $et = shift @data; + if ($et eq 'role') { + my ($role, $privlist) = @data; + if (!PVE::AccessControl::verify_rolename($role, 1)) { + warn "user config - ignore role '$role' - invalid characters in role name\n"; + next; + } - $roles->{$role} = {} if !$roles->{$role}; - for my $priv (split_list($privlist)) { - $roles->{$role}->{$priv} = 1; - } - } elsif ($et eq 'acl') { - my ($propagate, $pathtxt, $uglist, $rolelist) = @data; - for my $role (split_list($rolelist)) { - 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!" - ); - } - } - } + $roles->{$role} = {} if !$roles->{$role}; + for my $priv (split_list($privlist)) { + $roles->{$role}->{$priv} = 1; + } + } elsif ($et eq 'acl') { + my ($propagate, $pathtxt, $uglist, $rolelist) = @data; + for my $role (split_list($rolelist)) { + 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!"); + } + } + } } log_info("Checking custom role IDs for clashes with new 'PVE' namespace.."); my ($custom_roles, $pve_namespace_clashes) = (0, 0); for my $role (sort keys %{$roles}) { - next if PVE::AccessControl::role_is_special($role); - $custom_roles++; + next if PVE::AccessControl::role_is_special($role); + $custom_roles++; - if ($role =~ /^PVE/i) { - log_warn("custom role '$role' clashes with 'PVE' namespace for built-in roles"); - $pve_namespace_clashes++; - } + if ($role =~ /^PVE/i) { + log_warn("custom role '$role' clashes with 'PVE' namespace for built-in roles"); + $pve_namespace_clashes++; + } } 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" + ); } } my sub check_max_length { my ($raw, $max_length, $warning) = @_; - log_warn($warning) if defined($raw) && length($raw) > $max_length; + log_warn($warning) if defined($raw) && length($raw) > $max_length; } sub check_node_and_guest_configurations { log_info("Checking node and guest description/note length.."); my @affected_nodes = grep { - my $desc = PVE::NodeConfig::load_config($_)->{desc}; - defined($desc) && length($desc) > 64 * 1024 + my $desc = PVE::NodeConfig::load_config($_)->{desc}; + defined($desc) && length($desc) > 64 * 1024 } 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 " - . join(', ', @affected_nodes)); + 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"); + log_pass("All node config descriptions fit in the new limit of 64 KiB"); } my $affected_guests_long_desc = []; @@ -797,38 +820,46 @@ sub check_node_and_guest_configurations { my $cts = PVE::LXC::config_list(); for my $vmid (sort { $a <=> $b } keys %$cts) { - my $conf = PVE::LXC::Config->load_config($vmid); + my $conf = PVE::LXC::Config->load_config($vmid); - my $desc = $conf->{description}; - push @$affected_guests_long_desc, "CT $vmid" if defined($desc) && length($desc) > 8 * 1024; + my $desc = $conf->{description}; + 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)); + my $lxc_raw_conf = $conf->{lxc}; + 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) { - my $desc = PVE::QemuConfig->load_config($vmid)->{description}; - push @$affected_guests_long_desc, "VM $vmid" if defined($desc) && length($desc) > 8 * 1024; + my $desc = PVE::QemuConfig->load_config($vmid)->{description}; + 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"); + log_pass("All guest config descriptions fit in the new limit of 8 KiB"); } log_info("Checking container configs for deprecated lxc.cgroup entries"); 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."); - } 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"); - } + if ($forced_legacy_cgroup) { + 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" + ); + } } else { - log_pass("No legacy 'lxc.cgroup' keys found."); + log_pass("No legacy 'lxc.cgroup' keys found."); } } @@ -841,145 +872,144 @@ sub check_storage_content { my $storage_cfg = PVE::Storage::config(); for my $storeid (sort keys $storage_cfg->{ids}->%*) { - my $scfg = $storage_cfg->{ids}->{$storeid}; + my $scfg = $storage_cfg->{ids}->{$storeid}; - next if $scfg->{shared}; - next if !PVE::Storage::storage_check_enabled($storage_cfg, $storeid, undef, 1); + next if $scfg->{shared}; + next if !PVE::Storage::storage_check_enabled($storage_cfg, $storeid, undef, 1); - my $valid_content = PVE::Storage::Plugin::valid_content_types($scfg->{type}); + my $valid_content = PVE::Storage::Plugin::valid_content_types($scfg->{type}); - if (scalar(keys $scfg->{content}->%*) == 0 && !$valid_content->{none}) { - $pass = 0; - log_fail("storage '$storeid' does not support configured content type 'none'"); - delete $scfg->{content}->{none}; # scan for guest images below - } + if (scalar(keys $scfg->{content}->%*) == 0 && !$valid_content->{none}) { + $pass = 0; + log_fail("storage '$storeid' does not support configured content type 'none'"); + delete $scfg->{content}->{none}; # scan for guest images below + } - next if $scfg->{content}->{images}; - next if $scfg->{content}->{rootdir}; + next if $scfg->{content}->{images}; + next if $scfg->{content}->{rootdir}; - # Skip 'iscsi(direct)' (and foreign plugins with potentially similar behavior) with 'none', - # because that means "use LUNs directly" and vdisk_list() in PVE 6.x still lists those. - # It's enough to *not* skip 'dir', because it is the only other storage that supports 'none' - # and 'images' or 'rootdir', hence being potentially misconfigured. - next if $scfg->{type} ne 'dir' && $scfg->{content}->{none}; + # Skip 'iscsi(direct)' (and foreign plugins with potentially similar behavior) with 'none', + # because that means "use LUNs directly" and vdisk_list() in PVE 6.x still lists those. + # It's enough to *not* skip 'dir', because it is the only other storage that supports 'none' + # and 'images' or 'rootdir', hence being potentially misconfigured. + next if $scfg->{type} ne 'dir' && $scfg->{content}->{none}; - eval { PVE::Storage::activate_storage($storage_cfg, $storeid) }; - if (my $err = $@) { - log_warn("activating '$storeid' failed - $err"); - next; - } + eval { PVE::Storage::activate_storage($storage_cfg, $storeid) }; + if (my $err = $@) { + log_warn("activating '$storeid' failed - $err"); + next; + } - my $res = eval { PVE::Storage::vdisk_list($storage_cfg, $storeid); }; - if (my $err = $@) { - log_warn("listing images on '$storeid' failed - $err"); - next; - } - my @volids = map { $_->{volid} } $res->{$storeid}->@*; + my $res = eval { PVE::Storage::vdisk_list($storage_cfg, $storeid); }; + if (my $err = $@) { + log_warn("listing images on '$storeid' failed - $err"); + next; + } + my @volids = map { $_->{volid} } $res->{$storeid}->@*; - my $number = scalar(@volids); - if ($number > 0) { - log_info( - "storage '$storeid' - neither content type 'images' nor 'rootdir' configured, but" - ."found $number guest volume(s)" - ); - } + my $number = scalar(@volids); + if ($number > 0) { + log_info( + "storage '$storeid' - neither content type 'images' nor 'rootdir' configured, but" + . "found $number guest volume(s)"); + } } my $check_volid = sub { - my ($volid, $vmid, $vmtype, $reference) = @_; + my ($volid, $vmid, $vmtype, $reference) = @_; - my $guesttext = $vmtype eq 'qemu' ? 'VM' : 'CT'; - my $prefix = "$guesttext $vmid - volume '$volid' ($reference)"; + my $guesttext = $vmtype eq 'qemu' ? 'VM' : 'CT'; + my $prefix = "$guesttext $vmid - volume '$volid' ($reference)"; - my ($storeid) = PVE::Storage::parse_volume_id($volid, 1); - return if !defined($storeid); + my ($storeid) = PVE::Storage::parse_volume_id($volid, 1); + return if !defined($storeid); - my $scfg = $storage_cfg->{ids}->{$storeid}; - if (!$scfg) { - $pass = 0; - log_warn("$prefix - storage does not exist!"); - return; - } + my $scfg = $storage_cfg->{ids}->{$storeid}; + if (!$scfg) { + $pass = 0; + log_warn("$prefix - storage does not exist!"); + return; + } - # cannot use parse_volname for containers, as it can return 'images' - # but containers cannot have ISO images attached, so assume 'rootdir' - my $vtype = 'rootdir'; - if ($vmtype eq 'qemu') { - ($vtype) = eval { PVE::Storage::parse_volname($storage_cfg, $volid); }; - return if $@; - } + # cannot use parse_volname for containers, as it can return 'images' + # but containers cannot have ISO images attached, so assume 'rootdir' + my $vtype = 'rootdir'; + if ($vmtype eq 'qemu') { + ($vtype) = eval { PVE::Storage::parse_volname($storage_cfg, $volid); }; + return if $@; + } - if (!$scfg->{content}->{$vtype}) { - $found = 1; - $pass = 0; - log_warn("$prefix - storage does not have content type '$vtype' configured."); - } + if (!$scfg->{content}->{$vtype}) { + $found = 1; + $pass = 0; + log_warn("$prefix - storage does not have content type '$vtype' configured."); + } }; my $cts = PVE::LXC::config_list(); for my $vmid (sort { $a <=> $b } keys %$cts) { - my $conf = PVE::LXC::Config->load_config($vmid); + my $conf = PVE::LXC::Config->load_config($vmid); - my $volhash = {}; + my $volhash = {}; - my $check = sub { - my ($ms, $mountpoint, $reference) = @_; + my $check = sub { + my ($ms, $mountpoint, $reference) = @_; - my $volid = $mountpoint->{volume}; - return if !$volid || $mountpoint->{type} ne 'volume'; + my $volid = $mountpoint->{volume}; + return if !$volid || $mountpoint->{type} ne 'volume'; - return if $volhash->{$volid}; # volume might be referenced multiple times + return if $volhash->{$volid}; # volume might be referenced multiple times - $volhash->{$volid} = 1; + $volhash->{$volid} = 1; - $check_volid->($volid, $vmid, 'lxc', $reference); - }; + $check_volid->($volid, $vmid, 'lxc', $reference); + }; - my $opts = { include_unused => 1 }; - PVE::LXC::Config->foreach_volume_full($conf, $opts, $check, 'in config'); - for my $snapname (keys $conf->{snapshots}->%*) { - my $snap = $conf->{snapshots}->{$snapname}; - PVE::LXC::Config->foreach_volume_full($snap, $opts, $check, "in snapshot '$snapname'"); - } + my $opts = { include_unused => 1 }; + PVE::LXC::Config->foreach_volume_full($conf, $opts, $check, 'in config'); + for my $snapname (keys $conf->{snapshots}->%*) { + my $snap = $conf->{snapshots}->{$snapname}; + PVE::LXC::Config->foreach_volume_full($snap, $opts, $check, "in snapshot '$snapname'"); + } } my $vms = PVE::QemuServer::config_list(); for my $vmid (sort { $a <=> $b } keys %$vms) { - my $conf = PVE::QemuConfig->load_config($vmid); + my $conf = PVE::QemuConfig->load_config($vmid); - my $volhash = {}; + my $volhash = {}; - my $check = sub { - my ($key, $drive, $reference) = @_; + my $check = sub { + my ($key, $drive, $reference) = @_; - my $volid = $drive->{file}; - return if $volid =~ m|^/|; - return if $volhash->{$volid}; # volume might be referenced multiple times + my $volid = $drive->{file}; + return if $volid =~ m|^/|; + return if $volhash->{$volid}; # volume might be referenced multiple times - $volhash->{$volid} = 1; - $check_volid->($volid, $vmid, 'qemu', $reference); - }; + $volhash->{$volid} = 1; + $check_volid->($volid, $vmid, 'qemu', $reference); + }; - my $opts = { - extra_keys => ['vmstate'], - include_unused => 1, - }; - # startup from a suspended state works even without 'images' content type on the - # state storage, so do not check 'vmstate' for $conf - PVE::QemuConfig->foreach_volume_full($conf, { include_unused => 1 }, $check, 'in config'); - for my $snapname (keys $conf->{snapshots}->%*) { - my $snap = $conf->{snapshots}->{$snapname}; - PVE::QemuConfig->foreach_volume_full($snap, $opts, $check, "in snapshot '$snapname'"); - } + my $opts = { + extra_keys => ['vmstate'], + include_unused => 1, + }; + # startup from a suspended state works even without 'images' content type on the + # state storage, so do not check 'vmstate' for $conf + PVE::QemuConfig->foreach_volume_full($conf, { include_unused => 1 }, $check, 'in config'); + for my $snapname (keys $conf->{snapshots}->%*) { + my $snap = $conf->{snapshots}->{$snapname}; + PVE::QemuConfig->foreach_volume_full($snap, $opts, $check, "in snapshot '$snapname'"); + } } 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) { - log_pass("no storage content problems found"); + log_pass("no storage content problems found"); } } @@ -989,194 +1019,205 @@ sub check_storage_content_dirs { # check that content dirs are pairwise inequal my $any_problematic = 0; for my $storeid (sort keys $storage_cfg->{ids}->%*) { - my $scfg = $storage_cfg->{ids}->{$storeid}; + my $scfg = $storage_cfg->{ids}->{$storeid}; - next if !PVE::Storage::storage_check_enabled($storage_cfg, $storeid, undef, 1); - next if !$scfg->{path} || !$scfg->{content}; + next if !PVE::Storage::storage_check_enabled($storage_cfg, $storeid, undef, 1); + next if !$scfg->{path} || !$scfg->{content}; - eval { PVE::Storage::activate_storage($storage_cfg, $storeid) }; - if (my $err = $@) { - log_warn("activating '$storeid' failed - $err"); - next; - } + eval { PVE::Storage::activate_storage($storage_cfg, $storeid) }; + if (my $err = $@) { + log_warn("activating '$storeid' failed - $err"); + next; + } - my $resolved_subdirs = {}; - my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); - for my $vtype (keys $scfg->{content}->%*) { - my $abs_subdir = Cwd::abs_path($plugin->get_subdir($scfg, $vtype)); - next if !defined($abs_subdir); - push $resolved_subdirs->{$abs_subdir}->@*, $vtype; - } - 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)."); - $any_problematic = 1; - } - } + my $resolved_subdirs = {}; + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); + for my $vtype (keys $scfg->{content}->%*) { + my $abs_subdir = Cwd::abs_path($plugin->get_subdir($scfg, $vtype)); + next if !defined($abs_subdir); + push $resolved_subdirs->{$abs_subdir}->@*, $vtype; + } + 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)." + ); + $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)." - ); + 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)." + ); } my $supports_cgroupv2 = sub { - my ($conf, $rootdir, $ctid) = @_; + my ($conf, $rootdir, $ctid) = @_; - my $get_systemd_version = sub { - my ($self) = @_; + my $get_systemd_version = sub { + my ($self) = @_; - my @dirs = ( - '/lib/systemd', - '/usr/lib/systemd', - '/usr/lib/x86_64-linux-gnu/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/) { - return $1; - } + my @dirs = ( + '/lib/systemd', + '/usr/lib/systemd', + '/usr/lib/x86_64-linux-gnu/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/ + ) { + return $1; + } - return undef; - }; + return undef; + }; - my $unified_cgroupv2_support = sub { - my ($self) = @_; + my $unified_cgroupv2_support = sub { + my ($self) = @_; - # https://www.freedesktop.org/software/systemd/man/systemd.html - # systemd is installed as symlink to /sbin/init - my $systemd = CORE::readlink('/sbin/init'); + # https://www.freedesktop.org/software/systemd/man/systemd.html + # systemd is installed as symlink to /sbin/init + my $systemd = CORE::readlink('/sbin/init'); - # assume non-systemd init will run with unified cgroupv2 - if (!defined($systemd) || $systemd !~ m@/systemd$@) { - return 1; - } + # assume non-systemd init will run with unified cgroupv2 + if (!defined($systemd) || $systemd !~ m@/systemd$@) { + return 1; + } - # systemd version 232 (e.g. debian stretch) supports the unified hierarchy - my $sdver = $get_systemd_version->(); - if (!defined($sdver) || $sdver < 232) { - return 0; - } + # systemd version 232 (e.g. debian stretch) supports the unified hierarchy + my $sdver = $get_systemd_version->(); + if (!defined($sdver) || $sdver < 232) { + return 0; + } - return 1; - }; + return 1; + }; - my $ostype = $conf->{ostype}; - if (!defined($ostype)) { - log_warn("Found CT ($ctid) without 'ostype' set!"); - } elsif ($ostype eq 'devuan' || $ostype eq 'alpine') { - return 1; # no systemd, no cgroup problems - } + my $ostype = $conf->{ostype}; + if (!defined($ostype)) { + log_warn("Found CT ($ctid) without 'ostype' set!"); + } elsif ($ostype eq 'devuan' || $ostype eq 'alpine') { + return 1; # no systemd, no cgroup problems + } - my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir); - return $lxc_setup->protected_call($unified_cgroupv2_support); + my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir); + return $lxc_setup->protected_call($unified_cgroupv2_support); }; 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"; - 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." - ); + my ($ctid) = @_; + 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." + ); }; my $cts = eval { PVE::API2::LXC->vmlist({ node => $nodename }) }; if ($@) { - log_warn("Failed to retrieve information about this node's CTs - $@"); - return; + log_warn("Failed to retrieve information about this node's CTs - $@"); + return; } if (!defined($cts) || !scalar(@$cts)) { - log_skip("No containers on node detected."); - return; + log_skip("No containers on node detected."); + return; } my @running_cts = sort { $a <=> $b } grep { $_->{status} eq 'running' } @$cts; my @offline_cts = sort { $a <=> $b } grep { $_->{status} ne 'running' } @$cts; for my $ct (@running_cts) { - my $ctid = $ct->{vmid}; - my $pid = eval { PVE::LXC::find_lxc_pid($ctid) }; - if (my $err = $@) { - log_warn("Failed to get PID for running CT $ctid - $err"); - next; - } - my $rootdir = "/proc/$pid/root"; - my $conf = PVE::LXC::Config->load_config($ctid); + my $ctid = $ct->{vmid}; + my $pid = eval { PVE::LXC::find_lxc_pid($ctid) }; + if (my $err = $@) { + log_warn("Failed to get PID for running CT $ctid - $err"); + next; + } + my $rootdir = "/proc/$pid/root"; + my $conf = PVE::LXC::Config->load_config($ctid); - my $ret = eval { $supports_cgroupv2->($conf, $rootdir, $ctid) }; - if (my $err = $@) { - log_warn("Failed to get cgroup support status for CT $ctid - $err"); - next; - } - if (!$ret) { - $log_problem->($ctid); - return; - } + my $ret = eval { $supports_cgroupv2->($conf, $rootdir, $ctid) }; + if (my $err = $@) { + log_warn("Failed to get cgroup support status for CT $ctid - $err"); + next; + } + if (!$ret) { + $log_problem->($ctid); + return; + } } my $storage_cfg = PVE::Storage::config(); for my $ct (@offline_cts) { - my $ctid = $ct->{vmid}; - my ($conf, $rootdir, $ret); - eval { - $conf = PVE::LXC::Config->load_config($ctid); - $rootdir = PVE::LXC::mount_all($ctid, $storage_cfg, $conf); - $ret = $supports_cgroupv2->($conf, $rootdir, $ctid); - }; - if (my $err = $@) { - log_warn("Failed to load config and mount CT $ctid - $err"); - eval { PVE::LXC::umount_all($ctid, $storage_cfg, $conf) }; - next; - } - if (!$ret) { - $log_problem->($ctid); - eval { PVE::LXC::umount_all($ctid, $storage_cfg, $conf) }; - last; - } + my $ctid = $ct->{vmid}; + my ($conf, $rootdir, $ret); + eval { + $conf = PVE::LXC::Config->load_config($ctid); + $rootdir = PVE::LXC::mount_all($ctid, $storage_cfg, $conf); + $ret = $supports_cgroupv2->($conf, $rootdir, $ctid); + }; + if (my $err = $@) { + log_warn("Failed to load config and mount CT $ctid - $err"); + eval { PVE::LXC::umount_all($ctid, $storage_cfg, $conf) }; + next; + } + if (!$ret) { + $log_problem->($ctid); + eval { PVE::LXC::umount_all($ctid, $storage_cfg, $conf) }; + last; + } - eval { PVE::LXC::umount_all($ctid, $storage_cfg, $conf) }; + 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.."); if (!$upgraded) { - log_skip("not yet upgraded, no need to check the FUSE library version LXCFS uses"); - return; + log_skip("not yet upgraded, no need to check the FUSE library version LXCFS uses"); + return; } my $lxcfs_pid = eval { file_get_contents('/run/lxcfs.pid') }; if (my $err = $@) { - log_fail("failed to get LXCFS pid - $err"); - return; + log_fail("failed to get LXCFS pid - $err"); + return; } chomp $lxcfs_pid; my $lxcfs_maps = eval { file_get_contents("/proc/${lxcfs_pid}/maps") }; if (my $err = $@) { - log_fail("failed to get LXCFS maps - $err"); - return; + log_fail("failed to get LXCFS maps - $err"); + return; } 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; } @@ -1195,52 +1236,52 @@ sub check_apt_repos { my ($mismatches, $strange_suites); my $check_file = sub { - my ($file) = @_; + my ($file) = @_; - $file = "${dir}/${file}" if $in_dir; + $file = "${dir}/${file}" if $in_dir; - my $raw = eval { PVE::Tools::file_get_contents($file) }; - return if !defined($raw); - my @lines = split(/\n/, $raw); + my $raw = eval { PVE::Tools::file_get_contents($file) }; + return if !defined($raw); + my @lines = split(/\n/, $raw); - my $number = 0; - for my $line (@lines) { - $number++; + my $number = 0; + for my $line (@lines) { + $number++; - next if length($line) == 0; # split would result in undef then... + next if length($line) == 0; # split would result in undef then... - ($line) = split(/#/, $line); + ($line) = split(/#/, $line); - next if $line !~ m/^deb[[:space:]]/; # is case sensitive + next if $line !~ m/^deb[[:space:]]/; # is case sensitive - my $suite; - if ($line =~ m|deb\s+\w+://\S+\s+(\S*)|i) { - $suite = $1; - } else { - next; - } - my $where = "in ${file}:${number}"; + my $suite; + if ($line =~ m|deb\s+\w+://\S+\s+(\S*)|i) { + $suite = $1; + } else { + next; + } + my $where = "in ${file}:${number}"; - $suite =~ s/-(?:(?:proposed-)?updates|backports|debug|security)(?:-debug)?$//; - if ($suite ne $old_suite && $suite ne $new_suite && !$older_suites->{$suite}) { - push $strange_suites->@*, { suite => $suite, where => $where }; - next; - } + $suite =~ s/-(?:(?:proposed-)?updates|backports|debug|security)(?:-debug)?$//; + if ($suite ne $old_suite && $suite ne $new_suite && !$older_suites->{$suite}) { + push $strange_suites->@*, { suite => $suite, where => $where }; + next; + } - if (!defined($found_suite)) { - $found_suite = $suite; - $found_suite_where = $where; - } elsif ($suite ne $found_suite) { - if (!defined($mismatches)) { - $mismatches = []; - push $mismatches->@*, - { suite => $found_suite, where => $found_suite_where}, - { suite => $suite, where => $where}; - } else { - push $mismatches->@*, { suite => $suite, where => $where}; - } - } - } + if (!defined($found_suite)) { + $found_suite = $suite; + $found_suite_where = $where; + } elsif ($suite ne $found_suite) { + if (!defined($mismatches)) { + $mismatches = []; + push $mismatches->@*, + { suite => $found_suite, where => $found_suite_where }, + { suite => $suite, where => $where }; + } else { + push $mismatches->@*, { suite => $suite, where => $where }; + } + } + } }; $check_file->("/etc/apt/sources.list"); @@ -1250,27 +1291,30 @@ sub check_apt_repos { PVE::Tools::dir_glob_foreach($dir, '^.*\.list$', $check_file); if ($strange_suites) { - 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!" - ); + 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!" + ); } if (defined($mismatches)) { - my @mismatch_list = map { "found suite $_->{suite} at $_->{where}" } $mismatches->@*; + my @mismatch_list = map { "found suite $_->{suite} at $_->{where}" } $mismatches->@*; - 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." - ); + 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."); } elsif (defined($strange_suites)) { - log_notice("found no suite mismatches, but found at least one strange suite"); + log_notice("found no suite mismatches, but found at least one strange suite"); } else { - log_pass("found no suite mismatch"); + log_pass("found no suite mismatch"); } } @@ -1278,73 +1322,75 @@ 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') { - log_warn("Running $msg"); + log_warn("Running $msg"); } elsif ($state && $state ne 'unknown') { - log_warn($msg); + log_warn($msg); } else { - log_pass("No NVIDIA vGPU Service found."); + log_pass("No NVIDIA vGPU Service found."); } } 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" - ); + 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" + ); } 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'))) { - log_pass("Detected active time synchronisation unit '$active_ntp'"); + 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') + ) + ) { + 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!" - ); + log_warn( + "No (active) time synchronisation daemon (NTP) detected, but synchronized systems are important," + . " especially for cluster and/or ceph!"); } } sub check_bootloader { log_info("Checking bootloader configuration..."); - if (! -d '/sys/firmware/efi') { - log_skip("System booted in legacy-mode - no need for additional packages"); - return; + 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 (!$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") { - log_pass("bootloader packages installed correctly"); - return; - } - 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" - ); - 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," - . " new grub versions will not be installed to /boot/efi!" - . " Install grub-efi-amd64." - ); - return; + 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") { + log_pass("bootloader packages installed correctly"); + return; + } + 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"); + 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," + . " new grub versions will not be installed to /boot/efi!" + . " Install grub-efi-amd64."); + return; } else { - log_pass("bootloader packages installed correctly"); + log_pass("bootloader packages installed correctly"); } } @@ -1353,22 +1399,22 @@ sub check_dkms_modules { my $count; my $set_count = sub { - $count = scalar @_; + $count = scalar @_; }; 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; if ($exit_code != 0) { - log_skip("could not get dkms status"); + log_skip("could not get dkms status"); } elsif (!$count) { - log_pass("no dkms modules found"); + log_pass("no dkms modules found"); } else { - log_warn("dkms modules found, this might cause issues during upgrade."); + log_warn("dkms modules found, this might cause issues during upgrade."); } } @@ -1376,10 +1422,10 @@ sub check_misc { print_header("MISCELLANEOUS CHECKS"); my $ssh_config = eval { PVE::Tools::file_get_contents('/root/.ssh/config') }; if (defined($ssh_config)) { - log_fail("Unsupported SSH Cipher configured for root in /root/.ssh/config: $1") - if $ssh_config =~ /^Ciphers .*(blowfish|arcfour|3des).*$/m; + log_fail("Unsupported SSH Cipher configured for root in /root/.ssh/config: $1") + if $ssh_config =~ /^Ciphers .*(blowfish|arcfour|3des).*$/m; } else { - log_skip("No SSH config file found."); + log_skip("No SSH config file found."); } log_info("Checking common daemon services.."); @@ -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,63 +1452,66 @@ 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.."); my $local_ip = eval { PVE::Network::get_ip_from_hostname($nodename) }; if ($@) { - log_warn("Failed to resolve hostname '$nodename' to IP - $@"); + log_warn("Failed to resolve hostname '$nodename' to IP - $@"); } else { - log_info("Checking if resolved IP is configured on local node.."); - my $cidr = Net::IP::ip_is_ipv6($local_ip) ? "$local_ip/128" : "$local_ip/32"; - my $configured_ips = PVE::Network::get_local_ip_from_cidr($cidr); - my $ip_count = scalar(@$configured_ips); + log_info("Checking if resolved IP is configured on local node.."); + my $cidr = Net::IP::ip_is_ipv6($local_ip) ? "$local_ip/128" : "$local_ip/32"; + my $configured_ips = PVE::Network::get_local_ip_from_cidr($cidr); + my $ip_count = scalar(@$configured_ips); - if ($ip_count <= 0) { - log_fail("Resolved node IP '$local_ip' not configured or active for '$nodename'"); - } elsif ($ip_count > 1) { - log_warn("Resolved node IP '$local_ip' active on multiple ($ip_count) interfaces!"); - } else { - log_pass("Resolved node IP '$local_ip' configured and active on single interface."); - } + if ($ip_count <= 0) { + log_fail("Resolved node IP '$local_ip' not configured or active for '$nodename'"); + } elsif ($ip_count > 1) { + log_warn("Resolved node IP '$local_ip' active on multiple ($ip_count) interfaces!"); + } else { + log_pass("Resolved node IP '$local_ip' configured and active on single interface."); + } } log_info("Check node certificate's RSA key size"); my $certs = PVE::API2::Certificates->info({ node => $nodename }); my $certs_check = { - 'rsaEncryption' => { - minsize => 2048, - name => 'RSA', - }, - 'id-ecPublicKey' => { - minsize => 224, - name => 'ECC', - }, + 'rsaEncryption' => { + minsize => 2048, + name => 'RSA', + }, + 'id-ecPublicKey' => { + minsize => 224, + name => 'ECC', + }, }; my $certs_check_failed = 0; for my $cert (@$certs) { - my ($type, $size, $fn) = $cert->@{qw(public-key-type public-key-bits filename)}; + my ($type, $size, $fn) = $cert->@{qw(public-key-type public-key-bits filename)}; - if (!defined($type) || !defined($size)) { - log_warn("'$fn': cannot check certificate, failed to get it's type or size!"); - } + if (!defined($type) || !defined($size)) { + log_warn("'$fn': cannot check certificate, failed to get it's type or size!"); + } - my $check = $certs_check->{$type}; - if (!defined($check)) { - log_warn("'$fn': certificate's public key type '$type' unknown!"); - next; - } + my $check = $certs_check->{$type}; + if (!defined($check)) { + log_warn("'$fn': certificate's public key type '$type' unknown!"); + next; + } - if ($size < $check->{minsize}) { - 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)"); - } + if ($size < $check->{minsize}) { + 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)" + ); + } } check_backup_retention_settings(); @@ -1478,66 +1527,73 @@ 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', description => 'Check (pre-/post-)upgrade conditions.', parameters => { - additionalProperties => 0, - properties => { - full => { - description => 'perform additional, expensive checks.', - type => 'boolean', - optional => 1, - default => 0, - }, - }, + additionalProperties => 0, + properties => { + full => { + description => 'perform additional, expensive checks.', + type => 'boolean', + optional => 1, + default => 0, + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $kernel_cli = PVE::Tools::file_get_contents('/proc/cmdline'); - if ($kernel_cli =~ /systemd.unified_cgroup_hierarchy=0/){ - $forced_legacy_cgroup = 1; - } + my $kernel_cli = PVE::Tools::file_get_contents('/proc/cmdline'); + if ($kernel_cli =~ /systemd.unified_cgroup_hierarchy=0/) { + $forced_legacy_cgroup = 1; + } - check_pve_packages(); - check_cluster_corosync(); - check_ceph(); - check_storage_health(); - check_misc(); + check_pve_packages(); + check_cluster_corosync(); + check_ceph(); + check_storage_health(); + check_misc(); - if ($param->{full}) { - check_containers_cgroup_compat(); - } else { - log_skip("NOTE: Expensive checks, like CT cgroupv2 compat, not performed without '--full' parameter"); - } + if ($param->{full}) { + check_containers_cgroup_compat(); + } else { + log_skip( + "NOTE: Expensive checks, like CT cgroupv2 compat, not performed without '--full' parameter" + ); + } - print_header("SUMMARY"); + print_header("SUMMARY"); - my $total = 0; - $total += $_ for values %$counters; + my $total = 0; + $total += $_ for values %$counters; - print "TOTAL: $total\n"; - print colored("PASSED: $counters->{pass}\n", 'green'); - print "SKIPPED: $counters->{skip}\n"; - print colored_if("WARNINGS: $counters->{warn}\n", 'yellow', $counters->{warn} > 0); - print colored_if("FAILURES: $counters->{fail}\n", 'bold red', $counters->{fail} > 0); + print "TOTAL: $total\n"; + print colored("PASSED: $counters->{pass}\n", 'green'); + print "SKIPPED: $counters->{skip}\n"; + print colored_if("WARNINGS: $counters->{warn}\n", 'yellow', $counters->{warn} > 0); + print colored_if("FAILURES: $counters->{fail}\n", 'bold red', $counters->{fail} > 0); - 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; - } + 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; + } - return undef; - }}); + return undef; + }, +}); -our $cmddef = [ __PACKAGE__, 'checklist', [], {}]; +our $cmddef = [__PACKAGE__, 'checklist', [], {}]; 1; diff --git a/PVE/CLI/pveam.pm b/PVE/CLI/pveam.pm index 3db2a3c8..5b4534b7 100644 --- a/PVE/CLI/pveam.pm +++ b/PVE/CLI/pveam.pm @@ -24,181 +24,196 @@ sub setup_environment { PVE::RPCEnvironment->setup_default_cli_env(); } -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'update', path => 'update', method => 'PUT', description => "Update Container Template Database.", parameters => { - additionalProperties => 0, - properties => {}, + additionalProperties => 0, + properties => {}, }, - returns => { type => 'null'}, + returns => { type => 'null' }, code => sub { - my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg'); + my $dccfg = PVE::Cluster::cfs_read_file('datacenter.cfg'); - my $res = PVE::APLInfo::update($dccfg->{http_proxy}); + my $res = PVE::APLInfo::update($dccfg->{http_proxy}); - if ($res) { - print STDOUT "update successful\n" - } else { - print STDERR "update failed - see /var/log/pveam.log for details\n" - } + if ($res) { + print STDOUT "update successful\n"; + } else { + print STDERR "update failed - see /var/log/pveam.log for details\n"; + } - return undef; + return undef; - }}); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'available', path => 'available', method => 'GET', description => "List available templates.", parameters => { - additionalProperties => 0, - properties => { - section => { - type => 'string', - description => "Restrict list to specified section.", - enum => ['system', 'mail', 'turnkeylinux'], - optional => 1, - }, - } + additionalProperties => 0, + properties => { + section => { + type => 'string', + description => "Restrict list to specified section.", + enum => ['system', 'mail', 'turnkeylinux'], + optional => 1, + }, + }, }, - returns => { type => 'null'}, + returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $list = PVE::APLInfo::load_data(); + my $list = PVE::APLInfo::load_data(); - foreach my $section (sort keys %$list) { - next if $section eq 'all'; - next if $param->{section} && $section ne $param->{section}; - foreach my $template (sort keys %{$list->{$section}}) { - print sprintf("%-15s %s\n", $section, $template); - } - } - return undef; + foreach my $section (sort keys %$list) { + next if $section eq 'all'; + next if $param->{section} && $section ne $param->{section}; + foreach my $template (sort keys %{ $list->{$section} }) { + print sprintf("%-15s %s\n", $section, $template); + } + } + return undef; - }}); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'index', path => 'index', method => 'GET', description => "Get list of all templates on storage", permissions => { - description => "Show all users the template which have permission on that storage.", - check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']], + description => "Show all users the template which have permission on that storage.", + check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']], }, proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - storage => get_standard_option('pve-storage-id', { - description => "Only list templates on specified storage", - completion => \&PVE::Storage::complete_storage_enabled, - }), - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + storage => get_standard_option( + 'pve-storage-id', + { + description => "Only list templates on specified storage", + completion => \&PVE::Storage::complete_storage_enabled, + }, + ), + }, }, returns => { - type => 'array', - items => { - type => "object", - properties => {}, - }, + type => 'array', + items => { + type => "object", + properties => {}, + }, }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); - my $storeid = $param->{storage}; + my $storeid = $param->{storage}; - my $cfg = PVE::Storage::config(); + my $cfg = PVE::Storage::config(); - PVE::Storage::storage_check_enabled($cfg, $storeid); + PVE::Storage::storage_check_enabled($cfg, $storeid); - die "Storage does not support templates!\n" if !$cfg->{ids}->{$storeid}->{content}->{vztmpl}; + die "Storage does not support templates!\n" + if !$cfg->{ids}->{$storeid}->{content}->{vztmpl}; - my $vollist = PVE::Storage::volume_list($cfg, $storeid, undef, 'vztmpl'); + my $vollist = PVE::Storage::volume_list($cfg, $storeid, undef, 'vztmpl'); - my $res = []; - foreach my $item (@$vollist) { - eval { PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $item->{volid}); }; - next if $@; - push @$res, $item; - } + my $res = []; + foreach my $item (@$vollist) { + eval { + PVE::Storage::check_volume_access( + $rpcenv, $authuser, $cfg, undef, $item->{volid}, + ); + }; + next if $@; + push @$res, $item; + } - return $res; - }}); + return $res; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'remove', path => 'remove', method => 'DELETE', description => "Remove a template.", permissions => { - description => "Only user who can create templates can remove them.", - check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']], + description => "Only user who can create templates can remove them.", + check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']], }, proxyto => 'node', protected => 1, parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - template_path => { - type => 'string', - description => "The template to remove.", - maxLength => 255, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + template_path => { + type => 'string', + description => "The template to remove.", + maxLength => 255, + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); + my $authuser = $rpcenv->get_user(); - my $template = $param->{template_path}; + my $template = $param->{template_path}; - my $cfg = PVE::Storage::config(); + my $cfg = PVE::Storage::config(); - PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $template, 'vztmpl'); + PVE::Storage::check_volume_access($rpcenv, $authuser, $cfg, undef, $template, 'vztmpl'); - my $abs_path = PVE::Storage::abs_filesystem_path($cfg, $template); + my $abs_path = PVE::Storage::abs_filesystem_path($cfg, $template); - unlink $abs_path; - - return undef; - }}); + unlink $abs_path; + return undef; + }, +}); my $print_list = sub { my ($list) = @_; - printf "%-60s %-6s\n", - qw(NAME SIZE); + printf "%-60s %-6s\n", qw(NAME SIZE); foreach my $rec (@$list) { - printf "%-60s %-4.2fMB\n", $rec->{volid}, $rec->{size}/(1024*1024); + printf "%-60s %-4.2fMB\n", $rec->{volid}, $rec->{size} / (1024 * 1024); } }; our $cmddef = { - update => [ __PACKAGE__, 'update', []], - download => [ 'PVE::API2::Nodes::Nodeinfo', 'apl_download', [ 'storage', 'template'], { node => $nodename } ], - available => [ __PACKAGE__, 'available', []], - list => [ __PACKAGE__, 'index', [ 'storage' ], { node => $nodename }, $print_list ], - remove => [ __PACKAGE__, 'remove', [ 'template_path' ], { node => $nodename }] + update => [__PACKAGE__, 'update', []], + download => [ + 'PVE::API2::Nodes::Nodeinfo', + 'apl_download', + ['storage', 'template'], + { node => $nodename }, + ], + available => [__PACKAGE__, 'available', []], + list => [__PACKAGE__, 'index', ['storage'], { node => $nodename }, $print_list], + remove => [__PACKAGE__, 'remove', ['template_path'], { node => $nodename }], }; 1; diff --git a/PVE/CLI/pveceph.pm b/PVE/CLI/pveceph.pm index 488aea04..e6da6b0c 100755 --- a/PVE/CLI/pveceph.pm +++ b/PVE/CLI/pveceph.pm @@ -45,69 +45,70 @@ sub setup_environment { PVE::RPCEnvironment->setup_default_cli_env(); } -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'purge', path => 'purge', method => 'POST', description => "Destroy ceph related data and configuration files.", parameters => { - additionalProperties => 0, - properties => { - logs => { - description => 'Additionally purge Ceph logs, /var/log/ceph.', - type => 'boolean', - optional => 1, - }, - crash => { - description => 'Additionally purge Ceph crash logs, /var/lib/ceph/crash.', - type => 'boolean', - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + logs => { + description => 'Additionally purge Ceph logs, /var/log/ceph.', + type => 'boolean', + optional => 1, + }, + crash => { + description => 'Additionally purge Ceph crash logs, /var/lib/ceph/crash.', + type => 'boolean', + optional => 1, + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $message; - my $pools = []; - my $monstat = {}; - my $mdsstat = {}; - my $osdstat = []; + my $message; + my $pools = []; + my $monstat = {}; + my $mdsstat = {}; + my $osdstat = []; - eval { - my $rados = PVE::RADOS->new(); - $pools = PVE::Ceph::Tools::ls_pools(undef, $rados); - $monstat = PVE::Ceph::Services::get_services_info('mon', undef, $rados); - $mdsstat = PVE::Ceph::Services::get_services_info('mds', undef, $rados); - $osdstat = $rados->mon_command({ prefix => 'osd metadata' }); - }; - warn "Error gathering ceph info, already purged? Message: $@" if $@; + eval { + my $rados = PVE::RADOS->new(); + $pools = PVE::Ceph::Tools::ls_pools(undef, $rados); + $monstat = PVE::Ceph::Services::get_services_info('mon', undef, $rados); + $mdsstat = PVE::Ceph::Services::get_services_info('mds', undef, $rados); + $osdstat = $rados->mon_command({ prefix => 'osd metadata' }); + }; + warn "Error gathering ceph info, already purged? Message: $@" if $@; - my $osd = grep { $_->{hostname} eq $nodename } @$osdstat; - my $mds = grep { $mdsstat->{$_}->{host} eq $nodename } keys %$mdsstat; - my $mon = grep { $monstat->{$_}->{host} eq $nodename } keys %$monstat; + my $osd = grep { $_->{hostname} eq $nodename } @$osdstat; + my $mds = grep { $mdsstat->{$_}->{host} eq $nodename } keys %$mdsstat; + my $mon = grep { $monstat->{$_}->{host} eq $nodename } keys %$monstat; - # no pools = no data - $message .= "- remove pools, this will !!DESTROY DATA!!\n" if @$pools; - $message .= "- remove active OSD on $nodename\n" if $osd; - $message .= "- remove active MDS on $nodename\n" if $mds; - $message .= "- remove other MONs, $nodename is not the last MON\n" - if scalar(keys %$monstat) > 1 && $mon; + # no pools = no data + $message .= "- remove pools, this will !!DESTROY DATA!!\n" if @$pools; + $message .= "- remove active OSD on $nodename\n" if $osd; + $message .= "- remove active MDS on $nodename\n" if $mds; + $message .= "- remove other MONs, $nodename is not the last MON\n" + if scalar(keys %$monstat) > 1 && $mon; - # display all steps at once - die "Unable to purge Ceph!\n\nTo continue:\n$message" if $message; + # display all steps at once + die "Unable to purge Ceph!\n\nTo continue:\n$message" if $message; - my $services = PVE::Ceph::Services::get_local_services(); - $services->{mon} = $monstat if $mon; - $services->{crash}->{$nodename} = { direxists => 1 } if $param->{crash}; - $services->{logs}->{$nodename} = { direxists => 1 } if $param->{logs}; + my $services = PVE::Ceph::Services::get_local_services(); + $services->{mon} = $monstat if $mon; + $services->{crash}->{$nodename} = { direxists => 1 } if $param->{crash}; + $services->{logs}->{$nodename} = { direxists => 1 } if $param->{logs}; - PVE::Ceph::Tools::purge_all_ceph_services($services); - PVE::Ceph::Tools::purge_all_ceph_files($services); + PVE::Ceph::Tools::purge_all_ceph_services($services); + PVE::Ceph::Tools::purge_all_ceph_files($services); - return undef; - }}); + return undef; + }, +}); my sub has_valid_subscription { my $info = eval { Proxmox::RS::Subscription::read_subscription('/etc/subscription') } // {}; @@ -118,155 +119,168 @@ my sub has_valid_subscription { my $available_ceph_release_codenames = PVE::Ceph::Releases::get_available_ceph_release_codenames(1); my $default_ceph_version = PVE::Ceph::Releases::get_default_ceph_release_codename(); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'install', path => 'install', method => 'POST', description => "Install ceph related packages.", parameters => { - additionalProperties => 0, - properties => { - version => { - type => 'string', - enum => $available_ceph_release_codenames, - default => $default_ceph_version, - description => "Ceph version to install.", - optional => 1, - }, - repository => { - type => 'string', - enum => ['enterprise', 'no-subscription', 'test'], - default => 'enterprise', - description => "Ceph repository to use.", - optional => 1, - }, - 'allow-experimental' => { - type => 'boolean', - default => 0, - optional => 1, - description => "Allow experimental versions. Use with care!", - }, - }, + additionalProperties => 0, + properties => { + version => { + type => 'string', + enum => $available_ceph_release_codenames, + default => $default_ceph_version, + description => "Ceph version to install.", + optional => 1, + }, + repository => { + type => 'string', + enum => ['enterprise', 'no-subscription', 'test'], + default => 'enterprise', + description => "Ceph repository to use.", + optional => 1, + }, + 'allow-experimental' => { + type => 'boolean', + default => 0, + optional => 1, + description => "Allow experimental versions. Use with care!", + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $cephver = $param->{version} || $default_ceph_version; + my $cephver = $param->{version} || $default_ceph_version; - my $repo = $param->{'repository'} // 'enterprise'; - my $enterprise_repo = $repo eq 'enterprise'; - my $cdn = $enterprise_repo ? 'https://enterprise.proxmox.com' : 'http://download.proxmox.com'; + my $repo = $param->{'repository'} // 'enterprise'; + my $enterprise_repo = $repo eq 'enterprise'; + my $cdn = + $enterprise_repo ? 'https://enterprise.proxmox.com' : 'http://download.proxmox.com'; - if (has_valid_subscription()) { - warn "\nNOTE: The node has an active subscription but a non-production Ceph repository selected.\n\n" - if !$enterprise_repo; - } elsif ($enterprise_repo) { - warn "\nWARN: Enterprise repository selected, but no active subscription!\n\n"; - } elsif ($repo eq 'no-subscription') { - warn "\nHINT: The no-subscription repository is not the best choice for production setups.\n" - ."Proxmox recommends using the enterprise repository with a valid subscription.\n"; - } else { - warn "\nWARN: The test repository should only be used for test setups or after consulting" - ." the official Proxmox support!\n\n" - } + if (has_valid_subscription()) { + warn + "\nNOTE: The node has an active subscription but a non-production Ceph repository selected.\n\n" + if !$enterprise_repo; + } elsif ($enterprise_repo) { + warn "\nWARN: Enterprise repository selected, but no active subscription!\n\n"; + } elsif ($repo eq 'no-subscription') { + warn + "\nHINT: The no-subscription repository is not the best choice for production setups.\n" + . "Proxmox recommends using the enterprise repository with a valid subscription.\n"; + } else { + warn + "\nWARN: The test repository should only be used for test setups or after consulting" + . " the official Proxmox support!\n\n"; + } - my $available_ceph_releases = PVE::Ceph::Releases::get_all_available_ceph_releases(); - die "unsupported ceph version: $cephver" if !exists($available_ceph_releases->{$cephver}); + my $available_ceph_releases = PVE::Ceph::Releases::get_all_available_ceph_releases(); + die "unsupported ceph version: $cephver" + if !exists($available_ceph_releases->{$cephver}); - my $repolist = "deb ${cdn}/debian/ceph-${cephver} bookworm $repo\n"; + my $repolist = "deb ${cdn}/debian/ceph-${cephver} bookworm $repo\n"; - my $rendered_release = $available_ceph_releases->{$cephver}->{release} . ' ' . ucfirst($cephver); - if (-t STDOUT && !$param->{version}) { - print "This will install Ceph ${rendered_release} - continue (y/N)? "; + my $rendered_release = + $available_ceph_releases->{$cephver}->{release} . ' ' . ucfirst($cephver); + if (-t STDOUT && !$param->{version}) { + print "This will install Ceph ${rendered_release} - continue (y/N)? "; - my $answer = ; - my $continue = defined($answer) && $answer =~ m/^\s*y(?:es)?\s*$/i; + my $answer = ; + my $continue = defined($answer) && $answer =~ m/^\s*y(?:es)?\s*$/i; - die "Aborting installation as requested\n" if !$continue; - } + die "Aborting installation as requested\n" if !$continue; + } - PVE::Tools::file_set_contents("/etc/apt/sources.list.d/ceph.list", $repolist); + PVE::Tools::file_set_contents("/etc/apt/sources.list.d/ceph.list", $repolist); - if ($available_ceph_releases->{$cephver}->{unsupported}) { - if ($param->{'allow-experimental'}) { - warn "NOTE: installing experimental/tech-preview Ceph release ${rendered_release}!\n"; - } elsif (-t STDOUT) { - print "Ceph ${rendered_release} is currently considered a technology preview for Proxmox VE - continue (y/N)? "; - my $answer = ; - my $continue = defined($answer) && $answer =~ m/^\s*y(?:es)?\s*$/i; + if ($available_ceph_releases->{$cephver}->{unsupported}) { + if ($param->{'allow-experimental'}) { + warn + "NOTE: installing experimental/tech-preview Ceph release ${rendered_release}!\n"; + } elsif (-t STDOUT) { + print + "Ceph ${rendered_release} is currently considered a technology preview for Proxmox VE - continue (y/N)? "; + my $answer = ; + my $continue = defined($answer) && $answer =~ m/^\s*y(?:es)?\s*$/i; - die "Aborting installation as requested\n" if !$continue; - } else { - die "refusing to install tech-preview Ceph release ${rendered_release} without 'allow-experimental' parameter!\n"; - } - } + die "Aborting installation as requested\n" if !$continue; + } else { + die + "refusing to install tech-preview Ceph release ${rendered_release} without 'allow-experimental' parameter!\n"; + } + } - local $ENV{DEBIAN_FRONTEND} = 'noninteractive'; - print "update available package list\n"; - eval { - run_command( - ['apt-get', '-q', 'update'], - outfunc => sub {}, - errfunc => sub { print STDERR "$_[0]\n" }, - ) - }; + local $ENV{DEBIAN_FRONTEND} = 'noninteractive'; + print "update available package list\n"; + eval { + run_command( + ['apt-get', '-q', 'update'], + outfunc => sub { }, + errfunc => sub { print STDERR "$_[0]\n" }, + ); + }; - my @apt_install = qw(apt-get --no-install-recommends -o Dpkg::Options::=--force-confnew install --); - my @ceph_packages = qw( - ceph - ceph-common - ceph-fuse - ceph-mds - ceph-volume - gdisk - nvme-cli - ); + my @apt_install = + qw(apt-get --no-install-recommends -o Dpkg::Options::=--force-confnew install --); + my @ceph_packages = qw( + ceph + ceph-common + ceph-fuse + ceph-mds + ceph-volume + gdisk + nvme-cli + ); - print "start installation\n"; + print "start installation\n"; - # this flag helps to determine when apt is actually done installing (vs. partial extracting) - my $install_flag_fn = PVE::Ceph::Tools::ceph_install_flag_file(); - open(my $install_flag, '>', $install_flag_fn) or die "could not create install flag - $!\n"; - close $install_flag; + # this flag helps to determine when apt is actually done installing (vs. partial extracting) + my $install_flag_fn = PVE::Ceph::Tools::ceph_install_flag_file(); + open(my $install_flag, '>', $install_flag_fn) + or die "could not create install flag - $!\n"; + close $install_flag; - if (system(@apt_install, @ceph_packages) != 0) { - unlink $install_flag_fn or warn "could not remove Ceph installation flag - $!"; - die "apt failed during ceph installation ($?)\n"; - } + if (system(@apt_install, @ceph_packages) != 0) { + unlink $install_flag_fn or warn "could not remove Ceph installation flag - $!"; + die "apt failed during ceph installation ($?)\n"; + } - print "\ninstalled Ceph ${rendered_release} successfully!\n"; - # done: drop flag file so that the PVE::Ceph::Tools check returns Ok now. - unlink $install_flag_fn or warn "could not remove Ceph installation flag - $!"; + print "\ninstalled Ceph ${rendered_release} successfully!\n"; + # done: drop flag file so that the PVE::Ceph::Tools check returns Ok now. + unlink $install_flag_fn or warn "could not remove Ceph installation flag - $!"; - print "\nreloading API to load new Ceph RADOS library...\n"; - run_command([ - 'systemctl', 'try-reload-or-restart', 'pvedaemon.service', 'pveproxy.service' - ]); + print "\nreloading API to load new Ceph RADOS library...\n"; + run_command([ + 'systemctl', 'try-reload-or-restart', 'pvedaemon.service', 'pveproxy.service', + ]); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'status', path => 'status', method => 'GET', description => "Get Ceph Status.", parameters => { - additionalProperties => 0, + additionalProperties => 0, }, returns => { type => 'null' }, code => sub { - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - run_command( - ['ceph', '-s'], - outfunc => sub { print "$_[0]\n" }, - errfunc => sub { print STDERR "$_[0]\n" }, - timeout => 15, - ); - return undef; - }}); + run_command( + ['ceph', '-s'], + outfunc => sub { print "$_[0]\n" }, + errfunc => sub { print STDERR "$_[0]\n" }, + timeout => 15, + ); + return undef; + }, +}); my $get_storages = sub { my ($fs, $is_default) = @_; @@ -276,159 +290,161 @@ my $get_storages = sub { my $storages = $cfg->{ids}; my $res = {}; foreach my $storeid (keys %$storages) { - my $curr = $storages->{$storeid}; - next if $curr->{type} ne 'cephfs'; - my $cur_fs = $curr->{'fs-name'}; - $res->{$storeid} = $storages->{$storeid} - if (!defined($cur_fs) && $is_default) || (defined($cur_fs) && $fs eq $cur_fs); + my $curr = $storages->{$storeid}; + next if $curr->{type} ne 'cephfs'; + my $cur_fs = $curr->{'fs-name'}; + $res->{$storeid} = $storages->{$storeid} + if (!defined($cur_fs) && $is_default) || (defined($cur_fs) && $fs eq $cur_fs); } return $res; }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'destroyfs', path => 'destroyfs', method => 'DELETE', description => "Destroy a Ceph filesystem", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - name => { - description => "The ceph filesystem name.", - type => 'string', - }, - 'remove-storages' => { - description => "Remove all pveceph-managed storages configured for this fs.", - type => 'boolean', - optional => 1, - default => 0, - }, - 'remove-pools' => { - description => "Remove data and metadata pools configured for this fs.", - type => 'boolean', - optional => 1, - default => 0, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + name => { + description => "The ceph filesystem name.", + type => 'string', + }, + 'remove-storages' => { + description => "Remove all pveceph-managed storages configured for this fs.", + type => 'boolean', + optional => 1, + default => 0, + }, + 'remove-pools' => { + description => "Remove data and metadata pools configured for this fs.", + type => 'boolean', + optional => 1, + default => 0, + }, + }, }, returns => { type => 'string' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $rpcenv = PVE::RPCEnvironment::get(); - my $user = $rpcenv->get_user(); + my $rpcenv = PVE::RPCEnvironment::get(); + my $user = $rpcenv->get_user(); - my $fs_name = $param->{name}; + my $fs_name = $param->{name}; - my $fs; - my $fs_list = PVE::Ceph::Tools::ls_fs(); - for my $entry (@$fs_list) { - next if $entry->{name} ne $fs_name; - $fs = $entry; - last; - } - die "no such cephfs '$fs_name'\n" if !$fs; + my $fs; + my $fs_list = PVE::Ceph::Tools::ls_fs(); + for my $entry (@$fs_list) { + next if $entry->{name} ne $fs_name; + $fs = $entry; + last; + } + die "no such cephfs '$fs_name'\n" if !$fs; - my $worker = sub { - my $rados = PVE::RADOS->new(); + my $worker = sub { + my $rados = PVE::RADOS->new(); - if ($param->{'remove-storages'}) { - my $defaultfs; - my $fs_dump = $rados->mon_command({ prefix => "fs dump" }); - for my $fs ($fs_dump->{filesystems}->@*) { - next if $fs->{id} != $fs_dump->{default_fscid}; - $defaultfs = $fs->{mdsmap}->{fs_name}; - } - warn "no default fs found, maybe not all relevant storages are removed\n" - if !defined($defaultfs); + if ($param->{'remove-storages'}) { + my $defaultfs; + my $fs_dump = $rados->mon_command({ prefix => "fs dump" }); + for my $fs ($fs_dump->{filesystems}->@*) { + next if $fs->{id} != $fs_dump->{default_fscid}; + $defaultfs = $fs->{mdsmap}->{fs_name}; + } + warn "no default fs found, maybe not all relevant storages are removed\n" + if !defined($defaultfs); - my $storages = $get_storages->($fs_name, $fs_name eq ($defaultfs // '')); - for my $storeid (keys %$storages) { - my $store = $storages->{$storeid}; - if (!$store->{disable}) { - die "storage '$storeid' is not disabled, make sure to disable ". - "and unmount the storage first\n"; - } - } + my $storages = $get_storages->($fs_name, $fs_name eq ($defaultfs // '')); + for my $storeid (keys %$storages) { + my $store = $storages->{$storeid}; + if (!$store->{disable}) { + die "storage '$storeid' is not disabled, make sure to disable " + . "and unmount the storage first\n"; + } + } - my $err; - for my $storeid (keys %$storages) { - # skip external clusters, not managed by pveceph - next if $storages->{$storeid}->{monhost}; - eval { PVE::API2::Storage::Config->delete({storage => $storeid}) }; - if ($@) { - warn "failed to remove storage '$storeid': $@\n"; - $err = 1; - } - } - die "failed to remove (some) storages - check log and remove manually!\n" - if $err; - } + my $err; + for my $storeid (keys %$storages) { + # skip external clusters, not managed by pveceph + next if $storages->{$storeid}->{monhost}; + eval { PVE::API2::Storage::Config->delete({ storage => $storeid }) }; + if ($@) { + warn "failed to remove storage '$storeid': $@\n"; + $err = 1; + } + } + die "failed to remove (some) storages - check log and remove manually!\n" + if $err; + } - PVE::Ceph::Tools::destroy_fs($fs_name, $rados); + PVE::Ceph::Tools::destroy_fs($fs_name, $rados); - if ($param->{'remove-pools'}) { - warn "removing metadata pool '$fs->{metadata_pool}'\n"; - eval { PVE::Ceph::Tools::destroy_pool($fs->{metadata_pool}, $rados) }; - warn "$@\n" if $@; + if ($param->{'remove-pools'}) { + warn "removing metadata pool '$fs->{metadata_pool}'\n"; + eval { PVE::Ceph::Tools::destroy_pool($fs->{metadata_pool}, $rados) }; + warn "$@\n" if $@; - foreach my $pool ($fs->{data_pools}->@*) { - warn "removing data pool '$pool'\n"; - eval { PVE::Ceph::Tools::destroy_pool($pool, $rados) }; - warn "$@\n" if $@; - } - } + foreach my $pool ($fs->{data_pools}->@*) { + warn "removing data pool '$pool'\n"; + eval { PVE::Ceph::Tools::destroy_pool($pool, $rados) }; + warn "$@\n" if $@; + } + } - }; - return $rpcenv->fork_worker('cephdestroyfs', $fs_name, $user, $worker); - }}); + }; + return $rpcenv->fork_worker('cephdestroyfs', $fs_name, $user, $worker); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'osd-details', path => 'osd-details', method => 'GET', description => "Get OSD details.", parameters => { - additionalProperties => 0, - properties => { - node => get_standard_option('pve-node'), - osdid => { - description => "ID of the OSD", - type => 'string', - }, - verbose => { - description => "Print verbose information, same as json-pretty output format.", - type => 'boolean', - default => 0, - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + osdid => { + description => "ID of the OSD", + type => 'string', + }, + verbose => { + description => "Print verbose information, same as json-pretty output format.", + type => 'boolean', + default => 0, + optional => 1, + }, + }, }, returns => { type => 'object' }, code => sub { - my ($param) = @_; + my ($param) = @_; - PVE::Ceph::Tools::check_ceph_inited(); + PVE::Ceph::Tools::check_ceph_inited(); - my $res = PVE::API2::Ceph::OSD->osddetails({ - osdid => $param->{osdid}, - node => $param->{node}, - }); + my $res = PVE::API2::Ceph::OSD->osddetails({ + osdid => $param->{osdid}, + node => $param->{node}, + }); - for my $dev ($res->{devices}->@*) { - $dev->{"lv-info"} = PVE::API2::Ceph::OSD->osdvolume({ - osdid => $param->{osdid}, - node => $param->{node}, - type => $dev->{device}, - }); - } - $res->{verbose} = 1 if $param->{verbose}; - return $res; - }}); + for my $dev ($res->{devices}->@*) { + $dev->{"lv-info"} = PVE::API2::Ceph::OSD->osdvolume({ + osdid => $param->{osdid}, + node => $param->{node}, + type => $dev->{device}, + }); + } + $res->{verbose} = 1 if $param->{verbose}; + return $res; + }, +}); my $format_osddetails = sub { my ($data, $schema, $options) = @_; @@ -436,91 +452,118 @@ my $format_osddetails = sub { $options->{"output-format"} //= "text"; if ($data->{verbose}) { - $options->{"output-format"} = "json-pretty"; - delete $data->{verbose}; + $options->{"output-format"} = "json-pretty"; + delete $data->{verbose}; } if ($options->{"output-format"} eq "text") { - for my $dev ($data->{devices}->@*) { - my ($disk, $type, $device) = $dev->@{'physical_device', 'type', 'device'}; - my ($lv_size, $lv_ctime) = $dev->{'lv-info'}->@{'lv_size', 'creation_time'}; + for my $dev ($data->{devices}->@*) { + my ($disk, $type, $device) = $dev->@{ 'physical_device', 'type', 'device' }; + my ($lv_size, $lv_ctime) = $dev->{'lv-info'}->@{ 'lv_size', 'creation_time' }; - $data->{osd}->{$device} = "Disk: $disk, Type: $type, LV Size: $lv_size, LV Creation Time: $lv_ctime"; - } - PVE::CLIFormatter::print_api_result($data->{osd}, $schema, undef, $options); + $data->{osd}->{$device} = + "Disk: $disk, Type: $type, LV Size: $lv_size, LV Creation Time: $lv_ctime"; + } + PVE::CLIFormatter::print_api_result($data->{osd}, $schema, undef, $options); } else { - PVE::CLIFormatter::print_api_result($data, $schema, undef, $options); + PVE::CLIFormatter::print_api_result($data, $schema, undef, $options); } }; our $cmddef = { - init => [ 'PVE::API2::Ceph', 'init', [], { node => $nodename } ], + init => ['PVE::API2::Ceph', 'init', [], { node => $nodename }], pool => { - ls => [ 'PVE::API2::Ceph::Pool', 'lspools', [], { node => $nodename }, sub { - my ($data, $schema, $options) = @_; - PVE::CLIFormatter::print_api_result($data, $schema, - [ - 'pool_name', - 'size', - 'min_size', - 'pg_num', - 'pg_num_min', - 'pg_num_final', - 'pg_autoscale_mode', - 'target_size', - 'target_size_ratio', - 'crush_rule_name', - 'percent_used', - 'bytes_used', - ], - $options); - }, $PVE::RESTHandler::standard_output_options], - create => [ 'PVE::API2::Ceph::Pool', 'createpool', ['name'], { node => $nodename }], - destroy => [ 'PVE::API2::Ceph::Pool', 'destroypool', ['name'], { node => $nodename } ], - set => [ 'PVE::API2::Ceph::Pool', 'setpool', ['name'], { node => $nodename } ], - get => [ 'PVE::API2::Ceph::Pool', 'getpool', ['name'], { node => $nodename }, sub { - my ($data, $schema, $options) = @_; - PVE::CLIFormatter::print_api_result($data, $schema, undef, $options); - }, $PVE::RESTHandler::standard_output_options], + ls => [ + 'PVE::API2::Ceph::Pool', + 'lspools', + [], + { node => $nodename }, + sub { + my ($data, $schema, $options) = @_; + PVE::CLIFormatter::print_api_result( + $data, + $schema, + [ + 'pool_name', + 'size', + 'min_size', + 'pg_num', + 'pg_num_min', + 'pg_num_final', + 'pg_autoscale_mode', + 'target_size', + 'target_size_ratio', + 'crush_rule_name', + 'percent_used', + 'bytes_used', + ], + $options, + ); + }, + $PVE::RESTHandler::standard_output_options, + ], + create => ['PVE::API2::Ceph::Pool', 'createpool', ['name'], { node => $nodename }], + destroy => ['PVE::API2::Ceph::Pool', 'destroypool', ['name'], { node => $nodename }], + set => ['PVE::API2::Ceph::Pool', 'setpool', ['name'], { node => $nodename }], + get => [ + 'PVE::API2::Ceph::Pool', + 'getpool', + ['name'], + { node => $nodename }, + sub { + my ($data, $schema, $options) = @_; + PVE::CLIFormatter::print_api_result($data, $schema, undef, $options); + }, + $PVE::RESTHandler::standard_output_options, + ], }, lspools => { alias => 'pool ls' }, createpool => { alias => 'pool create' }, destroypool => { alias => 'pool destroy' }, fs => { - create => [ 'PVE::API2::Ceph::FS', 'createfs', [], { node => $nodename }], - destroy => [ __PACKAGE__, 'destroyfs', ['name'], { node => $nodename }], + create => ['PVE::API2::Ceph::FS', 'createfs', [], { node => $nodename }], + destroy => [__PACKAGE__, 'destroyfs', ['name'], { node => $nodename }], }, osd => { - create => [ 'PVE::API2::Ceph::OSD', 'createosd', ['dev'], { node => $nodename }, $upid_exit], - destroy => [ 'PVE::API2::Ceph::OSD', 'destroyosd', ['osdid'], { node => $nodename }, $upid_exit], - details => [ - __PACKAGE__, 'osd-details', ['osdid'], { node => $nodename }, $format_osddetails, - $PVE::RESTHandler::standard_output_options, - ], + create => + ['PVE::API2::Ceph::OSD', 'createosd', ['dev'], { node => $nodename }, $upid_exit], + destroy => + ['PVE::API2::Ceph::OSD', 'destroyosd', ['osdid'], { node => $nodename }, $upid_exit], + details => [ + __PACKAGE__, + 'osd-details', + ['osdid'], + { node => $nodename }, + $format_osddetails, + $PVE::RESTHandler::standard_output_options, + ], }, createosd => { alias => 'osd create' }, destroyosd => { alias => 'osd destroy' }, mon => { - create => [ 'PVE::API2::Ceph::MON', 'createmon', [], { node => $nodename }, $upid_exit], - destroy => [ 'PVE::API2::Ceph::MON', 'destroymon', ['monid'], { node => $nodename }, $upid_exit], + create => ['PVE::API2::Ceph::MON', 'createmon', [], { node => $nodename }, $upid_exit], + destroy => + ['PVE::API2::Ceph::MON', 'destroymon', ['monid'], { node => $nodename }, $upid_exit], }, createmon => { alias => 'mon create' }, destroymon => { alias => 'mon destroy' }, mgr => { - create => [ 'PVE::API2::Ceph::MGR', 'createmgr', [], { node => $nodename }, $upid_exit], - destroy => [ 'PVE::API2::Ceph::MGR', 'destroymgr', ['id'], { node => $nodename }, $upid_exit], + create => ['PVE::API2::Ceph::MGR', 'createmgr', [], { node => $nodename }, $upid_exit], + destroy => + ['PVE::API2::Ceph::MGR', 'destroymgr', ['id'], { node => $nodename }, $upid_exit], }, createmgr => { alias => 'mgr create' }, destroymgr => { alias => 'mgr destroy' }, mds => { - create => [ 'PVE::API2::Ceph::MDS', 'createmds', [], { node => $nodename }, $upid_exit], - destroy => [ 'PVE::API2::Ceph::MDS', 'destroymds', ['name'], { node => $nodename }, $upid_exit], + create => ['PVE::API2::Ceph::MDS', 'createmds', [], { node => $nodename }, $upid_exit], + destroy => + ['PVE::API2::Ceph::MDS', 'destroymds', ['name'], { node => $nodename }, $upid_exit], }, - start => [ 'PVE::API2::Ceph', 'start', [], { node => $nodename }, $upid_exit], - stop => [ 'PVE::API2::Ceph', 'stop', [], { node => $nodename }, $upid_exit], - install => [ __PACKAGE__, 'install', [] ], - purge => [ __PACKAGE__, 'purge', [] ], - status => [ __PACKAGE__, 'status', []], + start => ['PVE::API2::Ceph', 'start', [], { node => $nodename }, $upid_exit], + stop => ['PVE::API2::Ceph', 'stop', [], { node => $nodename }, $upid_exit], + install => [__PACKAGE__, 'install', []], + purge => [__PACKAGE__, 'purge', []], + status => [__PACKAGE__, 'status', []], }; 1; diff --git a/PVE/CLI/pvenode.pm b/PVE/CLI/pvenode.pm index a6fbb34e..76b05887 100644 --- a/PVE/CLI/pvenode.pm +++ b/PVE/CLI/pvenode.pm @@ -43,22 +43,35 @@ sub param_mapping { my ($name) = @_; my $load_file_and_encode = sub { - my ($filename) = @_; + my ($filename) = @_; - return PVE::ACME::Challenge->encode_value('string', 'data', PVE::Tools::file_get_contents($filename)); + return PVE::ACME::Challenge->encode_value( + 'string', + 'data', + PVE::Tools::file_get_contents($filename), + ); }; my $mapping = { - 'upload_custom_cert' => [ - 'certificates', - 'key', - ], - 'add_plugin' => [ - ['data', $load_file_and_encode, "File with one key-value pair per line, will be base64url encode for storage in plugin config.", 0], - ], - 'update_plugin' => [ - ['data', $load_file_and_encode, "File with one key-value pair per line, will be base64url encode for storage in plugin config.", 0], - ], + 'upload_custom_cert' => [ + 'certificates', 'key', + ], + 'add_plugin' => [ + [ + 'data', + $load_file_and_encode, + "File with one key-value pair per line, will be base64url encode for storage in plugin config.", + 0, + ], + ], + 'update_plugin' => [ + [ + 'data', + $load_file_and_encode, + "File with one key-value pair per line, will be base64url encode for storage in plugin config.", + 0, + ], + ], }; return $mapping->{$name}; @@ -70,203 +83,297 @@ __PACKAGE__->register_method({ method => 'POST', description => "Register a new ACME account with a compatible CA.", parameters => { - additionalProperties => 0, - properties => { - name => get_standard_option('pve-acme-account-name'), - contact => get_standard_option('pve-acme-account-contact'), - directory => get_standard_option('pve-acme-directory-url', { - optional => 1, - }), - }, + additionalProperties => 0, + properties => { + name => get_standard_option('pve-acme-account-name'), + contact => get_standard_option('pve-acme-account-contact'), + directory => get_standard_option('pve-acme-directory-url', { + optional => 1, + }), + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $custom_directory = 0; - if (!$param->{directory}) { - my $directories = PVE::API2::ACMEAccount->get_directories({}); - print "Directory endpoints:\n"; - my $i = 0; - while ($i < @$directories) { - print $i, ") ", $directories->[$i]->{name}, " (", $directories->[$i]->{url}, ")\n"; - $i++; - } - print $i, ") Custom\n"; + my $custom_directory = 0; + if (!$param->{directory}) { + my $directories = PVE::API2::ACMEAccount->get_directories({}); + print "Directory endpoints:\n"; + my $i = 0; + while ($i < @$directories) { + print $i, ") ", $directories->[$i]->{name}, " (", $directories->[$i]->{url}, + ")\n"; + $i++; + } + print $i, ") Custom\n"; - my $term = Term::ReadLine->new('pvenode'); - my $get_dir_selection = sub { - my $selection = $term->readline("Enter selection: "); - if ($selection =~ /^(\d+)$/) { - $selection = $1; - if ($selection == $i) { - $param->{directory} = $term->readline("Enter custom URL: "); - $custom_directory = 1; - return; - } elsif ($selection < $i && $selection >= 0) { - $param->{directory} = $directories->[$selection]->{url}; - return; - } - } - print "Invalid selection.\n"; - }; + my $term = Term::ReadLine->new('pvenode'); + my $get_dir_selection = sub { + my $selection = $term->readline("Enter selection: "); + if ($selection =~ /^(\d+)$/) { + $selection = $1; + if ($selection == $i) { + $param->{directory} = $term->readline("Enter custom URL: "); + $custom_directory = 1; + return; + } elsif ($selection < $i && $selection >= 0) { + $param->{directory} = $directories->[$selection]->{url}; + return; + } + } + print "Invalid selection.\n"; + }; - my $attempts = 0; - while (!$param->{directory}) { - die "Aborting.\n" if $attempts > 3; - $get_dir_selection->(); - $attempts++; - } - } - print "\nAttempting to fetch Terms of Service from '$param->{directory}'..\n"; - my $meta = PVE::API2::ACMEAccount->get_meta({ directory => $param->{directory} }); - if ($meta->{termsOfService}) { - my $tos = $meta->{termsOfService}; - print "Terms of Service: $tos\n"; - my $term = Term::ReadLine->new('pvenode'); - my $agreed = $term->readline('Do you agree to the above terms? [y|N]: '); - die "Cannot continue without agreeing to ToS, aborting.\n" - if ($agreed !~ /^y$/i); + my $attempts = 0; + while (!$param->{directory}) { + die "Aborting.\n" if $attempts > 3; + $get_dir_selection->(); + $attempts++; + } + } + print "\nAttempting to fetch Terms of Service from '$param->{directory}'..\n"; + my $meta = PVE::API2::ACMEAccount->get_meta({ directory => $param->{directory} }); + if ($meta->{termsOfService}) { + my $tos = $meta->{termsOfService}; + print "Terms of Service: $tos\n"; + my $term = Term::ReadLine->new('pvenode'); + my $agreed = $term->readline('Do you agree to the above terms? [y|N]: '); + die "Cannot continue without agreeing to ToS, aborting.\n" + if ($agreed !~ /^y$/i); - $param->{tos_url} = $tos; - } else { - print "No Terms of Service found, proceeding.\n"; - } + $param->{tos_url} = $tos; + } else { + print "No Terms of Service found, proceeding.\n"; + } - my $eab_enabled = $meta->{externalAccountRequired}; - if (!$eab_enabled && $custom_directory) { - my $term = Term::ReadLine->new('pvenode'); - my $agreed = $term->readline('Do you want to use external account binding? [y|N]: '); - $eab_enabled = ($agreed =~ /^y$/i); - } elsif ($eab_enabled) { - print "The CA requires external account binding.\n"; - } - if ($eab_enabled) { - print "You should have received a key id and a key from your CA.\n"; - my $term = Term::ReadLine->new('pvenode'); - my $eab_kid = $term->readline('Enter EAB key id: '); - my $eab_hmac_key = $term->readline('Enter EAB key: '); + my $eab_enabled = $meta->{externalAccountRequired}; + if (!$eab_enabled && $custom_directory) { + my $term = Term::ReadLine->new('pvenode'); + my $agreed = + $term->readline('Do you want to use external account binding? [y|N]: '); + $eab_enabled = ($agreed =~ /^y$/i); + } elsif ($eab_enabled) { + print "The CA requires external account binding.\n"; + } + if ($eab_enabled) { + print "You should have received a key id and a key from your CA.\n"; + my $term = Term::ReadLine->new('pvenode'); + my $eab_kid = $term->readline('Enter EAB key id: '); + my $eab_hmac_key = $term->readline('Enter EAB key: '); - $param->{'eab-kid'} = $eab_kid; - $param->{'eab-hmac-key'} = $eab_hmac_key; - } + $param->{'eab-kid'} = $eab_kid; + $param->{'eab-hmac-key'} = $eab_hmac_key; + } - print "\nAttempting to register account with '$param->{directory}'..\n"; + print "\nAttempting to register account with '$param->{directory}'..\n"; - $upid_exit->(PVE::API2::ACMEAccount->register_account($param)); - }}); + $upid_exit->(PVE::API2::ACMEAccount->register_account($param)); + }, +}); my $print_cert_info = sub { my ($schema, $cert, $options) = @_; - my $order = [qw(filename fingerprint subject issuer notbefore notafter public-key-type public-key-bits san)]; + my $order = [ + qw(filename fingerprint subject issuer notbefore notafter public-key-type public-key-bits san) + ]; PVE::CLIFormatter::print_api_result( - $cert, $schema, $order, { %$options, noheader => 1, sort_key => 0 }); + $cert, + $schema, + $order, + { %$options, noheader => 1, sort_key => 0 }, + ); }; our $cmddef = { config => { - get => [ 'PVE::API2::NodeConfig', 'get_config', [], { node => $nodename }, sub { - my ($res) = @_; - print PVE::NodeConfig::write_node_config($res); - }], - set => [ 'PVE::API2::NodeConfig', 'set_options', [], { node => $nodename } ], + get => [ + 'PVE::API2::NodeConfig', + 'get_config', + [], + { node => $nodename }, + sub { + my ($res) = @_; + print PVE::NodeConfig::write_node_config($res); + }, + ], + set => ['PVE::API2::NodeConfig', 'set_options', [], { node => $nodename }], }, - startall => [ 'PVE::API2::Nodes::Nodeinfo', 'startall', [], { node => $nodename } ], - stopall => [ 'PVE::API2::Nodes::Nodeinfo', 'stopall', [], { node => $nodename } ], - migrateall => [ 'PVE::API2::Nodes::Nodeinfo', 'migrateall', [ 'target' ], { node => $nodename } ], + startall => ['PVE::API2::Nodes::Nodeinfo', 'startall', [], { node => $nodename }], + stopall => ['PVE::API2::Nodes::Nodeinfo', 'stopall', [], { node => $nodename }], + migrateall => + ['PVE::API2::Nodes::Nodeinfo', 'migrateall', ['target'], { node => $nodename }], cert => { - info => [ 'PVE::API2::Certificates', 'info', [], { node => $nodename }, sub { - my ($res, $schema, $options) = @_; + info => [ + 'PVE::API2::Certificates', + 'info', + [], + { node => $nodename }, + sub { + my ($res, $schema, $options) = @_; - if (!$options->{'output-format'} || $options->{'output-format'} eq 'text') { - for my $cert (sort { $a->{filename} cmp $b->{filename} } @$res) { - $print_cert_info->($schema->{items}, $cert, $options); - } - } else { - PVE::CLIFormatter::print_api_result($res, $schema, undef, $options); - } + if (!$options->{'output-format'} || $options->{'output-format'} eq 'text') { + for my $cert (sort { $a->{filename} cmp $b->{filename} } @$res) { + $print_cert_info->($schema->{items}, $cert, $options); + } + } else { + PVE::CLIFormatter::print_api_result($res, $schema, undef, $options); + } - }, $PVE::RESTHandler::standard_output_options], - set => [ 'PVE::API2::Certificates', 'upload_custom_cert', ['certificates', 'key'], { node => $nodename }, sub { - my ($res, $schema, $options) = @_; - $print_cert_info->($schema, $res, $options); - }, $PVE::RESTHandler::standard_output_options], - delete => [ 'PVE::API2::Certificates', 'remove_custom_cert', ['restart'], { node => $nodename } ], + }, + $PVE::RESTHandler::standard_output_options, + ], + set => [ + 'PVE::API2::Certificates', + 'upload_custom_cert', + ['certificates', 'key'], + { node => $nodename }, + sub { + my ($res, $schema, $options) = @_; + $print_cert_info->($schema, $res, $options); + }, + $PVE::RESTHandler::standard_output_options, + ], + delete => + ['PVE::API2::Certificates', 'remove_custom_cert', ['restart'], { node => $nodename }], }, task => { - list => [ 'PVE::API2::Tasks', 'node_tasks', [], { node => $nodename }, sub { - my ($data, $schema, $options) = @_; - foreach my $task (@$data) { - if (!defined($task->{status})) { - $task->{status} = 'UNKNOWN'; - # RUNNING is set by the API call and needs to be checked explicitly - } elsif (PVE::Tools::upid_status_is_error($task->{status}) && - $task->{status} ne 'RUNNING') - { - $task->{status} = 'ERROR'; - } - } - PVE::CLIFormatter::print_api_result($data, $schema, ['upid', 'type', 'id', 'user', 'starttime', 'endtime', 'status' ], $options); - }, $PVE::RESTHandler::standard_output_options], - status => [ 'PVE::API2::Tasks', 'read_task_status', [ 'upid' ], { node => $nodename }, sub { - my ($data, $schema, $options) = @_; - PVE::CLIFormatter::print_api_result($data, $schema, undef, $options); - }, $PVE::RESTHandler::standard_output_options], - # set limit to 1000000, so we see the whole log, not only the first 50 lines by default - log => [ 'PVE::API2::Tasks', 'read_task_log', [ 'upid' ], { node => $nodename, limit => 1000000 }, sub { - my ($data, $resultprops) = @_; - foreach my $line (@$data) { - print $line->{t} . "\n"; - } - }], + list => [ + 'PVE::API2::Tasks', + 'node_tasks', + [], + { node => $nodename }, + sub { + my ($data, $schema, $options) = @_; + foreach my $task (@$data) { + if (!defined($task->{status})) { + $task->{status} = 'UNKNOWN'; + # RUNNING is set by the API call and needs to be checked explicitly + } elsif ( + PVE::Tools::upid_status_is_error($task->{status}) + && $task->{status} ne 'RUNNING' + ) { + $task->{status} = 'ERROR'; + } + } + PVE::CLIFormatter::print_api_result( + $data, + $schema, + ['upid', 'type', 'id', 'user', 'starttime', 'endtime', 'status'], + $options, + ); + }, + $PVE::RESTHandler::standard_output_options, + ], + status => [ + 'PVE::API2::Tasks', + 'read_task_status', + ['upid'], + { node => $nodename }, + sub { + my ($data, $schema, $options) = @_; + PVE::CLIFormatter::print_api_result($data, $schema, undef, $options); + }, + $PVE::RESTHandler::standard_output_options, + ], + # set limit to 1000000, so we see the whole log, not only the first 50 lines by default + log => [ + 'PVE::API2::Tasks', + 'read_task_log', + ['upid'], + { node => $nodename, limit => 1000000 }, + sub { + my ($data, $resultprops) = @_; + foreach my $line (@$data) { + print $line->{t} . "\n"; + } + }, + ], }, acme => { - account => { - list => [ 'PVE::API2::ACMEAccount', 'account_index', [], {}, sub { - my ($res) = @_; - for my $acc (@$res) { - print "$acc->{name}\n"; - } - }], - register => [ __PACKAGE__, 'acme_register', ['name', 'contact'], {}, $upid_exit ], - deactivate => [ 'PVE::API2::ACMEAccount', 'deactivate_account', ['name'], {}, $upid_exit ], - info => [ 'PVE::API2::ACMEAccount', 'get_account', ['name'], {}, sub { - my ($data, $schema, $options) = @_; - PVE::CLIFormatter::print_api_result($data, $schema, undef, $options); - }, $PVE::RESTHandler::standard_output_options], - update => [ 'PVE::API2::ACMEAccount', 'update_account', ['name'], {}, $upid_exit ], - }, - cert => { - order => [ 'PVE::API2::ACME', 'new_certificate', [], { node => $nodename }, $upid_exit ], - renew => [ 'PVE::API2::ACME', 'renew_certificate', [], { node => $nodename }, $upid_exit ], - revoke => [ 'PVE::API2::ACME', 'revoke_certificate', [], { node => $nodename }, $upid_exit ], - }, - plugin => { - list => [ 'PVE::API2::ACMEPlugin', 'index', [], {}, sub { - my ($data, $schema, $options) = @_; - PVE::CLIFormatter::print_api_result($data, $schema, undef, $options); - }, $PVE::RESTHandler::standard_output_options ], - config => [ 'PVE::API2::ACMEPlugin', 'get_plugin_config', ['id'], {}, sub { - my ($data, $schema, $options) = @_; - PVE::CLIFormatter::print_api_result($data, $schema, undef, $options); - }, $PVE::RESTHandler::standard_output_options ], - add => [ 'PVE::API2::ACMEPlugin', 'add_plugin', ['type', 'id'] ], - set => [ 'PVE::API2::ACMEPlugin', 'update_plugin', ['id'] ], - remove => [ 'PVE::API2::ACMEPlugin', 'delete_plugin', ['id'] ], - }, + account => { + list => [ + 'PVE::API2::ACMEAccount', + 'account_index', + [], + {}, + sub { + my ($res) = @_; + for my $acc (@$res) { + print "$acc->{name}\n"; + } + }, + ], + register => [__PACKAGE__, 'acme_register', ['name', 'contact'], {}, $upid_exit], + deactivate => + ['PVE::API2::ACMEAccount', 'deactivate_account', ['name'], {}, $upid_exit], + info => [ + 'PVE::API2::ACMEAccount', + 'get_account', + ['name'], + {}, + sub { + my ($data, $schema, $options) = @_; + PVE::CLIFormatter::print_api_result($data, $schema, undef, $options); + }, + $PVE::RESTHandler::standard_output_options, + ], + update => ['PVE::API2::ACMEAccount', 'update_account', ['name'], {}, $upid_exit], + }, + cert => { + order => + ['PVE::API2::ACME', 'new_certificate', [], { node => $nodename }, $upid_exit], + renew => + ['PVE::API2::ACME', 'renew_certificate', [], { node => $nodename }, $upid_exit], + revoke => + ['PVE::API2::ACME', 'revoke_certificate', [], { node => $nodename }, $upid_exit], + }, + plugin => { + list => [ + 'PVE::API2::ACMEPlugin', + 'index', + [], + {}, + sub { + my ($data, $schema, $options) = @_; + PVE::CLIFormatter::print_api_result($data, $schema, undef, $options); + }, + $PVE::RESTHandler::standard_output_options, + ], + config => [ + 'PVE::API2::ACMEPlugin', + 'get_plugin_config', + ['id'], + {}, + sub { + my ($data, $schema, $options) = @_; + PVE::CLIFormatter::print_api_result($data, $schema, undef, $options); + }, + $PVE::RESTHandler::standard_output_options, + ], + add => ['PVE::API2::ACMEPlugin', 'add_plugin', ['type', 'id']], + set => ['PVE::API2::ACMEPlugin', 'update_plugin', ['id']], + remove => ['PVE::API2::ACMEPlugin', 'delete_plugin', ['id']], + }, }, - wakeonlan => [ 'PVE::API2::Nodes::Nodeinfo', 'wakeonlan', [ 'node' ], {}, sub { - my ($mac_addr) = @_; + wakeonlan => [ + 'PVE::API2::Nodes::Nodeinfo', + 'wakeonlan', + ['node'], + {}, + sub { + my ($mac_addr) = @_; - print "Wake on LAN packet send for '$mac_addr'\n"; - } ], + print "Wake on LAN packet send for '$mac_addr'\n"; + }, + ], }; diff --git a/PVE/CLI/pvesh.pm b/PVE/CLI/pvesh.pm index 0c21dd4e..2a994ee9 100755 --- a/PVE/CLI/pvesh.pm +++ b/PVE/CLI/pvesh.pm @@ -27,18 +27,18 @@ my $optmatch; do { $optmatch = 0; if ($ARGV[0]) { - if ($ARGV[0] eq '--noproxy') { - shift @ARGV; - $disable_proxy = 1; - $optmatch = 1; - } elsif ($ARGV[0] eq '--nooutput') { - # we use this when starting task in CLI (suppress printing upid) - # for example 'pvesh --nooutput create /nodes/localhost/stopall' - shift @ARGV; - $opt_nooutput = 1; - $optmatch = 1; - } - } + if ($ARGV[0] eq '--noproxy') { + shift @ARGV; + $disable_proxy = 1; + $optmatch = 1; + } elsif ($ARGV[0] eq '--nooutput') { + # we use this when starting task in CLI (suppress printing upid) + # for example 'pvesh --nooutput create /nodes/localhost/stopall' + shift @ARGV; + $opt_nooutput = 1; + $optmatch = 1; + } + } } while ($optmatch); sub setup_environment { @@ -46,7 +46,7 @@ sub setup_environment { } sub complete_api_path { - my($text) = @_; + my ($text) = @_; my ($dir, undef, $rest) = $text =~ m|^(.*/)?(([^/]*))?$|; @@ -60,16 +60,16 @@ sub complete_api_path { my $di = dir_info($path); if (my $children = $di->{children}) { - foreach my $c (@$children) { - if ($c =~ /^\Q$rest/) { - my $new = $dir ? "$dir$c" : $c; - push @$res, $new; - } - } + foreach my $c (@$children) { + if ($c =~ /^\Q$rest/) { + my $new = $dir ? "$dir$c" : $c; + push @$res, $new; + } + } } if (scalar(@$res) == 1) { - return [$res->[0], "$res->[0]/"]; + return [$res->[0], "$res->[0]/"]; } return $res; @@ -90,14 +90,18 @@ sub check_proxyto { my $all_params = { %$uri_param, %$params }; if ($info->{proxyto} || $info->{proxyto_callback}) { - my $node = PVE::API2Tools::resolve_proxyto( - $rpcenv, $info->{proxyto_callback}, $info->{proxyto}, $all_params); + my $node = PVE::API2Tools::resolve_proxyto( + $rpcenv, + $info->{proxyto_callback}, + $info->{proxyto}, + $all_params, + ); - if ($node ne 'localhost' && ($node ne PVE::INotify::nodename())) { - die "proxy loop detected - aborting\n" if $disable_proxy; - my $remip = PVE::Cluster::remote_node_ip($node); - return ($node, $remip); - } + if ($node ne 'localhost' && ($node ne PVE::INotify::nodename())) { + die "proxy loop detected - aborting\n" if $disable_proxy; + my $remip = PVE::Cluster::remote_node_ip($node); + return ($node, $remip); + } } return undef; @@ -108,32 +112,32 @@ sub proxy_handler { my $args = []; foreach my $key (keys %$param) { - next if $key eq 'quiet' || $key eq 'output-format'; # just to be sure - if (ref($param->{$key}) eq 'ARRAY') { - push @$args, "--$key", $_ for $param->{$key}->@*; - } else { - push @$args, "--$key", $_ for split(/\0/, $param->{$key}); - } + next if $key eq 'quiet' || $key eq 'output-format'; # just to be sure + if (ref($param->{$key}) eq 'ARRAY') { + push @$args, "--$key", $_ for $param->{$key}->@*; + } else { + push @$args, "--$key", $_ for split(/\0/, $param->{$key}); + } } my $ssh_tunnel_cmd = PVE::SSHInfo::ssh_info_to_command({ ip => $remip, name => $node }); my @pvesh_cmd = ('pvesh', '--noproxy', $cmd, $path, '--output-format', 'json'); if (scalar(@$args)) { - my $cmdargs = [ String::ShellQuote::shell_quote(@$args) ]; - push @pvesh_cmd, @$cmdargs; + my $cmdargs = [String::ShellQuote::shell_quote(@$args)]; + push @pvesh_cmd, @$cmdargs; } my $res = ''; PVE::Tools::run_command( - [ $ssh_tunnel_cmd->@*, '--', @pvesh_cmd ], - errmsg => "proxy handler failed", - outfunc => sub { $res .= shift }, + [$ssh_tunnel_cmd->@*, '--', @pvesh_cmd], + errmsg => "proxy handler failed", + outfunc => sub { $res .= shift }, ); my $decoded_json = eval { decode_json($res) }; if ($@) { - return $res; # do not error, '' (null) is valid too + return $res; # do not error, '' (null) is valid too } return $decoded_json; } @@ -147,13 +151,13 @@ sub extract_children { my $href = $lnk->{href}; if ($href =~ m/^\{(\S+)\}$/) { - my $prop = $1; + my $prop = $1; - foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @$data) { - next if !ref($elem); - my $value = $elem->{$prop}; - push @$res, $value; - } + foreach my $elem (sort { $a->{$prop} cmp $b->{$prop} } @$data) { + next if !ref($elem); + my $value = $elem->{$prop}; + push @$res, $value; + } } return $res; @@ -166,11 +170,11 @@ sub dir_info { my $uri_param = {}; my ($handler, $info, $pm) = PVE::API2->find_handler('GET', $path, $uri_param); if ($handler && $info) { - eval { - my $data = $handler->handle($info, $uri_param); - my $lnk = PVE::JSONSchema::method_get_child_link($info); - $res->{children} = extract_children($lnk, $data); - }; # ignore errors ? + eval { + my $data = $handler->handle($info, $uri_param); + my $lnk = PVE::JSONSchema::method_get_child_link($info); + $res->{children} = extract_children($lnk, $data); + }; # ignore errors ? } return $res; } @@ -182,34 +186,34 @@ sub resource_cap { my ($handler, $info) = PVE::API2->find_handler('GET', $path); if (!($handler && $info)) { - $res .= '--'; + $res .= '--'; } else { - if (PVE::JSONSchema::method_get_child_link($info)) { - $res .= 'Dr'; - } else { - $res .= '-r'; - } + if (PVE::JSONSchema::method_get_child_link($info)) { + $res .= 'Dr'; + } else { + $res .= '-r'; + } } ($handler, $info) = PVE::API2->find_handler('PUT', $path); if (!($handler && $info)) { - $res .= '-'; + $res .= '-'; } else { - $res .= 'w'; + $res .= 'w'; } ($handler, $info) = PVE::API2->find_handler('POST', $path); if (!($handler && $info)) { - $res .= '-'; + $res .= '-'; } else { - $res .= 'c'; + $res .= 'c'; } ($handler, $info) = PVE::API2->find_handler('DELETE', $path); if (!($handler && $info)) { - $res .= '-'; + $res .= '-'; } else { - $res .= 'd'; + $res .= 'd'; } return $res; @@ -224,52 +228,51 @@ sub extract_path_info { my ($handler, $info); my $test_path_properties = sub { - my ($method, $path) = @_; - ($handler, $info) = PVE::API2->find_handler($method, $path, $uri_param); + my ($method, $path) = @_; + ($handler, $info) = PVE::API2->find_handler($method, $path, $uri_param); }; if (defined(my $cmd = $ARGV[0])) { - if (my $method = $method_map->{$cmd}) { - if (my $path = $ARGV[1]) { - $test_path_properties->($method, $path); - if (!defined($handler)) { - print STDERR "No '$cmd' handler defined for '$path'\n"; - exit(1); - } - } - } elsif ($cmd eq 'bashcomplete') { - my $cmdline = substr($ENV{COMP_LINE}, 0, $ENV{COMP_POINT}); - my $args = PVE::Tools::split_args($cmdline); - if (defined(my $cmd = $args->[1])) { - if (my $method = $method_map->{$cmd}) { - if (my $path = $args->[2]) { - $test_path_properties->($method, $path); - } - } - } - } + if (my $method = $method_map->{$cmd}) { + if (my $path = $ARGV[1]) { + $test_path_properties->($method, $path); + if (!defined($handler)) { + print STDERR "No '$cmd' handler defined for '$path'\n"; + exit(1); + } + } + } elsif ($cmd eq 'bashcomplete') { + my $cmdline = substr($ENV{COMP_LINE}, 0, $ENV{COMP_POINT}); + my $args = PVE::Tools::split_args($cmdline); + if (defined(my $cmd = $args->[1])) { + if (my $method = $method_map->{$cmd}) { + if (my $path = $args->[2]) { + $test_path_properties->($method, $path); + } + } + } + } } return $info; } - my $path_properties = {}; my $api_path_property = { description => "API path.", type => 'string', completion => sub { - my ($cmd, $pname, $cur, $args) = @_; - return complete_api_path($cur); + my ($cmd, $pname, $cur, $args) = @_; + return complete_api_path($cur); }, }; my $uri_param = {}; if (my $info = extract_path_info($uri_param)) { - foreach my $key (keys %{$info->{parameters}->{properties}}) { - next if defined($uri_param->{$key}); - $path_properties->{$key} = $info->{parameters}->{properties}->{$key}; + foreach my $key (keys %{ $info->{parameters}->{properties} }) { + next if defined($uri_param->{$key}); + $path_properties->{$key} = $info->{parameters}->{properties}->{$key}; } } @@ -285,7 +288,8 @@ my $extract_std_options = 1; my $cond_add_standard_output_properties = sub { my ($props) = @_; - my $keys = [ grep { !defined($props->{$_}) } keys %$PVE::RESTHandler::standard_output_options ]; + my $keys = + [grep { !defined($props->{$_}) } keys %$PVE::RESTHandler::standard_output_options]; return PVE::RESTHandler::add_standard_output_properties($props, $keys); }; @@ -293,28 +297,30 @@ my $cond_add_standard_output_properties = sub { my $handle_streamed_response = sub { my ($download) = @_; my ($fh, $path, $encoding, $type) = - $download->@{'fh', 'path', 'content-encoding', 'content-type'}; + $download->@{ 'fh', 'path', 'content-encoding', 'content-type' }; - die "{download} returned but neither fh nor path given\n" if !defined($fh) && !defined($path); + die "{download} returned but neither fh nor path given\n" + if !defined($fh) && !defined($path); die "unknown 'content-encoding' $encoding\n" if defined($encoding) && $encoding ne 'gzip'; - die "unknown 'content-type' $type\n" if defined($type) && $type !~ qw!^(?:text/plain|application/json)$!; + die "unknown 'content-type' $type\n" + if defined($type) && $type !~ qw!^(?:text/plain|application/json)$!; if (defined($path)) { - open($fh, '<', $path) or die "open stream path '$path' for reading failed - $!\n"; + open($fh, '<', $path) or die "open stream path '$path' for reading failed - $!\n"; } local $/; my $data = <$fh>; if (defined($encoding)) { - my $out; - gunzip(\$data => \$out); - $data = $out; + my $out; + gunzip(\$data => \$out); + $data = $out; } if (defined($type) && $type eq 'application/json') { - $data = decode_json($data)->{data}; + $data = decode_json($data)->{data}; } return $data; @@ -328,7 +334,8 @@ sub call_api_method { my $path = PVE::Tools::extract_param($param, 'api_path'); die "missing API path\n" if !defined($path); - my $stdopts = $extract_std_options + my $stdopts = + $extract_std_options ? PVE::RESTHandler::extract_standard_output_properties($param) : {}; @@ -337,28 +344,28 @@ sub call_api_method { my $uri_param = {}; my ($handler, $info) = PVE::API2->find_handler($method, $path, $uri_param); if (!$handler || !$info) { - die "no '$cmd' handler for '$path'\n"; + die "no '$cmd' handler for '$path'\n"; } my $data; my ($node, $remip) = check_proxyto($info, $uri_param, $param); if ($node) { - $data = proxy_handler($node, $remip, $path, $cmd, $param); + $data = proxy_handler($node, $remip, $path, $cmd, $param); } else { - foreach my $p (keys %$uri_param) { - $param->{$p} = $uri_param->{$p}; - } + foreach my $p (keys %$uri_param) { + $param->{$p} = $uri_param->{$p}; + } - $data = $handler->handle($info, $param); + $data = $handler->handle($info, $param); - # TODO: remove 'download' check with PVE 9.0 - if ( - ref($data) eq 'HASH' - && ref($data->{download}) eq 'HASH' - && ($info->{download_allowed} || $info->{download}) - ) { - $data = $handle_streamed_response->($data->{download}) - } + # TODO: remove 'download' check with PVE 9.0 + if ( + ref($data) eq 'HASH' + && ref($data->{download}) eq 'HASH' + && ($info->{download_allowed} || $info->{download}) + ) { + $data = $handle_streamed_response->($data->{download}); + } } return if $opt_nooutput || $stdopts->{quiet}; @@ -366,208 +373,226 @@ sub call_api_method { PVE::CLIFormatter::print_api_result($data, $info->{returns}, undef, $stdopts); } -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'ls', path => 'ls', method => 'GET', description => "List child objects on .", parameters => { - additionalProperties => 0, - properties => $cond_add_standard_output_properties->($path_properties), + additionalProperties => 0, + properties => $cond_add_standard_output_properties->($path_properties), }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $path = PVE::Tools::extract_param($param, 'api_path'); + my $path = PVE::Tools::extract_param($param, 'api_path'); - my $stdopts = PVE::RESTHandler::extract_standard_output_properties($param); + my $stdopts = PVE::RESTHandler::extract_standard_output_properties($param); - my $uri_param = {}; - my ($handler, $info) = PVE::API2->find_handler('GET', $path, $uri_param); - if (!$handler || !$info) { - die "no such resource '$path'\n"; - } + my $uri_param = {}; + my ($handler, $info) = PVE::API2->find_handler('GET', $path, $uri_param); + if (!$handler || !$info) { + die "no such resource '$path'\n"; + } - my $link = PVE::JSONSchema::method_get_child_link($info); - die "resource '$path' does not define child links\n" if !$link; + my $link = PVE::JSONSchema::method_get_child_link($info); + die "resource '$path' does not define child links\n" if !$link; - my $res; + my $res; - my ($node, $remip) = check_proxyto($info, $uri_param, $param); - if ($node) { - $res = proxy_handler($node, $remip, $path, 'ls', $param); - } else { - foreach my $p (keys %$uri_param) { - $param->{$p} = $uri_param->{$p}; - } + my ($node, $remip) = check_proxyto($info, $uri_param, $param); + if ($node) { + $res = proxy_handler($node, $remip, $path, 'ls', $param); + } else { + foreach my $p (keys %$uri_param) { + $param->{$p} = $uri_param->{$p}; + } - my $data = $handler->handle($info, $param); + my $data = $handler->handle($info, $param); - my $children = extract_children($link, $data); + my $children = extract_children($link, $data); - $res = []; - foreach my $c (@$children) { - my $item = { name => $c, capabilities => resource_cap("$path/$c")}; - push @$res, $item; - } - } + $res = []; + foreach my $c (@$children) { + my $item = { name => $c, capabilities => resource_cap("$path/$c") }; + push @$res, $item; + } + } - my $schema = { type => 'array', items => { type => 'object' }}; - $stdopts->{sort_key} = 'name'; - $stdopts->{noborder} //= 1; - $stdopts->{noheader} //= 1; - PVE::CLIFormatter::print_api_result($res, $schema, ['capabilities', 'name'], $stdopts); + my $schema = { type => 'array', items => { type => 'object' } }; + $stdopts->{sort_key} = 'name'; + $stdopts->{noborder} //= 1; + $stdopts->{noheader} //= 1; + PVE::CLIFormatter::print_api_result($res, $schema, ['capabilities', 'name'], $stdopts); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'get', path => 'get', method => 'GET', description => "Call API GET on .", parameters => { - additionalProperties => 0, - properties => $cond_add_standard_output_properties->($path_properties), + additionalProperties => 0, + properties => $cond_add_standard_output_properties->($path_properties), }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - call_api_method('get', $param); + call_api_method('get', $param); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'set', path => 'set', method => 'PUT', description => "Call API PUT on .", parameters => { - additionalProperties => 0, - properties => $cond_add_standard_output_properties->($path_properties), + additionalProperties => 0, + properties => $cond_add_standard_output_properties->($path_properties), }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - call_api_method('set', $param); + call_api_method('set', $param); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'create', path => 'create', method => 'POST', description => "Call API POST on .", parameters => { - additionalProperties => 0, - properties => $cond_add_standard_output_properties->($path_properties), + additionalProperties => 0, + properties => $cond_add_standard_output_properties->($path_properties), }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - call_api_method('create', $param); + call_api_method('create', $param); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'delete', path => 'delete', method => 'DELETE', description => "Call API DELETE on .", parameters => { - additionalProperties => 0, - properties => $cond_add_standard_output_properties->($path_properties), + additionalProperties => 0, + properties => $cond_add_standard_output_properties->($path_properties), }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - call_api_method('delete', $param); + call_api_method('delete', $param); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'usage', path => 'usage', method => 'GET', description => "print API usage information for .", parameters => { - additionalProperties => 0, - properties => { - api_path => $api_path_property, - verbose => { - description => "Verbose output format.", - type => 'boolean', - optional => 1, - }, - returns => { - description => "Including schema for returned data.", - type => 'boolean', - optional => 1, - }, - command => { - description => "API command.", - type => 'string', - enum => [ keys %$method_map ], - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + api_path => $api_path_property, + verbose => { + description => "Verbose output format.", + type => 'boolean', + optional => 1, + }, + returns => { + description => "Including schema for returned data.", + type => 'boolean', + optional => 1, + }, + command => { + description => "API command.", + type => 'string', + enum => [keys %$method_map], + optional => 1, + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $path = $param->{api_path}; + my $path = $param->{api_path}; - my $found = 0; - foreach my $cmd (qw(get set create delete)) { - next if $param->{command} && $cmd ne $param->{command}; - my $method = $method_map->{$cmd}; - my $uri_param = {}; - my ($handler, $info) = PVE::API2->find_handler($method, $path, $uri_param); - next if !$handler; - $found = 1; + my $found = 0; + foreach my $cmd (qw(get set create delete)) { + next if $param->{command} && $cmd ne $param->{command}; + my $method = $method_map->{$cmd}; + my $uri_param = {}; + my ($handler, $info) = PVE::API2->find_handler($method, $path, $uri_param); + next if !$handler; + $found = 1; - if ($param->{verbose}) { - print $handler->usage_str( - $info->{name}, "pvesh $cmd $path", undef, $uri_param, 'full'); + if ($param->{verbose}) { + print $handler->usage_str( + $info->{name}, + "pvesh $cmd $path", + undef, + $uri_param, + 'full', + ); - } else { - print "USAGE: " . $handler->usage_str( - $info->{name}, "pvesh $cmd $path", undef, $uri_param, 'short'); - } - if ($param-> {returns}) { - my $schema = to_json($info->{returns}, {utf8 => 1, canonical => 1, pretty => 1 }); - print "RETURNS: $schema\n"; - } - } + } else { + print "USAGE: " + . $handler->usage_str( + $info->{name}, + "pvesh $cmd $path", + undef, + $uri_param, + 'short', + ); + } + if ($param->{returns}) { + my $schema = + to_json($info->{returns}, { utf8 => 1, canonical => 1, pretty => 1 }); + print "RETURNS: $schema\n"; + } + } - if (!$found) { - if ($param->{command}) { - die "no '$param->{command}' handler for '$path'\n"; - } else { - die "no such resource '$path'\n" - } - } + if (!$found) { + if ($param->{command}) { + die "no '$param->{command}' handler for '$path'\n"; + } else { + die "no such resource '$path'\n"; + } + } - return undef; - }}); + return undef; + }, +}); our $cmddef = { - usage => [ __PACKAGE__, 'usage', ['api_path']], - get => [ __PACKAGE__, 'get', ['api_path']], - ls => [ __PACKAGE__, 'ls', ['api_path']], - set => [ __PACKAGE__, 'set', ['api_path']], - create => [ __PACKAGE__, 'create', ['api_path']], - delete => [ __PACKAGE__, 'delete', ['api_path']], + usage => [__PACKAGE__, 'usage', ['api_path']], + get => [__PACKAGE__, 'get', ['api_path']], + ls => [__PACKAGE__, 'ls', ['api_path']], + set => [__PACKAGE__, 'set', ['api_path']], + create => [__PACKAGE__, 'create', ['api_path']], + delete => [__PACKAGE__, 'delete', ['api_path']], }; 1; diff --git a/PVE/CLI/pvesr.pm b/PVE/CLI/pvesr.pm index 95dad64e..09040cef 100644 --- a/PVE/CLI/pvesr.pm +++ b/PVE/CLI/pvesr.pm @@ -36,306 +36,329 @@ my $check_wanted_volid = sub { my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid); my $scfg = PVE::Storage::storage_check_enabled($storecfg, $storeid, $local_node); die "storage '$storeid' is not replicatable\n" - if !$replicatable_storage_types->{$scfg->{type}}; + if !$replicatable_storage_types->{ $scfg->{type} }; my ($vtype, undef, $ownervm) = PVE::Storage::parse_volname($storecfg, $volid); die "volume '$volid' has wrong vtype ($vtype != 'images')\n" - if $vtype ne 'images'; + if $vtype ne 'images'; die "volume '$volid' has wrong owner\n" - if !$ownervm || $vmid != $ownervm; + if !$ownervm || $vmid != $ownervm; return $storeid; }; -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'prepare_local_job', path => 'prepare_local_job', method => 'POST', - description => "Prepare for starting a replication job. This is called on the target node before replication starts. This call is for internal use, and return a JSON object on stdout. The method first test if VM 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 . 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 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 . It also removes any unused volumes. Returns a hash with boolean markers for all volumes with existing replication snapshots.", parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-replication-id'), - 'extra-args' => get_standard_option('extra-args', { - description => "The list of volume IDs to consider." }), - scan => { - description => "List of storage IDs to scan for stale volumes.", - type => 'string', format => 'pve-storage-id-list', - optional => 1, - }, - force => { - description => "Allow to remove all existion volumes (empty volume list).", - type => 'boolean', - optional => 1, - default => 0, - }, - last_sync => { - description => "Time (UNIX epoch) of last successful sync. If not specified, all replication snapshots get removed.", - type => 'integer', - minimum => 0, - optional => 1, - }, - parent_snapname => get_standard_option('pve-snapshot-name', { + additionalProperties => 0, + properties => { + id => get_standard_option('pve-replication-id'), + 'extra-args' => get_standard_option( + 'extra-args', + { description => "The list of volume IDs to consider." }, + ), + scan => { + description => "List of storage IDs to scan for stale volumes.", + type => 'string', + format => 'pve-storage-id-list', optional => 1, + }, + force => { + description => "Allow to remove all existion volumes (empty volume list).", + type => 'boolean', + optional => 1, + default => 0, + }, + last_sync => { + description => + "Time (UNIX epoch) of last successful sync. If not specified, all replication snapshots get removed.", + type => 'integer', + minimum => 0, + optional => 1, + }, + parent_snapname => get_standard_option('pve-snapshot-name', { + optional => 1, }), - }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $logfunc = sub { - my ($msg) = @_; - print STDERR "$msg\n"; - }; + my $logfunc = sub { + my ($msg) = @_; + print STDERR "$msg\n"; + }; - my $local_node = PVE::INotify::nodename(); + my $local_node = PVE::INotify::nodename(); - die "no volumes specified\n" - if !$param->{force} && !scalar(@{$param->{'extra-args'}}); + die "no volumes specified\n" + if !$param->{force} && !scalar(@{ $param->{'extra-args'} }); - my ($vmid, undef, $jobid) = PVE::ReplicationConfig::parse_replication_job_id($param->{id}); + my ($vmid, undef, $jobid) = + PVE::ReplicationConfig::parse_replication_job_id($param->{id}); - my $vms = PVE::Cluster::get_vmlist(); - die "guest '$vmid' is on local node\n" - if $vms->{ids}->{$vmid} && $vms->{ids}->{$vmid}->{node} eq $local_node; + my $vms = PVE::Cluster::get_vmlist(); + die "guest '$vmid' is on local node\n" + if $vms->{ids}->{$vmid} && $vms->{ids}->{$vmid}->{node} eq $local_node; - my $last_sync = $param->{last_sync} // 0; - my $parent_snapname = $param->{parent_snapname}; + my $last_sync = $param->{last_sync} // 0; + my $parent_snapname = $param->{parent_snapname}; - my $storecfg = PVE::Storage::config(); + my $storecfg = PVE::Storage::config(); - # compute list of storages we want to scan - my $storage_hash = {}; - foreach my $storeid (PVE::Tools::split_list($param->{scan})) { - my $scfg = PVE::Storage::storage_check_enabled($storecfg, $storeid, $local_node, 1); - next if !$scfg; # simply ignore unavailable storages here - die "storage '$storeid' is not replicatable\n" if !$replicatable_storage_types->{$scfg->{type}}; - $storage_hash->{$storeid} = 1; - } + # compute list of storages we want to scan + my $storage_hash = {}; + foreach my $storeid (PVE::Tools::split_list($param->{scan})) { + my $scfg = PVE::Storage::storage_check_enabled($storecfg, $storeid, $local_node, 1); + next if !$scfg; # simply ignore unavailable storages here + die "storage '$storeid' is not replicatable\n" + if !$replicatable_storage_types->{ $scfg->{type} }; + $storage_hash->{$storeid} = 1; + } - my $wanted_volids = {}; - foreach my $volid (@{$param->{'extra-args'}}) { - my $storeid = $check_wanted_volid->($storecfg, $vmid, $volid, $local_node); - $wanted_volids->{$volid} = 1; - $storage_hash->{$storeid} = 1; - } - my $storage_list = [ sort keys %$storage_hash ]; + my $wanted_volids = {}; + foreach my $volid (@{ $param->{'extra-args'} }) { + my $storeid = $check_wanted_volid->($storecfg, $vmid, $volid, $local_node); + $wanted_volids->{$volid} = 1; + $storage_hash->{$storeid} = 1; + } + my $storage_list = [sort keys %$storage_hash]; - # activate all used storage - my $cache = {}; - PVE::Storage::activate_storage_list($storecfg, $storage_list, $cache); + # activate all used storage + my $cache = {}; + PVE::Storage::activate_storage_list($storecfg, $storage_list, $cache); - my $snapname = PVE::ReplicationState::replication_snapshot_name($jobid, $last_sync); + my $snapname = PVE::ReplicationState::replication_snapshot_name($jobid, $last_sync); - # find replication snapshots - my $volids = []; - foreach my $storeid (@$storage_list) { - my $scfg = PVE::Storage::storage_config($storecfg, $storeid); - my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); - my $images = $plugin->list_images($storeid, $scfg, $vmid, undef, $cache); - push @$volids, map { $_->{volid} } @$images; - } - my ($local_snapshots, $cleaned_replicated_volumes) = PVE::Replication::prepare($storecfg, $volids, $jobid, $last_sync, $parent_snapname, $logfunc); - for my $volid ($volids->@*) { - next if $wanted_volids->{$volid}; + # find replication snapshots + my $volids = []; + foreach my $storeid (@$storage_list) { + my $scfg = PVE::Storage::storage_config($storecfg, $storeid); + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); + my $images = $plugin->list_images($storeid, $scfg, $vmid, undef, $cache); + push @$volids, map { $_->{volid} } @$images; + } + my ($local_snapshots, $cleaned_replicated_volumes) = PVE::Replication::prepare( + $storecfg, $volids, $jobid, $last_sync, $parent_snapname, $logfunc, + ); + for my $volid ($volids->@*) { + next if $wanted_volids->{$volid}; - my $stale = $cleaned_replicated_volumes->{$volid}; - # prepare() will not remove the last_sync snapshot, but if the volume was used by the - # job and is not wanted anymore, it is stale too. And not removing it now might cause - # it to be missed later, because the relevant storage might not get scanned anymore. - $stale ||= grep { - PVE::Replication::is_replication_snapshot($_, $jobid) - } keys %{$local_snapshots->{$volid} // {}}; + my $stale = $cleaned_replicated_volumes->{$volid}; + # prepare() will not remove the last_sync snapshot, but if the volume was used by the + # job and is not wanted anymore, it is stale too. And not removing it now might cause + # it to be missed later, because the relevant storage might not get scanned anymore. + $stale ||= grep { + PVE::Replication::is_replication_snapshot($_, $jobid) + } keys %{ $local_snapshots->{$volid} // {} }; - if ($stale) { - $logfunc->("$jobid: delete stale volume '$volid'"); - PVE::Storage::vdisk_free($storecfg, $volid); - delete $local_snapshots->{$volid}; - } - } + if ($stale) { + $logfunc->("$jobid: delete stale volume '$volid'"); + PVE::Storage::vdisk_free($storecfg, $volid); + delete $local_snapshots->{$volid}; + } + } - print to_json($local_snapshots) . "\n"; + print to_json($local_snapshots) . "\n"; - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'finalize_local_job', path => 'finalize_local_job', method => 'POST', - description => "Finalize a replication job. This removes all replications snapshots with timestamps different than .", + description => + "Finalize a replication job. This removes all replications snapshots with timestamps different than .", parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-replication-id'), - 'extra-args' => get_standard_option('extra-args', { - description => "The list of volume IDs to consider." }), - last_sync => { - description => "Time (UNIX epoch) of last successful sync. If not specified, all replication snapshots gets removed.", - type => 'integer', - minimum => 0, - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + id => get_standard_option('pve-replication-id'), + 'extra-args' => get_standard_option( + 'extra-args', + { description => "The list of volume IDs to consider." }, + ), + last_sync => { + description => + "Time (UNIX epoch) of last successful sync. If not specified, all replication snapshots gets removed.", + type => 'integer', + minimum => 0, + optional => 1, + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my ($vmid, undef, $jobid) = PVE::ReplicationConfig::parse_replication_job_id($param->{id}); - my $last_sync = $param->{last_sync} // 0; + my ($vmid, undef, $jobid) = + PVE::ReplicationConfig::parse_replication_job_id($param->{id}); + my $last_sync = $param->{last_sync} // 0; - my $local_node = PVE::INotify::nodename(); + my $local_node = PVE::INotify::nodename(); - my $vms = PVE::Cluster::get_vmlist(); - die "guest '$vmid' is on local node\n" - if $vms->{ids}->{$vmid} && $vms->{ids}->{$vmid}->{node} eq $local_node; + my $vms = PVE::Cluster::get_vmlist(); + die "guest '$vmid' is on local node\n" + if $vms->{ids}->{$vmid} && $vms->{ids}->{$vmid}->{node} eq $local_node; - my $storecfg = PVE::Storage::config(); + my $storecfg = PVE::Storage::config(); - my $volids = []; + my $volids = []; - die "no volumes specified\n" if !scalar(@{$param->{'extra-args'}}); + die "no volumes specified\n" if !scalar(@{ $param->{'extra-args'} }); - foreach my $volid (@{$param->{'extra-args'}}) { - $check_wanted_volid->($storecfg, $vmid, $volid, $local_node); - push @$volids, $volid; - } + foreach my $volid (@{ $param->{'extra-args'} }) { + $check_wanted_volid->($storecfg, $vmid, $volid, $local_node); + push @$volids, $volid; + } - $volids = [ sort @$volids ]; + $volids = [sort @$volids]; - my $logfunc = sub { - my ($msg) = @_; - print STDERR "$msg\n"; - }; + my $logfunc = sub { + my ($msg) = @_; + print STDERR "$msg\n"; + }; - PVE::Replication::prepare($storecfg, $volids, $jobid, $last_sync, undef, $logfunc); + PVE::Replication::prepare($storecfg, $volids, $jobid, $last_sync, undef, $logfunc); - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'run', path => 'run', method => 'POST', - description => "This method is called by the systemd-timer and executes all (or a specific) sync jobs.", + description => + "This method is called by the systemd-timer and executes all (or a specific) sync jobs.", parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-replication-id', { optional => 1 }), - verbose => { - description => "Print more verbose logs to stdout.", - type => 'boolean', - default => 0, - optional => 1, - }, - mail => { - description => "Send an email notification in case of a failure.", - type => 'boolean', - default => 0, - optional => 1, - }, - }, + additionalProperties => 0, + properties => { + id => get_standard_option('pve-replication-id', { optional => 1 }), + verbose => { + description => "Print more verbose logs to stdout.", + type => 'boolean', + default => 0, + optional => 1, + }, + mail => { + description => "Send an email notification in case of a failure.", + type => 'boolean', + default => 0, + optional => 1, + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - die "Mail and id are mutually exclusive!\n" - if $param->{id} && $param->{mail}; + die "Mail and id are mutually exclusive!\n" + if $param->{id} && $param->{mail}; - my $logfunc; + my $logfunc; - if ($param->{verbose}) { - $logfunc = sub { - my ($msg) = @_; - print "$msg\n"; - }; - } + if ($param->{verbose}) { + $logfunc = sub { + my ($msg) = @_; + print "$msg\n"; + }; + } - if (my $id = extract_param($param, 'id')) { + if (my $id = extract_param($param, 'id')) { - PVE::API2::Replication::run_single_job($id, undef, $logfunc); + PVE::API2::Replication::run_single_job($id, undef, $logfunc); - } else { + } else { - PVE::API2::Replication::run_jobs(undef, $logfunc, 0, $param->{mail}); - } + PVE::API2::Replication::run_jobs(undef, $logfunc, 0, $param->{mail}); + } - return undef; - }}); + return undef; + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'enable', path => 'enable', method => 'POST', description => "Enable a replication job.", parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-replication-id'), - }, + additionalProperties => 0, + properties => { + id => get_standard_option('pve-replication-id'), + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - $param->{disable} = 0; + $param->{disable} = 0; - return PVE::API2::ReplicationConfig->update($param); - }}); + return PVE::API2::ReplicationConfig->update($param); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'disable', path => 'disable', method => 'POST', description => "Disable a replication job.", parameters => { - additionalProperties => 0, - properties => { - id => get_standard_option('pve-replication-id'), - }, + additionalProperties => 0, + properties => { + id => get_standard_option('pve-replication-id'), + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - $param->{disable} = 1; + $param->{disable} = 1; - return PVE::API2::ReplicationConfig->update($param); - }}); + return PVE::API2::ReplicationConfig->update($param); + }, +}); -__PACKAGE__->register_method ({ +__PACKAGE__->register_method({ name => 'set_state', path => '', protected => 1, method => 'POST', - description => "Set the job replication state on migration. This call is for internal use. It will accept the job state as ja JSON obj.", + description => + "Set the job replication state on migration. This call is for internal use. It will accept the job state as ja JSON obj.", permissions => { - check => ['perm', '/storage', ['Datastore.Allocate']], + check => ['perm', '/storage', ['Datastore.Allocate']], }, parameters => { - additionalProperties => 0, - properties => { - vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_vmid }), - state => { - description => "Job state as JSON decoded string.", - type => 'string', - }, - }, + additionalProperties => 0, + properties => { + vmid => + get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_vmid }), + state => { + description => "Job state as JSON decoded string.", + type => 'string', + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $vmid = extract_param($param, 'vmid'); - my $json_string = extract_param($param, 'state'); - my $remote_job_state= decode_json $json_string; + my $vmid = extract_param($param, 'vmid'); + my $json_string = extract_param($param, 'state'); + my $remote_job_state = decode_json $json_string; - PVE::ReplicationState::write_vmid_job_states($remote_job_state, $vmid); - return undef; - }}); + PVE::ReplicationState::write_vmid_job_states($remote_job_state, $vmid); + return undef; + }, +}); my $print_job_list = sub { my ($list) = @_; @@ -345,14 +368,16 @@ my $print_job_list = sub { printf($format, "JobID", "Target", "Schedule", "Rate", "Enabled"); foreach my $job (sort { $a->{guest} <=> $b->{guest} } @$list) { - my $plugin = PVE::ReplicationConfig->lookup($job->{type}); - my $tid = $plugin->get_unique_target_id($job); + my $plugin = PVE::ReplicationConfig->lookup($job->{type}); + my $tid = $plugin->get_unique_target_id($job); - printf($format, $job->{id}, $tid, - defined($job->{schedule}) ? $job->{schedule} : '*/15', - defined($job->{rate}) ? $job->{rate} : '-', - $job->{disable} ? 'no' : 'yes' - ); + printf( + $format, + $job->{id}, $tid, + defined($job->{schedule}) ? $job->{schedule} : '*/15', + defined($job->{rate}) ? $job->{rate} : '-', + $job->{disable} ? 'no' : 'yes', + ); } }; @@ -361,56 +386,73 @@ my $print_job_status = sub { my $format = "%-10s %-10s %-20s %20s %20s %10s %10s %s\n"; - printf($format, "JobID", "Enabled", "Target", "LastSync", "NextSync", "Duration", "FailCount", "State"); + printf($format, + "JobID", + "Enabled", + "Target", + "LastSync", + "NextSync", + "Duration", + "FailCount", + "State"); foreach my $job (sort { $a->{guest} <=> $b->{guest} } @$list) { - my $plugin = PVE::ReplicationConfig->lookup($job->{type}); - my $tid = $plugin->get_unique_target_id($job); + my $plugin = PVE::ReplicationConfig->lookup($job->{type}); + my $tid = $plugin->get_unique_target_id($job); - my $timestr = '-'; - if ($job->{last_sync}) { - $timestr = strftime("%Y-%m-%d_%H:%M:%S", localtime($job->{last_sync})); - } + my $timestr = '-'; + if ($job->{last_sync}) { + $timestr = strftime("%Y-%m-%d_%H:%M:%S", localtime($job->{last_sync})); + } - my $nextstr = '-'; - if (my $next = $job->{next_sync}) { - my $now = time(); - if ($next > $now) { - $nextstr = strftime("%Y-%m-%d_%H:%M:%S", localtime($job->{next_sync})); - } else { - $nextstr = 'pending'; - } - } + my $nextstr = '-'; + if (my $next = $job->{next_sync}) { + my $now = time(); + if ($next > $now) { + $nextstr = strftime("%Y-%m-%d_%H:%M:%S", localtime($job->{next_sync})); + } else { + $nextstr = 'pending'; + } + } - my $state = $job->{pid} ? "SYNCING" : $job->{error} // 'OK'; - my $enabled = $job->{disable} ? 'No' : 'Yes'; + my $state = $job->{pid} ? "SYNCING" : $job->{error} // 'OK'; + my $enabled = $job->{disable} ? 'No' : 'Yes'; - printf($format, $job->{id}, $enabled, $tid, - $timestr, $nextstr, $job->{duration} // '-', - $job->{fail_count}, $state); + printf($format, + $job->{id}, $enabled, $tid, $timestr, $nextstr, $job->{duration} // '-', + $job->{fail_count}, $state); } }; our $cmddef = { - status => [ 'PVE::API2::Replication', 'status', [], { node => $nodename }, $print_job_status ], - 'schedule-now' => [ 'PVE::API2::Replication', 'schedule_now', ['id'], { node => $nodename }], + status => + ['PVE::API2::Replication', 'status', [], { node => $nodename }, $print_job_status], + 'schedule-now' => ['PVE::API2::Replication', 'schedule_now', ['id'], { node => $nodename }], - list => [ 'PVE::API2::ReplicationConfig', 'index' , [], {}, $print_job_list ], - read => [ 'PVE::API2::ReplicationConfig', 'read' , ['id'], {}, - sub { my $res = shift; print to_json($res, { utf8 => 1, pretty => 1, canonical => 1}); }], - update => [ 'PVE::API2::ReplicationConfig', 'update' , ['id'], {} ], - delete => [ 'PVE::API2::ReplicationConfig', 'delete' , ['id'], {} ], - 'create-local-job' => [ 'PVE::API2::ReplicationConfig', 'create' , ['id', 'target'], - { type => 'local' } ], + list => ['PVE::API2::ReplicationConfig', 'index', [], {}, $print_job_list], + read => [ + 'PVE::API2::ReplicationConfig', + 'read', + ['id'], + {}, + sub { + my $res = shift; + print to_json($res, { utf8 => 1, pretty => 1, canonical => 1 }); + }, + ], + update => ['PVE::API2::ReplicationConfig', 'update', ['id'], {}], + delete => ['PVE::API2::ReplicationConfig', 'delete', ['id'], {}], + 'create-local-job' => + ['PVE::API2::ReplicationConfig', 'create', ['id', 'target'], { type => 'local' }], - enable => [ __PACKAGE__, 'enable', ['id'], {}], - disable => [ __PACKAGE__, 'disable', ['id'], {}], + enable => [__PACKAGE__, 'enable', ['id'], {}], + disable => [__PACKAGE__, 'disable', ['id'], {}], - 'prepare-local-job' => [ __PACKAGE__, 'prepare_local_job', ['id', 'extra-args'], {} ], - 'finalize-local-job' => [ __PACKAGE__, 'finalize_local_job', ['id', 'extra-args'], {} ], + 'prepare-local-job' => [__PACKAGE__, 'prepare_local_job', ['id', 'extra-args'], {}], + 'finalize-local-job' => [__PACKAGE__, 'finalize_local_job', ['id', 'extra-args'], {}], - run => [ __PACKAGE__ , 'run'], - 'set-state' => [ __PACKAGE__ , 'set_state', ['vmid', 'state']], + run => [__PACKAGE__, 'run'], + 'set-state' => [__PACKAGE__, 'set_state', ['vmid', 'state']], }; 1; diff --git a/PVE/CLI/pvesubscription.pm b/PVE/CLI/pvesubscription.pm index 51f5b71e..214dc009 100755 --- a/PVE/CLI/pvesubscription.pm +++ b/PVE/CLI/pvesubscription.pm @@ -25,42 +25,51 @@ __PACKAGE__->register_method({ name => 'set_offline_key', path => 'set_offline_key', method => 'POST', - description => "Internal use only! To set an offline key, use the package proxmox-offline-mirror-helper instead.", + description => + "Internal use only! To set an offline key, use the package proxmox-offline-mirror-helper instead.", parameters => { - additionalProperties => 0, - properties => { - data => { - description => "A signed subscription info blob", - type => "string", - }, - }, + additionalProperties => 0, + properties => { + data => { + description => "A signed subscription info blob", + type => "string", + }, + }, }, returns => { type => 'null' }, code => sub { - my ($param) = @_; + my ($param) = @_; - my $info = decode_json(decode_base64($param->{data})); + my $info = decode_json(decode_base64($param->{data})); - $info = Proxmox::RS::Subscription::check_signature($info); - $info = Proxmox::RS::Subscription::check_server_id($info); - $info = Proxmox::RS::Subscription::check_age($info, 0); + $info = Proxmox::RS::Subscription::check_signature($info); + $info = Proxmox::RS::Subscription::check_server_id($info); + $info = Proxmox::RS::Subscription::check_age($info, 0); - PVE::API2::Subscription::check_key($info->{key}, PVE::API2::Subscription::get_sockets()); + PVE::API2::Subscription::check_key($info->{key}, + PVE::API2::Subscription::get_sockets()); - PVE::API2::Subscription::write_etc_subscription($info); + PVE::API2::Subscription::write_etc_subscription($info); - return; -}}); + return; + }, +}); our $cmddef = { - update => [ 'PVE::API2::Subscription', 'update', undef, { node => $nodename } ], - get => [ 'PVE::API2::Subscription', 'get', undef, { node => $nodename }, sub { - my $info = shift; - print "$_: $info->{$_}\n" for sort keys %$info; - }], - set => [ 'PVE::API2::Subscription', 'set', ['key'], { node => $nodename } ], - "set-offline-key" => [ __PACKAGE__, 'set_offline_key', ['data'] ], - delete => [ 'PVE::API2::Subscription', 'delete', undef, { node => $nodename } ], + update => ['PVE::API2::Subscription', 'update', undef, { node => $nodename }], + get => [ + 'PVE::API2::Subscription', + 'get', + undef, + { node => $nodename }, + sub { + my $info = shift; + print "$_: $info->{$_}\n" for sort keys %$info; + }, + ], + set => ['PVE::API2::Subscription', 'set', ['key'], { node => $nodename }], + "set-offline-key" => [__PACKAGE__, 'set_offline_key', ['data']], + delete => ['PVE::API2::Subscription', 'delete', undef, { node => $nodename }], }; 1; diff --git a/PVE/CLI/vzdump.pm b/PVE/CLI/vzdump.pm index 3f625574..e4787740 100755 --- a/PVE/CLI/vzdump.pm +++ b/PVE/CLI/vzdump.pm @@ -14,12 +14,17 @@ sub setup_environment { } # Note: use string 'vmid' as $arg_param option, to allow vmid lists -our $cmddef = [ 'PVE::API2::VZDump', 'vzdump', 'vmid', undef, - sub { - my $upid = shift; - exit(0) if $upid eq 'OK'; - my $status = PVE::Tools::upid_read_status($upid); - exit(PVE::Tools::upid_status_is_error($status) ? -1 : 0); - }]; +our $cmddef = [ + 'PVE::API2::VZDump', + 'vzdump', + 'vmid', + undef, + sub { + my $upid = shift; + exit(0) if $upid eq 'OK'; + my $status = PVE::Tools::upid_read_status($upid); + exit(PVE::Tools::upid_status_is_error($status) ? -1 : 0); + }, +]; 1; diff --git a/PVE/Ceph/Releases.pm b/PVE/Ceph/Releases.pm index d8c33bbf..76a6ae28 100644 --- a/PVE/Ceph/Releases.pm +++ b/PVE/Ceph/Releases.pm @@ -5,14 +5,14 @@ use v5.36; use PVE::pvecfg; my sub get_current_pve_major_release { - my $release_tuples = [ split(/\./, PVE::pvecfg::release()) ]; + my $release_tuples = [split(/\./, PVE::pvecfg::release())]; return $release_tuples->[0]; } my $_ceph_release_info; my sub get_ceph_release_def { if (defined($_ceph_release_info)) { - return $_ceph_release_info; + return $_ceph_release_info; } # Track all ceph releases (from a initial cut-off) with some metainfo. @@ -34,56 +34,57 @@ my sub get_ceph_release_def { # NOTE: very old releases got left out from the list, but there's _no_ need to clean-up # periodically check https://docs.ceph.com/en/latest/releases/ for all past releases my $ceph_release_info = { - octopus => { - release => '15.2', - 'initial-upstream-release' => '2020-03-23', - 'estimated-end-of-upstream-support' => '2022-08-09', - 'available-for-pve-release' => { - 6 => 1, - 7 => 1, - }, - }, - pacific => { - release => '16.2', - 'initial-upstream-release' => '2021-03-31', - 'estimated-end-of-upstream-support' => '2024-03-04', - 'available-for-pve-release' => { - 7 => 1, - }, - }, - quincy => { - release => '17.2', - 'current-backend-default' => 1, - 'initial-upstream-release' => '2022-04-19', - 'estimated-end-of-upstream-support' => '2024-06-01', - 'available-for-pve-release' => { - 7 => 1, - 8 => 1, - }, - }, - reef => { - release => '18.2', - 'initial-upstream-release' => '2023-08-07', - 'estimated-end-of-upstream-support' => '2025-08-01', - 'available-for-pve-release' => { - 8 => 1, - }, - }, - squid => { - release => '19.2', - 'initial-upstream-release' => '2024-09-27', - 'estimated-end-of-upstream-support' => '2026-09-19', - 'available-for-pve-release' => { - 8 => 1, - }, - }, + octopus => { + release => '15.2', + 'initial-upstream-release' => '2020-03-23', + 'estimated-end-of-upstream-support' => '2022-08-09', + 'available-for-pve-release' => { + 6 => 1, + 7 => 1, + }, + }, + pacific => { + release => '16.2', + 'initial-upstream-release' => '2021-03-31', + 'estimated-end-of-upstream-support' => '2024-03-04', + 'available-for-pve-release' => { + 7 => 1, + }, + }, + quincy => { + release => '17.2', + 'current-backend-default' => 1, + 'initial-upstream-release' => '2022-04-19', + 'estimated-end-of-upstream-support' => '2024-06-01', + 'available-for-pve-release' => { + 7 => 1, + 8 => 1, + }, + }, + reef => { + release => '18.2', + 'initial-upstream-release' => '2023-08-07', + 'estimated-end-of-upstream-support' => '2025-08-01', + 'available-for-pve-release' => { + 8 => 1, + }, + }, + squid => { + release => '19.2', + 'initial-upstream-release' => '2024-09-27', + 'estimated-end-of-upstream-support' => '2026-09-19', + 'available-for-pve-release' => { + 8 => 1, + }, + }, }; my $current_pve_major_release = get_current_pve_major_release(); for my $codename (sort keys $ceph_release_info->%*) { - my $ceph_release = $ceph_release_info->{$codename}; - $ceph_release->{codename} = $codename; - $ceph_release->{'available-for-current-pve-release'} = $ceph_release->{'available-for-pve-release'}->{$current_pve_major_release}; + my $ceph_release = $ceph_release_info->{$codename}; + $ceph_release->{codename} = $codename; + $ceph_release->{'available-for-current-pve-release'} = + $ceph_release->{'available-for-pve-release'}->{$current_pve_major_release}; } $_ceph_release_info = $ceph_release_info; @@ -95,15 +96,16 @@ sub get_ceph_release_info($codename) { } my $_available_ceph_releases; + sub get_all_available_ceph_releases { if (!defined($_available_ceph_releases)) { - my $ceph_releases = get_ceph_release_def(); - $_available_ceph_releases = {}; - for my $codename (sort keys $ceph_releases->%*) { - if ($ceph_releases->{$codename}->{'available-for-current-pve-release'}) { - $_available_ceph_releases->{$codename} = $ceph_releases->{$codename}; - } - } + my $ceph_releases = get_ceph_release_def(); + $_available_ceph_releases = {}; + for my $codename (sort keys $ceph_releases->%*) { + if ($ceph_releases->{$codename}->{'available-for-current-pve-release'}) { + $_available_ceph_releases->{$codename} = $ceph_releases->{$codename}; + } + } } return $_available_ceph_releases; } @@ -112,21 +114,22 @@ sub get_available_ceph_release_codenames($include_unstable_releases = 0) { my $available_releases = get_all_available_ceph_releases(); return $include_unstable_releases - ? [ sort keys $available_releases->%* ] - : [ grep { !$available_releases->{$_}->{unsupported} } sort keys $available_releases->%* ] - ; + ? [sort keys $available_releases->%*] + : [grep { !$available_releases->{$_}->{unsupported} } sort keys $available_releases->%*]; } my $_default_ceph_release_codename; + sub get_default_ceph_release_codename { if (!defined($_default_ceph_release_codename)) { - my $ceph_releases = get_all_available_ceph_releases(); - my @default_release = grep { $ceph_releases->{$_}->{'current-backend-default'} } keys $ceph_releases->%*; - die "internal error: got multiple ceph releases with 'current-backend-default' set\n" - if scalar(@default_release) > 1; - die "internal error: got no ceph releases with 'current-backend-default' set\n" - if scalar(@default_release) < 1; - $_default_ceph_release_codename = $default_release[0]; + my $ceph_releases = get_all_available_ceph_releases(); + my @default_release = + grep { $ceph_releases->{$_}->{'current-backend-default'} } keys $ceph_releases->%*; + die "internal error: got multiple ceph releases with 'current-backend-default' set\n" + if scalar(@default_release) > 1; + die "internal error: got no ceph releases with 'current-backend-default' set\n" + if scalar(@default_release) < 1; + $_default_ceph_release_codename = $default_release[0]; } return $_default_ceph_release_codename; } diff --git a/PVE/Ceph/Services.pm b/PVE/Ceph/Services.pm index 04bf296d..4c3f3d99 100644 --- a/PVE/Ceph/Services.pm +++ b/PVE/Ceph/Services.pm @@ -19,21 +19,29 @@ sub get_local_services { my $res = {}; for my $type (qw(mds mgr mon)) { - $res->{$type} = {}; + $res->{$type} = {}; - my $path = "/etc/systemd/system/ceph-$type.target.wants"; - my $regex = "ceph-$type\@(.*)\.service"; - PVE::Tools::dir_glob_foreach($path, $regex, sub { - my (undef, $id) = @_; - $res->{$type}->{$id}->{service} = 1; - }); + my $path = "/etc/systemd/system/ceph-$type.target.wants"; + my $regex = "ceph-$type\@(.*)\.service"; + PVE::Tools::dir_glob_foreach( + $path, + $regex, + sub { + my (undef, $id) = @_; + $res->{$type}->{$id}->{service} = 1; + }, + ); - $path = "/var/lib/ceph/$type"; - $regex = "([^-]+)-(.*)"; - PVE::Tools::dir_glob_foreach($path, $regex, sub { - my (undef, $clustername, $id) = @_; - $res->{$type}->{$id}->{direxists} = 1; - }); + $path = "/var/lib/ceph/$type"; + $regex = "([^-]+)-(.*)"; + PVE::Tools::dir_glob_foreach( + $path, + $regex, + sub { + my (undef, $clustername, $id) = @_; + $res->{$type}->{$id}->{direxists} = 1; + }, + ); } return $res; } @@ -42,8 +50,8 @@ sub broadcast_ceph_services { my $services = get_local_services(); for my $type (keys %$services) { - my $data = encode_json($services->{$type}); - PVE::Cluster::broadcast_node_kv("ceph-$type", $data); + my $data = encode_json($services->{$type}); + PVE::Cluster::broadcast_node_kv("ceph-$type", $data); } } @@ -55,19 +63,23 @@ sub broadcast_ceph_versions { my $nodename = PVE::INotify::nodename(); my $old_versions = PVE::Cluster::get_node_kv("ceph-versions", $nodename); if (length(my $old_version_raw = $old_versions->{$nodename})) { - my $old = eval { decode_json($old_version_raw) }; - warn "failed to parse ceph-versions '$old_version_raw' as JSON - $@" if $@; # should not happen - if (defined($old) && $old->{buildcommit} eq $buildcommit && $old->{version}->{str} eq $version) { - return; # up to date, nothing to do so avoid (not exactly cheap) broadcast - } + my $old = eval { decode_json($old_version_raw) }; + warn "failed to parse ceph-versions '$old_version_raw' as JSON - $@" if $@; # should not happen + if ( + defined($old) + && $old->{buildcommit} eq $buildcommit + && $old->{version}->{str} eq $version + ) { + return; # up to date, nothing to do so avoid (not exactly cheap) broadcast + } } my $node_versions = { - version => { - str => $version, - parts => $vers_parts, - }, - buildcommit => $buildcommit, + version => { + str => $version, + parts => $vers_parts, + }, + buildcommit => $buildcommit, }; PVE::Cluster::broadcast_node_kv("ceph-versions", encode_json($node_versions)); } @@ -76,9 +88,11 @@ sub get_ceph_versions { my $res; if (defined(my $versions = PVE::Cluster::get_node_kv("ceph-versions"))) { - $res = { - map { eval { $_ => decode_json($versions->{$_}) } } keys %$versions - }; + $res = { + map { + eval { $_ => decode_json($versions->{$_}) } + } keys %$versions + }; } return $res; @@ -89,7 +103,9 @@ sub get_cluster_service { my $raw = PVE::Cluster::get_node_kv("ceph-$type"); my $res = { - map { $_ => eval { decode_json($raw->{$_}) } } keys $raw->%* + map { + $_ => eval { decode_json($raw->{$_}) } + } keys $raw->%* }; return $res; @@ -99,9 +115,9 @@ sub ceph_service_cmd { my ($action, $service) = @_; if ($service && $service =~ m/^(mon|osd|mds|mgr|radosgw)(\.(${\SERVICE_REGEX}))?$/) { - $service = defined($3) ? "ceph-$1\@$3" : "ceph-$1.target"; + $service = defined($3) ? "ceph-$1\@$3" : "ceph-$1.target"; } else { - $service = "ceph.target"; + $service = "ceph.target"; } run_command(['/bin/systemctl', $action, $service]); @@ -114,46 +130,46 @@ sub get_services_info { my $services = get_cluster_service($type); foreach my $host (sort keys %$services) { - foreach my $id (sort keys %{$services->{$host}}) { - my $service = $result->{$id} = $services->{$host}->{$id}; - $service->{host} = $host; - $service->{name} = $id; - $service->{state} = 'unknown'; - if ($service->{service}) { - $service->{state} = 'stopped'; - } - } + foreach my $id (sort keys %{ $services->{$host} }) { + my $service = $result->{$id} = $services->{$host}->{$id}; + $service->{host} = $host; + $service->{name} = $id; + $service->{state} = 'unknown'; + if ($service->{service}) { + $service->{state} = 'stopped'; + } + } } if (!$cfg) { - $cfg = cfs_read_file('ceph.conf'); + $cfg = cfs_read_file('ceph.conf'); } foreach my $section (keys %$cfg) { - my $d = $cfg->{$section}; - if ($section =~ m/^$type\.(\S+)$/) { - my $id = $1; - my $service = $result->{$id}; - my $addr = $d->{"${type}_addr"} // $d->{public_addr} // $d->{host}; - $service->{name} //= $id; - $service->{addr} //= $addr; - $service->{state} //= 'unknown'; - $service->{host} //= $d->{host}; - } + my $d = $cfg->{$section}; + if ($section =~ m/^$type\.(\S+)$/) { + my $id = $1; + my $service = $result->{$id}; + my $addr = $d->{"${type}_addr"} // $d->{public_addr} // $d->{host}; + $service->{name} //= $id; + $service->{addr} //= $addr; + $service->{state} //= 'unknown'; + $service->{host} //= $d->{host}; + } } if (!$rados) { - return $result; + return $result; } my $metadata = $rados->mon_command({ prefix => "$type metadata" }); foreach my $info (@$metadata) { - my $id = $info->{name} // $info->{id}; - my $service = $result->{$id}; - $service->{ceph_version_short} = $info->{ceph_version_short}; - $service->{ceph_version} = $info->{ceph_version}; - $service->{host} //= $info->{hostname}; - $service->{addr} //= $info->{addr}; + my $id = $info->{name} // $info->{id}; + my $service = $result->{$id}; + $service->{ceph_version_short} = $info->{ceph_version_short}; + $service->{ceph_version} = $info->{ceph_version}; + $service->{host} //= $info->{hostname}; + $service->{addr} //= $info->{addr}; } return $result; @@ -166,10 +182,14 @@ sub list_local_mds_ids { my $ceph_mds_data_dir = PVE::Ceph::Tools::get_config('ceph_mds_data_dir'); my $ccname = PVE::Ceph::Tools::get_config('ccname'); - PVE::Tools::dir_glob_foreach($ceph_mds_data_dir, qr/$ccname-(\S+)/, sub { - my (undef, $mds_id) = @_; - push @$mds_list, $mds_id; - }); + PVE::Tools::dir_glob_foreach( + $ceph_mds_data_dir, + qr/$ccname-(\S+)/, + sub { + my (undef, $mds_id) = @_; + push @$mds_list, $mds_id; + }, + ); return $mds_list; } @@ -180,38 +200,37 @@ sub get_cluster_mds_state { my $mds_state = {}; if (!defined($rados)) { - $rados = PVE::RADOS->new(); + $rados = PVE::RADOS->new(); } my $add_state = sub { - my ($mds, $fsname) = @_; + my ($mds, $fsname) = @_; - my $state = {}; - $state->{addr} = $mds->{addr}; - $state->{rank} = $mds->{rank}; - $state->{standby_replay} = $mds->{standby_replay} ? 1 : 0; - $state->{state} = $mds->{state}; - $state->{fs_name} = $fsname; + my $state = {}; + $state->{addr} = $mds->{addr}; + $state->{rank} = $mds->{rank}; + $state->{standby_replay} = $mds->{standby_replay} ? 1 : 0; + $state->{state} = $mds->{state}; + $state->{fs_name} = $fsname; - $mds_state->{$mds->{name}} = $state; + $mds_state->{ $mds->{name} } = $state; }; my $mds_dump = $rados->mon_command({ prefix => 'mds stat' }); my $fsmap = $mds_dump->{fsmap}; - - foreach my $mds (@{$fsmap->{standbys}}) { - $add_state->($mds); + foreach my $mds (@{ $fsmap->{standbys} }) { + $add_state->($mds); } - for my $fs_info (@{$fsmap->{filesystems}}) { - my $active_mds = $fs_info->{mdsmap}->{info}; + for my $fs_info (@{ $fsmap->{filesystems} }) { + my $active_mds = $fs_info->{mdsmap}->{info}; - # normally there's only one active MDS, but we can have multiple active for - # different ranks (e.g., different cephs path hierarchy). So just add all. - foreach my $mds (values %$active_mds) { - $add_state->($mds, $fs_info->{mdsmap}->{fs_name}); - } + # normally there's only one active MDS, but we can have multiple active for + # different ranks (e.g., different cephs path hierarchy). So just add all. + foreach my $mds (values %$active_mds) { + $add_state->($mds, $fs_info->{mdsmap}->{fs_name}); + } } return $mds_state; @@ -221,22 +240,22 @@ sub is_mds_active { my ($rados, $fs_name) = @_; if (!defined($rados)) { - $rados = PVE::RADOS->new(); + $rados = PVE::RADOS->new(); } my $mds_dump = $rados->mon_command({ prefix => 'mds stat' }); my $fsmap = $mds_dump->{fsmap}->{filesystems}; if (!($fsmap && scalar(@$fsmap) > 0)) { - return undef; + return undef; } for my $fs (@$fsmap) { - next if defined($fs_name) && $fs->{mdsmap}->{fs_name} ne $fs_name; + next if defined($fs_name) && $fs->{mdsmap}->{fs_name} ne $fs_name; - my $active_mds = $fs->{mdsmap}->{info}; - for my $mds (values %$active_mds) { - return 1 if $mds->{state} eq 'up:active'; - } + my $active_mds = $fs->{mdsmap}->{info}; + for my $mds (values %$active_mds) { + return 1 if $mds->{state} eq 'up:active'; + } } return 0; @@ -247,10 +266,10 @@ sub create_mds { # `ceph fs status` fails with numeric only ID. die "ID: $id, numeric only IDs are not supported\n" - if $id =~ /^\d+$/; + if $id =~ /^\d+$/; if (!defined($rados)) { - $rados = PVE::RADOS->new(); + $rados = PVE::RADOS->new(); } my $ccname = PVE::Ceph::Tools::get_config('ccname'); @@ -259,7 +278,7 @@ sub create_mds { my $service_name = "mds.$id"; die "ceph MDS directory '$service_dir' already exists\n" - if -d $service_dir; + if -d $service_dir; print "creating MDS directory '$service_dir'\n"; eval { File::Path::mkpath($service_dir) }; @@ -268,17 +287,17 @@ sub create_mds { # http://docs.ceph.com/docs/luminous/install/manual-deployment/#adding-mds my $priv = [ - mon => 'allow profile mds', - osd => 'allow rwx', - mds => 'allow *', + mon => 'allow profile mds', + osd => 'allow rwx', + mds => 'allow *', ]; print "creating keys for '$service_name'\n"; my $output = $rados->mon_command({ - prefix => 'auth get-or-create', - entity => $service_name, - caps => $priv, - format => 'plain', + prefix => 'auth get-or-create', + entity => $service_name, + caps => $priv, + format => 'plain', }); PVE::Tools::file_set_contents($service_keyring, $output); @@ -294,13 +313,13 @@ sub create_mds { broadcast_ceph_services(); return undef; -}; +} sub destroy_mds { my ($id, $rados) = @_; if (!defined($rados)) { - $rados = PVE::RADOS->new(); + $rados = PVE::RADOS->new(); } my $ccname = PVE::Ceph::Tools::get_config('ccname'); @@ -314,23 +333,23 @@ sub destroy_mds { ceph_service_cmd('stop', $service_name); if (-d $service_dir) { - print "removing ceph-mds directory '$service_dir'\n"; - File::Path::remove_tree($service_dir); + print "removing ceph-mds directory '$service_dir'\n"; + File::Path::remove_tree($service_dir); } else { - warn "cannot cleanup MDS $id directory, '$service_dir' not found\n" + warn "cannot cleanup MDS $id directory, '$service_dir' not found\n"; } print "removing ceph auth for '$service_name'\n"; $rados->mon_command({ - prefix => 'auth del', - entity => $service_name, - format => 'plain' - }); + prefix => 'auth del', + entity => $service_name, + format => 'plain', + }); broadcast_ceph_services(); return undef; -}; +} # MGR @@ -348,14 +367,14 @@ sub create_mgr { mkdir $mgrdir; print "creating keys for '$mgrname'\n"; my $output = $rados->mon_command({ - prefix => 'auth get-or-create', - entity => $mgrname, - caps => [ - mon => 'allow profile mgr', - osd => 'allow *', - mds => 'allow *', - ], - format => 'plain' + prefix => 'auth get-or-create', + entity => $mgrname, + caps => [ + mon => 'allow profile mgr', + osd => 'allow *', + mds => 'allow *', + ], + format => 'plain', }); PVE::Tools::file_set_contents($mgrkeyring, $output); @@ -380,7 +399,7 @@ sub destroy_mgr { my $mgrdir = "/var/lib/ceph/mgr/$clustername-$mgrid"; die "ceph manager directory '$mgrdir' not found\n" - if ! -d $mgrdir; + if !-d $mgrdir; print "disabling service 'ceph-mgr\@$mgrid.service'\n"; ceph_service_cmd('disable', $mgrname); @@ -392,7 +411,7 @@ sub destroy_mgr { print "removing authkeys for $mgrname\n"; if (!$rados) { - $rados = PVE::RADOS->new(); + $rados = PVE::RADOS->new(); } $rados->mon_command({ prefix => 'auth del', entity => "$mgrname" }); diff --git a/PVE/Ceph/Tools.pm b/PVE/Ceph/Tools.pm index ee16161c..f50d2272 100644 --- a/PVE/Ceph/Tools.pm +++ b/PVE/Ceph/Tools.pm @@ -61,9 +61,9 @@ sub get_local_version { my $ceph_version; run_command( - [ $ceph_service->{ceph_bin}, '--version' ], - noerr => $noerr, - outfunc => sub { $ceph_version = shift if !defined $ceph_version }, + [$ceph_service->{ceph_bin}, '--version'], + noerr => $noerr, + outfunc => sub { $ceph_version = shift if !defined $ceph_version }, ); return undef if !defined $ceph_version; @@ -91,13 +91,13 @@ sub parse_ceph_version : prototype($) { /x; if ($ceph_version =~ /$re_ceph_version/) { - my ($version, $buildcommit) = ($1, $2); - my $subversions = [ split(/\.|-/, $version) ]; + my ($version, $buildcommit) = ($1, $2); + my $subversions = [split(/\.|-/, $version)]; - # return (version, buildid, [major, minor, ...]) : major; - return wantarray - ? ($version, $buildcommit, $subversions) - : $subversions->[0]; + # return (version, buildid, [major, minor, ...]) : major; + return wantarray + ? ($version, $buildcommit, $subversions) + : $subversions->[0]; } return undef; @@ -116,7 +116,7 @@ sub get_config { my $value = $config_values->{$key} // $config_files->{$key}; - die "no such ceph config '$key'" if ! defined($value); + die "no such ceph config '$key'" if !defined($value); return $value; } @@ -124,38 +124,41 @@ sub get_config { sub purge_all_ceph_files { my ($services) = @_; my $is_local_mon; - my $monlist = [ split(',', PVE::CephConfig::get_monaddr_list($pve_ceph_cfgpath)) ]; + my $monlist = [split(',', PVE::CephConfig::get_monaddr_list($pve_ceph_cfgpath))]; foreach my $service (keys %$services) { - my $type = $services->{$service}; - next if (!%$type); + my $type = $services->{$service}; + next if (!%$type); - foreach my $name (keys %$type) { - my $dir_exists = $type->{$name}->{direxists}; + foreach my $name (keys %$type) { + my $dir_exists = $type->{$name}->{direxists}; - $is_local_mon = grep($type->{$name}->{addr}, @$monlist) - if $service eq 'mon'; + $is_local_mon = grep($type->{$name}->{addr}, @$monlist) + if $service eq 'mon'; - my $path = "/var/lib/ceph/$service"; - $path = '/var/log/ceph' if $service eq 'logs'; - if ($dir_exists) { - my $err; - File::Path::remove_tree($path, { - keep_root => 1, - error => \$err, - }); - warn "Error removing path, '$path'\n" if @$err; - } - } + my $path = "/var/lib/ceph/$service"; + $path = '/var/log/ceph' if $service eq 'logs'; + if ($dir_exists) { + my $err; + File::Path::remove_tree( + $path, + { + keep_root => 1, + error => \$err, + }, + ); + warn "Error removing path, '$path'\n" if @$err; + } + } } if (scalar @$monlist > 0 && !$is_local_mon) { - warn "Foreign MON address in ceph.conf. Keeping config & keyrings\n" + warn "Foreign MON address in ceph.conf. Keeping config & keyrings\n"; } else { - print "Removing config & keyring files\n"; - for my $file (%$config_files) { - unlink $file if (-e $file); - } + print "Removing config & keyring files\n"; + for my $file (%$config_files) { + unlink $file if (-e $file); + } } } @@ -163,24 +166,24 @@ sub purge_all_ceph_services { my ($services) = @_; foreach my $service (keys %$services) { - my $type = $services->{$service}; - next if (!%$type); + my $type = $services->{$service}; + next if (!%$type); - foreach my $name (keys %$type) { - my $service_exists = $type->{$name}->{service}; + foreach my $name (keys %$type) { + my $service_exists = $type->{$name}->{service}; - if ($service_exists) { - eval { PVE::Ceph::Services::ceph_service_cmd('disable', "$service.$name") }; - warn "Could not disable ceph-$service\@$name, error: $@\n" if $@; + if ($service_exists) { + eval { PVE::Ceph::Services::ceph_service_cmd('disable', "$service.$name") }; + warn "Could not disable ceph-$service\@$name, error: $@\n" if $@; - eval { PVE::Ceph::Services::ceph_service_cmd('stop', "$service.$name") }; - warn "Could not stop ceph-$service\@$name, error: $@\n" if $@; - } - } + eval { PVE::Ceph::Services::ceph_service_cmd('stop', "$service.$name") }; + warn "Could not stop ceph-$service\@$name, error: $@\n" if $@; + } + } } } -sub ceph_install_flag_file { return '/run/pve-ceph-install-flag' }; +sub ceph_install_flag_file { return '/run/pve-ceph-install-flag' } sub check_ceph_installed { my ($service, $noerr) = @_; @@ -189,21 +192,20 @@ sub check_ceph_installed { # NOTE: the flag file is checked as on a new installation, the binary gets # extracted by dpkg before the installation is finished - if (! -x $ceph_service->{$service} || -f ceph_install_flag_file()) { - die "binary not installed: $ceph_service->{$service}\n" if !$noerr; - return undef; + if (!-x $ceph_service->{$service} || -f ceph_install_flag_file()) { + die "binary not installed: $ceph_service->{$service}\n" if !$noerr; + return undef; } return 1; } - sub check_ceph_configured { check_ceph_inited(); die "ceph not fully configured - missing '$pve_ckeyring_path'\n" - if ! -f $pve_ckeyring_path; + if !-f $pve_ckeyring_path; return 1; } @@ -215,13 +217,13 @@ sub check_ceph_inited { my @errors; - push(@errors, "missing '$pve_ceph_cfgpath'") if ! -f $pve_ceph_cfgpath; - push(@errors, "missing '$pve_ceph_cfgdir'") if ! -d $pve_ceph_cfgdir; + push(@errors, "missing '$pve_ceph_cfgpath'") if !-f $pve_ceph_cfgpath; + push(@errors, "missing '$pve_ceph_cfgdir'") if !-d $pve_ceph_cfgdir; if (@errors) { - my $err = 'pveceph configuration not initialized - ' . join(', ', @errors) . "\n"; - die $err if !$noerr; - return undef; + my $err = 'pveceph configuration not initialized - ' . join(', ', @errors) . "\n"; + die $err if !$noerr; + return undef; } return 1; @@ -232,9 +234,9 @@ sub check_ceph_enabled { return undef if !check_ceph_inited($noerr); - if (! -f $ceph_cfgpath) { - die "pveceph configuration not enabled\n" if !$noerr; - return undef; + if (!-f $ceph_cfgpath) { + die "pveceph configuration not enabled\n" if !$noerr; + return undef; } return 1; @@ -245,19 +247,19 @@ my $set_pool_setting = sub { my $command; if ($setting eq 'application') { - $command = { - prefix => "osd pool application enable", - pool => "$pool", - app => "$value", - }; + $command = { + prefix => "osd pool application enable", + pool => "$pool", + app => "$value", + }; } else { - $command = { - prefix => "osd pool set", - pool => "$pool", - var => "$setting", - val => "$value", - format => 'plain', - }; + $command = { + prefix => "osd pool set", + pool => "$pool", + var => "$setting", + val => "$value", + format => 'plain', + }; } $rados = PVE::RADOS->new() if !$rados; @@ -271,42 +273,42 @@ sub set_pool { my $rados = PVE::RADOS->new(); if (get_pool_type($pool, $rados) eq 'erasure') { - #remove parameters that cannot be changed for erasure coded pools - my $ignore_params = ['size', 'crush_rule']; - for my $setting (@$ignore_params) { - if ($param->{$setting}) { - print "cannot set '${setting}' for erasure coded pool\n"; - delete $param->{$setting}; - } - } + #remove parameters that cannot be changed for erasure coded pools + my $ignore_params = ['size', 'crush_rule']; + for my $setting (@$ignore_params) { + if ($param->{$setting}) { + print "cannot set '${setting}' for erasure coded pool\n"; + delete $param->{$setting}; + } + } } # by default, pool size always resets min_size, so set it as first item # https://tracker.ceph.com/issues/44862 - my $keys = [ grep { $_ ne 'size' } sort keys %$param ]; + my $keys = [grep { $_ ne 'size' } sort keys %$param]; unshift @$keys, 'size' if exists $param->{size}; my $current_properties = get_pool_properties($pool, $rados); for my $setting (@$keys) { - my $value = $param->{$setting}; + my $value = $param->{$setting}; - if (defined($current_properties->{$setting}) && $value eq $current_properties->{$setting}) { - print "skipping '${setting}', did not change\n"; - delete $param->{$setting}; - next; - } + if (defined($current_properties->{$setting}) && $value eq $current_properties->{$setting}) { + print "skipping '${setting}', did not change\n"; + delete $param->{$setting}; + next; + } - print "pool $pool: applying $setting = $value\n"; - if (my $err = $set_pool_setting->($pool, $setting, $value, $rados)) { - print "$err"; - } else { - delete $param->{$setting}; - } + print "pool $pool: applying $setting = $value\n"; + if (my $err = $set_pool_setting->($pool, $setting, $value, $rados)) { + print "$err"; + } else { + delete $param->{$setting}; + } } if (scalar(keys %$param) > 0) { - my $missing = join(', ', sort keys %$param ); - die "Could not set: $missing\n"; + my $missing = join(', ', sort keys %$param); + die "Could not set: $missing\n"; } } @@ -315,10 +317,10 @@ sub get_pool_properties { my ($pool, $rados) = @_; $rados = PVE::RADOS->new() if !defined($rados); my $command = { - prefix => "osd pool get", - pool => "$pool", - var => "all", - format => 'json', + prefix => "osd pool get", + pool => "$pool", + var => "all", + format => 'json', }; return $rados->mon_command($command); } @@ -337,14 +339,14 @@ sub create_pool { my $pg_num = $param->{pg_num} || 128; my $mon_params = { - prefix => "osd pool create", - pool => $pool, - pg_num => int($pg_num), - format => 'plain', + prefix => "osd pool create", + pool => $pool, + pg_num => int($pg_num), + format => 'plain', }; $mon_params->{pool_type} = extract_param($param, 'pool_type') if $param->{pool_type}; $mon_params->{erasure_code_profile} = extract_param($param, 'erasure_code_profile') - if $param->{erasure_code_profile}; + if $param->{erasure_code_profile}; $rados->mon_command($mon_params); @@ -367,11 +369,11 @@ sub destroy_pool { # fixme: '--yes-i-really-really-mean-it' $rados->mon_command({ - prefix => "osd pool delete", - pool => $pool, - pool2 => $pool, - 'yes_i_really_really_mean_it' => JSON::true, - format => 'plain', + prefix => "osd pool delete", + pool => $pool, + pool2 => $pool, + 'yes_i_really_really_mean_it' => JSON::true, + format => 'plain', }); } @@ -396,15 +398,15 @@ sub create_fs { my ($fs, $param, $rados) = @_; if (!defined($rados)) { - $rados = PVE::RADOS->new(); + $rados = PVE::RADOS->new(); } $rados->mon_command({ - prefix => "fs new", - fs_name => $fs, - metadata => $param->{pool_metadata}, - data => $param->{pool_data}, - format => 'plain', + prefix => "fs new", + fs_name => $fs, + metadata => $param->{pool_metadata}, + data => $param->{pool_data}, + format => 'plain', }); } @@ -413,23 +415,23 @@ sub destroy_fs { $rados = PVE::RADOS->new() if !defined($rados); $rados->mon_command({ - prefix => "fs rm", - fs_name => $fs, - 'yes_i_really_mean_it' => JSON::true, - format => 'plain', + prefix => "fs rm", + fs_name => $fs, + 'yes_i_really_mean_it' => JSON::true, + format => 'plain', }); } sub setup_pve_symlinks { # fail if we find a real file instead of a link if (-f $ceph_cfgpath) { - my $lnk = readlink($ceph_cfgpath); - die "file '$ceph_cfgpath' already exists and is not a symlink to $pve_ceph_cfgpath\n" - if !$lnk || $lnk ne $pve_ceph_cfgpath; + my $lnk = readlink($ceph_cfgpath); + die "file '$ceph_cfgpath' already exists and is not a symlink to $pve_ceph_cfgpath\n" + if !$lnk || $lnk ne $pve_ceph_cfgpath; } else { - mkdir $ceph_cfgdir; - symlink($pve_ceph_cfgpath, $ceph_cfgpath) || - die "unable to create symlink '$ceph_cfgpath' - $!\n"; + mkdir $ceph_cfgdir; + symlink($pve_ceph_cfgpath, $ceph_cfgpath) + || die "unable to create symlink '$ceph_cfgpath' - $!\n"; } my $ceph_uid = getpwnam('ceph'); my $ceph_gid = getgrnam('ceph'); @@ -437,18 +439,18 @@ sub setup_pve_symlinks { } sub get_or_create_admin_keyring { - if (! -f $pve_ckeyring_path) { - run_command("ceph-authtool --create-keyring $pve_ckeyring_path " . - "--gen-key -n client.admin " . - "--cap mon 'allow *' " . - "--cap osd 'allow *' " . - "--cap mds 'allow *' " . - "--cap mgr 'allow *' "); - # we do not want to overwrite it - if (! -f $ckeyring_path) { - run_command("cp $pve_ckeyring_path $ckeyring_path"); - run_command("chown ceph:ceph $ckeyring_path"); - } + if (!-f $pve_ckeyring_path) { + run_command("ceph-authtool --create-keyring $pve_ckeyring_path " + . "--gen-key -n client.admin " + . "--cap mon 'allow *' " + . "--cap osd 'allow *' " + . "--cap mds 'allow *' " + . "--cap mgr 'allow *' "); + # we do not want to overwrite it + if (!-f $ckeyring_path) { + run_command("cp $pve_ckeyring_path $ckeyring_path"); + run_command("chown ceph:ceph $ckeyring_path"); + } } return $pve_ckeyring_path; } @@ -458,29 +460,29 @@ sub create_or_update_crash_keyring_file { my ($rados) = @_; if (!defined($rados)) { - $rados = PVE::RADOS->new(); + $rados = PVE::RADOS->new(); } my $output = $rados->mon_command({ - prefix => 'auth get-or-create', - entity => 'client.crash', - caps => [ - mon => 'profile crash', - mgr => 'profile crash', - ], - format => 'plain', + prefix => 'auth get-or-create', + entity => 'client.crash', + caps => [ + mon => 'profile crash', + mgr => 'profile crash', + ], + format => 'plain', }); if (-f $pve_ceph_crash_key_path) { - my $contents = PVE::Tools::file_get_contents($pve_ceph_crash_key_path); + my $contents = PVE::Tools::file_get_contents($pve_ceph_crash_key_path); - if ($contents ne $output) { - PVE::Tools::file_set_contents($pve_ceph_crash_key_path, $output); - return 1; - } + if ($contents ne $output) { + PVE::Tools::file_set_contents($pve_ceph_crash_key_path, $output); + return 1; + } } else { - PVE::Tools::file_set_contents($pve_ceph_crash_key_path, $output); - return 1; + PVE::Tools::file_set_contents($pve_ceph_crash_key_path, $output); + return 1; } return 0; @@ -491,11 +493,11 @@ sub ceph_volume_list { my $result = {}; if (!check_ceph_installed('ceph_volume', 1)) { - return $result; + return $result; } my $output = ''; - my $cmd = [ $ceph_service->{ceph_volume}, 'lvm', 'list', '--format', 'json' ]; + my $cmd = [$ceph_service->{ceph_volume}, 'lvm', 'list', '--format', 'json']; run_command($cmd, outfunc => sub { $output .= shift }); $result = eval { decode_json($output) }; @@ -508,7 +510,7 @@ sub ceph_volume_zap { die "no osdid given\n" if !defined($osdid); - my $cmd = [ $ceph_service->{ceph_volume}, 'lvm', 'zap', '--osd-id', $osdid ]; + my $cmd = [$ceph_service->{ceph_volume}, 'lvm', 'zap', '--osd-id', $osdid]; push @$cmd, '--destroy' if $destroy; run_command($cmd); @@ -520,84 +522,88 @@ sub get_db_wal_sizes { my $rados = PVE::RADOS->new(); my $db_config = $rados->mon_command({ prefix => 'config-key dump', key => 'config/' }); - $res->{db} = $db_config->{"config/osd/bluestore_block_db_size"} // - $db_config->{"config/global/bluestore_block_db_size"}; + $res->{db} = $db_config->{"config/osd/bluestore_block_db_size"} + // $db_config->{"config/global/bluestore_block_db_size"}; - $res->{wal} = $db_config->{"config/osd/bluestore_block_wal_size"} // - $db_config->{"config/global/bluestore_block_wal_size"}; + $res->{wal} = $db_config->{"config/osd/bluestore_block_wal_size"} + // $db_config->{"config/global/bluestore_block_wal_size"}; if (!$res->{db} || !$res->{wal}) { - my $cfg = cfs_read_file('ceph.conf'); - if (!$res->{db}) { - $res->{db} = $cfg->{osd}->{bluestore_block_db_size} // - $cfg->{global}->{bluestore_block_db_size}; - } + my $cfg = cfs_read_file('ceph.conf'); + if (!$res->{db}) { + $res->{db} = $cfg->{osd}->{bluestore_block_db_size} + // $cfg->{global}->{bluestore_block_db_size}; + } - if (!$res->{wal}) { - $res->{wal} = $cfg->{osd}->{bluestore_block_wal_size} // - $cfg->{global}->{bluestore_block_wal_size}; - } + if (!$res->{wal}) { + $res->{wal} = $cfg->{osd}->{bluestore_block_wal_size} + // $cfg->{global}->{bluestore_block_wal_size}; + } } return $res; } + sub get_possible_osd_flags { my $possible_flags = { - pause => { - description => 'Pauses read and writes.', - type => 'boolean', - optional=> 1, - }, - noup => { - description => 'OSDs are not allowed to start.', - type => 'boolean', - optional=> 1, - }, - nodown => { - description => 'OSD failure reports are being ignored, such that the monitors will not mark OSDs down.', - type => 'boolean', - optional=> 1, - }, - noout => { - description => 'OSDs will not automatically be marked out after the configured interval.', - type => 'boolean', - optional=> 1, - }, - noin => { - description => 'OSDs that were previously marked out will not be marked back in when they start.', - type => 'boolean', - optional=> 1, - }, - nobackfill => { - description => 'Backfilling of PGs is suspended.', - type => 'boolean', - optional=> 1, - }, - norebalance => { - description => 'Rebalancing of PGs is suspended.', - type => 'boolean', - optional=> 1, - }, - norecover => { - description => 'Recovery of PGs is suspended.', - type => 'boolean', - optional=> 1, - }, - noscrub => { - description => 'Scrubbing is disabled.', - type => 'boolean', - optional=> 1, - }, - 'nodeep-scrub' => { - description => 'Deep Scrubbing is disabled.', - type => 'boolean', - optional=> 1, - }, - notieragent => { - description => 'Cache tiering activity is suspended.', - type => 'boolean', - optional=> 1, - }, + pause => { + description => 'Pauses read and writes.', + type => 'boolean', + optional => 1, + }, + noup => { + description => 'OSDs are not allowed to start.', + type => 'boolean', + optional => 1, + }, + nodown => { + description => + 'OSD failure reports are being ignored, such that the monitors will not mark OSDs down.', + type => 'boolean', + optional => 1, + }, + noout => { + description => + 'OSDs will not automatically be marked out after the configured interval.', + type => 'boolean', + optional => 1, + }, + noin => { + description => + 'OSDs that were previously marked out will not be marked back in when they start.', + type => 'boolean', + optional => 1, + }, + nobackfill => { + description => 'Backfilling of PGs is suspended.', + type => 'boolean', + optional => 1, + }, + norebalance => { + description => 'Rebalancing of PGs is suspended.', + type => 'boolean', + optional => 1, + }, + norecover => { + description => 'Recovery of PGs is suspended.', + type => 'boolean', + optional => 1, + }, + noscrub => { + description => 'Scrubbing is disabled.', + type => 'boolean', + optional => 1, + }, + 'nodeep-scrub' => { + description => 'Deep Scrubbing is disabled.', + type => 'boolean', + optional => 1, + }, + notieragent => { + description => 'Cache tiering activity is suspended.', + type => 'boolean', + optional => 1, + }, }; return $possible_flags; } @@ -608,7 +614,7 @@ sub get_real_flag_name { # the 'pause' flag gets always set to both 'pauserd' and 'pausewr' # so decide that the 'pause' flag is set if we detect 'pauserd' my $flagmap = { - 'pause' => 'pauserd', + 'pause' => 'pauserd', }; return $flagmap->{$flag} // $flag; @@ -622,8 +628,8 @@ sub ceph_cluster_status { $status->{health} = $rados->mon_command({ prefix => 'health', detail => 'detail' }); if (!exists $status->{monmap}->{mons}) { # octopus moved most info out of status, re-add - $status->{monmap} = $rados->mon_command({ prefix => 'mon dump' }); - $status->{mgrmap} = $rados->mon_command({ prefix => 'mgr dump' }); + $status->{monmap} = $rados->mon_command({ prefix => 'mon dump' }); + $status->{mgrmap} = $rados->mon_command({ prefix => 'mgr dump' }); } return $status; @@ -646,17 +652,15 @@ sub create_ecprofile { $failure_domain = 'host' if !$failure_domain; my $profile = [ - "crush-failure-domain=${failure_domain}", - "k=${k}", - "m=${m}", + "crush-failure-domain=${failure_domain}", "k=${k}", "m=${m}", ]; push(@$profile, "crush-device-class=${device_class}") if $device_class; $rados->mon_command({ - prefix => 'osd erasure-code-profile set', - name => $name, - profile => $profile, + prefix => 'osd erasure-code-profile set', + name => $name, + profile => $profile, }); } @@ -665,9 +669,9 @@ sub destroy_ecprofile { $rados = PVE::RADOS->new() if !$rados; my $command = { - prefix => 'osd erasure-code-profile rm', - name => $profile, - format => 'plain', + prefix => 'osd erasure-code-profile rm', + name => $profile, + format => 'plain', }; return $rados->mon_command($command); } @@ -682,9 +686,9 @@ sub destroy_crush_rule { $rados = PVE::RADOS->new() if !$rados; my $command = { - prefix => 'osd crush rule rm', - name => $rule, - format => 'plain', + prefix => 'osd crush rule rm', + name => $rule, + format => 'plain', }; return $rados->mon_command($command); } diff --git a/PVE/CertCache.pm b/PVE/CertCache.pm index 4eadab29..2296081e 100644 --- a/PVE/CertCache.pm +++ b/PVE/CertCache.pm @@ -18,37 +18,36 @@ sub update_cert_cache { my ($update_node, $clear) = @_; syslog('info', "Clearing outdated entries from certificate cache") - if $clear; + if $clear; $cert_cache_timestamp = time() if !defined($update_node); - my $node_list = defined($update_node) ? - [ $update_node ] : [ keys %$cert_cache_nodes ]; + my $node_list = defined($update_node) ? [$update_node] : [keys %$cert_cache_nodes]; foreach my $node (@$node_list) { - my $clear_old = sub { - if (my $old_fp = $cert_cache_nodes->{$node}) { - # distrust old fingerprint - delete $cert_cache_fingerprints->{$old_fp}; - # ensure reload on next proxied request - delete $cert_cache_nodes->{$node}; - } - }; + my $clear_old = sub { + if (my $old_fp = $cert_cache_nodes->{$node}) { + # distrust old fingerprint + delete $cert_cache_fingerprints->{$old_fp}; + # ensure reload on next proxied request + delete $cert_cache_nodes->{$node}; + } + }; - my $fp = eval { PVE::Cluster::get_node_fingerprint($node) }; - if (my $err = $@) { - warn "$err\n"; - &$clear_old() if $clear; - next; - } + my $fp = eval { PVE::Cluster::get_node_fingerprint($node) }; + if (my $err = $@) { + warn "$err\n"; + &$clear_old() if $clear; + next; + } - my $old_fp = $cert_cache_nodes->{$node}; - $cert_cache_fingerprints->{$fp} = 1; - $cert_cache_nodes->{$node} = $fp; + my $old_fp = $cert_cache_nodes->{$node}; + $cert_cache_fingerprints->{$fp} = 1; + $cert_cache_nodes->{$node} = $fp; - if (defined($old_fp) && $fp ne $old_fp) { - delete $cert_cache_fingerprints->{$old_fp}; - } + if (defined($old_fp) && $fp ne $old_fp) { + delete $cert_cache_fingerprints->{$old_fp}; + } } } @@ -57,33 +56,36 @@ sub initialize_cert_cache { my ($node) = @_; update_cert_cache($node) - if defined($node) && !defined($cert_cache_nodes->{$node}); + if defined($node) && !defined($cert_cache_nodes->{$node}); } sub check_cert_fingerprint { my ($cert) = @_; # clear cache every 30 minutes at least - update_cert_cache(undef, 1) if time() - $cert_cache_timestamp >= 60*30; + update_cert_cache(undef, 1) if time() - $cert_cache_timestamp >= 60 * 30; # get fingerprint of server certificate my $fp = Net::SSLeay::X509_get_fingerprint($cert, 'sha256'); return 0 if !defined($fp) || $fp eq ''; # error my $check = sub { - for my $expected (keys %$cert_cache_fingerprints) { - return 1 if $fp eq $expected; - } - return 0; + for my $expected (keys %$cert_cache_fingerprints) { + return 1 if $fp eq $expected; + } + return 0; }; return 1 if &$check(); # clear cache and retry at most once every minute if (time() - $cert_cache_timestamp >= 60) { - syslog ('info', "Could not verify remote node certificate '$fp' with list of pinned certificates, refreshing cache"); - update_cert_cache(); - return &$check(); + syslog( + 'info', + "Could not verify remote node certificate '$fp' with list of pinned certificates, refreshing cache", + ); + update_cert_cache(); + return &$check(); } return 0; diff --git a/PVE/CertHelpers.pm b/PVE/CertHelpers.pm index 7e088cb9..ee945827 100644 --- a/PVE/CertHelpers.pm +++ b/PVE/CertHelpers.pm @@ -9,26 +9,35 @@ use PVE::Tools; my $account_prefix = '/etc/pve/priv/acme'; -PVE::JSONSchema::register_standard_option('pve-acme-account-name', { - description => 'ACME account config file name.', - type => 'string', - format => 'pve-configid', - format_description => 'name', - optional => 1, - default => 'default', -}); +PVE::JSONSchema::register_standard_option( + 'pve-acme-account-name', + { + description => 'ACME account config file name.', + type => 'string', + format => 'pve-configid', + format_description => 'name', + optional => 1, + default => 'default', + }, +); -PVE::JSONSchema::register_standard_option('pve-acme-account-contact', { - type => 'string', - format => 'email-list', - description => 'Contact email addresses.', -}); +PVE::JSONSchema::register_standard_option( + 'pve-acme-account-contact', + { + type => 'string', + format => 'email-list', + description => 'Contact email addresses.', + }, +); -PVE::JSONSchema::register_standard_option('pve-acme-directory-url', { - type => 'string', - description => 'URL of ACME CA directory endpoint.', - pattern => '^https?://.*', -}); +PVE::JSONSchema::register_standard_option( + 'pve-acme-directory-url', + { + type => 'string', + description => 'URL of ACME CA directory endpoint.', + pattern => '^https?://.*', + }, +); my $local_cert_lock = '/var/lock/pve-certs.lock'; @@ -61,30 +70,30 @@ sub set_cert_files { my $key_path_tmp = "${path_prefix}.key.old"; die "Custom certificate file exists but force flag is not set.\n" - if !$force && -e $cert_path; + if !$force && -e $cert_path; die "Custom certificate key file exists but force flag is not set.\n" - if !$force && -e $key_path; + if !$force && -e $key_path; PVE::Tools::file_copy($cert_path, $cert_path_tmp) if -e $cert_path; PVE::Tools::file_copy($key_path, $key_path_tmp) if -e $key_path; eval { - PVE::Tools::file_set_contents($cert_path, $cert); - PVE::Tools::file_set_contents($key_path, $key) if $key; - $info = PVE::Certificate::get_certificate_info($cert_path); + PVE::Tools::file_set_contents($cert_path, $cert); + PVE::Tools::file_set_contents($key_path, $key) if $key; + $info = PVE::Certificate::get_certificate_info($cert_path); }; my $err = $@; if ($err) { - if (-e $cert_path_tmp && -e $key_path_tmp) { - eval { - warn "Attempting to restore old certificate files..\n"; - PVE::Tools::file_copy($cert_path_tmp, $cert_path); - PVE::Tools::file_copy($key_path_tmp, $key_path); - }; - warn "$@\n" if $@; - } - die "Setting certificate files failed - $err\n" + if (-e $cert_path_tmp && -e $key_path_tmp) { + eval { + warn "Attempting to restore old certificate files..\n"; + PVE::Tools::file_copy($cert_path_tmp, $cert_path); + PVE::Tools::file_copy($key_path_tmp, $key_path); + }; + warn "$@\n" if $@; + } + die "Setting certificate files failed - $err\n"; } unlink $cert_path_tmp; @@ -100,14 +109,18 @@ sub acme_account_dir { sub list_acme_accounts { my $accounts = []; - return $accounts if ! -d $account_prefix; + return $accounts if !-d $account_prefix; - PVE::Tools::dir_glob_foreach($account_prefix, qr/[^.]+.*/, sub { - my ($name) = @_; + PVE::Tools::dir_glob_foreach( + $account_prefix, + qr/[^.]+.*/, + sub { + my ($name) = @_; - push @$accounts, $name - if PVE::JSONSchema::pve_verify_configid($name, 1); - }); + push @$accounts, $name + if PVE::JSONSchema::pve_verify_configid($name, 1); + }, + ); return $accounts; } diff --git a/PVE/ExtMetric.pm b/PVE/ExtMetric.pm index 3358c359..02e7c327 100644 --- a/PVE/ExtMetric.pm +++ b/PVE/ExtMetric.pm @@ -14,12 +14,12 @@ PVE::Status::Plugin->init(); sub foreach_plug($&) { my ($status_cfg, $code) = @_; - for my $id (sort keys %{$status_cfg->{ids}}) { - my $plugin_config = $status_cfg->{ids}->{$id}; - next if $plugin_config->{disable}; + for my $id (sort keys %{ $status_cfg->{ids} }) { + my $plugin_config = $status_cfg->{ids}->{$id}; + next if $plugin_config->{disable}; - my $plugin = PVE::Status::Plugin->lookup($plugin_config->{type}); - $code->($plugin, $id, $plugin_config); + my $plugin = PVE::Status::Plugin->lookup($plugin_config->{type}); + $code->($plugin, $id, $plugin_config); } } @@ -29,9 +29,9 @@ sub update_all($$@) { my $method = "update_${subsystem}_status"; for my $txn (@$transactions) { - my $plugin = PVE::Status::Plugin->lookup($txn->{cfg}->{type}); + my $plugin = PVE::Status::Plugin->lookup($txn->{cfg}->{type}); - $plugin->$method($txn, @params); + $plugin->$method($txn, @params); } } @@ -46,18 +46,22 @@ sub transactions_start { my $transactions = []; - foreach_plug($cfg, sub { - my ($plugin, $id, $plugin_config) = @_; + foreach_plug( + $cfg, + sub { + my ($plugin, $id, $plugin_config) = @_; - my $connection = $plugin->_connect($plugin_config, $id); + my $connection = $plugin->_connect($plugin_config, $id); - push @$transactions, { - connection => $connection, - cfg => $plugin_config, - id => $id, - data => '', - }; - }); + push @$transactions, + { + connection => $connection, + cfg => $plugin_config, + id => $id, + data => '', + }; + }, + ); return $transactions; } @@ -66,16 +70,16 @@ sub transactions_finish { my ($transactions) = @_; for my $txn (@$transactions) { - my $plugin = PVE::Status::Plugin->lookup($txn->{cfg}->{type}); + my $plugin = PVE::Status::Plugin->lookup($txn->{cfg}->{type}); - eval { $plugin->flush_data($txn) }; - my $flush_err = $@; - warn "$flush_err" if $flush_err; + eval { $plugin->flush_data($txn) }; + my $flush_err = $@; + warn "$flush_err" if $flush_err; - $plugin->_disconnect($txn->{connection}, $txn->{cfg}); - $txn->{connection} = undef; - # avoid log spam, already got a send error; disconnect would fail too - warn "disconnect failed: $@" if $@ && !$flush_err; + $plugin->_disconnect($txn->{connection}, $txn->{cfg}); + $txn->{connection} = undef; + # avoid log spam, already got a send error; disconnect would fail too + warn "disconnect failed: $@" if $@ && !$flush_err; } } diff --git a/PVE/HTTPServer.pm b/PVE/HTTPServer.pm index dabdf7f3..62b53fc7 100755 --- a/PVE/HTTPServer.pm +++ b/PVE/HTTPServer.pm @@ -28,8 +28,8 @@ sub new { my $self = $class->SUPER::new(%args); $self->{rpcenv} = PVE::RPCEnvironment->init( - $self->{trusted_env} ? 'priv' : 'pub', - atfork => sub { $self->atfork_handler() }, + $self->{trusted_env} ? 'priv' : 'pub', + atfork => sub { $self->atfork_handler() }, ); return $self; @@ -67,11 +67,13 @@ sub auth_handler { my $require_auth = 1; # explicitly allow some calls without auth - if (($rel_uri eq '/access/domains' && $method eq 'GET') || - ($rel_uri eq '/access/ticket' && ($method eq 'GET' || $method eq 'POST')) || - ($rel_uri eq '/access/openid/login' && $method eq 'POST') || - ($rel_uri eq '/access/openid/auth-url' && $method eq 'POST')) { - $require_auth = 0; + if ( + ($rel_uri eq '/access/domains' && $method eq 'GET') + || ($rel_uri eq '/access/ticket' && ($method eq 'GET' || $method eq 'POST')) + || ($rel_uri eq '/access/openid/login' && $method eq 'POST') + || ($rel_uri eq '/access/openid/auth-url' && $method eq 'POST') + ) { + $require_auth = 0; } my ($username, $age); @@ -79,50 +81,50 @@ sub auth_handler { my $isUpload = 0; if ($require_auth) { - if ($api_token) { - # the token-ID `@!` is the user for token based authentication - $username = PVE::AccessControl::verify_token($api_token); - } else { - die "No ticket\n" if !$ticket; + if ($api_token) { + # the token-ID `@!` is the user for token based authentication + $username = PVE::AccessControl::verify_token($api_token); + } else { + die "No ticket\n" if !$ticket; - ($username, $age, my $tfa_info) = PVE::AccessControl::verify_ticket($ticket); - $rpcenv->check_user_enabled($username); + ($username, $age, my $tfa_info) = PVE::AccessControl::verify_ticket($ticket); + $rpcenv->check_user_enabled($username); - if (defined($tfa_info)) { - if (defined(my $challenge = $tfa_info->{challenge})) { - $rpcenv->set_u2f_challenge($challenge); - } - die "No ticket\n" if ($rel_uri ne '/access/tfa' || $method ne 'POST'); - } - } + if (defined($tfa_info)) { + if (defined(my $challenge = $tfa_info->{challenge})) { + $rpcenv->set_u2f_challenge($challenge); + } + die "No ticket\n" if ($rel_uri ne '/access/tfa' || $method ne 'POST'); + } + } - $rpcenv->set_user($username); + $rpcenv->set_user($username); - if ($method eq 'POST' && $rel_uri =~ m|^/nodes/([^/]+)/storage/([^/]+)/upload$|) { - my ($node, $storeid) = ($1, $2); - # CSRF check are omitted if $isUpload is set, so check user upload permission here - my $perm = { check => ['perm', "/storage/$storeid", ['Datastore.AllocateTemplate']] }; - $rpcenv->check_api2_permissions($perm, $username, {}); - $isUpload = 1; - } + if ($method eq 'POST' && $rel_uri =~ m|^/nodes/([^/]+)/storage/([^/]+)/upload$|) { + my ($node, $storeid) = ($1, $2); + # CSRF check are omitted if $isUpload is set, so check user upload permission here + my $perm = { check => ['perm', "/storage/$storeid", ['Datastore.AllocateTemplate']] }; + $rpcenv->check_api2_permissions($perm, $username, {}); + $isUpload = 1; + } - # Skip CSRF check for file upload (difficult to pass CSRF header with native html forms). - # Also skip the check with API tokens, as one of the design goals of API tokens was to - # provide stateless API access without requiring round-trips to get such CSRF tokens. - # CSRF-prevention also does not make much sense outside of the browser context. - if ($method ne 'GET' && !($api_token || $isUpload)) { - my $euid = $>; - PVE::AccessControl::verify_csrf_prevention_token($username, $token) if $euid != 0; - } + # Skip CSRF check for file upload (difficult to pass CSRF header with native html forms). + # Also skip the check with API tokens, as one of the design goals of API tokens was to + # provide stateless API access without requiring round-trips to get such CSRF tokens. + # CSRF-prevention also does not make much sense outside of the browser context. + if ($method ne 'GET' && !($api_token || $isUpload)) { + my $euid = $>; + PVE::AccessControl::verify_csrf_prevention_token($username, $token) if $euid != 0; + } } return { - ticket => $ticket, - token => $token, - userid => $username, - age => $age, - isUpload => $isUpload, - api_token => $api_token, + ticket => $ticket, + token => $token, + userid => $username, + age => $age, + isUpload => $isUpload, + api_token => $api_token, }; } @@ -132,78 +134,83 @@ sub rest_handler { my $rpcenv = $self->{rpcenv}; my $resp = { - status => HTTP_NOT_IMPLEMENTED, - message => "Method '$method $rel_uri' not implemented", + status => HTTP_NOT_IMPLEMENTED, + message => "Method '$method $rel_uri' not implemented", }; my ($handler, $info); eval { - my $uri_param = {}; - ($handler, $info) = PVE::API2->find_handler($method, $rel_uri, $uri_param); - return if !$handler || !$info; + my $uri_param = {}; + ($handler, $info) = PVE::API2->find_handler($method, $rel_uri, $uri_param); + return if !$handler || !$info; - for my $p (sort keys %{$params}) { - if (defined($uri_param->{$p}) && $uri_param->{$p} ne $params->{$p}) { - raise_param_exc({ - $p => "duplicate parameter (already defined in URI) with conflicting values!" - }); - } - $uri_param->{$p} = $params->{$p}; - } + for my $p (sort keys %{$params}) { + if (defined($uri_param->{$p}) && $uri_param->{$p} ne $params->{$p}) { + raise_param_exc({ + $p => + "duplicate parameter (already defined in URI) with conflicting values!", + }); + } + $uri_param->{$p} = $params->{$p}; + } - raise_perm_exc("URI '$rel_uri' not available with API token, need proper ticket.\n") - if $auth->{api_token} && !$info->{allowtoken}; + raise_perm_exc("URI '$rel_uri' not available with API token, need proper ticket.\n") + if $auth->{api_token} && !$info->{allowtoken}; - # check access permissions - $rpcenv->check_api2_permissions($info->{permissions}, $auth->{userid}, $uri_param); + # check access permissions + $rpcenv->check_api2_permissions($info->{permissions}, $auth->{userid}, $uri_param); - if ($info->{proxyto} || $info->{proxyto_callback}) { - my $node = PVE::API2Tools::resolve_proxyto( - $rpcenv, $info->{proxyto_callback}, $info->{proxyto}, $uri_param); + if ($info->{proxyto} || $info->{proxyto_callback}) { + my $node = PVE::API2Tools::resolve_proxyto( + $rpcenv, + $info->{proxyto_callback}, + $info->{proxyto}, + $uri_param, + ); - if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) { - die "unable to proxy file uploads" if $auth->{isUpload}; - my $remip = $self->remote_node_ip($node); - $resp = { proxy => $remip, proxynode => $node, proxy_params => $params }; - return; - } - } + if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) { + die "unable to proxy file uploads" if $auth->{isUpload}; + my $remip = $self->remote_node_ip($node); + $resp = { proxy => $remip, proxynode => $node, proxy_params => $params }; + return; + } + } - my $euid = $>; - if ($info->{protected} && ($euid != 0)) { - $resp = { proxy => 'localhost' , proxy_params => $params }; - return; - } + my $euid = $>; + if ($info->{protected} && ($euid != 0)) { + $resp = { proxy => 'localhost', proxy_params => $params }; + return; + } - $resp = { - data => $handler->handle($info, $uri_param), - info => $info, # useful to format output - status => HTTP_OK, - }; + $resp = { + data => $handler->handle($info, $uri_param), + info => $info, # useful to format output + status => HTTP_OK, + }; - if (my $count = $rpcenv->get_result_attrib('total')) { - $resp->{total} = $count; - } + if (my $count = $rpcenv->get_result_attrib('total')) { + $resp->{total} = $count; + } - if (my $diff = $rpcenv->get_result_attrib('changes')) { - $resp->{changes} = $diff; - } + if (my $diff = $rpcenv->get_result_attrib('changes')) { + $resp->{changes} = $diff; + } }; my $err = $@; $rpcenv->set_user(undef); # clear after request if ($err) { - $resp = { info => $info }; - if (ref($err) eq "PVE::Exception") { - $resp->{status} = $err->{code} || HTTP_INTERNAL_SERVER_ERROR; - $resp->{errors} = $err->{errors} if $err->{errors}; - $resp->{message} = $err->{msg}; - } else { - $resp->{status} = HTTP_INTERNAL_SERVER_ERROR; - $resp->{message} = $err; - } + $resp = { info => $info }; + if (ref($err) eq "PVE::Exception") { + $resp->{status} = $err->{code} || HTTP_INTERNAL_SERVER_ERROR; + $resp->{errors} = $err->{errors} if $err->{errors}; + $resp->{message} = $err->{msg}; + } else { + $resp->{status} = HTTP_INTERNAL_SERVER_ERROR; + $resp->{message} = $err; + } } return $resp; diff --git a/PVE/Jobs.pm b/PVE/Jobs.pm index bd323332..40caf257 100644 --- a/PVE/Jobs.pm +++ b/PVE/Jobs.pm @@ -41,37 +41,41 @@ my $saved_config_props = [qw(enabled schedule)]; sub detect_changed_runtime_props { my ($jobid, $type, $cfg) = @_; - lock_job_state($jobid, $type, sub { - my $old_state = read_job_state($jobid, $type) // $default_state; + lock_job_state( + $jobid, + $type, + sub { + my $old_state = read_job_state($jobid, $type) // $default_state; - my $updated = 0; - for my $prop (@$saved_config_props) { - my $old_prop = $old_state->{config}->{$prop} // ''; - my $new_prop = $cfg->{$prop} // ''; - next if "$old_prop" eq "$new_prop"; + my $updated = 0; + for my $prop (@$saved_config_props) { + my $old_prop = $old_state->{config}->{$prop} // ''; + my $new_prop = $cfg->{$prop} // ''; + next if "$old_prop" eq "$new_prop"; - if (defined($cfg->{$prop})) { - $old_state->{config}->{$prop} = $cfg->{$prop}; - } else { - delete $old_state->{config}->{$prop}; - } + if (defined($cfg->{$prop})) { + $old_state->{config}->{$prop} = $cfg->{$prop}; + } else { + delete $old_state->{config}->{$prop}; + } - $updated = 1; - } + $updated = 1; + } - return if !$updated; - $old_state->{updated} = time(); + return if !$updated; + $old_state->{updated} = time(); - my $path = $get_state_file->($jobid, $type); - PVE::Tools::file_set_contents($path, encode_json($old_state)); - }); + my $path = $get_state_file->($jobid, $type); + PVE::Tools::file_set_contents($path, encode_json($old_state)); + }, + ); } # lockless, since we use file_get_contents, which is atomic sub read_job_state { my ($jobid, $type) = @_; my $path = $get_state_file->($jobid, $type); - return if ! -e $path; + return if !-e $path; my $raw = PVE::Tools::file_get_contents($path); @@ -79,7 +83,7 @@ sub read_job_state { # untaint $raw if ($raw =~ m/^(\{.*\})$/) { - return decode_json($1); + return decode_json($1); } die "invalid json data in '$path'\n"; @@ -100,16 +104,16 @@ my $get_job_task_status = sub { my ($state) = @_; if (!defined($state->{upid})) { - return; # not started + return; # not started } my ($task, $filename) = PVE::Tools::upid_decode($state->{upid}, 1); die "unable to parse worker upid - $state->{upid}\n" if !$task; - die "no such task\n" if ! -f $filename; + die "no such task\n" if !-f $filename; my $pstart = PVE::ProcFSTools::read_proc_starttime($task->{pid}); if ($pstart && $pstart == $task->{pstart}) { - return; # still running + return; # still running } return PVE::Tools::upid_read_status($state->{upid}); @@ -125,24 +129,28 @@ sub update_job_stopped { return if !defined($state) || $state->{state} ne 'started'; # removed or not started if (defined($get_job_task_status->($state))) { - lock_job_state($jobid, $type, sub { - my $state = read_job_state($jobid, $type); - return if !defined($state) || $state->{state} ne 'started'; # removed or not started + lock_job_state( + $jobid, + $type, + sub { + my $state = read_job_state($jobid, $type); + return if !defined($state) || $state->{state} ne 'started'; # removed or not started - my $new_state = { - state => 'stopped', - msg => $get_job_task_status->($state) // 'internal error', - upid => $state->{upid}, - config => $state->{config}, - }; + my $new_state = { + state => 'stopped', + msg => $get_job_task_status->($state) // 'internal error', + upid => $state->{upid}, + config => $state->{config}, + }; - if ($state->{updated}) { # save updated time stamp - $new_state->{updated} = $state->{updated}; - } + if ($state->{updated}) { # save updated time stamp + $new_state->{updated} = $state->{updated}; + } - my $path = $get_state_file->($jobid, $type); - PVE::Tools::file_set_contents($path, encode_json($new_state)); - }); + my $path = $get_state_file->($jobid, $type); + PVE::Tools::file_set_contents($path, encode_json($new_state)); + }, + ); } } @@ -150,23 +158,27 @@ sub update_job_stopped { sub create_job { my ($jobid, $type, $cfg) = @_; - lock_job_state($jobid, $type, sub { - my $state = read_job_state($jobid, $type) // $default_state; + lock_job_state( + $jobid, + $type, + sub { + my $state = read_job_state($jobid, $type) // $default_state; - if ($state->{state} ne 'created') { - die "job state already exists\n"; - } + if ($state->{state} ne 'created') { + die "job state already exists\n"; + } - $state->{time} = time(); - for my $prop (@$saved_config_props) { - if (defined($cfg->{$prop})) { - $state->{config}->{$prop} = $cfg->{$prop}; - } - } + $state->{time} = time(); + for my $prop (@$saved_config_props) { + if (defined($cfg->{$prop})) { + $state->{config}->{$prop} = $cfg->{$prop}; + } + } - my $path = $get_state_file->($jobid, $type); - PVE::Tools::file_set_contents($path, encode_json($state)); - }); + my $path = $get_state_file->($jobid, $type); + PVE::Tools::file_set_contents($path, encode_json($state)); + }, + ); } # to be called when the job is removed @@ -185,61 +197,73 @@ sub starting_job { my $state = read_job_state($jobid, $type); return 0 if !defined($state) || $state->{state} eq 'started'; # removed or already started - lock_job_state($jobid, $type, sub { - my $state = read_job_state($jobid, $type); - return 0 if !defined($state) || $state->{state} eq 'started'; # removed or already started + lock_job_state( + $jobid, + $type, + sub { + my $state = read_job_state($jobid, $type); + return 0 if !defined($state) || $state->{state} eq 'started'; # removed or already started - my $new_state = { - state => 'starting', - time => time(), - config => $state->{config}, - }; + my $new_state = { + state => 'starting', + time => time(), + config => $state->{config}, + }; - my $path = $get_state_file->($jobid, $type); - PVE::Tools::file_set_contents($path, encode_json($new_state)); - }); + my $path = $get_state_file->($jobid, $type); + PVE::Tools::file_set_contents($path, encode_json($new_state)); + }, + ); return 1; } sub started_job { my ($jobid, $type, $upid, $msg) = @_; - lock_job_state($jobid, $type, sub { - my $state = read_job_state($jobid, $type); - return if !defined($state); # job was removed, do not update - die "unexpected state '$state->{state}'\n" if $state->{state} ne 'starting'; + lock_job_state( + $jobid, + $type, + sub { + my $state = read_job_state($jobid, $type); + return if !defined($state); # job was removed, do not update + die "unexpected state '$state->{state}'\n" if $state->{state} ne 'starting'; - my $new_state; - if (defined($msg)) { - $new_state = { - state => 'stopped', - msg => $msg, - time => time(), - }; - } else { - $new_state = { - state => 'started', - upid => $upid, - }; - } - $new_state->{config} = $state->{config}; + my $new_state; + if (defined($msg)) { + $new_state = { + state => 'stopped', + msg => $msg, + time => time(), + }; + } else { + $new_state = { + state => 'started', + upid => $upid, + }; + } + $new_state->{config} = $state->{config}; - my $path = $get_state_file->($jobid, $type); - PVE::Tools::file_set_contents($path, encode_json($new_state)); - }); + my $path = $get_state_file->($jobid, $type); + PVE::Tools::file_set_contents($path, encode_json($new_state)); + }, + ); } # will be called when the job schedule is updated sub update_last_runtime { my ($jobid, $type) = @_; - lock_job_state($jobid, $type, sub { - my $old_state = read_job_state($jobid, $type) // $default_state; + lock_job_state( + $jobid, + $type, + sub { + my $old_state = read_job_state($jobid, $type) // $default_state; - $old_state->{updated} = time(); + $old_state->{updated} = time(); - my $path = $get_state_file->($jobid, $type); - PVE::Tools::file_set_contents($path, encode_json($old_state)); - }); + my $path = $get_state_file->($jobid, $type); + PVE::Tools::file_set_contents($path, encode_json($old_state)); + }, + ); } sub get_last_runtime { @@ -250,9 +274,9 @@ sub get_last_runtime { return $state->{updated} if defined($state->{updated}); if (my $upid = $state->{upid}) { - my ($task) = PVE::Tools::upid_decode($upid, 1); - die "unable to parse worker upid\n" if !$task; - return $task->{starttime}; + my ($task) = PVE::Tools::upid_decode($upid, 1); + die "unable to parse worker upid\n" if !$task; + return $task->{starttime}; } return $state->{time} // 0; @@ -266,77 +290,85 @@ sub run_jobs { my $jobs_cfg = cfs_read_file('jobs.cfg'); my $nodename = PVE::INotify::nodename(); - foreach my $id (sort keys %{$jobs_cfg->{ids}}) { - my $cfg = $jobs_cfg->{ids}->{$id}; - my $type = $cfg->{type}; - my $schedule = delete $cfg->{schedule}; + foreach my $id (sort keys %{ $jobs_cfg->{ids} }) { + my $cfg = $jobs_cfg->{ids}->{$id}; + my $type = $cfg->{type}; + my $schedule = delete $cfg->{schedule}; - # only schedule local jobs - next if defined($cfg->{node}) && $cfg->{node} ne $nodename; + # only schedule local jobs + next if defined($cfg->{node}) && $cfg->{node} ne $nodename; - eval { update_job_stopped($id, $type) }; - if (my $err = $@) { - warn "could not update job state, skipping - $err\n"; - next; - } + eval { update_job_stopped($id, $type) }; + if (my $err = $@) { + warn "could not update job state, skipping - $err\n"; + next; + } - # update last runtime on the first run when 'repeat-missed' is 0, so that a missed job - # will not start immediately after boot - update_last_runtime($id, $type) if $first_run && !$cfg->{'repeat-missed'}; + # update last runtime on the first run when 'repeat-missed' is 0, so that a missed job + # will not start immediately after boot + update_last_runtime($id, $type) if $first_run && !$cfg->{'repeat-missed'}; - next if defined($cfg->{enabled}) && !$cfg->{enabled}; # only schedule actually enabled jobs + next if defined($cfg->{enabled}) && !$cfg->{enabled}; # only schedule actually enabled jobs - my $last_run = get_last_runtime($id, $type); - my $calspec = PVE::CalendarEvent::parse_calendar_event($schedule); - my $next_sync = PVE::CalendarEvent::compute_next_event($calspec, $last_run); + my $last_run = get_last_runtime($id, $type); + my $calspec = PVE::CalendarEvent::parse_calendar_event($schedule); + my $next_sync = PVE::CalendarEvent::compute_next_event($calspec, $last_run); - next if !defined($next_sync) || time() < $next_sync; # not yet its (next) turn + next if !defined($next_sync) || time() < $next_sync; # not yet its (next) turn - my $plugin = PVE::Job::Registry->lookup($type); - if (starting_job($id, $type)) { - PVE::Cluster::cfs_update(); + my $plugin = PVE::Job::Registry->lookup($type); + if (starting_job($id, $type)) { + PVE::Cluster::cfs_update(); - my $upid = eval { $plugin->run($cfg, $id, $schedule) }; - if (my $err = $@) { - warn $@ if $@; - started_job($id, $type, undef, $err); - } elsif ($upid eq 'OK') { # some jobs return OK immediately - started_job($id, $type, undef, 'OK'); - } else { - started_job($id, $type, $upid); - } - } + my $upid = eval { $plugin->run($cfg, $id, $schedule) }; + if (my $err = $@) { + warn $@ if $@; + started_job($id, $type, undef, $err); + } elsif ($upid eq 'OK') { # some jobs return OK immediately + started_job($id, $type, undef, 'OK'); + } else { + started_job($id, $type, $upid); + } + } } } # creates and removes statefiles for job configs sub synchronize_job_states_with_config { - cfs_lock_file('jobs.cfg', undef, sub { - my $data = cfs_read_file('jobs.cfg'); + cfs_lock_file( + 'jobs.cfg', + undef, + sub { + my $data = cfs_read_file('jobs.cfg'); - for my $id (keys $data->{ids}->%*) { - my $job = $data->{ids}->{$id}; - my $type = $job->{type}; + for my $id (keys $data->{ids}->%*) { + my $job = $data->{ids}->{$id}; + my $type = $job->{type}; - my $path = $get_state_file->($id, $type); - if (-e $path) { - detect_changed_runtime_props($id, $type, $job); - } else { - create_job($id, $type, $job); - } - } + my $path = $get_state_file->($id, $type); + if (-e $path) { + detect_changed_runtime_props($id, $type, $job); + } else { + create_job($id, $type, $job); + } + } - my $valid_types = PVE::Job::Registry->lookup_types(); - my $type_regex = join("|", $valid_types->@*); + my $valid_types = PVE::Job::Registry->lookup_types(); + my $type_regex = join("|", $valid_types->@*); - PVE::Tools::dir_glob_foreach($state_dir, "(${type_regex})-(.*).json", sub { - my ($path, $type, $id) = @_; + PVE::Tools::dir_glob_foreach( + $state_dir, + "(${type_regex})-(.*).json", + sub { + my ($path, $type, $id) = @_; - if (!defined($data->{ids}->{$id})) { - remove_job($id, $type); - } - }); - }); + if (!defined($data->{ids}->{$id})) { + remove_job($id, $type); + } + }, + ); + }, + ); die $@ if $@; } diff --git a/PVE/Jobs/VZDump.pm b/PVE/Jobs/VZDump.pm index 2dad3f55..d7a2e08d 100644 --- a/PVE/Jobs/VZDump.pm +++ b/PVE/Jobs/VZDump.pm @@ -17,17 +17,17 @@ sub run { my $props = $class->properties(); # remove all non vzdump related options foreach my $opt (keys %$conf) { - delete $conf->{$opt} if !defined($props->{$opt}); + delete $conf->{$opt} if !defined($props->{$opt}); } $conf->{'job-id'} = $job_id; # Required as string parameters # FIXME why?! we could just check ref() for my $key (keys $PVE::VZDump::Common::PROPERTY_STRINGS->%*) { - if ($conf->{$key} && ref($conf->{$key}) eq 'HASH') { - my $format = $PVE::VZDump::Common::PROPERTY_STRINGS->{$key}; - $conf->{$key} = PVE::JSONSchema::print_property_string($conf->{$key}, $format); - } + if ($conf->{$key} && ref($conf->{$key}) eq 'HASH') { + my $format = $PVE::VZDump::Common::PROPERTY_STRINGS->{$key}; + $conf->{$key} = PVE::JSONSchema::print_property_string($conf->{$key}, $format); + } } $conf->{quiet} = 1; # do not write to stdout/stderr diff --git a/PVE/NodeConfig.pm b/PVE/NodeConfig.pm index d9d83615..87eea2a5 100644 --- a/PVE/NodeConfig.pm +++ b/PVE/NodeConfig.pm @@ -15,25 +15,31 @@ my $MAXDOMAINS = 5; my $node_config_lock = '/var/lock/pvenode.lock'; -PVE::JSONSchema::register_format('pve-acme-domain', sub { - my ($domain, $noerr) = @_; +PVE::JSONSchema::register_format( + 'pve-acme-domain', + sub { + my ($domain, $noerr) = @_; - my $label = qr/[a-z0-9][a-z0-9_-]*/i; + my $label = qr/[a-z0-9][a-z0-9_-]*/i; - return $domain if $domain =~ /^$label(?:\.$label)+$/; - return undef if $noerr; - die "value '$domain' does not look like a valid domain name!\n"; -}); + return $domain if $domain =~ /^$label(?:\.$label)+$/; + return undef if $noerr; + die "value '$domain' does not look like a valid domain name!\n"; + }, +); -PVE::JSONSchema::register_format('pve-acme-alias', sub { - my ($alias, $noerr) = @_; +PVE::JSONSchema::register_format( + 'pve-acme-alias', + sub { + my ($alias, $noerr) = @_; - my $label = qr/[a-z0-9_][a-z0-9_-]*/i; + my $label = qr/[a-z0-9_][a-z0-9_-]*/i; - return $alias if $alias =~ /^$label(?:\.$label)+$/; - return undef if $noerr; - die "value '$alias' does not look like a valid alias name!\n"; -}); + return $alias if $alias =~ /^$label(?:\.$label)+$/; + return undef if $noerr; + die "value '$alias' does not look like a valid alias name!\n"; + }, +); sub config_file { my ($node) = @_; @@ -66,8 +72,8 @@ sub lock_config { # make sure configuration file is up-to-date my $code = sub { - PVE::Cluster::cfs_update(); - $realcode->(@_); + PVE::Cluster::cfs_update(); + $realcode->(@_); }; my $res = lock_file($node_config_lock, 10, $code, @param); @@ -79,53 +85,54 @@ sub lock_config { my $confdesc = { description => { - type => 'string', - description => "Description for the Node. Shown in the web-interface node notes panel." - ." This is saved as comment inside the configuration file.", - maxLength => 64 * 1024, - optional => 1, + type => 'string', + description => "Description for the Node. Shown in the web-interface node notes panel." + . " This is saved as comment inside the configuration file.", + maxLength => 64 * 1024, + optional => 1, }, 'startall-onboot-delay' => { - description => 'Initial delay in seconds, before starting all the Virtual Guests with on-boot enabled.', - type => 'integer', - minimum => 0, - maximum => 300, - default => 0, - optional => 1, + description => + 'Initial delay in seconds, before starting all the Virtual Guests with on-boot enabled.', + type => 'integer', + minimum => 0, + maximum => 300, + default => 0, + optional => 1, }, 'ballooning-target' => { - description => 'RAM usage target for ballooning (in percent of total memory)', - type => 'integer', - minimum => 0, - maximum => 100, - default => 80, - optional => 1, + description => 'RAM usage target for ballooning (in percent of total memory)', + type => 'integer', + minimum => 0, + maximum => 100, + default => 80, + optional => 1, }, }; my $wakeonlan_desc = { mac => { - type => 'string', - description => 'MAC address for wake on LAN', - format => 'mac-addr', - format_description => 'MAC address', - default_key => 1, + type => 'string', + description => 'MAC address for wake on LAN', + format => 'mac-addr', + format_description => 'MAC address', + default_key => 1, }, 'bind-interface' => { - type => 'string', - description => 'Bind to this interface when sending wake on LAN packet', - default => 'The interface carrying the default route', - format => 'pve-iface', - format_description => 'bind interface', - optional => 1, + type => 'string', + description => 'Bind to this interface when sending wake on LAN packet', + default => 'The interface carrying the default route', + format => 'pve-iface', + format_description => 'bind interface', + optional => 1, }, 'broadcast-address' => { - type => 'string', - description => 'IPv4 broadcast address to use when sending wake on LAN packet', - default => '255.255.255.255', - format => 'ipv4', - format_description => 'IPv4 broadcast address', - optional => 1, + type => 'string', + description => 'IPv4 broadcast address to use when sending wake on LAN packet', + default => '255.255.255.255', + format => 'ipv4', + format_description => 'IPv4 broadcast address', + optional => 1, }, }; @@ -138,37 +145,37 @@ $confdesc->{wakeonlan} = { my $acme_domain_desc = { domain => { - type => 'string', - format => 'pve-acme-domain', - format_description => 'domain', - description => 'domain for this node\'s ACME certificate', - default_key => 1, + type => 'string', + format => 'pve-acme-domain', + format_description => 'domain', + description => 'domain for this node\'s ACME certificate', + default_key => 1, }, plugin => { - type => 'string', - format => 'pve-configid', - description => 'The ACME plugin ID', - format_description => 'name of the plugin configuration', - optional => 1, - default => 'standalone', + type => 'string', + format => 'pve-configid', + description => 'The ACME plugin ID', + format_description => 'name of the plugin configuration', + optional => 1, + default => 'standalone', }, alias => { - type => 'string', - format => 'pve-acme-alias', - format_description => 'domain', - description => 'Alias for the Domain to verify ACME Challenge over DNS', - optional => 1, + type => 'string', + format => 'pve-acme-alias', + format_description => 'domain', + description => 'Alias for the Domain to verify ACME Challenge over DNS', + optional => 1, }, }; my $acmedesc = { account => get_standard_option('pve-acme-account-name'), domains => { - type => 'string', - format => 'pve-acme-domain-list', - format_description => 'domain[;domain;...]', - description => 'List of domains for this node\'s ACME certificate', - optional => 1, + type => 'string', + format => 'pve-acme-domain-list', + format_description => 'domain[;domain;...]', + description => 'List of domains for this node\'s ACME certificate', + optional => 1, }, }; @@ -179,14 +186,14 @@ $confdesc->{acme} = { optional => 1, }; -for my $i (0..$MAXDOMAINS) { +for my $i (0 .. $MAXDOMAINS) { $confdesc->{"acmedomain$i"} = { - type => 'string', - description => 'ACME domain and validation plugin', - format => $acme_domain_desc, - optional => 1, + type => 'string', + description => 'ACME domain and validation plugin', + format => $acme_domain_desc, + optional => 1, }; -}; +} my $conf_schema = { type => 'object', @@ -212,17 +219,17 @@ sub write_node_config { # add description as comment to top of file my $descr = $conf->{description} || ''; foreach my $cl (split(/\n/, $descr)) { - $raw .= '#' . PVE::Tools::encode_text($cl) . "\n"; + $raw .= '#' . PVE::Tools::encode_text($cl) . "\n"; } for my $key (sort keys %$conf) { - next if ($key eq 'description'); - next if ($key eq 'digest'); + next if ($key eq 'description'); + next if ($key eq 'digest'); - my $value = $conf->{$key}; - die "detected invalid newline inside property '$key'\n" - if $value =~ m/\n/; - $raw .= "$key: $value\n"; + my $value = $conf->{$key}; + die "detected invalid newline inside property '$key'\n" + if $value =~ m/\n/; + $raw .= "$key: $value\n"; } return $raw; @@ -235,10 +242,10 @@ sub get_wakeonlan_config { my $res = {}; if (defined($node_conf->{wakeonlan})) { - $res = eval { - PVE::JSONSchema::parse_property_string($wakeonlan_desc, $node_conf->{wakeonlan}) - }; - die $@ if $@; + $res = eval { + PVE::JSONSchema::parse_property_string($wakeonlan_desc, $node_conf->{wakeonlan}); + }; + die $@ if $@; } return $res; @@ -254,55 +261,52 @@ sub get_acme_conf { my $res = {}; if (defined($node_conf->{acme})) { - $res = eval { - PVE::JSONSchema::parse_property_string($acmedesc, $node_conf->{acme}) - }; - if (my $err = $@) { - return undef if $noerr; - die $err; - } - my $standalone_domains = delete($res->{domains}) // ''; - $res->{domains} = {}; - for my $domain (split(";", $standalone_domains)) { - $domain = lc($domain); - die "duplicate domain '$domain' in ACME config properties\n" - if defined($res->{domains}->{$domain}); + $res = eval { PVE::JSONSchema::parse_property_string($acmedesc, $node_conf->{acme}) }; + if (my $err = $@) { + return undef if $noerr; + die $err; + } + my $standalone_domains = delete($res->{domains}) // ''; + $res->{domains} = {}; + for my $domain (split(";", $standalone_domains)) { + $domain = lc($domain); + die "duplicate domain '$domain' in ACME config properties\n" + if defined($res->{domains}->{$domain}); - $res->{domains}->{$domain}->{plugin} = 'standalone'; - $res->{domains}->{$domain}->{_configkey} = 'acme'; - } + $res->{domains}->{$domain}->{plugin} = 'standalone'; + $res->{domains}->{$domain}->{_configkey} = 'acme'; + } } $res->{account} //= 'default'; - for my $index (0..$MAXDOMAINS) { - my $domain_rec = $node_conf->{"acmedomain$index"}; - next if !defined($domain_rec); + for my $index (0 .. $MAXDOMAINS) { + my $domain_rec = $node_conf->{"acmedomain$index"}; + next if !defined($domain_rec); - my $parsed = eval { - PVE::JSONSchema::parse_property_string($acme_domain_desc, $domain_rec) - }; - if (my $err = $@) { - return undef if $noerr; - die $err; - } - my $domain = lc(delete $parsed->{domain}); - if (my $exists = $res->{domains}->{$domain}) { - return undef if $noerr; - die "duplicate domain '$domain' in ACME config properties" - ." 'acmedomain$index' and '$exists->{_configkey}'\n"; - } - $parsed->{plugin} //= 'standalone'; + my $parsed = + eval { PVE::JSONSchema::parse_property_string($acme_domain_desc, $domain_rec) }; + if (my $err = $@) { + return undef if $noerr; + die $err; + } + my $domain = lc(delete $parsed->{domain}); + if (my $exists = $res->{domains}->{$domain}) { + return undef if $noerr; + die "duplicate domain '$domain' in ACME config properties" + . " 'acmedomain$index' and '$exists->{_configkey}'\n"; + } + $parsed->{plugin} //= 'standalone'; - my $plugin_id = $parsed->{plugin}; - if ($plugin_id ne 'standalone') { - my $plugins = PVE::API2::ACMEPlugin::load_config(); - die "plugin '$plugin_id' for domain '$domain' not found!\n" - if !$plugins->{ids}->{$plugin_id}; - } + my $plugin_id = $parsed->{plugin}; + if ($plugin_id ne 'standalone') { + my $plugins = PVE::API2::ACMEPlugin::load_config(); + die "plugin '$plugin_id' for domain '$domain' not found!\n" + if !$plugins->{ids}->{$plugin_id}; + } - $parsed->{_configkey} = "acmedomain$index"; - $res->{domains}->{$domain} = $parsed; + $parsed->{_configkey} = "acmedomain$index"; + $res->{domains}->{$domain} = $parsed; } return $res; diff --git a/PVE/PullMetric.pm b/PVE/PullMetric.pm index 954bd604..f5565350 100644 --- a/PVE/PullMetric.pm +++ b/PVE/PullMetric.pm @@ -12,17 +12,18 @@ my $cache; my $get_cache = sub { if (!defined($cache)) { - my $uid = getpwnam('root'); - my $gid = getgrnam('www-data'); + my $uid = getpwnam('root'); + my $gid = getgrnam('www-data'); - $cache = Proxmox::RS::SharedCache->new({ - path => "/run/pve/metrics", - owner => $uid, - group => $gid, - entry_mode => 0640, # Entry permissions - keep_old => OLD_GENERATIONS, - } - ); + $cache = Proxmox::RS::SharedCache->new( + { + path => "/run/pve/metrics", + owner => $uid, + group => $gid, + entry_mode => 0640, # Entry permissions + keep_old => OLD_GENERATIONS, + }, + ); } return $cache; @@ -55,24 +56,24 @@ my sub gauge { my ($id, $timestamp, $metric, $value) = @_; return { - metric => $metric, - id => $id, - value => $value + 0, - timestamp => $timestamp + 0, - type => 'gauge', - } + metric => $metric, + id => $id, + value => $value + 0, + timestamp => $timestamp + 0, + type => 'gauge', + }; } my sub derive { my ($id, $timestamp, $metric, $value) = @_; return { - metric => $metric, - id => $id, - value => $value + 0, - timestamp => $timestamp + 0, - type => 'derive', - } + metric => $metric, + id => $id, + value => $value + 0, + timestamp => $timestamp + 0, + type => 'derive', + }; } my $nodename = PVE::INotify::nodename(); @@ -91,8 +92,8 @@ my sub get_node_metrics { my ($netin, $netout) = (0, 0); for my $dev (grep { /^$PVE::Network::PHYSICAL_NIC_RE$/ } keys $data->{nics}->%*) { - $netin += $data->{nics}->{$dev}->{receive}; - $netout += $data->{nics}->{$dev}->{transmit}; + $netin += $data->{nics}->{$dev}->{receive}; + $netout += $data->{nics}->{$dev}->{transmit}; } push @$metrics, derive($id, $timestamp, "net_in", $netin); push @$metrics, derive($id, $timestamp, "net_out", $netout); @@ -127,24 +128,24 @@ my sub get_qemu_metrics { my $timestamp = $stats->{timestamp}; for my $vmid (keys $stats->{data}->%*) { - my $id = "qemu/$vmid"; - my $guest_data = $stats->{data}->{$vmid}; + my $id = "qemu/$vmid"; + my $guest_data = $stats->{data}->{$vmid}; - if ($guest_data->{status} eq 'running') { - push @$metrics, gauge($id, $timestamp, "cpu_current", $guest_data->{cpu}); - push @$metrics, gauge($id, $timestamp, "mem_used", $guest_data->{mem}); - push @$metrics, derive($id, $timestamp, "disk_read", $guest_data->{diskread}); - push @$metrics, derive($id, $timestamp, "disk_write", $guest_data->{diskwrite}); - push @$metrics, derive($id, $timestamp, "net_in", $guest_data->{netin}); - push @$metrics, derive($id, $timestamp, "net_out", $guest_data->{netout}); - } + if ($guest_data->{status} eq 'running') { + push @$metrics, gauge($id, $timestamp, "cpu_current", $guest_data->{cpu}); + push @$metrics, gauge($id, $timestamp, "mem_used", $guest_data->{mem}); + push @$metrics, derive($id, $timestamp, "disk_read", $guest_data->{diskread}); + push @$metrics, derive($id, $timestamp, "disk_write", $guest_data->{diskwrite}); + push @$metrics, derive($id, $timestamp, "net_in", $guest_data->{netin}); + push @$metrics, derive($id, $timestamp, "net_out", $guest_data->{netout}); + } - push @$metrics, gauge($id, $timestamp, "uptime", $guest_data->{uptime}); - push @$metrics, gauge($id, $timestamp, "cpu_max", $guest_data->{cpus}); - push @$metrics, gauge($id, $timestamp, "mem_total", $guest_data->{maxmem}); - push @$metrics, gauge($id, $timestamp, "disk_total", $guest_data->{maxdisk}); - # TODO: This one always seems to be 0? - # push @$metrics, num_metric("disk_used", $id, $guest_data->{disk}, $timestamp); + push @$metrics, gauge($id, $timestamp, "uptime", $guest_data->{uptime}); + push @$metrics, gauge($id, $timestamp, "cpu_max", $guest_data->{cpus}); + push @$metrics, gauge($id, $timestamp, "mem_total", $guest_data->{maxmem}); + push @$metrics, gauge($id, $timestamp, "disk_total", $guest_data->{maxdisk}); + # TODO: This one always seems to be 0? + # push @$metrics, num_metric("disk_used", $id, $guest_data->{disk}, $timestamp); } return $metrics; @@ -158,23 +159,23 @@ my sub get_lxc_metrics { my $timestamp = $stats->{timestamp}; for my $vmid (keys $stats->{data}->%*) { - my $id = "lxc/$vmid"; - my $guest_data = $stats->{data}->{$vmid}; + my $id = "lxc/$vmid"; + my $guest_data = $stats->{data}->{$vmid}; - if ($guest_data->{status} eq 'running') { - push @$metrics, gauge($id, $timestamp, "cpu_current", $guest_data->{cpu}); - push @$metrics, gauge($id, $timestamp, "mem_used", $guest_data->{mem}); - push @$metrics, derive($id, $timestamp, "disk_read", $guest_data->{diskread}); - push @$metrics, derive($id, $timestamp, "disk_write", $guest_data->{diskwrite}); - push @$metrics, derive($id, $timestamp, "net_in", $guest_data->{netin}); - push @$metrics, derive($id, $timestamp, "net_out", $guest_data->{netout}); - push @$metrics, gauge($id, $timestamp, "disk_used", $guest_data->{disk}); - } + if ($guest_data->{status} eq 'running') { + push @$metrics, gauge($id, $timestamp, "cpu_current", $guest_data->{cpu}); + push @$metrics, gauge($id, $timestamp, "mem_used", $guest_data->{mem}); + push @$metrics, derive($id, $timestamp, "disk_read", $guest_data->{diskread}); + push @$metrics, derive($id, $timestamp, "disk_write", $guest_data->{diskwrite}); + push @$metrics, derive($id, $timestamp, "net_in", $guest_data->{netin}); + push @$metrics, derive($id, $timestamp, "net_out", $guest_data->{netout}); + push @$metrics, gauge($id, $timestamp, "disk_used", $guest_data->{disk}); + } - push @$metrics, gauge($id, $timestamp, "uptime", $guest_data->{uptime}); - push @$metrics, gauge($id, $timestamp, "cpu_max", $guest_data->{cpus}); - push @$metrics, gauge($id, $timestamp, "mem_total", $guest_data->{maxmem}); - push @$metrics, gauge($id, $timestamp, "disk_total", $guest_data->{maxdisk}); + push @$metrics, gauge($id, $timestamp, "uptime", $guest_data->{uptime}); + push @$metrics, gauge($id, $timestamp, "cpu_max", $guest_data->{cpus}); + push @$metrics, gauge($id, $timestamp, "mem_total", $guest_data->{maxmem}); + push @$metrics, gauge($id, $timestamp, "disk_total", $guest_data->{maxdisk}); } return $metrics; @@ -188,11 +189,11 @@ my sub get_storage_metrics { my $timestamp = $stats->{timestamp}; for my $sid (keys $stats->{data}->%*) { - my $id = "storage/$nodename/$sid"; - my $data = $stats->{data}->{$sid}; + my $id = "storage/$nodename/$sid"; + my $data = $stats->{data}->{$sid}; - push @$metrics, gauge($id, $timestamp, "disk_total", $data->{total}); - push @$metrics, gauge($id, $timestamp, "disk_used", $data->{used}); + push @$metrics, gauge($id, $timestamp, "disk_total", $data->{total}); + push @$metrics, gauge($id, $timestamp, "disk_used", $data->{used}); } return $metrics; @@ -213,10 +214,10 @@ sub get_local_metrics { my $data = $get_cache->()->get_last($history); for my $stat_gen ($data->@*) { - push @$metrics, get_node_metrics($stat_gen->{node})->@*; - push @$metrics, get_qemu_metrics($stat_gen->{qemu})->@*; - push @$metrics, get_lxc_metrics($stat_gen->{lxc})->@*; - push @$metrics, get_storage_metrics($stat_gen->{storage})->@*; + push @$metrics, get_node_metrics($stat_gen->{node})->@*; + push @$metrics, get_qemu_metrics($stat_gen->{qemu})->@*; + push @$metrics, get_lxc_metrics($stat_gen->{lxc})->@*; + push @$metrics, get_storage_metrics($stat_gen->{storage})->@*; } return $metrics; diff --git a/PVE/Report.pm b/PVE/Report.pm index 31ddadf3..34094a87 100644 --- a/PVE/Report.pm +++ b/PVE/Report.pm @@ -11,12 +11,16 @@ my sub dir2text { print STDERR "dir2text '${target_dir}${regexp}'..."; my $text = "# output '${target_dir}${regexp}' file(s)\n"; - PVE::Tools::dir_glob_foreach($target_dir, $regexp, sub { - my ($file) = @_; - return if $file eq '.' || $file eq '..'; - $text .= "\n# cat $target_dir$file\n"; - $text .= PVE::Tools::file_get_contents($target_dir.$file)."\n"; - }); + PVE::Tools::dir_glob_foreach( + $target_dir, + $regexp, + sub { + my ($file) = @_; + return if $file eq '.' || $file eq '..'; + $text .= "\n# cat $target_dir$file\n"; + $text .= PVE::Tools::file_get_contents($target_dir . $file) . "\n"; + }, + ); return $text; } @@ -25,147 +29,143 @@ my sub cmd_exists { system("command -v '$_[0]' > /dev/null 2>&1") == 0 } my $init_report_cmds = sub { my $report_def = { - general => { - title => 'general system info', - order => 10, - cmds => [ - 'hostname', - 'date -R', - 'cat /proc/cmdline', - 'pveversion --verbose', - 'cat /etc/hosts', - 'pvesubscription get', - 'cat /etc/apt/sources.list', - sub { dir2text('/etc/apt/sources.list.d/', '.+\.list') }, - sub { dir2text('/etc/apt/sources.list.d/', '.+\.sources') }, - 'apt-cache policy | grep -vP "^ +origin "', - 'apt-mark showhold', - 'lscpu', - 'pvesh get /cluster/resources --type node --output-format=yaml', - ], - }, - 'system-load' => { - title => 'overall system load info', - order => 20, - cmds => [ - 'top -b -c -w512 -n 1 -o TIME | head -n 30', - 'head /proc/pressure/*', - ], - }, - storage => { - order => 30, - cmds => [ - 'cat /etc/pve/storage.cfg', - 'pvesm status', - 'cat /etc/fstab', - 'findmnt --ascii', - 'df --human -T', - 'proxmox-boot-tool status', - ], - }, - 'virtual guests' => { - order => 40, - cmds => [ - 'qm list', - sub { dir2text('/etc/pve/qemu-server/', '\d+\.conf') }, - 'pct list', - sub { dir2text('/etc/pve/lxc/', '\d+\.conf') }, - ], - }, - network => { - order => 45, - cmds => [ - 'ip -details -statistics address', - 'ip -details -4 route show', - 'ip -details -6 route show', - 'cat /etc/network/interfaces', - sub { dir2text('/etc/network/interfaces.d/', '.*') }, - 'cat /etc/pve/sdn/.running-config', - sub { dir2text('/etc/pve/sdn/', '.+\.cfg') }, - sub { dir2text('/etc/pve/sdn/', '.+\.json') }, - ], - }, - firewall => { - order => 50, - cmds => [ - sub { dir2text('/etc/pve/firewall/', '.+\.fw') }, - 'cat /etc/pve/local/host.fw', - sub { dir2text('/etc/pve/sdn/firewall/', '.+\.fw') }, - 'iptables-save -c | column -t -l4 -o" "', - ], - }, - cluster => { - order => 60, - cmds => [ - 'pvecm nodes', - 'pvecm status', - 'cat /etc/pve/corosync.conf 2>/dev/null', - 'ha-manager status', - 'cat /etc/pve/datacenter.cfg', - ], - }, - jobs => { - order => 65, - cmds => [ - 'cat /etc/pve/jobs.cfg', - ], - }, - hardware => { - order => 70, - cmds => [ - 'dmidecode -t bios', - 'lspci -nnk', - ], - }, - 'block devices' => { - order => 80, - cmds => [ - 'lsblk --ascii -M -o +HOTPLUG,ROTA,PHY-SEC,FSTYPE,MODEL,TRAN,WWN', - 'ls -l /dev/disk/by-*/', - 'iscsiadm -m node', - 'iscsiadm -m session', - ], - }, - volumes => { - order => 90, - cmds => [ - 'pvs', - 'lvs', - 'vgs', - ], - }, + general => { + title => 'general system info', + order => 10, + cmds => [ + 'hostname', + 'date -R', + 'cat /proc/cmdline', + 'pveversion --verbose', + 'cat /etc/hosts', + 'pvesubscription get', + 'cat /etc/apt/sources.list', + sub { dir2text('/etc/apt/sources.list.d/', '.+\.list') }, + sub { dir2text('/etc/apt/sources.list.d/', '.+\.sources') }, + 'apt-cache policy | grep -vP "^ +origin "', + 'apt-mark showhold', + 'lscpu', + 'pvesh get /cluster/resources --type node --output-format=yaml', + ], + }, + 'system-load' => { + title => 'overall system load info', + order => 20, + cmds => [ + 'top -b -c -w512 -n 1 -o TIME | head -n 30', 'head /proc/pressure/*', + ], + }, + storage => { + order => 30, + cmds => [ + 'cat /etc/pve/storage.cfg', + 'pvesm status', + 'cat /etc/fstab', + 'findmnt --ascii', + 'df --human -T', + 'proxmox-boot-tool status', + ], + }, + 'virtual guests' => { + order => 40, + cmds => [ + 'qm list', + sub { dir2text('/etc/pve/qemu-server/', '\d+\.conf') }, + 'pct list', + sub { dir2text('/etc/pve/lxc/', '\d+\.conf') }, + ], + }, + network => { + order => 45, + cmds => [ + 'ip -details -statistics address', + 'ip -details -4 route show', + 'ip -details -6 route show', + 'cat /etc/network/interfaces', + sub { dir2text('/etc/network/interfaces.d/', '.*') }, + 'cat /etc/pve/sdn/.running-config', + sub { dir2text('/etc/pve/sdn/', '.+\.cfg') }, + sub { dir2text('/etc/pve/sdn/', '.+\.json') }, + ], + }, + firewall => { + order => 50, + cmds => [ + sub { dir2text('/etc/pve/firewall/', '.+\.fw') }, + 'cat /etc/pve/local/host.fw', + sub { dir2text('/etc/pve/sdn/firewall/', '.+\.fw') }, + 'iptables-save -c | column -t -l4 -o" "', + ], + }, + cluster => { + order => 60, + cmds => [ + 'pvecm nodes', + 'pvecm status', + 'cat /etc/pve/corosync.conf 2>/dev/null', + 'ha-manager status', + 'cat /etc/pve/datacenter.cfg', + ], + }, + jobs => { + order => 65, + cmds => [ + 'cat /etc/pve/jobs.cfg', + ], + }, + hardware => { + order => 70, + cmds => [ + 'dmidecode -t bios', 'lspci -nnk', + ], + }, + 'block devices' => { + order => 80, + cmds => [ + 'lsblk --ascii -M -o +HOTPLUG,ROTA,PHY-SEC,FSTYPE,MODEL,TRAN,WWN', + 'ls -l /dev/disk/by-*/', + 'iscsiadm -m node', + 'iscsiadm -m session', + ], + }, + volumes => { + order => 90, + cmds => [ + 'pvs', 'lvs', 'vgs', + ], + }, }; if (cmd_exists('zfs')) { - push @{$report_def->{volumes}->{cmds}}, - 'zpool status', - 'zpool list -v', - 'zfs list', - 'arcstat', - ; + push @{ $report_def->{volumes}->{cmds} }, + 'zpool status', + 'zpool list -v', + 'zfs list', + 'arcstat', + ; } if (-e '/etc/ceph/ceph.conf') { - push @{$report_def->{volumes}->{cmds}}, - 'pveceph status', - 'ceph osd status', - 'ceph df', - 'ceph osd df tree', - 'ceph device ls', - 'cat /etc/ceph/ceph.conf', - 'ceph config dump', - 'pveceph pool ls', - 'ceph versions', - 'ceph health detail', - ; + push @{ $report_def->{volumes}->{cmds} }, + 'pveceph status', + 'ceph osd status', + 'ceph df', + 'ceph osd df tree', + 'ceph device ls', + 'cat /etc/ceph/ceph.conf', + 'ceph config dump', + 'pveceph pool ls', + 'ceph versions', + 'ceph health detail', + ; } if (cmd_exists('multipath')) { - push @{$report_def->{disks}->{cmds}}, - 'cat /etc/multipath.conf', - 'cat /etc/multipath/wwids', - 'multipath -ll', - ; + push @{ $report_def->{disks}->{cmds} }, + 'cat /etc/multipath.conf', + 'cat /etc/multipath/wwids', + 'multipath -ll', + ; } return $report_def; @@ -176,40 +176,41 @@ sub generate { my $report = ''; my $record_output = sub { - $report .= shift . "\n"; + $report .= shift . "\n"; }; local $ENV{'PATH'} = '/sbin:/bin:/usr/sbin:/usr/bin'; my $cmd_timeout = 10; # generous timeout my $run_cmd_params = { - outfunc => $record_output, - errfunc => $record_output, - timeout => $cmd_timeout, - noerr => 1, # avoid checking programs exit code + outfunc => $record_output, + errfunc => $record_output, + timeout => $cmd_timeout, + noerr => 1, # avoid checking programs exit code }; - my $sorter = sub { ($def->{$_[0]}->{order} // 1<<30) <=> ($def->{$_[1]}->{order} // 1<<30) }; + my $sorter = + sub { ($def->{ $_[0] }->{order} // 1 << 30) <=> ($def->{ $_[1] }->{order} // 1 << 30) }; - for my $section ( sort { $sorter->($a, $b) } keys %$def) { - my $s = $def->{$section}; - my $title = $s->{title} // "info about $section"; + for my $section (sort { $sorter->($a, $b) } keys %$def) { + my $s = $def->{$section}; + my $title = $s->{title} // "info about $section"; - $report .= "\n==== $title ====\n"; - for my $command (@{$s->{cmds}}) { - eval { - if (ref $command eq 'CODE') { - $report .= PVE::Tools::run_with_timeout($cmd_timeout, $command); - } else { - print STDERR "Process ".$command."..."; - $report .= "\n# $command\n"; - PVE::Tools::run_command($command, %$run_cmd_params); - } - print STDERR "OK"; - }; - print STDERR "\n"; - $report .= "\nERROR: $@\n" if $@; - } + $report .= "\n==== $title ====\n"; + for my $command (@{ $s->{cmds} }) { + eval { + if (ref $command eq 'CODE') { + $report .= PVE::Tools::run_with_timeout($cmd_timeout, $command); + } else { + print STDERR "Process " . $command . "..."; + $report .= "\n# $command\n"; + PVE::Tools::run_command($command, %$run_cmd_params); + } + print STDERR "OK"; + }; + print STDERR "\n"; + $report .= "\nERROR: $@\n" if $@; + } } return $report; diff --git a/PVE/Service/pvedaemon.pm b/PVE/Service/pvedaemon.pm index 486d264c..9d7cbc0f 100755 --- a/PVE/Service/pvedaemon.pm +++ b/PVE/Service/pvedaemon.pm @@ -28,27 +28,27 @@ sub init { my $accept_lock_fn = "/var/lock/pvedaemon.lck"; - my $lockfh = IO::File->new(">>${accept_lock_fn}") || - die "unable to open lock file '${accept_lock_fn}' - $!\n"; + my $lockfh = IO::File->new(">>${accept_lock_fn}") + || die "unable to open lock file '${accept_lock_fn}' - $!\n"; my $socket = $self->create_reusable_socket(85, '127.0.0.1'); $self->{server_config} = { - keep_alive => 100, - max_conn => 500, - max_requests => 1000, - lockfile => $accept_lock_fn, - socket => $socket, - lockfh => $lockfh, - debug => $self->{debug}, - trusted_env => 1, + keep_alive => 100, + max_conn => 500, + max_requests => 1000, + lockfile => $accept_lock_fn, + socket => $socket, + lockfh => $lockfh, + debug => $self->{debug}, + trusted_env => 1, }; } sub run { my ($self) = @_; - my $server = PVE::HTTPServer->new(%{$self->{server_config}}); + my $server = PVE::HTTPServer->new(%{ $self->{server_config} }); $server->run(); } @@ -58,10 +58,10 @@ $daemon->register_stop_command(); $daemon->register_status_command(); our $cmddef = { - start => [ __PACKAGE__, 'start', []], - restart => [ __PACKAGE__, 'restart', []], - stop => [ __PACKAGE__, 'stop', []], - status => [ __PACKAGE__, 'status', [], undef, sub { print shift . "\n";} ], + start => [__PACKAGE__, 'start', []], + restart => [__PACKAGE__, 'restart', []], + stop => [__PACKAGE__, 'stop', []], + status => [__PACKAGE__, 'status', [], undef, sub { print shift . "\n"; }], }; 1; diff --git a/PVE/Service/pveproxy.pm b/PVE/Service/pveproxy.pm index 05c8a624..c4bb54ea 100755 --- a/PVE/Service/pveproxy.pm +++ b/PVE/Service/pveproxy.pm @@ -67,8 +67,8 @@ sub init { my $accept_lock_fn = "/var/lock/pveproxy.lck"; - my $lockfh = IO::File->new(">>${accept_lock_fn}") || - die "unable to open lock file '${accept_lock_fn}' - $!\n"; + my $lockfh = IO::File->new(">>${accept_lock_fn}") + || die "unable to open lock file '${accept_lock_fn}' - $!\n"; my $listen_ip = $proxyconf->{LISTEN_IP}; my $socket = $self->create_reusable_socket(8006, $listen_ip); @@ -78,13 +78,13 @@ sub init { add_dirs($dirs, '/novnc/' => "$basedirs->{novnc}/"); add_dirs($dirs, '/pve-docs/' => "$basedirs->{docs}/"); add_dirs($dirs, '/pve-docs/api-viewer/extjs/' => "$basedirs->{extjs}/"); - add_dirs($dirs, '/pve2/css/' => "$basedirs->{manager}/css/"); + add_dirs($dirs, '/pve2/css/' => "$basedirs->{manager}/css/"); add_dirs($dirs, '/pve2/ext6/', "$basedirs->{extjs}/"); - add_dirs($dirs, '/pve2/fa/css/' => "$basedirs->{fontawesome}/css/"); - add_dirs($dirs, '/pve2/fa/fonts/' => "$basedirs->{fontawesome}/fonts/"); - add_dirs($dirs, '/pve2/font-logos/' => "$basedirs->{fontlogos}/"); - add_dirs($dirs, '/pve2/images/' => "$basedirs->{manager}/images/"); - add_dirs($dirs, '/pve2/js/' => "$basedirs->{manager}/js/"); + add_dirs($dirs, '/pve2/fa/css/' => "$basedirs->{fontawesome}/css/"); + add_dirs($dirs, '/pve2/fa/fonts/' => "$basedirs->{fontawesome}/fonts/"); + add_dirs($dirs, '/pve2/font-logos/' => "$basedirs->{fontlogos}/"); + add_dirs($dirs, '/pve2/images/' => "$basedirs->{manager}/images/"); + add_dirs($dirs, '/pve2/js/' => "$basedirs->{manager}/js/"); add_dirs($dirs, '/pve2/locale/', "$basedirs->{i18n}/"); add_dirs($dirs, '/pve2/sencha-touch/', "$basedirs->{sencha_touch}/"); add_dirs($dirs, '/pve2/touch/', "$basedirs->{manager}/touch/"); @@ -94,70 +94,73 @@ sub init { add_dirs($dirs, '/xtermjs/' => "$basedirs->{xtermjs}/"); $self->{server_config} = { - title => 'Proxmox VE API', - keep_alive => 100, - max_conn => 500, - max_requests => 1000, - lockfile => $accept_lock_fn, - socket => $socket, - lockfh => $lockfh, - debug => $self->{debug}, - trusted_env => 0, # not trusted, anyone can connect - logfile => '/var/log/pveproxy/access.log', - allow_from => $proxyconf->{ALLOW_FROM}, - deny_from => $proxyconf->{DENY_FROM}, - policy => $proxyconf->{POLICY}, - ssl => { - cipher_list => $proxyconf->{CIPHERS}, - ciphersuites => $proxyconf->{CIPHERSUITES}, - key_file => '/etc/pve/local/pve-ssl.key', - cert_file => '/etc/pve/local/pve-ssl.pem', - honor_cipher_order => $proxyconf->{HONOR_CIPHER_ORDER}, - }, - compression => $proxyconf->{COMPRESSION}, - proxy_real_ip_header => $proxyconf->{PROXY_REAL_IP_HEADER}, - proxy_real_ip_allow_from => $proxyconf->{PROXY_REAL_IP_ALLOW_FROM}, - # Note: there is no authentication for those pages and dirs! - pages => { - '/' => sub { get_index($self->{nodename}, @_) }, - # avoid authentication when accessing favicon - '/favicon.ico' => { - file => "$basedirs->{manager}/images/favicon.ico", - }, - '/proxmoxlib.js' => { - file => "$basedirs->{widgettoolkit}/proxmoxlib.js", - }, - '/qrcode.min.js' => { - file => '/usr/share/javascript/qrcodejs/qrcode.min.js', - }, - }, - dirs => $dirs, + title => 'Proxmox VE API', + keep_alive => 100, + max_conn => 500, + max_requests => 1000, + lockfile => $accept_lock_fn, + socket => $socket, + lockfh => $lockfh, + debug => $self->{debug}, + trusted_env => 0, # not trusted, anyone can connect + logfile => '/var/log/pveproxy/access.log', + allow_from => $proxyconf->{ALLOW_FROM}, + deny_from => $proxyconf->{DENY_FROM}, + policy => $proxyconf->{POLICY}, + ssl => { + cipher_list => $proxyconf->{CIPHERS}, + ciphersuites => $proxyconf->{CIPHERSUITES}, + key_file => '/etc/pve/local/pve-ssl.key', + cert_file => '/etc/pve/local/pve-ssl.pem', + honor_cipher_order => $proxyconf->{HONOR_CIPHER_ORDER}, + }, + compression => $proxyconf->{COMPRESSION}, + proxy_real_ip_header => $proxyconf->{PROXY_REAL_IP_HEADER}, + proxy_real_ip_allow_from => $proxyconf->{PROXY_REAL_IP_ALLOW_FROM}, + # Note: there is no authentication for those pages and dirs! + pages => { + '/' => sub { get_index($self->{nodename}, @_) }, + # avoid authentication when accessing favicon + '/favicon.ico' => { + file => "$basedirs->{manager}/images/favicon.ico", + }, + '/proxmoxlib.js' => { + file => "$basedirs->{widgettoolkit}/proxmoxlib.js", + }, + '/qrcode.min.js' => { + file => '/usr/share/javascript/qrcodejs/qrcode.min.js', + }, + }, + dirs => $dirs, }; if (defined($proxyconf->{DHPARAMS})) { - $self->{server_config}->{ssl}->{dh_file} = $proxyconf->{DHPARAMS}; + $self->{server_config}->{ssl}->{dh_file} = $proxyconf->{DHPARAMS}; } if (defined($proxyconf->{DISABLE_TLS_1_2})) { - $self->{server_config}->{ssl}->{tlsv1_2} = !$proxyconf->{DISABLE_TLS_1_2}; + $self->{server_config}->{ssl}->{tlsv1_2} = !$proxyconf->{DISABLE_TLS_1_2}; } if (defined($proxyconf->{DISABLE_TLS_1_3})) { - $self->{server_config}->{ssl}->{tlsv1_3} = !$proxyconf->{DISABLE_TLS_1_3}; + $self->{server_config}->{ssl}->{tlsv1_3} = !$proxyconf->{DISABLE_TLS_1_3}; } my $custom_key_path = '/etc/pve/local/pveproxy-ssl.key'; if (defined($proxyconf->{TLS_KEY_FILE})) { - $custom_key_path = $proxyconf->{TLS_KEY_FILE}; + $custom_key_path = $proxyconf->{TLS_KEY_FILE}; } if (-f '/etc/pve/local/pveproxy-ssl.pem' && -f $custom_key_path) { - $self->{server_config}->{ssl}->{cert_file} = '/etc/pve/local/pveproxy-ssl.pem'; - $self->{server_config}->{ssl}->{key_file} = $custom_key_path; - syslog('info', 'Using \'/etc/pve/local/pveproxy-ssl.pem\' as certificate for the web interface.'); + $self->{server_config}->{ssl}->{cert_file} = '/etc/pve/local/pveproxy-ssl.pem'; + $self->{server_config}->{ssl}->{key_file} = $custom_key_path; + syslog( + 'info', + 'Using \'/etc/pve/local/pveproxy-ssl.pem\' as certificate for the web interface.', + ); } } sub run { my ($self) = @_; - my $server = PVE::HTTPServer->new(%{$self->{server_config}}); + my $server = PVE::HTTPServer->new(%{ $self->{server_config} }); $server->run(); } @@ -167,10 +170,10 @@ $daemon->register_stop_command(); $daemon->register_status_command(); our $cmddef = { - start => [ __PACKAGE__, 'start', []], - restart => [ __PACKAGE__, 'restart', []], - stop => [ __PACKAGE__, 'stop', []], - status => [ __PACKAGE__, 'status', [], undef, sub { print shift . "\n";} ], + start => [__PACKAGE__, 'start', []], + restart => [__PACKAGE__, 'restart', []], + stop => [__PACKAGE__, 'stop', []], + status => [__PACKAGE__, 'status', [], undef, sub { print shift . "\n"; }], }; sub is_phone { @@ -181,8 +184,8 @@ sub is_phone { return 1 if $ua =~ m/(iPhone|iPod|Windows Phone)/; if ($ua =~ m/Mobile(\/|\s)/) { - return 1 if $ua =~ m/(BlackBerry|BB)/; - return 1 if ($ua =~ m/(Android)/) && ($ua !~ m/(Silk)/); + return 1 if $ua =~ m/(BlackBerry|BB)/; + return 1 if ($ua =~ m/(Android)/) && ($ua !~ m/(Silk)/); } return 0; @@ -200,38 +203,40 @@ sub get_index { my $theme = "auto"; if (my $cookie = $r->header('Cookie')) { - if (my $newlang = ($cookie =~ /(?:^|\s)PVELangCookie=([^;]*)/)[0]) { - if ($newlang =~ m/^[a-z]{2,3}(_[A-Z]{2,3})?$/) { - $lang = $newlang; - } - } + if (my $newlang = ($cookie =~ /(?:^|\s)PVELangCookie=([^;]*)/)[0]) { + if ($newlang =~ m/^[a-z]{2,3}(_[A-Z]{2,3})?$/) { + $lang = $newlang; + } + } - if (my $newtheme = ($cookie =~ /(?:^|\s)PVEThemeCookie=([^;]*)/)[0]) { - # theme names need to be kebab case, with each segment a maximum of 10 characters long - # and at most 6 segments - if ($newtheme =~ m/^[a-z]{1,10}(-[a-z]{1,10}){0,5}$/) { - $theme = $newtheme; - } - } + if (my $newtheme = ($cookie =~ /(?:^|\s)PVEThemeCookie=([^;]*)/)[0]) { + # theme names need to be kebab case, with each segment a maximum of 10 characters long + # and at most 6 segments + if ($newtheme =~ m/^[a-z]{1,10}(-[a-z]{1,10}){0,5}$/) { + $theme = $newtheme; + } + } - my $ticket = PVE::APIServer::Formatter::extract_auth_value($cookie, $server->{cookie_name}); - if (($username = PVE::AccessControl::verify_ticket($ticket, 1))) { - $token = PVE::AccessControl::assemble_csrf_prevention_token($username); - } + my $ticket = PVE::APIServer::Formatter::extract_auth_value($cookie, $server->{cookie_name}); + if (($username = PVE::AccessControl::verify_ticket($ticket, 1))) { + $token = PVE::AccessControl::assemble_csrf_prevention_token($username); + } } my $consent_text; eval { - my $dc_conf = PVE::Cluster::cfs_read_file('datacenter.cfg'); - $consent_text = $dc_conf->{'consent-text'}; + my $dc_conf = PVE::Cluster::cfs_read_file('datacenter.cfg'); + $consent_text = $dc_conf->{'consent-text'}; - if (!$lang) { - $lang = $dc_conf->{language} // 'en'; - } + if (!$lang) { + $lang = $dc_conf->{language} // 'en'; + } }; warn "$@\n" if $@; - my $mobile = (is_phone($r->header('User-Agent')) && (!defined($args->{mobile}) || $args->{mobile})) || $args->{mobile}; + my $mobile = + (is_phone($r->header('User-Agent')) && (!defined($args->{mobile}) || $args->{mobile})) + || $args->{mobile}; my $novnc = defined($args->{console}) && $args->{novnc}; my $xtermjs = defined($args->{console}) && $args->{xtermjs}; @@ -245,36 +250,36 @@ sub get_index { my $debug = $server->{debug}; if (exists $args->{debug}) { - $debug = !defined($args->{debug}) || $args->{debug}; + $debug = !defined($args->{debug}) || $args->{debug}; } my $vars = { - lang => $lang, - langfile => $langfile, - username => $username || '', - token => $token, - console => $args->{console}, - nodename => $nodename, - debug => $debug, - version => "$version", - wtversion => $wtversion, - theme => $theme, - consenttext => $consent_text + lang => $lang, + langfile => $langfile, + username => $username || '', + token => $token, + console => $args->{console}, + nodename => $nodename, + debug => $debug, + version => "$version", + wtversion => $wtversion, + theme => $theme, + consenttext => $consent_text, }; # by default, load the normal index my $dir = $basedirs->{manager}; if ($novnc) { - $dir = $basedirs->{novnc}; + $dir = $basedirs->{novnc}; } elsif ($xtermjs) { - $dir = $basedirs->{xtermjs}; + $dir = $basedirs->{xtermjs}; } elsif ($mobile) { - $dir = "$basedirs->{manager}/touch"; + $dir = "$basedirs->{manager}/touch"; } my $page = ''; - my $template = Template->new({ABSOLUTE => 1}); + my $template = Template->new({ ABSOLUTE => 1 }); $template->process("$dir/index.html.tpl", $vars, \$page) || die $template->error(), "\n"; diff --git a/PVE/Service/pvescheduler.pm b/PVE/Service/pvescheduler.pm index 40be5977..f7271fc0 100755 --- a/PVE/Service/pvescheduler.pm +++ b/PVE/Service/pvescheduler.pm @@ -21,28 +21,28 @@ my @JOB_TYPES = qw(replication jobs); my sub running_job_pids : prototype($) { my ($self) = @_; - my $pids = [ map { keys $_->%* } values $self->{jobs}->%* ]; + my $pids = [map { keys $_->%* } values $self->{jobs}->%*]; return scalar($pids->@*) ? $pids : undef; } my sub finish_jobs : prototype($) { my ($self) = @_; for my $type (@JOB_TYPES) { - for my $cpid (keys $self->{jobs}->{$type}->%*) { - if (my $waitpid = waitpid($cpid, WNOHANG)) { - delete $self->{jobs}->{$type}->{$cpid} if $waitpid == $cpid || $waitpid == -1; - } - } + for my $cpid (keys $self->{jobs}->{$type}->%*) { + if (my $waitpid = waitpid($cpid, WNOHANG)) { + delete $self->{jobs}->{$type}->{$cpid} if $waitpid == $cpid || $waitpid == -1; + } + } } -}; +} sub hup { my ($self) = @_; my $old_workers = ""; for my $type (@JOB_TYPES) { - my $worker = $self->{jobs}->{$type} // next; - $old_workers .= "$type:$_;" for keys $worker->%*; + my $worker = $self->{jobs}->{$type} // next; + $old_workers .= "$type:$_;" for keys $worker->%*; } $ENV{"PVE_DAEMON_WORKER_PIDS"} = $old_workers; $self->{got_hup_signal} = 1; @@ -56,107 +56,111 @@ sub run { # modelled after PVE::Daemons logic, but with type added to PID if (my $wpids = $ENV{PVE_DAEMON_WORKER_PIDS}) { - print STDERR "got workers from previous daemon run: $wpids\n"; # FIXME: only log on debug? - for my $pid (split(';', $wpids)) { - if ($pid =~ m/^(\w+):(\d+)$/) { # check & untaint - $self->{jobs}->{$1}->{$2} = 1; - } else { - warn "could not parse previous pid entry '$pid', ignoring\n"; - } - } + print STDERR "got workers from previous daemon run: $wpids\n"; # FIXME: only log on debug? + for my $pid (split(';', $wpids)) { + if ($pid =~ m/^(\w+):(\d+)$/) { # check & untaint + $self->{jobs}->{$1}->{$2} = 1; + } else { + warn "could not parse previous pid entry '$pid', ignoring\n"; + } + } } my $old_sig_chld = $SIG{CHLD}; local $SIG{CHLD} = sub { - local ($@, $!, $?); # do not overwrite error vars - finish_jobs($self); - $old_sig_chld->(@_) if $old_sig_chld; + local ($@, $!, $?); # do not overwrite error vars + finish_jobs($self); + $old_sig_chld->(@_) if $old_sig_chld; }; my $fork = sub { - my ($type, $sub) = @_; + my ($type, $sub) = @_; - # don't fork again if the previous iteration still runs - # FIXME: some job types may handle this better themself or just not care - make configurable - return if scalar(keys $self->{jobs}->{$type}->%*); + # don't fork again if the previous iteration still runs + # FIXME: some job types may handle this better themself or just not care - make configurable + return if scalar(keys $self->{jobs}->{$type}->%*); - my $child = fork(); - if (!defined($child)) { - die "fork failed: $!\n"; - } elsif ($child == 0) { - $self->after_fork_cleanup(); - eval { - $sub->(); - }; - if (my $err = $@) { - syslog('err', "$type: $err"); - } - POSIX::_exit(0); - } + my $child = fork(); + if (!defined($child)) { + die "fork failed: $!\n"; + } elsif ($child == 0) { + $self->after_fork_cleanup(); + eval { $sub->(); }; + if (my $err = $@) { + syslog('err', "$type: $err"); + } + POSIX::_exit(0); + } - $jobs->{$type}->{$child} = 1; + $jobs->{$type}->{$child} = 1; }; my $first_run = 1; my $run_jobs = sub { - # TODO: actually integrate replication in PVE::Jobs and do not always fork here, we could - # do the state lookup and check if there's new work scheduled before doing so, e.g., by - # extending the PVE::Jobs interfacae e.g.; - # my $scheduled_jobs = PVE::Jobs::get_pending() or return; - # forked { PVE::Jobs::run_jobs($scheduled_jobs) } + # TODO: actually integrate replication in PVE::Jobs and do not always fork here, we could + # do the state lookup and check if there's new work scheduled before doing so, e.g., by + # extending the PVE::Jobs interfacae e.g.; + # my $scheduled_jobs = PVE::Jobs::get_pending() or return; + # forked { PVE::Jobs::run_jobs($scheduled_jobs) } - $fork->('replication', sub { - PVE::API2::Replication::run_jobs(undef, sub {}, 0, 1); - }); + $fork->( + 'replication', + sub { + PVE::API2::Replication::run_jobs(undef, sub { }, 0, 1); + }, + ); - $fork->('jobs', sub { - PVE::Jobs::run_jobs($first_run); - }); + $fork->( + 'jobs', + sub { + PVE::Jobs::run_jobs($first_run); + }, + ); - $first_run = 0; + $first_run = 0; }; PVE::Jobs::setup_dirs(); - for (my $count = 1000;;$count++) { - return if $self->{got_hup_signal}; # keep workers running, PVE::Daemon re-execs us on return - last if $self->{shutdown_request}; # exit main-run loop for shutdown + for (my $count = 1000;; $count++) { + return if $self->{got_hup_signal}; # keep workers running, PVE::Daemon re-execs us on return + last if $self->{shutdown_request}; # exit main-run loop for shutdown - $run_jobs->(); + $run_jobs->(); - my $sleep_time = 60; - if ($count >= 1000) { - # Job schedule has minute precision, so try running near the minute boundary. - my ($current_seconds) = localtime; - $sleep_time = (60 - $current_seconds) if (60 - $current_seconds >= 5); - $count = 0; - } + my $sleep_time = 60; + if ($count >= 1000) { + # Job schedule has minute precision, so try running near the minute boundary. + my ($current_seconds) = localtime; + $sleep_time = (60 - $current_seconds) if (60 - $current_seconds >= 5); + $count = 0; + } - my $slept = 0; # SIGCHLD interrupts sleep, so we need to keep track - while ($slept < $sleep_time) { - last if $self->{shutdown_request} || $self->{got_hup_signal}; - $slept += sleep($sleep_time - $slept); - # TODO: check if there's new work to do, e.g., if a job finished - # that had a longer runtime than run period - } + my $slept = 0; # SIGCHLD interrupts sleep, so we need to keep track + while ($slept < $sleep_time) { + last if $self->{shutdown_request} || $self->{got_hup_signal}; + $slept += sleep($sleep_time - $slept); + # TODO: check if there's new work to do, e.g., if a job finished + # that had a longer runtime than run period + } } # NOTE: we only get here on shutdown_request, so we already sent a TERM to all job-types my $timeout = 0; - while(my $pids = running_job_pids($self)) { - kill 'TERM', $pids->@*; # send TERM to all workers at once, possible thundering herd - FIXME? + while (my $pids = running_job_pids($self)) { + kill 'TERM', $pids->@*; # send TERM to all workers at once, possible thundering herd - FIXME? - finish_jobs($self); + finish_jobs($self); - # some jobs have a lock timeout of 60s, wait a bit more for graceful termination - last if $timeout > 75; - $timeout += sleep(3); + # some jobs have a lock timeout of 60s, wait a bit more for graceful termination + last if $timeout > 75; + $timeout += sleep(3); } if (my $pids = running_job_pids($self)) { - syslog('warn', "unresponsive job-worker, killing now: " . join(', ', $pids->@*)); - kill 'KILL', $pids->@*; + syslog('warn', "unresponsive job-worker, killing now: " . join(', ', $pids->@*)); + kill 'KILL', $pids->@*; } } @@ -166,7 +170,7 @@ sub shutdown { syslog('info', 'got shutdown request, signal running jobs to stop'); for my $jobs (values $self->{jobs}->%*) { - kill 'TERM', keys $jobs->%*; + kill 'TERM', keys $jobs->%*; } $self->{shutdown_request} = 1; } @@ -177,10 +181,10 @@ $daemon->register_restart_command(1); $daemon->register_status_command(); our $cmddef = { - start => [ __PACKAGE__, 'start', []], - stop => [ __PACKAGE__, 'stop', []], - restart => [ __PACKAGE__, 'restart', []], - status => [ __PACKAGE__, 'status', [], undef, sub { print shift . "\n";} ], + start => [__PACKAGE__, 'start', []], + stop => [__PACKAGE__, 'stop', []], + restart => [__PACKAGE__, 'restart', []], + status => [__PACKAGE__, 'status', [], undef, sub { print shift . "\n"; }], }; 1; diff --git a/PVE/Service/pvestatd.pm b/PVE/Service/pvestatd.pm index d80c62da..e3ea06bb 100755 --- a/PVE/Service/pvestatd.pm +++ b/PVE/Service/pvestatd.pm @@ -64,7 +64,7 @@ sub init { sub shutdown { my ($self) = @_; - syslog('info' , "server closing"); + syslog('info', "server closing"); # wait for children 1 while (waitpid(-1, POSIX::WNOHANG()) > 0); @@ -90,32 +90,37 @@ sub update_supported_cpuflags { return if $cached_kvm_version && $cached_kvm_version eq $kvm_version; if ($next_flag_update_time && $next_flag_update_time > time()) { - return; + return; } $next_flag_update_time = 0; my $supported_cpuflags = eval { PVE::QemuServer::query_supported_cpu_flags() }; warn $@ if $@; - if (!$supported_cpuflags || - (!$supported_cpuflags->{tcg} && !$supported_cpuflags->{kvm})) { - # something went wrong, clear broadcast flags and set try-again delay - warn "CPU flag detection failed, will try again after delay\n"; - $next_flag_update_time = time() + $failed_flag_update_delay_sec; + if ( + !$supported_cpuflags + || (!$supported_cpuflags->{tcg} && !$supported_cpuflags->{kvm}) + ) { + # something went wrong, clear broadcast flags and set try-again delay + warn "CPU flag detection failed, will try again after delay\n"; + $next_flag_update_time = time() + $failed_flag_update_delay_sec; - $supported_cpuflags = {}; + $supported_cpuflags = {}; } else { - # only set cached version if there's actually something to broadcast - $cached_kvm_version = $kvm_version; + # only set cached version if there's actually something to broadcast + $cached_kvm_version = $kvm_version; } for my $accel ("tcg", "kvm") { - if ($supported_cpuflags->{$accel}) { - PVE::Cluster::broadcast_node_kv("cpuflags-$accel", join(' ', @{$supported_cpuflags->{$accel}})); - } else { - # clear potentially invalid data - PVE::Cluster::broadcast_node_kv("cpuflags-$accel", ''); - } + if ($supported_cpuflags->{$accel}) { + PVE::Cluster::broadcast_node_kv( + "cpuflags-$accel", + join(' ', @{ $supported_cpuflags->{$accel} }), + ); + } else { + # clear potentially invalid data + PVE::Cluster::broadcast_node_kv("cpuflags-$accel", ''); + } } } @@ -135,16 +140,18 @@ my sub broadcast_static_node_info { $old = eval { decode_json($old->{$nodename}) } if defined($old->{$nodename}); if ( - !defined($old->{cpus}) || $old->{cpus} != $cpus - || !defined($old->{memory}) || $old->{memory} != $memory - || ($old->{'cgroup-mode'} // -1) != ($cgroup_mode // -1) + !defined($old->{cpus}) + || $old->{cpus} != $cpus + || !defined($old->{memory}) + || $old->{memory} != $memory + || ($old->{'cgroup-mode'} // -1) != ($cgroup_mode // -1) ) { - my $info = { - cpus => $cpus, - memory => $memory, - }; - $info->{'cgroup-mode'} = $cgroup_mode if defined($cgroup_mode); - PVE::Cluster::broadcast_node_kv('static-info', encode_json($info)); + my $info = { + cpus => $cpus, + memory => $memory, + }; + $info->{'cgroup-mode'} = $cgroup_mode if defined($cgroup_mode); + PVE::Cluster::broadcast_node_kv('static-info', encode_json($info)); } } @@ -167,32 +174,45 @@ sub update_node_status { # traffic from/to physical interface cards my ($netin, $netout) = (0, 0); for my $dev (grep { /^$PVE::Network::PHYSICAL_NIC_RE$/ } keys %$netdev) { - $netin += $netdev->{$dev}->{receive}; - $netout += $netdev->{$dev}->{transmit}; + $netin += $netdev->{$dev}->{receive}; + $netout += $netdev->{$dev}->{transmit}; } my $meminfo = PVE::ProcFSTools::read_meminfo(); - my $dinfo = df('/', 1); # output is bytes + my $dinfo = df('/', 1); # output is bytes # everything not free is considered to be used my $dused = $dinfo->{blocks} - $dinfo->{bfree}; my $ctime = time(); my $data = $generate_rrd_string->( - [$uptime, $sublevel, $ctime, $avg1, $maxcpu, $stat->{cpu}, $stat->{wait}, - $meminfo->{memtotal}, $meminfo->{memused}, - $meminfo->{swaptotal}, $meminfo->{swapused}, - $dinfo->{blocks}, $dused, $netin, $netout] + [ + $uptime, + $sublevel, + $ctime, + $avg1, + $maxcpu, + $stat->{cpu}, + $stat->{wait}, + $meminfo->{memtotal}, + $meminfo->{memused}, + $meminfo->{swaptotal}, + $meminfo->{swapused}, + $dinfo->{blocks}, + $dused, + $netin, + $netout, + ], ); PVE::Cluster::broadcast_rrd("pve2-node/$nodename", $data); my $node_metric = { - uptime => $uptime, - cpustat => $stat, - memory => $meminfo, - blockstat => $dinfo, - nics => $netdev, + uptime => $uptime, + cpustat => $stat, + memory => $meminfo, + blockstat => $dinfo, + nics => $netdev, }; $node_metric->{cpustat}->@{qw(avg1 avg5 avg15)} = ($avg1, $avg5, $avg15); $node_metric->{cpustat}->{cpus} = $maxcpu; @@ -207,7 +227,7 @@ sub update_node_status { } sub auto_balloning { - my ($vmstatus) = @_; + my ($vmstatus) = @_; my $log = sub { $opt_debug and printf @_ }; @@ -221,19 +241,20 @@ sub auto_balloning { my $target = int($config->{'ballooning-target'} // 80); # goal is the change amount required to achieve that my $goal = int($hostmeminfo->{memtotal} * $target / 100 - $hostmeminfo->{memused}); - $log->("target: $target%% host goal: $goal free: $hostfreemem total: $hostmeminfo->{memtotal}\n"); + $log->( + "target: $target%% host goal: $goal free: $hostfreemem total: $hostmeminfo->{memtotal}\n"); - my $maxchange = 100*1024*1024; + my $maxchange = 100 * 1024 * 1024; my $res = PVE::AutoBalloon::compute_alg1($vmstatus, $goal, $maxchange); for my $vmid (sort keys %$res) { - my $target = int($res->{$vmid}); - my $current = int($vmstatus->{$vmid}->{balloon}); - next if $target == $current; # no need to change + my $target = int($res->{$vmid}); + my $current = int($vmstatus->{$vmid}->{balloon}); + next if $target == $current; # no need to change - $log->("BALLOON $vmid to $target (%d)\n", $target - $current); - eval { PVE::QemuServer::Monitor::mon_cmd($vmid, "balloon", value => int($target)) }; - warn $@ if $@; + $log->("BALLOON $vmid to $target (%d)\n", $target - $current); + eval { PVE::QemuServer::Monitor::mon_cmd($vmid, "balloon", value => int($target)) }; + warn $@ if $@; } } @@ -248,23 +269,50 @@ sub update_qemu_status { my $transactions = PVE::ExtMetric::transactions_start($status_cfg); foreach my $vmid (keys %$vmstatus) { - my $d = $vmstatus->{$vmid}; - my $data; - my $status = $d->{qmpstatus} || $d->{status} || 'stopped'; - my $template = $d->{template} ? $d->{template} : "0"; - if ($d->{pid}) { # running - $data = $generate_rrd_string->( - [$d->{uptime}, $d->{name}, $status, $template, $ctime, $d->{cpus}, $d->{cpu}, - $d->{maxmem}, $d->{mem}, $d->{maxdisk}, $d->{disk}, - $d->{netin}, $d->{netout}, $d->{diskread}, $d->{diskwrite}]); - } else { - $data = $generate_rrd_string->( - [0, $d->{name}, $status, $template, $ctime, $d->{cpus}, undef, - $d->{maxmem}, undef, $d->{maxdisk}, $d->{disk}, undef, undef, undef, undef]); - } - PVE::Cluster::broadcast_rrd("pve2.3-vm/$vmid", $data); + my $d = $vmstatus->{$vmid}; + my $data; + my $status = $d->{qmpstatus} || $d->{status} || 'stopped'; + my $template = $d->{template} ? $d->{template} : "0"; + if ($d->{pid}) { # running + $data = $generate_rrd_string->([ + $d->{uptime}, + $d->{name}, + $status, + $template, + $ctime, + $d->{cpus}, + $d->{cpu}, + $d->{maxmem}, + $d->{mem}, + $d->{maxdisk}, + $d->{disk}, + $d->{netin}, + $d->{netout}, + $d->{diskread}, + $d->{diskwrite}, + ]); + } else { + $data = $generate_rrd_string->([ + 0, + $d->{name}, + $status, + $template, + $ctime, + $d->{cpus}, + undef, + $d->{maxmem}, + undef, + $d->{maxdisk}, + $d->{disk}, + undef, + undef, + undef, + undef, + ]); + } + PVE::Cluster::broadcast_rrd("pve2.3-vm/$vmid", $data); - PVE::ExtMetric::update_all($transactions, 'qemu', $vmid, $d, $ctime, $nodename); + PVE::ExtMetric::update_all($transactions, 'qemu', $vmid, $d, $ctime, $nodename); } PVE::ExtMetric::transactions_finish($transactions); @@ -278,28 +326,29 @@ sub remove_stale_lxc_consoles { my $pidhash = PVE::LXC::find_lxc_console_pids(); foreach my $vmid (keys %$pidhash) { - next if defined($vmstatus->{$vmid}); - syslog('info', "remove stale lxc-console for CT $vmid"); - foreach my $pid (@{$pidhash->{$vmid}}) { - kill(9, $pid); - } + next if defined($vmstatus->{$vmid}); + syslog('info', "remove stale lxc-console for CT $vmid"); + foreach my $pid (@{ $pidhash->{$vmid} }) { + kill(9, $pid); + } } } my $rebalance_error_count = {}; my $NO_REBALANCE; + sub rebalance_lxc_containers { # Make sure we can find the cpuset controller path: return if $NO_REBALANCE; my $cpuset_base = eval { PVE::CGroup::cpuset_controller_path() }; if (my $err = $@) { - syslog('info', "could not get cpuset controller path: $err"); + syslog('info', "could not get cpuset controller path: $err"); } if (!defined($cpuset_base)) { - $NO_REBALANCE = 1; - return; + $NO_REBALANCE = 1; + return; } # Figure out the cpu count & highest ID @@ -308,7 +357,7 @@ sub rebalance_lxc_containers { my $cpucount = scalar(@allowed_cpus); my $max_cpuid = $allowed_cpus[-1]; - my @cpu_ctcount = (0) x ($max_cpuid+1); + my @cpu_ctcount = (0) x ($max_cpuid + 1); my @balanced_cts; # A mapping { vmid => cgroup_payload_path } for containers where namespace @@ -316,135 +365,132 @@ sub rebalance_lxc_containers { my $ctinfo = {}; my $modify_cpuset = sub { - my ($vmid, $cpuset, $newset) = @_; + my ($vmid, $cpuset, $newset) = @_; - if (!$rebalance_error_count->{$vmid}) { - syslog('info', "modified cpu set for lxc/$vmid: " . $newset->short_string()); - } + if (!$rebalance_error_count->{$vmid}) { + syslog('info', "modified cpu set for lxc/$vmid: " . $newset->short_string()); + } - eval { - my $cgbase = $ctinfo->{$vmid}; + eval { + my $cgbase = $ctinfo->{$vmid}; - if (defined($cgbase)) { - # allow all, so that we can set new cpuset in /ns - $all_cpus->write_to_path($cgbase); - eval { - $newset->write_to_path("$cgbase/ns"); - }; - if (my $err = $@) { - warn $err if !$rebalance_error_count->{$vmid}++; - # restore original - $cpuset->write_to_path($cgbase); - } else { - # also apply to container root cgroup - $newset->write_to_path($cgbase); - $rebalance_error_count->{$vmid} = 0; - } - } else { - # old style container - $newset->write_to_path($cgbase); - $rebalance_error_count->{$vmid} = 0; - } - }; - if (my $err = $@) { - warn $err if !$rebalance_error_count->{$vmid}++; - } + if (defined($cgbase)) { + # allow all, so that we can set new cpuset in /ns + $all_cpus->write_to_path($cgbase); + eval { $newset->write_to_path("$cgbase/ns"); }; + if (my $err = $@) { + warn $err if !$rebalance_error_count->{$vmid}++; + # restore original + $cpuset->write_to_path($cgbase); + } else { + # also apply to container root cgroup + $newset->write_to_path($cgbase); + $rebalance_error_count->{$vmid} = 0; + } + } else { + # old style container + $newset->write_to_path($cgbase); + $rebalance_error_count->{$vmid} = 0; + } + }; + if (my $err = $@) { + warn $err if !$rebalance_error_count->{$vmid}++; + } }; my $ctlist = PVE::LXC::config_list(); foreach my $vmid (sort keys %$ctlist) { - my $cgpath = "$cpuset_base/lxc/$vmid"; - if (-d "$cgpath/ns") { - $ctinfo->{$vmid} = $cgpath; - } else { - next; # old style container - } + my $cgpath = "$cpuset_base/lxc/$vmid"; + if (-d "$cgpath/ns") { + $ctinfo->{$vmid} = $cgpath; + } else { + next; # old style container + } - my ($conf, $cpuset) = eval {( - PVE::LXC::Config->load_config($vmid), - PVE::CpuSet->new_from_path($cgpath), - )}; - if (my $err = $@) { - warn $err; - next; - } + my ($conf, $cpuset) = + eval { (PVE::LXC::Config->load_config($vmid), PVE::CpuSet->new_from_path($cgpath)) }; + if (my $err = $@) { + warn $err; + next; + } - my @cpuset_members = $cpuset->members(); + my @cpuset_members = $cpuset->members(); - if (!PVE::LXC::Config->has_lxc_entry($conf, 'lxc.cgroup.cpuset.cpus') - && !PVE::LXC::Config->has_lxc_entry($conf, 'lxc.cgroup2.cpuset.cpus') - ) { - my $cores = $conf->{cores} || $cpucount; - $cores = $cpucount if $cores > $cpucount; + if ( + !PVE::LXC::Config->has_lxc_entry($conf, 'lxc.cgroup.cpuset.cpus') + && !PVE::LXC::Config->has_lxc_entry($conf, 'lxc.cgroup2.cpuset.cpus') + ) { + my $cores = $conf->{cores} || $cpucount; + $cores = $cpucount if $cores > $cpucount; - # see if the number of cores was hot-reduced or hasn't been enacted at all yet - my $newset = PVE::CpuSet->new(); - if ($cores < scalar(@cpuset_members)) { - for (my $i = 0; $i < $cores; $i++) { - $newset->insert($cpuset_members[$i]); - } - } elsif ($cores > scalar(@cpuset_members)) { - my $count = $newset->insert(@cpuset_members); - foreach my $cpu (@allowed_cpus) { - $count += $newset->insert($cpu); - last if $count >= $cores; - } - } else { - $newset->insert(@cpuset_members); - } + # see if the number of cores was hot-reduced or hasn't been enacted at all yet + my $newset = PVE::CpuSet->new(); + if ($cores < scalar(@cpuset_members)) { + for (my $i = 0; $i < $cores; $i++) { + $newset->insert($cpuset_members[$i]); + } + } elsif ($cores > scalar(@cpuset_members)) { + my $count = $newset->insert(@cpuset_members); + foreach my $cpu (@allowed_cpus) { + $count += $newset->insert($cpu); + last if $count >= $cores; + } + } else { + $newset->insert(@cpuset_members); + } - # Apply hot-plugged changes if any: - if (!$newset->is_equal($cpuset)) { - @cpuset_members = $newset->members(); - $modify_cpuset->($vmid, $cpuset, $newset); - } + # Apply hot-plugged changes if any: + if (!$newset->is_equal($cpuset)) { + @cpuset_members = $newset->members(); + $modify_cpuset->($vmid, $cpuset, $newset); + } - # Note: no need to rebalance if we already use all cores - push @balanced_cts, [$vmid, $cores, $newset] - if defined($conf->{cores}) && ($cores != $cpucount); - } + # Note: no need to rebalance if we already use all cores + push @balanced_cts, [$vmid, $cores, $newset] + if defined($conf->{cores}) && ($cores != $cpucount); + } - foreach my $cpu (@cpuset_members) { - $cpu_ctcount[$cpu]++ if $cpu <= $max_cpuid; - } + foreach my $cpu (@cpuset_members) { + $cpu_ctcount[$cpu]++ if $cpu <= $max_cpuid; + } } my $find_best_cpu = sub { - my ($cpulist, $cpu) = @_; + my ($cpulist, $cpu) = @_; - my $cur_cost = $cpu_ctcount[$cpu]; - my $cur_cpu = $cpu; + my $cur_cost = $cpu_ctcount[$cpu]; + my $cur_cpu = $cpu; - foreach my $candidate (@$cpulist) { - my $cost = $cpu_ctcount[$candidate]; - if ($cost < ($cur_cost - 1)) { - $cur_cost = $cost; - $cur_cpu = $candidate; - } - } + foreach my $candidate (@$cpulist) { + my $cost = $cpu_ctcount[$candidate]; + if ($cost < ($cur_cost - 1)) { + $cur_cost = $cost; + $cur_cpu = $candidate; + } + } - return $cur_cpu; + return $cur_cpu; }; foreach my $bct (@balanced_cts) { - my ($vmid, $cores, $cpuset) = @$bct; + my ($vmid, $cores, $cpuset) = @$bct; - my $rest = [ grep { !$cpuset->has($_) } @allowed_cpus ]; + my $rest = [grep { !$cpuset->has($_) } @allowed_cpus]; - my $newset = PVE::CpuSet->new(); - for my $cpu ($cpuset->members()) { - my $best = $find_best_cpu->($rest, $cpu); - if ($best != $cpu) { - $cpu_ctcount[$best]++; - $cpu_ctcount[$cpu]--; - } - $newset->insert($best); - } + my $newset = PVE::CpuSet->new(); + for my $cpu ($cpuset->members()) { + my $best = $find_best_cpu->($rest, $cpu); + if ($best != $cpu) { + $cpu_ctcount[$best]++; + $cpu_ctcount[$cpu]--; + } + $newset->insert($best); + } - if (!$newset->is_equal($cpuset)) { - $modify_cpuset->($vmid, $cpuset, $newset); - } + if (!$newset->is_equal($cpuset)) { + $modify_cpuset->($vmid, $cpuset, $newset); + } } } @@ -457,25 +503,49 @@ sub update_lxc_status { my $transactions = PVE::ExtMetric::transactions_start($status_cfg); foreach my $vmid (keys %$vmstatus) { - my $d = $vmstatus->{$vmid}; - my $template = $d->{template} ? $d->{template} : "0"; - my $data; - if ($d->{status} eq 'running') { # running - $data = $generate_rrd_string->( - [$d->{uptime}, $d->{name}, $d->{status}, $template, - $ctime, $d->{cpus}, $d->{cpu}, - $d->{maxmem}, $d->{mem}, - $d->{maxdisk}, $d->{disk}, - $d->{netin}, $d->{netout}, - $d->{diskread}, $d->{diskwrite}]); - } else { - $data = $generate_rrd_string->( - [0, $d->{name}, $d->{status}, $template, $ctime, $d->{cpus}, undef, - $d->{maxmem}, undef, $d->{maxdisk}, $d->{disk}, undef, undef, undef, undef]); - } - PVE::Cluster::broadcast_rrd("pve2.3-vm/$vmid", $data); + my $d = $vmstatus->{$vmid}; + my $template = $d->{template} ? $d->{template} : "0"; + my $data; + if ($d->{status} eq 'running') { # running + $data = $generate_rrd_string->([ + $d->{uptime}, + $d->{name}, + $d->{status}, + $template, + $ctime, + $d->{cpus}, + $d->{cpu}, + $d->{maxmem}, + $d->{mem}, + $d->{maxdisk}, + $d->{disk}, + $d->{netin}, + $d->{netout}, + $d->{diskread}, + $d->{diskwrite}, + ]); + } else { + $data = $generate_rrd_string->([ + 0, + $d->{name}, + $d->{status}, + $template, + $ctime, + $d->{cpus}, + undef, + $d->{maxmem}, + undef, + $d->{maxdisk}, + $d->{disk}, + undef, + undef, + undef, + undef, + ]); + } + PVE::Cluster::broadcast_rrd("pve2.3-vm/$vmid", $data); - PVE::ExtMetric::update_all($transactions, 'lxc', $vmid, $d, $ctime, $nodename); + PVE::ExtMetric::update_all($transactions, 'lxc', $vmid, $d, $ctime, $nodename); } PVE::ExtMetric::transactions_finish($transactions); @@ -492,15 +562,15 @@ sub update_storage_status { my $transactions = PVE::ExtMetric::transactions_start($status_cfg); foreach my $storeid (keys %$info) { - my $d = $info->{$storeid}; - next if !$d->{active}; + my $d = $info->{$storeid}; + next if !$d->{active}; - my $data = $generate_rrd_string->([$ctime, $d->{total}, $d->{used}]); + my $data = $generate_rrd_string->([$ctime, $d->{total}, $d->{used}]); - my $key = "pve2-storage/${nodename}/$storeid"; - PVE::Cluster::broadcast_rrd($key, $data); + my $key = "pve2-storage/${nodename}/$storeid"; + PVE::Cluster::broadcast_rrd($key, $data); - PVE::ExtMetric::update_all($transactions, 'storage', $nodename, $storeid, $d, $ctime); + PVE::ExtMetric::update_all($transactions, 'storage', $nodename, $storeid, $d, $ctime); } PVE::ExtMetric::transactions_finish($transactions); @@ -521,22 +591,21 @@ sub update_ceph_metadata { sub update_sdn_status { - if($have_sdn) { - my ($transport_status, $vnet_status) = PVE::Network::SDN::status(); + if ($have_sdn) { + my ($transport_status, $vnet_status) = PVE::Network::SDN::status(); - my $status = $transport_status ? encode_json($transport_status) : undef; - PVE::Cluster::broadcast_node_kv("sdn", $status); + my $status = $transport_status ? encode_json($transport_status) : undef; + PVE::Cluster::broadcast_node_kv("sdn", $status); } } my $broadcast_version_info_done = 0; my sub broadcast_version_info : prototype() { if (!$broadcast_version_info_done) { - PVE::Cluster::broadcast_node_kv( - 'version-info', - encode_json(PVE::pvecfg::version_info()), - ); - $broadcast_version_info_done = 1; + PVE::Cluster::broadcast_node_kv( + 'version-info', encode_json(PVE::pvecfg::version_info()), + ); + $broadcast_version_info_done = 1; } } @@ -548,77 +617,55 @@ sub update_status { my $pull_txn = PVE::PullMetric::transaction_start(); eval { - my $tlist = $rpcenv->active_workers(); - PVE::Cluster::broadcast_tasklist($tlist); + my $tlist = $rpcenv->active_workers(); + PVE::Cluster::broadcast_tasklist($tlist); }; my $err = $@; syslog('err', $err) if $err; my $status_cfg = PVE::Cluster::cfs_read_file('status.cfg'); - eval { - update_node_status($status_cfg, $pull_txn); - }; + eval { update_node_status($status_cfg, $pull_txn); }; $err = $@; syslog('err', "node status update error: $err") if $err; - eval { - update_qemu_status($status_cfg, $pull_txn); - }; + eval { update_qemu_status($status_cfg, $pull_txn); }; $err = $@; syslog('err', "qemu status update error: $err") if $err; - eval { - update_lxc_status($status_cfg, $pull_txn); - }; + eval { update_lxc_status($status_cfg, $pull_txn); }; $err = $@; syslog('err', "lxc status update error: $err") if $err; - eval { - rebalance_lxc_containers(); - }; + eval { rebalance_lxc_containers(); }; $err = $@; syslog('err', "lxc cpuset rebalance error: $err") if $err; - eval { - update_storage_status($status_cfg, $pull_txn); - }; + eval { update_storage_status($status_cfg, $pull_txn); }; $err = $@; syslog('err', "storage status update error: $err") if $err; - eval { - remove_stale_lxc_consoles(); - }; + eval { remove_stale_lxc_consoles(); }; $err = $@; syslog('err', "lxc console cleanup error: $err") if $err; - eval { - rotate_authkeys(); - }; + eval { rotate_authkeys(); }; $err = $@; syslog('err', "authkey rotation error: $err") if $err; - eval { - update_ceph_metadata(); - }; + eval { update_ceph_metadata(); }; $err = $@; syslog('err', "ceph metadata update error: $err") if $err; - eval { - update_sdn_status(); - }; + eval { update_sdn_status(); }; $err = $@; syslog('err', "sdn status update error: $err") if $err; - eval { - broadcast_version_info(); - }; + eval { broadcast_version_info(); }; $err = $@; syslog('err', "version info update error: $err") if $err; - eval { - PVE::PullMetric::transaction_finish($pull_txn); - }; + eval { PVE::PullMetric::transaction_finish($pull_txn); }; $err = $@; syslog('err', "could not populate metric data cache: $err") if $err; } @@ -627,7 +674,7 @@ my $next_update = 0; # do not update directly after startup, because install scripts # have a problem with that -my $cycle = 0; +my $cycle = 0; my $updatetime = 10; my $initial_memory_usage; @@ -637,50 +684,59 @@ sub run { for (;;) { # forever - $next_update = time() + $updatetime; + $next_update = time() + $updatetime; - if ($cycle) { - my ($ccsec, $cusec) = gettimeofday (); - eval { - # syslog('info', "start status update"); - PVE::Cluster::cfs_update(); - update_status(); - }; - my $err = $@; + if ($cycle) { + my ($ccsec, $cusec) = gettimeofday(); + eval { + # syslog('info', "start status update"); + PVE::Cluster::cfs_update(); + update_status(); + }; + my $err = $@; - if ($err) { - syslog('err', "status update error: $err"); - } + if ($err) { + syslog('err', "status update error: $err"); + } - my ($ccsec_end, $cusec_end) = gettimeofday (); - my $cptime = ($ccsec_end-$ccsec) + ($cusec_end - $cusec)/1000000; + my ($ccsec_end, $cusec_end) = gettimeofday(); + my $cptime = ($ccsec_end - $ccsec) + ($cusec_end - $cusec) / 1000000; - syslog('info', sprintf("status update time (%.3f seconds)", $cptime)) - if ($cptime > 5); - } + syslog('info', sprintf("status update time (%.3f seconds)", $cptime)) + if ($cptime > 5); + } - $cycle++; + $cycle++; - my $mem = PVE::ProcFSTools::read_memory_usage(); - my $resident_kb = $mem->{resident} / 1024; + my $mem = PVE::ProcFSTools::read_memory_usage(); + my $resident_kb = $mem->{resident} / 1024; - if (!defined($initial_memory_usage) || ($cycle < 10)) { - $initial_memory_usage = $resident_kb; - } else { - my $diff = $resident_kb - $initial_memory_usage; - if ($diff > 15 * 1024) { - syslog ('info', "restarting server after $cycle cycles to " . - "reduce memory usage (free $resident_kb ($diff) KB)"); - $self->restart_daemon(); - } - } + if (!defined($initial_memory_usage) || ($cycle < 10)) { + $initial_memory_usage = $resident_kb; + } else { + my $diff = $resident_kb - $initial_memory_usage; + if ($diff > 15 * 1024) { + syslog( + 'info', + "restarting server after $cycle cycles to " + . "reduce memory usage (free $resident_kb ($diff) KB)", + ); + $self->restart_daemon(); + } + } - my $wcount = 0; - while ((time() < $next_update) && - ($wcount < $updatetime) && # protect against time wrap - !$restart_request) { $wcount++; sleep (1); }; + my $wcount = 0; + while ( + (time() < $next_update) + && ($wcount < $updatetime) + && # protect against time wrap + !$restart_request + ) { + $wcount++; + sleep(1); + } - $self->restart_daemon() if $restart_request; + $self->restart_daemon() if $restart_request; } } @@ -690,10 +746,10 @@ $daemon->register_stop_command(); $daemon->register_status_command(); our $cmddef = { - start => [ __PACKAGE__, 'start', []], - restart => [ __PACKAGE__, 'restart', []], - stop => [ __PACKAGE__, 'stop', []], - status => [ __PACKAGE__, 'status', [], undef, sub { print shift . "\n";} ], + start => [__PACKAGE__, 'start', []], + restart => [__PACKAGE__, 'restart', []], + stop => [__PACKAGE__, 'stop', []], + status => [__PACKAGE__, 'status', [], undef, sub { print shift . "\n"; }], }; 1; diff --git a/PVE/Service/spiceproxy.pm b/PVE/Service/spiceproxy.pm index 50b81c18..0c6859e2 100755 --- a/PVE/Service/spiceproxy.pm +++ b/PVE/Service/spiceproxy.pm @@ -1,7 +1,7 @@ package PVE::Service::spiceproxy; -# Note: In theory, all this can be done by 'pveproxy' daemon. But some -# API call still have blocking code, so we use a separate daemon to avoid +# Note: In theory, all this can be done by 'pveproxy' daemon. But some +# API call still have blocking code, so we use a separate daemon to avoid # that the console gets blocked. use strict; @@ -18,15 +18,15 @@ my $cmdline = [$0, @ARGV]; my %daemon_options = ( max_workers => 1, # todo: do we need more? - restart_on_error => 5, + restart_on_error => 5, stop_wait_time => 15, leave_children_open_on_reload => 1, setuid => 'www-data', setgid => 'www-data', pidfile => '/var/run/pveproxy/spiceproxy.pid', - ); +); -my $daemon = __PACKAGE__->new('spiceproxy', $cmdline, %daemon_options); +my $daemon = __PACKAGE__->new('spiceproxy', $cmdline, %daemon_options); sub init { my ($self) = @_; @@ -36,32 +36,32 @@ sub init { my $accept_lock_fn = "/var/lock/spiceproxy.lck"; - my $lockfh = IO::File->new(">>${accept_lock_fn}") || - die "unable to open lock file '${accept_lock_fn}' - $!\n"; + my $lockfh = IO::File->new(">>${accept_lock_fn}") + || die "unable to open lock file '${accept_lock_fn}' - $!\n"; my $listen_ip = $proxyconf->{LISTEN_IP}; my $socket = $self->create_reusable_socket(3128, $listen_ip); $self->{server_config} = { - keep_alive => 0, - max_conn => 500, - lockfile => $accept_lock_fn, - socket => $socket, - lockfh => $lockfh, - debug => $self->{debug}, - spiceproxy => 1, - trusted_env => 0, - logfile => '/var/log/pveproxy/access.log', - allow_from => $proxyconf->{ALLOW_FROM}, - deny_from => $proxyconf->{DENY_FROM}, - policy => $proxyconf->{POLICY}, + keep_alive => 0, + max_conn => 500, + lockfile => $accept_lock_fn, + socket => $socket, + lockfh => $lockfh, + debug => $self->{debug}, + spiceproxy => 1, + trusted_env => 0, + logfile => '/var/log/pveproxy/access.log', + allow_from => $proxyconf->{ALLOW_FROM}, + deny_from => $proxyconf->{DENY_FROM}, + policy => $proxyconf->{POLICY}, }; } sub run { my ($self) = @_; - my $server = PVE::HTTPServer->new(%{$self->{server_config}}); + my $server = PVE::HTTPServer->new(%{ $self->{server_config} }); $server->run(); } @@ -71,10 +71,10 @@ $daemon->register_stop_command(); $daemon->register_status_command(); our $cmddef = { - start => [ __PACKAGE__, 'start', []], - restart => [ __PACKAGE__, 'restart', []], - stop => [ __PACKAGE__, 'stop', []], - status => [ __PACKAGE__, 'status', [], undef, sub { print shift . "\n";} ], + start => [__PACKAGE__, 'start', []], + restart => [__PACKAGE__, 'restart', []], + stop => [__PACKAGE__, 'stop', []], + status => [__PACKAGE__, 'status', [], undef, sub { print shift . "\n"; }], }; 1; diff --git a/PVE/Status/Graphite.pm b/PVE/Status/Graphite.pm index 1b196c24..0adcfd1d 100644 --- a/PVE/Status/Graphite.pm +++ b/PVE/Status/Graphite.pm @@ -26,35 +26,36 @@ sub type { sub properties { return { - path => { - type => 'string', format => 'graphite-path', - description => "root graphite path (ex: proxmox.mycluster.mykey)", - }, - timeout => { - type => 'integer', - description => "graphite TCP socket timeout (default=1)", - minimum => 0, - default => 1, - optional => 1 - }, - proto => { - type => 'string', - enum => ['udp', 'tcp'], - description => "Protocol to send graphite data. TCP or UDP (default)", - optional => 1, - }, + path => { + type => 'string', + format => 'graphite-path', + description => "root graphite path (ex: proxmox.mycluster.mykey)", + }, + timeout => { + type => 'integer', + description => "graphite TCP socket timeout (default=1)", + minimum => 0, + default => 1, + optional => 1, + }, + proto => { + type => 'string', + enum => ['udp', 'tcp'], + description => "Protocol to send graphite data. TCP or UDP (default)", + optional => 1, + }, }; } sub options { return { - server => {}, - port => { optional => 1 }, - mtu => { optional => 1 }, - proto => { optional => 1 }, - timeout => { optional => 1 }, - path => { optional => 1 }, - disable => { optional => 1 }, + server => {}, + port => { optional => 1 }, + mtu => { optional => 1 }, + proto => { optional => 1 }, + timeout => { optional => 1 }, + path => { optional => 1 }, + disable => { optional => 1 }, }; } @@ -88,7 +89,7 @@ sub _send_batch_size { my ($class, $cfg) = @_; my $proto = $cfg->{proto} || 'udp'; if ($proto eq 'tcp') { - return 56000; + return 56000; } return $class->SUPER::_send_batch_size($cfg); } @@ -96,23 +97,23 @@ sub _send_batch_size { sub _connect { my ($class, $cfg) = @_; - my $host = $cfg->{server}; - my $port = $cfg->{port} || 2003; - my $proto = $cfg->{proto} || 'udp'; + my $host = $cfg->{server}; + my $port = $cfg->{port} || 2003; + my $proto = $cfg->{proto} || 'udp'; my $timeout = $cfg->{timeout} // 1; my $carbon_socket = IO::Socket::IP->new( - PeerAddr => $host, - PeerPort => $port, - Proto => $proto, - Timeout => $timeout, + PeerAddr => $host, + PeerPort => $port, + Proto => $proto, + Timeout => $timeout, ) || die "couldn't create carbon socket [$host]:$port - $@\n"; if ($proto eq 'tcp') { - # seconds and µs - my $timeout_struct = pack( 'l!l!', $timeout, 0); - setsockopt($carbon_socket, SOL_SOCKET, SO_SNDTIMEO, $timeout_struct); - setsockopt($carbon_socket, SOL_SOCKET, SO_RCVTIMEO, $timeout_struct); + # seconds and µs + my $timeout_struct = pack('l!l!', $timeout, 0); + setsockopt($carbon_socket, SOL_SOCKET, SO_SNDTIMEO, $timeout_struct); + setsockopt($carbon_socket, SOL_SOCKET, SO_RCVTIMEO, $timeout_struct); } return $carbon_socket; @@ -126,29 +127,29 @@ sub assemble { # we do not want boolean/state information to export to graphite my $key_blacklist = { - 'template' => 1, - 'pid' => 1, - 'agent' => 1, - 'serial' => 1, + 'template' => 1, + 'pid' => 1, + 'agent' => 1, + 'serial' => 1, }; my $assemble_graphite_data; $assemble_graphite_data = sub { - my ($metric, $path) = @_; + my ($metric, $path) = @_; - for my $key (sort keys %$metric) { - my $value = $metric->{$key}; - next if !defined($value); + for my $key (sort keys %$metric) { + my $value = $metric->{$key}; + next if !defined($value); - $key =~ s/\./-/g; - my $metricpath = $path . ".$key"; + $key =~ s/\./-/g; + my $metricpath = $path . ".$key"; - if (ref($value) eq 'HASH') { - $assemble_graphite_data->($value, $metricpath); - } elsif ($value =~ m/^[+-]?[0-9]*\.?[0-9]+$/ && !$key_blacklist->{$key}) { - $class->add_metric_data($txn, "$metricpath $value $ctime\n"); - } - } + if (ref($value) eq 'HASH') { + $assemble_graphite_data->($value, $metricpath); + } elsif ($value =~ m/^[+-]?[0-9]*\.?[0-9]+$/ && !$key_blacklist->{$key}) { + $class->add_metric_data($txn, "$metricpath $value $ctime\n"); + } + } }; $assemble_graphite_data->($data, $path); @@ -156,18 +157,18 @@ sub assemble { } PVE::JSONSchema::register_format('graphite-path', \&pve_verify_graphite_path); + sub pve_verify_graphite_path { my ($path, $noerr) = @_; my $regex = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)"; if ($path !~ /^(${regex}\.)*${regex}$/) { - return undef if $noerr; - die "value does not look like a valid graphite path\n"; + return undef if $noerr; + die "value does not look like a valid graphite path\n"; } return $path; } - 1; diff --git a/PVE/Status/InfluxDB.pm b/PVE/Status/InfluxDB.pm index 13a96711..610a13f7 100644 --- a/PVE/Status/InfluxDB.pm +++ b/PVE/Status/InfluxDB.pm @@ -21,64 +21,68 @@ sub type { sub properties { return { - organization => { - description => "The InfluxDB organization. Only necessary when using the http v2 api." - ." Has no meaning when using v2 compatibility api.", - type => 'string', - optional => 1, - }, - bucket => { - description => "The InfluxDB bucket/db. Only necessary when using the http v2 api.", - type => 'string', - optional => 1, - }, - token => { - description => "The InfluxDB access token. Only necessary when using the http v2 api." - ." If the v2 compatibility api is used, use 'user:password' instead.", - type => 'string', - optional => 1, - }, - 'api-path-prefix' => { - description => "An API path prefix inserted between ':/' and '/api2/'." - ." Can be useful if the InfluxDB service runs behind a reverse proxy.", - type => 'string', - optional => 1, - }, - influxdbproto => { - type => 'string', - enum => ['udp', 'http', 'https'], - default => 'udp', - optional => 1, - }, - 'max-body-size' => { - description => "InfluxDB max-body-size in bytes. Requests are batched up to this size.", - type => 'integer', - minimum => 1, - default => 25_000_000, - }, - 'verify-certificate' => { - description => "Set to 0 to disable certificate verification for https endpoints.", - type => 'boolean', - optional => 1, - default => 1, - }, + organization => { + description => + "The InfluxDB organization. Only necessary when using the http v2 api." + . " Has no meaning when using v2 compatibility api.", + type => 'string', + optional => 1, + }, + bucket => { + description => "The InfluxDB bucket/db. Only necessary when using the http v2 api.", + type => 'string', + optional => 1, + }, + token => { + description => + "The InfluxDB access token. Only necessary when using the http v2 api." + . " If the v2 compatibility api is used, use 'user:password' instead.", + type => 'string', + optional => 1, + }, + 'api-path-prefix' => { + description => "An API path prefix inserted between ':/' and '/api2/'." + . " Can be useful if the InfluxDB service runs behind a reverse proxy.", + type => 'string', + optional => 1, + }, + influxdbproto => { + type => 'string', + enum => ['udp', 'http', 'https'], + default => 'udp', + optional => 1, + }, + 'max-body-size' => { + description => + "InfluxDB max-body-size in bytes. Requests are batched up to this size.", + type => 'integer', + minimum => 1, + default => 25_000_000, + }, + 'verify-certificate' => { + description => "Set to 0 to disable certificate verification for https endpoints.", + type => 'boolean', + optional => 1, + default => 1, + }, }; } + sub options { return { - server => {}, - port => {}, - mtu => { optional => 1 }, - disable => { optional => 1 }, - organization => { optional => 1}, - bucket => { optional => 1}, - token => { optional => 1}, - influxdbproto => { optional => 1}, - timeout => { optional => 1}, - 'max-body-size' => { optional => 1 }, - 'api-path-prefix' => { optional => 1 }, - 'verify-certificate' => { optional => 1 }, - }; + server => {}, + port => {}, + mtu => { optional => 1 }, + disable => { optional => 1 }, + organization => { optional => 1 }, + bucket => { optional => 1 }, + token => { optional => 1 }, + influxdbproto => { optional => 1 }, + timeout => { optional => 1 }, + 'max-body-size' => { optional => 1 }, + 'api-path-prefix' => { optional => 1 }, + 'verify-certificate' => { optional => 1 }, + }; } my $set_ssl_opts = sub { @@ -86,10 +90,10 @@ my $set_ssl_opts = sub { my $cert_verify = $cfg->{'verify-certificate'} // 1; if (!$cert_verify) { - $ua->ssl_opts( - verify_hostname => 0, - SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE, - ); + $ua->ssl_opts( + verify_hostname => 0, + SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE, + ); } return; @@ -110,8 +114,8 @@ sub update_qemu_status { $ctime *= 1000000000; my $object = "object=qemu,vmid=$vmid,nodename=$nodename"; - if($data->{name} && $data->{name} ne '') { - $object .= ",host=$data->{name}"; + if ($data->{name} && $data->{name} ne '') { + $object .= ",host=$data->{name}"; } $object =~ s/\s/\\ /g; @@ -125,8 +129,8 @@ sub update_lxc_status { $ctime *= 1000000000; my $object = "object=lxc,vmid=$vmid,nodename=$nodename"; - if($data->{name} && $data->{name} ne '') { - $object .= ",host=$data->{name}"; + if ($data->{name} && $data->{name} ne '') { + $object .= ",host=$data->{name}"; } $object =~ s/\s/\\ /g; @@ -140,8 +144,8 @@ sub update_storage_status { $ctime *= 1000000000; my $object = "object=storages,nodename=$nodename,host=$storeid"; - if($data->{type} && $data->{type} ne '') { - $object .= ",type=$data->{type}"; + if ($data->{type} && $data->{type} ne '') { + $object .= ",type=$data->{type}"; } $object =~ s/\s/\\ /g; @@ -152,7 +156,7 @@ sub _send_batch_size { my ($class, $cfg) = @_; my $proto = $cfg->{influxdbproto} // 'udp'; if ($proto ne 'udp') { - return $cfg->{'max-body-size'} // 25_000_000; + return $cfg->{'max-body-size'} // 25_000_000; } return $class->SUPER::_send_batch_size($cfg); @@ -163,20 +167,20 @@ sub send { my $proto = $cfg->{influxdbproto} // 'udp'; if ($proto eq 'udp') { - return $class->SUPER::send($connection, $data, $cfg); + return $class->SUPER::send($connection, $data, $cfg); } elsif ($proto =~ m/^https?$/) { - my $ua = LWP::UserAgent->new(); - $set_ssl_opts->($cfg, $ua); - $ua->timeout($cfg->{timeout} // 1); - $connection->content($data); - my $response = $ua->request($connection); + my $ua = LWP::UserAgent->new(); + $set_ssl_opts->($cfg, $ua); + $ua->timeout($cfg->{timeout} // 1); + $connection->content($data); + my $response = $ua->request($connection); - if (!$response->is_success) { - my $err = $response->status_line; - die "$err\n"; - } + if (!$response->is_success) { + my $err = $response->status_line; + die "$err\n"; + } } else { - die "invalid protocol\n"; + die "invalid protocol\n"; } return; @@ -186,7 +190,7 @@ sub _disconnect { my ($class, $connection, $cfg) = @_; my $proto = $cfg->{influxdbproto} // 'udp'; if ($proto eq 'udp') { - return $class->SUPER::_disconnect($connection, $cfg); + return $class->SUPER::_disconnect($connection, $cfg); } return; @@ -197,11 +201,11 @@ sub _get_v2url { my ($proto, $host, $port) = $cfg->@{qw(influxdbproto server port)}; my $api_prefix = $cfg->{'api-path-prefix'} // '/'; if ($api_prefix ne '/' && $api_prefix =~ m!^/*(.+)/*$!) { - $api_prefix = "/$1/"; + $api_prefix = "/$1/"; } if ($api_path ne 'health') { - $api_path = "api/v2/${api_path}"; + $api_path = "api/v2/${api_path}"; } return "${proto}://${host}:${port}${api_prefix}${api_path}"; @@ -215,27 +219,27 @@ sub _connect { my $proto = $cfg->{influxdbproto} // 'udp'; if ($proto eq 'udp') { - my $socket = IO::Socket::IP->new( - PeerAddr => $host, - PeerPort => $port, - Proto => 'udp', - ) || die "couldn't create influxdb socket [$host]:$port - $@\n"; + my $socket = IO::Socket::IP->new( + PeerAddr => $host, + PeerPort => $port, + Proto => 'udp', + ) || die "couldn't create influxdb socket [$host]:$port - $@\n"; - $socket->blocking(0); + $socket->blocking(0); - return $socket; + return $socket; } elsif ($proto =~ m/^https?$/) { - my $token = get_credentials($id, 1); - my $org = $cfg->{organization} // 'proxmox'; - my $bucket = $cfg->{bucket} // 'proxmox'; - my $url = _get_v2url($cfg, "write?org=${org}&bucket=${bucket}"); + my $token = get_credentials($id, 1); + my $org = $cfg->{organization} // 'proxmox'; + my $bucket = $cfg->{bucket} // 'proxmox'; + my $url = _get_v2url($cfg, "write?org=${org}&bucket=${bucket}"); - my $req = HTTP::Request->new(POST => $url); - if (defined($token)) { - $req->header( "Authorization", "Token $token"); - } + my $req = HTTP::Request->new(POST => $url); + if (defined($token)) { + $req->header("Authorization", "Token $token"); + } - return $req; + return $req; } die "cannot connect to InfluxDB: invalid protocol '$proto'\n"; @@ -249,25 +253,25 @@ sub test_connection { my $proto = $cfg->{influxdbproto} // 'udp'; if ($proto eq 'udp') { - return $class->SUPER::test_connection($cfg, $id); + return $class->SUPER::test_connection($cfg, $id); } elsif ($proto =~ m/^https?$/) { - my $url = _get_v2url($cfg, "health"); - my $ua = LWP::UserAgent->new(); - $set_ssl_opts->($cfg, $ua); - $ua->timeout($cfg->{timeout} // 1); - # in the initial add connection test, the token may still be in $cfg - my $token = $cfg->{token} // get_credentials($id, 1); - if (defined($token)) { - $ua->default_header("Authorization" => "Token $token"); - } - my $response = $ua->get($url); + my $url = _get_v2url($cfg, "health"); + my $ua = LWP::UserAgent->new(); + $set_ssl_opts->($cfg, $ua); + $ua->timeout($cfg->{timeout} // 1); + # in the initial add connection test, the token may still be in $cfg + my $token = $cfg->{token} // get_credentials($id, 1); + if (defined($token)) { + $ua->default_header("Authorization" => "Token $token"); + } + my $response = $ua->get($url); - if (!$response->is_success) { - my $err = $response->status_line; - die "$err\n"; - } + if (!$response->is_success) { + my $err = $response->status_line; + die "$err\n"; + } } else { - die "invalid protocol\n"; + die "invalid protocol\n"; } return; @@ -277,40 +281,42 @@ sub build_influxdb_payload { my ($class, $txn, $data, $ctime, $tags, $excluded, $measurement, $instance) = @_; # 'abc' and '123' are both valid hostnames/tags, that confuses influx's type detection - my $to_quote = { name => 1, tags => 1, }; + my $to_quote = { name => 1, tags => 1 }; my @values = (); foreach my $key (sort keys %$data) { - next if defined($excluded) && $excluded->{$key}; - my $value = $data->{$key}; - next if !defined($value); + next if defined($excluded) && $excluded->{$key}; + my $value = $data->{$key}; + next if !defined($value); - if (!ref($value) && $value ne '') { - # value is scalar + if (!ref($value) && $value ne '') { + # value is scalar - if (defined(my $v = prepare_value($value, $to_quote->{$key}))) { - push @values, "$key=$v"; - } - } elsif (ref($value) eq 'HASH') { - # value is a hash + if (defined(my $v = prepare_value($value, $to_quote->{$key}))) { + push @values, "$key=$v"; + } + } elsif (ref($value) eq 'HASH') { + # value is a hash - if (!defined($measurement)) { - build_influxdb_payload($class, $txn, $value, $ctime, $tags, $excluded, $key); - } elsif(!defined($instance)) { - build_influxdb_payload($class, $txn, $value, $ctime, $tags, $excluded, $measurement, $key); - } else { - push @values, get_recursive_values($value); - } - } + if (!defined($measurement)) { + build_influxdb_payload($class, $txn, $value, $ctime, $tags, $excluded, $key); + } elsif (!defined($instance)) { + build_influxdb_payload( + $class, $txn, $value, $ctime, $tags, $excluded, $measurement, $key, + ); + } else { + push @values, get_recursive_values($value); + } + } } if (@values > 0) { - my $mm = $measurement // 'system'; - my $tagstring = $tags; - $tagstring .= ",instance=$instance" if defined($instance); - my $valuestr = join(',', @values); - $class->add_metric_data($txn, "$mm,$tagstring $valuestr $ctime\n"); + my $mm = $measurement // 'system'; + my $tagstring = $tags; + $tagstring .= ",instance=$instance" if defined($instance); + my $valuestr = join(',', @values); + $class->add_metric_data($txn, "$mm,$tagstring $valuestr $ctime\n"); } } @@ -320,14 +326,14 @@ sub get_recursive_values { my @values = (); foreach my $key (keys %$hash) { - my $value = $hash->{$key}; - if(ref($value) eq 'HASH') { - push(@values, get_recursive_values($value)); - } elsif (!ref($value) && $value ne '') { - if (defined(my $v = prepare_value($value))) { - push @values, "$key=$v"; - } - } + my $value = $hash->{$key}; + if (ref($value) eq 'HASH') { + push(@values, get_recursive_values($value)); + } elsif (!ref($value) && $value ne '') { + if (defined(my $v = prepare_value($value))) { + push @values, "$key=$v"; + } + } } return @values; @@ -338,13 +344,13 @@ sub prepare_value { # don't treat value like a number if quote is 1 if (!$force_quote && looks_like_number($value)) { - if (isnan($value) || isinf($value)) { - # we cannot send influxdb NaN or Inf - return undef; - } + if (isnan($value) || isinf($value)) { + # we cannot send influxdb NaN or Inf + return undef; + } - # influxdb also accepts 1.0e+10, etc. - return $value; + # influxdb also accepts 1.0e+10, etc. + return $value; } # non-numeric values require to be quoted, so escape " with \" @@ -365,8 +371,8 @@ sub delete_credentials { my ($id) = @_; if (my $cred_file = cred_file_name($id)) { - unlink($cred_file) - or warn "removing influxdb credentials file '$cred_file' failed: $!\n"; + unlink($cred_file) + or warn "removing influxdb credentials file '$cred_file' failed: $!\n"; } return; @@ -399,9 +405,9 @@ sub on_add_hook { my $token = $sensitive_opts->{token}; if (defined($token)) { - set_credentials($id, $token); + set_credentials($id, $token); } else { - delete_credentials($id); + delete_credentials($id); } return undef; @@ -413,9 +419,9 @@ sub on_update_hook { my $token = $sensitive_opts->{token}; if (defined($token)) { - set_credentials($id, $token); + set_credentials($id, $token); } else { - delete_credentials($id); + delete_credentials($id); } return undef; @@ -429,5 +435,4 @@ sub on_delete_hook { return undef; } - 1; diff --git a/PVE/Status/Plugin.pm b/PVE/Status/Plugin.pm index 1b3de52f..b767c271 100644 --- a/PVE/Status/Plugin.pm +++ b/PVE/Status/Plugin.pm @@ -9,44 +9,48 @@ use PVE::SectionConfig; use base qw(PVE::SectionConfig); -cfs_register_file('status.cfg', +cfs_register_file( + 'status.cfg', sub { __PACKAGE__->parse_config(@_); }, - sub { __PACKAGE__->write_config(@_); } + sub { __PACKAGE__->write_config(@_); }, ); my $defaultData = { propertyList => { - id => { - description => "The ID of the entry.", - type => 'string', format => 'pve-configid', - }, - type => { - description => "Plugin type.", - type => 'string', format => 'pve-configid', - }, - disable => { - description => "Flag to disable the plugin.", - type => 'boolean', - optional => 1, - }, - server => { - type => 'string', format => 'address', - description => "server dns name or IP address", - }, - port => { - type => 'integer', - description => "server network port", - minimum => 1, - maximum => 64*1024, - }, - mtu => { - type => 'integer', - description => "MTU for metrics transmission over UDP", - default => 1500, - minimum => 512, - maximum => 64*1024, - optional => 1, - }, + id => { + description => "The ID of the entry.", + type => 'string', + format => 'pve-configid', + }, + type => { + description => "Plugin type.", + type => 'string', + format => 'pve-configid', + }, + disable => { + description => "Flag to disable the plugin.", + type => 'boolean', + optional => 1, + }, + server => { + type => 'string', + format => 'address', + description => "server dns name or IP address", + }, + port => { + type => 'integer', + description => "server network port", + minimum => 1, + maximum => 64 * 1024, + }, + mtu => { + type => 'integer', + description => "MTU for metrics transmission over UDP", + default => 1500, + minimum => 512, + maximum => 64 * 1024, + optional => 1, + }, }, }; @@ -58,13 +62,13 @@ sub parse_section_header { my ($class, $line) = @_; if ($line =~ m/^(\S+):\s*(\S+)?\s*$/) { - my $type = lc($1); - my $id = $2 // $type; - my $errmsg = undef; # set if you want to skip whole section - eval { PVE::JSONSchema::pve_verify_configid($id) }; - $errmsg = $@ if $@; - my $config = {}; # to return additional attributes - return ($type, $id, $errmsg, $config); + my $type = lc($1); + my $id = $2 // $type; + my $errmsg = undef; # set if you want to skip whole section + eval { PVE::JSONSchema::pve_verify_configid($id) }; + $errmsg = $@ if $@; + my $config = {}; # to return additional attributes + return ($type, $id, $errmsg, $config); } return undef; } @@ -99,7 +103,7 @@ sub add_metric_data { my $dataq_len = length($txn->{data}) // 0; if (($dataq_len + $data_length) >= $batch_size) { - $class->flush_data($txn); + $class->flush_data($txn); } $txn->{data} //= ''; $txn->{data} .= "$data"; @@ -109,8 +113,8 @@ sub flush_data { my ($class, $txn) = @_; if (!$txn->{connection}) { - return if !$txn->{data}; # OK, if data was already sent/flushed - die "cannot flush metric data, no connection available!\n"; + return if !$txn->{data}; # OK, if data was already sent/flushed + die "cannot flush metric data, no connection available!\n"; } return if !defined($txn->{data}) || $txn->{data} eq ''; @@ -123,7 +127,7 @@ sub send { my ($class, $connection, $data, $cfg) = @_; defined($connection->send($data)) - or die "failed to send metrics: $!\n"; + or die "failed to send metrics: $!\n"; } sub test_connection { diff --git a/PVE/VZDump.pm b/PVE/VZDump.pm index 58fa0f64..cf71a9fa 100644 --- a/PVE/VZDump.pm +++ b/PVE/VZDump.pm @@ -31,12 +31,15 @@ use PVE::API2Tools; # and `$digest:$counter` values converted from vzdump.cron # TODO move to a better place once cycle # Jobs::VZDump -> API2::VZDump -> API2::Backups -> Jobs::VZDump is broken.. -PVE::JSONSchema::register_standard_option('pve-backup-jobid', { - type => 'string', - description => "The job ID.", - maxLength => 50, - pattern => '\S+', -}); +PVE::JSONSchema::register_standard_option( + 'pve-backup-jobid', + { + type => 'string', + description => "The job ID.", + maxLength => 50, + pattern => '\S+', + }, +); my @posix_filesystems = qw(ext3 ext4 nfs nfs4 reiserfs xfs); @@ -57,13 +60,13 @@ foreach my $plug (@pve_vzdump_classes) { my $filename = "/usr/share/perl5/$plug.pm"; $filename =~ s!::!/!g; if (-f $filename) { - eval { require $filename; }; - if (!$@) { - $plug->import (); - push @plugins, $plug; - } else { - die $@; - } + eval { require $filename; }; + if (!$@) { + $plug->import(); + push @plugins, $plug; + } else { + die $@; + } } } @@ -86,8 +89,8 @@ sub run_command { my ($logfd, $cmdstr, %param) = @_; my $logfunc = sub { - my $line = shift; - debugmsg ('info', $line, $logfd); + my $line = shift; + debugmsg('info', $line, $logfd); }; PVE::Tools::run_command($cmdstr, %param, logfunc => $logfunc); @@ -100,15 +103,15 @@ my $verify_notes_template = sub { my @problematic = (); while ($template =~ /\\(.)/g) { - my $char = $1; - push @problematic, "escape sequence '\\$char' at char " . (pos($template) - 2) - if $char !~ /^[n\\]$/; + my $char = $1; + push @problematic, "escape sequence '\\$char' at char " . (pos($template) - 2) + if $char !~ /^[n\\]$/; } while ($template =~ /\{\{([^\s{}]+)\}\}/g) { - my $var = $1; - push @problematic, "variable '$var' at char " . (pos($template) - length($var)) - if $var !~ /^(cluster|guestname|node|vmid)$/; + my $var = $1; + push @problematic, "variable '$var' at char " . (pos($template) - length($var)) + if $var !~ /^(cluster|guestname|node|vmid)$/; } die "found unknown: " . join(', ', @problematic) . "\n" if scalar(@problematic); @@ -120,17 +123,17 @@ my $generate_notes = sub { $verify_notes_template->($notes_template); my $info = { - cluster => PVE::Cluster::get_clinfo()->{cluster}->{name} // 'standalone node', - guestname => $task->{hostname} // "VM $task->{vmid}", # is always set for CTs - node => PVE::INotify::nodename(), - vmid => $task->{vmid}, + cluster => PVE::Cluster::get_clinfo()->{cluster}->{name} // 'standalone node', + guestname => $task->{hostname} // "VM $task->{vmid}", # is always set for CTs + node => PVE::INotify::nodename(), + vmid => $task->{vmid}, }; my $unescape = sub { - my ($char) = @_; - return '\\' if $char eq '\\'; - return "\n" if $char eq 'n'; - die "unexpected escape character '$char'\n"; + my ($char) = @_; + return '\\' if $char eq '\\'; + return "\n" if $char eq 'n'; + die "unexpected escape character '$char'\n"; }; $notes_template =~ s/\\(.)/$unescape->($1)/eg; @@ -145,8 +148,8 @@ sub parse_fleecing { my ($param) = @_; if (defined(my $fleecing = $param->{fleecing})) { - return $fleecing if ref($fleecing) eq 'HASH'; # already parsed - $param->{fleecing} = PVE::JSONSchema::parse_property_string('backup-fleecing', $fleecing); + return $fleecing if ref($fleecing) eq 'HASH'; # already parsed + $param->{fleecing} = PVE::JSONSchema::parse_property_string('backup-fleecing', $fleecing); } return $param->{fleecing}; @@ -156,8 +159,8 @@ my sub parse_performance { my ($param) = @_; if (defined(my $perf = $param->{performance})) { - return $perf if ref($perf) eq 'HASH'; # already parsed - $param->{performance} = PVE::JSONSchema::parse_property_string('backup-performance', $perf); + return $perf if ref($perf) eq 'HASH'; # already parsed + $param->{performance} = PVE::JSONSchema::parse_property_string('backup-performance', $perf); } return $param->{performance}; @@ -168,8 +171,8 @@ my sub merge_performance { my $res = {}; for my $opt (keys PVE::JSONSchema::get_format('backup-performance')->%*) { - $res->{$opt} = $prefer->{$opt} // $fallback->{$opt} - if defined($prefer->{$opt}) || defined($fallback->{$opt}); + $res->{$opt} = $prefer->{$opt} // $fallback->{$opt} + if defined($prefer->{$opt}) || defined($fallback->{$opt}); } return $res; } @@ -180,21 +183,22 @@ my $parse_prune_backups_maxfiles = sub { my $maxfiles = delete $param->{maxfiles}; my $prune_backups = $param->{'prune-backups'}; - debugmsg('warn', "both 'maxfiles' and 'prune-backups' defined as ${kind} - ignoring 'maxfiles'") - if defined($maxfiles) && defined($prune_backups); + debugmsg( + 'warn', + "both 'maxfiles' and 'prune-backups' defined as ${kind} - ignoring 'maxfiles'", + ) if defined($maxfiles) && defined($prune_backups); if (defined($prune_backups)) { - return $prune_backups if ref($prune_backups) eq 'HASH'; # already parsed - $param->{'prune-backups'} = PVE::JSONSchema::parse_property_string( - 'prune-backups', - $prune_backups - ); + return $prune_backups if ref($prune_backups) eq 'HASH'; # already parsed + $param->{'prune-backups'} = PVE::JSONSchema::parse_property_string( + 'prune-backups', $prune_backups, + ); } elsif (defined($maxfiles)) { - if ($maxfiles) { - $param->{'prune-backups'} = { 'keep-last' => $maxfiles }; - } else { - $param->{'prune-backups'} = { 'keep-all' => 1 }; - } + if ($maxfiles) { + $param->{'prune-backups'} = { 'keep-last' => $maxfiles }; + } else { + $param->{'prune-backups'} = { 'keep-all' => 1 }; + } } return $param->{'prune-backups'}; @@ -208,22 +212,23 @@ sub storage_info { my $type = $scfg->{type}; die "can't use storage '$storage' for backups - wrong content type\n" - if (!$scfg->{content}->{backup}); + if (!$scfg->{content}->{backup}); my $info = { - scfg => $scfg, + scfg => $scfg, }; - $info->{'prune-backups'} = PVE::JSONSchema::parse_property_string('prune-backups', $scfg->{'prune-backups'}) - if defined($scfg->{'prune-backups'}); + $info->{'prune-backups'} = + PVE::JSONSchema::parse_property_string('prune-backups', $scfg->{'prune-backups'}) + if defined($scfg->{'prune-backups'}); if (PVE::Storage::storage_has_feature($cfg, $storage, 'backup-provider')) { - $info->{'backup-provider'} = - PVE::Storage::new_backup_provider($cfg, $storage, sub { debugmsg($_[0], $_[1]); }); + $info->{'backup-provider'} = + PVE::Storage::new_backup_provider($cfg, $storage, sub { debugmsg($_[0], $_[1]); }); } elsif ($type eq 'pbs') { - $info->{pbs} = 1; + $info->{pbs} = 1; } else { - $info->{dumpdir} = PVE::Storage::get_backup_dir($cfg, $storage); + $info->{dumpdir} = PVE::Storage::get_backup_dir($cfg, $storage); } return $info; @@ -235,30 +240,30 @@ sub format_size { my $kb = $size / 1024; if ($kb < 1024) { - return int ($kb) . "KB"; + return int($kb) . "KB"; } - my $mb = $size / (1024*1024); + my $mb = $size / (1024 * 1024); if ($mb < 1024) { - return int ($mb) . "MB"; + return int($mb) . "MB"; } my $gb = $mb / 1024; if ($gb < 1024) { - return sprintf ("%.2fGB", $gb); + return sprintf("%.2fGB", $gb); } my $tb = $gb / 1024; - return sprintf ("%.2fTB", $tb); + return sprintf("%.2fTB", $tb); } sub format_time { my $seconds = shift; - my $hours = int ($seconds/3600); - $seconds = $seconds - $hours*3600; - my $min = int ($seconds/60); - $seconds = $seconds - $min*60; + my $hours = int($seconds / 3600); + $seconds = $seconds - $hours * 3600; + my $min = int($seconds / 60); + $seconds = $seconds - $min * 60; - return sprintf ("%02d:%02d:%02d", $hours, $min, $seconds); + return sprintf("%02d:%02d:%02d", $hours, $min, $seconds); } sub encode8bit { @@ -280,13 +285,13 @@ sub escape_html { } sub check_bin { - my ($bin) = @_; + my ($bin) = @_; - foreach my $p (split (/:/, $ENV{PATH})) { - my $fn = "$p/$bin"; - if (-x $fn) { - return $fn; - } + foreach my $p (split(/:/, $ENV{PATH})) { + my $fn = "$p/$bin"; + if (-x $fn) { + return $fn; + } } die "unable to find command '$bin'\n"; @@ -296,40 +301,39 @@ sub check_vmids { my (@vmids) = @_; my $res = []; - for my $vmid (sort {$a <=> $b} @vmids) { - die "ERROR: strange VM ID '${vmid}'\n" if $vmid !~ m/^\d+$/; - $vmid = int ($vmid); # remove leading zeros - next if !$vmid; - push @$res, $vmid; + for my $vmid (sort { $a <=> $b } @vmids) { + die "ERROR: strange VM ID '${vmid}'\n" if $vmid !~ m/^\d+$/; + $vmid = int($vmid); # remove leading zeros + next if !$vmid; + push @$res, $vmid; } return $res; } - sub read_vzdump_defaults { my $fn = "/etc/vzdump.conf"; my $defaults = { - map { - my $default = $confdesc->{$_}->{default}; - defined($default) ? ($_ => $default) : () - } keys %$confdesc_for_defaults + map { + my $default = $confdesc->{$_}->{default}; + defined($default) ? ($_ => $default) : () + } keys %$confdesc_for_defaults }; my $performance_fmt = PVE::JSONSchema::get_format('backup-performance'); $defaults->{performance} = { - map { - my $default = $performance_fmt->{$_}->{default}; - defined($default) ? ($_ => $default) : () - } keys $performance_fmt->%* + map { + my $default = $performance_fmt->{$_}->{default}; + defined($default) ? ($_ => $default) : () + } keys $performance_fmt->%* }; my $fleecing_fmt = PVE::JSONSchema::get_format('backup-fleecing'); $defaults->{fleecing} = { - map { - my $default = $fleecing_fmt->{$_}->{default}; - defined($default) ? ($_ => $default) : () - } keys $fleecing_fmt->%* + map { + my $default = $fleecing_fmt->{$_}->{default}; + defined($default) ? ($_ => $default) : () + } keys $fleecing_fmt->%* }; $parse_prune_backups_maxfiles->($defaults, "defaults in VZDump schema"); @@ -340,37 +344,37 @@ sub read_vzdump_defaults { my $conf_schema = { type => 'object', properties => $confdesc_for_defaults }; my $res = PVE::JSONSchema::parse_config($conf_schema, $fn, $raw); if (my $excludes = $res->{'exclude-path'}) { - if (ref($excludes) eq 'ARRAY') { - my $list = []; - for my $path ($excludes->@*) { - # We still use `split_args` here to be compatible with old configs where one line - # still has multiple space separated entries. - push $list->@*, PVE::Tools::split_args($path)->@*; - } - $res->{'exclude-path'} = $list; - } else { - $res->{'exclude-path'} = PVE::Tools::split_args($excludes); - } + if (ref($excludes) eq 'ARRAY') { + my $list = []; + for my $path ($excludes->@*) { + # We still use `split_args` here to be compatible with old configs where one line + # still has multiple space separated entries. + push $list->@*, PVE::Tools::split_args($path)->@*; + } + $res->{'exclude-path'} = $list; + } else { + $res->{'exclude-path'} = PVE::Tools::split_args($excludes); + } } if (defined($res->{mailto})) { - my @mailto = split_list($res->{mailto}); - $res->{mailto} = [ @mailto ]; + my @mailto = split_list($res->{mailto}); + $res->{mailto} = [@mailto]; } $parse_prune_backups_maxfiles->($res, "options in '$fn'"); parse_fleecing($res); parse_performance($res); for my $key (keys $defaults->%*) { - if (!defined($res->{$key})) { - $res->{$key} = $defaults->{$key}; - } elsif ($key eq 'performance') { - $res->{$key} = merge_performance($res->{$key}, $defaults->{$key}); - } + if (!defined($res->{$key})) { + $res->{$key} = $defaults->{$key}; + } elsif ($key eq 'performance') { + $res->{$key} = merge_performance($res->{$key}, $defaults->{$key}); + } } if (defined($res->{storage}) && defined($res->{dumpdir})) { - debugmsg('warn', "both 'storage' and 'dumpdir' defined in '$fn' - ignoring 'dumpdir'"); - delete $res->{dumpdir}; + debugmsg('warn', "both 'storage' and 'dumpdir' defined in '$fn' - ignoring 'dumpdir'"); + delete $res->{dumpdir}; } return $res; @@ -382,22 +386,22 @@ my sub read_backup_task_logs { my $task_logs = ""; for my $task (@$task_list) { - my $vmid = $task->{vmid}; - my $log_file = $task->{tmplog}; - if (!$task->{tmplog}) { - $task_logs .= "$vmid: no log available\n\n"; - next; - } - if (open (my $TMP, '<', "$log_file")) { - while (my $line = <$TMP>) { - next if $line =~ /^status: \d+/; # not useful in mails - $task_logs .= encode8bit ("$vmid: $line"); - } - close ($TMP); - } else { - $task_logs .= "$vmid: Could not open log file\n\n"; - } - $task_logs .= "\n"; + my $vmid = $task->{vmid}; + my $log_file = $task->{tmplog}; + if (!$task->{tmplog}) { + $task_logs .= "$vmid: no log available\n\n"; + next; + } + if (open(my $TMP, '<', "$log_file")) { + while (my $line = <$TMP>) { + next if $line =~ /^status: \d+/; # not useful in mails + $task_logs .= encode8bit("$vmid: $line"); + } + close($TMP); + } else { + $task_logs .= "$vmid: Could not open log file\n\n"; + } + $task_logs .= "\n"; } return $task_logs; @@ -407,51 +411,52 @@ my sub build_guest_table { my ($task_list) = @_; my $table = { - schema => { - columns => [ - { - label => "VMID", - id => "vmid" - }, - { - label => "Name", - id => "name" - }, - { - label => "Status", - id => "status" - }, - { - label => "Time", - id => "time", - renderer => "duration" - }, - { - label => "Size", - id => "size", - renderer => "human-bytes" - }, - { - label => "Filename", - id => "filename" - }, - ] - }, - data => [] + schema => { + columns => [ + { + label => "VMID", + id => "vmid", + }, + { + label => "Name", + id => "name", + }, + { + label => "Status", + id => "status", + }, + { + label => "Time", + id => "time", + renderer => "duration", + }, + { + label => "Size", + id => "size", + renderer => "human-bytes", + }, + { + label => "Filename", + id => "filename", + }, + ], + }, + data => [], }; for my $task (@$task_list) { - my $successful = $task->{state} eq 'ok'; - my $size = $successful ? $task->{size} : 0; - my $filename = $successful ? $task->{target} : undef; - push @{$table->{data}}, { - "vmid" => int($task->{vmid}), - "name" => $task->{hostname}, - "status" => $task->{state}, - "time" => int($task->{backuptime}), - "size" => int($size), - "filename" => $filename, - }; + my $successful = $task->{state} eq 'ok'; + my $size = $successful ? $task->{size} : 0; + my $filename = $successful ? $task->{target} : undef; + push @{ $table->{data} }, + { + "vmid" => int($task->{vmid}), + "name" => $task->{hostname}, + "status" => $task->{state}, + "time" => int($task->{backuptime}), + "size" => int($size), + "filename" => $filename, + }; } return $table; @@ -460,15 +465,15 @@ my sub build_guest_table { my sub sanitize_task_list { my ($task_list) = @_; for my $task (@$task_list) { - chomp $task->{msg} if $task->{msg}; - $task->{backuptime} = 0 if !$task->{backuptime}; - $task->{size} = 0 if !$task->{size}; - $task->{target} = 'unknown' if !$task->{target}; - $task->{hostname} = "VM $task->{vmid}" if !$task->{hostname}; + chomp $task->{msg} if $task->{msg}; + $task->{backuptime} = 0 if !$task->{backuptime}; + $task->{size} = 0 if !$task->{size}; + $task->{target} = 'unknown' if !$task->{target}; + $task->{hostname} = "VM $task->{vmid}" if !$task->{hostname}; - if ($task->{state} eq 'todo') { - $task->{msg} = 'aborted'; - } + if ($task->{state} eq 'todo') { + $task->{msg} = 'aborted'; + } } } @@ -478,8 +483,8 @@ my sub aggregate_task_statistics { my $error_count = 0; my $total_size = 0; for my $task (@$tasklist) { - $error_count++ if $task->{state} ne 'ok'; - $total_size += $task->{size} if $task->{state} eq 'ok'; + $error_count++ if $task->{state} ne 'ok'; + $total_size += $task->{size} if $task->{state} eq 'ok'; } return ($error_count, $total_size); @@ -491,7 +496,7 @@ my sub get_hostname { return $hostname; } -use constant MAX_LOG_SIZE => 1024*1024; +use constant MAX_LOG_SIZE => 1024 * 1024; sub send_notification { my ($self, $tasklist, $total_time, $err, $detail_pre, $detail_post) = @_; @@ -511,12 +516,12 @@ sub send_notification { my $status_text = $failed ? 'backup failed' : 'backup successful'; if ($err) { - if ($err =~ /\n/) { - $status_text .= ": multiple problems"; - } else { - $status_text .= ": $err"; - $err = undef; - } + if ($err =~ /\n/) { + $status_text .= ": multiple problems"; + } else { + $status_text .= ": $err"; + $err = undef; + } } my $text_log_part = "$cmdline\n\n"; @@ -524,12 +529,10 @@ sub send_notification { $text_log_part .= read_backup_task_logs($tasklist); $text_log_part .= $detail_post if defined($detail_post); - if (length($text_log_part) > MAX_LOG_SIZE) - { - # Let's limit the maximum length of included logs - $text_log_part = "Log output was too long to be sent. ". - "See Task History for details!\n"; - }; + if (length($text_log_part) > MAX_LOG_SIZE) { + # Let's limit the maximum length of included logs + $text_log_part = "Log output was too long to be sent. " . "See Task History for details!\n"; + } my $template_data = PVE::Notify::common_template_data(); $template_data->{error} = $err; @@ -540,9 +543,9 @@ sub send_notification { $template_data->{"total-time"} = $total_time; my $fields = { - type => "vzdump", - # Hostname (without domain part) - hostname => PVE::INotify::nodename(), + type => "vzdump", + # Hostname (without domain part) + hostname => PVE::INotify::nodename(), }; # Add backup-job metadata field in case this is a backup job. $fields->{'job-id'} = $job_id if $job_id; @@ -551,91 +554,84 @@ sub send_notification { my $email_configured = $mailto && scalar(@$mailto); if (($mode eq 'auto' && $email_configured) || $mode eq 'legacy-sendmail') { - if ($email_configured && ($policy eq "always" || ($policy eq "failure" && $failed))) { - # Start out with an empty config. Might still contain - # built-ins, so we need to disable/remove them. - my $notification_config = Proxmox::RS::Notify->parse_config('', ''); + if ($email_configured && ($policy eq "always" || ($policy eq "failure" && $failed))) { + # Start out with an empty config. Might still contain + # built-ins, so we need to disable/remove them. + my $notification_config = Proxmox::RS::Notify->parse_config('', ''); - # Remove built-in matchers, since we only want to send an - # email to the specified recipients and nobody else. - for my $matcher (@{$notification_config->get_matchers()}) { - $notification_config->delete_matcher($matcher->{name}); - } + # Remove built-in matchers, since we only want to send an + # email to the specified recipients and nobody else. + for my $matcher (@{ $notification_config->get_matchers() }) { + $notification_config->delete_matcher($matcher->{name}); + } - # <, >, @ are not allowed in endpoint names, but that is only - # verified once the config is serialized. That means that - # we can rely on that fact that no other endpoint with this name exists. - my $endpoint_name = "<" . join(",", @$mailto) . ">"; - $notification_config->add_sendmail_endpoint( - $endpoint_name, - $mailto, - undef, - undef, - "vzdump backup tool" - ); + # <, >, @ are not allowed in endpoint names, but that is only + # verified once the config is serialized. That means that + # we can rely on that fact that no other endpoint with this name exists. + my $endpoint_name = "<" . join(",", @$mailto) . ">"; + $notification_config->add_sendmail_endpoint( + $endpoint_name, + $mailto, + undef, + undef, + "vzdump backup tool", + ); - my $endpoints = [$endpoint_name]; + my $endpoints = [$endpoint_name]; - # Add a matcher that matches all notifications, set our - # newly created target as a target. - $notification_config->add_matcher( - "", - $endpoints, - ); + # Add a matcher that matches all notifications, set our + # newly created target as a target. + $notification_config->add_matcher( + "", $endpoints, + ); - PVE::Notify::notify( - $severity, - "vzdump", - $template_data, - $fields, - $notification_config - ); - } + PVE::Notify::notify( + $severity, "vzdump", $template_data, $fields, $notification_config, + ); + } } else { - # We use the 'new' system, or we are set to 'auto' and - # no email addresses were configured. - PVE::Notify::notify( - $severity, - "vzdump", - $template_data, - $fields, - ); + # We use the 'new' system, or we are set to 'auto' and + # no email addresses were configured. + PVE::Notify::notify( + $severity, "vzdump", $template_data, $fields, + ); } -}; +} sub new { my ($class, $cmdline, $opts, $skiplist) = @_; mkpath $logdir; - check_bin ('cp'); - check_bin ('df'); - check_bin ('sendmail'); - check_bin ('rsync'); - check_bin ('tar'); - check_bin ('mount'); - check_bin ('umount'); - check_bin ('cstream'); - check_bin ('ionice'); + check_bin('cp'); + check_bin('df'); + check_bin('sendmail'); + check_bin('rsync'); + check_bin('tar'); + check_bin('mount'); + check_bin('umount'); + check_bin('cstream'); + check_bin('ionice'); if ($opts->{mode} && $opts->{mode} eq 'snapshot') { - check_bin ('lvcreate'); - check_bin ('lvs'); - check_bin ('lvremove'); + check_bin('lvcreate'); + check_bin('lvs'); + check_bin('lvremove'); } my $defaults = read_vzdump_defaults(); foreach my $k (keys %$defaults) { - next if $k eq 'exclude-path' || $k eq 'prune-backups'; # dealt with separately - if ($k eq 'dumpdir' || $k eq 'storage') { - $opts->{$k} = $defaults->{$k} if !defined ($opts->{dumpdir}) && - !defined ($opts->{storage}); - } elsif (!defined($opts->{$k})) { - $opts->{$k} = $defaults->{$k}; - } elsif ($k eq 'performance') { - $opts->{$k} = merge_performance($opts->{$k}, $defaults->{$k}); - } + next if $k eq 'exclude-path' || $k eq 'prune-backups'; # dealt with separately + if ($k eq 'dumpdir' || $k eq 'storage') { + $opts->{$k} = $defaults->{$k} + if !defined($opts->{dumpdir}) + && !defined($opts->{storage}); + } elsif (!defined($opts->{$k})) { + $opts->{$k} = $defaults->{$k}; + } elsif ($k eq 'performance') { + $opts->{$k} = merge_performance($opts->{$k}, $defaults->{$k}); + } } $opts->{dumpdir} =~ s|/+$|| if ($opts->{dumpdir}); @@ -643,87 +639,83 @@ sub new { $skiplist = [] if !$skiplist; my $self = bless { - cmdline => $cmdline, - opts => $opts, - skiplist => $skiplist, + cmdline => $cmdline, + opts => $opts, + skiplist => $skiplist, }, $class; my $findexcl = $self->{findexcl} = []; if ($defaults->{'exclude-path'}) { - push @$findexcl, @{$defaults->{'exclude-path'}}; + push @$findexcl, @{ $defaults->{'exclude-path'} }; } if ($opts->{'exclude-path'}) { - push @$findexcl, @{$opts->{'exclude-path'}}; + push @$findexcl, @{ $opts->{'exclude-path'} }; } if ($opts->{stdexcludes}) { - push @$findexcl, - '/tmp/?*', - '/var/tmp/?*', - '/var/run/?*.pid', - ; + push @$findexcl, '/tmp/?*', '/var/tmp/?*', '/var/run/?*.pid',; } foreach my $p (@plugins) { - my $pd = $p->new($self); + my $pd = $p->new($self); - push @{$self->{plugins}}, $pd; + push @{ $self->{plugins} }, $pd; } if (defined($opts->{storage}) && $opts->{stdout}) { - die "cannot use options 'storage' and 'stdout' at the same time\n"; + die "cannot use options 'storage' and 'stdout' at the same time\n"; } elsif (defined($opts->{storage}) && defined($opts->{dumpdir})) { - die "cannot use options 'storage' and 'dumpdir' at the same time\n"; + die "cannot use options 'storage' and 'dumpdir' at the same time\n"; } if (my $storage = get_storage_param($opts)) { - $opts->{storage} = $storage; + $opts->{storage} = $storage; } # Enforced by the API too, but these options might come in via defaults. Drop them if necessary. if (!$opts->{storage}) { - delete $opts->{$_} for qw(notes-template protected); + delete $opts->{$_} for qw(notes-template protected); } my $errors = ''; my $add_error = sub { - my ($error) = @_; - $errors .= "\n" if $errors; - chomp($error); - $errors .= $error; + my ($error) = @_; + $errors .= "\n" if $errors; + chomp($error); + $errors .= $error; }; eval { - $self->{job_init_log} = ''; - open my $job_init_fd, '>', \$self->{job_init_log}; - $self->run_hook_script('job-init', undef, $job_init_fd); - close $job_init_fd; + $self->{job_init_log} = ''; + open my $job_init_fd, '>', \$self->{job_init_log}; + $self->run_hook_script('job-init', undef, $job_init_fd); + close $job_init_fd; - PVE::Cluster::cfs_update(); # Pick up possible changes made by the hook script. + PVE::Cluster::cfs_update(); # Pick up possible changes made by the hook script. }; $add_error->($@) if $@; if ($opts->{storage}) { - my $storage_cfg = PVE::Storage::config(); - eval { PVE::Storage::activate_storage($storage_cfg, $opts->{storage}) }; - $add_error->("could not activate storage '$opts->{storage}': $@") if $@; + my $storage_cfg = PVE::Storage::config(); + eval { PVE::Storage::activate_storage($storage_cfg, $opts->{storage}) }; + $add_error->("could not activate storage '$opts->{storage}': $@") if $@; - my $info = eval { storage_info ($opts->{storage}) }; - if (my $err = $@) { - $add_error->("could not get storage information for '$opts->{storage}': $err"); - } else { - $opts->{dumpdir} = $info->{dumpdir}; - $opts->{scfg} = $info->{scfg}; - $opts->{pbs} = $info->{pbs}; - $opts->{'prune-backups'} //= $info->{'prune-backups'}; - $self->{'backup-provider'} = $info->{'backup-provider'} if $info->{'backup-provider'}; - } + my $info = eval { storage_info($opts->{storage}) }; + if (my $err = $@) { + $add_error->("could not get storage information for '$opts->{storage}': $err"); + } else { + $opts->{dumpdir} = $info->{dumpdir}; + $opts->{scfg} = $info->{scfg}; + $opts->{pbs} = $info->{pbs}; + $opts->{'prune-backups'} //= $info->{'prune-backups'}; + $self->{'backup-provider'} = $info->{'backup-provider'} if $info->{'backup-provider'}; + } } elsif ($opts->{dumpdir}) { - $add_error->("dumpdir '$opts->{dumpdir}' does not exist") - if ! -d $opts->{dumpdir}; + $add_error->("dumpdir '$opts->{dumpdir}' does not exist") + if !-d $opts->{dumpdir}; } else { - die "internal error"; + die "internal error"; } $opts->{'prune-backups'} //= $defaults->{'prune-backups'}; @@ -731,14 +723,14 @@ sub new { # avoid triggering any remove code path if keep-all is set $opts->{remove} = 0 if $opts->{'prune-backups'}->{'keep-all'}; - if ($opts->{tmpdir} && ! -d $opts->{tmpdir}) { - $add_error->("tmpdir '$opts->{tmpdir}' does not exist"); + if ($opts->{tmpdir} && !-d $opts->{tmpdir}) { + $add_error->("tmpdir '$opts->{tmpdir}' does not exist"); } if ($errors) { - eval { $self->send_notification([], 0, $errors); }; - debugmsg ('err', $@) if $@; - die "$errors\n"; + eval { $self->send_notification([], 0, $errors); }; + debugmsg('err', $@) if $@; + die "$errors\n"; } return $self; @@ -749,23 +741,27 @@ sub get_mount_info { # Note: df 'available' can be negative, and percentage set to '-' - my $cmd = [ 'df', '-P', '-T', '-B', '1', $dir]; + my $cmd = ['df', '-P', '-T', '-B', '1', $dir]; my $res; my $parser = sub { - my $line = shift; - if (my ($fsid, $fstype, undef, $mp) = $line =~ - m!(\S+.*)\s+(\S+)\s+\d+\s+\-?\d+\s+\d+\s+(\d+%|-)\s+(/.*)$!) { - $res = { - device => $fsid, - fstype => $fstype, - mountpoint => $mp, - }; - } + my $line = shift; + if ( + my ($fsid, $fstype, undef, $mp) = + $line =~ m!(\S+.*)\s+(\S+)\s+\d+\s+\-?\d+\s+\d+\s+(\d+%|-)\s+(/.*)$! + ) { + $res = { + device => $fsid, + fstype => $fstype, + mountpoint => $mp, + }; + } }; - eval { PVE::Tools::run_command($cmd, errfunc => sub {}, outfunc => $parser); }; + eval { + PVE::Tools::run_command($cmd, errfunc => sub { }, outfunc => $parser); + }; warn $@ if $@; return $res; @@ -781,41 +777,41 @@ sub getlock { die "missing UPID" if !$upid; # should not happen my $SERVER_FLCK; - if (!open ($SERVER_FLCK, '>>', "$lockfile")) { - debugmsg ('err', "can't open lock on file '$lockfile' - $!", undef, 1); - die "can't open lock on file '$lockfile' - $!"; + if (!open($SERVER_FLCK, '>>', "$lockfile")) { + debugmsg('err', "can't open lock on file '$lockfile' - $!", undef, 1); + die "can't open lock on file '$lockfile' - $!"; } - if (!flock ($SERVER_FLCK, LOCK_EX|LOCK_NB)) { - if (!$maxwait) { - debugmsg ('err', "can't acquire lock '$lockfile' (wait = 0)", undef, 1); - die "can't acquire lock '$lockfile' (wait = 0)"; - } + if (!flock($SERVER_FLCK, LOCK_EX | LOCK_NB)) { + if (!$maxwait) { + debugmsg('err', "can't acquire lock '$lockfile' (wait = 0)", undef, 1); + die "can't acquire lock '$lockfile' (wait = 0)"; + } - debugmsg('info', "trying to get global lock - waiting...", undef, 1); - eval { - alarm ($maxwait * 60); + debugmsg('info', "trying to get global lock - waiting...", undef, 1); + eval { + alarm($maxwait * 60); - local $SIG{ALRM} = sub { alarm (0); die "got timeout\n"; }; + local $SIG{ALRM} = sub { alarm(0); die "got timeout\n"; }; - if (!flock ($SERVER_FLCK, LOCK_EX)) { - my $err = $!; - close ($SERVER_FLCK); - alarm (0); - die "$err\n"; - } - alarm (0); - }; - alarm (0); + if (!flock($SERVER_FLCK, LOCK_EX)) { + my $err = $!; + close($SERVER_FLCK); + alarm(0); + die "$err\n"; + } + alarm(0); + }; + alarm(0); - my $err = $@; + my $err = $@; - if ($err) { - debugmsg ('err', "can't acquire lock '$lockfile' - $err", undef, 1); - die "can't acquire lock '$lockfile' - $err"; - } + if ($err) { + debugmsg('err', "can't acquire lock '$lockfile' - $err", undef, 1); + die "can't acquire lock '$lockfile' - $err"; + } - debugmsg('info', "got global lock", undef, 1); + debugmsg('info', "got global lock", undef, 1); } PVE::Tools::file_set_contents($pidfile, $upid); @@ -831,14 +827,14 @@ sub run_hook_script { my $script = $opts->{script}; return if !$script; - die "Error: The hook script '$script' does not exist.\n" if ! -f $script; - die "Error: The hook script '$script' is not executable.\n" if ! -x $script; + die "Error: The hook script '$script' does not exist.\n" if !-f $script; + die "Error: The hook script '$script' is not executable.\n" if !-x $script; my $cmd = [$script, $phase]; if ($task) { - push @$cmd, $task->{mode}; - push @$cmd, $task->{vmid}; + push @$cmd, $task->{mode}; + push @$cmd, $task->{vmid}; } local %ENV; @@ -847,10 +843,10 @@ sub run_hook_script { $ENV{DUMPDIR} = $opts->{dumpdir} if $opts->{dumpdir}; foreach my $ek (qw(vmtype hostname target logfile)) { - $ENV{uc($ek)} = $task->{$ek} if $task->{$ek}; + $ENV{ uc($ek) } = $task->{$ek} if $task->{$ek}; } - run_command ($logfd, $cmd); + run_command($logfd, $cmd); } sub compressor_info { @@ -858,29 +854,29 @@ sub compressor_info { my $opt_compress = $opts->{compress}; if (!$opt_compress || $opt_compress eq '0') { - return undef; + return undef; } elsif ($opt_compress eq '1' || $opt_compress eq 'lzo') { - return ('lzop', 'lzo'); + return ('lzop', 'lzo'); } elsif ($opt_compress eq 'gzip') { - if ($opts->{pigz} > 0) { - my $pigz_threads = $opts->{pigz}; - if ($pigz_threads == 1) { - my $cpuinfo = PVE::ProcFSTools::read_cpuinfo(); - $pigz_threads = int(($cpuinfo->{cpus} + 1)/2); - } - return ("pigz -p ${pigz_threads} --rsyncable", 'gz'); - } else { - return ('gzip --rsyncable', 'gz'); - } + if ($opts->{pigz} > 0) { + my $pigz_threads = $opts->{pigz}; + if ($pigz_threads == 1) { + my $cpuinfo = PVE::ProcFSTools::read_cpuinfo(); + $pigz_threads = int(($cpuinfo->{cpus} + 1) / 2); + } + return ("pigz -p ${pigz_threads} --rsyncable", 'gz'); + } else { + return ('gzip --rsyncable', 'gz'); + } } elsif ($opt_compress eq 'zstd') { - my $zstd_threads = $opts->{zstd} // 1; - if ($zstd_threads == 0) { - my $cpuinfo = PVE::ProcFSTools::read_cpuinfo(); - $zstd_threads = int(($cpuinfo->{cpus} + 1)/2); - } - return ("zstd --threads=${zstd_threads}", 'zst'); + my $zstd_threads = $opts->{zstd} // 1; + if ($zstd_threads == 0) { + my $cpuinfo = PVE::ProcFSTools::read_cpuinfo(); + $zstd_threads = int(($cpuinfo->{cpus} + 1) / 2); + } + return ("zstd --threads=${zstd_threads}", 'zst'); } else { - die "internal error - unknown compression option '$opt_compress'"; + die "internal error - unknown compression option '$opt_compress'"; } } @@ -889,17 +885,17 @@ sub get_backup_file_list { my $bklist = []; foreach my $fn (<$dir/${bkname}-*>) { - my $archive_info = eval { PVE::Storage::archive_info($fn) } // {}; - if ($archive_info->{is_std_name}) { - my $path = "$dir/$archive_info->{filename}"; - my $backup = { - 'path' => $path, - 'ctime' => $archive_info->{ctime}, - }; - $backup->{mark} = "protected" - if -e PVE::Storage::protection_file_path($path); - push @{$bklist}, $backup; - } + my $archive_info = eval { PVE::Storage::archive_info($fn) } // {}; + if ($archive_info->{is_std_name}) { + my $path = "$dir/$archive_info->{filename}"; + my $backup = { + 'path' => $path, + 'ctime' => $archive_info->{ctime}, + }; + $backup->{mark} = "protected" + if -e PVE::Storage::protection_file_path($path); + push @{$bklist}, $backup; + } } return $bklist; @@ -919,411 +915,427 @@ sub exec_backup_task { my $pbs_group_name; my $pbs_snapshot_name; - my $vmstarttime = time (); + my $vmstarttime = time(); my $logfd; my $cleanup = {}; my $log_vm_online_again = sub { - return if !defined($task->{vmstoptime}); - $task->{vmconttime} //= time(); - my $delay = $task->{vmconttime} - $task->{vmstoptime}; - $delay = '<1' if $delay < 1; - debugmsg ('info', "guest is online again after $delay seconds", $logfd); + return if !defined($task->{vmstoptime}); + $task->{vmconttime} //= time(); + my $delay = $task->{vmconttime} - $task->{vmstoptime}; + $delay = '<1' if $delay < 1; + debugmsg('info', "guest is online again after $delay seconds", $logfd); }; eval { - die "unable to find VM '$vmid'\n" if !$plugin; + die "unable to find VM '$vmid'\n" if !$plugin; - my $vmtype = $plugin->type(); + my $vmtype = $plugin->type(); - if ($self->{opts}->{pbs}) { - if ($vmtype eq 'lxc') { - $pbs_group_name = "ct/$vmid"; - } elsif ($vmtype eq 'qemu') { - $pbs_group_name = "vm/$vmid"; - } else { - die "pbs backup not implemented for plugin type '$vmtype'\n"; - } - my $btime = strftime("%FT%TZ", gmtime($task->{backup_time})); - $pbs_snapshot_name = "$pbs_group_name/$btime"; - } + if ($self->{opts}->{pbs}) { + if ($vmtype eq 'lxc') { + $pbs_group_name = "ct/$vmid"; + } elsif ($vmtype eq 'qemu') { + $pbs_group_name = "vm/$vmid"; + } else { + die "pbs backup not implemented for plugin type '$vmtype'\n"; + } + my $btime = strftime("%FT%TZ", gmtime($task->{backup_time})); + $pbs_snapshot_name = "$pbs_group_name/$btime"; + } - # for now we deny backups of a running ha managed service in *stop* mode - # as it interferes with the HA stack (started services should not stop). - if ($opts->{mode} eq 'stop' && - PVE::HA::Config::vm_is_ha_managed($vmid, 'started')) - { - die "Cannot execute a backup with stop mode on a HA managed and". - " enabled Service. Use snapshot mode or disable the Service.\n"; - } + # for now we deny backups of a running ha managed service in *stop* mode + # as it interferes with the HA stack (started services should not stop). + if ( + $opts->{mode} eq 'stop' + && PVE::HA::Config::vm_is_ha_managed($vmid, 'started') + ) { + die "Cannot execute a backup with stop mode on a HA managed and" + . " enabled Service. Use snapshot mode or disable the Service.\n"; + } - my $tmplog = "$logdir/$vmtype-$vmid.log"; + my $tmplog = "$logdir/$vmtype-$vmid.log"; - my $bkname = "vzdump-$vmtype-$vmid"; - my $basename = $bkname . strftime("-%Y_%m_%d-%H_%M_%S", localtime($task->{backup_time})); + my $bkname = "vzdump-$vmtype-$vmid"; + my $basename = + $bkname . strftime("-%Y_%m_%d-%H_%M_%S", localtime($task->{backup_time})); - my $prune_options = $opts->{'prune-backups'}; + my $prune_options = $opts->{'prune-backups'}; - my $backup_limit = 0; - if (!$prune_options->{'keep-all'}) { - foreach my $keep (values %{$prune_options}) { - $backup_limit += $keep; - } - } + my $backup_limit = 0; + if (!$prune_options->{'keep-all'}) { + foreach my $keep (values %{$prune_options}) { + $backup_limit += $keep; + } + } - if (($backup_limit && !$opts->{remove}) || $opts->{protected}) { - my $count; - my $protected_count; - if (my $storeid = $opts->{storage}) { - my @backups = grep { - !$_->{subtype} || $_->{subtype} eq $vmtype - } PVE::Storage::volume_list($cfg, $storeid, $vmid, 'backup')->@*; + if (($backup_limit && !$opts->{remove}) || $opts->{protected}) { + my $count; + my $protected_count; + if (my $storeid = $opts->{storage}) { + my @backups = grep { + !$_->{subtype} || $_->{subtype} eq $vmtype + } PVE::Storage::volume_list($cfg, $storeid, $vmid, 'backup')->@*; - $count = grep { !$_->{protected} } @backups; - $protected_count = scalar(@backups) - $count; - } else { - $count = grep { !$_->{mark} || $_->{mark} ne "protected" } get_backup_file_list($opts->{dumpdir}, $bkname)->@*; - } + $count = grep { !$_->{protected} } @backups; + $protected_count = scalar(@backups) - $count; + } else { + $count = grep { !$_->{mark} || $_->{mark} ne "protected" } + get_backup_file_list($opts->{dumpdir}, $bkname)->@*; + } - if ($opts->{protected}) { - my $max_protected = PVE::Storage::get_max_protected_backups( - $opts->{scfg}, - $opts->{storage}, - ); - if ($max_protected > -1 && $protected_count >= $max_protected) { - die "The number of protected backups per guest is limited to $max_protected ". - "on storage '$opts->{storage}'\n"; - } - } elsif ($count >= $backup_limit) { - die "There is a max backup limit of $backup_limit enforced by the target storage ". - "or the vzdump parameters. Either increase the limit or delete old backups.\n"; - } - } + if ($opts->{protected}) { + my $max_protected = PVE::Storage::get_max_protected_backups( + $opts->{scfg}, $opts->{storage}, + ); + if ($max_protected > -1 && $protected_count >= $max_protected) { + die + "The number of protected backups per guest is limited to $max_protected " + . "on storage '$opts->{storage}'\n"; + } + } elsif ($count >= $backup_limit) { + die + "There is a max backup limit of $backup_limit enforced by the target storage " + . "or the vzdump parameters. Either increase the limit or delete old backups.\n"; + } + } - if (!$self->{opts}->{pbs} && !$self->{'backup-provider'}) { - $task->{logfile} = "$opts->{dumpdir}/$basename.log"; - } + if (!$self->{opts}->{pbs} && !$self->{'backup-provider'}) { + $task->{logfile} = "$opts->{dumpdir}/$basename.log"; + } - my $ext = $vmtype eq 'qemu' ? '.vma' : '.tar'; - my ($comp, $comp_ext) = compressor_info($opts); - if ($comp && $comp_ext) { - $ext .= ".${comp_ext}"; - } + my $ext = $vmtype eq 'qemu' ? '.vma' : '.tar'; + my ($comp, $comp_ext) = compressor_info($opts); + if ($comp && $comp_ext) { + $ext .= ".${comp_ext}"; + } - if ($self->{'backup-provider'}) { - die "unable to pipe backup to stdout\n" if $opts->{stdout}; - # the archive name $task->{target} is returned by the start hook a bit later - } elsif ($self->{opts}->{pbs}) { - die "unable to pipe backup to stdout\n" if $opts->{stdout}; - $task->{target} = $pbs_snapshot_name; - } else { - if ($opts->{stdout}) { - $task->{target} = '-'; - } else { - $task->{target} = $task->{tmptar} = "$opts->{dumpdir}/$basename$ext"; - $task->{tmptar} =~ s/\.[^\.]+$/\.dat/; - unlink $task->{tmptar}; - } - } + if ($self->{'backup-provider'}) { + die "unable to pipe backup to stdout\n" if $opts->{stdout}; + # the archive name $task->{target} is returned by the start hook a bit later + } elsif ($self->{opts}->{pbs}) { + die "unable to pipe backup to stdout\n" if $opts->{stdout}; + $task->{target} = $pbs_snapshot_name; + } else { + if ($opts->{stdout}) { + $task->{target} = '-'; + } else { + $task->{target} = $task->{tmptar} = "$opts->{dumpdir}/$basename$ext"; + $task->{tmptar} =~ s/\.[^\.]+$/\.dat/; + unlink $task->{tmptar}; + } + } - $task->{vmtype} = $vmtype; + $task->{vmtype} = $vmtype; - my $pid = $$; - if ($opts->{tmpdir}) { - $task->{tmpdir} = "$opts->{tmpdir}/vzdumptmp${pid}_$vmid/"; - } elsif ($self->{opts}->{pbs} || $self->{'backup-provider'}) { - $task->{tmpdir} = "/var/tmp/vzdumptmp${pid}_$vmid"; - } else { - # dumpdir is posix? then use it as temporary dir - my $info = get_mount_info($opts->{dumpdir}); - if ($vmtype eq 'qemu' || - grep ($_ eq $info->{fstype}, @posix_filesystems)) { - $task->{tmpdir} = "$opts->{dumpdir}/$basename.tmp"; - } else { - $task->{tmpdir} = "/var/tmp/vzdumptmp${pid}_$vmid"; - debugmsg ('info', "filesystem type on dumpdir is '$info->{fstype}' -" . - "using $task->{tmpdir} for temporary files", $logfd); - } - } + my $pid = $$; + if ($opts->{tmpdir}) { + $task->{tmpdir} = "$opts->{tmpdir}/vzdumptmp${pid}_$vmid/"; + } elsif ($self->{opts}->{pbs} || $self->{'backup-provider'}) { + $task->{tmpdir} = "/var/tmp/vzdumptmp${pid}_$vmid"; + } else { + # dumpdir is posix? then use it as temporary dir + my $info = get_mount_info($opts->{dumpdir}); + if ( + $vmtype eq 'qemu' + || grep ($_ eq $info->{fstype}, @posix_filesystems) + ) { + $task->{tmpdir} = "$opts->{dumpdir}/$basename.tmp"; + } else { + $task->{tmpdir} = "/var/tmp/vzdumptmp${pid}_$vmid"; + debugmsg( + 'info', + "filesystem type on dumpdir is '$info->{fstype}' -" + . "using $task->{tmpdir} for temporary files", + $logfd, + ); + } + } - rmtree $task->{tmpdir}; - mkdir $task->{tmpdir}; - -d $task->{tmpdir} || - die "unable to create temporary directory '$task->{tmpdir}'"; + rmtree $task->{tmpdir}; + mkdir $task->{tmpdir}; + -d $task->{tmpdir} + || die "unable to create temporary directory '$task->{tmpdir}'"; - $logfd = IO::File->new (">$tmplog") || - die "unable to create log file '$tmplog'"; + $logfd = IO::File->new(">$tmplog") + || die "unable to create log file '$tmplog'"; - $task->{dumpdir} = $opts->{dumpdir}; - $task->{storeid} = $opts->{storage}; - $task->{scfg} = $opts->{scfg}; - $task->{tmplog} = $tmplog; + $task->{dumpdir} = $opts->{dumpdir}; + $task->{storeid} = $opts->{storage}; + $task->{scfg} = $opts->{scfg}; + $task->{tmplog} = $tmplog; - unlink $task->{logfile} if defined($task->{logfile}); + unlink $task->{logfile} if defined($task->{logfile}); - debugmsg ('info', "Starting Backup of VM $vmid ($vmtype)", $logfd, 1); - debugmsg ('info', "Backup started at " . strftime("%F %H:%M:%S", localtime())); + debugmsg('info', "Starting Backup of VM $vmid ($vmtype)", $logfd, 1); + debugmsg('info', "Backup started at " . strftime("%F %H:%M:%S", localtime())); - $plugin->set_logfd ($logfd); + $plugin->set_logfd($logfd); - # test is VM is running - my ($running, $status_text) = $plugin->vm_status ($vmid); + # test is VM is running + my ($running, $status_text) = $plugin->vm_status($vmid); - debugmsg ('info', "status = ${status_text}", $logfd); + debugmsg('info', "status = ${status_text}", $logfd); - # lock VM (prevent config changes) - $plugin->lock_vm ($vmid); + # lock VM (prevent config changes) + $plugin->lock_vm($vmid); - $cleanup->{unlock} = 1; + $cleanup->{unlock} = 1; - # prepare + # prepare - my $mode = $running ? $task->{mode} : 'stop'; + my $mode = $running ? $task->{mode} : 'stop'; - if ($mode eq 'snapshot') { - my %saved_task = %$task; - eval { $plugin->prepare ($task, $vmid, $mode); }; - if (my $err = $@) { - die $err if $err !~ m/^mode failure/; - debugmsg ('info', $err, $logfd); - debugmsg ('info', "trying 'suspend' mode instead", $logfd); - $mode = 'suspend'; # so prepare is called again below - %$task = %saved_task; - } - } + if ($mode eq 'snapshot') { + my %saved_task = %$task; + eval { $plugin->prepare($task, $vmid, $mode); }; + if (my $err = $@) { + die $err if $err !~ m/^mode failure/; + debugmsg('info', $err, $logfd); + debugmsg('info', "trying 'suspend' mode instead", $logfd); + $mode = 'suspend'; # so prepare is called again below + %$task = %saved_task; + } + } - $cleanup->{prepared} = 1; + $cleanup->{prepared} = 1; - $task->{mode} = $mode; + $task->{mode} = $mode; - debugmsg ('info', "backup mode: $mode", $logfd); - debugmsg ('info', "bandwidth limit: $opts->{bwlimit} KiB/s", $logfd) if $opts->{bwlimit}; - debugmsg ('info', "ionice priority: $opts->{ionice}", $logfd); + debugmsg('info', "backup mode: $mode", $logfd); + debugmsg('info', "bandwidth limit: $opts->{bwlimit} KiB/s", $logfd) if $opts->{bwlimit}; + debugmsg('info', "ionice priority: $opts->{ionice}", $logfd); - my $backup_provider_init = sub { - my $init_result = - $self->{'backup-provider'}->backup_init($vmid, $vmtype, $task->{backup_time}); - die "backup init failed: did not receive a valid result from the backup provider\n" - if !defined($init_result) || ref($init_result) ne 'HASH'; - my $archive_name = $init_result->{'archive-name'}; - die "backup init failed: did not receive an archive name from backup provider\n" - if !defined($archive_name) || length($archive_name) == 0; - die "backup init failed: illegal characters in archive name '$archive_name'\n" - if $archive_name !~ m!^(${PVE::Storage::SAFE_CHAR_CLASS_RE}|/|:)+$!; - $task->{target} = $archive_name; - }; + my $backup_provider_init = sub { + my $init_result = + $self->{'backup-provider'}->backup_init($vmid, $vmtype, $task->{backup_time}); + die "backup init failed: did not receive a valid result from the backup provider\n" + if !defined($init_result) || ref($init_result) ne 'HASH'; + my $archive_name = $init_result->{'archive-name'}; + die "backup init failed: did not receive an archive name from backup provider\n" + if !defined($archive_name) || length($archive_name) == 0; + die "backup init failed: illegal characters in archive name '$archive_name'\n" + if $archive_name !~ m!^(${PVE::Storage::SAFE_CHAR_CLASS_RE}|/|:)+$!; + $task->{target} = $archive_name; + }; - if ($mode eq 'stop') { - $plugin->prepare ($task, $vmid, $mode); + if ($mode eq 'stop') { + $plugin->prepare($task, $vmid, $mode); - $backup_provider_init->() if $self->{'backup-provider'}; - $self->run_hook_script ('backup-start', $task, $logfd); + $backup_provider_init->() if $self->{'backup-provider'}; + $self->run_hook_script('backup-start', $task, $logfd); - if ($running) { - debugmsg ('info', "stopping virtual guest", $logfd); - $task->{vmstoptime} = time(); - $self->run_hook_script ('pre-stop', $task, $logfd); - $plugin->stop_vm ($task, $vmid); - $cleanup->{restart} = 1; - } + if ($running) { + debugmsg('info', "stopping virtual guest", $logfd); + $task->{vmstoptime} = time(); + $self->run_hook_script('pre-stop', $task, $logfd); + $plugin->stop_vm($task, $vmid); + $cleanup->{restart} = 1; + } + } elsif ($mode eq 'suspend') { + $plugin->prepare($task, $vmid, $mode); - } elsif ($mode eq 'suspend') { - $plugin->prepare ($task, $vmid, $mode); + $backup_provider_init->() if $self->{'backup-provider'}; + $self->run_hook_script('backup-start', $task, $logfd); - $backup_provider_init->() if $self->{'backup-provider'}; - $self->run_hook_script ('backup-start', $task, $logfd); + if ($vmtype eq 'lxc') { + # pre-suspend rsync + $plugin->copy_data_phase1($task, $vmid); + } - if ($vmtype eq 'lxc') { - # pre-suspend rsync - $plugin->copy_data_phase1($task, $vmid); - } + debugmsg('info', "suspending guest", $logfd); + $task->{vmstoptime} = time(); + $self->run_hook_script('pre-stop', $task, $logfd); + $plugin->suspend_vm($task, $vmid); + $cleanup->{resume} = 1; - debugmsg ('info', "suspending guest", $logfd); - $task->{vmstoptime} = time (); - $self->run_hook_script ('pre-stop', $task, $logfd); - $plugin->suspend_vm ($task, $vmid); - $cleanup->{resume} = 1; + if ($vmtype eq 'lxc') { + # post-suspend rsync + $plugin->copy_data_phase2($task, $vmid); - if ($vmtype eq 'lxc') { - # post-suspend rsync - $plugin->copy_data_phase2($task, $vmid); + debugmsg('info', "resuming guest", $logfd); + $cleanup->{resume} = 0; + $self->run_hook_script('pre-restart', $task, $logfd); + $plugin->resume_vm($task, $vmid); + $self->run_hook_script('post-restart', $task, $logfd); + $log_vm_online_again->(); + } - debugmsg ('info', "resuming guest", $logfd); - $cleanup->{resume} = 0; - $self->run_hook_script('pre-restart', $task, $logfd); - $plugin->resume_vm($task, $vmid); - $self->run_hook_script('post-restart', $task, $logfd); - $log_vm_online_again->(); - } + } elsif ($mode eq 'snapshot') { + $backup_provider_init->() if $self->{'backup-provider'}; + $self->run_hook_script('backup-start', $task, $logfd); - } elsif ($mode eq 'snapshot') { - $backup_provider_init->() if $self->{'backup-provider'}; - $self->run_hook_script ('backup-start', $task, $logfd); + my $snapshot_count = $task->{snapshot_count} || 0; - my $snapshot_count = $task->{snapshot_count} || 0; + $self->run_hook_script('pre-stop', $task, $logfd); - $self->run_hook_script ('pre-stop', $task, $logfd); + if ($snapshot_count > 1) { + debugmsg('info', "suspend vm to make snapshot", $logfd); + $task->{vmstoptime} = time(); + $plugin->suspend_vm($task, $vmid); + $cleanup->{resume} = 1; + } - if ($snapshot_count > 1) { - debugmsg ('info', "suspend vm to make snapshot", $logfd); - $task->{vmstoptime} = time (); - $plugin->suspend_vm ($task, $vmid); - $cleanup->{resume} = 1; - } + $plugin->snapshot($task, $vmid); - $plugin->snapshot ($task, $vmid); + $self->run_hook_script('pre-restart', $task, $logfd); - $self->run_hook_script ('pre-restart', $task, $logfd); + if ($snapshot_count > 1) { + debugmsg('info', "resume vm", $logfd); + $cleanup->{resume} = 0; + $plugin->resume_vm($task, $vmid); + $log_vm_online_again->(); + } - if ($snapshot_count > 1) { - debugmsg ('info', "resume vm", $logfd); - $cleanup->{resume} = 0; - $plugin->resume_vm ($task, $vmid); - $log_vm_online_again->(); - } + $self->run_hook_script('post-restart', $task, $logfd); - $self->run_hook_script ('post-restart', $task, $logfd); + } else { + die "internal error - unknown mode '$mode'\n"; + } - } else { - die "internal error - unknown mode '$mode'\n"; - } + # assemble archive image + $plugin->assemble($task, $vmid); - # assemble archive image - $plugin->assemble ($task, $vmid); + # produce archive - # produce archive + if ($opts->{stdout}) { + debugmsg('info', "sending archive to stdout", $logfd); + $plugin->archive($task, $vmid, $task->{tmptar}, $comp); + $self->run_hook_script('backup-end', $task, $logfd); + return; + } - if ($opts->{stdout}) { - debugmsg ('info', "sending archive to stdout", $logfd); - $plugin->archive($task, $vmid, $task->{tmptar}, $comp); - $self->run_hook_script ('backup-end', $task, $logfd); - return; - } + my $archive_txt = 'vzdump'; + $archive_txt = 'Proxmox Backup Server' if $self->{opts}->{pbs}; + $archive_txt = $self->{'backup-provider'}->provider_name() + if $self->{'backup-provider'}; + debugmsg('info', "creating $archive_txt archive '$task->{target}'", $logfd); + $plugin->archive($task, $vmid, $task->{tmptar}, $comp); - my $archive_txt = 'vzdump'; - $archive_txt = 'Proxmox Backup Server' if $self->{opts}->{pbs}; - $archive_txt = $self->{'backup-provider'}->provider_name() if $self->{'backup-provider'}; - debugmsg('info', "creating $archive_txt archive '$task->{target}'", $logfd); - $plugin->archive($task, $vmid, $task->{tmptar}, $comp); + if ($self->{'backup-provider'} || $self->{opts}->{pbs}) { + # size is added to task struct in guest vzdump plugins + } else { + rename($task->{tmptar}, $task->{target}) + || die "unable to rename '$task->{tmptar}' to '$task->{target}'\n"; - if ($self->{'backup-provider'} || $self->{opts}->{pbs}) { - # size is added to task struct in guest vzdump plugins - } else { - rename ($task->{tmptar}, $task->{target}) || - die "unable to rename '$task->{tmptar}' to '$task->{target}'\n"; + # determine size + $task->{size} = (-s $task->{target}) || 0; + my $cs = format_size($task->{size}); + debugmsg('info', "archive file size: $cs", $logfd); + } - # determine size - $task->{size} = (-s $task->{target}) || 0; - my $cs = format_size ($task->{size}); - debugmsg ('info', "archive file size: $cs", $logfd); - } + # Mark as protected before pruning. + if (my $storeid = $opts->{storage}) { + my $volname = + $opts->{pbs} || $self->{'backup-provider'} + ? $task->{target} + : basename($task->{target}); + my $volid = "${storeid}:backup/${volname}"; - # Mark as protected before pruning. - if (my $storeid = $opts->{storage}) { - my $volname = $opts->{pbs} || $self->{'backup-provider'} ? $task->{target} - : basename($task->{target}); - my $volid = "${storeid}:backup/${volname}"; + if ($opts->{'notes-template'} && $opts->{'notes-template'} ne '') { + debugmsg('info', "adding notes to backup", $logfd); + my $notes = eval { $generate_notes->($opts->{'notes-template'}, $task); }; + if (my $err = $@) { + debugmsg('warn', "unable to add notes - $err", $logfd); + } else { + eval { + PVE::Storage::update_volume_attribute($cfg, $volid, 'notes', $notes); + }; + debugmsg('warn', "unable to add notes - $@", $logfd) if $@; + } + } - if ($opts->{'notes-template'} && $opts->{'notes-template'} ne '') { - debugmsg('info', "adding notes to backup", $logfd); - my $notes = eval { $generate_notes->($opts->{'notes-template'}, $task); }; - if (my $err = $@) { - debugmsg('warn', "unable to add notes - $err", $logfd); - } else { - eval { PVE::Storage::update_volume_attribute($cfg, $volid, 'notes', $notes) }; - debugmsg('warn', "unable to add notes - $@", $logfd) if $@; - } - } + if ($opts->{protected}) { + debugmsg('info', "marking backup as protected", $logfd); + eval { PVE::Storage::update_volume_attribute($cfg, $volid, 'protected', 1) }; + die "unable to set protected flag - $@\n" if $@; + } + } - if ($opts->{protected}) { - debugmsg('info', "marking backup as protected", $logfd); - eval { PVE::Storage::update_volume_attribute($cfg, $volid, 'protected', 1) }; - die "unable to set protected flag - $@\n" if $@; - } - } + if ($opts->{remove}) { + my $keepstr = + join(', ', map { "$_=$prune_options->{$_}" } sort keys %$prune_options); + debugmsg('info', "prune older backups with retention: $keepstr", $logfd); + my $pruned = 0; + if (!defined($opts->{storage})) { + my $bklist = get_backup_file_list($opts->{dumpdir}, $bkname); - if ($opts->{remove}) { - my $keepstr = join(', ', map { "$_=$prune_options->{$_}" } sort keys %$prune_options); - debugmsg ('info', "prune older backups with retention: $keepstr", $logfd); - my $pruned = 0; - if (!defined($opts->{storage})) { - my $bklist = get_backup_file_list($opts->{dumpdir}, $bkname); + PVE::Storage::prune_mark_backup_group($bklist, $prune_options); - PVE::Storage::prune_mark_backup_group($bklist, $prune_options); + foreach my $prune_entry (@{$bklist}) { + next if $prune_entry->{mark} ne 'remove'; + $pruned++; + my $archive_path = $prune_entry->{path}; + debugmsg('info', "delete old backup '$archive_path'", $logfd); + PVE::Storage::archive_remove($archive_path); + } + } else { + my $pruned_list = PVE::Storage::prune_backups( + $cfg, + $opts->{storage}, + $prune_options, + $vmid, + $vmtype, + 0, + sub { debugmsg($_[0], $_[1], $logfd) }, + ); + $pruned = scalar(grep { $_->{mark} eq 'remove' } $pruned_list->@*); + } + my $log_pruned_extra = $pruned > 0 ? " not covered by keep-retention policy" : ""; + debugmsg('info', "pruned $pruned backup(s)${log_pruned_extra}", $logfd); + } - foreach my $prune_entry (@{$bklist}) { - next if $prune_entry->{mark} ne 'remove'; - $pruned++; - my $archive_path = $prune_entry->{path}; - debugmsg ('info', "delete old backup '$archive_path'", $logfd); - PVE::Storage::archive_remove($archive_path); - } - } else { - my $pruned_list = PVE::Storage::prune_backups( - $cfg, - $opts->{storage}, - $prune_options, - $vmid, - $vmtype, - 0, - sub { debugmsg($_[0], $_[1], $logfd) }, - ); - $pruned = scalar(grep { $_->{mark} eq 'remove' } $pruned_list->@*); - } - my $log_pruned_extra = $pruned > 0 ? " not covered by keep-retention policy" : ""; - debugmsg ('info', "pruned $pruned backup(s)${log_pruned_extra}", $logfd); - } - - if ($self->{'backup-provider'}) { - my $cleanup_result = $self->{'backup-provider'}->backup_cleanup($vmid, $vmtype, 1, {}); - $task->{size} = $cleanup_result->{stats}->{'archive-size'}; - } - $self->run_hook_script ('backup-end', $task, $logfd); + if ($self->{'backup-provider'}) { + my $cleanup_result = + $self->{'backup-provider'}->backup_cleanup($vmid, $vmtype, 1, {}); + $task->{size} = $cleanup_result->{stats}->{'archive-size'}; + } + $self->run_hook_script('backup-end', $task, $logfd); }; my $err = $@; if ($plugin) { - # clean-up + # clean-up - if ($cleanup->{unlock}) { - eval { $plugin->unlock_vm ($vmid); }; - warn $@ if $@; - } + if ($cleanup->{unlock}) { + eval { $plugin->unlock_vm($vmid); }; + warn $@ if $@; + } - if ($cleanup->{prepared}) { - # only call cleanup when necessary (when prepare was executed) - eval { $plugin->cleanup ($task, $vmid) }; - warn $@ if $@; - } + if ($cleanup->{prepared}) { + # only call cleanup when necessary (when prepare was executed) + eval { $plugin->cleanup($task, $vmid) }; + warn $@ if $@; + } - eval { $plugin->set_logfd (undef); }; - warn $@ if $@; + eval { $plugin->set_logfd(undef); }; + warn $@ if $@; - if ($cleanup->{resume} || $cleanup->{restart}) { - eval { - $self->run_hook_script ('pre-restart', $task, $logfd); - if ($cleanup->{resume}) { - debugmsg ('info', "resume vm", $logfd); - $plugin->resume_vm ($task, $vmid); - } else { - my $running = $plugin->vm_status($vmid); - if (!$running) { - debugmsg ('info', "restarting vm", $logfd); - $plugin->start_vm ($task, $vmid); - } - } - $self->run_hook_script ('post-restart', $task, $logfd); - }; - my $err = $@; - if ($err) { - warn $err; - } else { - $log_vm_online_again->(); - } - } + if ($cleanup->{resume} || $cleanup->{restart}) { + eval { + $self->run_hook_script('pre-restart', $task, $logfd); + if ($cleanup->{resume}) { + debugmsg('info', "resume vm", $logfd); + $plugin->resume_vm($task, $vmid); + } else { + my $running = $plugin->vm_status($vmid); + if (!$running) { + debugmsg('info', "restarting vm", $logfd); + $plugin->start_vm($task, $vmid); + } + } + $self->run_hook_script('post-restart', $task, $logfd); + }; + my $err = $@; + if ($err) { + warn $err; + } else { + $log_vm_online_again->(); + } + } } eval { unlink $task->{tmptar} if $task->{tmptar} && -f $task->{tmptar}; }; @@ -1332,57 +1344,57 @@ sub exec_backup_task { eval { rmtree $task->{tmpdir} if $task->{tmpdir} && -d $task->{tmpdir}; }; warn $@ if $@; - my $delay = $task->{backuptime} = time () - $vmstarttime; + my $delay = $task->{backuptime} = time() - $vmstarttime; if ($err) { - $task->{state} = 'err'; - $task->{msg} = $err; - debugmsg ('err', "Backup of VM $vmid failed - $err", $logfd, 1); - debugmsg ('info', "Failed at " . strftime("%F %H:%M:%S", localtime())); + $task->{state} = 'err'; + $task->{msg} = $err; + debugmsg('err', "Backup of VM $vmid failed - $err", $logfd, 1); + debugmsg('info', "Failed at " . strftime("%F %H:%M:%S", localtime())); - if ($self->{'backup-provider'}) { - eval { - $self->{'backup-provider'}->backup_cleanup( - $vmid, $task->{vmtype}, 0, { error => $err }); - }; - debugmsg('warn', "backup cleanup for external provider failed - $@") if $@; - } + if ($self->{'backup-provider'}) { + eval { + $self->{'backup-provider'} + ->backup_cleanup($vmid, $task->{vmtype}, 0, { error => $err }); + }; + debugmsg('warn', "backup cleanup for external provider failed - $@") if $@; + } - eval { $self->run_hook_script ('backup-abort', $task, $logfd); }; - debugmsg('warn', $@) if $@; # message already contains command with phase name + eval { $self->run_hook_script('backup-abort', $task, $logfd); }; + debugmsg('warn', $@) if $@; # message already contains command with phase name } else { - $task->{state} = 'ok'; - my $tstr = format_time ($delay); - debugmsg ('info', "Finished Backup of VM $vmid ($tstr)", $logfd, 1); - debugmsg ('info', "Backup finished at " . strftime("%F %H:%M:%S", localtime())); + $task->{state} = 'ok'; + my $tstr = format_time($delay); + debugmsg('info', "Finished Backup of VM $vmid ($tstr)", $logfd, 1); + debugmsg('info', "Backup finished at " . strftime("%F %H:%M:%S", localtime())); } - close ($logfd) if $logfd; + close($logfd) if $logfd; if ($task->{tmplog}) { - if ($self->{opts}->{pbs}) { - if ($task->{state} eq 'ok') { - eval { - PVE::Storage::PBSPlugin::run_raw_client_cmd( - $opts->{scfg}, - $opts->{storage}, - 'upload-log', - [ $pbs_snapshot_name, $task->{tmplog} ], - errmsg => "uploading backup task log failed", - outfunc => sub {}, - ); - }; - debugmsg('warn', "$@") if $@; # $@ contains already error prefix - } - } elsif ($self->{'backup-provider'}) { - $self->{'backup-provider'}->backup_handle_log_file($vmid, $task->{tmplog}); - } elsif ($task->{logfile}) { - system {'cp'} 'cp', $task->{tmplog}, $task->{logfile}; - } + if ($self->{opts}->{pbs}) { + if ($task->{state} eq 'ok') { + eval { + PVE::Storage::PBSPlugin::run_raw_client_cmd( + $opts->{scfg}, + $opts->{storage}, + 'upload-log', + [$pbs_snapshot_name, $task->{tmplog}], + errmsg => "uploading backup task log failed", + outfunc => sub { }, + ); + }; + debugmsg('warn', "$@") if $@; # $@ contains already error prefix + } + } elsif ($self->{'backup-provider'}) { + $self->{'backup-provider'}->backup_handle_log_file($vmid, $task->{tmplog}); + } elsif ($task->{logfile}) { + system {'cp'} 'cp', $task->{tmplog}, $task->{logfile}; + } } - eval { $self->run_hook_script ('log-end', $task); }; + eval { $self->run_hook_script('log-end', $task); }; debugmsg('warn', $@) if $@; # message already contains command with phase name die $err if $err && $err =~ m/^interrupted by signal$/; @@ -1393,36 +1405,37 @@ sub exec_backup { my $opts = $self->{opts}; - debugmsg ('info', "starting new backup job: $self->{cmdline}", undef, 1); + debugmsg('info', "starting new backup job: $self->{cmdline}", undef, 1); - if (scalar(@{$self->{skiplist}})) { - my $skip_string = join(', ', sort { $a <=> $b } @{$self->{skiplist}}); - debugmsg ('info', "skip external VMs: $skip_string"); + if (scalar(@{ $self->{skiplist} })) { + my $skip_string = join(', ', sort { $a <=> $b } @{ $self->{skiplist} }); + debugmsg('info', "skip external VMs: $skip_string"); } my $tasklist = []; - my $vzdump_plugins = {}; - foreach my $plugin (@{$self->{plugins}}) { - my $type = $plugin->type(); - next if exists $vzdump_plugins->{$type}; - $vzdump_plugins->{$type} = $plugin; + my $vzdump_plugins = {}; + foreach my $plugin (@{ $self->{plugins} }) { + my $type = $plugin->type(); + next if exists $vzdump_plugins->{$type}; + $vzdump_plugins->{$type} = $plugin; } my $vmlist = PVE::Cluster::get_vmlist(); - my $vmids = [ sort { $a <=> $b } @{$opts->{vmids}} ]; + my $vmids = [sort { $a <=> $b } @{ $opts->{vmids} }]; foreach my $vmid (@{$vmids}) { - my $plugin; - if (defined($vmlist->{ids}->{$vmid})) { - my $guest_type = $vmlist->{ids}->{$vmid}->{type}; - $plugin = $vzdump_plugins->{$guest_type}; - next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Backup' ], $opts->{all}); - } - push @$tasklist, { - mode => $opts->{mode}, - plugin => $plugin, - state => 'todo', - vmid => $vmid, - }; + my $plugin; + if (defined($vmlist->{ids}->{$vmid})) { + my $guest_type = $vmlist->{ids}->{$vmid}->{type}; + $plugin = $vzdump_plugins->{$guest_type}; + next if !$rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], $opts->{all}); + } + push @$tasklist, + { + mode => $opts->{mode}, + plugin => $plugin, + state => 'todo', + vmid => $vmid, + }; } # Use in-memory files for the outer hook logs to pass them to sendmail. @@ -1435,34 +1448,34 @@ sub exec_backup { my $errcount = 0; eval { - $self->{'backup-provider'}->job_init($starttime) if $self->{'backup-provider'}; - $self->run_hook_script ('job-start', undef, $job_start_fd); + $self->{'backup-provider'}->job_init($starttime) if $self->{'backup-provider'}; + $self->run_hook_script('job-start', undef, $job_start_fd); - foreach my $task (@$tasklist) { - $self->exec_backup_task ($task); - $errcount += 1 if $task->{state} ne 'ok'; - } + foreach my $task (@$tasklist) { + $self->exec_backup_task($task); + $errcount += 1 if $task->{state} ne 'ok'; + } - $self->{'backup-provider'}->job_cleanup() if $self->{'backup-provider'}; - $self->run_hook_script ('job-end', undef, $job_end_fd); + $self->{'backup-provider'}->job_cleanup() if $self->{'backup-provider'}; + $self->run_hook_script('job-end', undef, $job_end_fd); }; my $err = $@; if ($err) { - if ($self->{'backup-provider'}) { - eval { $self->{'backup-provider'}->job_cleanup(); }; - $err .= "job cleanup for external provider failed - $@" if $@; - } + if ($self->{'backup-provider'}) { + eval { $self->{'backup-provider'}->job_cleanup(); }; + $err .= "job cleanup for external provider failed - $@" if $@; + } - eval { $self->run_hook_script ('job-abort', undef, $job_end_fd); }; - $err .= $@ if $@; - debugmsg ('err', "Backup job failed - $err", undef, 1); + eval { $self->run_hook_script('job-abort', undef, $job_end_fd); }; + $err .= $@ if $@; + debugmsg('err', "Backup job failed - $err", undef, 1); } else { - if ($errcount) { - debugmsg ('info', "Backup job finished with errors", undef, 1); - } else { - debugmsg ('info', "Backup job finished successfully", undef, 1); - } + if ($errcount) { + debugmsg('info', "Backup job finished with errors", undef, 1); + } else { + debugmsg('info', "Backup job finished successfully", undef, 1); + } } close $job_start_fd; @@ -1471,19 +1484,19 @@ sub exec_backup { my $totaltime = time() - $starttime; eval { - # otherwise $self->send_notification() will interpret it as multiple problems - my $chomped_err = $err; - chomp($chomped_err) if $chomped_err; + # otherwise $self->send_notification() will interpret it as multiple problems + my $chomped_err = $err; + chomp($chomped_err) if $chomped_err; - $self->send_notification( - $tasklist, - $totaltime, - $chomped_err, - $self->{job_init_log} . $job_start_log, - $job_end_log, - ); + $self->send_notification( + $tasklist, + $totaltime, + $chomped_err, + $self->{job_init_log} . $job_start_log, + $job_end_log, + ); }; - debugmsg ('err', $@) if $@; + debugmsg('err', $@) if $@; die $err if $err; @@ -1492,7 +1505,6 @@ sub exec_backup { unlink $pidfile; } - sub option_exists { my $key = shift; return defined($confdesc->{$key}); @@ -1507,18 +1519,18 @@ sub parse_mailto_exclude_path { # exclude-path list need to be 0 separated or be an array if (defined($param->{'exclude-path'})) { - my $expaths; - if (ref($param->{'exclude-path'}) eq 'ARRAY') { - $expaths = $param->{'exclude-path'}; - } else { - $expaths = [split(/\0/, $param->{'exclude-path'} || '')]; - } - $param->{'exclude-path'} = $expaths; + my $expaths; + if (ref($param->{'exclude-path'}) eq 'ARRAY') { + $expaths = $param->{'exclude-path'}; + } else { + $expaths = [split(/\0/, $param->{'exclude-path'} || '')]; + } + $param->{'exclude-path'} = $expaths; } if (defined($param->{mailto})) { - my @mailto = PVE::Tools::split_list(extract_param($param, 'mailto')); - $param->{mailto} = [ @mailto ]; + my @mailto = PVE::Tools::split_list(extract_param($param, 'mailto')); + $param->{mailto} = [@mailto]; } return; @@ -1527,54 +1539,56 @@ sub parse_mailto_exclude_path { sub verify_vzdump_parameters { my ($param, $check_missing) = @_; - raise_param_exc({ all => "option conflicts with option 'vmid'"}) - if $param->{all} && $param->{vmid}; + raise_param_exc({ all => "option conflicts with option 'vmid'" }) + if $param->{all} && $param->{vmid}; - raise_param_exc({ exclude => "option conflicts with option 'vmid'"}) - if $param->{exclude} && $param->{vmid}; + raise_param_exc({ exclude => "option conflicts with option 'vmid'" }) + if $param->{exclude} && $param->{vmid}; - raise_param_exc({ pool => "option conflicts with option 'vmid'"}) - if $param->{pool} && $param->{vmid}; + raise_param_exc({ pool => "option conflicts with option 'vmid'" }) + if $param->{pool} && $param->{vmid}; - raise_param_exc({ 'prune-backups' => "option conflicts with option 'maxfiles'"}) - if defined($param->{'prune-backups'}) && defined($param->{maxfiles}); + raise_param_exc({ 'prune-backups' => "option conflicts with option 'maxfiles'" }) + if defined($param->{'prune-backups'}) && defined($param->{maxfiles}); $parse_prune_backups_maxfiles->($param, 'CLI parameters'); parse_fleecing($param); parse_performance($param); if (my $template = $param->{'notes-template'}) { - eval { $verify_notes_template->($template); }; - raise_param_exc({'notes-template' => $@}) if $@; + eval { $verify_notes_template->($template); }; + raise_param_exc({ 'notes-template' => $@ }) if $@; } $param->{all} = 1 if (defined($param->{exclude}) && !$param->{pool}); return if !$check_missing; - raise_param_exc({ vmid => "property is missing"}) - if !($param->{all} || $param->{stop} || $param->{pool}) && !$param->{vmid}; + raise_param_exc({ vmid => "property is missing" }) + if !($param->{all} || $param->{stop} || $param->{pool}) && !$param->{vmid}; } sub stop_running_backups { - my($self) = @_; + my ($self) = @_; my $upid = PVE::Tools::file_read_firstline($pidfile); return if !$upid; my $task = PVE::Tools::upid_decode($upid); - if (PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart}) && - PVE::ProcFSTools::read_proc_starttime($task->{pid}) == $task->{pstart}) { - kill(15, $task->{pid}); - # wait max 15 seconds to shut down (else, do nothing for now) - my $i; - for ($i = 15; $i > 0; $i--) { - last if !PVE::ProcFSTools::check_process_running(($task->{pid}, $task->{pstart})); - sleep (1); - } - die "stopping backup process $task->{pid} failed\n" if $i == 0; + if ( + PVE::ProcFSTools::check_process_running($task->{pid}, $task->{pstart}) + && PVE::ProcFSTools::read_proc_starttime($task->{pid}) == $task->{pstart} + ) { + kill(15, $task->{pid}); + # wait max 15 seconds to shut down (else, do nothing for now) + my $i; + for ($i = 15; $i > 0; $i--) { + last if !PVE::ProcFSTools::check_process_running(($task->{pid}, $task->{pstart})); + sleep(1); + } + die "stopping backup process $task->{pid} failed\n" if $i == 0; } } @@ -1587,32 +1601,32 @@ sub get_included_guests { my $vmlist = PVE::Cluster::get_vmlist(); if ($job->{pool}) { - $vmids = PVE::API2Tools::get_resource_pool_guest_members($job->{pool}); + $vmids = PVE::API2Tools::get_resource_pool_guest_members($job->{pool}); } elsif ($job->{vmid}) { - $vmids = [ split_list($job->{vmid}) ]; + $vmids = [split_list($job->{vmid})]; } elsif ($job->{all}) { - # all or exclude - my $exclude = check_vmids(split_list($job->{exclude})); - my $excludehash = { map { $_ => 1 } @$exclude }; + # all or exclude + my $exclude = check_vmids(split_list($job->{exclude})); + my $excludehash = { map { $_ => 1 } @$exclude }; - for my $id (keys %{$vmlist->{ids}}) { - next if $excludehash->{$id}; - push @$vmids, $id; - } + for my $id (keys %{ $vmlist->{ids} }) { + next if $excludehash->{$id}; + push @$vmids, $id; + } } else { - return $vmids_per_node; + return $vmids_per_node; } $vmids = check_vmids(@$vmids); for my $vmid (@$vmids) { - if (defined($vmlist->{ids}->{$vmid})) { - my $node = $vmlist->{ids}->{$vmid}->{node}; - next if (defined $job->{node} && $job->{node} ne $node); + if (defined($vmlist->{ids}->{$vmid})) { + my $node = $vmlist->{ids}->{$vmid}->{node}; + next if (defined $job->{node} && $job->{node} ne $node); - push @{$vmids_per_node->{$node}}, $vmid; - } else { - push @{$vmids_per_node->{''}}, $vmid; - } + push @{ $vmids_per_node->{$node} }, $vmid; + } else { + push @{ $vmids_per_node->{''} }, $vmid; + } } return $vmids_per_node; diff --git a/aplinfo/apltest.pl b/aplinfo/apltest.pl index a4d1e153..1f4847c0 100755 --- a/aplinfo/apltest.pl +++ b/aplinfo/apltest.pl @@ -10,7 +10,7 @@ my $pkglist = PVE::APLInfo::load_data(); my $err = 0; -foreach my $k (keys %{$pkglist->{'all'}}) { +foreach my $k (keys %{ $pkglist->{'all'} }) { next if $k eq 'pve-web-news'; my $res = $pkglist->{all}->{$k}; @@ -18,11 +18,11 @@ foreach my $k (keys %{$pkglist->{'all'}}) { my $template = "$res->{package}_$res->{version}_$res->{architecture}.tar"; if ($k !~ m/^($res->{os}-)?\Q$template\E\.(gz|xz|zst)$/) { - print "ERROR: $k != $template\n"; - #print Dumper($res) . "\n"; - $err = 1; + print "ERROR: $k != $template\n"; + #print Dumper($res) . "\n"; + $err = 1; } } -$err ? exit (-11) : exit (0); +$err ? exit(-11) : exit(0); diff --git a/configs/country.pl b/configs/country.pl index 119a5c70..bf74e2c9 100755 --- a/configs/country.pl +++ b/configs/country.pl @@ -13,13 +13,13 @@ use PVE::Tools; my $country = {}; my $line; -open (TMP, ")) { +open(TMP, ")) { if ($line =~ m/^([A-Z][A-Z])\s+(.*\S)\s*$/) { - $country->{lc($1)} = $2; + $country->{ lc($1) } = $2; } } -close (TMP); +close(TMP); # we need mappings for X11, console, and kvm vnc @@ -27,52 +27,51 @@ close (TMP); my $keymaps = PVE::Tools::kvmkeymaps(); foreach my $km (sort keys %$keymaps) { - my ($desc, $kvm, $console, $x11, $x11var) = @{$keymaps->{$km}}; + my ($desc, $kvm, $console, $x11, $x11var) = @{ $keymaps->{$km} }; - if ($km =~m/^([a-z][a-z])-([a-z][a-z])$/i) { - defined ($country->{$2}) || die "undefined country code '$2'"; + if ($km =~ m/^([a-z][a-z])-([a-z][a-z])$/i) { + defined($country->{$2}) || die "undefined country code '$2'"; } else { - defined ($country->{$km}) || die "undefined country code '$km'"; + defined($country->{$km}) || die "undefined country code '$km'"; } - $x11var = '' if !defined ($x11var); + $x11var = '' if !defined($x11var); print "map:$km:$desc:$kvm:$console:$x11:$x11var:\n"; } my $defmap = { - 'us' => 'en-us', - 'be' => 'fr-be', - 'br' => 'pt-br', - 'ca' => 'en-us', - 'dk' => 'dk', - 'nl' => 'en-us', # most Dutch people us US layout - 'fi' => 'fi', - 'fr' => 'fr', - 'de' => 'de', - 'at' => 'de', - 'hu' => 'hu', - 'is' => 'is', - 'it' => 'it', - 'va' => 'it', - 'jp' => 'jp', - 'lt' => 'lt', - 'mk' => 'mk', - 'no' => 'no', - 'pl' => 'pl', - 'pt' => 'pt', - 'si' => 'si', - 'es' => 'es', - 'gi' => 'es', - 'ch' => 'de-ch', - 'gb' => 'en-gb', - 'lu' => 'fr-ch', - 'li' => 'de-ch', + 'us' => 'en-us', + 'be' => 'fr-be', + 'br' => 'pt-br', + 'ca' => 'en-us', + 'dk' => 'dk', + 'nl' => 'en-us', # most Dutch people us US layout + 'fi' => 'fi', + 'fr' => 'fr', + 'de' => 'de', + 'at' => 'de', + 'hu' => 'hu', + 'is' => 'is', + 'it' => 'it', + 'va' => 'it', + 'jp' => 'jp', + 'lt' => 'lt', + 'mk' => 'mk', + 'no' => 'no', + 'pl' => 'pl', + 'pt' => 'pt', + 'si' => 'si', + 'es' => 'es', + 'gi' => 'es', + 'ch' => 'de-ch', + 'gb' => 'en-gb', + 'lu' => 'fr-ch', + 'li' => 'de-ch', }; - my $mirrors = PVE::Tools::debmirrors(); foreach my $cc (keys %$mirrors) { - die "undefined country code '$cc'" if !defined ($country->{$cc}); + die "undefined country code '$cc'" if !defined($country->{$cc}); } foreach my $cc (sort keys %$country) { diff --git a/test/OSD_test.pl b/test/OSD_test.pl index 17409770..3dfec9da 100755 --- a/test/OSD_test.pl +++ b/test/OSD_test.pl @@ -14,67 +14,64 @@ use Data::Dumper; # NOTE: not exhausive, reduced to actually required fields! my $tree = { nodes => [ - { - id => -3, - name => 'pveA', - children => [ 0,1,2,3 ], - type => 'host', - }, - { - id => -5, - name => 'pveB', - children => [ 4,5,6,7 ], - type => 'host', - }, - { - id => -7, - name => 'pveC', - children => [ 8,9,10,11 ], - type => 'host', - }, + { + id => -3, + name => 'pveA', + children => [0, 1, 2, 3], + type => 'host', + }, + { + id => -5, + name => 'pveB', + children => [4, 5, 6, 7], + type => 'host', + }, + { + id => -7, + name => 'pveC', + children => [8, 9, 10, 11], + type => 'host', + }, ], }; - # Check if all the grep and casts are correct -my @belong_to_B = ( 4,5 ); -my @not_belong_to_B = ( -1,1,10,15 ); +my @belong_to_B = (4, 5); +my @not_belong_to_B = (-1, 1, 10, 15); foreach (@belong_to_B) { - is ( - PVE::API2::Ceph::OSD::osd_belongs_to_node($tree, 'pveB', $_), - 1, - "OSD $_ belongs to node pveB", + is( + PVE::API2::Ceph::OSD::osd_belongs_to_node($tree, 'pveB', $_), + 1, + "OSD $_ belongs to node pveB", ); } foreach (@not_belong_to_B) { - is ( - PVE::API2::Ceph::OSD::osd_belongs_to_node($tree, 'pveB', $_), - 0, - "OSD $_ does not belong to node pveB", + is( + PVE::API2::Ceph::OSD::osd_belongs_to_node($tree, 'pveB', $_), + 0, + "OSD $_ does not belong to node pveB", ); } - my $double_nodes_tree = { nodes => [ - { - name => 'pveA', - type => 'host', - }, - { - name => 'pveA', - type => 'host', - } - ] + { + name => 'pveA', + type => 'host', + }, + { + name => 'pveA', + type => 'host', + }, + ], }; eval { PVE::API2::Ceph::OSD::osd_belongs_to_node($double_nodes_tree, 'pveA') }; like($@, qr/duplicate host name found/, "Die if node occurs too often"); -is ( +is( PVE::API2::Ceph::OSD::osd_belongs_to_node(undef), 0, "Early-return false if there's no/empty node tree", ); - done_testing(@belong_to_B + @not_belong_to_B + 2); diff --git a/test/ReplicationTestEnv.pm b/test/ReplicationTestEnv.pm index 883bebca..6bb4c278 100755 --- a/test/ReplicationTestEnv.pm +++ b/test/ReplicationTestEnv.pm @@ -35,9 +35,9 @@ our $mocked_ct_configs = {}; my $mocked_get_members = sub { return { - node1 => { online => 1 }, - node2 => { online => 1 }, - node3 => { online => 1 }, + node1 => { online => 1 }, + node2 => { online => 1 }, + node3 => { online => 1 }, }; }; @@ -45,18 +45,18 @@ my $mocked_vmlist = sub { my $res = {}; foreach my $id (keys %$mocked_ct_configs) { - my $d = $mocked_ct_configs->{$id}; - $res->{$id} = { 'type' => 'lxc', 'node' => $d->{node}, 'version' => 1 }; + my $d = $mocked_ct_configs->{$id}; + $res->{$id} = { 'type' => 'lxc', 'node' => $d->{node}, 'version' => 1 }; } foreach my $id (keys %$mocked_vm_configs) { - my $d = $mocked_vm_configs->{$id}; - $res->{$id} = { 'type' => 'qemu', 'node' => $d->{node}, 'version' => 1 }; + my $d = $mocked_vm_configs->{$id}; + $res->{$id} = { 'type' => 'qemu', 'node' => $d->{node}, 'version' => 1 }; } return { 'ids' => $res }; }; -my $mocked_get_ssh_info = sub { +my $mocked_get_ssh_info = sub { my ($node, $network_cidr) = @_; return { node => $node }; @@ -129,24 +129,24 @@ my $mocked_replication_config_new = sub { my $mocked_storage_config = { ids => { - local => { - type => 'dir', - shared => 0, - content => { - 'iso' => 1, - 'backup' => 1, - }, - path => "/var/lib/vz", - }, - 'local-zfs' => { - type => 'zfspool', - pool => 'nonexistent-testpool', - shared => 0, - content => { - 'images' => 1, - 'rootdir' => 1 - }, - }, + local => { + type => 'dir', + shared => 0, + content => { + 'iso' => 1, + 'backup' => 1, + }, + path => "/var/lib/vz", + }, + 'local-zfs' => { + type => 'zfspool', + pool => 'nonexistent-testpool', + shared => 0, + content => { + 'images' => 1, + 'rootdir' => 1, + }, + }, }, }; @@ -160,8 +160,8 @@ sub generate_snapshot_info { $timestamp_counter++; return { - id => $timestamp_counter, - timestamp => $timestamp_counter, + id => $timestamp_counter, + timestamp => $timestamp_counter, }; } @@ -169,8 +169,8 @@ sub register_mocked_volid { my ($volid, $snapname) = @_; my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid); - my $scfg = $mocked_storage_config->{ids}->{$storeid} || - die "no such storage '$storeid'\n"; + my $scfg = $mocked_storage_config->{ids}->{$storeid} + || die "no such storage '$storeid'\n"; my $d = $mocked_storage_content->{$storeid}->{$volname} //= {}; @@ -261,9 +261,9 @@ sub setup { $pve_storage_module->mock(volume_snapshot_info => $mocked_volume_snapshot_info); $pve_replication_config_module->mock( - new => $mocked_replication_config_new, - lock => sub { $mocked_cfs_lock_file->('replication.cfg', undef, $_[0]); }, - write => sub { $mocked_cfs_write_file->('replication.cfg', $_[0]); }, + new => $mocked_replication_config_new, + lock => sub { $mocked_cfs_lock_file->('replication.cfg', undef, $_[0]); }, + write => sub { $mocked_cfs_write_file->('replication.cfg', $_[0]); }, ); $pve_qemuserver_module->mock(check_running => sub { return 0; }); $pve_qemuconfig_module->mock(load_config => $mocked_qemu_load_conf); @@ -271,20 +271,20 @@ sub setup { $pve_lxc_config_module->mock(load_config => $mocked_lxc_load_conf); $pve_sshinfo_module->mock( - get_ssh_info => $mocked_get_ssh_info, - ssh_info_to_command => $mocked_ssh_info_to_command, + get_ssh_info => $mocked_get_ssh_info, + ssh_info_to_command => $mocked_ssh_info_to_command, ); $pve_cluster_module->mock( - get_vmlist => sub { return $mocked_vmlist->(); }, - get_members => $mocked_get_members, - cfs_update => sub {}, - cfs_lock_file => $mocked_cfs_lock_file, - cfs_write_file => $mocked_cfs_write_file, - cfs_read_file => $mocked_cfs_read_file, + get_vmlist => sub { return $mocked_vmlist->(); }, + get_members => $mocked_get_members, + cfs_update => sub { }, + cfs_lock_file => $mocked_cfs_lock_file, + cfs_write_file => $mocked_cfs_write_file, + cfs_read_file => $mocked_cfs_read_file, ); $pve_inotify_module->mock('nodename' => sub { return $mocked_nodename; }); -}; +} # code to generate/conpare test logs @@ -295,19 +295,19 @@ sub openlog { my ($filename) = @_; if (!$filename) { - # compute from $0 - $filename = basename($0); - if ($filename =~ m/^(\S+)\.pl$/) { - $filename = "$1.log"; - } else { - die "unable to compute log name for $0"; - } + # compute from $0 + $filename = basename($0); + if ($filename =~ m/^(\S+)\.pl$/) { + $filename = "$1.log"; + } else { + die "unable to compute log name for $0"; + } } die "log already open" if defined($logname); - open (my $fh, ">", "$filename.tmp") || - die "unable to open log - $!"; + open(my $fh, ">", "$filename.tmp") + || die "unable to open log - $!"; $logname = $filename; $logfh = $fh; @@ -318,15 +318,15 @@ sub commit_log { close($logfh); if (-f $logname) { - my $diff = `diff -u '$logname' '$logname.tmp'`; - if ($diff) { - warn "got unexpected output\n"; - print "# diff -u '$logname' '$logname.tmp'\n"; - print $diff; - exit(-1); - } + my $diff = `diff -u '$logname' '$logname.tmp'`; + if ($diff) { + warn "got unexpected output\n"; + print "# diff -u '$logname' '$logname.tmp'\n"; + print $diff; + exit(-1); + } } else { - rename("$logname.tmp", $logname) || die "rename log failed - $!"; + rename("$logname.tmp", $logname) || die "rename log failed - $!"; } } @@ -339,18 +339,18 @@ sub track_jobs { $mocked_log_time = $ctime; my $logmsg = sub { - my ($msg) = @_; + my ($msg) = @_; - print "$msg\n"; - print $logfh "$msg\n"; + print "$msg\n"; + print $logfh "$msg\n"; }; if (!$status) { - $status = PVE::ReplicationState::job_status(); - foreach my $jobid (sort keys %$status) { - my $jobcfg = $status->{$jobid}; - $logmsg->("$ctime $jobid: new job next_sync => $jobcfg->{next_sync}"); - } + $status = PVE::ReplicationState::job_status(); + foreach my $jobid (sort keys %$status) { + my $jobcfg = $status->{$jobid}; + $logmsg->("$ctime $jobid: new job next_sync => $jobcfg->{next_sync}"); + } } PVE::API2::Replication::run_jobs($ctime, $logmsg, 1); @@ -359,62 +359,61 @@ sub track_jobs { # detect removed jobs foreach my $jobid (sort keys %$status) { - if (!$new->{$jobid}) { - $logmsg->("$ctime $jobid: vanished job"); - } + if (!$new->{$jobid}) { + $logmsg->("$ctime $jobid: vanished job"); + } } foreach my $jobid (sort keys %$new) { - my $jobcfg = $new->{$jobid}; - my $oldcfg = $status->{$jobid}; - if (!$oldcfg) { - $logmsg->("$ctime $jobid: new job next_sync => $jobcfg->{next_sync}"); - next; # no old state to compare - } else { - foreach my $k (qw(target guest vmtype next_sync)) { - my $changes = ''; - if ($oldcfg->{$k} ne $jobcfg->{$k}) { - $changes .= ', ' if $changes; - $changes .= "$k => $jobcfg->{$k}"; - } - $logmsg->("$ctime $jobid: changed config $changes") if $changes; - } - } + my $jobcfg = $new->{$jobid}; + my $oldcfg = $status->{$jobid}; + if (!$oldcfg) { + $logmsg->("$ctime $jobid: new job next_sync => $jobcfg->{next_sync}"); + next; # no old state to compare + } else { + foreach my $k (qw(target guest vmtype next_sync)) { + my $changes = ''; + if ($oldcfg->{$k} ne $jobcfg->{$k}) { + $changes .= ', ' if $changes; + $changes .= "$k => $jobcfg->{$k}"; + } + $logmsg->("$ctime $jobid: changed config $changes") if $changes; + } + } - my $oldstate = $oldcfg->{state}; + my $oldstate = $oldcfg->{state}; - my $state = $jobcfg->{state}; + my $state = $jobcfg->{state}; - my $changes = ''; - foreach my $k (qw(last_node last_try last_sync fail_count error)) { - if (($oldstate->{$k} // '') ne ($state->{$k} // '')) { - my $value = $state->{$k} // ''; - chomp $value; - $changes .= ', ' if $changes; - $changes .= "$k => $value"; - } - } - $logmsg->("$ctime $jobid: changed state $changes") if $changes; + my $changes = ''; + foreach my $k (qw(last_node last_try last_sync fail_count error)) { + if (($oldstate->{$k} // '') ne ($state->{$k} // '')) { + my $value = $state->{$k} // ''; + chomp $value; + $changes .= ', ' if $changes; + $changes .= "$k => $value"; + } + } + $logmsg->("$ctime $jobid: changed state $changes") if $changes; - my $old_storeid_list = $oldstate->{storeid_list}; - my $storeid_list = $state->{storeid_list}; + my $old_storeid_list = $oldstate->{storeid_list}; + my $storeid_list = $state->{storeid_list}; - my $storeid_list_changes = 0; - foreach my $storeid (@$storeid_list) { - next if grep { $_ eq $storeid } @$old_storeid_list; - $storeid_list_changes = 1; - } + my $storeid_list_changes = 0; + foreach my $storeid (@$storeid_list) { + next if grep { $_ eq $storeid } @$old_storeid_list; + $storeid_list_changes = 1; + } - foreach my $storeid (@$old_storeid_list) { - next if grep { $_ eq $storeid } @$storeid_list; - $storeid_list_changes = 1; - } + foreach my $storeid (@$old_storeid_list) { + next if grep { $_ eq $storeid } @$storeid_list; + $storeid_list_changes = 1; + } - $logmsg->("$ctime $jobid: changed storeid list " . join(',', @$storeid_list)) - if $storeid_list_changes; + $logmsg->("$ctime $jobid: changed storeid list " . join(',', @$storeid_list)) + if $storeid_list_changes; } $status = $new; } - 1; diff --git a/test/balloontest.pl b/test/balloontest.pl index 5ebf7e20..a6c32eaf 100755 --- a/test/balloontest.pl +++ b/test/balloontest.pl @@ -10,18 +10,18 @@ my $debug = 0; my $test_status1 = { 100 => { - maxmem => GB(2), - shares => 2000, - balloon => GB(1), - balloon_min => GB(1), - freemem => MB(0), + maxmem => GB(2), + shares => 2000, + balloon => GB(1), + balloon_min => GB(1), + freemem => MB(0), }, 101 => { - maxmem => GB(2), - shares => 1000, - balloon => GB(1), - balloon_min => GB(1), - freemem => MB(0), + maxmem => GB(2), + shares => 1000, + balloon => GB(1), + balloon_min => GB(1), + freemem => MB(0), }, }; @@ -36,18 +36,18 @@ absim($test_status1, MB(900), 100 => MB(1600), 101 => MB(1300)); my $test_status2 = { 100 => { - maxmem => GB(2), - shares => 2000, - balloon => GB(2), - balloon_min => GB(2), - freemem => MB(0), + maxmem => GB(2), + shares => 2000, + balloon => GB(2), + balloon_min => GB(2), + freemem => MB(0), }, 101 => { - maxmem => GB(2), - shares => 1000, - balloon => GB(1), - balloon_min => GB(1), - freemem => MB(0), + maxmem => GB(2), + shares => 1000, + balloon => GB(1), + balloon_min => GB(1), + freemem => MB(0), }, }; @@ -57,38 +57,38 @@ abtest($test_status2, MB(500), 101 => MB(1100)); my $test_status3 = { 100 => { - maxmem => GB(2), - shares => 2000, - balloon => GB(2), - balloon_min => GB(2), - freemem => MB(0), + maxmem => GB(2), + shares => 2000, + balloon => GB(2), + balloon_min => GB(2), + freemem => MB(0), }, 101 => { - maxmem => GB(2), - shares => 1000, - balloon => GB(1)+MB(7), - balloon_min => GB(1), - freemem => MB(0), + maxmem => GB(2), + shares => 1000, + balloon => GB(1) + MB(7), + balloon_min => GB(1), + freemem => MB(0), }, 102 => { - maxmem => GB(2), - shares => 1000, - balloon => GB(1), - balloon_min => GB(1), - freemem => MB(512), + maxmem => GB(2), + shares => 1000, + balloon => GB(1), + balloon_min => GB(1), + freemem => MB(512), }, }; abtest($test_status3, 0); -abtest($test_status3, MB(11), 101 => MB(1018)); -abtest($test_status3, MB(80), 101 => MB(1087)); -abtest($test_status3, MB(200), 101 => MB(1107)); +abtest($test_status3, MB(11), 101 => MB(1018)); +abtest($test_status3, MB(80), 101 => MB(1087)); +abtest($test_status3, MB(200), 101 => MB(1107)); -my $status = absim($test_status3, MB(593), 101 => MB(1300), 102 => MB(1300)); +my $status = absim($test_status3, MB(593), 101 => MB(1300), 102 => MB(1300)); absim($status, -MB(200), 101 => MB(1200), 102 => MB(1200)); absim($status, -MB(400), 101 => MB(1200), 102 => GB(1)); absim($status, -MB(593), 101 => MB(1007), 102 => GB(1)); -exit (0); +exit(0); sub abapply { my ($vmstatus, $res, $sum) = @_; @@ -96,39 +96,40 @@ sub abapply { my $changes = 0; my $abschanges = 0; foreach my $vmid (keys %$res) { - my $diff = $res->{$vmid} - $vmstatus->{$vmid}->{balloon}; - if ($diff != 0) { - # fixme: adjust freemem ? - $vmstatus->{$vmid}->{freemem} += $diff; - $vmstatus->{$vmid}->{freemem} = 0 if $vmstatus->{$vmid}->{freemem} < 0; - $vmstatus->{$vmid}->{balloon} = $res->{$vmid}; - $sum->{$vmid} = $res->{$vmid}; - $changes += $diff; - $abschanges += $diff > 0 ? $diff : -$diff; - } + my $diff = $res->{$vmid} - $vmstatus->{$vmid}->{balloon}; + if ($diff != 0) { + # fixme: adjust freemem ? + $vmstatus->{$vmid}->{freemem} += $diff; + $vmstatus->{$vmid}->{freemem} = 0 if $vmstatus->{$vmid}->{freemem} < 0; + $vmstatus->{$vmid}->{balloon} = $res->{$vmid}; + $sum->{$vmid} = $res->{$vmid}; + $changes += $diff; + $abschanges += $diff > 0 ? $diff : -$diff; + } } return ($changes, $abschanges); } my $tcount = 0; + sub absim { my ($vmstatus, $goal, %expect) = @_; $tcount++; print "BALLOON SIM $tcount\n" if $debug; - + $vmstatus = dclone($vmstatus); # do not change original my $changes = 0; my $abschanges = 0; my $sum = {}; do { - my $res = PVE::AutoBalloon::compute_alg1($vmstatus, $goal, MB(100), $debug); - print Dumper($res) if $debug; - ($changes, $abschanges) = abapply($vmstatus, $res, $sum); - $goal -= $changes; + my $res = PVE::AutoBalloon::compute_alg1($vmstatus, $goal, MB(100), $debug); + print Dumper($res) if $debug; + ($changes, $abschanges) = abapply($vmstatus, $res, $sum); + $goal -= $changes; } while ($abschanges); abcheck($sum, %expect); @@ -143,19 +144,19 @@ sub abcheck { my ($res, %expect) = @_; foreach my $vmid (keys %expect) { - my $ev = $expect{$vmid}; - if (defined ($res->{$vmid})) { - die "T$tcount: wrong value for VM $vmid ($ev != $res->{$vmid})\n" - if $ev != $res->{$vmid}; - } else { - die "T$tcount: missing value for VM $vmid (extected $ev)\n"; - } + my $ev = $expect{$vmid}; + if (defined($res->{$vmid})) { + die "T$tcount: wrong value for VM $vmid ($ev != $res->{$vmid})\n" + if $ev != $res->{$vmid}; + } else { + die "T$tcount: missing value for VM $vmid (extected $ev)\n"; + } } foreach my $vmid (keys %$res) { - die "T$tcount: got unexpected result for $vmid\n" - if (defined($res->{$vmid}) && - !defined($expect{$vmid})); + die "T$tcount: got unexpected result for $vmid\n" + if (defined($res->{$vmid}) + && !defined($expect{$vmid})); } } @@ -177,9 +178,10 @@ sub abtest { sub MB { my $mb = shift; - return $mb*1000*1000; -}; + return $mb * 1000 * 1000; +} + sub GB { my $gb = shift; - return $gb*1000*1000*1000; -}; + return $gb * 1000 * 1000 * 1000; +} diff --git a/test/perftest3.pl b/test/perftest3.pl index e1ad61cd..bd191950 100755 --- a/test/perftest3.pl +++ b/test/perftest3.pl @@ -23,45 +23,48 @@ sub test_rpc { my ($host) = @_; for (my $i = 0; $i < $qcount; $i++) { - eval { - my ($page, $response, %reply_headers) - = get_https($host, 8006, '/api2/json', - make_headers(Cookie => "PVEAuthCookie=$ticket")); - die "$response\n" if $response !~ m/200 OK/; - }; + eval { + my ($page, $response, %reply_headers) = get_https( + $host, + 8006, + '/api2/json', + make_headers(Cookie => "PVEAuthCookie=$ticket"), + ); + die "$response\n" if $response !~ m/200 OK/; + }; - my $err = $@; + my $err = $@; - if ($err) { + if ($err) { - print "ERROR: $err\n"; - last; - } + print "ERROR: $err\n"; + last; + } } } sub run_tests { my ($host) = @_; - + my $workers; my $starttime = [gettimeofday]; for (my $i = 0; $i < $wcount; $i++) { - if (my $pid = fork ()) { - $workers->{$pid} = 1; - } else { - test_rpc ($host); - exit (0); - } + if (my $pid = fork()) { + $workers->{$pid} = 1; + } else { + test_rpc($host); + exit(0); + } } # wait for children 1 while (wait > 0); - my $elapsed = int(tv_interval ($starttime) * 1000); + my $elapsed = int(tv_interval($starttime) * 1000); - my $tpq = $elapsed / ($wcount*$qcount); + my $tpq = $elapsed / ($wcount * $qcount); print "$host: $tpq ms per query\n"; } diff --git a/test/replication_test1.pl b/test/replication_test1.pl index 7a31cfc3..2632959b 100755 --- a/test/replication_test1.pl +++ b/test/replication_test1.pl @@ -15,7 +15,7 @@ use Test::More tests => 3; $ReplicationTestEnv::mocked_nodename = 'node1'; my $testjob = { - 'type' => 'local', + 'type' => 'local', 'target' => 'node1', 'guest' => 900, }; @@ -26,11 +26,11 @@ $ReplicationTestEnv::mocked_replication_jobs = { $ReplicationTestEnv::mocked_vm_configs = { 900 => { - node => 'node1', - snapshots => {}, - ide0 => 'local-lvm:vm-900-disk-1,size=4G', - memory => 512, - ide2 => 'none,media=cdrom', + node => 'node1', + snapshots => {}, + ide0 => 'local-lvm:vm-900-disk-1,size=4G', + memory => 512, + ide2 => 'none,media=cdrom', }, }; @@ -39,8 +39,8 @@ ReplicationTestEnv::setup(); ok(PVE::INotify::nodename() eq 'node1'); my $list = PVE::Cluster::get_vmlist(); -is_deeply($list, { ids => {900 => { node => 'node1', type => 'qemu', version => 1}}}); +is_deeply($list, { ids => { 900 => { node => 'node1', type => 'qemu', version => 1 } } }); my $cfg = PVE::ReplicationConfig->new(); -is_deeply($cfg, { ids => { job_900_to_node1 => $testjob }}); +is_deeply($cfg, { ids => { job_900_to_node1 => $testjob } }); exit(0); diff --git a/test/replication_test2.pl b/test/replication_test2.pl index ff5fbaff..4ad19df3 100755 --- a/test/replication_test2.pl +++ b/test/replication_test2.pl @@ -21,14 +21,15 @@ my $schedule = []; my $mocked_replicate = sub { my ($guest_class, $jobcfg, $state, $start_time, $logfunc) = @_; - push @$schedule, { - id => $jobcfg->{id}, - guest => $jobcfg->{guest}, - vmtype => $jobcfg->{vmtype}, - guest_class => $guest_class, - last_sync => $state->{last_sync}, - start => $start_time, - }; + push @$schedule, + { + id => $jobcfg->{id}, + guest => $jobcfg->{guest}, + vmtype => $jobcfg->{vmtype}, + guest_class => $guest_class, + last_sync => $state->{last_sync}, + start => $start_time, + }; }; my $pve_replication_module = Test::MockModule->new('PVE::Replication'); @@ -36,66 +37,66 @@ $pve_replication_module->mock(replicate => $mocked_replicate); $ReplicationTestEnv::mocked_replication_jobs = { '900-1_to_node2' => { - 'type' => 'local', - 'target' => 'node2', - 'guest' => 900, + 'type' => 'local', + 'target' => 'node2', + 'guest' => 900, }, '900-2_to_node1' => { - 'type' => 'local', - 'target' => 'node1', # local node, job should be skipped - 'guest' => 900, + 'type' => 'local', + 'target' => 'node1', # local node, job should be skipped + 'guest' => 900, }, }; $ReplicationTestEnv::mocked_vm_configs = { 900 => { - node => 'node1', - snapshots => {}, - ide0 => 'local-lvm:vm-900-disk-1,size=4G', - memory => 512, - ide2 => 'none,media=cdrom', + node => 'node1', + snapshots => {}, + ide0 => 'local-lvm:vm-900-disk-1,size=4G', + memory => 512, + ide2 => 'none,media=cdrom', }, }; ReplicationTestEnv::setup(); for (my $i = 0; $i < 61; $i++) { - PVE::API2::Replication::run_jobs($i*60); + PVE::API2::Replication::run_jobs($i * 60); } my $exptected_schedule = [ { - last_sync => 0, - start => 900, - vmtype => 'qemu', - guest_class => 'PVE::QemuConfig', - id => '900-1_to_node2', - guest => 900 + last_sync => 0, + start => 900, + vmtype => 'qemu', + guest_class => 'PVE::QemuConfig', + id => '900-1_to_node2', + guest => 900, }, { - last_sync => 900, - start => 1800, - vmtype => 'qemu', - guest_class => 'PVE::QemuConfig', - id => '900-1_to_node2', - guest => 900, - }, - { - last_sync => 1800, - start => 2700, - vmtype => 'qemu', - guest_class => 'PVE::QemuConfig', - id => '900-1_to_node2', - guest => 900 + last_sync => 900, + start => 1800, + vmtype => 'qemu', + guest_class => 'PVE::QemuConfig', + id => '900-1_to_node2', + guest => 900, }, { - last_sync => 2700, - start => 3600, - vmtype => 'qemu', - guest_class => 'PVE::QemuConfig', - id => '900-1_to_node2', - guest => 900 - } + last_sync => 1800, + start => 2700, + vmtype => 'qemu', + guest_class => 'PVE::QemuConfig', + id => '900-1_to_node2', + guest => 900, + }, + { + last_sync => 2700, + start => 3600, + vmtype => 'qemu', + guest_class => 'PVE::QemuConfig', + id => '900-1_to_node2', + guest => 900, + }, ]; is_deeply($schedule, $exptected_schedule); diff --git a/test/replication_test3.pl b/test/replication_test3.pl index 54004c70..e3673334 100755 --- a/test/replication_test3.pl +++ b/test/replication_test3.pl @@ -18,26 +18,26 @@ use PVE::API2::Replication; $ReplicationTestEnv::mocked_nodename = 'node1'; my $testjob = { - 'type' => 'local', + 'type' => 'local', 'target' => 'node1', 'guest' => 900, }; $ReplicationTestEnv::mocked_replication_jobs = { - job_900_to_node1 => { - 'type' => 'local', - 'target' => 'node1', # local node, job should be skipped - 'guest' => 900, + job_900_to_node1 => { + 'type' => 'local', + 'target' => 'node1', # local node, job should be skipped + 'guest' => 900, }, }; $ReplicationTestEnv::mocked_vm_configs = { 900 => { - node => 'node1', - snapshots => {}, - ide0 => 'local-lvm:vm-900-disk-1,size=4G', - memory => 512, - ide2 => 'none,media=cdrom', + node => 'node1', + snapshots => {}, + ide0 => 'local-lvm:vm-900-disk-1,size=4G', + memory => 512, + ide2 => 'none,media=cdrom', }, }; diff --git a/test/replication_test4.pl b/test/replication_test4.pl index 38fef0e3..7b5ec313 100755 --- a/test/replication_test4.pl +++ b/test/replication_test4.pl @@ -16,35 +16,34 @@ use PVE::Tools; $ReplicationTestEnv::mocked_nodename = 'node1'; my $pve_replication_module = Test::MockModule->new('PVE::Replication'); -$pve_replication_module->mock( - replicate => sub { die "faked replication error\n"; }); +$pve_replication_module->mock(replicate => sub { die "faked replication error\n"; }); my $testjob = { - 'type' => 'local', + 'type' => 'local', 'target' => 'node1', 'guest' => 900, }; $ReplicationTestEnv::mocked_replication_jobs = { job_900_to_node2 => { - 'type' => 'local', - 'target' => 'node2', - 'guest' => 900, + 'type' => 'local', + 'target' => 'node2', + 'guest' => 900, }, job_900_to_node1 => { - 'type' => 'local', - 'target' => 'node1', # local node, job should be skipped - 'guest' => 900, + 'type' => 'local', + 'target' => 'node1', # local node, job should be skipped + 'guest' => 900, }, }; $ReplicationTestEnv::mocked_vm_configs = { 900 => { - node => 'node1', - snapshots => {}, - ide0 => 'local-lvm:vm-900-disk-1,size=4G', - memory => 512, - ide2 => 'none,media=cdrom', + node => 'node1', + snapshots => {}, + ide0 => 'local-lvm:vm-900-disk-1,size=4G', + memory => 512, + ide2 => 'none,media=cdrom', }, }; diff --git a/test/replication_test5.pl b/test/replication_test5.pl index b884881e..68fe6025 100755 --- a/test/replication_test5.pl +++ b/test/replication_test5.pl @@ -30,7 +30,8 @@ use PVE::Storage; my $replicated_volume_status = {}; my $mocked_remote_prepare_local_job = sub { - my ($ssh_info, $jobid, $vmid, $volumes, $storeid_list, $last_sync, $parent_snapname, $force) = @_; + my ($ssh_info, $jobid, $vmid, $volumes, $storeid_list, $last_sync, $parent_snapname, $force) + = @_; my $target = $ssh_info->{node}; @@ -38,16 +39,17 @@ my $mocked_remote_prepare_local_job = sub { return $last_snapshots if !defined($replicated_volume_status->{$target}); - my $last_sync_snapname = PVE::ReplicationState::replication_snapshot_name($jobid, $last_sync); + my $last_sync_snapname = + PVE::ReplicationState::replication_snapshot_name($jobid, $last_sync); - foreach my $volid (keys %{$replicated_volume_status->{$target}}) { - if (!grep { $_ eq $volid } @$volumes) { - delete $replicated_volume_status->{$target}->{$volid}; - next; - } - my $snapname = $replicated_volume_status->{$target}->{$volid}; + foreach my $volid (keys %{ $replicated_volume_status->{$target} }) { + if (!grep { $_ eq $volid } @$volumes) { + delete $replicated_volume_status->{$target}->{$volid}; + next; + } + my $snapname = $replicated_volume_status->{$target}->{$volid}; - $last_snapshots->{$volid}->{$snapname} = 1 if $last_sync_snapname eq $snapname; + $last_snapshots->{$volid}->{$snapname} = 1 if $last_sync_snapname eq $snapname; } return $last_snapshots; @@ -80,29 +82,30 @@ my $pve_replication_module = Test::MockModule->new('PVE::Replication'); $pve_replication_module->mock( remote_prepare_local_job => $mocked_remote_prepare_local_job, remote_finalize_local_job => $mocked_remote_finalize_local_job, - replicate_volume => $mocked_replicate_volume); + replicate_volume => $mocked_replicate_volume, +); my $testjob = { - 'type' => 'local', + 'type' => 'local', 'target' => 'node1', 'guest' => 900, }; $ReplicationTestEnv::mocked_replication_jobs = { job_900_to_node2 => { - 'type' => 'local', - 'target' => 'node2', - 'guest' => 900, + 'type' => 'local', + 'target' => 'node2', + 'guest' => 900, }, }; $ReplicationTestEnv::mocked_vm_configs = { 900 => { - node => 'node1', - snapshots => {}, - ide0 => 'local-zfs:vm-900-disk-1,size=4G', - memory => 512, - ide2 => 'none,media=cdrom', + node => 'node1', + snapshots => {}, + ide0 => 'local-zfs:vm-900-disk-1,size=4G', + memory => 512, + ide2 => 'none,media=cdrom', }, }; @@ -122,7 +125,7 @@ for (my $i = 0; $i < 15; $i++) { } # add a new, disk (but disk does not exist, so replication fails) -$ReplicationTestEnv::mocked_vm_configs->{900}->{ide1} = 'local-zfs:vm-900-disk-2,size=4G'; +$ReplicationTestEnv::mocked_vm_configs->{900}->{ide1} = 'local-zfs:vm-900-disk-2,size=4G'; for (my $i = 0; $i < 15; $i++) { ReplicationTestEnv::track_jobs($ctime); $ctime += 60; @@ -142,8 +145,6 @@ for (my $i = 0; $i < 15; $i++) { $ctime += 60; } - - ReplicationTestEnv::commit_log(); exit(0); diff --git a/test/replication_test6.pl b/test/replication_test6.pl index 5b2303b5..14ec01ab 100755 --- a/test/replication_test6.pl +++ b/test/replication_test6.pl @@ -20,31 +20,30 @@ my $mocked_delete_job = sub { }; my $pve_replication_config_module = Test::MockModule->new('PVE::ReplicationConfig'); -$pve_replication_config_module->mock( - delete_job => $mocked_delete_job); +$pve_replication_config_module->mock(delete_job => $mocked_delete_job); my $testjob = { - 'type' => 'local', + 'type' => 'local', 'target' => 'node1', 'guest' => 900, }; $ReplicationTestEnv::mocked_replication_jobs = { job_900_to_node1 => { - remove_job => 'full', - type => 'local', - target => 'node1', # local node, job should be skipped - guest => 900, + remove_job => 'full', + type => 'local', + target => 'node1', # local node, job should be skipped + guest => 900, }, }; $ReplicationTestEnv::mocked_vm_configs = { 900 => { - node => 'node1', - snapshots => {}, - ide0 => 'local-zfs:vm-900-disk-1,size=4G', - memory => 512, - ide2 => 'none,media=cdrom', + node => 'node1', + snapshots => {}, + ide0 => 'local-zfs:vm-900-disk-1,size=4G', + memory => 512, + ide2 => 'none,media=cdrom', }, }; diff --git a/test/vzdump_guest_included_test.pl b/test/vzdump_guest_included_test.pl index 157644de..be42e9ab 100755 --- a/test/vzdump_guest_included_test.pl +++ b/test/vzdump_guest_included_test.pl @@ -14,39 +14,39 @@ use Data::Dumper; my $vmlist = { 'ids' => { - 100 => { - 'type' => 'qemu', - 'node' => 'node1', - }, - 101 => { - 'type' => 'qemu', - 'node' => 'node1', - }, - 112 => { - 'type' => 'lxc', - 'node' => 'node1', - }, - 113 => { - 'type' => 'lxc', - 'node' => 'node1', - }, - 200 => { - 'type' => 'qemu', - 'node' => 'node2', - }, - 201 => { - 'type' => 'qemu', - 'node' => 'node2', - }, - 212 => { - 'type' => 'lxc', - 'node' => 'node2', - }, - 213 => { - 'type' => 'lxc', - 'node' => 'node2', - }, - } + 100 => { + 'type' => 'qemu', + 'node' => 'node1', + }, + 101 => { + 'type' => 'qemu', + 'node' => 'node1', + }, + 112 => { + 'type' => 'lxc', + 'node' => 'node1', + }, + 113 => { + 'type' => 'lxc', + 'node' => 'node1', + }, + 200 => { + 'type' => 'qemu', + 'node' => 'node2', + }, + 201 => { + 'type' => 'qemu', + 'node' => 'node2', + }, + 212 => { + 'type' => 'lxc', + 'node' => 'node2', + }, + 213 => { + 'type' => 'lxc', + 'node' => 'node2', + }, + }, }; my $pools = { @@ -56,135 +56,164 @@ my $pools = { my $pve_cluster_module = Test::MockModule->new('PVE::Cluster'); $pve_cluster_module->mock( get_vmlist => sub { - return $vmlist; - } + return $vmlist; + }, ); my $pve_inotify = Test::MockModule->new('PVE::INotify'); $pve_inotify->mock( nodename => sub { - return 'node1'; - } + return 'node1'; + }, ); my $pve_api2tools = Test::MockModule->new('PVE::API2Tools'); $pve_api2tools->mock( get_resource_pool_guest_members => sub { - return $pools->{testpool}; - } + return $pools->{testpool}; + }, ); my $tests = []; my $addtest = sub { my ($name, $test) = @_; - push @$tests, { - name => $name, - test => $test, - }; + push @$tests, + { + name => $name, + test => $test, + }; }; -$addtest->('Test all guests', { - expected => { - node1 => [ 100, 101, 112, 113 ], - node2 => [ 200, 201, 212, 213 ], +$addtest->( + 'Test all guests', + { + expected => { + node1 => [100, 101, 112, 113], + node2 => [200, 201, 212, 213], + }, + param => { + all => 1, + }, }, - param => { - all => 1, - } -}); +); -$addtest->('Test all guests with node limit', { - expected => { - node2 => [ 200, 201, 212, 213 ], +$addtest->( + 'Test all guests with node limit', + { + expected => { + node2 => [200, 201, 212, 213], + }, + param => { + all => 1, + node => 'node2', + }, }, - param => { - all => 1, - node => 'node2', - } -}); +); -$addtest->('Test exclude', { - expected => { - node1 =>[ 101, 112, 113 ], - node2 => [ 201, 212, 213 ], +$addtest->( + 'Test exclude', + { + expected => { + node1 => [101, 112, 113], + node2 => [201, 212, 213], + }, + param => { + all => 1, + exclude => '100, 102, 200, 202', + }, }, - param => { - all => 1, - exclude => '100, 102, 200, 202', - } -}); +); -$addtest->('Test exclude with node limit', { - expected => { - node1 =>[ 101, 112, 113 ], +$addtest->( + 'Test exclude with node limit', + { + expected => { + node1 => [101, 112, 113], + }, + param => { + all => 1, + exclude => '100, 102, 200, 202', + node => 'node1', + }, }, - param => { - all => 1, - exclude => '100, 102, 200, 202', - node => 'node1', - } -}); +); -$addtest->('Test pool members', { - expected => { - node1 =>[ 100, 101 ], - node2 => [ 200, 201 ], +$addtest->( + 'Test pool members', + { + expected => { + node1 => [100, 101], + node2 => [200, 201], + }, + param => { + pool => 'testpool', + }, }, - param => { - pool => 'testpool', - } -}); +); -$addtest->('Test pool members with node limit', { - expected => { - node2 => [ 200, 201 ], +$addtest->( + 'Test pool members with node limit', + { + expected => { + node2 => [200, 201], + }, + param => { + pool => 'testpool', + node => 'node2', + }, }, - param => { - pool => 'testpool', - node => 'node2', - } -}); +); -$addtest->('Test selected VMIDs', { - expected => { - node1 =>[ 100 ], - node2 => [ 201, 212 ], +$addtest->( + 'Test selected VMIDs', + { + expected => { + node1 => [100], + node2 => [201, 212], + }, + param => { + vmid => '100, 201, 212', + }, }, - param => { - vmid => '100, 201, 212', - } -}); +); -$addtest->('Test selected VMIDs with node limit', { - expected => { - node1 =>[ 100 ], +$addtest->( + 'Test selected VMIDs with node limit', + { + expected => { + node1 => [100], + }, + param => { + vmid => '100, 201, 212', + node => 'node1', + }, }, - param => { - vmid => '100, 201, 212', - node => 'node1', - } -}); +); -$addtest->('Test selected VMIDs on other nodes', { - expected => { +$addtest->( + 'Test selected VMIDs on other nodes', + { + expected => {}, + param => { + vmid => '201, 212', + node => 'node1', + }, }, - param => { - vmid => '201, 212', - node => 'node1', - } -}); - -$addtest->('Test VMID not present in vmlist', { - expected => { - node1 => [ 100 ], - node2 => [ 201, 212 ], - '' => [ 7654 ], - }, - param => { - vmid => '100, 201, 212, 7654', - } -}); +); +$addtest->( + 'Test VMID not present in vmlist', + { + expected => { + node1 => [100], + node2 => [201, 212], + '' => [7654], + }, + param => { + vmid => '100, 201, 212, 7654', + }, + }, +); for my $test (@{$tests}) { my $testname = $test->{name}; @@ -192,7 +221,7 @@ for my $test (@{$tests}) { # note($testname); - my $result = PVE::VZDump::get_included_guests($testdata->{param}); + my $result = PVE::VZDump::get_included_guests($testdata->{param}); is_deeply($result, $testdata->{expected}, $testname); } diff --git a/test/vzdump_new_test.pl b/test/vzdump_new_test.pl index 01f2a661..36225ebb 100755 --- a/test/vzdump_new_test.pl +++ b/test/vzdump_new_test.pl @@ -21,8 +21,8 @@ sub prepare_storage_config { $storage_config .= "\tpath /var/lib/vz\n"; foreach my $key (keys %{$param}) { - my $value = $param->{$key}; - $storage_config .= "\t${key} ${value}\n"; + my $value = $param->{$key}; + $storage_config .= "\t${key} ${value}\n"; } } @@ -31,52 +31,52 @@ sub prepare_vzdump_config { $vzdump_config = ""; foreach my $key (keys %{$param}) { - my $value = $param->{$key}; - $vzdump_config .= "${key}: ${value}\n"; + my $value = $param->{$key}; + $vzdump_config .= "${key}: ${value}\n"; } } my $pve_vzdump_module = Test::MockModule->new('PVE::VZDump'); $pve_vzdump_module->mock( mkpath => sub { - return; + return; }, check_bin => sub { - return; + return; }, ); my $pve_storage_module = Test::MockModule->new('PVE::Storage'); $pve_storage_module->mock( activate_storage => sub { - return; + return; }, get_backup_provider => sub { - return; + return; }, ); my $pve_cluster_module = Test::MockModule->new('PVE::Cluster'); $pve_cluster_module->mock( get_config => sub { - my ($filename) = @_; + my ($filename) = @_; - die "unexpected filename '$filename'\n" if $filename ne 'storage.cfg'; - return $storage_config; + die "unexpected filename '$filename'\n" if $filename ne 'storage.cfg'; + return $storage_config; }, # never update during the tests cfs_update => sub { - return; + return; }, ); my $pve_tools_module = Test::MockModule->new('PVE::Tools'); $pve_tools_module->mock( file_get_contents => sub { - my ($filename) = @_; + my ($filename) = @_; - die "unexpected filename '$filename'\n" if $filename ne '/etc/vzdump.conf'; - return $vzdump_config; + die "unexpected filename '$filename'\n" if $filename ne '/etc/vzdump.conf'; + return $vzdump_config; }, ); @@ -92,547 +92,539 @@ my $tested_options; # To begin testing for different options, use a fake test like the first one my @tests = ( { - description => 'BEGIN RETENTION TESTS', - tested_options => ['prune-backups', 'remove'], + description => 'BEGIN RETENTION TESTS', + tested_options => ['prune-backups', 'remove'], }, { - description => 'no params', - expected => { - 'prune-backups' => { - 'keep-all' => 1, - }, - remove => 0, - }, + description => 'no params', + expected => { + 'prune-backups' => { + 'keep-all' => 1, + }, + remove => 0, + }, }, # TODO make parse error critical? { - description => 'maxfiles vzdump 1', - vzdump_param => { - maxfiles => 0, - }, - expected => { - 'prune-backups' => { - 'keep-all' => 1, - }, - remove => 0, - }, + description => 'maxfiles vzdump 1', + vzdump_param => { + maxfiles => 0, + }, + expected => { + 'prune-backups' => { + 'keep-all' => 1, + }, + remove => 0, + }, }, { - description => 'maxfiles vzdump 2', - vzdump_param => { - maxfiles => 7, - }, - expected => { - 'prune-backups' => { - 'keep-last' => 7, - }, - remove => 1, - }, + description => 'maxfiles vzdump 2', + vzdump_param => { + maxfiles => 7, + }, + expected => { + 'prune-backups' => { + 'keep-last' => 7, + }, + remove => 1, + }, }, { - description => 'maxfiles storage 1', - storage_param => { - maxfiles => 0, - }, - expected => { - 'prune-backups' => { - 'keep-all' => 1, - }, - remove => 0, - }, + description => 'maxfiles storage 1', + storage_param => { + maxfiles => 0, + }, + expected => { + 'prune-backups' => { + 'keep-all' => 1, + }, + remove => 0, + }, }, { - description => 'maxfiles storage 2', - storage_param => { - maxfiles => 7, - }, - expected => { - 'prune-backups' => { - 'keep-last' => 7, - }, - remove => 1, - }, + description => 'maxfiles storage 2', + storage_param => { + maxfiles => 7, + }, + expected => { + 'prune-backups' => { + 'keep-last' => 7, + }, + remove => 1, + }, }, { - description => 'maxfiles CLI 1', - cli_param => { - maxfiles => 0, - }, - expected => { - 'prune-backups' => { - 'keep-all' => 1, - }, - remove => 0, - }, + description => 'maxfiles CLI 1', + cli_param => { + maxfiles => 0, + }, + expected => { + 'prune-backups' => { + 'keep-all' => 1, + }, + remove => 0, + }, }, { - description => 'maxfiles CLI 2', - cli_param => { - maxfiles => 7, - }, - expected => { - 'prune-backups' => { - 'keep-last' => 7, - }, - remove => 1, - }, + description => 'maxfiles CLI 2', + cli_param => { + maxfiles => 7, + }, + expected => { + 'prune-backups' => { + 'keep-last' => 7, + }, + remove => 1, + }, }, { - 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', - }, - expected => { - 'prune-backups' => { - 'keep-last' => 1, - 'keep-hourly' => 2, - 'keep-daily' => 3, - 'keep-weekly' => 4, - 'keep-monthly' => 5, - 'keep-yearly' => 6, - }, - remove => 1, - }, + 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', + }, + expected => { + 'prune-backups' => { + 'keep-last' => 1, + 'keep-hourly' => 2, + 'keep-daily' => 3, + 'keep-weekly' => 4, + 'keep-monthly' => 5, + 'keep-yearly' => 6, + }, + remove => 1, + }, }, { - description => 'prune-backups vzdump 2', - vzdump_param => { - 'prune-backups' => 'keep-all=1', - }, - expected => { - 'prune-backups' => { - 'keep-all' => 1, - }, - remove => 0, - }, + description => 'prune-backups vzdump 2', + vzdump_param => { + 'prune-backups' => 'keep-all=1', + }, + expected => { + 'prune-backups' => { + 'keep-all' => 1, + }, + remove => 0, + }, }, { - description => 'prune-backups vzdump 3', - vzdump_param => { - 'prune-backups' => 'keep-hourly=0,keep-monthly=0,keep-yearly=0', - }, - expected => { - 'prune-backups' => { - 'keep-all' => 1, - }, - remove => 0, - }, + description => 'prune-backups vzdump 3', + vzdump_param => { + 'prune-backups' => 'keep-hourly=0,keep-monthly=0,keep-yearly=0', + }, + expected => { + 'prune-backups' => { + 'keep-all' => 1, + }, + remove => 0, + }, }, { - description => 'both vzdump 1', - vzdump_param => { - 'prune-backups' => 'keep-all=1', - maxfiles => 7, - }, - expected => { - 'prune-backups' => { - 'keep-all' => 1, - }, - remove => 0, - }, + description => 'both vzdump 1', + vzdump_param => { + 'prune-backups' => 'keep-all=1', + maxfiles => 7, + }, + expected => { + 'prune-backups' => { + 'keep-all' => 1, + }, + remove => 0, + }, }, { - 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', - }, - expected => { - 'prune-backups' => { - 'keep-last' => 1, - 'keep-hourly' => 2, - 'keep-daily' => 3, - 'keep-weekly' => 4, - 'keep-monthly' => 5, - 'keep-yearly' => 6, - }, - remove => 1, - }, + 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', + }, + expected => { + 'prune-backups' => { + 'keep-last' => 1, + 'keep-hourly' => 2, + 'keep-daily' => 3, + 'keep-weekly' => 4, + 'keep-monthly' => 5, + 'keep-yearly' => 6, + }, + remove => 1, + }, }, { - 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', - }, - expected => { - 'prune-backups' => { - 'keep-all' => 1, - }, - remove => 0, - }, + 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', + }, + expected => { + 'prune-backups' => { + 'keep-all' => 1, + }, + remove => 0, + }, }, { - description => 'prune-backups storage 3', - storage_param => { - 'prune-backups' => 'keep-hourly=0,keep-monthly=0,keep-yearly=0', - }, - expected => { - 'prune-backups' => { - 'keep-all' => 1, - }, - remove => 0, - }, + description => 'prune-backups storage 3', + storage_param => { + 'prune-backups' => 'keep-hourly=0,keep-monthly=0,keep-yearly=0', + }, + expected => { + 'prune-backups' => { + 'keep-all' => 1, + }, + remove => 0, + }, }, { - description => 'both storage 1', - storage_param => { - 'prune-backups' => 'keep-hourly=1,keep-monthly=2,keep-yearly=3', - maxfiles => 0, - }, - expected => { - 'prune-backups' => { - 'keep-hourly' => 1, - 'keep-monthly' => 2, - 'keep-yearly' => 3, - }, - remove => 1, - }, + description => 'both storage 1', + storage_param => { + 'prune-backups' => 'keep-hourly=1,keep-monthly=2,keep-yearly=3', + maxfiles => 0, + }, + expected => { + 'prune-backups' => { + 'keep-hourly' => 1, + 'keep-monthly' => 2, + 'keep-yearly' => 3, + }, + remove => 1, + }, }, { - 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', - }, - expected => { - 'prune-backups' => { - 'keep-last' => 1, - 'keep-hourly' => 2, - 'keep-daily' => 3, - 'keep-weekly' => 4, - 'keep-monthly' => 5, - 'keep-yearly' => 6, - }, - remove => 1, - }, + 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', + }, + expected => { + 'prune-backups' => { + 'keep-last' => 1, + 'keep-hourly' => 2, + 'keep-daily' => 3, + 'keep-weekly' => 4, + 'keep-monthly' => 5, + 'keep-yearly' => 6, + }, + remove => 1, + }, }, { - 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', - }, - expected => { - 'prune-backups' => { - 'keep-all' => 1, - }, - remove => 0, - }, + 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', + }, + expected => { + 'prune-backups' => { + 'keep-all' => 1, + }, + remove => 0, + }, }, { - description => 'prune-backups CLI 3', - 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", + description => 'prune-backups CLI 3', + 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", }, { - description => 'both CLI 1', - cli_param => { - '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", + description => 'both CLI 1', + cli_param => { + '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", }, { - description => 'mixed 1', - vzdump_param => { - maxfiles => 7, - }, - storage_param => { - 'prune-backups' => 'keep-hourly=24', - }, - expected => { - 'prune-backups' => { - 'keep-hourly' => 24, - }, - remove => 1, - }, + description => 'mixed 1', + vzdump_param => { + maxfiles => 7, + }, + storage_param => { + 'prune-backups' => 'keep-hourly=24', + }, + expected => { + 'prune-backups' => { + 'keep-hourly' => 24, + }, + remove => 1, + }, }, # TODO make parse error critical? { - description => 'mixed 2', - vzdump_param => { - maxfiles => 7, - }, - storage_param => { - 'prune-backups' => 'keephourly=24', - }, - expected => { - 'prune-backups' => { - 'keep-last' => 7, - }, - remove => 1, - }, + description => 'mixed 2', + vzdump_param => { + maxfiles => 7, + }, + storage_param => { + 'prune-backups' => 'keephourly=24', + }, + expected => { + 'prune-backups' => { + 'keep-last' => 7, + }, + remove => 1, + }, }, { - description => 'mixed 3', - vzdump_param => { - maxfiles => 7, - }, - cli_param => { - 'prune-backups' => 'keep-all=1', - }, - expected => { - 'prune-backups' => { - 'keep-all' => 1, - }, - remove => 0, - }, + description => 'mixed 3', + vzdump_param => { + maxfiles => 7, + }, + cli_param => { + 'prune-backups' => 'keep-all=1', + }, + expected => { + 'prune-backups' => { + 'keep-all' => 1, + }, + remove => 0, + }, }, { - description => 'mixed 4', - vzdump_param => { - maxfiles => 7, - }, - storage_param => { - 'prune-backups' => 'keep-all=0,keep-last=10', - }, - cli_param => { - 'prune-backups' => 'keep-all=1', - }, - expected => { - 'prune-backups' => { - 'keep-all' => 1, - }, - remove => 0, - }, + description => 'mixed 4', + vzdump_param => { + maxfiles => 7, + }, + storage_param => { + 'prune-backups' => 'keep-all=0,keep-last=10', + }, + cli_param => { + 'prune-backups' => 'keep-all=1', + }, + expected => { + 'prune-backups' => { + 'keep-all' => 1, + }, + remove => 0, + }, }, { - description => 'mixed 5', - vzdump_param => { - maxfiles => 7, - }, - storage_param => { - 'prune-backups' => 'keep-all=0,keep-last=10', - }, - expected => { - 'prune-backups' => { - 'keep-last' => 10, - }, - remove => 1, - }, + description => 'mixed 5', + vzdump_param => { + maxfiles => 7, + }, + storage_param => { + 'prune-backups' => 'keep-all=0,keep-last=10', + }, + expected => { + 'prune-backups' => { + 'keep-last' => 10, + }, + remove => 1, + }, }, { - description => 'mixed 6', - storage_param => { - 'prune-backups' => 'keep-last=10', - }, - cli_param => { - 'prune-backups' => 'keep-all=1', - }, - expected => { - 'prune-backups' => { - 'keep-all' => 1, - }, - remove => 0, - }, + description => 'mixed 6', + storage_param => { + 'prune-backups' => 'keep-last=10', + }, + cli_param => { + 'prune-backups' => 'keep-all=1', + }, + expected => { + 'prune-backups' => { + 'keep-all' => 1, + }, + remove => 0, + }, }, { - description => 'mixed 7', - storage_param => { - 'prune-backups' => 'keep-all=1', - }, - cli_param => { - 'prune-backups' => 'keep-last=10', - }, - expected => { - 'prune-backups' => { - 'keep-last' => 10, - }, - remove => 1, - }, + description => 'mixed 7', + storage_param => { + 'prune-backups' => 'keep-all=1', + }, + cli_param => { + 'prune-backups' => 'keep-last=10', + }, + expected => { + 'prune-backups' => { + 'keep-last' => 10, + }, + remove => 1, + }, }, { - description => 'mixed 8', - storage_param => { - 'prune-backups' => 'keep-last=10', - }, - vzdump_param => { - 'prune-backups' => 'keep-all=1', - }, - expected => { - 'prune-backups' => { - 'keep-last' => 10, - }, - remove => 1, - }, + description => 'mixed 8', + storage_param => { + 'prune-backups' => 'keep-last=10', + }, + vzdump_param => { + 'prune-backups' => 'keep-all=1', + }, + expected => { + 'prune-backups' => { + 'keep-last' => 10, + }, + remove => 1, + }, }, { - description => 'mixed 9', - vzdump_param => { - 'prune-backups' => 'keep-last=10', - }, - cli_param => { - 'prune-backups' => 'keep-all=1', - }, - expected => { - 'prune-backups' => { - 'keep-all' => 1, - }, - remove => 0, - }, + description => 'mixed 9', + vzdump_param => { + 'prune-backups' => 'keep-last=10', + }, + cli_param => { + 'prune-backups' => 'keep-all=1', + }, + expected => { + 'prune-backups' => { + 'keep-all' => 1, + }, + remove => 0, + }, }, { - description => 'BEGIN MAILTO TESTS', - tested_options => ['mailto'], + description => 'BEGIN MAILTO TESTS', + tested_options => ['mailto'], }, { - description => 'mailto vzdump 1', - vzdump_param => { - 'mailto' => 'developer@proxmox.com', - }, - expected => { - 'mailto' => [ - 'developer@proxmox.com', - ], - }, + description => 'mailto vzdump 1', + vzdump_param => { + 'mailto' => 'developer@proxmox.com', + }, + expected => { + 'mailto' => [ + 'developer@proxmox.com', + ], + }, }, { - description => 'mailto vzdump 2', - vzdump_param => { - 'mailto' => 'developer@proxmox.com admin@proxmox.com', - }, - expected => { - 'mailto' => [ - 'developer@proxmox.com', - 'admin@proxmox.com', - ], - }, + description => 'mailto vzdump 2', + vzdump_param => { + 'mailto' => 'developer@proxmox.com admin@proxmox.com', + }, + expected => { + 'mailto' => [ + 'developer@proxmox.com', 'admin@proxmox.com', + ], + }, }, { - description => 'mailto vzdump 3', - vzdump_param => { - 'mailto' => 'developer@proxmox.com,admin@proxmox.com', - }, - expected => { - 'mailto' => [ - 'developer@proxmox.com', - 'admin@proxmox.com', - ], - }, + description => 'mailto vzdump 3', + vzdump_param => { + 'mailto' => 'developer@proxmox.com,admin@proxmox.com', + }, + expected => { + 'mailto' => [ + 'developer@proxmox.com', 'admin@proxmox.com', + ], + }, }, { - description => 'mailto vzdump 4', - vzdump_param => { - 'mailto' => 'developer@proxmox.com, admin@proxmox.com', - }, - expected => { - 'mailto' => [ - 'developer@proxmox.com', - 'admin@proxmox.com', - ], - }, + description => 'mailto vzdump 4', + vzdump_param => { + 'mailto' => 'developer@proxmox.com, admin@proxmox.com', + }, + expected => { + 'mailto' => [ + 'developer@proxmox.com', 'admin@proxmox.com', + ], + }, }, { - description => 'mailto vzdump 5', - vzdump_param => { - 'mailto' => ' ,,; developer@proxmox.com, ; admin@proxmox.com ', - }, - expected => { - 'mailto' => [ - 'developer@proxmox.com', - 'admin@proxmox.com', - ], - }, + description => 'mailto vzdump 5', + vzdump_param => { + 'mailto' => ' ,,; developer@proxmox.com, ; admin@proxmox.com ', + }, + expected => { + 'mailto' => [ + 'developer@proxmox.com', 'admin@proxmox.com', + ], + }, }, { - description => 'mailto vzdump 6', - vzdump_param => { - 'mailto' => '', - }, - expected => { - 'mailto' => [], - }, + description => 'mailto vzdump 6', + vzdump_param => { + 'mailto' => '', + }, + expected => { + 'mailto' => [], + }, }, { - description => 'mailto CLI 1', - cli_param => { - 'mailto' => 'developer@proxmox.com', - }, - expected => { - 'mailto' => [ - 'developer@proxmox.com', - ], - }, + description => 'mailto CLI 1', + cli_param => { + 'mailto' => 'developer@proxmox.com', + }, + expected => { + 'mailto' => [ + 'developer@proxmox.com', + ], + }, }, { - description => 'mailto CLI 2', - cli_param => { - 'mailto' => 'developer@proxmox.com admin@proxmox.com', - }, - expected => { - 'mailto' => [ - 'developer@proxmox.com', - 'admin@proxmox.com', - ], - }, + description => 'mailto CLI 2', + cli_param => { + 'mailto' => 'developer@proxmox.com admin@proxmox.com', + }, + expected => { + 'mailto' => [ + 'developer@proxmox.com', 'admin@proxmox.com', + ], + }, }, { - description => 'mailto CLI 3', - cli_param => { - 'mailto' => 'developer@proxmox.com,admin@proxmox.com', - }, - expected => { - 'mailto' => [ - 'developer@proxmox.com', - 'admin@proxmox.com', - ], - }, + description => 'mailto CLI 3', + cli_param => { + 'mailto' => 'developer@proxmox.com,admin@proxmox.com', + }, + expected => { + 'mailto' => [ + 'developer@proxmox.com', 'admin@proxmox.com', + ], + }, }, { - description => 'mailto CLI 4', - cli_param => { - 'mailto' => 'developer@proxmox.com, admin@proxmox.com', - }, - expected => { - 'mailto' => [ - 'developer@proxmox.com', - 'admin@proxmox.com', - ], - }, + description => 'mailto CLI 4', + cli_param => { + 'mailto' => 'developer@proxmox.com, admin@proxmox.com', + }, + expected => { + 'mailto' => [ + 'developer@proxmox.com', 'admin@proxmox.com', + ], + }, }, { - description => 'mailto CLI 5', - cli_param => { - 'mailto' => ' ,,; developer@proxmox.com, ; admin@proxmox.com ', - }, - expected => { - 'mailto' => [ - 'developer@proxmox.com', - 'admin@proxmox.com', - ], - }, + description => 'mailto CLI 5', + cli_param => { + 'mailto' => ' ,,; developer@proxmox.com, ; admin@proxmox.com ', + }, + expected => { + 'mailto' => [ + 'developer@proxmox.com', 'admin@proxmox.com', + ], + }, }, { - description => 'mailto both 1', - vzdump_param => { - 'mailto' => 'developer@proxmox.com', - }, - cli_param => { - 'mailto' => 'admin@proxmox.com', - }, - expected => { - 'mailto' => [ - 'admin@proxmox.com', - ], - }, + description => 'mailto both 1', + vzdump_param => { + 'mailto' => 'developer@proxmox.com', + }, + cli_param => { + 'mailto' => 'admin@proxmox.com', + }, + expected => { + 'mailto' => [ + 'admin@proxmox.com', + ], + }, }, { - description => 'mailto both 2', - vzdump_param => { - 'mailto' => 'developer@proxmox.com', - }, - cli_param => { - 'mailto' => '', - }, - expected => { - 'mailto' => [], - }, + description => 'mailto both 2', + vzdump_param => { + 'mailto' => 'developer@proxmox.com', + }, + cli_param => { + 'mailto' => '', + }, + expected => { + 'mailto' => [], + }, }, ); @@ -640,9 +632,9 @@ plan tests => scalar @tests; foreach my $test (@tests) { if (defined($test->{tested_options})) { - $tested_options = $test->{tested_options}; - ok(1, $test->{description}); - next; + $tested_options = $test->{tested_options}; + ok(1, $test->{description}); + next; } prepare_storage_config($test->{storage_param}); @@ -652,20 +644,20 @@ foreach my $test (@tests) { $test->{cli_param}->{storage} = 'local'; my $got = eval { - PVE::VZDump::verify_vzdump_parameters($test->{cli_param}, 1); - PVE::VZDump::parse_mailto_exclude_path($test->{cli_param}); + PVE::VZDump::verify_vzdump_parameters($test->{cli_param}, 1); + PVE::VZDump::parse_mailto_exclude_path($test->{cli_param}); - my $vzdump = PVE::VZDump->new('fake cmdline', $test->{cli_param}, undef); + my $vzdump = PVE::VZDump->new('fake cmdline', $test->{cli_param}, undef); - my $opts = $vzdump->{opts} or die "did not get options\n"; - die "maxfiles is defined" if defined($opts->{maxfiles}); + my $opts = $vzdump->{opts} or die "did not get options\n"; + die "maxfiles is defined" if defined($opts->{maxfiles}); - my $res = {}; - foreach my $opt (@{$tested_options}) { - next if !defined($opts->{$opt}); - $res->{$opt} = $opts->{$opt}; - } - return $res; + my $res = {}; + foreach my $opt (@{$tested_options}) { + next if !defined($opts->{$opt}); + $res->{$opt} = $opts->{$opt}; + } + return $res; }; $got = $@ if $@; diff --git a/vzdump-hook-script.pl b/vzdump-hook-script.pl index 4d530cef..578e5494 100755 --- a/vzdump-hook-script.pl +++ b/vzdump-hook-script.pl @@ -4,17 +4,18 @@ # This can also be added as a line in /etc/vzdump.conf # e.g. 'script: /usr/local/bin/vzdump-hook-script.pl' - use strict; -print "HOOK: " . join (' ', @ARGV) . "\n"; +print "HOOK: " . join(' ', @ARGV) . "\n"; my $phase = shift; -if ($phase eq 'job-init' || - $phase eq 'job-start' || - $phase eq 'job-end' || - $phase eq 'job-abort') { +if ( + $phase eq 'job-init' + || $phase eq 'job-start' + || $phase eq 'job-end' + || $phase eq 'job-abort' +) { # undef for Proxmox Backup Server storages # undef in phase 'job-init' except when --dumpdir is used directly @@ -30,21 +31,23 @@ if ($phase eq 'job-init' || # example: wake up remote storage node and enable storage if ($phase eq 'job-init') { - #system("wakeonlan AA:BB:CC:DD:EE:FF"); - #sleep(30); - #system ("/sbin/pvesm set $storeid --disable 0") == 0 || - # die "enabling storage $storeid failed"; + #system("wakeonlan AA:BB:CC:DD:EE:FF"); + #sleep(30); + #system ("/sbin/pvesm set $storeid --disable 0") == 0 || + # die "enabling storage $storeid failed"; } # do what you want -} elsif ($phase eq 'backup-start' || - $phase eq 'backup-end' || - $phase eq 'backup-abort' || - $phase eq 'log-end' || - $phase eq 'pre-stop' || - $phase eq 'pre-restart' || - $phase eq 'post-restart') { +} elsif ( + $phase eq 'backup-start' + || $phase eq 'backup-end' + || $phase eq 'backup-abort' + || $phase eq 'log-end' + || $phase eq 'pre-stop' + || $phase eq 'pre-restart' + || $phase eq 'post-restart' +) { my $mode = shift; # stop/suspend/snapshot @@ -69,7 +72,7 @@ if ($phase eq 'job-init' || print "HOOK-ENV: "; for my $var (qw(vmtype dumpdir storeid hostname target logfile)) { - print "$var=$ENV{uc($var)};" if defined($ENV{uc($var)}); + print "$var=$ENV{uc($var)};" if defined($ENV{ uc($var) }); } print "\n"; @@ -91,4 +94,4 @@ if ($phase eq 'job-init' || } -exit (0); +exit(0);