pve-manager/PVE/API2/Ceph.pm
Aaron Lauterer 26cf20bc0f api: ceph: add ceph/cfg path, deprecate ceph/config and ceph/configdb
Consolidating the different config paths lets us add more as needed
without polluting our API with too many 'configxxx' endpoints.

The config and configdb paths are renamed under the ceph/cfg path:
* config -> raw (returns the ceph.conf file as is)
* configdb -> db (returns the ceph config db contents)

The old paths are still available and need to be dropped at some point.

Signed-off-by: Aaron Lauterer <a.lauterer@proxmox.com>
Reviewed-by: Dominik Csapak <d.csapak@proxmox.com>
Tested-by:  Dominik Csapak <d.csapak@proxmox.com>
2023-03-20 15:31:04 +01:00

765 lines
17 KiB
Perl

package PVE::API2::Ceph;
use strict;
use warnings;
use File::Path;
use Net::IP;
use UUID;
use PVE::Ceph::Tools;
use PVE::Ceph::Services;
use PVE::Cluster qw(cfs_read_file cfs_write_file);
use PVE::JSONSchema qw(get_standard_option);
use PVE::Network;
use PVE::RADOS;
use PVE::RESTHandler;
use PVE::RPCEnvironment;
use PVE::Storage;
use PVE::Tools qw(run_command file_get_contents file_set_contents extract_param);
use PVE::API2::Ceph::Cfg;
use PVE::API2::Ceph::OSD;
use PVE::API2::Ceph::FS;
use PVE::API2::Ceph::MDS;
use PVE::API2::Ceph::MGR;
use PVE::API2::Ceph::MON;
use PVE::API2::Ceph::Pool;
use PVE::API2::Ceph::Pools;
use PVE::API2::Storage::Config;
use base qw(PVE::RESTHandler);
my $pve_osd_default_journal_size = 1024*5;
__PACKAGE__->register_method ({
subclass => "PVE::API2::Ceph::Cfg",
path => 'cfg',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Ceph::OSD",
path => 'osd',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Ceph::MDS",
path => 'mds',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Ceph::MGR",
path => 'mgr',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Ceph::MON",
path => 'mon',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Ceph::FS",
path => 'fs',
});
__PACKAGE__->register_method ({
subclass => "PVE::API2::Ceph::Pool",
path => 'pool',
});
# TODO: deprecrated, remove with PVE 8
__PACKAGE__->register_method ({
subclass => "PVE::API2::Ceph::Pools",
path => 'pools',
});
__PACKAGE__->register_method ({
name => 'index',
path => '',
method => 'GET',
description => "Directory index.",
permissions => { user => 'all' },
permissions => {
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
},
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 => 'cmd-safety' },
{ name => 'cfg' },
{ name => 'config' },
{ name => 'configdb' },
{ name => 'crush' },
{ name => 'fs' },
{ name => 'init' },
{ name => 'log' },
{ name => 'mds' },
{ name => 'mgr' },
{ name => 'mon' },
{ name => 'osd' },
{ name => 'pools' },
{ name => 'restart' },
{ name => 'rules' },
{ name => 'start' },
{ name => 'status' },
{ name => 'stop' },
];
return $result;
}});
# TODO: deprecrated, remove with PVE 8
__PACKAGE__->register_method ({
name => 'config',
path => 'config',
method => 'GET',
proxyto => 'node',
permissions => {
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
},
description => "Get the Ceph configuration file. Deprecated, please use `/nodes/{node}/ceph/cfg/raw.",
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => { type => 'string' },
code => sub {
my ($param) = @_;
PVE::Ceph::Tools::check_ceph_inited();
my $path = PVE::Ceph::Tools::get_config('pve_ceph_cfgpath');
return file_get_contents($path);
}});
# TODO: deprecrated, remove with PVE 8
__PACKAGE__->register_method ({
name => 'configdb',
path => 'configdb',
method => 'GET',
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
},
description => "Get the Ceph configuration database. Deprecated, please use `/nodes/{node}/ceph/cfg/db.",
parameters => {
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" },
},
},
},
code => sub {
my ($param) = @_;
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;
}});
__PACKAGE__->register_method ({
name => 'init',
path => 'init',
method => 'POST',
description => "Create initial ceph default configuration and setup symlinks.",
proxyto => 'node',
protected => 1,
permissions => {
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,
},
pg_bits => {
description => "Placement group bits, used to specify the " .
"default number of placement groups.\n\nNOTE: 'osd pool " .
"default pg num' does not work for default pools.",
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 $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');
}
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');
if (!$cfg->{global}) {
my $fsid;
my $uuid;
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',
};
# 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 ($param->{pg_bits}) {
$cfg->{global}->{'osd pg bits'} = $param->{pg_bits};
$cfg->{global}->{'osd pgp bits'} = $param->{pg_bits};
}
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'};
}
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 $@;
return undef;
}});
__PACKAGE__->register_method ({
name => 'stop',
path => 'stop',
method => 'POST',
description => "Stop ceph services.",
proxyto => 'node',
protected => 1,
permissions => {
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.')?',
},
},
},
returns => { type => 'string' },
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
PVE::Ceph::Tools::check_ceph_inited();
my $cfg = cfs_read_file('ceph.conf');
scalar(keys %$cfg) || die "no configuration\n";
my $worker = sub {
my $upid = shift;
my $cmd = ['stop'];
if ($param->{service}) {
push @$cmd, $param->{service};
}
PVE::Ceph::Services::ceph_service_cmd(@$cmd);
};
return $rpcenv->fork_worker('srvstop', $param->{service} || 'ceph',
$authuser, $worker);
}});
__PACKAGE__->register_method ({
name => 'start',
path => 'start',
method => 'POST',
description => "Start ceph services.",
proxyto => 'node',
protected => 1,
permissions => {
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.')?',
},
},
},
returns => { type => 'string' },
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
PVE::Ceph::Tools::check_ceph_inited();
my $cfg = cfs_read_file('ceph.conf');
scalar(keys %$cfg) || die "no configuration\n";
my $worker = sub {
my $upid = shift;
my $cmd = ['start'];
if ($param->{service}) {
push @$cmd, $param->{service};
}
PVE::Ceph::Services::ceph_service_cmd(@$cmd);
};
return $rpcenv->fork_worker('srvstart', $param->{service} || 'ceph',
$authuser, $worker);
}});
__PACKAGE__->register_method ({
name => 'restart',
path => 'restart',
method => 'POST',
description => "Restart ceph services.",
proxyto => 'node',
protected => 1,
permissions => {
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.')?',
},
},
},
returns => { type => 'string' },
code => sub {
my ($param) = @_;
my $rpcenv = PVE::RPCEnvironment::get();
my $authuser = $rpcenv->get_user();
PVE::Ceph::Tools::check_ceph_inited();
my $cfg = cfs_read_file('ceph.conf');
scalar(keys %$cfg) || die "no configuration\n";
my $worker = sub {
my $upid = shift;
my $cmd = ['restart'];
if ($param->{service}) {
push @$cmd, $param->{service};
}
PVE::Ceph::Services::ceph_service_cmd(@$cmd);
};
return $rpcenv->fork_worker('srvrestart', $param->{service} || 'ceph',
$authuser, $worker);
}});
__PACKAGE__->register_method ({
name => 'status',
path => 'status',
method => 'GET',
description => "Get ceph status.",
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => { type => 'object' },
code => sub {
my ($param) = @_;
PVE::Ceph::Tools::check_ceph_inited();
return PVE::Ceph::Tools::ceph_cluster_status();
}});
__PACKAGE__->register_method ({
name => 'crush',
path => 'crush',
method => 'GET',
description => "Get OSD crush map",
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
},
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
},
},
returns => { type => 'string' },
code => sub {
my ($param) = @_;
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);
my $txt = '';
my $mapfile = "/var/tmp/ceph-crush.map.$$";
my $mapdata = "/var/tmp/ceph-crush.txt.$$";
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 = $@;
unlink $mapfile;
unlink $mapdata;
die $err if $err;
return $txt;
}});
__PACKAGE__->register_method({
name => 'log',
path => 'log',
method => 'GET',
description => "Read ceph log",
proxyto => 'node',
permissions => {
check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
},
protected => 1,
parameters => {
additionalProperties => 0,
properties => {
node => get_standard_option('pve-node'),
start => {
type => 'integer',
minimum => 0,
optional => 1,
},
limit => {
type => 'integer',
minimum => 0,
optional => 1,
},
},
},
returns => {
type => 'array',
items => {
type => "object",
properties => {
n => {
description=> "Line number",
type=> 'integer',
},
t => {
description=> "Line text",
type => 'string',
}
}
}
},
code => sub {
my ($param) = @_;
PVE::Ceph::Tools::check_ceph_inited();
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});
$rpcenv->set_result_attrib('total', $count);
return $lines;
}});
__PACKAGE__->register_method ({
name => 'rules',
path => 'rules',
method => 'GET',
description => "List ceph rules.",
proxyto => 'node',
protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
},
parameters => {
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}" } ],
},
code => sub {
my ($param) = @_;
PVE::Ceph::Tools::check_ceph_inited();
my $rados = PVE::RADOS->new();
my $rules = $rados->mon_command({ prefix => 'osd crush rule ls' });
my $res = [];
foreach my $rule (@$rules) {
push @$res, { name => $rule };
}
return $res;
}});
__PACKAGE__->register_method ({
name => 'cmd_safety',
path => 'cmd-safety',
method => 'GET',
description => "Heuristical check if it is safe to perform an action.",
proxyto => 'node',
protected => 1,
permissions => {
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'],
},
},
},
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.'
},
},
},
code => sub {
my ($param) = @_;
PVE::Ceph::Tools::check_ceph_inited();
my $id = $param->{id};
my $service = $param->{service};
my $action = $param->{action};
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',
},
};
die "Service does not support this action: ${service}: ${action}\n"
if !$supported_actions->{$service}->{$action};
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 ];
}
$result = $rados->mon_cmd($params, 1);
die $@ if $@;
$result->{safe} = $result->{return_code} == 0 ? 1 : 0;
$result->{status} = $result->{status_message};
return $result;
}});
1;