]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Ceph/OSD.pm
ui: ldap: fix `Parameter verification error`
[pve-manager.git] / PVE / API2 / Ceph / OSD.pm
CommitLineData
79fa41a2
DC
1package PVE::API2::Ceph::OSD;
2
3use strict;
4use warnings;
5
6use Cwd qw(abs_path);
7use IO::File;
7783f755 8use UUID;
79fa41a2
DC
9
10use PVE::Ceph::Tools;
11use PVE::Ceph::Services;
12use PVE::CephConfig;
13use PVE::Cluster qw(cfs_read_file cfs_write_file);
14use PVE::Diskmanage;
7783f755 15use PVE::Storage::LVMPlugin;
79fa41a2
DC
16use PVE::Exception qw(raise_param_exc);
17use PVE::JSONSchema qw(get_standard_option);
a05349ab 18use PVE::INotify;
79fa41a2
DC
19use PVE::RADOS;
20use PVE::RESTHandler;
21use PVE::RPCEnvironment;
22use PVE::Tools qw(run_command file_set_contents);
3c6aa3f4 23use PVE::ProcFSTools;
05bd76ac 24use PVE::Network;
79fa41a2
DC
25
26use base qw(PVE::RESTHandler);
27
a05349ab
TL
28my $nodename = PVE::INotify::nodename();
29
79fa41a2
DC
30my $get_osd_status = sub {
31 my ($rados, $osdid) = @_;
32
33 my $stat = $rados->mon_command({ prefix => 'osd dump' });
34
35 my $osdlist = $stat->{osds} || [];
36
37 my $flags = $stat->{flags} || undef;
38
39 my $osdstat;
40 foreach my $d (@$osdlist) {
41 $osdstat->{$d->{osd}} = $d if defined($d->{osd});
42 }
43 if (defined($osdid)) {
44 die "no such OSD '$osdid'\n" if !$osdstat->{$osdid};
45 return $osdstat->{$osdid};
46 }
47
017bb1a8 48 return wantarray ? ($osdstat, $flags) : $osdstat;
79fa41a2
DC
49};
50
51my $get_osd_usage = sub {
52 my ($rados) = @_;
53
de6ad72f
TL
54 my $osdlist = $rados->mon_command({ prefix => 'pg dump', dumpcontents => [ 'osds' ]});
55 if (!($osdlist && ref($osdlist))) {
56 warn "got unknown result format for 'pg dump osds' command\n";
57 return [];
91564b72 58 }
79fa41a2 59
de6ad72f
TL
60 if (ref($osdlist) eq "HASH") { # since nautilus
61 $osdlist = $osdlist->{osd_stats};
62 }
63
64 my $osdstat = {};
65 for my $d (@$osdlist) {
79fa41a2
DC
66 $osdstat->{$d->{osd}} = $d if defined($d->{osd});
67 }
68
69 return $osdstat;
70};
71
72__PACKAGE__->register_method ({
73 name => 'index',
74 path => '',
75 method => 'GET',
76 description => "Get Ceph osd list/tree.",
77 proxyto => 'node',
78 protected => 1,
79 permissions => {
80 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
81 },
82 parameters => {
83 additionalProperties => 0,
84 properties => {
85 node => get_standard_option('pve-node'),
86 },
87 },
88 # fixme: return a list instead of extjs tree format ?
89 returns => {
90 type => "object",
b62ba85a
AL
91 items => {
92 type => "object",
93 properties => {
94 flags => { type => "string" },
95 root => {
96 type => "object",
97 description => "Tree with OSDs in the CRUSH map structure.",
98 },
99 },
100 },
79fa41a2
DC
101 },
102 code => sub {
103 my ($param) = @_;
104
105 PVE::Ceph::Tools::check_ceph_inited();
106
107 my $rados = PVE::RADOS->new();
108 my $res = $rados->mon_command({ prefix => 'osd tree' });
109
110 die "no tree nodes found\n" if !($res && $res->{nodes});
111
9cc5ac9e 112 my ($osdhash, $flags) = $get_osd_status->($rados);
79fa41a2 113
de6ad72f 114 my $osd_usage = $get_osd_usage->($rados);
79fa41a2 115
78c2d7f7
TL
116 my $osdmetadata_res = $rados->mon_command({ prefix => 'osd metadata' });
117 my $osdmetadata = { map { $_->{id} => $_ } @$osdmetadata_res };
79fa41a2 118
d3eed3b4 119 my $hostversions = PVE::Ceph::Services::get_ceph_versions();
cead98bd 120
79fa41a2
DC
121 my $nodes = {};
122 my $newnodes = {};
123 foreach my $e (@{$res->{nodes}}) {
cead98bd
TL
124 my ($id, $name) = $e->@{qw(id name)};
125
126 $nodes->{$id} = $e;
79fa41a2
DC
127
128 my $new = {
cead98bd
TL
129 id => $id,
130 name => $name,
79fa41a2
DC
131 type => $e->{type}
132 };
133
134 foreach my $opt (qw(status crush_weight reweight device_class)) {
135 $new->{$opt} = $e->{$opt} if defined($e->{$opt});
136 }
137
cead98bd 138 if (my $stat = $osdhash->{$id}) {
79fa41a2
DC
139 $new->{in} = $stat->{in} if defined($stat->{in});
140 }
141
cead98bd 142 if (my $stat = $osd_usage->{$id}) {
79fa41a2
DC
143 $new->{total_space} = ($stat->{kb} || 1) * 1024;
144 $new->{bytes_used} = ($stat->{kb_used} || 0) * 1024;
145 $new->{percent_used} = ($new->{bytes_used}*100)/$new->{total_space};
146 if (my $d = $stat->{perf_stat}) {
147 $new->{commit_latency_ms} = $d->{commit_latency_ms};
148 $new->{apply_latency_ms} = $d->{apply_latency_ms};
149 }
150 }
151
cead98bd 152 my $osdmd = $osdmetadata->{$id};
79fa41a2
DC
153 if ($e->{type} eq 'osd' && $osdmd) {
154 if ($osdmd->{bluefs}) {
155 $new->{osdtype} = 'bluestore';
156 $new->{blfsdev} = $osdmd->{bluestore_bdev_dev_node};
157 $new->{dbdev} = $osdmd->{bluefs_db_dev_node};
158 $new->{waldev} = $osdmd->{bluefs_wal_dev_node};
159 } else {
160 $new->{osdtype} = 'filestore';
161 }
e0297023
DC
162 for my $field (qw(ceph_version ceph_version_short)) {
163 $new->{$field} = $osdmd->{$field} if $osdmd->{$field};
164 }
79fa41a2
DC
165 }
166
cead98bd 167 $newnodes->{$id} = $new;
79fa41a2
DC
168 }
169
170 foreach my $e (@{$res->{nodes}}) {
cead98bd
TL
171 my ($id, $name) = $e->@{qw(id name)};
172 my $new = $newnodes->{$id};
173
79fa41a2
DC
174 if ($e->{children} && scalar(@{$e->{children}})) {
175 $new->{children} = [];
176 $new->{leaf} = 0;
177 foreach my $cid (@{$e->{children}}) {
cead98bd
TL
178 $nodes->{$cid}->{parent} = $id;
179 if ($nodes->{$cid}->{type} eq 'osd' && $e->{type} eq 'host') {
180 $newnodes->{$cid}->{host} = $name;
79fa41a2
DC
181 }
182 push @{$new->{children}}, $newnodes->{$cid};
183 }
184 } else {
cead98bd 185 $new->{leaf} = ($id >= 0) ? 1 : 0;
79fa41a2 186 }
69ad2e53 187
cead98bd 188 if ($name && $e->{type} eq 'host') {
d3eed3b4 189 $new->{version} = $hostversions->{$name}->{version}->{str};
69ad2e53 190 }
79fa41a2
DC
191 }
192
cead98bd 193 my $realroots = [];
79fa41a2 194 foreach my $e (@{$res->{nodes}}) {
cead98bd
TL
195 my $id = $e->{id};
196 if (!$nodes->{$id}->{parent}) {
197 push @$realroots, $newnodes->{$id};
79fa41a2
DC
198 }
199 }
200
cead98bd 201 die "no root node\n" if scalar(@$realroots) < 1;
79fa41a2 202
cead98bd
TL
203 my $data = {
204 root => {
205 leaf => 0,
206 children => $realroots
207 },
cead98bd 208 };
79fa41a2 209
cead98bd 210 $data->{flags} = $flags if $flags; # we want this for the noout flag
79fa41a2
DC
211
212 return $data;
213 }});
214
215__PACKAGE__->register_method ({
216 name => 'createosd',
217 path => '',
218 method => 'POST',
219 description => "Create OSD",
220 proxyto => 'node',
221 protected => 1,
222 parameters => {
223 additionalProperties => 0,
224 properties => {
225 node => get_standard_option('pve-node'),
226 dev => {
227 description => "Block device name.",
228 type => 'string',
229 },
7783f755
DC
230 db_dev => {
231 description => "Block device name for block.db.",
79fa41a2
DC
232 optional => 1,
233 type => 'string',
234 },
596bb7b1 235 db_dev_size => {
0e5f83ba
TL
236 description => "Size in GiB for block.db.",
237 verbose_description => "If a block.db is requested but the size is not given, ".
238 "will be automatically selected by: bluestore_block_db_size from the ".
7783f755
DC
239 "ceph database (osd or global section) or config (osd or global section)".
240 "in that order. If this is not available, it will be sized 10% of the size ".
241 "of the OSD device. Fails if the available size is not enough.",
79fa41a2 242 optional => 1,
7783f755 243 type => 'number',
0e5f83ba 244 default => 'bluestore_block_db_size or 10% of OSD size',
7783f755
DC
245 requires => 'db_dev',
246 minimum => 1.0,
79fa41a2 247 },
7783f755
DC
248 wal_dev => {
249 description => "Block device name for block.wal.",
79fa41a2 250 optional => 1,
7783f755 251 type => 'string',
79fa41a2 252 },
596bb7b1 253 wal_dev_size => {
0e5f83ba
TL
254 description => "Size in GiB for block.wal.",
255 verbose_description => "If a block.wal is requested but the size is not given, ".
256 "will be automatically selected by: bluestore_block_wal_size from the ".
7783f755
DC
257 "ceph database (osd or global section) or config (osd or global section)".
258 "in that order. If this is not available, it will be sized 1% of the size ".
259 "of the OSD device. Fails if the available size is not enough.",
79fa41a2 260 optional => 1,
7783f755 261 minimum => 0.5,
0e5f83ba 262 default => 'bluestore_block_wal_size or 1% of OSD size',
7783f755
DC
263 requires => 'wal_dev',
264 type => 'number',
79fa41a2 265 },
4ce04578
DC
266 encrypted => {
267 type => 'boolean',
268 optional => 1,
269 default => 0,
270 description => "Enables encryption of the OSD."
271 },
2184098e
AA
272 'crush-device-class' => {
273 optional => 1,
274 type => 'string',
275 description => "Set the device class of the OSD in crush."
276 },
79fa41a2
DC
277 },
278 },
279 returns => { type => 'string' },
280 code => sub {
281 my ($param) = @_;
282
283 my $rpcenv = PVE::RPCEnvironment::get();
284
285 my $authuser = $rpcenv->get_user();
286
45d45a63 287 # test basic requirements
79fa41a2 288 PVE::Ceph::Tools::check_ceph_inited();
79fa41a2 289 PVE::Ceph::Tools::setup_pve_symlinks();
79fa41a2 290 PVE::Ceph::Tools::check_ceph_installed('ceph_osd');
7783f755 291 PVE::Ceph::Tools::check_ceph_installed('ceph_volume');
79fa41a2 292
45d45a63
DC
293 # extract parameter info and fail if a device is set more than once
294 my $devs = {};
79fa41a2 295
05bd76ac
AL
296 my $ceph_conf = cfs_read_file('ceph.conf');
297
a05349ab
TL
298 my $osd_network = $ceph_conf->{global}->{cluster_network};
299 $osd_network //= $ceph_conf->{global}->{public_network}; # fallback
05bd76ac 300
a0ef509a
DC
301 if ($osd_network) { # check only if something is configured
302 my $cluster_net_ips = PVE::Network::get_local_ip_from_cidr($osd_network);
303 if (scalar(@$cluster_net_ips) < 1) {
304 my $osd_net_obj = PVE::Network::IP_from_cidr($osd_network);
305 my $osd_base_cidr = $osd_net_obj->{ip} . "/" . $osd_net_obj->{prefixlen};
306
307 die "No address from ceph cluster network (${osd_base_cidr}) found on node '$nodename'. ".
308 "Check your network config.\n";
309 }
05bd76ac
AL
310 }
311
970f96fd
TL
312 for my $type ( qw(dev db_dev wal_dev) ) {
313 next if !$param->{$type};
0154e795 314
970f96fd 315 my $type_dev = PVE::Diskmanage::verify_blockdev_path($param->{$type});
45d45a63 316 (my $type_devname = $type_dev) =~ s|/dev/||;
79fa41a2 317
970f96fd 318 raise_param_exc({ $type => "cannot chose '$type_dev' for more than one type." })
45d45a63 319 if grep { $_->{name} eq $type_devname } values %$devs;
79fa41a2 320
45d45a63
DC
321 $devs->{$type} = {
322 dev => $type_dev,
323 name => $type_devname,
324 };
79fa41a2 325
45d45a63
DC
326 if (my $size = $param->{"${type}_size"}) {
327 $devs->{$type}->{size} = PVE::Tools::convert_size($size, 'gb' => 'b') ;
328 }
329 }
79fa41a2 330
e2565956
FE
331 my $test_disk_requirements = sub {
332 my ($disklist) = @_;
333
334 my $dev = $devs->{dev}->{dev};
335 my $devname = $devs->{dev}->{name};
336 die "unable to get device info for '$dev'\n" if !$disklist->{$devname};
337 die "device '$dev' is already in use\n" if $disklist->{$devname}->{used};
338
339 for my $type ( qw(db_dev wal_dev) ) {
340 my $d = $devs->{$type};
341 next if !$d;
342 my $name = $d->{name};
343 my $info = $disklist->{$name};
344 die "unable to get device info for '$d->{dev}' for type $type\n" if !$disklist->{$name};
345 if (my $usage = $info->{used}) {
346 if ($usage eq 'partitions') {
347 die "device '$d->{dev}' is not GPT partitioned\n" if !$info->{gpt};
348 } elsif ($usage ne 'LVM') {
349 die "device '$d->{dev}' is already in use and has no LVM on it\n";
350 }
385df838
DC
351 }
352 }
e2565956
FE
353 };
354
355
356 # test disk requirements early
357 my $devlist = [ map { $_->{name} } values %$devs ];
5161a0c2 358 my $disklist = PVE::Diskmanage::get_disks($devlist, 1, 1);
e2565956 359 $test_disk_requirements->($disklist);
0154e795 360
45d45a63 361 # get necessary ceph infos
79fa41a2 362 my $rados = PVE::RADOS->new();
e25dda25 363 my $monstat = $rados->mon_command({ prefix => 'quorum_status' });
79fa41a2 364
0154e795 365 die "unable to get fsid\n" if !$monstat->{monmap} || !$monstat->{monmap}->{fsid};
79fa41a2
DC
366 my $fsid = $monstat->{monmap}->{fsid};
367 $fsid = $1 if $fsid =~ m/^([0-9a-f\-]+)$/;
368
369 my $ceph_bootstrap_osd_keyring = PVE::Ceph::Tools::get_config('ceph_bootstrap_osd_keyring');
370
7712a4e1 371 if (! -f $ceph_bootstrap_osd_keyring && $ceph_conf->{global}->{auth_client_required} eq 'cephx') {
217dde83
DC
372 my $bindata = $rados->mon_command({
373 prefix => 'auth get-or-create',
374 entity => 'client.bootstrap-osd',
375 caps => [
376 'mon' => 'allow profile bootstrap-osd'
377 ],
378 format => 'plain',
379 });
79fa41a2
DC
380 file_set_contents($ceph_bootstrap_osd_keyring, $bindata);
381 };
382
45d602f2
FE
383 # See FIXME below
384 my @udev_trigger_devs = ();
385
7783f755
DC
386 my $create_part_or_lv = sub {
387 my ($dev, $size, $type) = @_;
388
0154e795
TL
389 $size =~ m/^(\d+)$/ or die "invalid size '$size'\n";
390 $size = $1;
7783f755
DC
391
392 die "'$dev->{devpath}' is smaller than requested size '$size' bytes\n"
393 if $dev->{size} < $size;
79fa41a2 394
ab62d137
DC
395 # sgdisk and lvcreate can only sizes divisible by 512b
396 # so we round down to the nearest kb
397 $size = PVE::Tools::convert_size($size, 'b' => 'kb', 1);
398
7783f755
DC
399 if (!$dev->{used}) {
400 # create pv,vg,lv
79fa41a2 401
7783f755
DC
402 my $vg = "ceph-" . UUID::uuid();
403 my $lv = $type . "-" . UUID::uuid();
79fa41a2 404
7783f755 405 PVE::Storage::LVMPlugin::lvm_create_volume_group($dev->{devpath}, $vg);
ab62d137 406 PVE::Storage::LVMPlugin::lvcreate($vg, $lv, "${size}k");
79fa41a2 407
cffeb115
FE
408 if (PVE::Diskmanage::is_partition($dev->{devpath})) {
409 eval { PVE::Diskmanage::change_parttype($dev->{devpath}, '8E00'); };
410 warn $@ if $@;
411 }
412
45d602f2
FE
413 push @udev_trigger_devs, $dev->{devpath};
414
7783f755
DC
415 return "$vg/$lv";
416
417 } elsif ($dev->{used} eq 'LVM') {
418 # check pv/vg and create lv
419
420 my $vgs = PVE::Storage::LVMPlugin::lvm_vgs(1);
421 my $vg;
422 for my $vgname ( sort keys %$vgs ) {
423 next if $vgname !~ /^ceph-/;
424
425 for my $pv ( @{$vgs->{$vgname}->{pvs}} ) {
426 next if $pv->{name} ne $dev->{devpath};
427 $vg = $vgname;
428 last;
429 }
430 last if $vg;
431 }
432
433 die "no ceph vg found on '$dev->{devpath}'\n" if !$vg;
434 die "vg '$vg' has not enough free space\n" if $vgs->{$vg}->{free} < $size;
435
afa09e02 436 my $lv = $type . "-" . UUID::uuid();
7783f755 437
ab62d137 438 PVE::Storage::LVMPlugin::lvcreate($vg, $lv, "${size}k");
7783f755
DC
439
440 return "$vg/$lv";
441
3d7b3992 442 } elsif ($dev->{used} eq 'partitions' && $dev->{gpt}) {
7783f755 443 # create new partition at the end
46b1ccc3
FE
444 my $parttypes = {
445 'osd-db' => '30CD0809-C2B2-499C-8879-2D6B78529876',
446 'osd-wal' => '5CE17FCE-4087-4169-B7FF-056CC58473F9',
447 };
7783f755 448
45d602f2 449 my $part = PVE::Diskmanage::append_partition($dev->{devpath}, $size * 1024);
46b1ccc3
FE
450
451 if (my $parttype = $parttypes->{$type}) {
452 eval { PVE::Diskmanage::change_parttype($part, $parttype); };
453 warn $@ if $@;
454 }
455
45d602f2
FE
456 push @udev_trigger_devs, $part;
457 return $part;
7783f755
DC
458 }
459
460 die "cannot use '$dev->{devpath}' for '$type'\n";
461 };
462
463 my $worker = sub {
464 my $upid = shift;
465
466 PVE::Diskmanage::locked_disk_action(sub {
e2565956 467 # update disklist and re-test requirements
5161a0c2 468 $disklist = PVE::Diskmanage::get_disks($devlist, 1, 1);
e2565956 469 $test_disk_requirements->($disklist);
7783f755 470
2184098e 471 my $dev_class = $param->{'crush-device-class'};
7783f755 472 my $cmd = ['ceph-volume', 'lvm', 'create', '--cluster-fsid', $fsid ];
2184098e 473 push @$cmd, '--crush-device-class', $dev_class if $dev_class;
79fa41a2 474
e2565956 475 my $devname = $devs->{dev}->{name};
45d45a63 476 my $devpath = $disklist->{$devname}->{devpath};
79fa41a2 477 print "create OSD on $devpath (bluestore)\n";
79fa41a2 478
45d602f2
FE
479 push @udev_trigger_devs, $devpath;
480
45d45a63
DC
481 my $osd_size = $disklist->{$devname}->{size};
482 my $size_map = {
483 db => int($osd_size / 10), # 10% of OSD
484 wal => int($osd_size / 100), # 1% of OSD
485 };
486
487 my $sizes;
488 foreach my $type ( qw(db wal) ) {
489 my $fallback_size = $size_map->{$type};
970f96fd 490 my $d = $devs->{"${type}_dev"};
45d45a63
DC
491 next if !$d;
492
493 # size was not set via api, getting from config/fallback
494 if (!defined($d->{size})) {
495 $sizes = PVE::Ceph::Tools::get_db_wal_sizes() if !$sizes;
496 $d->{size} = $sizes->{$type} // $fallback_size;
497 }
498 print "creating block.$type on '$d->{dev}'\n";
499 my $name = $d->{name};
500 my $part_or_lv = $create_part_or_lv->($disklist->{$name}, $d->{size}, "osd-$type");
79fa41a2 501
45d45a63
DC
502 print "using '$part_or_lv' for block.$type\n";
503 push @$cmd, "--block.$type", $part_or_lv;
79fa41a2
DC
504 }
505
7783f755 506 push @$cmd, '--data', $devpath;
4ce04578 507 push @$cmd, '--dmcrypt' if $param->{encrypted};
79fa41a2 508
683a3563 509 PVE::Diskmanage::wipe_blockdev($devpath);
79fa41a2 510
cffeb115
FE
511 if (PVE::Diskmanage::is_partition($devpath)) {
512 eval { PVE::Diskmanage::change_parttype($devpath, '8E00'); };
513 warn $@ if $@;
514 }
515
7783f755 516 run_command($cmd);
45d602f2
FE
517
518 # FIXME: Remove once we depend on systemd >= v249.
519 # Work around udev bug https://github.com/systemd/systemd/issues/18525 to ensure the
520 # udev database is updated.
521 eval { run_command(['udevadm', 'trigger', @udev_trigger_devs]); };
522 warn $@ if $@;
7783f755 523 });
79fa41a2
DC
524 };
525
e2565956 526 return $rpcenv->fork_worker('cephcreateosd', $devs->{dev}->{name}, $authuser, $worker);
79fa41a2
DC
527 }});
528
220173e9
DJ
529# Check if $osdid belongs to $nodename
530# $tree ... rados osd tree (passing the tree makes it easy to test)
531sub osd_belongs_to_node {
532 my ($tree, $nodename, $osdid) = @_;
d7a63207 533 return 0 if !($tree && $tree->{nodes});
220173e9 534
d7a63207
TL
535 my $node_map = {};
536 for my $el (grep { defined($_->{type}) && $_->{type} eq 'host' } @{$tree->{nodes}}) {
537 my $name = $el->{name};
538 die "internal error: duplicate host name found '$name'\n" if $node_map->{$name};
539 $node_map->{$name} = $el;
540 }
220173e9 541
d7a63207
TL
542 my $osds = $node_map->{$nodename}->{children};
543 return 0 if !$osds;
220173e9 544
220173e9
DJ
545 return grep($_ == $osdid, @$osds);
546}
547
79fa41a2
DC
548__PACKAGE__->register_method ({
549 name => 'destroyosd',
550 path => '{osdid}',
551 method => 'DELETE',
552 description => "Destroy OSD",
553 proxyto => 'node',
554 protected => 1,
555 parameters => {
556 additionalProperties => 0,
557 properties => {
558 node => get_standard_option('pve-node'),
559 osdid => {
560 description => 'OSD ID',
561 type => 'integer',
562 },
563 cleanup => {
564 description => "If set, we remove partition table entries.",
565 type => 'boolean',
566 optional => 1,
567 default => 0,
568 },
569 },
570 },
571 returns => { type => 'string' },
572 code => sub {
573 my ($param) = @_;
574
575 my $rpcenv = PVE::RPCEnvironment::get();
576
577 my $authuser = $rpcenv->get_user();
578
579 PVE::Ceph::Tools::check_ceph_inited();
580
581 my $osdid = $param->{osdid};
5ebb945c 582 my $cleanup = $param->{cleanup};
79fa41a2
DC
583
584 my $rados = PVE::RADOS->new();
220173e9
DJ
585
586 my $osd_belongs_to_node = osd_belongs_to_node(
587 $rados->mon_command({ prefix => 'osd tree' }),
588 $param->{node},
589 $osdid,
590 );
591 die "OSD osd.$osdid does not belong to node $param->{node}!"
592 if !$osd_belongs_to_node;
593
017bb1a8 594 # dies if osdid is unknown
9cc5ac9e 595 my $osdstat = $get_osd_status->($rados, $osdid);
79fa41a2
DC
596
597 die "osd is in use (in == 1)\n" if $osdstat->{in};
598 #&$run_ceph_cmd(['osd', 'out', $osdid]);
599
017bb1a8 600 die "osd is still running (up == 1)\n" if $osdstat->{up};
79fa41a2
DC
601
602 my $osdsection = "osd.$osdid";
603
604 my $worker = sub {
605 my $upid = shift;
606
607 # reopen with longer timeout
608 $rados = PVE::RADOS->new(timeout => PVE::Ceph::Tools::get_config('long_rados_timeout'));
609
610 print "destroy OSD $osdsection\n";
611
612 eval {
613 PVE::Ceph::Services::ceph_service_cmd('stop', $osdsection);
614 PVE::Ceph::Services::ceph_service_cmd('disable', $osdsection);
615 };
616 warn $@ if $@;
617
618 print "Remove $osdsection from the CRUSH map\n";
619 $rados->mon_command({ prefix => "osd crush remove", name => $osdsection, format => 'plain' });
620
621 print "Remove the $osdsection authentication key.\n";
622 $rados->mon_command({ prefix => "auth del", entity => $osdsection, format => 'plain' });
623
624 print "Remove OSD $osdsection\n";
625 $rados->mon_command({ prefix => "osd rm", ids => [ $osdsection ], format => 'plain' });
626
627 # try to unmount from standard mount point
628 my $mountpoint = "/var/lib/ceph/osd/ceph-$osdid";
629
45d602f2
FE
630 # See FIXME below
631 my $udev_trigger_devs = {};
632
79fa41a2
DC
633 my $remove_partition = sub {
634 my ($part) = @_;
635
636 return if !$part || (! -b $part );
637 my $partnum = PVE::Diskmanage::get_partnum($part);
638 my $devpath = PVE::Diskmanage::get_blockdev($part);
639
45d602f2
FE
640 $udev_trigger_devs->{$devpath} = 1;
641
683a3563 642 PVE::Diskmanage::wipe_blockdev($part);
79fa41a2
DC
643 print "remove partition $part (disk '${devpath}', partnum $partnum)\n";
644 eval { run_command(['/sbin/sgdisk', '-d', $partnum, "${devpath}"]); };
645 warn $@ if $@;
79fa41a2
DC
646 };
647
9b44d03d
DC
648 my $osd_list = PVE::Ceph::Tools::ceph_volume_list();
649
b32e9255 650 if ($osd_list->{$osdid}) { # ceph-volume managed
79fa41a2 651
b32e9255 652 eval { PVE::Ceph::Tools::ceph_volume_zap($osdid, $cleanup) };
9b44d03d 653 warn $@ if $@;
5ebb945c
TL
654
655 if ($cleanup) {
9b44d03d 656 # try to remove pvs, but do not fail if it does not work
b32e9255
TL
657 for my $osd_part (@{$osd_list->{$osdid}}) {
658 for my $dev (@{$osd_part->{devices}}) {
c92fc8a1
SI
659 ($dev) = ($dev =~ m|^(/dev/[-_.a-zA-Z0-9\/]+)$|); #untaint
660
259b557c 661 eval { run_command(['/sbin/pvremove', $dev], errfunc => sub {}) };
b32e9255 662 warn $@ if $@;
45d602f2
FE
663
664 $udev_trigger_devs->{$dev} = 1;
b32e9255 665 }
9b44d03d
DC
666 }
667 }
668 } else {
669 my $partitions_to_remove = [];
5ebb945c 670 if ($cleanup) {
9b44d03d
DC
671 if (my $mp = PVE::ProcFSTools::parse_proc_mounts()) {
672 foreach my $line (@$mp) {
673 my ($dev, $path, $fstype) = @$line;
674 next if !($dev && $path && $fstype);
675 next if $dev !~ m|^/dev/|;
676
677 if ($path eq $mountpoint) {
678 abs_path($dev) =~ m|^(/.+)| or die "invalid dev: $dev\n";
679 push @$partitions_to_remove, $1;
680 last;
681 }
682 }
683 }
684
685 foreach my $path (qw(journal block block.db block.wal)) {
686 abs_path("$mountpoint/$path") =~ m|^(/.+)| or die "invalid path: $path\n";
687 push @$partitions_to_remove, $1;
688 }
79fa41a2 689 }
79fa41a2 690
9b44d03d
DC
691 print "Unmount OSD $osdsection from $mountpoint\n";
692 eval { run_command(['/bin/umount', $mountpoint]); };
693 if (my $err = $@) {
694 warn $err;
5ebb945c 695 } elsif ($cleanup) {
9b44d03d
DC
696 #be aware of the ceph udev rules which can remount.
697 foreach my $part (@$partitions_to_remove) {
698 $remove_partition->($part);
699 }
79fa41a2 700 }
79fa41a2 701 }
45d602f2
FE
702
703 # FIXME: Remove once we depend on systemd >= v249.
704 # Work around udev bug https://github.com/systemd/systemd/issues/18525 to ensure the
705 # udev database is updated.
706 if ($cleanup) {
707 eval { run_command(['udevadm', 'trigger', keys $udev_trigger_devs->%*]); };
708 warn $@ if $@;
709 }
79fa41a2
DC
710 };
711
712 return $rpcenv->fork_worker('cephdestroyosd', $osdsection, $authuser, $worker);
713 }});
714
715__PACKAGE__->register_method ({
716 name => 'in',
717 path => '{osdid}/in',
718 method => 'POST',
719 description => "ceph osd in",
720 proxyto => 'node',
721 protected => 1,
722 permissions => {
723 check => ['perm', '/', [ 'Sys.Modify' ]],
724 },
725 parameters => {
726 additionalProperties => 0,
727 properties => {
728 node => get_standard_option('pve-node'),
729 osdid => {
730 description => 'OSD ID',
731 type => 'integer',
732 },
733 },
734 },
735 returns => { type => "null" },
736 code => sub {
737 my ($param) = @_;
738
739 PVE::Ceph::Tools::check_ceph_inited();
740
741 my $osdid = $param->{osdid};
742
743 my $rados = PVE::RADOS->new();
744
9cc5ac9e 745 $get_osd_status->($rados, $osdid); # osd exists?
79fa41a2
DC
746
747 my $osdsection = "osd.$osdid";
748
749 $rados->mon_command({ prefix => "osd in", ids => [ $osdsection ], format => 'plain' });
750
751 return undef;
752 }});
753
754__PACKAGE__->register_method ({
755 name => 'out',
756 path => '{osdid}/out',
757 method => 'POST',
758 description => "ceph osd out",
759 proxyto => 'node',
760 protected => 1,
761 permissions => {
762 check => ['perm', '/', [ 'Sys.Modify' ]],
763 },
764 parameters => {
765 additionalProperties => 0,
766 properties => {
767 node => get_standard_option('pve-node'),
768 osdid => {
769 description => 'OSD ID',
770 type => 'integer',
771 },
772 },
773 },
774 returns => { type => "null" },
775 code => sub {
776 my ($param) = @_;
777
778 PVE::Ceph::Tools::check_ceph_inited();
779
780 my $osdid = $param->{osdid};
781
782 my $rados = PVE::RADOS->new();
783
9cc5ac9e 784 $get_osd_status->($rados, $osdid); # osd exists?
79fa41a2
DC
785
786 my $osdsection = "osd.$osdid";
787
788 $rados->mon_command({ prefix => "osd out", ids => [ $osdsection ], format => 'plain' });
789
790 return undef;
791 }});
792
b7701301
DC
793__PACKAGE__->register_method ({
794 name => 'scrub',
795 path => '{osdid}/scrub',
796 method => 'POST',
797 description => "Instruct the OSD to scrub.",
798 proxyto => 'node',
799 protected => 1,
800 permissions => {
801 check => ['perm', '/', [ 'Sys.Modify' ]],
802 },
803 parameters => {
804 additionalProperties => 0,
805 properties => {
806 node => get_standard_option('pve-node'),
807 osdid => {
808 description => 'OSD ID',
809 type => 'integer',
810 },
811 deep => {
812 description => 'If set, instructs a deep scrub instead of a normal one.',
813 type => 'boolean',
814 optional => 1,
815 default => 0,
816 },
817 },
818 },
819 returns => { type => "null" },
820 code => sub {
821 my ($param) = @_;
822
823 PVE::Ceph::Tools::check_ceph_inited();
824
825 my $osdid = $param->{osdid};
826 my $deep = $param->{deep} // 0;
827
828 my $rados = PVE::RADOS->new();
829
9cc5ac9e 830 $get_osd_status->($rados, $osdid); # osd exists?
b7701301 831
9cc5ac9e 832 my $prefix = $deep ? 'osd deep-scrub' : 'osd scrub';
b7701301
DC
833 $rados->mon_command({ prefix => $prefix, who => $osdid });
834
835 return undef;
836 }});
837
79fa41a2 8381;