Files
pve-storage/PVE/Storage/CephFSPlugin.pm
Thomas Lamprecht 9a80a3eae0 cephfs mount: reload systemd if existing unit gets regenerated
One the first write bringing the unit file in existence we can just
start it, after that we need to tell systemd that we want to actively
reload it.

While this is slightly shaky due to the fact that we do not check all
paths where such a unit could reside, it is something we can do
because earlier one couldn't have a unit/overwrite anyway (from
procfs mountinfo generated one do not support that) and does adding
such override ones from now on should work.

Also note that we can only get here in the "user does no weird stuff"
case when "cephfs_is_mounted" actively tells that there is no cephfs
mounted at the $mountpoint - at which time we can safely re-write the
potential updated unit file, reload and mount again.

So let's make our life a bit easier here until a user actually
complains about a rational issue for this, maybe we have PVE 7.0 then
and can get rid of that anyway :)

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2020-01-29 19:54:40 +01:00

240 lines
6.2 KiB
Perl

package PVE::Storage::CephFSPlugin;
use strict;
use warnings;
use IO::File;
use Net::IP;
use File::Path;
use PVE::Tools qw(run_command file_set_contents);
use PVE::ProcFSTools;
use PVE::Storage::Plugin;
use PVE::JSONSchema qw(get_standard_option);
use PVE::CephConfig;
use base qw(PVE::Storage::Plugin);
sub cephfs_is_mounted {
my ($scfg, $storeid, $mountdata) = @_;
my $cmd_option = PVE::CephConfig::ceph_connect_option($scfg, $storeid);
my $configfile = $cmd_option->{ceph_conf};
my $subdir = $scfg->{subdir} // '/';
my $mountpoint = $scfg->{path};
$mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
return $mountpoint if grep {
$_->[2] =~ m#^ceph|fuse\.ceph-fuse# &&
$_->[0] =~ m#\Q:$subdir\E$|^ceph-fuse$# &&
$_->[1] eq $mountpoint
} @$mountdata;
warn "A filesystem is already mounted on $mountpoint\n"
if grep { $_->[1] eq $mountpoint } @$mountdata;
return undef;
}
# FIXME: duplicate of api/diskmanage one, move to common helper (pve-common's
# Tools or Systemd ?)
sub systemd_escape {
my ($val, $is_path) = @_;
# NOTE: this is not complete, but enough for our needs. normally all
# characters which are not alpha-numerical, '.' or '_' would need escaping
$val =~ s/\-/\\x2d/g;
if ($is_path) {
$val =~ s/^\///g;
$val =~ s/\/$//g;
}
$val =~ s/\//-/g;
return $val;
}
# FIXME: remove in PVE 7.0 where systemd is recent enough to not have those
# local-fs/remote-fs dependency cycles generated for _netdev mounts...
sub systemd_netmount {
my ($where, $type, $what, $opts) = @_;
# don't do default deps, systemd v241 generator produces ordering deps on both
# local-fs(-pre) and remote-fs(-pre) targets if we use the required _netdev
# option. Over thre corners this gets us an ordering cycle on shutdown, which
# may make shutdown hang if the random cycle breaking hits the "wrong" unit to
# delete.
my $unit = <<"EOF";
[Unit]
Description=${where}
DefaultDependencies=no
Requires=system.slice
Wants=network-online.target
Before=umount.target remote-fs.target
After=systemd-journald.socket system.slice network.target -.mount remote-fs-pre.target network-online.target
Conflicts=umount.target
[Mount]
Where=${where}
What=${what}
Type=${type}
Options=${opts}
EOF
my $unit_fn = systemd_escape($where, 1) . ".mount";
my $unit_path = "/run/systemd/system/$unit_fn";
my $daemon_needs_reload = -e $unit_path;
file_set_contents($unit_path, $unit);
run_command(['systemctl', 'daemon-reload'], errmsg => "daemon-reload error")
if $daemon_needs_reload;
run_command(['systemctl', 'start', $unit_fn], errmsg => "mount error");
}
sub cephfs_mount {
my ($scfg, $storeid) = @_;
my $mountpoint = $scfg->{path};
my $subdir = $scfg->{subdir} // '/';
my $cmd_option = PVE::CephConfig::ceph_connect_option($scfg, $storeid);
my $configfile = $cmd_option->{ceph_conf};
my $secretfile = $cmd_option->{keyring};
my $server = $cmd_option->{mon_host} // PVE::CephConfig::get_monaddr_list($configfile);
my $type = 'ceph';
my @opts = ();
if ($scfg->{fuse}) {
$type = 'fuse.ceph';
push @opts, "ceph.id=$cmd_option->{userid}";
push @opts, "ceph.keyfile=$secretfile" if defined($secretfile);
push @opts, "ceph.conf=$configfile" if defined($configfile);
} else {
push @opts, "name=$cmd_option->{userid}";
push @opts, "secretfile=$secretfile" if defined($secretfile);
push @opts, "conf=$configfile" if defined($configfile);
}
push @opts, $scfg->{options} if $scfg->{options};
systemd_netmount($mountpoint, $type, "$server:$subdir", join(',', @opts));
}
# Configuration
sub type {
return 'cephfs';
}
sub plugindata {
return {
content => [ { vztmpl => 1, iso => 1, backup => 1, snippets => 1},
{ backup => 1 }],
};
}
sub properties {
return {
fuse => {
description => "Mount CephFS through FUSE.",
type => 'boolean',
},
subdir => {
description => "Subdir to mount.",
type => 'string', format => 'pve-storage-path',
},
};
}
sub options {
return {
path => { fixed => 1 },
monhost => { optional => 1},
nodes => { optional => 1 },
subdir => { optional => 1 },
disable => { optional => 1 },
options => { optional => 1 },
username => { optional => 1 },
content => { optional => 1 },
format => { optional => 1 },
mkdir => { optional => 1 },
fuse => { optional => 1 },
bwlimit => { optional => 1 },
maxfiles => { optional => 1 },
};
}
sub check_config {
my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_;
$config->{path} = "/mnt/pve/$sectionId" if $create && !$config->{path};
return $class->SUPER::check_config($sectionId, $config, $create, $skipSchemaCheck);
}
# Storage implementation
sub on_add_hook {
my ($class, $storeid, $scfg, %param) = @_;
return if defined($scfg->{monhost}); # nothing to do if not pve managed ceph
PVE::CephConfig::ceph_create_keyfile($scfg->{type}, $storeid);
}
sub on_delete_hook {
my ($class, $storeid, $scfg) = @_;
return if defined($scfg->{monhost}); # nothing to do if not pve managed ceph
PVE::CephConfig::ceph_remove_keyfile($scfg->{type}, $storeid);
}
sub status {
my ($class, $storeid, $scfg, $cache) = @_;
$cache->{mountdata} //= PVE::ProcFSTools::parse_proc_mounts();
return undef if !cephfs_is_mounted($scfg, $storeid, $cache->{mountdata});
return $class->SUPER::status($storeid, $scfg, $cache);
}
sub activate_storage {
my ($class, $storeid, $scfg, $cache) = @_;
$cache->{mountdata} //= PVE::ProcFSTools::parse_proc_mounts();
# NOTE: mkpath may hang if storage is mounted but not reachable
if (!cephfs_is_mounted($scfg, $storeid, $cache->{mountdata})) {
my $path = $scfg->{path};
mkpath $path if !(defined($scfg->{mkdir}) && !$scfg->{mkdir});
die "unable to activate storage '$storeid' - " .
"directory '$path' does not exist\n" if ! -d $path;
cephfs_mount($scfg, $storeid);
}
$class->SUPER::activate_storage($storeid, $scfg, $cache);
}
sub deactivate_storage {
my ($class, $storeid, $scfg, $cache) = @_;
$cache->{mountdata} //= PVE::ProcFSTools::parse_proc_mounts();
my $path = $scfg->{path};
if (cephfs_is_mounted($scfg, $storeid, $cache->{mountdata})) {
run_command(['/bin/umount', $path], errmsg => 'umount error');
}
}
1;