]> git.proxmox.com Git - pve-manager.git/blobdiff - PVE/CLI/pveceph.pm
pveceph: add osd details command
[pve-manager.git] / PVE / CLI / pveceph.pm
index ba5067b1ec5ee394c11f4c1330ca63432f70f9b6..77749696c6e5d9c9ecd9c8ee3dce5e3502af4608 100755 (executable)
@@ -10,6 +10,8 @@ use JSON;
 use Data::Dumper;
 use LWP::UserAgent;
 
+use Proxmox::RS::Subscription;
+
 use PVE::SafeSyslog;
 use PVE::Cluster;
 use PVE::INotify;
@@ -35,7 +37,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 {
@@ -106,6 +108,15 @@ __PACKAGE__->register_method ({
        return undef;
     }});
 
+my sub has_valid_subscription {
+    my $info = eval { Proxmox::RS::Subscription::read_subscription('/etc/subscription') } // {};
+    warn "couldn't check subscription info - $@" if $@;
+    return $info->{status} && $info->{status} eq 'active'; # age check?
+}
+
+my $supported_ceph_versions = ['quincy'];
+my $default_ceph_version = 'quincy';
+
 __PACKAGE__->register_method ({
     name => 'install',
     path => 'install',
@@ -116,12 +127,18 @@ __PACKAGE__->register_method ({
        properties => {
            version => {
                type => 'string',
-               # for buster, luminous kept for testing/upgrade purposes only! - FIXME: remove with 6.2?
-               enum => ['luminous', 'nautilus', 'octopus'],
-               default => 'nautilus',
+               enum => $supported_ceph_versions,
+               default => $default_ceph_version,
                description => "Ceph version to install.",
                optional => 1,
            },
+           repository => {
+               type => 'string',
+               enum => ['enterprise', 'no-subscription', 'test'],
+               default => 'enterprise',
+               description => "Ceph repository to use.",
+               optional => 1,
+           },
            'allow-experimental' => {
                type => 'boolean',
                default => 0,
@@ -134,43 +151,77 @@ __PACKAGE__->register_method ({
     code => sub {
        my ($param) = @_;
 
-       my $default_vers = 'nautilus';
-       my $cephver = $param->{version} || $default_vers;
+       my $cephver = $param->{version} || $default_ceph_version;
+
+       my $repo = $param->{'repository'} // 'enterprise';
+       my $enterprise_repo = $repo eq 'enterprise';
+       my $cdn = $enterprise_repo ? 'https://enterprise.proxmox.com' : 'http://download.proxmox.com';
+
+       if (has_valid_subscription()) {
+           warn "\nNOTE: The node has an active subscription but a non-production Ceph repository selected.\n\n"
+               if !$enterprise_repo;
+       } elsif ($enterprise_repo) {
+           warn "\nWARN: Enterprise repository selected, but no active subscription!\n\n";
+       } elsif ($repo eq 'no-subscription') {
+           warn "\nHINT: The no-subscription repository is not the best choice for production setups.\n"
+               ."Proxmox recommends using the enterprise repository with a valid subscription.\n";
+       } else {
+           warn "\nWARN: The test repository should only be used for test setups or after consulting"
+               ." the official Proxmox support!\n\n"
+       }
 
        my $repolist;
-       if ($cephver eq 'nautilus') {
-           $repolist = "deb http://download.proxmox.com/debian/ceph-nautilus buster main\n";
-       } elsif ($cephver eq 'luminous') {
-           die "Not allowed to select version '$cephver'\n" if !$param->{'allow-experimental'};
-           $repolist = "deb http://download.proxmox.com/debian/ceph-luminous buster main\n";
-       } elsif ($cephver eq 'octopus') {
-           $repolist = "deb http://download.proxmox.com/debian/ceph-octopus buster main\n";
+       if ($cephver eq 'quincy') {
+           $repolist = "deb ${cdn}/debian/ceph-quincy bookworm $repo\n";
        } else {
-           die "not implemented ceph version: $cephver";
+           die "unsupported ceph version: $cephver";
        }
        PVE::Tools::file_set_contents("/etc/apt/sources.list.d/ceph.list", $repolist);
 
-       warn "WARNING: installing non-default ceph release '$cephver'!\n\n" if $cephver ne $default_vers;
+       my $supported_re = join('|', $supported_ceph_versions->@*);
+       warn "WARNING: installing non-default ceph release '$cephver'!\n" if $cephver !~ qr/^(?:$supported_re)$/;
 
        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" }) };
+       eval {
+           run_command(
+               ['apt-get', '-q', 'update'],
+               outfunc => sub {},
+               errfunc => sub { print STDERR "$_[0]\n" },
+           )
+       };
 
        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
+           ceph-mds
+           ceph-volume
            gdisk
+           nvme-cli
        );
 
        print "start installation\n";
+
+       # 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";
+       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;
     }});
@@ -196,10 +247,194 @@ __PACKAGE__->register_method ({
        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);
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'osddetails',
+    path => 'osddetails',
+    method => 'GET',
+    description => "Get OSD details.",
+    parameters => {
+       additionalProperties => 0,
+       properties => {
+           node => get_standard_option('pve-node'),
+           osdid => {
+               description => "ID of the OSD",
+               type => 'string',
+           },
+           verbose => {
+               description => "Print verbose information, same as json-pretty output format.",
+               type => 'boolean',
+               default => 0,
+               optional => 1,
+           },
+       },
+    },
+    returns => { type => 'object' },
+    code => sub {
+       my ($param) = @_;
+       PVE::Ceph::Tools::check_ceph_inited();
+       my $res = PVE::API2::Ceph::OSD->osddetails({
+               osdid => $param->{osdid},
+               node => $param->{node},
+           });
+
+       for my $dev (@{ $res->{devices} }) {
+           $dev->{"lv-info"} = PVE::API2::Ceph::OSD->osdvolume({
+                   osdid => $param->{osdid},
+                   node => $param->{node},
+                   type => $dev->{device},
+               });
+       }
+       $res->{verbose} = 1 if $param->{verbose};
+       return $res;
+    }});
+
+my $format_osddetails = sub {
+    my ($data, $schema, $options) = @_;
+    $options->{"output-format"} //= "text";
+
+    if ($data->{verbose}) {
+       $options->{"output-format"} = "json-pretty";
+       delete $data->{verbose};
+    }
+
+    if ($options->{"output-format"} eq "text") {
+       for my $dev (@{ $data->{devices} }) {
+           my $str = "Disk: $dev->{physical_device},"
+               ." Type: $dev->{type},"
+               ." LV Size: $dev->{'lv-info'}->{lv_size},"
+               ." LV Creation Time: $dev->{'lv-info'}->{creation_time}";
+
+           $data->{osd}->{$dev->{device}} = $str;
+       }
+       PVE::CLIFormatter::print_api_result($data->{osd}, $schema, undef, $options);
+    } else {
+       PVE::CLIFormatter::print_api_result($data, $schema, undef, $options);
+    }
+};
+
 our $cmddef = {
     init => [ 'PVE::API2::Ceph', 'init', [], { node => $nodename } ],
     pool => {
-       ls => [ 'PVE::API2::Ceph::Pools', 'lspools', [], { node => $nodename }, sub {
+       ls => [ 'PVE::API2::Ceph::Pool', 'lspools', [], { node => $nodename }, sub {
            my ($data, $schema, $options) = @_;
            PVE::CLIFormatter::print_api_result($data, $schema,
                [
@@ -207,17 +442,21 @@ our $cmddef = {
                    '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::Pools', 'createpool', ['name'], { node => $nodename }],
-       destroy => [ 'PVE::API2::Ceph::Pools', 'destroypool', ['name'], { node => $nodename } ],
-       set => [ 'PVE::API2::Ceph::Pools', 'setpool', ['name'], { node => $nodename } ],
-       get => [ 'PVE::API2::Ceph::Pools', 'getpool', ['name'], { node => $nodename }, sub {
+       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],
@@ -227,10 +466,12 @@ our $cmddef = {
     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],
        destroy => [ 'PVE::API2::Ceph::OSD', 'destroyosd', ['osdid'], { node => $nodename }, $upid_exit],
+       details => [ __PACKAGE__, 'osddetails', ['osdid'], { node => $nodename }, $format_osddetails, $PVE::RESTHandler::standard_output_options],
     },
     createosd => { alias => 'osd create' },
     destroyosd => { alias => 'osd destroy' },