]> git.proxmox.com Git - pve-storage.git/blame - src/PVE/CephConfig.pm
cephconfig: allow writing arbitrary sections
[pve-storage.git] / src / PVE / CephConfig.pm
CommitLineData
4050fcc1 1package PVE::CephConfig;
9b7ba1db
AA
2
3use strict;
4use warnings;
5use Net::IP;
6use PVE::Tools qw(run_command);
71be0113 7use PVE::Cluster qw(cfs_register_file);
9b7ba1db 8
71be0113
DC
9cfs_register_file('ceph.conf',
10 \&parse_ceph_config,
11 \&write_ceph_config);
e34ce144 12
71be0113
DC
13sub parse_ceph_config {
14 my ($filename, $raw) = @_;
e34ce144 15
71be0113 16 my $cfg = {};
5b5534a9 17 return $cfg if !defined($raw);
e34ce144 18
71be0113 19 my @lines = split /\n/, $raw;
e34ce144
AA
20
21 my $section;
22
23 foreach my $line (@lines) {
c835d9ec 24 $line =~ s/#.*$//;
e34ce144 25 $line =~ s/^\s+//;
c835d9ec 26 $line =~ s/^;.*$//;
e34ce144
AA
27 $line =~ s/\s+$//;
28 next if !$line;
29
30 $section = $1 if $line =~ m/^\[(\S+)\]$/;
31 if (!$section) {
32 warn "no section - skip: $line\n";
33 next;
34 }
35
36 if ($line =~ m/^(.*?\S)\s*=\s*(\S.*)$/) {
9841fa31 37 my ($key, $val) = ($1, $2);
60fd7082 38 # ceph treats ' ', '_' and '-' in keys the same, so lets do too
9841fa31
DC
39 $key =~ s/[-\ ]/_/g;
40 $cfg->{$section}->{$key} = $val;
e34ce144
AA
41 }
42
43 }
44
45 return $cfg;
71be0113
DC
46}
47
48my $parse_ceph_file = sub {
49 my ($filename) = @_;
50
51 my $cfg = {};
52
53 return $cfg if ! -f $filename;
54
55 my $content = PVE::Tools::file_get_contents($filename);
56
57 return parse_ceph_config($filename, $content);
e34ce144
AA
58};
59
71be0113
DC
60sub write_ceph_config {
61 my ($filename, $cfg) = @_;
62
8777f4a6 63 my $written_sections = {};
71be0113
DC
64 my $out = '';
65
66 my $cond_write_sec = sub {
67 my $re = shift;
68
e8dbfc50 69 for my $section (sort keys $cfg->%*) {
71be0113 70 next if $section !~ m/^$re$/;
8777f4a6 71 next if exists($written_sections->{$section});
e8dbfc50 72
71be0113 73 $out .= "[$section]\n";
e8dbfc50 74 for my $key (sort keys $cfg->{$section}->%*) {
71be0113
DC
75 $out .= "\t $key = $cfg->{$section}->{$key}\n";
76 }
77 $out .= "\n";
8777f4a6
MC
78
79 $written_sections->{$section} = 1;
71be0113
DC
80 }
81 };
82
e8dbfc50
MC
83 my @rexprs = (
84 qr/global/,
85 qr/client/,
86
87 qr/mds/,
88 qr/mon/,
89 qr/osd/,
90 qr/mgr/,
71be0113 91
e8dbfc50
MC
92 qr/mds\..*/,
93 qr/mon\..*/,
94 qr/osd\..*/,
95 qr/mgr\..*/,
8777f4a6
MC
96
97 qr/.*/,
e8dbfc50 98 );
71be0113 99
e8dbfc50
MC
100 for my $re (@rexprs) {
101 $cond_write_sec->($re);
102 }
71be0113
DC
103
104 return $out;
105}
106
e34ce144
AA
107my $ceph_get_key = sub {
108 my ($keyfile, $username) = @_;
109
110 my $key = $parse_ceph_file->($keyfile);
111 my $secret = $key->{"client.$username"}->{key};
112
113 return $secret;
114};
115
c8a32345
DC
116my $get_host = sub {
117 my ($hostport) = @_;
118 my ($host, $port) = PVE::Tools::parse_host_and_port($hostport);
119 if (!defined($host)) {
120 return "";
121 }
122 $port = defined($port) ? ":$port" : '';
123 $host = "[$host]" if Net::IP::ip_is_ipv6($host);
124 return "${host}${port}";
125};
126
e34ce144
AA
127sub get_monaddr_list {
128 my ($configfile) = shift;
129
e34ce144
AA
130 if (!defined($configfile)) {
131 warn "No ceph config specified\n";
132 return;
133 }
134
135 my $config = $parse_ceph_file->($configfile);
e34ce144 136
5b79dac9 137 my $monhostlist = {};
4b3088a0 138
ffc31266 139 # get all ip addresses from mon_host
5b79dac9
DC
140 my $monhosts = [ split (/[ ,;]+/, $config->{global}->{mon_host} // "") ];
141
142 foreach my $monhost (@$monhosts) {
143 $monhost =~ s/^\[?v\d\://; # remove beginning of vector
144 $monhost =~ s|/\d+\]?||; # remove end of vector
145 my $host = $get_host->($monhost);
146 if ($host ne "") {
147 $monhostlist->{$host} = 1;
148 }
149 }
150
151 # then get all addrs from mon. sections
152 for my $section ( keys %$config ) {
153 next if $section !~ m/^mon\./;
154
155 if (my $addr = $config->{$section}->{mon_addr}) {
156 $monhostlist->{$addr} = 1;
157 }
158 }
159
160 return join(',', sort keys %$monhostlist);
161}
e34ce144 162
9b7ba1db
AA
163sub hostlist {
164 my ($list_text, $separator) = @_;
165
166 my @monhostlist = PVE::Tools::split_list($list_text);
c8a32345 167 return join($separator, map { $get_host->($_) } @monhostlist);
9b7ba1db
AA
168}
169
5527c824
TL
170my $ceph_check_keyfile = sub {
171 my ($filename, $type) = @_;
172
173 return if ! -f $filename;
174
175 my $content = PVE::Tools::file_get_contents($filename);
176 eval {
177 die if !$content;
178
179 if ($type eq 'rbd') {
180 die if $content !~ /\s*\[\S+\]\s*key\s*=\s*\S+==\s*$/m;
181 } elsif ($type eq 'cephfs') {
182 die if $content !~ /\S+==\s*$/;
183 }
184 };
185 die "Not a proper $type authentication file: $filename\n" if $@;
186
187 return undef;
188};
189
9b7ba1db
AA
190sub ceph_connect_option {
191 my ($scfg, $storeid, %options) = @_;
192
193 my $cmd_option = {};
9b7ba1db
AA
194 my $keyfile = "/etc/pve/priv/ceph/${storeid}.keyring";
195 $keyfile = "/etc/pve/priv/ceph/${storeid}.secret" if ($scfg->{type} eq 'cephfs');
196 my $pveceph_managed = !defined($scfg->{monhost});
197
428872eb 198 $cmd_option->{ceph_conf} = '/etc/pve/ceph.conf' if $pveceph_managed;
9b7ba1db 199
5527c824 200 $ceph_check_keyfile->($keyfile, $scfg->{type});
3e479172 201
428872eb
TL
202 if (-e "/etc/pve/priv/ceph/${storeid}.conf") {
203 # allow custom ceph configuration for external clusters
9b7ba1db
AA
204 if ($pveceph_managed) {
205 warn "ignoring custom ceph config for storage '$storeid', 'monhost' is not set (assuming pveceph managed cluster)!\n";
206 } else {
428872eb 207 $cmd_option->{ceph_conf} = "/etc/pve/priv/ceph/${storeid}.conf";
9b7ba1db
AA
208 }
209 }
210
211 $cmd_option->{keyring} = $keyfile if (-e $keyfile);
212 $cmd_option->{auth_supported} = (defined $cmd_option->{keyring}) ? 'cephx' : 'none';
213 $cmd_option->{userid} = $scfg->{username} ? $scfg->{username} : 'admin';
214 $cmd_option->{mon_host} = hostlist($scfg->{monhost}, ',') if (defined($scfg->{monhost}));
215
216 if (%options) {
217 foreach my $k (keys %options) {
218 $cmd_option->{$k} = $options{$k};
219 }
220 }
221
222 return $cmd_option;
223
224}
225
e34ce144 226sub ceph_create_keyfile {
a4a1fe64 227 my ($type, $storeid, $secret) = @_;
e34ce144
AA
228
229 my $extension = 'keyring';
230 $extension = 'secret' if ($type eq 'cephfs');
231
232 my $ceph_admin_keyring = '/etc/pve/priv/ceph.client.admin.keyring';
233 my $ceph_storage_keyring = "/etc/pve/priv/ceph/${storeid}.$extension";
234
235 die "ceph authx keyring file for storage '$storeid' already exists!\n"
a4a1fe64 236 if -e $ceph_storage_keyring && !defined($secret);
e34ce144 237
a4a1fe64 238 if (-e $ceph_admin_keyring || defined($secret)) {
e34ce144 239 eval {
a4a1fe64
AL
240 if (defined($secret)) {
241 mkdir '/etc/pve/priv/ceph';
e7bc1f03
AL
242 chomp $secret;
243 PVE::Tools::file_set_contents($ceph_storage_keyring, "${secret}\n", 0400);
a4a1fe64 244 } elsif ($type eq 'rbd') {
e34ce144
AA
245 mkdir '/etc/pve/priv/ceph';
246 PVE::Tools::file_copy($ceph_admin_keyring, $ceph_storage_keyring);
247 } elsif ($type eq 'cephfs') {
a4a1fe64 248 my $cephfs_secret = $ceph_get_key->($ceph_admin_keyring, 'admin');
e34ce144 249 mkdir '/etc/pve/priv/ceph';
e7bc1f03
AL
250 chomp $cephfs_secret;
251 PVE::Tools::file_set_contents($ceph_storage_keyring, "${cephfs_secret}\n", 0400);
e34ce144
AA
252 }
253 };
254 if (my $err = $@) {
255 unlink $ceph_storage_keyring;
256 die "failed to copy ceph authx $extension for storage '$storeid': $err\n";
257 }
258 } else {
259 warn "$ceph_admin_keyring not found, authentication is disabled.\n";
260 }
261}
262
263sub ceph_remove_keyfile {
264 my ($type, $storeid) = @_;
265
266 my $extension = 'keyring';
267 $extension = 'secret' if ($type eq 'cephfs');
268 my $ceph_storage_keyring = "/etc/pve/priv/ceph/${storeid}.$extension";
269
270 if (-f $ceph_storage_keyring) {
271 unlink($ceph_storage_keyring) or warn "removing keyring of storage failed: $!\n";
272 }
273}
274
e54c3e33
AA
275my $ceph_version_parser = sub {
276 my $ceph_version = shift;
277 # FIXME this is the same as pve-manager PVE::Ceph::Tools get_local_version
5c04a0b3 278 if ($ceph_version =~ /^ceph.*\sv?(\d+(?:\.\d+)+(?:-pve\d+)?)\s+(?:\(([a-zA-Z0-9]+)\))?/) {
e54c3e33
AA
279 my ($version, $buildcommit) = ($1, $2);
280 my $subversions = [ split(/\.|-/, $version) ];
281
282 return ($subversions, $version, $buildcommit);
283 }
284 warn "Could not parse Ceph version: '$ceph_version'\n";
285};
286
7435dc90 287sub local_ceph_version {
e54c3e33
AA
288 my ($cache) = @_;
289
290 my $version_string = $cache;
291 if (!defined($version_string)) {
292 run_command('ceph --version', outfunc => sub {
293 $version_string = shift;
294 });
295 }
296 return undef if !defined($version_string);
297 # subversion is an array ref. with the version parts from major to minor
298 # version is the filtered version string
299 my ($subversions, $version) = $ceph_version_parser->($version_string);
300
301 return wantarray ? ($subversions, $version) : $version;
302}
303
9b7ba1db 3041;