]> git.proxmox.com Git - pve-manager.git/blame - PVE/Ceph/Tools.pm
use RealmCombobox from widget-toolkit
[pve-manager.git] / PVE / Ceph / Tools.pm
CommitLineData
6fb08cb9 1package PVE::Ceph::Tools;
a34866f0
DM
2
3use strict;
4use warnings;
f8346b52 5
a34866f0 6use File::Path;
b436dca8 7use File::Basename;
f8346b52 8use IO::File;
48e8a06d 9use JSON;
a34866f0 10
f8346b52 11use PVE::Tools qw(run_command dir_glob_foreach);
48e8a06d 12use PVE::Cluster qw(cfs_read_file);
f96d7012 13use PVE::RADOS;
91dfa228
AA
14use PVE::Ceph::Services;
15use PVE::CephConfig;
a34866f0
DM
16
17my $ccname = 'ceph'; # ceph cluster name
18my $ceph_cfgdir = "/etc/ceph";
19my $pve_ceph_cfgpath = "/etc/pve/$ccname.conf";
20my $ceph_cfgpath = "$ceph_cfgdir/$ccname.conf";
21
22my $pve_mon_key_path = "/etc/pve/priv/$ccname.mon.keyring";
23my $pve_ckeyring_path = "/etc/pve/priv/$ccname.client.admin.keyring";
17709566 24my $ckeyring_path = "/etc/ceph/ceph.client.admin.keyring";
a34866f0
DM
25my $ceph_bootstrap_osd_keyring = "/var/lib/ceph/bootstrap-osd/$ccname.keyring";
26my $ceph_bootstrap_mds_keyring = "/var/lib/ceph/bootstrap-mds/$ccname.keyring";
b82649cc 27my $ceph_mds_data_dir = '/var/lib/ceph/mds';
a34866f0 28
c64c04dd
AA
29my $ceph_service = {
30 ceph_bin => "/usr/bin/ceph",
31 ceph_mon => "/usr/bin/ceph-mon",
32 ceph_mgr => "/usr/bin/ceph-mgr",
b82649cc
TL
33 ceph_osd => "/usr/bin/ceph-osd",
34 ceph_mds => "/usr/bin/ceph-mds",
48e8a06d 35 ceph_volume => '/usr/sbin/ceph-volume',
c64c04dd 36};
a34866f0
DM
37
38my $config_hash = {
39 ccname => $ccname,
40 pve_ceph_cfgpath => $pve_ceph_cfgpath,
41 pve_mon_key_path => $pve_mon_key_path,
42 pve_ckeyring_path => $pve_ckeyring_path,
43 ceph_bootstrap_osd_keyring => $ceph_bootstrap_osd_keyring,
44 ceph_bootstrap_mds_keyring => $ceph_bootstrap_mds_keyring,
b82649cc 45 ceph_mds_data_dir => $ceph_mds_data_dir,
7d4fc5ef 46 long_rados_timeout => 60,
91dfa228 47 ceph_cfgpath => $ceph_cfgpath,
a34866f0
DM
48};
49
c64c04dd
AA
50sub get_local_version {
51 my ($noerr) = @_;
52
6fb08cb9 53 if (check_ceph_installed('ceph_bin', $noerr)) {
c64c04dd 54 my $ceph_version;
3248590d
TL
55 run_command(
56 [ $ceph_service->{ceph_bin}, '--version' ],
57 noerr => $noerr,
58 outfunc => sub { $ceph_version = shift if !defined $ceph_version },
59 );
60 return undef if !defined $ceph_version;
61
52bdf49f 62 if ($ceph_version =~ /^ceph.*\s(\d+(?:\.\d+)+(?:-pve\d+)?)\s+(?:\(([a-zA-Z0-9]+)\))?/) {
3248590d
TL
63 my ($version, $buildcommit) = ($1, $2);
64 my $subversions = [ split(/\.|-/, $version) ];
65
66 # return (version, buildid, major, minor, ...) : major;
67 return wantarray
68 ? ($version, $buildcommit, $subversions)
69 : $subversions->[0];
c64c04dd
AA
70 }
71 }
72
73 return undef;
74}
75
8ba0d0a0
FG
76sub get_cluster_versions {
77 my ($service, $noerr) = @_;
78
79 my $rados = PVE::RADOS->new();
80 my $cmd = $service ? "$service versions" : 'versions';
81 return $rados->mon_command({ prefix => $cmd });
82}
83
a34866f0
DM
84sub get_config {
85 my $key = shift;
86
87 my $value = $config_hash->{$key};
88
6f8bf83d 89 die "no such ceph config '$key'" if !$value;
a34866f0
DM
90
91 return $value;
92}
93
a34866f0 94sub purge_all_ceph_files {
91dfa228
AA
95 my ($services) = @_;
96 my $is_local_mon;
97 my $monlist = [ split(',', PVE::CephConfig::get_monaddr_list($pve_ceph_cfgpath)) ];
98
99 foreach my $service (keys %$services) {
100 my $type = $services->{$service};
101 next if (!%$type);
102
103 foreach my $name (keys %$type) {
104 my $dir_exists = $type->{$name}->{direxists};
105
106 $is_local_mon = grep($type->{$name}->{addr}, @$monlist)
107 if $service eq 'mon';
108
109 my $path = "/var/lib/ceph/$service";
110 $path = '/var/log/ceph' if $service eq 'logs';
111 if ($dir_exists) {
112 my $err;
113 File::Path::remove_tree($path, {
114 keep_root => 1,
115 error => \$err,
116 });
117 warn "Error removing path, '$path'\n" if @$err;
118 }
119 }
120 }
a34866f0 121
91dfa228
AA
122 if (scalar @$monlist > 0 && !$is_local_mon) {
123 warn "Foreign MON address in ceph.conf. Keeping config & keyrings\n"
124 } else {
125 print "Removing config & keyring files\n";
126 foreach my $file (%$config_hash) {
127 unlink $file if (-e $file);
128 }
129 }
130}
a34866f0 131
91dfa228
AA
132sub purge_all_ceph_services {
133 my ($services) = @_;
134
135 foreach my $service (keys %$services) {
136 my $type = $services->{$service};
137 next if (!%$type);
138
139 foreach my $name (keys %$type) {
140 my $service_exists = $type->{$name}->{service};
141
142 if ($service_exists) {
143 eval {
144 PVE::Ceph::Services::ceph_service_cmd('disable', "$service.$name");
145 PVE::Ceph::Services::ceph_service_cmd('stop', "$service.$name");
146 };
147 my $err = $@ if $@;
148 warn "Could not disable/stop ceph-$service\@$name, error: $err\n"
149 if $err;
150 }
151 }
152 }
a34866f0
DM
153}
154
155sub check_ceph_installed {
c64c04dd
AA
156 my ($service, $noerr) = @_;
157
158 $service = 'ceph_bin' if !defined($service);
a34866f0 159
c64c04dd
AA
160 if (! -x $ceph_service->{$service}) {
161 die "binary not installed: $ceph_service->{$service}\n" if !$noerr;
a34866f0
DM
162 return undef;
163 }
164
165 return 1;
166}
167
7ef69f33
TL
168
169sub check_ceph_configured {
170
171 check_ceph_inited();
172
173 die "ceph not fully configured - missing '$pve_ckeyring_path'\n"
174 if ! -f $pve_ckeyring_path;
175
176 return 1;
177}
178
a34866f0
DM
179sub check_ceph_inited {
180 my ($noerr) = @_;
181
315304f3 182 return undef if !check_ceph_installed('ceph_mon', $noerr);
6f8bf83d 183
a34866f0
DM
184 if (! -f $pve_ceph_cfgpath) {
185 die "pveceph configuration not initialized\n" if !$noerr;
186 return undef;
187 }
188
189 return 1;
190}
191
192sub check_ceph_enabled {
193 my ($noerr) = @_;
194
195 return undef if !check_ceph_inited($noerr);
196
197 if (! -f $ceph_cfgpath) {
198 die "pveceph configuration not enabled\n" if !$noerr;
199 return undef;
200 }
201
202 return 1;
203}
204
f96d7012
TL
205sub create_pool {
206 my ($pool, $param, $rados) = @_;
207
208 if (!defined($rados)) {
209 $rados = PVE::RADOS->new();
210 }
211
6ad70a2b 212 my $pg_num = $param->{pg_num} || 128;
f96d7012
TL
213 my $size = $param->{size} || 3;
214 my $min_size = $param->{min_size} || 2;
215 my $application = $param->{application} // 'rbd';
216
217 $rados->mon_command({
218 prefix => "osd pool create",
219 pool => $pool,
220 pg_num => int($pg_num),
221 format => 'plain',
222 });
223
224 $rados->mon_command({
225 prefix => "osd pool set",
226 pool => $pool,
227 var => 'min_size',
507b7cfe 228 val => "$min_size",
f96d7012
TL
229 format => 'plain',
230 });
231
232 $rados->mon_command({
233 prefix => "osd pool set",
234 pool => $pool,
235 var => 'size',
507b7cfe 236 val => "$size",
f96d7012
TL
237 format => 'plain',
238 });
239
240 if (defined($param->{crush_rule})) {
241 $rados->mon_command({
242 prefix => "osd pool set",
243 pool => $pool,
244 var => 'crush_rule',
245 val => $param->{crush_rule},
246 format => 'plain',
247 });
248 }
249
250 $rados->mon_command({
251 prefix => "osd pool application enable",
252 pool => $pool,
253 app => $application,
254 });
255
256}
257
7e1a9d25
TL
258sub ls_pools {
259 my ($pool, $rados) = @_;
260
261 if (!defined($rados)) {
262 $rados = PVE::RADOS->new();
263 }
264
265 my $res = $rados->mon_command({ prefix => "osd lspools" });
266
267 return $res;
268}
269
f96d7012
TL
270sub destroy_pool {
271 my ($pool, $rados) = @_;
272
273 if (!defined($rados)) {
274 $rados = PVE::RADOS->new();
275 }
276
277 # fixme: '--yes-i-really-really-mean-it'
278 $rados->mon_command({
279 prefix => "osd pool delete",
280 pool => $pool,
281 pool2 => $pool,
f8eade23 282 'yes_i_really_really_mean_it' => JSON::true,
f96d7012
TL
283 format => 'plain',
284 });
285}
286
a34866f0
DM
287sub setup_pve_symlinks {
288 # fail if we find a real file instead of a link
289 if (-f $ceph_cfgpath) {
290 my $lnk = readlink($ceph_cfgpath);
291 die "file '$ceph_cfgpath' already exists\n"
292 if !$lnk || $lnk ne $pve_ceph_cfgpath;
293 } else {
c5a673ed 294 mkdir $ceph_cfgdir;
a34866f0
DM
295 symlink($pve_ceph_cfgpath, $ceph_cfgpath) ||
296 die "unable to create symlink '$ceph_cfgpath' - $!\n";
297 }
c18db15b
TL
298 my $ceph_uid = getpwnam('ceph');
299 my $ceph_gid = getgrnam('ceph');
300 chown $ceph_uid, $ceph_gid, $ceph_cfgdir;
a34866f0
DM
301}
302
d558d296
DC
303sub get_or_create_admin_keyring {
304 if (! -f $pve_ckeyring_path) {
305 run_command("ceph-authtool --create-keyring $pve_ckeyring_path " .
306 "--gen-key -n client.admin " .
307 "--cap mon 'allow *' " .
308 "--cap osd 'allow *' " .
309 "--cap mds 'allow *' " .
310 "--cap mgr 'allow *' ");
311 # we do not want to overwrite it
312 if (! -f $ckeyring_path) {
313 run_command("cp $pve_ckeyring_path $ckeyring_path");
ea60e3b7 314 run_command("chown ceph:ceph $ckeyring_path");
d558d296
DC
315 }
316 }
317 return $pve_ckeyring_path;
318}
319
456a7f4d
AA
320# wipe the first 200 MB to clear off leftovers from previous use, otherwise a
321# create OSD fails.
1343ae6d 322sub wipe_disks {
456a7f4d
AA
323 my (@devs) = @_;
324
b436dca8
AA
325 my @wipe_cmd = qw(/bin/dd if=/dev/zero bs=1M conv=fdatasync);
326
a1a7aa74 327 foreach my $devpath (@devs) {
b436dca8
AA
328 my $devname = basename($devpath);
329 my $dev_size = PVE::Tools::file_get_contents("/sys/class/block/$devname/size");
330
331 ($dev_size) = $dev_size =~ m|(\d+)|; # untaint $dev_size
332 die "Coulnd't get the size of the device $devname\n" if (!defined($dev_size));
333
334 my $size = ($dev_size * 512 / 1024 / 1024);
335 my $count = ($size < 200) ? $size : 200;
336
337 print "wipe disk/partition: $devpath\n";
338 eval { run_command([@wipe_cmd, "count=$count", "of=${devpath}"]) };
339 warn $@ if $@;
456a7f4d
AA
340 }
341};
342
48e8a06d
DC
343# get ceph-volume managed osds
344sub ceph_volume_list {
345 my $result = {};
48e8a06d
DC
346
347 if (!check_ceph_installed('ceph_volume', 1)) {
348 return $result;
349 }
350
d79e9eb5
TL
351 my $output = '';
352 my $cmd = [ $ceph_service->{ceph_volume}, 'lvm', 'list', '--format', 'json' ];
353 run_command($cmd, outfunc => sub { $output .= shift });
48e8a06d
DC
354
355 $result = eval { decode_json($output) };
356 warn $@ if $@;
357 return $result;
358}
359
360sub ceph_volume_zap {
361 my ($osdid, $destroy) = @_;
362
363 die "no osdid given\n" if !defined($osdid);
364
d79e9eb5 365 my $cmd = [ $ceph_service->{ceph_volume}, 'lvm', 'zap', '--osd-id', $osdid ];
48e8a06d
DC
366 push @$cmd, '--destroy' if $destroy;
367
368 run_command($cmd);
369}
370
d4e7f1bf
DC
371sub get_db_wal_sizes {
372 my $res = {};
373
374 my $rados = PVE::RADOS->new();
375 my $db_config = $rados->mon_command({ prefix => 'config-key dump', key => 'config/' });
376
377 $res->{db} = $db_config->{"config/osd/bluestore_block_db_size"} //
378 $db_config->{"config/global/bluestore_block_db_size"};
379
380 $res->{wal} = $db_config->{"config/osd/bluestore_block_wal_size"} //
381 $db_config->{"config/global/bluestore_block_wal_size"};
382
383 if (!$res->{db} || !$res->{wal}) {
384 my $cfg = cfs_read_file('ceph.conf');
385 if (!$res->{db}) {
386 $res->{db} = $cfg->{osd}->{bluestore_block_db_size} //
387 $cfg->{global}->{bluestore_block_db_size};
388 }
389
390 if (!$res->{wal}) {
391 $res->{wal} = $cfg->{osd}->{bluestore_block_wal_size} //
392 $cfg->{global}->{bluestore_block_wal_size};
393 }
394 }
395
396 return $res;
397}
735f24eb
TL
398sub get_possible_osd_flags {
399 my $possible_flags = {
400 pause => {
401 description => 'Pauses read and writes.',
402 type => 'boolean',
403 optional=> 1,
404 },
405 noup => {
406 description => 'OSDs are not allowed to start.',
407 type => 'boolean',
408 optional=> 1,
409 },
410 nodown => {
411 description => 'OSD failure reports are being ignored, such that the monitors will not mark OSDs down.',
412 type => 'boolean',
413 optional=> 1,
414 },
415 noout => {
416 description => 'OSDs will not automatically be marked out after the configured interval.',
417 type => 'boolean',
418 optional=> 1,
419 },
420 noin => {
421 description => 'OSDs that were previously marked out will not be marked back in when they start.',
422 type => 'boolean',
423 optional=> 1,
424 },
425 nobackfill => {
426 description => 'Backfilling of PGs is suspended.',
427 type => 'boolean',
428 optional=> 1,
429 },
430 norebalance => {
431 description => 'Rebalancing of PGs is suspended.',
432 type => 'boolean',
433 optional=> 1,
434 },
435 norecover => {
436 description => 'Recovery of PGs is suspended.',
437 type => 'boolean',
438 optional=> 1,
439 },
440 noscrub => {
441 description => 'Scrubbing is disabled.',
442 type => 'boolean',
443 optional=> 1,
444 },
445 'nodeep-scrub' => {
446 description => 'Deep Scrubbing is disabled.',
447 type => 'boolean',
448 optional=> 1,
449 },
450 notieragent => {
451 description => 'Cache tiering activity is suspended.',
452 type => 'boolean',
453 optional=> 1,
454 },
455 };
456 return $possible_flags;
457}
458
459sub get_real_flag_name {
460 my ($flag) = @_;
461
462 # the 'pause' flag gets always set to both 'pauserd' and 'pausewr'
463 # so decide that the 'pause' flag is set if we detect 'pauserd'
464 my $flagmap = {
465 'pause' => 'pauserd',
466 };
467
468 return $flagmap->{$flag} // $flag;
469}
d4e7f1bf 470
a34866f0 4711;