]>
Commit | Line | Data |
---|---|---|
4050fcc1 | 1 | package PVE::CephConfig; |
9b7ba1db AA |
2 | |
3 | use strict; | |
4 | use warnings; | |
5 | use Net::IP; | |
6 | use PVE::Tools qw(run_command); | |
71be0113 | 7 | use PVE::Cluster qw(cfs_register_file); |
9b7ba1db | 8 | |
71be0113 DC |
9 | cfs_register_file('ceph.conf', |
10 | \&parse_ceph_config, | |
11 | \&write_ceph_config); | |
e34ce144 | 12 | |
71be0113 DC |
13 | sub 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 | ||
48 | my $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 |
60 | sub 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}->%*) { |
d93f8aea | 75 | $out .= "\t$key = $cfg->{$section}->{$key}\n"; |
71be0113 DC |
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/, | |
f9d9bd32 | 85 | |
e8dbfc50 | 86 | qr/client/, |
f9d9bd32 | 87 | qr/client\..*/, |
e8dbfc50 MC |
88 | |
89 | qr/mds/, | |
e8dbfc50 | 90 | qr/mds\..*/, |
f9d9bd32 MC |
91 | |
92 | qr/mon/, | |
e8dbfc50 | 93 | qr/mon\..*/, |
f9d9bd32 MC |
94 | |
95 | qr/osd/, | |
e8dbfc50 | 96 | qr/osd\..*/, |
f9d9bd32 MC |
97 | |
98 | qr/mgr/, | |
e8dbfc50 | 99 | qr/mgr\..*/, |
8777f4a6 MC |
100 | |
101 | qr/.*/, | |
e8dbfc50 | 102 | ); |
71be0113 | 103 | |
e8dbfc50 MC |
104 | for my $re (@rexprs) { |
105 | $cond_write_sec->($re); | |
106 | } | |
71be0113 | 107 | |
ad6bcc9e MC |
108 | # Escape comment literals that aren't escaped already |
109 | $out =~ s/(?<!\\)([;#])/\\$1/gm; | |
110 | ||
71be0113 DC |
111 | return $out; |
112 | } | |
113 | ||
e34ce144 AA |
114 | my $ceph_get_key = sub { |
115 | my ($keyfile, $username) = @_; | |
116 | ||
117 | my $key = $parse_ceph_file->($keyfile); | |
118 | my $secret = $key->{"client.$username"}->{key}; | |
119 | ||
120 | return $secret; | |
121 | }; | |
122 | ||
c8a32345 DC |
123 | my $get_host = sub { |
124 | my ($hostport) = @_; | |
125 | my ($host, $port) = PVE::Tools::parse_host_and_port($hostport); | |
126 | if (!defined($host)) { | |
127 | return ""; | |
128 | } | |
129 | $port = defined($port) ? ":$port" : ''; | |
130 | $host = "[$host]" if Net::IP::ip_is_ipv6($host); | |
131 | return "${host}${port}"; | |
132 | }; | |
133 | ||
e34ce144 AA |
134 | sub get_monaddr_list { |
135 | my ($configfile) = shift; | |
136 | ||
e34ce144 AA |
137 | if (!defined($configfile)) { |
138 | warn "No ceph config specified\n"; | |
139 | return; | |
140 | } | |
141 | ||
142 | my $config = $parse_ceph_file->($configfile); | |
e34ce144 | 143 | |
5b79dac9 | 144 | my $monhostlist = {}; |
4b3088a0 | 145 | |
ffc31266 | 146 | # get all ip addresses from mon_host |
5b79dac9 DC |
147 | my $monhosts = [ split (/[ ,;]+/, $config->{global}->{mon_host} // "") ]; |
148 | ||
149 | foreach my $monhost (@$monhosts) { | |
150 | $monhost =~ s/^\[?v\d\://; # remove beginning of vector | |
151 | $monhost =~ s|/\d+\]?||; # remove end of vector | |
152 | my $host = $get_host->($monhost); | |
153 | if ($host ne "") { | |
154 | $monhostlist->{$host} = 1; | |
155 | } | |
156 | } | |
157 | ||
158 | # then get all addrs from mon. sections | |
159 | for my $section ( keys %$config ) { | |
160 | next if $section !~ m/^mon\./; | |
161 | ||
162 | if (my $addr = $config->{$section}->{mon_addr}) { | |
163 | $monhostlist->{$addr} = 1; | |
164 | } | |
165 | } | |
166 | ||
167 | return join(',', sort keys %$monhostlist); | |
168 | } | |
e34ce144 | 169 | |
9b7ba1db AA |
170 | sub hostlist { |
171 | my ($list_text, $separator) = @_; | |
172 | ||
173 | my @monhostlist = PVE::Tools::split_list($list_text); | |
c8a32345 | 174 | return join($separator, map { $get_host->($_) } @monhostlist); |
9b7ba1db AA |
175 | } |
176 | ||
5527c824 TL |
177 | my $ceph_check_keyfile = sub { |
178 | my ($filename, $type) = @_; | |
179 | ||
180 | return if ! -f $filename; | |
181 | ||
182 | my $content = PVE::Tools::file_get_contents($filename); | |
183 | eval { | |
184 | die if !$content; | |
185 | ||
186 | if ($type eq 'rbd') { | |
187 | die if $content !~ /\s*\[\S+\]\s*key\s*=\s*\S+==\s*$/m; | |
188 | } elsif ($type eq 'cephfs') { | |
189 | die if $content !~ /\S+==\s*$/; | |
190 | } | |
191 | }; | |
192 | die "Not a proper $type authentication file: $filename\n" if $@; | |
193 | ||
194 | return undef; | |
195 | }; | |
196 | ||
9b7ba1db AA |
197 | sub ceph_connect_option { |
198 | my ($scfg, $storeid, %options) = @_; | |
199 | ||
200 | my $cmd_option = {}; | |
9b7ba1db AA |
201 | my $keyfile = "/etc/pve/priv/ceph/${storeid}.keyring"; |
202 | $keyfile = "/etc/pve/priv/ceph/${storeid}.secret" if ($scfg->{type} eq 'cephfs'); | |
203 | my $pveceph_managed = !defined($scfg->{monhost}); | |
204 | ||
428872eb | 205 | $cmd_option->{ceph_conf} = '/etc/pve/ceph.conf' if $pveceph_managed; |
9b7ba1db | 206 | |
5527c824 | 207 | $ceph_check_keyfile->($keyfile, $scfg->{type}); |
3e479172 | 208 | |
428872eb TL |
209 | if (-e "/etc/pve/priv/ceph/${storeid}.conf") { |
210 | # allow custom ceph configuration for external clusters | |
9b7ba1db AA |
211 | if ($pveceph_managed) { |
212 | warn "ignoring custom ceph config for storage '$storeid', 'monhost' is not set (assuming pveceph managed cluster)!\n"; | |
213 | } else { | |
428872eb | 214 | $cmd_option->{ceph_conf} = "/etc/pve/priv/ceph/${storeid}.conf"; |
9b7ba1db AA |
215 | } |
216 | } | |
217 | ||
218 | $cmd_option->{keyring} = $keyfile if (-e $keyfile); | |
219 | $cmd_option->{auth_supported} = (defined $cmd_option->{keyring}) ? 'cephx' : 'none'; | |
220 | $cmd_option->{userid} = $scfg->{username} ? $scfg->{username} : 'admin'; | |
221 | $cmd_option->{mon_host} = hostlist($scfg->{monhost}, ',') if (defined($scfg->{monhost})); | |
222 | ||
223 | if (%options) { | |
224 | foreach my $k (keys %options) { | |
225 | $cmd_option->{$k} = $options{$k}; | |
226 | } | |
227 | } | |
228 | ||
229 | return $cmd_option; | |
230 | ||
231 | } | |
232 | ||
e34ce144 | 233 | sub ceph_create_keyfile { |
a4a1fe64 | 234 | my ($type, $storeid, $secret) = @_; |
e34ce144 AA |
235 | |
236 | my $extension = 'keyring'; | |
237 | $extension = 'secret' if ($type eq 'cephfs'); | |
238 | ||
239 | my $ceph_admin_keyring = '/etc/pve/priv/ceph.client.admin.keyring'; | |
240 | my $ceph_storage_keyring = "/etc/pve/priv/ceph/${storeid}.$extension"; | |
241 | ||
242 | die "ceph authx keyring file for storage '$storeid' already exists!\n" | |
a4a1fe64 | 243 | if -e $ceph_storage_keyring && !defined($secret); |
e34ce144 | 244 | |
a4a1fe64 | 245 | if (-e $ceph_admin_keyring || defined($secret)) { |
e34ce144 | 246 | eval { |
a4a1fe64 AL |
247 | if (defined($secret)) { |
248 | mkdir '/etc/pve/priv/ceph'; | |
e7bc1f03 AL |
249 | chomp $secret; |
250 | PVE::Tools::file_set_contents($ceph_storage_keyring, "${secret}\n", 0400); | |
a4a1fe64 | 251 | } elsif ($type eq 'rbd') { |
e34ce144 AA |
252 | mkdir '/etc/pve/priv/ceph'; |
253 | PVE::Tools::file_copy($ceph_admin_keyring, $ceph_storage_keyring); | |
254 | } elsif ($type eq 'cephfs') { | |
a4a1fe64 | 255 | my $cephfs_secret = $ceph_get_key->($ceph_admin_keyring, 'admin'); |
e34ce144 | 256 | mkdir '/etc/pve/priv/ceph'; |
e7bc1f03 AL |
257 | chomp $cephfs_secret; |
258 | PVE::Tools::file_set_contents($ceph_storage_keyring, "${cephfs_secret}\n", 0400); | |
e34ce144 AA |
259 | } |
260 | }; | |
261 | if (my $err = $@) { | |
262 | unlink $ceph_storage_keyring; | |
263 | die "failed to copy ceph authx $extension for storage '$storeid': $err\n"; | |
264 | } | |
265 | } else { | |
266 | warn "$ceph_admin_keyring not found, authentication is disabled.\n"; | |
267 | } | |
268 | } | |
269 | ||
270 | sub ceph_remove_keyfile { | |
271 | my ($type, $storeid) = @_; | |
272 | ||
273 | my $extension = 'keyring'; | |
274 | $extension = 'secret' if ($type eq 'cephfs'); | |
275 | my $ceph_storage_keyring = "/etc/pve/priv/ceph/${storeid}.$extension"; | |
276 | ||
277 | if (-f $ceph_storage_keyring) { | |
278 | unlink($ceph_storage_keyring) or warn "removing keyring of storage failed: $!\n"; | |
279 | } | |
280 | } | |
281 | ||
e54c3e33 AA |
282 | my $ceph_version_parser = sub { |
283 | my $ceph_version = shift; | |
284 | # FIXME this is the same as pve-manager PVE::Ceph::Tools get_local_version | |
5c04a0b3 | 285 | if ($ceph_version =~ /^ceph.*\sv?(\d+(?:\.\d+)+(?:-pve\d+)?)\s+(?:\(([a-zA-Z0-9]+)\))?/) { |
e54c3e33 AA |
286 | my ($version, $buildcommit) = ($1, $2); |
287 | my $subversions = [ split(/\.|-/, $version) ]; | |
288 | ||
289 | return ($subversions, $version, $buildcommit); | |
290 | } | |
291 | warn "Could not parse Ceph version: '$ceph_version'\n"; | |
292 | }; | |
293 | ||
7435dc90 | 294 | sub local_ceph_version { |
e54c3e33 AA |
295 | my ($cache) = @_; |
296 | ||
297 | my $version_string = $cache; | |
298 | if (!defined($version_string)) { | |
299 | run_command('ceph --version', outfunc => sub { | |
300 | $version_string = shift; | |
301 | }); | |
302 | } | |
303 | return undef if !defined($version_string); | |
304 | # subversion is an array ref. with the version parts from major to minor | |
305 | # version is the filtered version string | |
306 | my ($subversions, $version) = $ceph_version_parser->($version_string); | |
307 | ||
308 | return wantarray ? ($subversions, $version) : $version; | |
309 | } | |
310 | ||
9b7ba1db | 311 | 1; |