]>
Commit | Line | Data |
---|---|---|
6fb08cb9 | 1 | package PVE::Ceph::Tools; |
a34866f0 DM |
2 | |
3 | use strict; | |
4 | use warnings; | |
f8346b52 | 5 | |
a34866f0 | 6 | use File::Path; |
b436dca8 | 7 | use File::Basename; |
f8346b52 | 8 | use IO::File; |
48e8a06d | 9 | use JSON; |
a34866f0 | 10 | |
f8346b52 | 11 | use PVE::Tools qw(run_command dir_glob_foreach); |
48e8a06d | 12 | use PVE::Cluster qw(cfs_read_file); |
f96d7012 | 13 | use PVE::RADOS; |
91dfa228 AA |
14 | use PVE::Ceph::Services; |
15 | use PVE::CephConfig; | |
a34866f0 DM |
16 | |
17 | my $ccname = 'ceph'; # ceph cluster name | |
18 | my $ceph_cfgdir = "/etc/ceph"; | |
19 | my $pve_ceph_cfgpath = "/etc/pve/$ccname.conf"; | |
20 | my $ceph_cfgpath = "$ceph_cfgdir/$ccname.conf"; | |
21 | ||
22 | my $pve_mon_key_path = "/etc/pve/priv/$ccname.mon.keyring"; | |
23 | my $pve_ckeyring_path = "/etc/pve/priv/$ccname.client.admin.keyring"; | |
17709566 | 24 | my $ckeyring_path = "/etc/ceph/ceph.client.admin.keyring"; |
a34866f0 DM |
25 | my $ceph_bootstrap_osd_keyring = "/var/lib/ceph/bootstrap-osd/$ccname.keyring"; |
26 | my $ceph_bootstrap_mds_keyring = "/var/lib/ceph/bootstrap-mds/$ccname.keyring"; | |
b82649cc | 27 | my $ceph_mds_data_dir = '/var/lib/ceph/mds'; |
a34866f0 | 28 | |
c64c04dd AA |
29 | my $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 | |
38 | my $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 |
50 | sub 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 |
76 | sub 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 |
84 | sub 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 | 94 | sub 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 |
132 | sub 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 | ||
155 | sub 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 | |
169 | sub 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 |
179 | sub 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 | ||
192 | sub 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 |
205 | sub 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 |
258 | sub 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 |
270 | sub 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 |
287 | sub 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 |
303 | sub 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 | 322 | sub 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 |
344 | sub 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 | ||
360 | sub 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 |
371 | sub 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 |
398 | sub 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 | ||
459 | sub 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 | 471 | 1; |