]>
Commit | Line | Data |
---|---|---|
27439be6 DC |
1 | package PVE::Ceph::Services; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use PVE::Ceph::Tools; | |
d5373b7d | 7 | use PVE::Cluster qw(cfs_read_file); |
27439be6 DC |
8 | use PVE::Tools qw(run_command); |
9 | use PVE::RADOS; | |
10 | ||
4e76dbd7 | 11 | use JSON; |
27439be6 DC |
12 | use File::Path; |
13 | ||
7e98f79e DC |
14 | use constant SERVICE_REGEX => '[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?'; |
15 | ||
ac4f971d | 16 | # checks /etc/systemd/system/ceph-* to list all services, even if not running |
74628668 | 17 | # also checks /var/lib/ceph/$type |
23b20ae4 DC |
18 | sub get_local_services { |
19 | my $res = {}; | |
ac4f971d | 20 | |
23b20ae4 DC |
21 | for my $type (qw(mds mgr mon)) { |
22 | $res->{$type} = {}; | |
ac4f971d | 23 | |
23b20ae4 DC |
24 | my $path = "/etc/systemd/system/ceph-$type.target.wants"; |
25 | my $regex = "ceph-$type\@(.*)\.service"; | |
26 | PVE::Tools::dir_glob_foreach($path, $regex, sub { | |
27 | my (undef, $id) = @_; | |
74628668 DC |
28 | $res->{$type}->{$id}->{service} = 1; |
29 | }); | |
30 | ||
31 | $path = "/var/lib/ceph/$type"; | |
32 | $regex = "([^-]+)-(.*)"; | |
33 | PVE::Tools::dir_glob_foreach($path, $regex, sub { | |
34 | my (undef, $clustername, $id) = @_; | |
35 | $res->{$type}->{$id}->{direxists} = 1; | |
23b20ae4 DC |
36 | }); |
37 | } | |
38 | return $res; | |
39 | } | |
40 | ||
4e76dbd7 DC |
41 | sub broadcast_ceph_services { |
42 | my $services = get_local_services(); | |
43 | ||
44 | for my $type (keys %$services) { | |
45 | my $data = encode_json($services->{$type}); | |
46 | PVE::Cluster::broadcast_node_kv("ceph-$type", $data); | |
47 | } | |
48 | } | |
49 | ||
0496138e DC |
50 | sub broadcast_ceph_versions { |
51 | my ($version, $buildcommit, $vers_parts) = PVE::Ceph::Tools::get_local_version(1); | |
52 | ||
53 | if ($version) { | |
25bea057 FE |
54 | my $nodename = PVE::INotify::nodename(); |
55 | my $old = PVE::Cluster::get_node_kv("ceph-versions", $nodename); | |
56 | if (defined($old->{$nodename})) { | |
d66b726c | 57 | $old = eval { decode_json($old->{$nodename}) }; |
656667db | 58 | warn $@ if $@; # should not happen |
d66b726c | 59 | if (defined($old) && $old->{buildcommit} eq $buildcommit && $old->{version}->{str} eq $version) { |
656667db TL |
60 | return; # up to date, nothing to do so avoid (not exactly cheap) broadcast |
61 | } | |
62 | } | |
04aae00d TL |
63 | # FIXME: remove with 8.0 (or 7.2, its not _that_ bad) - for backward compat only |
64 | PVE::Cluster::broadcast_node_kv("ceph-version", $version); | |
65 | ||
0496138e DC |
66 | my $node_versions = { |
67 | version => { | |
68 | str => $version, | |
69 | parts => $vers_parts, | |
70 | }, | |
71 | buildcommit => $buildcommit, | |
72 | }; | |
73 | PVE::Cluster::broadcast_node_kv("ceph-versions", encode_json($node_versions)); | |
74 | } | |
75 | } | |
76 | ||
77 | sub get_ceph_versions { | |
78 | my $res; | |
79 | ||
a1132828 | 80 | if (defined(my $versions = PVE::Cluster::get_node_kv("ceph-versions"))) { |
0496138e | 81 | $res = { |
a1132828 | 82 | map { eval { $_ => decode_json($versions->{$_}) } } keys %$versions |
0496138e DC |
83 | }; |
84 | } | |
85 | ||
86 | return $res; | |
87 | } | |
88 | ||
4e76dbd7 DC |
89 | sub get_cluster_service { |
90 | my ($type) = @_; | |
930c1d6a | 91 | |
4e76dbd7 | 92 | my $raw = PVE::Cluster::get_node_kv("ceph-$type"); |
b1a3af5c TL |
93 | my $res = { |
94 | map { $_ => eval { decode_json($raw->{$_}) } } keys $raw->%* | |
95 | }; | |
4e76dbd7 DC |
96 | |
97 | return $res; | |
98 | } | |
99 | ||
27439be6 DC |
100 | sub ceph_service_cmd { |
101 | my ($action, $service) = @_; | |
102 | ||
7e98f79e | 103 | if ($service && $service =~ m/^(mon|osd|mds|mgr|radosgw)(\.(${\SERVICE_REGEX}))?$/) { |
bba5c712 | 104 | $service = defined($3) ? "ceph-$1\@$3" : "ceph-$1.target"; |
27439be6 | 105 | } else { |
bba5c712 | 106 | $service = "ceph.target"; |
27439be6 | 107 | } |
bba5c712 TL |
108 | |
109 | run_command(['/bin/systemctl', $action, $service]); | |
27439be6 DC |
110 | } |
111 | ||
d5373b7d DC |
112 | sub get_services_info { |
113 | my ($type, $cfg, $rados) = @_; | |
114 | ||
115 | my $result = {}; | |
116 | my $services = get_cluster_service($type); | |
117 | ||
118 | foreach my $host (sort keys %$services) { | |
9ecb3d10 TL |
119 | foreach my $id (sort keys %{$services->{$host}}) { |
120 | my $service = $result->{$id} = $services->{$host}->{$id}; | |
121 | $service->{host} = $host; | |
122 | $service->{name} = $id; | |
123 | $service->{state} = 'unknown'; | |
124 | if ($service->{service}) { | |
125 | $service->{state} = 'stopped'; | |
d5373b7d DC |
126 | } |
127 | } | |
128 | } | |
129 | ||
130 | if (!$cfg) { | |
131 | $cfg = cfs_read_file('ceph.conf'); | |
132 | } | |
133 | ||
134 | foreach my $section (keys %$cfg) { | |
135 | my $d = $cfg->{$section}; | |
136 | if ($section =~ m/^$type\.(\S+)$/) { | |
137 | my $id = $1; | |
9ecb3d10 | 138 | my $service = $result->{$id}; |
342de4e7 | 139 | my $addr = $d->{"${type}_addr"} // $d->{public_addr} // $d->{host}; |
9ecb3d10 TL |
140 | $service->{name} //= $id; |
141 | $service->{addr} //= $addr; | |
142 | $service->{state} //= 'unknown'; | |
143 | $service->{host} //= $d->{host}; | |
d5373b7d DC |
144 | } |
145 | } | |
146 | ||
147 | if (!$rados) { | |
e7e61576 | 148 | return $result; |
d5373b7d | 149 | } |
e7e61576 | 150 | |
d5373b7d | 151 | my $metadata = $rados->mon_command({ prefix => "$type metadata" }); |
9ecb3d10 | 152 | foreach my $info (@$metadata) { |
46fb9c50 DC |
153 | my $id = $info->{name} // $info->{id}; |
154 | my $service = $result->{$id}; | |
9ecb3d10 TL |
155 | $service->{ceph_version_short} = $info->{ceph_version_short}; |
156 | $service->{ceph_version} = $info->{ceph_version}; | |
157 | $service->{host} //= $info->{hostname}; | |
158 | $service->{addr} //= $info->{addr}; | |
d5373b7d DC |
159 | } |
160 | ||
161 | return $result; | |
162 | } | |
163 | ||
27439be6 DC |
164 | # MDS |
165 | ||
166 | sub list_local_mds_ids { | |
167 | my $mds_list = []; | |
168 | my $ceph_mds_data_dir = PVE::Ceph::Tools::get_config('ceph_mds_data_dir'); | |
169 | my $ccname = PVE::Ceph::Tools::get_config('ccname'); | |
170 | ||
171 | PVE::Tools::dir_glob_foreach($ceph_mds_data_dir, qr/$ccname-(\S+)/, sub { | |
172 | my (undef, $mds_id) = @_; | |
173 | push @$mds_list, $mds_id; | |
174 | }); | |
175 | ||
176 | return $mds_list; | |
177 | } | |
178 | ||
179 | sub get_cluster_mds_state { | |
180 | my ($rados) = @_; | |
181 | ||
182 | my $mds_state = {}; | |
183 | ||
184 | if (!defined($rados)) { | |
185 | $rados = PVE::RADOS->new(); | |
186 | } | |
187 | ||
188 | my $add_state = sub { | |
c51db18c | 189 | my ($mds, $fsname) = @_; |
27439be6 DC |
190 | |
191 | my $state = {}; | |
192 | $state->{addr} = $mds->{addr}; | |
193 | $state->{rank} = $mds->{rank}; | |
194 | $state->{standby_replay} = $mds->{standby_replay} ? 1 : 0; | |
195 | $state->{state} = $mds->{state}; | |
c51db18c | 196 | $state->{fs_name} = $fsname; |
27439be6 DC |
197 | |
198 | $mds_state->{$mds->{name}} = $state; | |
199 | }; | |
200 | ||
201 | my $mds_dump = $rados->mon_command({ prefix => 'mds stat' }); | |
202 | my $fsmap = $mds_dump->{fsmap}; | |
203 | ||
204 | ||
205 | foreach my $mds (@{$fsmap->{standbys}}) { | |
206 | $add_state->($mds); | |
207 | } | |
208 | ||
c51db18c DC |
209 | for my $fs_info (@{$fsmap->{filesystems}}) { |
210 | my $active_mds = $fs_info->{mdsmap}->{info}; | |
27439be6 | 211 | |
c51db18c DC |
212 | # normally there's only one active MDS, but we can have multiple active for |
213 | # different ranks (e.g., different cephs path hierarchy). So just add all. | |
214 | foreach my $mds (values %$active_mds) { | |
215 | $add_state->($mds, $fs_info->{mdsmap}->{fs_name}); | |
216 | } | |
27439be6 DC |
217 | } |
218 | ||
219 | return $mds_state; | |
220 | } | |
221 | ||
352a0e5c DC |
222 | sub is_mds_active { |
223 | my ($rados, $fs_name) = @_; | |
27439be6 DC |
224 | |
225 | if (!defined($rados)) { | |
226 | $rados = PVE::RADOS->new(); | |
227 | } | |
228 | ||
229 | my $mds_dump = $rados->mon_command({ prefix => 'mds stat' }); | |
352a0e5c | 230 | my $fsmap = $mds_dump->{fsmap}->{filesystems}; |
27439be6 | 231 | |
352a0e5c | 232 | if (!($fsmap && scalar(@$fsmap) > 0)) { |
27439be6 DC |
233 | return undef; |
234 | } | |
352a0e5c DC |
235 | for my $fs (@$fsmap) { |
236 | next if defined($fs_name) && $fs->{mdsmap}->{fs_name} ne $fs_name; | |
27439be6 | 237 | |
352a0e5c DC |
238 | my $active_mds = $fs->{mdsmap}->{info}; |
239 | for my $mds (values %$active_mds) { | |
240 | return 1 if $mds->{state} eq 'up:active'; | |
241 | } | |
27439be6 DC |
242 | } |
243 | ||
244 | return 0; | |
245 | } | |
246 | ||
247 | sub create_mds { | |
248 | my ($id, $rados) = @_; | |
249 | ||
250 | # `ceph fs status` fails with numeric only ID. | |
251 | die "ID: $id, numeric only IDs are not supported\n" | |
252 | if $id =~ /^\d+$/; | |
253 | ||
254 | if (!defined($rados)) { | |
255 | $rados = PVE::RADOS->new(); | |
256 | } | |
257 | ||
258 | my $ccname = PVE::Ceph::Tools::get_config('ccname'); | |
259 | my $service_dir = "/var/lib/ceph/mds/$ccname-$id"; | |
260 | my $service_keyring = "$service_dir/keyring"; | |
261 | my $service_name = "mds.$id"; | |
262 | ||
263 | die "ceph MDS directory '$service_dir' already exists\n" | |
264 | if -d $service_dir; | |
265 | ||
266 | print "creating MDS directory '$service_dir'\n"; | |
267 | eval { File::Path::mkpath($service_dir) }; | |
268 | my $err = $@; | |
269 | die "creation MDS directory '$service_dir' failed\n" if $err; | |
270 | ||
271 | # http://docs.ceph.com/docs/luminous/install/manual-deployment/#adding-mds | |
272 | my $priv = [ | |
273 | mon => 'allow profile mds', | |
274 | osd => 'allow rwx', | |
275 | mds => 'allow *', | |
276 | ]; | |
277 | ||
278 | print "creating keys for '$service_name'\n"; | |
279 | my $output = $rados->mon_command({ | |
280 | prefix => 'auth get-or-create', | |
281 | entity => $service_name, | |
282 | caps => $priv, | |
283 | format => 'plain', | |
284 | }); | |
285 | ||
286 | PVE::Tools::file_set_contents($service_keyring, $output); | |
287 | ||
288 | print "setting ceph as owner for service directory\n"; | |
289 | run_command(["chown", 'ceph:ceph', '-R', $service_dir]); | |
290 | ||
291 | print "enabling service 'ceph-mds\@$id.service'\n"; | |
292 | ceph_service_cmd('enable', $service_name); | |
293 | print "starting service 'ceph-mds\@$id.service'\n"; | |
294 | ceph_service_cmd('start', $service_name); | |
295 | ||
4e76dbd7 DC |
296 | broadcast_ceph_services(); |
297 | ||
27439be6 DC |
298 | return undef; |
299 | }; | |
300 | ||
301 | sub destroy_mds { | |
302 | my ($id, $rados) = @_; | |
303 | ||
304 | if (!defined($rados)) { | |
305 | $rados = PVE::RADOS->new(); | |
306 | } | |
307 | ||
308 | my $ccname = PVE::Ceph::Tools::get_config('ccname'); | |
309 | ||
310 | my $service_name = "mds.$id"; | |
311 | my $service_dir = "/var/lib/ceph/mds/$ccname-$id"; | |
312 | ||
313 | print "disabling service 'ceph-mds\@$id.service'\n"; | |
314 | ceph_service_cmd('disable', $service_name); | |
315 | print "stopping service 'ceph-mds\@$id.service'\n"; | |
316 | ceph_service_cmd('stop', $service_name); | |
317 | ||
318 | if (-d $service_dir) { | |
319 | print "removing ceph-mds directory '$service_dir'\n"; | |
320 | File::Path::remove_tree($service_dir); | |
321 | } else { | |
322 | warn "cannot cleanup MDS $id directory, '$service_dir' not found\n" | |
323 | } | |
324 | ||
325 | print "removing ceph auth for '$service_name'\n"; | |
326 | $rados->mon_command({ | |
327 | prefix => 'auth del', | |
328 | entity => $service_name, | |
329 | format => 'plain' | |
330 | }); | |
331 | ||
4e76dbd7 DC |
332 | broadcast_ceph_services(); |
333 | ||
27439be6 DC |
334 | return undef; |
335 | }; | |
336 | ||
be7edba1 DC |
337 | # MGR |
338 | ||
339 | sub create_mgr { | |
340 | my ($id, $rados) = @_; | |
341 | ||
342 | my $clustername = PVE::Ceph::Tools::get_config('ccname'); | |
343 | my $mgrdir = "/var/lib/ceph/mgr/$clustername-$id"; | |
344 | my $mgrkeyring = "$mgrdir/keyring"; | |
345 | my $mgrname = "mgr.$id"; | |
346 | ||
51498a26 | 347 | die "ceph manager directory '$mgrdir' already exists\n" if -d $mgrdir; |
be7edba1 DC |
348 | |
349 | print "creating manager directory '$mgrdir'\n"; | |
350 | mkdir $mgrdir; | |
351 | print "creating keys for '$mgrname'\n"; | |
51498a26 TL |
352 | my $output = $rados->mon_command({ |
353 | prefix => 'auth get-or-create', | |
354 | entity => $mgrname, | |
355 | caps => [ | |
356 | mon => 'allow profile mgr', | |
357 | osd => 'allow *', | |
358 | mds => 'allow *', | |
359 | ], | |
360 | format => 'plain' | |
361 | }); | |
be7edba1 DC |
362 | PVE::Tools::file_set_contents($mgrkeyring, $output); |
363 | ||
364 | print "setting owner for directory\n"; | |
365 | run_command(["chown", 'ceph:ceph', '-R', $mgrdir]); | |
366 | ||
367 | print "enabling service 'ceph-mgr\@$id.service'\n"; | |
368 | ceph_service_cmd('enable', $mgrname); | |
369 | print "starting service 'ceph-mgr\@$id.service'\n"; | |
370 | ceph_service_cmd('start', $mgrname); | |
4e76dbd7 DC |
371 | |
372 | broadcast_ceph_services(); | |
373 | ||
374 | return undef; | |
be7edba1 DC |
375 | } |
376 | ||
377 | sub destroy_mgr { | |
b5215730 | 378 | my ($mgrid, $rados) = @_; |
be7edba1 DC |
379 | |
380 | my $clustername = PVE::Ceph::Tools::get_config('ccname'); | |
381 | my $mgrname = "mgr.$mgrid"; | |
382 | my $mgrdir = "/var/lib/ceph/mgr/$clustername-$mgrid"; | |
383 | ||
384 | die "ceph manager directory '$mgrdir' not found\n" | |
385 | if ! -d $mgrdir; | |
386 | ||
387 | print "disabling service 'ceph-mgr\@$mgrid.service'\n"; | |
388 | ceph_service_cmd('disable', $mgrname); | |
389 | print "stopping service 'ceph-mgr\@$mgrid.service'\n"; | |
390 | ceph_service_cmd('stop', $mgrname); | |
391 | ||
392 | print "removing manager directory '$mgrdir'\n"; | |
393 | File::Path::remove_tree($mgrdir); | |
4e76dbd7 | 394 | |
b5215730 DC |
395 | print "removing authkeys for $mgrname\n"; |
396 | if (!$rados) { | |
397 | $rados = PVE::RADOS->new(); | |
398 | } | |
399 | ||
400 | $rados->mon_command({ prefix => 'auth del', entity => "$mgrname" }); | |
401 | ||
4e76dbd7 DC |
402 | broadcast_ceph_services(); |
403 | ||
404 | return undef; | |
be7edba1 DC |
405 | } |
406 | ||
27439be6 | 407 | 1; |