]>
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}->%*) { |
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 |
107 | my $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 |
116 | my $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 |
127 | sub 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 |
163 | sub 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 |
170 | my $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 |
190 | sub 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 | 226 | sub 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 | ||
263 | sub 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 |
275 | my $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 | 287 | sub 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 | 304 | 1; |