]> git.proxmox.com Git - pve-storage.git/blame - PVE/Storage/CephFSPlugin.pm
fix random hangs on reboot with active CephFS mount ordering cycle
[pve-storage.git] / PVE / Storage / CephFSPlugin.pm
CommitLineData
e34ce144
AA
1package PVE::Storage::CephFSPlugin;
2
3use strict;
4use warnings;
5402cea5 5
e34ce144
AA
6use IO::File;
7use Net::IP;
8use File::Path;
5402cea5 9
d9ece228 10use PVE::Tools qw(run_command file_set_contents);
e34ce144
AA
11use PVE::ProcFSTools;
12use PVE::Storage::Plugin;
13use PVE::JSONSchema qw(get_standard_option);
4050fcc1 14use PVE::CephConfig;
e34ce144
AA
15
16use base qw(PVE::Storage::Plugin);
17
18sub cephfs_is_mounted {
19 my ($scfg, $storeid, $mountdata) = @_;
20
4050fcc1 21 my $cmd_option = PVE::CephConfig::ceph_connect_option($scfg, $storeid);
e34ce144 22 my $configfile = $cmd_option->{ceph_conf};
e34ce144
AA
23
24 my $subdir = $scfg->{subdir} // '/';
25 my $mountpoint = $scfg->{path};
e34ce144
AA
26
27 $mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
28 return $mountpoint if grep {
29 $_->[2] =~ m#^ceph|fuse\.ceph-fuse# &&
82881c5f 30 $_->[0] =~ m#\Q:$subdir\E$|^ceph-fuse$# &&
e34ce144
AA
31 $_->[1] eq $mountpoint
32 } @$mountdata;
33
34 warn "A filesystem is already mounted on $mountpoint\n"
35 if grep { $_->[1] eq $mountpoint } @$mountdata;
36
37 return undef;
38}
39
d9ece228 40
edaaf47a
TL
41# FIXME: duplicate of api/diskmanage one, move to common helper (pve-common's
42# Tools or Systemd ?)
43sub systemd_escape {
44 my ($val, $is_path) = @_;
45
46 # NOTE: this is not complete, but enough for our needs. normally all
47 # characters which are not alpha-numerical, '.' or '_' would need escaping
48 $val =~ s/\-/\\x2d/g;
49
50 if ($is_path) {
51 $val =~ s/^\///g;
52 $val =~ s/\/$//g;
53 }
54 $val =~ s/\//-/g;
55
56 return $val;
57}
58
d9ece228
TL
59# FIXME: remove in PVE 7.0 where systemd is recent enough to not have those
60# local-fs/remote-fs dependency cycles generated for _netdev mounts...
61sub systemd_netmount {
62 my ($where, $type, $what, $opts) = @_;
63
64# don't do default deps, systemd v241 generator produces ordering deps on both
65# local-fs(-pre) and remote-fs(-pre) targets if we use the required _netdev
66# option. Over thre corners this gets us an ordering cycle on shutdown, which
67# may make shutdown hang if the random cycle breaking hits the "wrong" unit to
68# delete.
69 my $unit = <<"EOF";
70[Unit]
71Description=${where}
72DefaultDependencies=no
73Requires=system.slice
74Wants=network-online.target
75Before=umount.target remote-fs.target
76After=systemd-journald.socket system.slice network.target -.mount remote-fs-pre.target network-online.target
77Conflicts=umount.target
78
79[Mount]
80Where=${where}
81What=${what}
82Type=${type}
83Options=${opts}
84EOF
85
86 my $unit_fn = systemd_escape($where, 1) . ".mount";
87 my $unit_path = "/run/systemd/system/$unit_fn";
88
89 file_set_contents($unit_path, $unit);
90 run_command(['systemctl', 'start', $unit_fn], errmsg => "mount error");
91
92}
93
e34ce144
AA
94sub cephfs_mount {
95 my ($scfg, $storeid) = @_;
96
97 my $cmd;
98 my $mountpoint = $scfg->{path};
99 my $subdir = $scfg->{subdir} // '/';
100
4050fcc1 101 my $cmd_option = PVE::CephConfig::ceph_connect_option($scfg, $storeid);
e34ce144
AA
102 my $configfile = $cmd_option->{ceph_conf};
103 my $secretfile = $cmd_option->{keyring};
4050fcc1 104 my $server = $cmd_option->{mon_host} // PVE::CephConfig::get_monaddr_list($configfile);
e34ce144
AA
105
106 # fuse -> client-enforced quotas (kernel doesn't), updates w/ ceph-fuse pkg
107 # kernel -> better performance, less frequent updates
108 if ($scfg->{fuse}) {
109 # FIXME: ceph-fuse client complains about missing ceph.conf or keyring if
110 # not provided on its default locations but still connects. Fix upstream??
111 $cmd = ['/usr/bin/ceph-fuse', '-n', "client.$cmd_option->{userid}", '-m', $server];
112 push @$cmd, '--keyfile', $secretfile if defined($secretfile);
113 push @$cmd, '-r', $subdir if !($subdir =~ m|^/$|);
114 push @$cmd, $mountpoint;
115 push @$cmd, '--conf', $configfile if defined($configfile);
d9ece228
TL
116
117 if ($scfg->{options}) {
118 push @$cmd, '-o', $scfg->{options};
119 }
120
121 run_command($cmd, errmsg => "mount error");
5402cea5 122 } else {
e34ce144 123 my $source = "$server:$subdir";
d9ece228
TL
124 my @opts = ( "name=$cmd_option->{userid}" );
125 push @opts, "secretfile=$secretfile" if defined($secretfile);
126 push @opts, $scfg->{options} if $scfg->{options};
e34ce144 127
d9ece228 128 systemd_netmount($mountpoint, 'ceph', $source, join(',', @opts));
e34ce144 129 }
e34ce144
AA
130}
131
132# Configuration
133
134sub type {
135 return 'cephfs';
136}
137
138sub plugindata {
139 return {
d1eb35ea 140 content => [ { vztmpl => 1, iso => 1, backup => 1, snippets => 1},
e34ce144
AA
141 { backup => 1 }],
142 };
143}
144
145sub properties {
146 return {
147 fuse => {
148 description => "Mount CephFS through FUSE.",
149 type => 'boolean',
150 },
151 subdir => {
152 description => "Subdir to mount.",
153 type => 'string', format => 'pve-storage-path',
154 },
155 };
156}
157
158sub options {
159 return {
160 path => { fixed => 1 },
161 monhost => { optional => 1},
162 nodes => { optional => 1 },
163 subdir => { optional => 1 },
164 disable => { optional => 1 },
165 options => { optional => 1 },
166 username => { optional => 1 },
167 content => { optional => 1 },
168 format => { optional => 1 },
169 mkdir => { optional => 1 },
170 fuse => { optional => 1 },
171 bwlimit => { optional => 1 },
d35a0b4b 172 maxfiles => { optional => 1 },
e34ce144
AA
173 };
174}
175
176sub check_config {
177 my ($class, $sectionId, $config, $create, $skipSchemaCheck) = @_;
178
179 $config->{path} = "/mnt/pve/$sectionId" if $create && !$config->{path};
180
181 return $class->SUPER::check_config($sectionId, $config, $create, $skipSchemaCheck);
182}
183
184# Storage implementation
185
186sub on_add_hook {
187 my ($class, $storeid, $scfg, %param) = @_;
188
189 return if defined($scfg->{monhost}); # nothing to do if not pve managed ceph
190
4050fcc1 191 PVE::CephConfig::ceph_create_keyfile($scfg->{type}, $storeid);
e34ce144
AA
192}
193
194sub on_delete_hook {
195 my ($class, $storeid, $scfg) = @_;
196
197 return if defined($scfg->{monhost}); # nothing to do if not pve managed ceph
198
4050fcc1 199 PVE::CephConfig::ceph_remove_keyfile($scfg->{type}, $storeid);
e34ce144
AA
200}
201
202sub status {
203 my ($class, $storeid, $scfg, $cache) = @_;
204
5402cea5 205 $cache->{mountdata} //= PVE::ProcFSTools::parse_proc_mounts();
e34ce144
AA
206
207 return undef if !cephfs_is_mounted($scfg, $storeid, $cache->{mountdata});
208
209 return $class->SUPER::status($storeid, $scfg, $cache);
210}
211
212sub activate_storage {
213 my ($class, $storeid, $scfg, $cache) = @_;
214
5402cea5 215 $cache->{mountdata} //= PVE::ProcFSTools::parse_proc_mounts();
e34ce144 216
5402cea5 217 # NOTE: mkpath may hang if storage is mounted but not reachable
e34ce144 218 if (!cephfs_is_mounted($scfg, $storeid, $cache->{mountdata})) {
5402cea5 219 my $path = $scfg->{path};
e34ce144
AA
220
221 mkpath $path if !(defined($scfg->{mkdir}) && !$scfg->{mkdir});
222
223 die "unable to activate storage '$storeid' - " .
224 "directory '$path' does not exist\n" if ! -d $path;
225
226 cephfs_mount($scfg, $storeid);
227 }
228
229 $class->SUPER::activate_storage($storeid, $scfg, $cache);
230}
231
232sub deactivate_storage {
233 my ($class, $storeid, $scfg, $cache) = @_;
234
5402cea5 235 $cache->{mountdata} //= PVE::ProcFSTools::parse_proc_mounts();
e34ce144
AA
236
237 my $path = $scfg->{path};
238
239 if (cephfs_is_mounted($scfg, $storeid, $cache->{mountdata})) {
5402cea5 240 run_command(['/bin/umount', $path], errmsg => 'umount error');
e34ce144
AA
241 }
242}
243
2441;