]>
git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Ceph.pm
1 package PVE
:: API2
:: Ceph
;
7 use POSIX qw
( LONG_MAX
);
14 use PVE
:: Tools
qw(extract_param run_command file_get_contents file_read_firstline dir_glob_regex dir_glob_foreach) ;
15 use PVE
:: Exception
qw(raise raise_param_exc) ;
17 use PVE
:: Cluster
qw(cfs_lock_file cfs_read_file cfs_write_file) ;
18 use PVE
:: AccessControl
;
21 use PVE
:: RPCEnvironment
;
22 use PVE
:: JSONSchema
qw(get_standard_option) ;
25 use base
qw(PVE::RESTHandler) ;
27 use Data
:: Dumper
; # fixme: remove
29 my $ccname = 'ceph' ; # ceph cluster name
30 my $ceph_cfgdir = "/etc/ceph" ;
31 my $pve_ceph_cfgpath = "/etc/pve/ $ccname .conf" ;
32 my $ceph_cfgpath = " $ceph_cfgdir/$ccname .conf" ;
33 my $pve_mon_key_path = "/etc/pve/priv/ $ccname .mon.keyring" ;
34 my $pve_ckeyring_path = "/etc/pve/priv/ $ccname .client.admin.keyring" ;
36 my $ceph_bootstrap_osd_keyring = "/var/lib/ceph/bootstrap-osd/ $ccname .keyring" ;
37 my $ceph_bootstrap_mds_keyring = "/var/lib/ceph/bootstrap-mds/ $ccname .keyring" ;
39 my $ceph_bin = "/usr/bin/ceph" ;
41 my $pve_osd_default_journal_size = 1024 * 5 ;
43 sub purge_all_ceph_files
{
44 # fixme: this is very dangerous - should we really support this function?
48 unlink $pve_ceph_cfgpath ;
49 unlink $pve_ckeyring_path ;
50 unlink $pve_mon_key_path ;
52 unlink $ceph_bootstrap_osd_keyring ;
53 unlink $ceph_bootstrap_mds_keyring ;
55 system ( "rm -rf /var/lib/ceph/mon/ceph-*" );
61 my $check_ceph_installed = sub {
65 die "ceph binaries not installed \n " if ! $noerr ;
72 my $check_ceph_inited = sub {
75 return undef if !& $check_ceph_installed ( $noerr );
77 if (! - f
$pve_ceph_cfgpath ) {
78 die "pveceph configuration not initialized \n " if ! $noerr ;
85 my $check_ceph_enabled = sub {
88 return undef if !& $check_ceph_inited ( $noerr );
90 if (! - f
$ceph_cfgpath ) {
91 die "pveceph configuration not enabled \n " if ! $noerr ;
98 my $parse_ceph_config = sub {
103 return $cfg if ! - f
$filename ;
105 my $fh = IO
:: File-
> new ( $filename, "r" ) ||
106 die "unable to open ' $filename ' - $!\n " ;
110 while ( defined ( my $line = < $fh >)) {
111 $line =~ s/[;#].*$// ;
116 $section = $1 if $line =~ m/^\[(\S+)\]$/ ;
118 warn "no section - skip: $line\n " ;
122 if ( $line =~ m/^(.*\S)\s*=\s*(\S.*)$/ ) {
123 $cfg ->{ $section }->{ $1 } = $2 ;
131 my $run_ceph_cmd = sub {
132 my ( $cmd, %params ) = @_ ;
136 run_command
([ 'ceph' , '-c' , $pve_ceph_cfgpath,
137 '--connect-timeout' , $timeout,
141 my $run_ceph_cmd_text = sub {
142 my ( $cmd, %opts ) = @_ ;
146 my $quiet = delete $opts { quiet
};
155 print " $line\n " if ! $quiet ;
158 & $run_ceph_cmd ( $cmd, outfunc
=> $parser, errfunc
=> $errfunc );
163 my $run_ceph_cmd_json = sub {
164 my ( $cmd, %opts ) = @_ ;
166 my $json = & $run_ceph_cmd_text ([ @$cmd, '--format' , 'json' ], %opts );
168 return decode_json
( $json );
171 sub ceph_mon_status
{
174 return & $run_ceph_cmd_json ([ 'mon_status' ], quiet
=> $quiet );
178 my $ceph_osd_status = sub {
181 return & $run_ceph_cmd_json ([ 'osd' , 'dump' ], quiet
=> $quiet );
184 my $write_ceph_config = sub {
189 my $cond_write_sec = sub {
192 foreach my $section ( keys %$cfg ) {
193 next if $section !~ m/^$re$/ ;
194 $out .= "[ $section ] \n " ;
195 foreach my $key ( sort keys %{ $cfg ->{ $section }}) {
196 $out .= " \t $key = $cfg ->{ $section }->{ $key } \n " ;
202 & $cond_write_sec ( 'global' );
203 & $cond_write_sec ( 'mon' );
204 & $cond_write_sec ( 'osd' );
205 & $cond_write_sec ( 'mon\..*' );
206 & $cond_write_sec ( 'osd\..*' );
208 PVE
:: Tools
:: file_set_contents
( $pve_ceph_cfgpath, $out );
211 my $setup_pve_symlinks = sub {
212 # fail if we find a real file instead of a link
213 if (- f
$ceph_cfgpath ) {
214 my $lnk = readlink ( $ceph_cfgpath );
215 die "file ' $ceph_cfgpath ' already exists \n "
216 if ! $lnk || $lnk ne $pve_ceph_cfgpath ;
218 symlink ( $pve_ceph_cfgpath, $ceph_cfgpath ) ||
219 die "unable to create symlink ' $ceph_cfgpath ' - $!\n " ;
223 my $ceph_service_cmd = sub {
224 run_command
([ 'service' , 'ceph' , '-c' , $pve_ceph_cfgpath, @_ ]);
230 my $fd = IO
:: File-
> new ( "/proc/mounts" , "r" ) ||
231 die "unable to open /proc/mounts - $!\n " ;
235 while ( defined ( my $line = < $fd >)) {
236 my ( $dev, $path, $fstype ) = split ( /\s+/ , $line );
237 next if !( $dev && $path && $fstype );
238 next if $dev !~ m
|^ /dev/ |;
239 my $real_dev = abs_path
( $dev );
240 $mounted ->{ $real_dev } = $path ;
244 my $dev_is_mounted = sub {
246 return $mounted ->{ $dev };
249 my $dir_is_epmty = sub {
252 my $dh = IO
:: Dir-
> new ( $dir );
255 while ( defined ( my $tmp = $dh -> read )) {
256 next if $tmp eq '.' || $tmp eq '..' ;
264 my $journal_uuid = '45b0969e-9b03-4f30-b4c6-b4b80ceff106' ;
266 my $journalhash = {};
267 dir_glob_foreach
( '/dev/disk/by-parttypeuuid' , " $journal_uuid\ ..+" , sub {
269 my $real_dev = abs_path
( "/dev/disk/by-parttypeuuid/ $entry " );
270 $journalhash ->{ $real_dev } = 1 ;
273 dir_glob_foreach
( '/sys/block' , '.*' , sub {
276 return if $dev eq '.' ;
277 return if $dev eq '..' ;
279 return if $dev =~ m
|^ ram\d
+$|; # skip ram devices
280 return if $dev =~ m
|^ loop \d
+$|; # skip loop devices
281 return if $dev =~ m
|^ md\d
+$|; # skip md devices
282 return if $dev =~ m
|^ dm-
.*$|; # skip dm related things
283 return if $dev =~ m
|^ fd\d
+$|; # skip Floppy
284 return if $dev =~ m
|^ sr\d
+$|; # skip CDs
286 my $devdir = "/sys/block/ $dev/device " ;
287 return if ! - d
$devdir ;
289 my $size = file_read_firstline
( "/sys/block/ $dev/size " );
294 my $info = `udevadm info --path /sys/block/ $dev --query all` ;
297 return if $info !~ m/^E: DEVTYPE=disk$/m ;
298 return if $info =~ m/^E: ID_CDROM/m ;
300 my $serial = 'unknown' ;
301 if ( $info =~ m/^E: ID_SERIAL_SHORT=(\S+)$/m ) {
306 if ( $info =~ m/^E: ID_PART_TABLE_TYPE=gpt$/m ) {
310 # detect SSD (fixme - currently only works for ATA disks)
311 my $rpm = 7200 ; # default guess
312 if ( $info =~ m/^E: ID_ATA_ROTATION_RATE_RPM=(\d+)$/m ) {
316 my $vendor = file_read_firstline
( " $devdir/vendor " ) || 'unknown' ;
317 my $model = file_read_firstline
( " $devdir/model " ) || 'unknown' ;
321 $used = 'LVM' if !& $dir_is_epmty ( "/sys/block/ $dev/holders " );
323 $used = 'mounted' if & $dev_is_mounted ( "/dev/ $dev " );
325 $disklist ->{ $dev } = {
336 my $journal_count = 0 ;
338 my $found_partitions ;
340 my $found_mountpoints ;
341 dir_glob_foreach
( "/sys/block/ $dev " , " $dev .+" , sub {
344 $found_partitions = 1 ;
346 if ( my $mp = & $dev_is_mounted ( "/dev/ $part " )) {
347 $found_mountpoints = 1 ;
348 if ( $mp =~ m
|^ /var/li b
/ceph/osd / ceph-
( \d
+)$|) {
352 if (!& $dir_is_epmty ( "/sys/block/ $dev/$part/holders " )) {
355 $journal_count++ if $journalhash ->{ "/dev/ $part " };
358 $used = 'mounted' if $found_mountpoints && ! $used ;
359 $used = 'LVM' if $found_lvm && ! $used ;
360 $used = 'partitions' if $found_partitions && ! $used ;
362 $disklist ->{ $dev }->{ used
} = $used if $used ;
363 $disklist ->{ $dev }->{ osdid
} = $osdid ;
364 $disklist ->{ $dev }->{ journals
} = $journal_count ;
370 my $lookup_diskinfo = sub {
371 my ( $disklist, $disk ) = @_ ;
373 my $real_dev = abs_path
( $disk );
374 $real_dev =~ s
| /dev/ ||;
375 my $diskinfo = $disklist ->{ $real_dev };
377 die "disk ' $disk ' not found in disk list \n " if ! $diskinfo ;
379 return wantarray ?
( $diskinfo, $real_dev ) : $diskinfo ;
383 my $count_journal_disks = sub {
384 my ( $disklist, $disk ) = @_ ;
388 my ( $diskinfo, $real_dev ) = & $lookup_diskinfo ( $disklist, $disk );
389 die "journal disk ' $disk ' does not contain a GUID partition table \n "
390 if ! $diskinfo ->{ gpt
};
392 $count = $diskinfo ->{ journals
} if $diskinfo ->{ journals
};
397 __PACKAGE__-
> register_method ({
401 description
=> "Directory index." ,
402 permissions
=> { user
=> 'all' },
404 additionalProperties
=> 0 ,
406 node
=> get_standard_option
( 'pve-node' ),
415 links
=> [ { rel
=> 'child' , href
=> "{name}" } ],
427 { name
=> 'status' },
429 { name
=> 'config' },
437 __PACKAGE__-
> register_method ({
441 description
=> "List local disks." ,
445 additionalProperties
=> 0 ,
447 node
=> get_standard_option
( 'pve-node' ),
449 description
=> "Only list specific types of disks." ,
451 enum
=> [ 'unused' , 'journal_disks' ],
461 dev
=> { type
=> 'string' },
462 used
=> { type
=> 'string' , optional
=> 1 },
463 gpt
=> { type
=> 'boolean' },
464 size
=> { type
=> 'integer' },
465 osdid
=> { type
=> 'integer' },
466 vendor
=> { type
=> 'string' , optional
=> 1 },
467 model
=> { type
=> 'string' , optional
=> 1 },
468 serial
=> { type
=> 'string' , optional
=> 1 },
471 # links => [ { rel => 'child', href => "{}" } ],
476 & $check_ceph_inited ();
478 my $disks = list_disks
();
481 foreach my $dev ( keys %$disks ) {
482 my $d = $disks ->{ $dev };
483 if ( $param ->{ type
}) {
484 if ( $param ->{ type
} eq 'journal_disks' ) {
485 next if $d ->{ osdid
} >= 0 ;
487 } elsif ( $param ->{ type
} eq 'unused' ) {
490 die "internal error" ; # should not happen
494 $d ->{ dev
} = "/dev/ $dev " ;
501 __PACKAGE__-
> register_method ({
505 description
=> "Get Ceph configuration." ,
507 additionalProperties
=> 0 ,
509 node
=> get_standard_option
( 'pve-node' ),
512 returns
=> { type
=> 'string' },
516 & $check_ceph_inited ();
518 return PVE
:: Tools
:: file_get_contents
( $pve_ceph_cfgpath );
522 __PACKAGE__-
> register_method ({
526 description
=> "Get Ceph monitor list." ,
530 additionalProperties
=> 0 ,
532 node
=> get_standard_option
( 'pve-node' ),
540 name
=> { type
=> 'string' },
541 addr
=> { type
=> 'string' },
544 links
=> [ { rel
=> 'child' , href
=> "{name}" } ],
549 & $check_ceph_inited ();
553 my $cfg = & $parse_ceph_config ( $pve_ceph_cfgpath );
556 foreach my $section ( keys %$cfg ) {
557 my $d = $cfg ->{ $section };
558 if ( $section =~ m/^mon\.(\S+)$/ ) {
560 if ( $d ->{ 'mon addr' } && $d ->{ 'host' }) {
561 $monhash ->{ $monid } = {
562 addr
=> $d ->{ 'mon addr' },
563 host
=> $d ->{ 'host' },
571 my $monstat = ceph_mon_status
();
572 my $mons = $monstat ->{ monmap
}->{ mons
};
573 foreach my $d ( @$mons ) {
574 next if ! defined ( $d ->{ name
});
575 $monhash ->{ $d ->{ name
}}->{ rank
} = $d ->{ rank
};
576 $monhash ->{ $d ->{ name
}}->{ addr
} = $d ->{ addr
};
577 if ( grep { $_ eq $d ->{ rank
} } @{ $monstat ->{ quorum
}}) {
578 $monhash ->{ $d ->{ name
}}->{ quorum
} = 1 ;
584 return PVE
:: RESTHandler
:: hash_to_array
( $monhash, 'name' );
587 __PACKAGE__-
> register_method ({
591 description
=> "Create initial ceph default configuration and setup symlinks." ,
595 additionalProperties
=> 0 ,
597 node
=> get_standard_option
( 'pve-node' ),
599 description
=> "Use specific network for all ceph related traffic" ,
600 type
=> 'string' , format
=> 'CIDR' ,
605 description
=> 'Number of replicas per object' ,
613 description
=> "Placement group bits, used to specify the default number of placement groups (Note: 'osd pool default pg num' does not work for deafult pools)" ,
622 returns
=> { type
=> 'null' },
626 & $check_ceph_installed ();
628 # simply load old config if it already exists
629 my $cfg = & $parse_ceph_config ( $pve_ceph_cfgpath );
631 if (! $cfg ->{ global
}) {
636 UUID
:: generate
( $uuid );
637 UUID
:: unparse
( $uuid, $fsid );
641 'auth supported' => 'cephx' ,
642 'auth cluster required' => 'cephx' ,
643 'auth service required' => 'cephx' ,
644 'auth client required' => 'cephx' ,
645 'filestore xattr use omap' => 'true' ,
646 'osd journal size' => $pve_osd_default_journal_size,
647 'osd pool default min size' => 1 ,
650 # this does not work for default pools
651 #'osd pool default pg num' => $pg_num,
652 #'osd pool default pgp num' => $pg_num,
655 $cfg ->{ global
}->{ keyring
} = '/etc/pve/priv/ $cluster . $name .keyring' ;
656 $cfg ->{ osd
}->{ keyring
} = '/var/lib/ceph/osd/ceph- $id/keyring ' ;
658 $cfg ->{ global
}->{ 'osd pool default size' } = $param ->{ size
} if $param ->{ size
};
660 if ( $param ->{ pg_bits
}) {
661 $cfg ->{ global
}->{ 'osd pg bits' } = $param ->{ pg_bits
};
662 $cfg ->{ global
}->{ 'osd pgp bits' } = $param ->{ pg_bits
};
665 if ( $param ->{ network
}) {
666 $cfg ->{ global
}->{ 'public network' } = $param ->{ network
};
667 $cfg ->{ global
}->{ 'cluster network' } = $param ->{ network
};
670 & $write_ceph_config ( $cfg );
672 & $setup_pve_symlinks ();
677 my $find_node_ip = sub {
680 my $config = PVE
:: INotify
:: read_file
( 'interfaces' );
682 my $net = Net
:: IP-
> new ( $cidr ) || die Net
:: IP
:: Error
() . " \n " ;
684 foreach my $iface ( keys %$config ) {
685 my $d = $config ->{ $iface };
686 next if ! $d ->{ address
};
687 my $a = Net
:: IP-
> new ( $d ->{ address
});
689 return $d ->{ address
} if $net -> overlaps ( $a );
692 die "unable to find local address within network ' $cidr ' \n " ;
695 __PACKAGE__-
> register_method ({
699 description
=> "Create Ceph Monitor" ,
703 additionalProperties
=> 0 ,
705 node
=> get_standard_option
( 'pve-node' ),
708 returns
=> { type
=> 'string' },
712 & $check_ceph_inited ();
714 & $setup_pve_symlinks ();
716 my $rpcenv = PVE
:: RPCEnvironment
:: get
();
718 my $authuser = $rpcenv -> get_user ();
720 my $cfg = & $parse_ceph_config ( $pve_ceph_cfgpath );
724 my $monaddrhash = {};
726 foreach my $section ( keys %$cfg ) {
727 next if $section eq 'global' ;
728 my $d = $cfg ->{ $section };
729 if ( $section =~ m/^mon\./ ) {
731 if ( $d ->{ 'mon addr' }) {
732 $monaddrhash ->{ $d ->{ 'mon addr' }} = $section ;
738 for ( my $i = 0 ; $i < 7 ; $i++ ) {
739 if (! $cfg ->{ "mon. $i " }) {
744 die "unable to find usable monitor id \n " if ! defined ( $monid );
746 my $monsection = "mon. $monid " ;
748 if ( my $pubnet = $cfg ->{ global
}->{ 'public network' }) {
749 $ip = & $find_node_ip ( $pubnet );
751 $ip = PVE
:: Cluster
:: remote_node_ip
( $param ->{ node
});
754 my $monaddr = " $ip :6789" ;
755 my $monname = $param ->{ node
};
757 die "monitor ' $monsection ' already exists \n " if $cfg ->{ $monsection };
758 die "monitor address ' $monaddr ' already in use by ' $monaddrhash ->{ $monaddr }' \n "
759 if $monaddrhash ->{ $monaddr };
764 if (! - f
$pve_ckeyring_path ) {
765 run_command
( "ceph-authtool $pve_ckeyring_path --create-keyring " .
766 "--gen-key -n client.admin" );
769 if (! - f
$pve_mon_key_path ) {
770 run_command
( "cp $pve_ckeyring_path $pve_mon_key_path .tmp" );
771 run_command
( "ceph-authtool $pve_mon_key_path .tmp -n client.admin --set-uid=0 " .
772 "--cap mds 'allow' " .
773 "--cap osd 'allow *' " .
774 "--cap mon 'allow *'" );
775 run_command
( "ceph-authtool $pve_mon_key_path .tmp --gen-key -n mon. --cap mon 'allow *'" );
776 run_command
( "mv $pve_mon_key_path .tmp $pve_mon_key_path " );
779 my $mondir = "/var/lib/ceph/mon/ $ccname - $monid " ;
780 - d
$mondir && die "monitor filesystem ' $mondir ' already exist \n " ;
782 my $monmap = "/tmp/monmap" ;
788 my $monstat = ceph_mon_status
(); # online test
789 & $run_ceph_cmd ([ 'mon' , 'getmap' , '-o' , $monmap ]);
791 run_command
( "monmaptool --create --clobber --add $monid $monaddr --print $monmap " );
794 run_command
( "ceph-mon --mkfs -i $monid --monmap $monmap --keyring $pve_mon_key_path " );
799 File
:: Path
:: remove_tree
( $mondir );
803 $cfg ->{ $monsection } = {
805 'mon addr' => $monaddr,
808 & $write_ceph_config ( $cfg );
810 & $ceph_service_cmd ( 'start' , $monsection );
813 return $rpcenv -> fork_worker ( 'cephcreatemon' , $monsection, $authuser, $worker );
816 __PACKAGE__-
> register_method ({
817 name
=> 'destroymon' ,
818 path
=> 'mon/{monid}' ,
820 description
=> "Destroy Ceph monitor." ,
824 additionalProperties
=> 0 ,
826 node
=> get_standard_option
( 'pve-node' ),
828 description
=> 'Monitor ID' ,
833 returns
=> { type
=> 'string' },
837 my $rpcenv = PVE
:: RPCEnvironment
:: get
();
839 my $authuser = $rpcenv -> get_user ();
841 & $check_ceph_inited ();
843 my $cfg = & $parse_ceph_config ( $pve_ceph_cfgpath );
845 my $monid = $param ->{ monid
};
846 my $monsection = "mon. $monid " ;
848 my $monstat = ceph_mon_status
();
849 my $monlist = $monstat ->{ monmap
}->{ mons
};
851 die "no such monitor id ' $monid ' \n "
852 if ! defined ( $cfg ->{ $monsection });
855 my $mondir = "/var/lib/ceph/mon/ $ccname - $monid " ;
856 - d
$mondir || die "monitor filesystem ' $mondir ' does not exist on this node \n " ;
858 die "can't remove last monitor \n " if scalar ( @$monlist ) <= 1 ;
863 & $run_ceph_cmd ([ 'mon' , 'remove' , $monid ]);
865 eval { & $ceph_service_cmd ( 'stop' , $monsection ); };
868 delete $cfg ->{ $monsection };
869 & $write_ceph_config ( $cfg );
870 File
:: Path
:: remove_tree
( $mondir );
873 return $rpcenv -> fork_worker ( 'cephdestroymon' , $monsection, $authuser, $worker );
876 __PACKAGE__-
> register_method ({
880 description
=> "Stop ceph services." ,
884 additionalProperties
=> 0 ,
886 node
=> get_standard_option
( 'pve-node' ),
888 description
=> 'Ceph service name.' ,
891 pattern
=> '(mon|mds|osd)\.[A-Za-z0-9]{1,32}' ,
895 returns
=> { type
=> 'string' },
899 my $rpcenv = PVE
:: RPCEnvironment
:: get
();
901 my $authuser = $rpcenv -> get_user ();
903 & $check_ceph_inited ();
905 my $cfg = & $parse_ceph_config ( $pve_ceph_cfgpath );
906 scalar ( keys %$cfg ) || die "no configuration \n " ;
912 if ( $param ->{ service
}) {
913 push @$cmd, $param ->{ service
};
916 & $ceph_service_cmd ( @$cmd );
919 return $rpcenv -> fork_worker ( 'srvstop' , $param ->{ service
} || 'ceph' ,
923 __PACKAGE__-
> register_method ({
927 description
=> "Start ceph services." ,
931 additionalProperties
=> 0 ,
933 node
=> get_standard_option
( 'pve-node' ),
935 description
=> 'Ceph service name.' ,
938 pattern
=> '(mon|mds|osd)\.[A-Za-z0-9]{1,32}' ,
942 returns
=> { type
=> 'string' },
946 my $rpcenv = PVE
:: RPCEnvironment
:: get
();
948 my $authuser = $rpcenv -> get_user ();
950 & $check_ceph_inited ();
952 my $cfg = & $parse_ceph_config ( $pve_ceph_cfgpath );
953 scalar ( keys %$cfg ) || die "no configuration \n " ;
959 if ( $param ->{ service
}) {
960 push @$cmd, $param ->{ service
};
963 & $ceph_service_cmd ( @$cmd );
966 return $rpcenv -> fork_worker ( 'srvstart' , $param ->{ service
} || 'ceph' ,
970 __PACKAGE__-
> register_method ({
974 description
=> "Get ceph status." ,
978 additionalProperties
=> 0 ,
980 node
=> get_standard_option
( 'pve-node' ),
983 returns
=> { type
=> 'object' },
987 & $check_ceph_enabled ();
989 return & $run_ceph_cmd_json ([ 'status' ], quiet
=> 1 );
992 __PACKAGE__-
> register_method ({
996 description
=> "List all pools." ,
1000 additionalProperties
=> 0 ,
1002 node
=> get_standard_option
( 'pve-node' ),
1010 pool
=> { type
=> 'integer' },
1011 pool_name
=> { type
=> 'string' },
1012 size
=> { type
=> 'integer' },
1015 links
=> [ { rel
=> 'child' , href
=> "{pool_name}" } ],
1020 & $check_ceph_inited ();
1022 my $res = & $run_ceph_cmd_json ([ 'osd' , 'dump' ], quiet
=> 1 );
1025 foreach my $e (@{ $res ->{ pools
}}) {
1027 foreach my $attr ( qw(pool pool_name size min_size pg_num crush_ruleset) ) {
1028 $d ->{ $attr } = $e ->{ $attr } if defined ( $e ->{ $attr });
1036 __PACKAGE__-
> register_method ({
1037 name
=> 'createpool' ,
1040 description
=> "Create POOL" ,
1044 additionalProperties
=> 0 ,
1046 node
=> get_standard_option
( 'pve-node' ),
1048 description
=> "The name of the pool. It must be unique." ,
1052 description
=> 'Number of replicas per object' ,
1060 description
=> 'Minimum number of replicas per object' ,
1068 description
=> "Number of placement groups." ,
1076 description
=> "The ruleset to use for mapping object placement in the cluster." ,
1085 returns
=> { type
=> 'null' },
1089 & $check_ceph_inited ();
1091 die "not fully configured - missing ' $pve_ckeyring_path ' \n "
1092 if ! - f
$pve_ckeyring_path ;
1094 my $pg_num = $param ->{ pg_num
} || 64 ;
1095 my $size = $param ->{ size
} || 2 ;
1096 my $min_size = $param ->{ min_size
} || 1 ;
1098 & $run_ceph_cmd ([ 'osd' , 'pool' , 'create' , $param ->{ name
}, $pg_num ]);
1100 & $run_ceph_cmd ([ 'osd' , 'pool' , 'set' , $param ->{ name
}, 'min_size' , $min_size ]);
1102 & $run_ceph_cmd ([ 'osd' , 'pool' , 'set' , $param ->{ name
}, 'size' , $size ]);
1104 if ( defined ( $param ->{ crush_ruleset
})) {
1105 & $run_ceph_cmd ([ 'osd' , 'pool' , 'set' , $param ->{ name
}, 'crush_ruleset' , $param ->{ crush_ruleset
}]);
1111 __PACKAGE__-
> register_method ({
1112 name
=> 'destroypool' ,
1113 path
=> 'pools/{name}' ,
1115 description
=> "Destroy pool" ,
1119 additionalProperties
=> 0 ,
1121 node
=> get_standard_option
( 'pve-node' ),
1123 description
=> "The name of the pool. It must be unique." ,
1128 returns
=> { type
=> 'null' },
1132 & $check_ceph_inited ();
1134 & $run_ceph_cmd ([ 'osd' , 'pool' , 'delete' , $param ->{ name
}, $param ->{ name
}, '--yes-i-really-really-mean-it' ]);
1139 __PACKAGE__-
> register_method ({
1143 description
=> "Get Ceph osd list/tree." ,
1147 additionalProperties
=> 0 ,
1149 node
=> get_standard_option
( 'pve-node' ),
1158 & $check_ceph_inited ();
1160 my $res = & $run_ceph_cmd_json ([ 'osd' , 'tree' ], quiet
=> 1 );
1162 die "no tree nodes found \n " if !( $res && $res ->{ nodes
});
1166 foreach my $e (@{ $res ->{ nodes
}}) {
1167 $nodes ->{ $e ->{ id
}} = $e ;
1175 foreach my $opt ( qw(status crush_weight reweight) ) {
1176 $new ->{ $opt } = $e ->{ $opt } if defined ( $e ->{ $opt });
1179 $newnodes ->{ $e ->{ id
}} = $new ;
1182 foreach my $e (@{ $res ->{ nodes
}}) {
1183 my $new = $newnodes ->{ $e ->{ id
}};
1184 if ( $e ->{ children
} && scalar (@{ $e ->{ children
}})) {
1185 $new ->{ children
} = [];
1187 foreach my $cid (@{ $e ->{ children
}}) {
1188 $nodes ->{ $cid }->{ parent
} = $e ->{ id
};
1189 if ( $nodes ->{ $cid }->{ type
} eq 'osd' &&
1190 $e ->{ type
} eq 'host' ) {
1191 $newnodes ->{ $cid }->{ host
} = $e ->{ name
};
1193 push @{ $new ->{ children
}}, $newnodes ->{ $cid };
1196 $new ->{ leaf
} = ( $e ->{ id
} >= 0 ) ?
1 : 0 ;
1201 foreach my $e (@{ $res ->{ nodes
}}) {
1202 if (! $nodes ->{ $e ->{ id
}}->{ parent
}) {
1203 $rootnode = $newnodes ->{ $e ->{ id
}};
1208 die "no root node \n " if ! $rootnode ;
1210 my $data = { root
=> $rootnode };
1215 __PACKAGE__-
> register_method ({
1216 name
=> 'createosd' ,
1219 description
=> "Create OSD" ,
1223 additionalProperties
=> 0 ,
1225 node
=> get_standard_option
( 'pve-node' ),
1227 description
=> "Block device name." ,
1231 description
=> "Block device name for journal." ,
1236 description
=> "File system type." ,
1238 enum
=> [ 'xfs' , 'ext4' , 'btrfs' ],
1244 returns
=> { type
=> 'string' },
1248 my $rpcenv = PVE
:: RPCEnvironment
:: get
();
1250 my $authuser = $rpcenv -> get_user ();
1252 & $check_ceph_inited ();
1254 & $setup_pve_symlinks ();
1258 if ( $param ->{ journal_dev
} && ( $param ->{ journal_dev
} ne $param ->{ dev
})) {
1259 - b
$param ->{ journal_dev
} || die "no such block device ' $param ->{journal_dev}' \n " ;
1260 $journal_dev = $param ->{ journal_dev
};
1263 - b
$param ->{ dev
} || die "no such block device ' $param ->{dev}' \n " ;
1265 my $disklist = list_disks
();
1267 my $devname = $param ->{ dev
};
1268 $devname =~ s
| /dev/ ||;
1270 my $diskinfo = $disklist ->{ $devname };
1271 die "unable to get device info for ' $devname ' \n "
1274 die "device ' $param ->{dev}' is in use \n "
1275 if $diskinfo ->{ used
};
1277 my $monstat = ceph_mon_status
( 1 );
1278 die "unable to get fsid \n " if ! $monstat ->{ monmap
} || ! $monstat ->{ monmap
}->{ fsid
};
1279 my $fsid = $monstat ->{ monmap
}->{ fsid
};
1281 if (! - f
$ceph_bootstrap_osd_keyring ) {
1282 & $run_ceph_cmd ([ 'auth' , 'get' , 'client.bootstrap-osd' , '-o' , $ceph_bootstrap_osd_keyring ]);
1288 my $fstype = $param ->{ fstype
} || 'xfs' ;
1290 print "create OSD on $param ->{dev} ( $fstype ) \n " ;
1292 my $cmd = [ 'ceph-disk' , 'prepare' , '--zap-disk' , '--fs-type' , $fstype,
1293 '--cluster' , $ccname, '--cluster-uuid' , $fsid ];
1296 print "using device ' $journal_dev ' for journal \n " ;
1297 push @$cmd, '--journal-dev' , $param ->{ dev
}, $journal_dev ;
1299 push @$cmd, $param ->{ dev
};
1305 return $rpcenv -> fork_worker ( 'cephcreateosd' , $devname, $authuser, $worker );
1308 __PACKAGE__-
> register_method ({
1309 name
=> 'destroyosd' ,
1310 path
=> 'osd/{osdid}' ,
1312 description
=> "Destroy OSD" ,
1316 additionalProperties
=> 0 ,
1318 node
=> get_standard_option
( 'pve-node' ),
1320 description
=> 'OSD ID' ,
1324 description
=> "If set, we remove partition table entries." ,
1331 returns
=> { type
=> 'string' },
1335 my $rpcenv = PVE
:: RPCEnvironment
:: get
();
1337 my $authuser = $rpcenv -> get_user ();
1339 & $check_ceph_inited ();
1341 my $osdid = $param ->{ osdid
};
1343 # fixme: not 100% sure what we should do here
1345 my $stat = & $ceph_osd_status ();
1347 my $osdlist = $stat ->{ osds
} || [];
1350 foreach my $d ( @$osdlist ) {
1351 if ( $d ->{ osd
} == $osdid ) {
1356 die "no such OSD ' $osdid ' \n " if ! $osdstat ;
1358 die "osd is in use (in == 1) \n " if $osdstat ->{ in };
1359 #&$run_ceph_cmd(['osd', 'out', $osdid]);
1361 die "osd is still runnung (up == 1) \n " if $osdstat ->{ up
};
1363 my $osdsection = "osd. $osdid " ;
1368 print "destroy OSD $osdsection\n " ;
1370 eval { & $ceph_service_cmd ( 'stop' , $osdsection ); };
1373 print "Remove $osdsection from the CRUSH map \n " ;
1374 & $run_ceph_cmd ([ 'osd' , 'crush' , 'remove' , $osdsection ]);
1376 print "Remove the $osdsection authentication key. \n " ;
1377 & $run_ceph_cmd ([ 'auth' , 'del' , $osdsection ]);
1379 print "Remove OSD $osdsection\n " ;
1380 & $run_ceph_cmd ([ 'osd' , 'rm' , $osdid ]);
1382 # try to unmount from standard mount point
1383 my $mountpoint = "/var/lib/ceph/osd/ceph- $osdid " ;
1385 my $remove_partition = sub {
1386 my ( $disklist, $part ) = @_ ;
1388 return if ! $part || (! - b
$part );
1390 foreach my $real_dev ( keys %$disklist ) {
1391 my $diskinfo = $disklist ->{ $real_dev };
1392 next if ! $diskinfo ->{ gpt
};
1393 if ( $part =~ m
|^ /dev/ ${ real_dev
}( \d
+)$|) {
1395 print "remove partition $part (disk '/dev/${real_dev}', partnum $partnum ) \n " ;
1396 eval { run_command
([ '/sbin/sgdisk' , '-d' , $partnum, "/dev/${real_dev}" ]); };
1406 if ( $param ->{ cleanup
}) {
1407 my $jpath = " $mountpoint/journal " ;
1408 $journal_part = abs_path
( $jpath );
1410 if ( my $fd = IO
:: File-
> new ( "/proc/mounts" , "r" )) {
1411 while ( defined ( my $line = < $fd >)) {
1412 my ( $dev, $path, $fstype ) = split ( /\s+/ , $line );
1413 next if !( $dev && $path && $fstype );
1414 next if $dev !~ m
|^ /dev/ |;
1415 if ( $path eq $mountpoint ) {
1416 $data_part = abs_path
( $dev );
1424 print "Unmount OSD $osdsection from $mountpoint\n " ;
1425 eval { run_command
([ 'umount' , $mountpoint ]); };
1428 } elsif ( $param ->{ cleanup
}) {
1429 my $disklist = list_disks
();
1430 & $remove_partition ( $disklist, $journal_part );
1431 & $remove_partition ( $disklist, $data_part );
1435 return $rpcenv -> fork_worker ( 'cephdestroyosd' , $osdsection, $authuser, $worker );
1438 __PACKAGE__-
> register_method ({
1442 description
=> "Get OSD crush map" ,
1446 additionalProperties
=> 0 ,
1448 node
=> get_standard_option
( 'pve-node' ),
1451 returns
=> { type
=> 'string' },
1455 & $check_ceph_inited ();
1457 # this produces JSON (difficult to read for the user)
1458 # my $txt = &$run_ceph_cmd_text(['osd', 'crush', 'dump'], quiet => 1);
1462 my $mapfile = "/var/tmp/ceph-crush.map. $$ " ;
1463 my $mapdata = "/var/tmp/ceph-crush.txt. $$ " ;
1466 & $run_ceph_cmd ([ 'osd' , 'getcrushmap' , '-o' , $mapfile ]);
1467 run_command
([ 'crushtool' , '-d' , $mapfile, '-o' , $mapdata ]);
1468 $txt = PVE
:: Tools
:: file_get_contents
( $mapdata );
1480 __PACKAGE__-
> register_method ({
1484 description
=> "Read ceph log" ,
1487 check
=> [ 'perm' , '/nodes/{node}' , [ 'Sys.Syslog' ]],
1491 additionalProperties
=> 0 ,
1493 node
=> get_standard_option
( 'pve-node' ),
1512 description
=> "Line number" ,
1516 description
=> "Line text" ,
1525 my $rpcenv = PVE
:: RPCEnvironment
:: get
();
1526 my $user = $rpcenv -> get_user ();
1527 my $node = $param ->{ node
};
1529 my $logfile = "/var/log/ceph/ceph.log" ;
1530 my ( $count, $lines ) = PVE
:: Tools
:: dump_logfile
( $logfile, $param ->{ start
}, $param ->{ limit
});
1532 $rpcenv -> set_result_attrib ( 'total' , $count );