]>
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 sub purge_all_ceph_files
{
42 # fixme: this is very dangerous - should we really support this function?
46 unlink $pve_ceph_cfgpath ;
47 unlink $pve_ckeyring_path ;
48 unlink $pve_mon_key_path ;
50 unlink $ceph_bootstrap_osd_keyring ;
51 unlink $ceph_bootstrap_mds_keyring ;
53 system ( "rm -rf /var/lib/ceph/mon/ceph-*" );
59 my $check_ceph_installed = sub {
63 die "ceph binaries not installed \n " if ! $noerr ;
70 my $check_ceph_inited = sub {
73 return undef if !& $check_ceph_installed ( $noerr );
75 if (! - f
$pve_ceph_cfgpath ) {
76 die "pveceph configuration not initialized \n " if ! $noerr ;
83 my $check_ceph_enabled = sub {
86 return undef if !& $check_ceph_inited ( $noerr );
88 if (! - f
$ceph_cfgpath ) {
89 die "pveceph configuration not enabled \n " if ! $noerr ;
96 my $parse_ceph_config = sub {
101 return $cfg if ! - f
$filename ;
103 my $fh = IO
:: File-
> new ( $filename, "r" ) ||
104 die "unable to open ' $filename ' - $!\n " ;
108 while ( defined ( my $line = < $fh >)) {
109 $line =~ s/[;#].*$// ;
114 $section = $1 if $line =~ m/^\[(\S+)\]$/ ;
116 warn "no section - skip: $line\n " ;
120 if ( $line =~ m/^(.*\S)\s*=\s*(\S.*)$/ ) {
121 $cfg ->{ $section }->{ $1 } = $2 ;
129 my $run_ceph_cmd = sub {
130 my ( $cmd, %params ) = @_ ;
134 run_command
([ 'ceph' , '-c' , $pve_ceph_cfgpath,
135 '--connect-timeout' , $timeout,
139 my $run_ceph_cmd_text = sub {
140 my ( $cmd, %opts ) = @_ ;
144 my $quiet = delete $opts { quiet
};
153 print " $line\n " if ! $quiet ;
156 & $run_ceph_cmd ( $cmd, outfunc
=> $parser, errfunc
=> $errfunc );
161 my $run_ceph_cmd_json = sub {
162 my ( $cmd, %opts ) = @_ ;
164 my $json = & $run_ceph_cmd_text ([ @$cmd, '--format' , 'json' ], %opts );
166 return decode_json
( $json );
169 sub ceph_mon_status
{
172 return & $run_ceph_cmd_json ([ 'mon_status' ], quiet
=> $quiet );
176 my $ceph_osd_status = sub {
179 return & $run_ceph_cmd_json ([ 'osd' , 'dump' ], quiet
=> $quiet );
182 my $write_ceph_config = sub {
187 my $cond_write_sec = sub {
190 foreach my $section ( keys %$cfg ) {
191 next if $section !~ m/^$re$/ ;
192 $out .= "[ $section ] \n " ;
193 foreach my $key ( sort keys %{ $cfg ->{ $section }}) {
194 $out .= " \t $key = $cfg ->{ $section }->{ $key } \n " ;
200 & $cond_write_sec ( 'global' );
201 & $cond_write_sec ( 'mon' );
202 & $cond_write_sec ( 'osd' );
203 & $cond_write_sec ( 'mon\..*' );
204 & $cond_write_sec ( 'osd\..*' );
206 PVE
:: Tools
:: file_set_contents
( $pve_ceph_cfgpath, $out );
209 my $setup_pve_symlinks = sub {
210 # fail if we find a real file instead of a link
211 if (- f
$ceph_cfgpath ) {
212 my $lnk = readlink ( $ceph_cfgpath );
213 die "file ' $ceph_cfgpath ' already exists \n "
214 if ! $lnk || $lnk ne $pve_ceph_cfgpath ;
216 symlink ( $pve_ceph_cfgpath, $ceph_cfgpath ) ||
217 die "unable to create symlink ' $ceph_cfgpath ' - $!\n " ;
221 my $ceph_service_cmd = sub {
222 run_command
([ 'service' , 'ceph' , '-c' , $pve_ceph_cfgpath, @_ ]);
229 my $fd = IO
:: File-
> new ( "/proc/mounts" , "r" ) ||
230 die "unable to open /proc/mounts - $!\n " ;
234 while ( defined ( my $line = < $fd >)) {
235 my ( $dev, $path, $fstype ) = split ( /\s+/ , $line );
236 next if !( $dev && $path && $fstype );
237 next if $dev !~ m
|^ /dev/ |;
238 my $real_dev = abs_path
( $dev );
239 $mounted ->{ $real_dev } = $path ;
243 my $dev_is_mounted = sub {
245 return $mounted ->{ $dev };
248 my $dir_is_epmty = sub {
251 my $dh = IO
:: Dir-
> new ( $dir );
254 while ( defined ( my $tmp = $dh -> read )) {
255 next if $tmp eq '.' || $tmp eq '..' ;
263 dir_glob_foreach
( '/sys/block' , '.*' , sub {
266 return if $dev eq '.' ;
267 return if $dev eq '..' ;
269 return if $dev =~ m
|^ ram\d
+$|; # skip ram devices
270 return if $dev =~ m
|^ loop \d
+$|; # skip loop devices
271 return if $dev =~ m
|^ md\d
+$|; # skip md devices
272 return if $dev =~ m
|^ dm-
.*$|; # skip dm related things
273 return if $dev =~ m
|^ fd\d
+$|; # skip Floppy
274 return if $dev =~ m
|^ sr\d
+$|; # skip CDs
276 my $devdir = "/sys/block/ $dev/device " ;
277 return if ! - d
$devdir ;
279 my $size = file_read_firstline
( "/sys/block/ $dev/size " );
284 my $info = `udevadm info --path /sys/block/ $dev --query all` ;
287 return if $info !~ m/^E: DEVTYPE=disk$/m ;
288 return if $info =~ m/^E: ID_CDROM/m ;
290 my $serial = 'unknown' ;
291 if ( $info =~ m/^E: ID_SERIAL_SHORT=(\S+)$/m ) {
295 my $vendor = file_read_firstline
( " $devdir/vendor " ) || 'unknown' ;
296 my $model = file_read_firstline
( " $devdir/model " ) || 'unknown' ;
298 my $used = & $dir_is_epmty ( "/sys/block/ $dev/holders " ) ?
0 : 1 ;
300 $used = 1 if & $dev_is_mounted ( "/dev/ $dev " );
302 $disklist ->{ $dev } = {
311 dir_glob_foreach
( "/sys/block/ $dev " , " $dev .+" , sub {
313 if (!& $dir_is_epmty ( "/sys/block/ $dev/$part/holders " )) {
316 if ( my $mp = & $dev_is_mounted ( "/dev/ $part " )) {
318 if ( $mp =~ m
|^ /var/li b
/ceph/osd / ceph-
( \d
+)$|) {
324 $disklist ->{ $dev }->{ used
} = $used ;
325 $disklist ->{ $dev }->{ osdid
} = $osdid ;
331 __PACKAGE__-
> register_method ({
335 description
=> "Directory index." ,
336 permissions
=> { user
=> 'all' },
338 additionalProperties
=> 0 ,
340 node
=> get_standard_option
( 'pve-node' ),
349 links
=> [ { rel
=> 'child' , href
=> "{name}" } ],
361 { name
=> 'status' },
363 { name
=> 'config' },
371 __PACKAGE__-
> register_method ({
375 description
=> "List local disks." ,
379 additionalProperties
=> 0 ,
381 node
=> get_standard_option
( 'pve-node' ),
389 dev
=> { type
=> 'string' },
390 used
=> { type
=> 'boolean' },
391 size
=> { type
=> 'integer' },
392 osdid
=> { type
=> 'integer' },
393 vendor
=> { type
=> 'string' , optional
=> 1 },
394 model
=> { type
=> 'string' , optional
=> 1 },
395 serial
=> { type
=> 'string' , optional
=> 1 },
398 # links => [ { rel => 'child', href => "{}" } ],
403 & $check_ceph_inited ();
405 my $res = list_disks
();
407 return PVE
:: RESTHandler
:: hash_to_array
( $res, 'dev' );
410 __PACKAGE__-
> register_method ({
414 description
=> "Get Ceph configuration." ,
416 additionalProperties
=> 0 ,
418 node
=> get_standard_option
( 'pve-node' ),
421 returns
=> { type
=> 'string' },
425 & $check_ceph_inited ();
427 return PVE
:: Tools
:: file_get_contents
( $pve_ceph_cfgpath );
431 __PACKAGE__-
> register_method ({
435 description
=> "Get Ceph monitor list." ,
439 additionalProperties
=> 0 ,
441 node
=> get_standard_option
( 'pve-node' ),
449 name
=> { type
=> 'string' },
450 addr
=> { type
=> 'string' },
453 links
=> [ { rel
=> 'child' , href
=> "{name}" } ],
458 & $check_ceph_inited ();
462 my $cfg = & $parse_ceph_config ( $pve_ceph_cfgpath );
465 foreach my $section ( keys %$cfg ) {
466 my $d = $cfg ->{ $section };
467 if ( $section =~ m/^mon\.(\S+)$/ ) {
469 if ( $d ->{ 'mon addr' } && $d ->{ 'host' }) {
470 $monhash ->{ $monid } = {
471 addr
=> $d ->{ 'mon addr' },
472 host
=> $d ->{ 'host' },
480 my $monstat = ceph_mon_status
();
481 my $mons = $monstat ->{ monmap
}->{ mons
};
482 foreach my $d ( @$mons ) {
483 next if ! defined ( $d ->{ name
});
484 $monhash ->{ $d ->{ name
}}->{ rank
} = $d ->{ rank
};
485 $monhash ->{ $d ->{ name
}}->{ addr
} = $d ->{ addr
};
486 if ( grep { $_ eq $d ->{ rank
} } @{ $monstat ->{ quorum
}}) {
487 $monhash ->{ $d ->{ name
}}->{ quorum
} = 1 ;
493 return PVE
:: RESTHandler
:: hash_to_array
( $monhash, 'name' );
496 __PACKAGE__-
> register_method ({
500 description
=> "Create initial ceph default configuration and setup symlinks." ,
504 additionalProperties
=> 0 ,
506 node
=> get_standard_option
( 'pve-node' ),
508 description
=> "Use specific network for all ceph related traffic" ,
509 type
=> 'string' , format
=> 'CIDR' ,
514 description
=> 'Number of replicas per object' ,
522 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)" ,
531 returns
=> { type
=> 'null' },
535 & $check_ceph_installed ();
537 # simply load old config if it already exists
538 my $cfg = & $parse_ceph_config ( $pve_ceph_cfgpath );
540 if (! $cfg ->{ global
}) {
545 UUID
:: generate
( $uuid );
546 UUID
:: unparse
( $uuid, $fsid );
550 'auth supported' => 'cephx' ,
551 'auth cluster required' => 'cephx' ,
552 'auth service required' => 'cephx' ,
553 'auth client required' => 'cephx' ,
554 'filestore xattr use omap' => 'true' ,
555 'osd journal size' => '1024' ,
556 'osd pool default min size' => 1 ,
559 # this does not work for default pools
560 #'osd pool default pg num' => $pg_num,
561 #'osd pool default pgp num' => $pg_num,
564 $cfg ->{ global
}->{ keyring
} = '/etc/pve/priv/ $cluster . $name .keyring' ;
565 $cfg ->{ osd
}->{ keyring
} = '/var/lib/ceph/osd/ceph- $id/keyring ' ;
567 $cfg ->{ global
}->{ 'osd pool default size' } = $param ->{ size
} if $param ->{ size
};
569 if ( $param ->{ pg_bits
}) {
570 $cfg ->{ global
}->{ 'osd pg bits' } = $param ->{ pg_bits
};
571 $cfg ->{ global
}->{ 'osd pgp bits' } = $param ->{ pg_bits
};
574 if ( $param ->{ network
}) {
575 $cfg ->{ global
}->{ 'public network' } = $param ->{ network
};
576 $cfg ->{ global
}->{ 'cluster network' } = $param ->{ network
};
579 & $write_ceph_config ( $cfg );
581 & $setup_pve_symlinks ();
586 my $find_node_ip = sub {
589 my $config = PVE
:: INotify
:: read_file
( 'interfaces' );
591 my $net = Net
:: IP-
> new ( $cidr ) || die Net
:: IP
:: Error
() . " \n " ;
593 foreach my $iface ( keys %$config ) {
594 my $d = $config ->{ $iface };
595 next if ! $d ->{ address
};
596 my $a = Net
:: IP-
> new ( $d ->{ address
});
598 return $d ->{ address
} if $net -> overlaps ( $a );
601 die "unable to find local address within network ' $cidr ' \n " ;
604 __PACKAGE__-
> register_method ({
608 description
=> "Create Ceph Monitor" ,
612 additionalProperties
=> 0 ,
614 node
=> get_standard_option
( 'pve-node' ),
617 returns
=> { type
=> 'string' },
621 & $check_ceph_inited ();
623 & $setup_pve_symlinks ();
625 my $rpcenv = PVE
:: RPCEnvironment
:: get
();
627 my $authuser = $rpcenv -> get_user ();
629 my $cfg = & $parse_ceph_config ( $pve_ceph_cfgpath );
633 my $monaddrhash = {};
635 foreach my $section ( keys %$cfg ) {
636 next if $section eq 'global' ;
637 my $d = $cfg ->{ $section };
638 if ( $section =~ m/^mon\./ ) {
640 if ( $d ->{ 'mon addr' }) {
641 $monaddrhash ->{ $d ->{ 'mon addr' }} = $section ;
647 for ( my $i = 0 ; $i < 7 ; $i++ ) {
648 if (! $cfg ->{ "mon. $i " }) {
653 die "unable to find usable monitor id \n " if ! defined ( $monid );
655 my $monsection = "mon. $monid " ;
657 if ( my $pubnet = $cfg ->{ global
}->{ 'public network' }) {
658 $ip = & $find_node_ip ( $pubnet );
660 $ip = PVE
:: Cluster
:: remote_node_ip
( $param ->{ node
});
663 my $monaddr = " $ip :6789" ;
664 my $monname = $param ->{ node
};
666 die "monitor ' $monsection ' already exists \n " if $cfg ->{ $monsection };
667 die "monitor address ' $monaddr ' already in use by ' $monaddrhash ->{ $monaddr }' \n "
668 if $monaddrhash ->{ $monaddr };
673 if (! - f
$pve_ckeyring_path ) {
674 run_command
( "ceph-authtool $pve_ckeyring_path --create-keyring " .
675 "--gen-key -n client.admin" );
678 if (! - f
$pve_mon_key_path ) {
679 run_command
( "cp $pve_ckeyring_path $pve_mon_key_path .tmp" );
680 run_command
( "ceph-authtool $pve_mon_key_path .tmp -n client.admin --set-uid=0 " .
681 "--cap mds 'allow' " .
682 "--cap osd 'allow *' " .
683 "--cap mon 'allow *'" );
684 run_command
( "ceph-authtool $pve_mon_key_path .tmp --gen-key -n mon. --cap mon 'allow *'" );
685 run_command
( "mv $pve_mon_key_path .tmp $pve_mon_key_path " );
688 my $mondir = "/var/lib/ceph/mon/ $ccname - $monid " ;
689 - d
$mondir && die "monitor filesystem ' $mondir ' already exist \n " ;
691 my $monmap = "/tmp/monmap" ;
697 my $monstat = ceph_mon_status
(); # online test
698 & $run_ceph_cmd ([ 'mon' , 'getmap' , '-o' , $monmap ]);
700 run_command
( "monmaptool --create --clobber --add $monid $monaddr --print $monmap " );
703 run_command
( "ceph-mon --mkfs -i $monid --monmap $monmap --keyring $pve_mon_key_path " );
708 File
:: Path
:: remove_tree
( $mondir );
712 $cfg ->{ $monsection } = {
714 'mon addr' => $monaddr,
717 & $write_ceph_config ( $cfg );
719 & $ceph_service_cmd ( 'start' , $monsection );
722 return $rpcenv -> fork_worker ( 'cephcreatemon' , $monsection, $authuser, $worker );
725 __PACKAGE__-
> register_method ({
726 name
=> 'destroymon' ,
727 path
=> 'mon/{monid}' ,
729 description
=> "Destroy Ceph monitor." ,
733 additionalProperties
=> 0 ,
735 node
=> get_standard_option
( 'pve-node' ),
737 description
=> 'Monitor ID' ,
742 returns
=> { type
=> 'string' },
746 my $rpcenv = PVE
:: RPCEnvironment
:: get
();
748 my $authuser = $rpcenv -> get_user ();
750 & $check_ceph_inited ();
752 my $cfg = & $parse_ceph_config ( $pve_ceph_cfgpath );
754 my $monid = $param ->{ monid
};
755 my $monsection = "mon. $monid " ;
757 my $monstat = ceph_mon_status
();
758 my $monlist = $monstat ->{ monmap
}->{ mons
};
760 die "no such monitor id ' $monid ' \n "
761 if ! defined ( $cfg ->{ $monsection });
764 my $mondir = "/var/lib/ceph/mon/ $ccname - $monid " ;
765 - d
$mondir || die "monitor filesystem ' $mondir ' does not exist on this node \n " ;
767 die "can't remove last monitor \n " if scalar ( @$monlist ) <= 1 ;
772 & $run_ceph_cmd ([ 'mon' , 'remove' , $monid ]);
774 eval { & $ceph_service_cmd ( 'stop' , $monsection ); };
777 delete $cfg ->{ $monsection };
778 & $write_ceph_config ( $cfg );
779 File
:: Path
:: remove_tree
( $mondir );
782 return $rpcenv -> fork_worker ( 'cephdestroymon' , $monsection, $authuser, $worker );
785 __PACKAGE__-
> register_method ({
789 description
=> "Stop ceph services." ,
793 additionalProperties
=> 0 ,
795 node
=> get_standard_option
( 'pve-node' ),
797 description
=> 'Ceph service name.' ,
800 pattern
=> '(mon|mds|osd)\.[A-Za-z0-9]{1,32}' ,
804 returns
=> { type
=> 'string' },
808 my $rpcenv = PVE
:: RPCEnvironment
:: get
();
810 my $authuser = $rpcenv -> get_user ();
812 & $check_ceph_inited ();
814 my $cfg = & $parse_ceph_config ( $pve_ceph_cfgpath );
815 scalar ( keys %$cfg ) || die "no configuration \n " ;
821 if ( $param ->{ service
}) {
822 push @$cmd, $param ->{ service
};
825 & $ceph_service_cmd ( @$cmd );
828 return $rpcenv -> fork_worker ( 'srvstop' , $param ->{ service
} || 'ceph' ,
832 __PACKAGE__-
> register_method ({
836 description
=> "Start ceph services." ,
840 additionalProperties
=> 0 ,
842 node
=> get_standard_option
( 'pve-node' ),
844 description
=> 'Ceph service name.' ,
847 pattern
=> '(mon|mds|osd)\.[A-Za-z0-9]{1,32}' ,
851 returns
=> { type
=> 'string' },
855 my $rpcenv = PVE
:: RPCEnvironment
:: get
();
857 my $authuser = $rpcenv -> get_user ();
859 & $check_ceph_inited ();
861 my $cfg = & $parse_ceph_config ( $pve_ceph_cfgpath );
862 scalar ( keys %$cfg ) || die "no configuration \n " ;
868 if ( $param ->{ service
}) {
869 push @$cmd, $param ->{ service
};
872 & $ceph_service_cmd ( @$cmd );
875 return $rpcenv -> fork_worker ( 'srvstart' , $param ->{ service
} || 'ceph' ,
879 __PACKAGE__-
> register_method ({
883 description
=> "Get ceph status." ,
887 additionalProperties
=> 0 ,
889 node
=> get_standard_option
( 'pve-node' ),
892 returns
=> { type
=> 'object' },
896 & $check_ceph_enabled ();
898 return & $run_ceph_cmd_json ([ 'status' ], quiet
=> 1 );
901 __PACKAGE__-
> register_method ({
905 description
=> "List all pools." ,
909 additionalProperties
=> 0 ,
911 node
=> get_standard_option
( 'pve-node' ),
919 pool
=> { type
=> 'integer' },
920 pool_name
=> { type
=> 'string' },
921 size
=> { type
=> 'integer' },
924 links
=> [ { rel
=> 'child' , href
=> "{pool_name}" } ],
929 & $check_ceph_inited ();
931 my $res = & $run_ceph_cmd_json ([ 'osd' , 'dump' ], quiet
=> 1 );
934 foreach my $e (@{ $res ->{ pools
}}) {
936 foreach my $attr ( qw(pool pool_name size min_size pg_num crush_ruleset) ) {
937 $d ->{ $attr } = $e ->{ $attr } if defined ( $e ->{ $attr });
945 __PACKAGE__-
> register_method ({
946 name
=> 'createpool' ,
949 description
=> "Create POOL" ,
953 additionalProperties
=> 0 ,
955 node
=> get_standard_option
( 'pve-node' ),
957 description
=> "The name of the pool. It must be unique." ,
961 description
=> 'Number of replicas per object' ,
969 description
=> 'Minimum number of replicas per object' ,
977 description
=> "Number of placement groups." ,
986 returns
=> { type
=> 'null' },
990 & $check_ceph_inited ();
992 die "not fully configured - missing ' $pve_ckeyring_path ' \n "
993 if ! - f
$pve_ckeyring_path ;
995 my $pg_num = $param ->{ pg_num
} || 512 ;
996 my $size = $param ->{ size
} || 2 ;
997 my $min_size = $param ->{ min_size
} || 1 ;
999 & $run_ceph_cmd ([ 'osd' , 'pool' , 'create' , $param ->{ name
}, $pg_num ]);
1001 & $run_ceph_cmd ([ 'osd' , 'pool' , 'set' , $param ->{ name
}, 'min_size' , $min_size ]);
1003 & $run_ceph_cmd ([ 'osd' , 'pool' , 'set' , $param ->{ name
}, 'size' , $size ]);
1008 __PACKAGE__-
> register_method ({
1009 name
=> 'destroypool' ,
1010 path
=> 'pools/{name}' ,
1012 description
=> "Destroy pool" ,
1016 additionalProperties
=> 0 ,
1018 node
=> get_standard_option
( 'pve-node' ),
1020 description
=> "The name of the pool. It must be unique." ,
1025 returns
=> { type
=> 'null' },
1029 & $check_ceph_inited ();
1031 & $run_ceph_cmd ([ 'osd' , 'pool' , 'delete' , $param ->{ name
}, $param ->{ name
}, '--yes-i-really-really-mean-it' ]);
1036 __PACKAGE__-
> register_method ({
1040 description
=> "Get Ceph osd list/tree." ,
1044 additionalProperties
=> 0 ,
1046 node
=> get_standard_option
( 'pve-node' ),
1055 & $check_ceph_inited ();
1057 my $res = & $run_ceph_cmd_json ([ 'osd' , 'tree' ], quiet
=> 1 );
1059 die "no tree nodes found \n " if !( $res && $res ->{ nodes
});
1063 foreach my $e (@{ $res ->{ nodes
}}) {
1064 $nodes ->{ $e ->{ id
}} = $e ;
1072 foreach my $opt ( qw(status crush_weight reweight) ) {
1073 $new ->{ $opt } = $e ->{ $opt } if defined ( $e ->{ $opt });
1076 $newnodes ->{ $e ->{ id
}} = $new ;
1079 foreach my $e (@{ $res ->{ nodes
}}) {
1080 my $new = $newnodes ->{ $e ->{ id
}};
1081 if ( $e ->{ children
} && scalar (@{ $e ->{ children
}})) {
1082 $new ->{ children
} = [];
1084 foreach my $cid (@{ $e ->{ children
}}) {
1085 $nodes ->{ $cid }->{ parent
} = $e ->{ id
};
1086 if ( $nodes ->{ $cid }->{ type
} eq 'osd' &&
1087 $e ->{ type
} eq 'host' ) {
1088 $newnodes ->{ $cid }->{ host
} = $e ->{ name
};
1090 push @{ $new ->{ children
}}, $newnodes ->{ $cid };
1093 $new ->{ leaf
} = ( $e ->{ id
} >= 0 ) ?
1 : 0 ;
1098 foreach my $e (@{ $res ->{ nodes
}}) {
1099 if (! $nodes ->{ $e ->{ id
}}->{ parent
}) {
1100 $rootnode = $newnodes ->{ $e ->{ id
}};
1105 die "no root node \n " if ! $rootnode ;
1107 my $data = { root
=> $rootnode };
1112 __PACKAGE__-
> register_method ({
1113 name
=> 'createosd' ,
1116 description
=> "Create OSD" ,
1120 additionalProperties
=> 0 ,
1122 node
=> get_standard_option
( 'pve-node' ),
1124 description
=> "Block device name." ,
1128 description
=> "File system type." ,
1130 enum
=> [ 'xfs' , 'ext4' , 'btrfs' ],
1136 returns
=> { type
=> 'string' },
1140 my $rpcenv = PVE
:: RPCEnvironment
:: get
();
1142 my $authuser = $rpcenv -> get_user ();
1144 & $check_ceph_inited ();
1146 & $setup_pve_symlinks ();
1148 - b
$param ->{ dev
} || die "no such block device ' $param ->{dev}' \n " ;
1150 my $disklist = list_disks
();
1152 my $devname = $param ->{ dev
};
1153 $devname =~ s
| /dev/ ||;
1155 my $diskinfo = $disklist ->{ $devname };
1156 die "unable to get device info for ' $devname ' \n "
1159 die "device ' $param ->{dev}' is in use \n "
1160 if $diskinfo ->{ used
};
1162 my $monstat = ceph_mon_status
( 1 );
1163 die "unable to get fsid \n " if ! $monstat ->{ monmap
} || ! $monstat ->{ monmap
}->{ fsid
};
1164 my $fsid = $monstat ->{ monmap
}->{ fsid
};
1166 if (! - f
$ceph_bootstrap_osd_keyring ) {
1167 & $run_ceph_cmd ([ 'auth' , 'get' , 'client.bootstrap-osd' , '-o' , $ceph_bootstrap_osd_keyring ]);
1173 my $fstype = $param ->{ fstype
} || 'xfs' ;
1175 print "create OSD on $param ->{dev} ( $fstype ) \n " ;
1177 run_command
([ 'ceph-disk' , 'prepare' , '--zap-disk' , '--fs-type' , $fstype,
1178 '--cluster' , $ccname, '--cluster-uuid' , $fsid,
1179 '--' , $param ->{ dev
}]);
1182 return $rpcenv -> fork_worker ( 'cephcreateosd' , $devname, $authuser, $worker );
1185 __PACKAGE__-
> register_method ({
1186 name
=> 'destroyosd' ,
1187 path
=> 'osd/{osdid}' ,
1189 description
=> "Destroy OSD" ,
1193 additionalProperties
=> 0 ,
1195 node
=> get_standard_option
( 'pve-node' ),
1197 description
=> 'OSD ID' ,
1202 returns
=> { type
=> 'string' },
1206 my $rpcenv = PVE
:: RPCEnvironment
:: get
();
1208 my $authuser = $rpcenv -> get_user ();
1210 & $check_ceph_inited ();
1212 my $osdid = $param ->{ osdid
};
1214 # fixme: not 100% sure what we should do here
1216 my $stat = & $ceph_osd_status ();
1218 my $osdlist = $stat ->{ osds
} || [];
1221 foreach my $d ( @$osdlist ) {
1222 if ( $d ->{ osd
} == $osdid ) {
1227 die "no such OSD ' $osdid ' \n " if ! $osdstat ;
1229 die "osd is in use (in == 1) \n " if $osdstat ->{ in };
1230 #&$run_ceph_cmd(['osd', 'out', $osdid]);
1232 die "osd is still runnung (up == 1) \n " if $osdstat ->{ up
};
1234 my $osdsection = "osd. $osdid " ;
1239 print "destroy OSD $osdsection\n " ;
1241 eval { & $ceph_service_cmd ( 'stop' , $osdsection ); };
1244 print "Remove $osdsection from the CRUSH map \n " ;
1245 & $run_ceph_cmd ([ 'osd' , 'crush' , 'remove' , $osdsection ]);
1247 print "Remove the $osdsection authentication key. \n " ;
1248 & $run_ceph_cmd ([ 'auth' , 'del' , $osdsection ]);
1250 print "Remove OSD $osdsection\n " ;
1251 & $run_ceph_cmd ([ 'osd' , 'rm' , $osdid ]);
1254 return $rpcenv -> fork_worker ( 'cephdestroyosd' , $osdsection, $authuser, $worker );
1257 __PACKAGE__-
> register_method ({
1261 description
=> "Get OSD crush map" ,
1265 additionalProperties
=> 0 ,
1267 node
=> get_standard_option
( 'pve-node' ),
1270 returns
=> { type
=> 'string' },
1274 & $check_ceph_inited ();
1276 # this produces JSON (difficult to read for the user)
1277 # my $txt = &$run_ceph_cmd_text(['osd', 'crush', 'dump'], quiet => 1);
1281 my $mapfile = "/var/tmp/ceph-crush.map. $$ " ;
1282 my $mapdata = "/var/tmp/ceph-crush.txt. $$ " ;
1285 & $run_ceph_cmd ([ 'osd' , 'getcrushmap' , '-o' , $mapfile ]);
1286 run_command
([ 'crushtool' , '-d' , $mapfile, '-o' , $mapdata ]);
1287 $txt = PVE
:: Tools
:: file_get_contents
( $mapdata );
1299 __PACKAGE__-
> register_method ({
1303 description
=> "Read ceph log" ,
1306 check
=> [ 'perm' , '/nodes/{node}' , [ 'Sys.Syslog' ]],
1310 additionalProperties
=> 0 ,
1312 node
=> get_standard_option
( 'pve-node' ),
1331 description
=> "Line number" ,
1335 description
=> "Line text" ,
1344 my $rpcenv = PVE
:: RPCEnvironment
:: get
();
1345 my $user = $rpcenv -> get_user ();
1346 my $node = $param ->{ node
};
1348 my $logfile = "/var/log/ceph/ceph.log" ;
1349 my ( $count, $lines ) = PVE
:: Tools
:: dump_logfile
( $logfile, $param ->{ start
}, $param ->{ limit
});
1351 $rpcenv -> set_result_attrib ( 'total' , $count );