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' }, # FIXME: remove with 7.0
- { name => 'flag' },
{ name => 'rules' },
+ { name => 'start' },
+ { name => 'status' },
+ { name => 'stop' },
];
return $result;
}});
-__PACKAGE__->register_method ({
- name => 'disks',
- path => 'disks',
- method => 'GET',
- description => "List local disks.",
- proxyto => 'node',
- protected => 1,
- permissions => {
- check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
- },
- 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 => "{}" } ],
- },
- 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;
- }
-
- return $res;
- }});
+# TODO: deprecrated, remove with PVE 8
__PACKAGE__->register_method ({
name => 'config',
path => 'config',
permissions => {
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
},
- description => "Get Ceph configuration.",
+ description => "Get the Ceph configuration file. Deprecated, please use `/nodes/{node}/ceph/cfg/raw.",
parameters => {
additionalProperties => 0,
properties => {
}});
+# TODO: deprecrated, remove with PVE 8
__PACKAGE__->register_method ({
name => 'configdb',
path => 'configdb',
permissions => {
check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
},
- description => "Get Ceph configuration database.",
+ description => "Get the Ceph configuration database. Deprecated, please use `/nodes/{node}/ceph/cfg/db.",
parameters => {
additionalProperties => 0,
properties => {
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',
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_configured();
-
- 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);
- }});
-
-my $possible_flags = PVE::Ceph::Tools::get_possible_osd_flags();
-my $possible_flags_list = [ sort keys %$possible_flags ];
-
-my $get_current_set_flags = sub {
- my $rados = shift;
-
- $rados //= PVE::RADOS->new();
-
- my $stat = $rados->mon_command({ prefix => 'osd dump' });
- my $setflags = $stat->{flags} // '';
- return { map { $_ => 1 } PVE::Tools::split_list($setflags) };
-};
-
-__PACKAGE__->register_method ({
- name => 'flag',
- path => 'flag',
- method => 'GET',
- description => "get the status of all ceph flags",
- proxyto => 'node',
- protected => 1,
- permissions => {
- check => ['perm', '/', [ 'Sys.Audit' ]],
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- node => get_standard_option('pve-node'),
- },
- },
- returns => { type => 'array' },
- code => sub {
- my ($param) = @_;
-
- PVE::Ceph::Tools::check_ceph_configured();
-
- 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 $realflag = PVE::Ceph::Tools::get_real_flag_name($flag);
- if ($setflags->{$realflag}) {
- $el->{value} = 1;
- }
-
- push @$res, $el;
- }
-
- return $res;
- }});
-
-# FIXME: Remove with PVE 7.0
-__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_configured();
-
- my $rados = PVE::RADOS->new();
-
- my $stat = $rados->mon_command({ prefix => 'osd dump' });
-
- return $stat->{flags} // '';
- }});
-
-__PACKAGE__->register_method ({
- name => 'set_flags',
- path => 'flag',
- method => 'PUT',
- description => "Set/Unset multiple ceph flags at once.",
- proxyto => 'node',
- protected => 1,
- permissions => {
- check => ['perm', '/', [ 'Sys.Modify' ]],
- },
- parameters => {
- additionalProperties => 0,
- properties => {
- node => get_standard_option('pve-node'),
- %$possible_flags,
- },
- },
- returns => { type => 'string' },
- code => sub {
- my ($param) = @_;
-
- 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 $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);
-
- 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++;
- }
- }
-
- if ($errors) {
- die "could not set/unset $errors flags\n";
- }
- };
-
- return $rpcenv->fork_worker('cephsetflags', undef, $user, $worker);
- }});
-
-# FIXME: Remove with PVE 7.0
-__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',
- type => 'string',
- enum => $possible_flags_list,
- },
- },
- },
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- PVE::Ceph::Tools::check_ceph_configured();
-
- 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 unset',
- type => 'string',
- enum => $possible_flags_list,
- },
- },
- },
- returns => { type => 'null' },
- code => sub {
- my ($param) = @_;
-
- PVE::Ceph::Tools::check_ceph_configured();
-
- 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;