1 package PVE
::CephTools
;
7 use POSIX qw
(LONG_MAX
);
11 use PVE
::Tools
qw(extract_param run_command file_get_contents file_read_firstline dir_glob_regex dir_glob_foreach);
13 my $ccname = 'ceph'; # ceph cluster name
14 my $ceph_cfgdir = "/etc/ceph";
15 my $pve_ceph_cfgpath = "/etc/pve/$ccname.conf";
16 my $ceph_cfgpath = "$ceph_cfgdir/$ccname.conf";
18 my $pve_mon_key_path = "/etc/pve/priv/$ccname.mon.keyring";
19 my $pve_ckeyring_path = "/etc/pve/priv/$ccname.client.admin.keyring";
20 my $ceph_bootstrap_osd_keyring = "/var/lib/ceph/bootstrap-osd/$ccname.keyring";
21 my $ceph_bootstrap_mds_keyring = "/var/lib/ceph/bootstrap-mds/$ccname.keyring";
23 my $ceph_bin = "/usr/bin/ceph";
27 pve_ceph_cfgpath
=> $pve_ceph_cfgpath,
28 pve_mon_key_path
=> $pve_mon_key_path,
29 pve_ckeyring_path
=> $pve_ckeyring_path,
30 ceph_bootstrap_osd_keyring
=> $ceph_bootstrap_osd_keyring,
31 ceph_bootstrap_mds_keyring
=> $ceph_bootstrap_mds_keyring,
32 long_rados_timeout
=> 60,
38 my $value = $config_hash->{$key};
40 die "no such ceph config '$key'" if !$value;
45 sub verify_blockdev_path
{
48 die "missing path" if !$rel_path;
49 my $path = abs_path
($rel_path);
50 die "failed to get absolute path to $rel_path" if !$path;
52 die "got unusual device path '$path'\n" if $path !~ m
|^/dev/(.*)$|;
54 $path = "/dev/$1"; # untaint
56 die "no such block device '$path'\n"
62 sub purge_all_ceph_files
{
63 # fixme: this is very dangerous - should we really support this function?
67 unlink $pve_ceph_cfgpath;
68 unlink $pve_ckeyring_path;
69 unlink $pve_mon_key_path;
71 unlink $ceph_bootstrap_osd_keyring;
72 unlink $ceph_bootstrap_mds_keyring;
74 system("rm -rf /var/lib/ceph/mon/ceph-*");
79 sub check_ceph_installed
{
83 die "ceph binaries not installed\n" if !$noerr;
90 sub check_ceph_inited
{
93 return undef if !check_ceph_installed
($noerr);
95 if (! -f
$pve_ceph_cfgpath) {
96 die "pveceph configuration not initialized\n" if !$noerr;
103 sub check_ceph_enabled
{
106 return undef if !check_ceph_inited
($noerr);
108 if (! -f
$ceph_cfgpath) {
109 die "pveceph configuration not enabled\n" if !$noerr;
116 sub parse_ceph_config
{
119 $filename = $pve_ceph_cfgpath if !$filename;
123 return $cfg if ! -f
$filename;
125 my $fh = IO
::File-
>new($filename, "r") ||
126 die "unable to open '$filename' - $!\n";
130 while (defined(my $line = <$fh>)) {
131 $line =~ s/[;#].*$//;
136 $section = $1 if $line =~ m/^\[(\S+)\]$/;
138 warn "no section - skip: $line\n";
142 if ($line =~ m/^(.*?\S)\s*=\s*(\S.*)$/) {
143 $cfg->{$section}->{$1} = $2;
151 sub write_ceph_config
{
156 my $cond_write_sec = sub {
159 foreach my $section (keys %$cfg) {
160 next if $section !~ m/^$re$/;
161 $out .= "[$section]\n";
162 foreach my $key (sort keys %{$cfg->{$section}}) {
163 $out .= "\t $key = $cfg->{$section}->{$key}\n";
169 &$cond_write_sec('global');
170 &$cond_write_sec('client');
171 &$cond_write_sec('mds');
172 &$cond_write_sec('mds\..*');
173 &$cond_write_sec('mon');
174 &$cond_write_sec('osd');
175 &$cond_write_sec('mon\..*');
176 &$cond_write_sec('osd\..*');
178 PVE
::Tools
::file_set_contents
($pve_ceph_cfgpath, $out);
181 sub setup_pve_symlinks
{
182 # fail if we find a real file instead of a link
183 if (-f
$ceph_cfgpath) {
184 my $lnk = readlink($ceph_cfgpath);
185 die "file '$ceph_cfgpath' already exists\n"
186 if !$lnk || $lnk ne $pve_ceph_cfgpath;
188 symlink($pve_ceph_cfgpath, $ceph_cfgpath) ||
189 die "unable to create symlink '$ceph_cfgpath' - $!\n";
193 sub ceph_service_cmd
{
194 my ($action, $service) = @_;
196 if (systemd_managed
()) {
198 if ($service && $service =~ m/^(mon|osd|mds|radosgw)(\.([A-Za-z0-9]{1,32}))?$/) {
199 $service = defined($3) ?
"ceph-$1\@$3" : "ceph-$1.target";
201 $service = "ceph.target";
204 PVE
::Tools
::run_command
(['/bin/systemctl', $action, $service]);
207 # ceph daemons does not call 'setsid', so we do that ourself
208 # (fork_worker send KILL to whole process group)
209 PVE
::Tools
::run_command
(['setsid', 'service', 'ceph', '-c', $pve_ceph_cfgpath, $action, $service]);
216 my $fd = IO
::File-
>new("/proc/mounts", "r") ||
217 die "unable to open /proc/mounts - $!\n";
221 while (defined(my $line = <$fd>)) {
222 my ($dev, $path, $fstype) = split(/\s+/, $line);
223 next if !($dev && $path && $fstype);
224 next if $dev !~ m
|^/dev/|;
225 my $real_dev = abs_path
($dev);
226 $mounted->{$real_dev} = $path;
230 my $dev_is_mounted = sub {
232 return $mounted->{$dev};
235 my $dir_is_empty = sub {
238 my $dh = IO
::Dir-
>new ($dir);
241 while (defined(my $tmp = $dh->read)) {
242 next if $tmp eq '.' || $tmp eq '..';
250 my $journal_uuid = '45b0969e-9b03-4f30-b4c6-b4b80ceff106';
252 my $journalhash = {};
253 dir_glob_foreach
('/dev/disk/by-parttypeuuid', "$journal_uuid\..+", sub {
255 my $real_dev = abs_path
("/dev/disk/by-parttypeuuid/$entry");
256 $journalhash->{$real_dev} = 1;
259 dir_glob_foreach
('/sys/block', '.*', sub {
262 return if $dev eq '.';
263 return if $dev eq '..';
265 return if $dev =~ m
|^ram\d
+$|; # skip ram devices
266 return if $dev =~ m
|^loop\d
+$|; # skip loop devices
267 return if $dev =~ m
|^md\d
+$|; # skip md devices
268 return if $dev =~ m
|^dm-
.*$|; # skip dm related things
269 return if $dev =~ m
|^fd\d
+$|; # skip Floppy
270 return if $dev =~ m
|^sr\d
+$|; # skip CDs
272 my $devdir = "/sys/block/$dev/device";
273 return if ! -d
$devdir;
275 my $size = file_read_firstline
("/sys/block/$dev/size");
280 my $info = `udevadm info --path /sys/block/$dev --query all`;
283 return if $info !~ m/^E: DEVTYPE=disk$/m;
284 return if $info =~ m/^E: ID_CDROM/m;
286 my $serial = 'unknown';
287 if ($info =~ m/^E: ID_SERIAL_SHORT=(\S+)$/m) {
292 if ($info =~ m/^E: ID_PART_TABLE_TYPE=gpt$/m) {
296 # detect SSD (fixme - currently only works for ATA disks)
297 my $rpm = 7200; # default guess
298 if ($info =~ m/^E: ID_ATA_ROTATION_RATE_RPM=(\d+)$/m) {
302 my $vendor = file_read_firstline
("$devdir/vendor") || 'unknown';
303 my $model = file_read_firstline
("$devdir/model") || 'unknown';
307 $used = 'LVM' if !&$dir_is_empty("/sys/block/$dev/holders");
309 $used = 'mounted' if &$dev_is_mounted("/dev/$dev");
311 $disklist->{$dev} = {
322 my $journal_count = 0;
324 my $found_partitions;
326 my $found_mountpoints;
327 dir_glob_foreach
("/sys/block/$dev", "$dev.+", sub {
330 $found_partitions = 1;
332 if (my $mp = &$dev_is_mounted("/dev/$part")) {
333 $found_mountpoints = 1;
334 if ($mp =~ m
|^/var/lib
/ceph/osd/ceph-
(\d
+)$|) {
338 if (!&$dir_is_empty("/sys/block/$dev/$part/holders")) {
341 $journal_count++ if $journalhash->{"/dev/$part"};
344 $used = 'mounted' if $found_mountpoints && !$used;
345 $used = 'LVM' if $found_lvm && !$used;
346 $used = 'partitions' if $found_partitions && !$used;
348 $disklist->{$dev}->{used
} = $used if $used;
349 $disklist->{$dev}->{osdid
} = $osdid;
350 $disklist->{$dev}->{journals
} = $journal_count;
356 # Ceph versions greater Hammer use 'ceph' as user and group instead
357 # of 'root', and use systemd.
358 sub systemd_managed
{
360 if (-f
"/lib/systemd/system/ceph-osd\@.service") {