]> git.proxmox.com Git - pve-manager.git/blame - PVE/Ceph/Services.pm
api: backup: update: check permissions of delete params too
[pve-manager.git] / PVE / Ceph / Services.pm
CommitLineData
27439be6
DC
1package PVE::Ceph::Services;
2
3use strict;
4use warnings;
5
6use PVE::Ceph::Tools;
d5373b7d 7use PVE::Cluster qw(cfs_read_file);
27439be6
DC
8use PVE::Tools qw(run_command);
9use PVE::RADOS;
10
4e76dbd7 11use JSON;
27439be6
DC
12use File::Path;
13
7e98f79e
DC
14use 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
18sub 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
41sub 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
50sub 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
77sub 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
89sub 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
100sub 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
112sub 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
166sub 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
179sub 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
222sub 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
247sub 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
301sub 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
339sub 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
377sub 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 4071;