]>
git.proxmox.com Git - pve-manager.git/blob - PVE/CLI/pveceph.pm
1 package PVE
::CLI
::pveceph
;
13 use Proxmox
::RS
::Subscription
;
18 use PVE
::RPCEnvironment
;
20 use PVE
::Tools
qw(run_command);
21 use PVE
::JSONSchema
qw(get_standard_option);
23 use PVE
::Ceph
::Services
;
25 use PVE
::API2
::Ceph
::FS
;
26 use PVE
::API2
::Ceph
::MDS
;
27 use PVE
::API2
::Ceph
::MGR
;
28 use PVE
::API2
::Ceph
::MON
;
29 use PVE
::API2
::Ceph
::OSD
;
33 use base
qw(PVE::CLIHandler);
35 my $nodename = PVE
::INotify
::nodename
();
39 my $status = PVE
::Tools
::upid_read_status
($upid);
40 exit(PVE
::Tools
::upid_status_is_error
($status) ?
-1 : 0);
43 sub setup_environment
{
44 PVE
::RPCEnvironment-
>setup_default_cli_env();
47 __PACKAGE__-
>register_method ({
51 description
=> "Destroy ceph related data and configuration files.",
53 additionalProperties
=> 0,
56 description
=> 'Additionally purge Ceph logs, /var/log/ceph.',
61 description
=> 'Additionally purge Ceph crash logs, /var/lib/ceph/crash.',
67 returns
=> { type
=> 'null' },
78 my $rados = PVE
::RADOS-
>new();
79 $pools = PVE
::Ceph
::Tools
::ls_pools
(undef, $rados);
80 $monstat = PVE
::Ceph
::Services
::get_services_info
('mon', undef, $rados);
81 $mdsstat = PVE
::Ceph
::Services
::get_services_info
('mds', undef, $rados);
82 $osdstat = $rados->mon_command({ prefix
=> 'osd metadata' });
84 warn "Error gathering ceph info, already purged? Message: $@" if $@;
86 my $osd = grep { $_->{hostname
} eq $nodename } @$osdstat;
87 my $mds = grep { $mdsstat->{$_}->{host
} eq $nodename } keys %$mdsstat;
88 my $mon = grep { $monstat->{$_}->{host
} eq $nodename } keys %$monstat;
91 $message .= "- remove pools, this will !!DESTROY DATA!!\n" if @$pools;
92 $message .= "- remove active OSD on $nodename\n" if $osd;
93 $message .= "- remove active MDS on $nodename\n" if $mds;
94 $message .= "- remove other MONs, $nodename is not the last MON\n"
95 if scalar(keys %$monstat) > 1 && $mon;
97 # display all steps at once
98 die "Unable to purge Ceph!\n\nTo continue:\n$message" if $message;
100 my $services = PVE
::Ceph
::Services
::get_local_services
();
101 $services->{mon
} = $monstat if $mon;
102 $services->{crash
}->{$nodename} = { direxists
=> 1 } if $param->{crash
};
103 $services->{logs
}->{$nodename} = { direxists
=> 1 } if $param->{logs
};
105 PVE
::Ceph
::Tools
::purge_all_ceph_services
($services);
106 PVE
::Ceph
::Tools
::purge_all_ceph_files
($services);
111 my sub has_valid_subscription
{
112 my $info = eval { Proxmox
::RS
::Subscription
::read_subscription
('/etc/subscription') } // {};
113 warn "couldn't check subscription info - $@" if $@;
114 return $info->{status
} && $info->{status
} eq 'active'; # age check?
117 my $supported_ceph_versions = ['quincy', 'reef'];
118 my $default_ceph_version = 'quincy';
120 __PACKAGE__-
>register_method ({
124 description
=> "Install ceph related packages.",
126 additionalProperties
=> 0,
130 enum
=> $supported_ceph_versions,
131 default => $default_ceph_version,
132 description
=> "Ceph version to install.",
137 enum
=> ['enterprise', 'no-subscription', 'test'],
138 default => 'enterprise',
139 description
=> "Ceph repository to use.",
142 'allow-experimental' => {
146 description
=> "Allow experimental versions. Use with care!",
150 returns
=> { type
=> 'null' },
154 my $cephver = $param->{version
} || $default_ceph_version;
156 my $repo = $param->{'repository'} // 'enterprise';
157 my $enterprise_repo = $repo eq 'enterprise';
158 my $cdn = $enterprise_repo ?
'https://enterprise.proxmox.com' : 'http://download.proxmox.com';
160 if (has_valid_subscription
()) {
161 warn "\nNOTE: The node has an active subscription but a non-production Ceph repository selected.\n\n"
162 if !$enterprise_repo;
163 } elsif ($enterprise_repo) {
164 warn "\nWARN: Enterprise repository selected, but no active subscription!\n\n";
165 } elsif ($repo eq 'no-subscription') {
166 warn "\nHINT: The no-subscription repository is not the best choice for production setups.\n"
167 ."Proxmox recommends using the enterprise repository with a valid subscription.\n";
169 warn "\nWARN: The test repository should only be used for test setups or after consulting"
170 ." the official Proxmox support!\n\n"
174 if ($cephver eq 'reef') {
175 $repolist = "deb ${cdn}/debian/ceph-reef bookworm $repo\n";
176 } elsif ($cephver eq 'quincy') {
177 $repolist = "deb ${cdn}/debian/ceph-quincy bookworm $repo\n";
179 die "unsupported ceph version: $cephver";
182 if (-t STDOUT
&& !$param->{version
}) {
183 print "This will install Ceph " . ucfirst($cephver) . " - continue (y/N)? ";
185 my $answer = <STDIN
>;
186 my $continue = defined($answer) && $answer =~ m/^\s*y(?:es)?\s*$/i;
188 die "Aborting installation as requested\n" if !$continue;
191 PVE
::Tools
::file_set_contents
("/etc/apt/sources.list.d/ceph.list", $repolist);
193 my $supported_re = join('|', $supported_ceph_versions->@*);
194 warn "WARNING: installing non-default ceph release '$cephver'!\n" if $cephver !~ qr/^(?:$supported_re)$/;
196 local $ENV{DEBIAN_FRONTEND
} = 'noninteractive';
197 print "update available package list\n";
200 ['apt-get', '-q', 'update'],
202 errfunc
=> sub { print STDERR
"$_[0]\n" },
206 my @apt_install = qw(apt-get --no-install-recommends -o Dpkg::Options::=--force-confnew install --);
207 my @ceph_packages = qw(
217 print "start installation\n";
219 # this flag helps to determine when apt is actually done installing (vs. partial extracing)
220 my $install_flag_fn = PVE
::Ceph
::Tools
::ceph_install_flag_file
();
221 open(my $install_flag, '>', $install_flag_fn) or die "could not create install flag - $!\n";
224 if (system(@apt_install, @ceph_packages) != 0) {
225 unlink $install_flag_fn or warn "could not remove Ceph installation flag - $!";
226 die "apt failed during ceph installation ($?)\n";
229 print "\ninstalled ceph $cephver successfully!\n";
230 # done: drop flag file so that the PVE::Ceph::Tools check returns Ok now.
231 unlink $install_flag_fn or warn "could not remove Ceph installation flag - $!";
233 print "\nreloading API to load new Ceph RADOS library...\n";
235 'systemctl', 'try-reload-or-restart', 'pvedaemon.service', 'pveproxy.service'
241 __PACKAGE__-
>register_method ({
245 description
=> "Get Ceph Status.",
247 additionalProperties
=> 0,
249 returns
=> { type
=> 'null' },
251 PVE
::Ceph
::Tools
::check_ceph_inited
();
255 outfunc
=> sub { print "$_[0]\n" },
256 errfunc
=> sub { print STDERR
"$_[0]\n" },
262 my $get_storages = sub {
263 my ($fs, $is_default) = @_;
265 my $cfg = PVE
::Storage
::config
();
267 my $storages = $cfg->{ids
};
269 foreach my $storeid (keys %$storages) {
270 my $curr = $storages->{$storeid};
271 next if $curr->{type
} ne 'cephfs';
272 my $cur_fs = $curr->{'fs-name'};
273 $res->{$storeid} = $storages->{$storeid}
274 if (!defined($cur_fs) && $is_default) || (defined($cur_fs) && $fs eq $cur_fs);
280 __PACKAGE__-
>register_method ({
284 description
=> "Destroy a Ceph filesystem",
286 additionalProperties
=> 0,
288 node
=> get_standard_option
('pve-node'),
290 description
=> "The ceph filesystem name.",
293 'remove-storages' => {
294 description
=> "Remove all pveceph-managed storages configured for this fs.",
300 description
=> "Remove data and metadata pools configured for this fs.",
307 returns
=> { type
=> 'string' },
311 PVE
::Ceph
::Tools
::check_ceph_inited
();
313 my $rpcenv = PVE
::RPCEnvironment
::get
();
314 my $user = $rpcenv->get_user();
316 my $fs_name = $param->{name
};
319 my $fs_list = PVE
::Ceph
::Tools
::ls_fs
();
320 for my $entry (@$fs_list) {
321 next if $entry->{name
} ne $fs_name;
325 die "no such cephfs '$fs_name'\n" if !$fs;
328 my $rados = PVE
::RADOS-
>new();
330 if ($param->{'remove-storages'}) {
332 my $fs_dump = $rados->mon_command({ prefix
=> "fs dump" });
333 for my $fs ($fs_dump->{filesystems
}->@*) {
334 next if $fs->{id
} != $fs_dump->{default_fscid
};
335 $defaultfs = $fs->{mdsmap
}->{fs_name
};
337 warn "no default fs found, maybe not all relevant storages are removed\n"
338 if !defined($defaultfs);
340 my $storages = $get_storages->($fs_name, $fs_name eq ($defaultfs // ''));
341 for my $storeid (keys %$storages) {
342 my $store = $storages->{$storeid};
343 if (!$store->{disable
}) {
344 die "storage '$storeid' is not disabled, make sure to disable ".
345 "and unmount the storage first\n";
350 for my $storeid (keys %$storages) {
351 # skip external clusters, not managed by pveceph
352 next if $storages->{$storeid}->{monhost
};
353 eval { PVE
::API2
::Storage
::Config-
>delete({storage
=> $storeid}) };
355 warn "failed to remove storage '$storeid': $@\n";
359 die "failed to remove (some) storages - check log and remove manually!\n"
363 PVE
::Ceph
::Tools
::destroy_fs
($fs_name, $rados);
365 if ($param->{'remove-pools'}) {
366 warn "removing metadata pool '$fs->{metadata_pool}'\n";
367 eval { PVE
::Ceph
::Tools
::destroy_pool
($fs->{metadata_pool
}, $rados) };
370 foreach my $pool ($fs->{data_pools
}->@*) {
371 warn "removing data pool '$pool'\n";
372 eval { PVE
::Ceph
::Tools
::destroy_pool
($pool, $rados) };
378 return $rpcenv->fork_worker('cephdestroyfs', $fs_name, $user, $worker);
381 __PACKAGE__-
>register_method ({
382 name
=> 'osd-details',
383 path
=> 'osd-details',
385 description
=> "Get OSD details.",
387 additionalProperties
=> 0,
389 node
=> get_standard_option
('pve-node'),
391 description
=> "ID of the OSD",
395 description
=> "Print verbose information, same as json-pretty output format.",
402 returns
=> { type
=> 'object' },
406 PVE
::Ceph
::Tools
::check_ceph_inited
();
408 my $res = PVE
::API2
::Ceph
::OSD-
>osddetails({
409 osdid
=> $param->{osdid
},
410 node
=> $param->{node
},
413 for my $dev ($res->{devices
}->@*) {
414 $dev->{"lv-info"} = PVE
::API2
::Ceph
::OSD-
>osdvolume({
415 osdid
=> $param->{osdid
},
416 node
=> $param->{node
},
417 type
=> $dev->{device
},
420 $res->{verbose
} = 1 if $param->{verbose
};
424 my $format_osddetails = sub {
425 my ($data, $schema, $options) = @_;
427 $options->{"output-format"} //= "text";
429 if ($data->{verbose
}) {
430 $options->{"output-format"} = "json-pretty";
431 delete $data->{verbose
};
434 if ($options->{"output-format"} eq "text") {
435 for my $dev ($data->{devices
}->@*) {
436 my ($disk, $type, $device) = $dev->@{'physical_device', 'type', 'device'};
437 my ($lv_size, $lv_ctime) = $dev->{'lv-info'}->@{'lv_size', 'creation_time'};
439 $data->{osd
}->{$device} = "Disk: $disk, Type: $type, LV Size: $lv_size, LV Creation Time: $lv_ctime";
441 PVE
::CLIFormatter
::print_api_result
($data->{osd
}, $schema, undef, $options);
443 PVE
::CLIFormatter
::print_api_result
($data, $schema, undef, $options);
448 init
=> [ 'PVE::API2::Ceph', 'init', [], { node
=> $nodename } ],
450 ls
=> [ 'PVE::API2::Ceph::Pool', 'lspools', [], { node
=> $nodename }, sub {
451 my ($data, $schema, $options) = @_;
452 PVE
::CLIFormatter
::print_api_result
($data, $schema,
468 }, $PVE::RESTHandler
::standard_output_options
],
469 create
=> [ 'PVE::API2::Ceph::Pool', 'createpool', ['name'], { node
=> $nodename }],
470 destroy
=> [ 'PVE::API2::Ceph::Pool', 'destroypool', ['name'], { node
=> $nodename } ],
471 set
=> [ 'PVE::API2::Ceph::Pool', 'setpool', ['name'], { node
=> $nodename } ],
472 get
=> [ 'PVE::API2::Ceph::Pool', 'getpool', ['name'], { node
=> $nodename }, sub {
473 my ($data, $schema, $options) = @_;
474 PVE
::CLIFormatter
::print_api_result
($data, $schema, undef, $options);
475 }, $PVE::RESTHandler
::standard_output_options
],
477 lspools
=> { alias
=> 'pool ls' },
478 createpool
=> { alias
=> 'pool create' },
479 destroypool
=> { alias
=> 'pool destroy' },
481 create
=> [ 'PVE::API2::Ceph::FS', 'createfs', [], { node
=> $nodename }],
482 destroy
=> [ __PACKAGE__
, 'destroyfs', ['name'], { node
=> $nodename }],
485 create
=> [ 'PVE::API2::Ceph::OSD', 'createosd', ['dev'], { node
=> $nodename }, $upid_exit],
486 destroy
=> [ 'PVE::API2::Ceph::OSD', 'destroyosd', ['osdid'], { node
=> $nodename }, $upid_exit],
488 __PACKAGE__
, 'osd-details', ['osdid'], { node
=> $nodename }, $format_osddetails,
489 $PVE::RESTHandler
::standard_output_options
,
492 createosd
=> { alias
=> 'osd create' },
493 destroyosd
=> { alias
=> 'osd destroy' },
495 create
=> [ 'PVE::API2::Ceph::MON', 'createmon', [], { node
=> $nodename }, $upid_exit],
496 destroy
=> [ 'PVE::API2::Ceph::MON', 'destroymon', ['monid'], { node
=> $nodename }, $upid_exit],
498 createmon
=> { alias
=> 'mon create' },
499 destroymon
=> { alias
=> 'mon destroy' },
501 create
=> [ 'PVE::API2::Ceph::MGR', 'createmgr', [], { node
=> $nodename }, $upid_exit],
502 destroy
=> [ 'PVE::API2::Ceph::MGR', 'destroymgr', ['id'], { node
=> $nodename }, $upid_exit],
504 createmgr
=> { alias
=> 'mgr create' },
505 destroymgr
=> { alias
=> 'mgr destroy' },
507 create
=> [ 'PVE::API2::Ceph::MDS', 'createmds', [], { node
=> $nodename }, $upid_exit],
508 destroy
=> [ 'PVE::API2::Ceph::MDS', 'destroymds', ['name'], { node
=> $nodename }, $upid_exit],
510 start
=> [ 'PVE::API2::Ceph', 'start', [], { node
=> $nodename }, $upid_exit],
511 stop
=> [ 'PVE::API2::Ceph', 'stop', [], { node
=> $nodename }, $upid_exit],
512 install
=> [ __PACKAGE__
, 'install', [] ],
513 purge
=> [ __PACKAGE__
, 'purge', [] ],
514 status
=> [ __PACKAGE__
, 'status', []],