2018-12-19 13:24:39 +03:00
package PVE::CephConfig ;
2018-07-04 13:43:28 +03:00
use strict ;
use warnings ;
use Net::IP ;
use PVE::Tools qw( run_command ) ;
2018-12-19 13:24:40 +03:00
use PVE::Cluster qw( cfs_register_file ) ;
2018-07-04 13:43:28 +03:00
2018-12-19 13:24:40 +03:00
cfs_register_file ( 'ceph.conf' ,
\ & parse_ceph_config ,
\ & write_ceph_config ) ;
2018-07-04 13:43:31 +03:00
2018-12-19 13:24:40 +03:00
sub parse_ceph_config {
my ( $ filename , $ raw ) = @ _ ;
2018-07-04 13:43:31 +03:00
2018-12-19 13:24:40 +03:00
my $ cfg = { } ;
2019-01-03 11:44:59 +03:00
return $ cfg if ! defined ( $ raw ) ;
2018-07-04 13:43:31 +03:00
2018-12-19 13:24:40 +03:00
my @ lines = split /\n/ , $ raw ;
2018-07-04 13:43:31 +03:00
my $ section ;
foreach my $ line ( @ lines ) {
2019-06-27 11:43:10 +03:00
$ line =~ s/#.*$// ;
2018-07-04 13:43:31 +03:00
$ line =~ s/^\s+// ;
2019-06-27 11:43:10 +03:00
$ line =~ s/^;.*$// ;
2018-07-04 13:43:31 +03: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 10:17:52 +03:00
my ( $ key , $ val ) = ( $ 1 , $ 2 ) ;
2019-06-19 11:23:01 +03:00
# ceph treats ' ', '_' and '-' in keys the same, so lets do too
2019-06-19 10:17:52 +03:00
$ key =~ s/[-\ ]/_/g ;
$ cfg - > { $ section } - > { $ key } = $ val ;
2018-07-04 13:43:31 +03:00
}
}
return $ cfg ;
2018-12-19 13:24:40 +03: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 13:43:31 +03:00
} ;
2018-12-19 13:24:40 +03:00
sub write_ceph_config {
my ( $ filename , $ cfg ) = @ _ ;
my $ out = '' ;
my $ cond_write_sec = sub {
my $ re = shift ;
2020-01-29 21:59:08 +03:00
foreach my $ section ( sort keys %$ cfg ) {
2018-12-19 13:24:40 +03: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 13:43:31 +03: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 11:43:11 +03: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 13:43:31 +03: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 11:43:12 +03:00
my $ monhostlist = { } ;
2019-01-09 17:23:39 +03:00
2021-06-23 09:07:20 +03:00
# get all ip addresses from mon_host
2019-06-27 11:43:12 +03: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 13:43:31 +03:00
2018-07-04 13:43:28 +03:00
sub hostlist {
my ( $ list_text , $ separator ) = @ _ ;
my @ monhostlist = PVE::Tools:: split_list ( $ list_text ) ;
2019-06-27 11:43:11 +03:00
return join ( $ separator , map { $ get_host - > ( $ _ ) } @ monhostlist ) ;
2018-07-04 13:43:28 +03:00
}
2018-07-04 17:56:24 +03: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 13:43:28 +03:00
sub ceph_connect_option {
my ( $ scfg , $ storeid , % options ) = @ _ ;
my $ cmd_option = { } ;
my $ ceph_storeid_conf = "/etc/pve/priv/ceph/${storeid}.conf" ;
my $ pveceph_config = '/etc/pve/ceph.conf' ;
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 } ) ;
$ cmd_option - > { ceph_conf } = $ pveceph_config if $ pveceph_managed ;
2018-07-04 17:56:24 +03:00
$ ceph_check_keyfile - > ( $ keyfile , $ scfg - > { type } ) ;
2018-07-04 13:43:30 +03:00
2018-07-04 13:43:28 +03:00
if ( - e $ ceph_storeid_conf ) {
if ( $ pveceph_managed ) {
warn "ignoring custom ceph config for storage '$storeid', 'monhost' is not set (assuming pveceph managed cluster)!\n" ;
} else {
$ cmd_option - > { ceph_conf } = $ ceph_storeid_conf ;
}
}
$ 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 13:43:31 +03:00
sub ceph_create_keyfile {
my ( $ type , $ storeid ) = @ _ ;
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"
if - e $ ceph_storage_keyring ;
if ( - e $ ceph_admin_keyring ) {
eval {
if ( $ type eq 'rbd' ) {
mkdir '/etc/pve/priv/ceph' ;
PVE::Tools:: file_copy ( $ ceph_admin_keyring , $ ceph_storage_keyring ) ;
} elsif ( $ type eq 'cephfs' ) {
my $ secret = $ ceph_get_key - > ( $ ceph_admin_keyring , 'admin' ) ;
mkdir '/etc/pve/priv/ceph' ;
PVE::Tools:: file_set_contents ( $ ceph_storage_keyring , $ secret , 0400 ) ;
}
} ;
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 18:29:47 +03: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 19:50:06 +03:00
if ( $ ceph_version =~ /^ceph.*\sv?(\d+(?:\.\d+)+(?:-pve\d+)?)\s+(?:\(([a-zA-Z0-9]+)\))?/ ) {
2020-04-24 18:29:47 +03: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 12:37:26 +03:00
sub local_ceph_version {
2020-04-24 18:29:47 +03: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 13:43:28 +03:00
1 ;