]> git.proxmox.com Git - pve-manager.git/blobdiff - PVE/CLI/pveceph.pm
pveceph: adapt to new Pool module
[pve-manager.git] / PVE / CLI / pveceph.pm
index 831ad007fce66d5c058f4192e8dfbd50e1f02bc0..52c91629861e54e98fa1858c745e1710123030a5 100755 (executable)
@@ -18,6 +18,7 @@ use PVE::Storage;
 use PVE::Tools qw(run_command);
 use PVE::JSONSchema qw(get_standard_option);
 use PVE::Ceph::Tools;
+use PVE::Ceph::Services;
 use PVE::API2::Ceph;
 use PVE::API2::Ceph::FS;
 use PVE::API2::Ceph::MDS;
@@ -34,7 +35,7 @@ my $nodename = PVE::INotify::nodename();
 my $upid_exit = sub {
     my $upid = shift;
     my $status = PVE::Tools::upid_read_status($upid);
-    exit($status eq 'OK' ? 0 : -1);
+    exit(PVE::Tools::upid_status_is_error($status) ? -1 : 0);
 };
 
 sub setup_environment {
@@ -49,29 +50,65 @@ __PACKAGE__->register_method ({
     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,
+           },
        },
     },
     returns => { type => 'null' },
     code => sub {
        my ($param) = @_;
 
-       my $monstat;
+       my $message;
+       my $pools = [];
+       my $monstat = {};
+       my $mdsstat = {};
+       my $osdstat = [];
 
        eval {
            my $rados = PVE::RADOS->new();
-           my $monstat = $rados->mon_command({ prefix => 'mon_status' });
+           $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' });
        };
-       my $err = $@;
+       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;
+
+       # 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;
 
-       die "detected running ceph services- unable to purge data\n"
-           if !$err;
+       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};
 
-       # fixme: this is dangerous - should we really support this function?
-       PVE::Ceph::Tools::purge_all_ceph_files();
+       PVE::Ceph::Tools::purge_all_ceph_services($services);
+       PVE::Ceph::Tools::purge_all_ceph_files($services);
 
        return undef;
     }});
 
+my $supported_ceph_versions = ['octopus', 'pacific', 'quincy'];
+my $default_ceph_version = 'pacific';
+
 __PACKAGE__->register_method ({
     name => 'install',
     path => 'install',
@@ -82,109 +119,275 @@ __PACKAGE__->register_method ({
        properties => {
            version => {
                type => 'string',
-               #enum => ['hammer', 'jewel'], # for jessie
-               enum => ['luminous',], # for stretch
+               enum => $supported_ceph_versions,
+               default => $default_ceph_version,
+               description => "Ceph version to install.",
                optional => 1,
-           }
+           },
+           'allow-experimental' => {
+               type => 'boolean',
+               default => 0,
+               optional => 1,
+               description => "Allow experimental versions. Use with care!",
+           },
+           'test-repository' => {
+               type => 'boolean',
+               default => 0,
+               optional => 1,
+               description => "Use the test, not the main repository. Use with care!",
+           },
        },
     },
     returns => { type => 'null' },
     code => sub {
        my ($param) = @_;
 
-       my $cephver = $param->{version} || 'luminous';
+       my $cephver = $param->{version} || $default_ceph_version;
 
-       local $ENV{DEBIAN_FRONTEND} = 'noninteractive';
+       my $repo = $param->{'test-repository'} ? 'test' : 'main';
 
-       if ($cephver eq 'luminous') {
-           PVE::Tools::file_set_contents("/etc/apt/sources.list.d/ceph.list",
-               "deb http://download.proxmox.com/debian/ceph-luminous stretch main\n");
+       my $repolist;
+       if ($cephver eq 'octopus') {
+           warn "Ceph Octopus will go EOL after 2022-07\n";
+           $repolist = "deb http://download.proxmox.com/debian/ceph-octopus bullseye $repo\n";
+       } elsif ($cephver eq 'pacific') {
+           $repolist = "deb http://download.proxmox.com/debian/ceph-pacific bullseye $repo\n";
+       } elsif ($cephver eq 'quincy') {
+           $repolist = "deb http://download.proxmox.com/debian/ceph-quincy bullseye $repo\n";
        } else {
-           # use fixed devel repo for now, because there is no officila repo for jessie
-           my $devrepo = undef;
-
-           my $keyurl = $devrepo ?
-               "https://git.ceph.com/?p=ceph.git;a=blob_plain;f=keys/autobuild.asc" :
-               "https://git.ceph.com/?p=ceph.git;a=blob_plain;f=keys/release.asc";
-
-           print "download and import ceph repository keys\n";
-
-           # Note: wget on Debian wheezy cannot handle new ceph.com certificates, so
-           # we use LWP::UserAgent
-           #system("wget -q -O- '$keyurl'| apt-key add - 2>&1 >/dev/null") == 0 ||
-           #die "unable to download ceph release key\n";
-
-           my $tmp_key_file = "/tmp/ceph-release-keys.asc";
-           my $ua = LWP::UserAgent->new(protocols_allowed => ['http', 'https'], timeout => 120);
-           $ua->env_proxy;
-           my $response = $ua->get($keyurl);
-           if ($response->is_success) {
-               my $data = $response->decoded_content;
-               PVE::Tools::file_set_contents($tmp_key_file, $data);
-           } else {
-               die "unable to download ceph release key: " . $response->status_line . "\n";
-           }
-
-           system("apt-key add $tmp_key_file 2>&1 >/dev/null") == 0 ||
-               die "unable to download ceph release key\n";
+           die "unsupported ceph version: $cephver";
+       }
+       PVE::Tools::file_set_contents("/etc/apt/sources.list.d/ceph.list", $repolist);
 
-           unlink $tmp_key_file;
+       my $supported_re = join('|', $supported_ceph_versions->@*);
+       warn "WARNING: installing non-default ceph release '$cephver'!\n" if $cephver !~ qr/^(?:$supported_re)$/;
 
-           my $source = $devrepo ?
-               "deb http://gitbuilder.ceph.com/ceph-deb-jessie-x86_64-basic/ref/$devrepo jessie main\n" :
-               "deb http://download.ceph.com/debian-$cephver jessie main\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" },
+           )
+       };
 
-           PVE::Tools::file_set_contents("/etc/apt/sources.list.d/ceph.list", $source);
+       my @apt_install = qw(apt-get --no-install-recommends -o Dpkg::Options::=--force-confnew install --);
+       my @ceph_packages = qw(
+           ceph
+           ceph-common
+           ceph-mds
+           ceph-fuse
+           gdisk
+           nvme-cli
+       );
+
+       # got split out with quincy and is required by PVE tooling, conditionally exclude it for older
+       # FIXME: remove condition with PVE 8.0, i.e., once we only support quincy+ new installations
+       if ($cephver ne 'octopus' and $cephver ne 'pacific') {
+           push @ceph_packages, 'ceph-volume';
        }
 
-       print "update available package list\n";
-       eval { run_command(['apt-get', '-q', 'update'], outfunc => sub {}, errfunc => sub {}); };
-
        print "start installation\n";
-       system('apt-get', '--no-install-recommends',
-               '-o', 'Dpkg::Options::=--force-confnew',
-               'install', '--',
-               'ceph', 'ceph-common', 'ceph-mds', 'ceph-fuse', 'gdisk') == 0
-           || die "apt failed during ceph installation ($?)\n";
-
-       print "\ninstalled ceph $cephver successfully\n";
-
-       if (PVE::Ceph::Tools::systemd_managed() && ! -e '/etc/systemd/system/ceph.service') {
-           #to disable old SysV init scripts.
-           print "replacing ceph init script with own ceph.service\n";
-           eval {
-               PVE::Tools::run_command('cp -v /usr/share/doc/pve-manager/examples/ceph.service /etc/systemd/system/ceph.service');
-               PVE::Tools::run_command('systemctl daemon-reload');
-               PVE::Tools::run_command('systemctl enable ceph.service');
-           };
-           warn "could not install ceph.service\n" if $@;
+
+       # this flag helps to determine when apt is actually done installing (vs. partial extracing)
+       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";
        }
 
+       print "\ninstalled ceph $cephver 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'
+       ]);
+
+       return undef;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'status',
+    path => 'status',
+    method => 'GET',
+    description => "Get Ceph Status.",
+    parameters => {
+       additionalProperties => 0,
+    },
+    returns => { type => 'null' },
+    code => sub {
+       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;
     }});
 
+my $get_storages = sub {
+    my ($fs, $is_default) = @_;
+
+    my $cfg = PVE::Storage::config();
+
+    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);
+    }
+
+    return $res;
+};
+
+__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,
+           },
+       },
+    },
+    returns => { type => 'string' },
+    code => sub {
+       my ($param) = @_;
+
+       PVE::Ceph::Tools::check_ceph_inited();
+
+       my $rpcenv = PVE::RPCEnvironment::get();
+       my $user = $rpcenv->get_user();
+
+       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 $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);
+
+               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;
+           }
+
+           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 $@;
+
+               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);
+    }});
+
 our $cmddef = {
     init => [ 'PVE::API2::Ceph', 'init', [], { node => $nodename } ],
     pool => {
-       ls => [ 'PVE::API2::Ceph', 'lspools', [], { node => $nodename }, sub {
-           my $res = shift;
-
-           printf("%-20s %10s %10s %10s %10s %20s\n", "Name", "size", "min_size",
-                   "pg_num", "%-used", "used");
-           foreach my $p (sort {$a->{pool_name} cmp $b->{pool_name}} @$res) {
-               printf("%-20s %10d %10d %10d %10.2f %20d\n", $p->{pool_name},
-                       $p->{size}, $p->{min_size}, $p->{pg_num},
-                       $p->{percent_used}, $p->{bytes_used});
-           }
-       }],
-       create => [ 'PVE::API2::Ceph', 'createpool', ['name'], { node => $nodename }],
-       destroy => [ 'PVE::API2::Ceph', 'destroypool', ['name'], { node => $nodename } ],
+       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 }],
     },
     osd => {
        create => [ 'PVE::API2::Ceph::OSD', 'createosd', ['dev'], { node => $nodename }, $upid_exit],
@@ -208,15 +411,11 @@ our $cmddef = {
        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', ['service'], { node => $nodename }, $upid_exit],
-    stop => [ 'PVE::API2::Ceph', 'stop', ['service'], { 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 => [ 'PVE::API2::Ceph', 'status', [], { node => $nodename }, sub {
-       my $res = shift;
-       my $json = JSON->new->allow_nonref;
-       print $json->pretty->encode($res) . "\n";
-    }],
+    status => [ __PACKAGE__, 'status', []],
 };
 
 1;