]> git.proxmox.com Git - pve-manager.git/blobdiff - PVE/API2/Ceph.pm
api: ceph: remove deprecrated config and configdb endpoints
[pve-manager.git] / PVE / API2 / Ceph.pm
index 2db9c7db32ea23dffdf23e9dbc4adb55ea56cc53..1b765b759ebfb9951fa2f5d803ea3c3bf8609ca9 100644 (file)
@@ -16,19 +16,26 @@ use PVE::RADOS;
 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::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',
@@ -54,6 +61,11 @@ __PACKAGE__->register_method ({
     path => 'fs',
 });
 
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::Ceph::Pool",
+    path => 'pool',
+});
+
 __PACKAGE__->register_method ({
     name => 'index',
     path => '',
@@ -81,150 +93,29 @@ __PACKAGE__->register_method ({
        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;
     }});
 
-__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;
-    }});
-
-__PACKAGE__->register_method ({
-    name => 'config',
-    path => 'config',
-    method => 'GET',
-    proxyto => 'node',
-    permissions => {
-       check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
-    },
-    description => "Get Ceph configuration.",
-    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);
-
-    }});
-
-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',
@@ -296,61 +187,67 @@ __PACKAGE__->register_method ({
 
        my $version = PVE::Ceph::Tools::get_local_version(1);
 
-       if (!$version || $version < 12) {
-           die "Ceph Luminous required - please run 'pveceph install'\n";
+       if (!$version || $version < 14) {
+           die "Ceph Nautilus required - please run 'pveceph install'\n";
        } else {
            PVE::Ceph::Tools::check_ceph_installed('ceph_bin');
        }
 
-       # simply load old config if it already exists
-       my $cfg = cfs_read_file('ceph.conf');
-
-       if (!$cfg->{global}) {
-
-           my $fsid;
-           my $uuid;
-
-           UUID::generate($uuid);
-           UUID::unparse($uuid, $fsid);
-
-           my $auth = $param->{disable_cephx} ? 'none' : 'cephx';
+       my $auth = $param->{disable_cephx} ? 'none' : 'cephx';
 
-           $cfg->{global} = {
-               'fsid' => $fsid,
-               'auth cluster required' => $auth,
-               'auth service required' => $auth,
-               'auth client required' => $auth,
-               'osd journal size' => $pve_osd_default_journal_size,
-               '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,
-       }
+       # 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,
+           }
 
-       $cfg->{global}->{keyring} = '/etc/pve/priv/$cluster.$name.keyring';
-       $cfg->{osd}->{keyring} = '/var/lib/ceph/osd/ceph-$id/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};
-           $cfg->{global}->{'osd pgp bits'} = $param->{pg_bits};
-       }
+           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->{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);
 
-       PVE::Ceph::Tools::setup_pve_symlinks();
+           if ($auth eq 'cephx') {
+               PVE::Ceph::Tools::get_or_create_admin_keyring();
+           }
+           PVE::Ceph::Tools::setup_pve_symlinks();
+       });
+       die $@ if $@;
 
        return undef;
     }});
@@ -374,7 +271,7 @@ __PACKAGE__->register_method ({
                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.')?',
            },
        },
     },
@@ -425,7 +322,7 @@ __PACKAGE__->register_method ({
                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.')?',
            },
        },
     },
@@ -476,7 +373,7 @@ __PACKAGE__->register_method ({
                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.')?',
            },
        },
     },
@@ -530,393 +427,7 @@ __PACKAGE__->register_method ({
 
        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();
     }});
 
 
@@ -1047,7 +558,12 @@ __PACKAGE__->register_method ({
        type => 'array',
        items => {
            type => "object",
-           properties => {},
+           properties => {
+               name => {
+                   description => "Name of the CRUSH rule.",
+                   type => "string",
+               }
+           },
        },
        links => [ { rel => 'child', href => "{name}" } ],
     },
@@ -1069,4 +585,100 @@ __PACKAGE__->register_method ({
        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;