use PVE::RESTHandler;
use PVE::RPCEnvironment;
use PVE::Storage;
-use PVE::Tools qw(run_command file_get_contents file_set_contents);
+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',
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 => '',
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 => 'fs' },
- { name => 'mds' },
- { name => 'stop' },
- { name => 'start' },
{ name => 'restart' },
- { name => 'status' },
- { name => 'crush' },
- { name => 'config' },
- { name => 'log' },
- { name => 'disks' },
- { name => 'flags' },
{ name => 'rules' },
+ { name => 'start' },
+ { name => 'status' },
+ { name => 'stop' },
];
return $result;
}});
+
+# TODO: deprecrated, remove with PVE 8
__PACKAGE__->register_method ({
- name => 'disks',
- path => 'disks',
+ name => 'config',
+ path => 'config',
method => 'GET',
- description => "List local disks.",
proxyto => 'node',
- protected => 1,
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'),
- type => {
- description => "Only list specific types of disks.",
- type => 'string',
- enum => ['unused', 'journal_disks'],
- optional => 1,
- },
},
},
- returns => {
- type => 'array',
- items => {
- type => "object",
- properties => {
- dev => { type => 'string' },
- used => { type => 'string', optional => 1 },
- gpt => { type => 'boolean' },
- size => { type => 'integer' },
- osdid => { type => 'integer' },
- vendor => { type => 'string', optional => 1 },
- model => { type => 'string', optional => 1 },
- serial => { type => 'string', optional => 1 },
- },
- },
- # links => [ { rel => 'child', href => "{}" } ],
- },
+ returns => { type => 'string' },
code => sub {
my ($param) = @_;
PVE::Ceph::Tools::check_ceph_inited();
- my $disks = PVE::Diskmanage::get_disks(undef, 1);
-
- my $res = [];
- foreach my $dev (keys %$disks) {
- my $d = $disks->{$dev};
- if ($param->{type}) {
- if ($param->{type} eq 'journal_disks') {
- next if $d->{osdid} >= 0;
- next if !$d->{gpt};
- } elsif ($param->{type} eq 'unused') {
- next if $d->{used};
- } else {
- die "internal error"; # should not happen
- }
- }
-
- $d->{dev} = "/dev/$dev";
- push @$res, $d;
- }
+ my $path = PVE::Ceph::Tools::get_config('pve_ceph_cfgpath');
+ return file_get_contents($path);
- return $res;
}});
+# TODO: deprecrated, remove with PVE 8
__PACKAGE__->register_method ({
- name => 'config',
- path => 'config',
+ name => 'configdb',
+ path => 'configdb',
method => 'GET',
proxyto => 'node',
+ protected => 1,
permissions => {
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
},
- description => "Get Ceph configuration.",
+ 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 => 'string' },
+ 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 $path = PVE::Ceph::Tools::get_config('pve_ceph_cfgpath');
- return file_get_contents($path);
+ 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 $add_storage = sub {
- my ($pool, $storeid) = @_;
-
- my $storage_params = {
- type => 'rbd',
- pool => $pool,
- storage => $storeid,
- krbd => 0,
- content => 'rootdir,images',
- };
-
- PVE::API2::Storage::Config->create($storage_params);
-};
-
-my $get_storages = sub {
- my ($pool) = @_;
-
- my $cfg = PVE::Storage::config();
-
- my $storages = $cfg->{ids};
- my $res = {};
- foreach my $storeid (keys %$storages) {
- my $curr = $storages->{$storeid};
- $res->{$storeid} = $storages->{$storeid}
- if $curr->{type} eq 'rbd' && $pool eq $curr->{pool};
- }
-
- return $res;
-};
-
__PACKAGE__->register_method ({
name => 'init',
path => 'init',
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');
UUID::generate($uuid);
UUID::unparse($uuid, $fsid);
- my $auth = $param->{disable_cephx} ? 'none' : 'cephx';
-
$cfg->{global} = {
'fsid' => $fsid,
'auth cluster required' => $auth,
#'osd pool default pgp num' => $pg_num,
}
- $cfg->{client}->{keyring} = '/etc/pve/priv/$cluster.$name.keyring';
+ 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};
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;
}});
type => 'string',
optional => 1,
default => 'ceph.target',
- pattern => '(ceph|mon|mds|osd|mgr)\.[A-Za-z0-9\-]{1,32}',
+ pattern => '(ceph|mon|mds|osd|mgr)(\.'.PVE::Ceph::Services::SERVICE_REGEX.')?',
},
},
},
type => 'string',
optional => 1,
default => 'ceph.target',
- pattern => '(ceph|mon|mds|osd|mgr)\.[A-Za-z0-9\-]{1,32}',
+ pattern => '(ceph|mon|mds|osd|mgr)(\.'.PVE::Ceph::Services::SERVICE_REGEX.')?',
},
},
},
type => 'string',
optional => 1,
default => 'ceph.target',
- pattern => '(mon|mds|osd|mgr)\.[A-Za-z0-9\-]{1,32}',
+ pattern => '(mon|mds|osd|mgr)(\.'.PVE::Ceph::Services::SERVICE_REGEX.')?',
},
},
},
PVE::Ceph::Tools::check_ceph_inited();
- my $rados = PVE::RADOS->new();
- my $status = $rados->mon_command({ prefix => 'status' });
- $status->{health} = $rados->mon_command({ prefix => 'health', detail => 'detail' });
- return $status;
- }});
-
-__PACKAGE__->register_method ({
- name => 'lspools',
- path => 'pools',
- method => 'GET',
- description => "List all pools.",
- 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 => {
- pool => { type => 'integer' },
- pool_name => { type => 'string' },
- size => { type => 'integer' },
- },
- },
- links => [ { rel => 'child', href => "{pool_name}" } ],
- },
- code => sub {
- my ($param) = @_;
-
- PVE::Ceph::Tools::check_ceph_inited();
-
- my $rados = PVE::RADOS->new();
-
- 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};
- }
-
- $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 $data = [];
- foreach my $e (@{$res->{pools}}) {
- my $d = {};
- foreach my $attr (qw(pool pool_name size min_size pg_num crush_rule)) {
- $d->{$attr} = $e->{$attr} if defined($e->{$attr});
- }
-
- 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};
- }
- push @$data, $d;
- }
-
-
- return $data;
- }});
-
-__PACKAGE__->register_method ({
- name => 'createpool',
- path => 'pools',
- method => 'POST',
- description => "Create POOL",
- proxyto => 'node',
- protected => 1,
- permissions => {
- 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',
- },
- size => {
- description => 'Number of replicas per object',
- type => 'integer',
- default => 3,
- optional => 1,
- minimum => 1,
- maximum => 7,
- },
- min_size => {
- description => 'Minimum number of replicas per object',
- type => 'integer',
- default => 2,
- optional => 1,
- minimum => 1,
- maximum => 7,
- },
- pg_num => {
- description => "Number of placement groups.",
- type => 'integer',
- default => 128,
- optional => 1,
- minimum => 8,
- maximum => 32768,
- },
- crush_rule => {
- description => "The rule to use for mapping object placement in the cluster.",
- type => 'string',
- optional => 1,
- },
- application => {
- description => "The application of the pool, 'rbd' by default.",
- type => 'string',
- enum => ['rbd', 'cephfs', 'rgw'],
- optional => 1,
- },
- add_storages => {
- description => "Configure VM and CT storage using the new pool.",
- type => 'boolean',
- optional => 1,
- },
- },
- },
- returns => { type => 'string' },
- code => sub {
- my ($param) = @_;
-
- PVE::Cluster::check_cfs_quorum();
- PVE::Ceph::Tools::check_ceph_inited();
-
- my $pve_ckeyring_path = PVE::Ceph::Tools::get_config('pve_ckeyring_path');
-
- die "not fully configured - missing '$pve_ckeyring_path'\n"
- if ! -f $pve_ckeyring_path;
-
- my $pool = $param->{name};
- my $rpcenv = PVE::RPCEnvironment::get();
- my $user = $rpcenv->get_user();
-
- if ($param->{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);
- }
-
- my $pg_num = $param->{pg_num} || 128;
- my $size = $param->{size} || 3;
- my $min_size = $param->{min_size} || 2;
- my $application = $param->{application} // 'rbd';
-
- my $worker = sub {
-
- PVE::Ceph::Tools::create_pool($pool, $param);
-
- if ($param->{add_storages}) {
- my $err;
- eval { $add_storage->($pool, "${pool}"); };
- if ($@) {
- warn "failed to add storage: $@";
- $err = 1;
- }
- die "adding storage for pool '$pool' failed, check log and add manually!\n"
- if $err;
- }
- };
-
- return $rpcenv->fork_worker('cephcreatepool', $pool, $user, $worker);
- }});
-
-__PACKAGE__->register_method ({
- name => 'get_flags',
- path => 'flags',
- method => 'GET',
- description => "get all set ceph flags",
- proxyto => 'node',
- protected => 1,
- permissions => {
- check => ['perm', '/', [ 'Sys.Audit' ]],
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- node => get_standard_option('pve-node'),
- },
- },
- returns => { type => 'string' },
- code => sub {
- my ($param) = @_;
-
- PVE::Ceph::Tools::check_ceph_inited();
-
- my $pve_ckeyring_path = PVE::Ceph::Tools::get_config('pve_ckeyring_path');
-
- die "not fully configured - missing '$pve_ckeyring_path'\n"
- if ! -f $pve_ckeyring_path;
-
- my $rados = PVE::RADOS->new();
-
- my $stat = $rados->mon_command({ prefix => 'osd dump' });
-
- return $stat->{flags} // '';
- }});
-
-__PACKAGE__->register_method ({
- name => 'set_flag',
- path => 'flags/{flag}',
- method => 'POST',
- description => "Set a ceph flag",
- proxyto => 'node',
- protected => 1,
- permissions => {
- check => ['perm', '/', [ 'Sys.Modify' ]],
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- node => get_standard_option('pve-node'),
- flag => {
- description => 'The ceph flag to set/unset',
- type => 'string',
- enum => [ 'full', 'pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill', 'norebalance', 'norecover', 'noscrub', 'nodeep-scrub', 'notieragent'],
- },
- },
- },
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- PVE::Ceph::Tools::check_ceph_inited();
-
- my $pve_ckeyring_path = PVE::Ceph::Tools::get_config('pve_ckeyring_path');
-
- die "not fully configured - missing '$pve_ckeyring_path'\n"
- if ! -f $pve_ckeyring_path;
-
- my $set = $param->{set} // !$param->{unset};
- my $rados = PVE::RADOS->new();
-
- $rados->mon_command({
- prefix => "osd set",
- key => $param->{flag},
- });
-
- return undef;
- }});
-
-__PACKAGE__->register_method ({
- name => 'unset_flag',
- path => 'flags/{flag}',
- method => 'DELETE',
- description => "Unset a ceph flag",
- proxyto => 'node',
- protected => 1,
- permissions => {
- check => ['perm', '/', [ 'Sys.Modify' ]],
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- node => get_standard_option('pve-node'),
- flag => {
- description => 'The ceph flag to set/unset',
- type => 'string',
- enum => [ 'full', 'pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill', 'norebalance', 'norecover', 'noscrub', 'nodeep-scrub', 'notieragent'],
- },
- },
- },
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- PVE::Ceph::Tools::check_ceph_inited();
-
- my $pve_ckeyring_path = PVE::Ceph::Tools::get_config('pve_ckeyring_path');
-
- die "not fully configured - missing '$pve_ckeyring_path'\n"
- if ! -f $pve_ckeyring_path;
-
- my $set = $param->{set} // !$param->{unset};
- my $rados = PVE::RADOS->new();
-
- $rados->mon_command({
- prefix => "osd unset",
- key => $param->{flag},
- });
-
- return undef;
- }});
-
-__PACKAGE__->register_method ({
- name => 'destroypool',
- path => 'pools/{name}',
- method => 'DELETE',
- description => "Destroy pool",
- proxyto => 'node',
- protected => 1,
- permissions => {
- 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,
- },
- },
- },
- returns => { type => 'string' },
- code => sub {
- my ($param) = @_;
-
- 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 $pool = $param->{name};
-
- 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};
-
- # 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;
- }
- }
-
- PVE::Ceph::Tools::destroy_pool($pool);
-
- 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);
+ return PVE::Ceph::Tools::ceph_cluster_status();
}});
type => 'array',
items => {
type => "object",
- properties => {},
+ properties => {
+ name => {
+ description => "Name of the CRUSH rule.",
+ type => "string",
+ }
+ },
},
links => [ { rel => 'child', href => "{name}" } ],
},
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;