2018-12-19 11:24:39 +01:00
package PVE::CephConfig ;
2018-07-04 12:43:28 +02:00
use strict ;
use warnings ;
use Net::IP ;
use PVE::Tools qw( run_command ) ;
2018-12-19 11:24:40 +01:00
use PVE::Cluster qw( cfs_register_file ) ;
2018-07-04 12:43:28 +02:00
2018-12-19 11:24:40 +01:00
cfs_register_file ( 'ceph.conf' ,
\ & parse_ceph_config ,
\ & write_ceph_config ) ;
2018-07-04 12:43:31 +02:00
2018-12-19 11:24:40 +01:00
sub parse_ceph_config {
my ( $ filename , $ raw ) = @ _ ;
2018-07-04 12:43:31 +02:00
2018-12-19 11:24:40 +01:00
my $ cfg = { } ;
2019-01-03 09:44:59 +01:00
return $ cfg if ! defined ( $ raw ) ;
2018-07-04 12:43:31 +02:00
2018-12-19 11:24:40 +01:00
my @ lines = split /\n/ , $ raw ;
2018-07-04 12:43:31 +02:00
my $ section ;
foreach my $ line ( @ lines ) {
2019-06-27 10:43:10 +02:00
$ line =~ s/#.*$// ;
2018-07-04 12:43:31 +02:00
$ line =~ s/^\s+// ;
2019-06-27 10:43:10 +02:00
$ line =~ s/^;.*$// ;
2018-07-04 12:43:31 +02:00
$ line =~ s/\s+$// ;
next if ! $ line ;
$ section = $ 1 if $ line =~ m/^\[(\S+)\]$/ ;
if ( ! $ section ) {
warn "no section - skip: $line\n" ;
next ;
}
if ( $ line =~ m/^(.*?\S)\s*=\s*(\S.*)$/ ) {
2019-06-19 09:17:52 +02:00
my ( $ key , $ val ) = ( $ 1 , $ 2 ) ;
2019-06-19 10:23:01 +02:00
# ceph treats ' ', '_' and '-' in keys the same, so lets do too
2019-06-19 09:17:52 +02:00
$ key =~ s/[-\ ]/_/g ;
$ cfg - > { $ section } - > { $ key } = $ val ;
2018-07-04 12:43:31 +02:00
}
}
return $ cfg ;
2018-12-19 11:24:40 +01:00
}
my $ parse_ceph_file = sub {
my ( $ filename ) = @ _ ;
my $ cfg = { } ;
return $ cfg if ! - f $ filename ;
my $ content = PVE::Tools:: file_get_contents ( $ filename ) ;
return parse_ceph_config ( $ filename , $ content ) ;
2018-07-04 12:43:31 +02:00
} ;
2018-12-19 11:24:40 +01:00
sub write_ceph_config {
my ( $ filename , $ cfg ) = @ _ ;
my $ out = '' ;
my $ cond_write_sec = sub {
my $ re = shift ;
2020-01-29 19:59:08 +01:00
foreach my $ section ( sort keys %$ cfg ) {
2018-12-19 11:24:40 +01:00
next if $ section !~ m/^$re$/ ;
$ out . = "[$section]\n" ;
foreach my $ key ( sort keys % { $ cfg - > { $ section } } ) {
$ out . = "\t $key = $cfg->{$section}->{$key}\n" ;
}
$ out . = "\n" ;
}
} ;
& $ cond_write_sec ( 'global' ) ;
& $ cond_write_sec ( 'client' ) ;
& $ cond_write_sec ( 'mds' ) ;
& $ cond_write_sec ( 'mon' ) ;
& $ cond_write_sec ( 'osd' ) ;
& $ cond_write_sec ( 'mgr' ) ;
& $ cond_write_sec ( 'mds\..*' ) ;
& $ cond_write_sec ( 'mon\..*' ) ;
& $ cond_write_sec ( 'osd\..*' ) ;
& $ cond_write_sec ( 'mgr\..*' ) ;
return $ out ;
}
2018-07-04 12:43:31 +02:00
my $ ceph_get_key = sub {
my ( $ keyfile , $ username ) = @ _ ;
my $ key = $ parse_ceph_file - > ( $ keyfile ) ;
my $ secret = $ key - > { "client.$username" } - > { key } ;
return $ secret ;
} ;
2019-06-27 10:43:11 +02:00
my $ get_host = sub {
my ( $ hostport ) = @ _ ;
my ( $ host , $ port ) = PVE::Tools:: parse_host_and_port ( $ hostport ) ;
if ( ! defined ( $ host ) ) {
return "" ;
}
$ port = defined ( $ port ) ? ":$port" : '' ;
$ host = "[$host]" if Net::IP:: ip_is_ipv6 ( $ host ) ;
return "${host}${port}" ;
} ;
2018-07-04 12:43:31 +02:00
sub get_monaddr_list {
my ( $ configfile ) = shift ;
if ( ! defined ( $ configfile ) ) {
warn "No ceph config specified\n" ;
return ;
}
my $ config = $ parse_ceph_file - > ( $ configfile ) ;
2019-06-27 10:43:12 +02:00
my $ monhostlist = { } ;
2019-01-09 15:23:39 +01:00
2021-06-23 08:07:20 +02:00
# get all ip addresses from mon_host
2019-06-27 10:43:12 +02:00
my $ monhosts = [ split ( /[ ,;]+/ , $ config - > { global } - > { mon_host } // "" ) ] ;
foreach my $ monhost ( @$ monhosts ) {
$ monhost =~ s/^\[?v\d\:// ; # remove beginning of vector
$ monhost =~ s | / \ d + \ ] ? || ; # remove end of vector
my $ host = $ get_host - > ( $ monhost ) ;
if ( $ host ne "" ) {
$ monhostlist - > { $ host } = 1 ;
}
}
# then get all addrs from mon. sections
for my $ section ( keys %$ config ) {
next if $ section !~ m/^mon\./ ;
if ( my $ addr = $ config - > { $ section } - > { mon_addr } ) {
$ monhostlist - > { $ addr } = 1 ;
}
}
return join ( ',' , sort keys %$ monhostlist ) ;
}
2018-07-04 12:43:31 +02:00
2018-07-04 12:43:28 +02:00
sub hostlist {
my ( $ list_text , $ separator ) = @ _ ;
my @ monhostlist = PVE::Tools:: split_list ( $ list_text ) ;
2019-06-27 10:43:11 +02:00
return join ( $ separator , map { $ get_host - > ( $ _ ) } @ monhostlist ) ;
2018-07-04 12:43:28 +02:00
}
2018-07-04 16:56:24 +02:00
my $ ceph_check_keyfile = sub {
my ( $ filename , $ type ) = @ _ ;
return if ! - f $ filename ;
my $ content = PVE::Tools:: file_get_contents ( $ filename ) ;
eval {
die if ! $ content ;
if ( $ type eq 'rbd' ) {
die if $ content !~ /\s*\[\S+\]\s*key\s*=\s*\S+==\s*$/m ;
} elsif ( $ type eq 'cephfs' ) {
die if $ content !~ /\S+==\s*$/ ;
}
} ;
die "Not a proper $type authentication file: $filename\n" if $@ ;
return undef ;
} ;
2018-07-04 12:43:28 +02:00
sub ceph_connect_option {
my ( $ scfg , $ storeid , % options ) = @ _ ;
my $ cmd_option = { } ;
my $ keyfile = "/etc/pve/priv/ceph/${storeid}.keyring" ;
$ keyfile = "/etc/pve/priv/ceph/${storeid}.secret" if ( $ scfg - > { type } eq 'cephfs' ) ;
my $ pveceph_managed = ! defined ( $ scfg - > { monhost } ) ;
2022-04-26 12:47:54 +02:00
$ cmd_option - > { ceph_conf } = '/etc/pve/ceph.conf' if $ pveceph_managed ;
2018-07-04 12:43:28 +02:00
2018-07-04 16:56:24 +02:00
$ ceph_check_keyfile - > ( $ keyfile , $ scfg - > { type } ) ;
2018-07-04 12:43:30 +02:00
2022-04-26 12:47:54 +02:00
if ( - e "/etc/pve/priv/ceph/${storeid}.conf" ) {
# allow custom ceph configuration for external clusters
2018-07-04 12:43:28 +02:00
if ( $ pveceph_managed ) {
warn "ignoring custom ceph config for storage '$storeid', 'monhost' is not set (assuming pveceph managed cluster)!\n" ;
} else {
2022-04-26 12:47:54 +02:00
$ cmd_option - > { ceph_conf } = "/etc/pve/priv/ceph/${storeid}.conf" ;
2018-07-04 12:43:28 +02:00
}
}
$ cmd_option - > { keyring } = $ keyfile if ( - e $ keyfile ) ;
$ cmd_option - > { auth_supported } = ( defined $ cmd_option - > { keyring } ) ? 'cephx' : 'none' ;
$ cmd_option - > { userid } = $ scfg - > { username } ? $ scfg - > { username } : 'admin' ;
$ cmd_option - > { mon_host } = hostlist ( $ scfg - > { monhost } , ',' ) if ( defined ( $ scfg - > { monhost } ) ) ;
if ( % options ) {
foreach my $ k ( keys % options ) {
$ cmd_option - > { $ k } = $ options { $ k } ;
}
}
return $ cmd_option ;
}
2018-07-04 12:43:31 +02:00
sub ceph_create_keyfile {
2021-08-26 12:03:31 +02:00
my ( $ type , $ storeid , $ secret ) = @ _ ;
2018-07-04 12:43:31 +02:00
my $ extension = 'keyring' ;
$ extension = 'secret' if ( $ type eq 'cephfs' ) ;
my $ ceph_admin_keyring = '/etc/pve/priv/ceph.client.admin.keyring' ;
my $ ceph_storage_keyring = "/etc/pve/priv/ceph/${storeid}.$extension" ;
die "ceph authx keyring file for storage '$storeid' already exists!\n"
2021-08-26 12:03:31 +02:00
if - e $ ceph_storage_keyring && ! defined ( $ secret ) ;
2018-07-04 12:43:31 +02:00
2021-08-26 12:03:31 +02:00
if ( - e $ ceph_admin_keyring || defined ( $ secret ) ) {
2018-07-04 12:43:31 +02:00
eval {
2021-08-26 12:03:31 +02:00
if ( defined ( $ secret ) ) {
mkdir '/etc/pve/priv/ceph' ;
2022-01-24 16:11:53 +01:00
chomp $ secret ;
PVE::Tools:: file_set_contents ( $ ceph_storage_keyring , "${secret}\n" , 0400 ) ;
2021-08-26 12:03:31 +02:00
} elsif ( $ type eq 'rbd' ) {
2018-07-04 12:43:31 +02:00
mkdir '/etc/pve/priv/ceph' ;
PVE::Tools:: file_copy ( $ ceph_admin_keyring , $ ceph_storage_keyring ) ;
} elsif ( $ type eq 'cephfs' ) {
2021-08-26 12:03:31 +02:00
my $ cephfs_secret = $ ceph_get_key - > ( $ ceph_admin_keyring , 'admin' ) ;
2018-07-04 12:43:31 +02:00
mkdir '/etc/pve/priv/ceph' ;
2022-01-24 16:11:53 +01:00
chomp $ cephfs_secret ;
PVE::Tools:: file_set_contents ( $ ceph_storage_keyring , "${cephfs_secret}\n" , 0400 ) ;
2018-07-04 12:43:31 +02:00
}
} ;
if ( my $ err = $@ ) {
unlink $ ceph_storage_keyring ;
die "failed to copy ceph authx $extension for storage '$storeid': $err\n" ;
}
} else {
warn "$ceph_admin_keyring not found, authentication is disabled.\n" ;
}
}
sub ceph_remove_keyfile {
my ( $ type , $ storeid ) = @ _ ;
my $ extension = 'keyring' ;
$ extension = 'secret' if ( $ type eq 'cephfs' ) ;
my $ ceph_storage_keyring = "/etc/pve/priv/ceph/${storeid}.$extension" ;
if ( - f $ ceph_storage_keyring ) {
unlink ( $ ceph_storage_keyring ) or warn "removing keyring of storage failed: $!\n" ;
}
}
2020-04-24 17:29:47 +02:00
my $ ceph_version_parser = sub {
my $ ceph_version = shift ;
# FIXME this is the same as pve-manager PVE::Ceph::Tools get_local_version
2020-05-22 18:50:06 +02:00
if ( $ ceph_version =~ /^ceph.*\sv?(\d+(?:\.\d+)+(?:-pve\d+)?)\s+(?:\(([a-zA-Z0-9]+)\))?/ ) {
2020-04-24 17:29:47 +02:00
my ( $ version , $ buildcommit ) = ( $ 1 , $ 2 ) ;
my $ subversions = [ split ( /\.|-/ , $ version ) ] ;
return ( $ subversions , $ version , $ buildcommit ) ;
}
warn "Could not parse Ceph version: '$ceph_version'\n" ;
} ;
2020-04-25 11:37:26 +02:00
sub local_ceph_version {
2020-04-24 17:29:47 +02:00
my ( $ cache ) = @ _ ;
my $ version_string = $ cache ;
if ( ! defined ( $ version_string ) ) {
run_command ( 'ceph --version' , outfunc = > sub {
$ version_string = shift ;
} ) ;
}
return undef if ! defined ( $ version_string ) ;
# subversion is an array ref. with the version parts from major to minor
# version is the filtered version string
my ( $ subversions , $ version ) = $ ceph_version_parser - > ( $ version_string ) ;
return wantarray ? ( $ subversions , $ version ) : $ version ;
}
2018-07-04 12:43:28 +02:00
1 ;