]>
git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Ceph.pm
1 package PVE
::API2
::CephOSD
;
9 use PVE
::Tools
qw(extract_param run_command file_get_contents file_read_firstline dir_glob_regex dir_glob_foreach);
10 use PVE
::Exception
qw(raise raise_param_exc);
12 use PVE
::Cluster
qw(cfs_lock_file cfs_read_file cfs_write_file);
13 use PVE
::AccessControl
;
16 use PVE
::RPCEnvironment
;
17 use PVE
::JSONSchema
qw(get_standard_option);
22 use base
qw(PVE::RESTHandler);
24 use Data
::Dumper
; # fixme: remove
26 my $get_osd_status = sub {
27 my ($rados, $osdid) = @_;
29 my $stat = $rados->mon_command({ prefix
=> 'osd dump' });
31 my $osdlist = $stat->{osds
} || [];
33 my $flags = $stat->{flags
} || undef;
36 foreach my $d (@$osdlist) {
37 $osdstat->{$d->{osd
}} = $d if defined($d->{osd
});
39 if (defined($osdid)) {
40 die "no such OSD '$osdid'\n" if !$osdstat->{$osdid};
41 return $osdstat->{$osdid};
44 return wantarray?
($osdstat, $flags):$osdstat;
47 my $get_osd_usage = sub {
50 my $osdlist = $rados->mon_command({ prefix
=> 'pg dump',
51 dumpcontents
=> [ 'osds' ]}) || [];
54 foreach my $d (@$osdlist) {
55 $osdstat->{$d->{osd
}} = $d if defined($d->{osd
});
61 __PACKAGE__-
>register_method ({
65 description
=> "Get Ceph osd list/tree.",
69 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
72 additionalProperties
=> 0,
74 node
=> get_standard_option
('pve-node'),
77 # fixme: return a list instead of extjs tree format ?
84 PVE
::CephTools
::check_ceph_inited
();
86 my $rados = PVE
::RADOS-
>new();
87 my $res = $rados->mon_command({ prefix
=> 'osd tree' });
89 die "no tree nodes found\n" if !($res && $res->{nodes
});
91 my ($osdhash, $flags) = &$get_osd_status($rados);
93 my $usagehash = &$get_osd_usage($rados);
97 foreach my $e (@{$res->{nodes
}}) {
98 $nodes->{$e->{id
}} = $e;
106 foreach my $opt (qw(status crush_weight reweight)) {
107 $new->{$opt} = $e->{$opt} if defined($e->{$opt});
110 if (my $stat = $osdhash->{$e->{id
}}) {
111 $new->{in} = $stat->{in} if defined($stat->{in});
114 if (my $stat = $usagehash->{$e->{id
}}) {
115 $new->{total_space
} = ($stat->{kb
} || 1) * 1024;
116 $new->{bytes_used
} = ($stat->{kb_used
} || 0) * 1024;
117 $new->{percent_used
} = ($new->{bytes_used
}*100)/$new->{total_space
};
118 if (my $d = $stat->{fs_perf_stat
}) {
119 $new->{commit_latency_ms
} = $d->{commit_latency_ms
};
120 $new->{apply_latency_ms
} = $d->{apply_latency_ms
};
124 $newnodes->{$e->{id
}} = $new;
127 foreach my $e (@{$res->{nodes
}}) {
128 my $new = $newnodes->{$e->{id
}};
129 if ($e->{children
} && scalar(@{$e->{children
}})) {
130 $new->{children
} = [];
132 foreach my $cid (@{$e->{children
}}) {
133 $nodes->{$cid}->{parent
} = $e->{id
};
134 if ($nodes->{$cid}->{type
} eq 'osd' &&
135 $e->{type
} eq 'host') {
136 $newnodes->{$cid}->{host
} = $e->{name
};
138 push @{$new->{children
}}, $newnodes->{$cid};
141 $new->{leaf
} = ($e->{id
} >= 0) ?
1 : 0;
146 foreach my $e (@{$res->{nodes
}}) {
147 if (!$nodes->{$e->{id
}}->{parent
}) {
148 push @$roots, $newnodes->{$e->{id
}};
152 die "no root node\n" if !@$roots;
154 my $data = { root
=> { leaf
=> 0, children
=> $roots } };
156 # we want this for the noout flag
157 $data->{flags
} = $flags if $flags;
162 __PACKAGE__-
>register_method ({
166 description
=> "Create OSD",
170 additionalProperties
=> 0,
172 node
=> get_standard_option
('pve-node'),
174 description
=> "Block device name.",
178 description
=> "Block device name for journal.",
183 description
=> "File system type (filestore only).",
185 enum
=> ['xfs', 'ext4', 'btrfs'],
190 description
=> "Use bluestore instead of filestore.",
197 returns
=> { type
=> 'string' },
201 my $rpcenv = PVE
::RPCEnvironment
::get
();
203 my $authuser = $rpcenv->get_user();
205 raise_param_exc
({ 'bluestore' => "conflicts with parameter 'fstype'" })
206 if (defined($param->{fstype
}) && defined($param->{bluestore
}) && $param->{bluestore
});
208 PVE
::CephTools
::check_ceph_inited
();
210 PVE
::CephTools
::setup_pve_symlinks
();
214 if ($param->{journal_dev
} && ($param->{journal_dev
} ne $param->{dev
})) {
215 $journal_dev = PVE
::Diskmanage
::verify_blockdev_path
($param->{journal_dev
});
218 $param->{dev
} = PVE
::Diskmanage
::verify_blockdev_path
($param->{dev
});
220 my $devname = $param->{dev
};
221 $devname =~ s
|/dev/||;
223 my $disklist = PVE
::Diskmanage
::get_disks
($devname, 1);
225 my $diskinfo = $disklist->{$devname};
226 die "unable to get device info for '$devname'\n"
229 die "device '$param->{dev}' is in use\n"
230 if $diskinfo->{used
};
232 my $devpath = $diskinfo->{devpath
};
233 my $rados = PVE
::RADOS-
>new();
234 my $monstat = $rados->mon_command({ prefix
=> 'mon_status' });
235 die "unable to get fsid\n" if !$monstat->{monmap
} || !$monstat->{monmap
}->{fsid
};
237 my $fsid = $monstat->{monmap
}->{fsid
};
238 $fsid = $1 if $fsid =~ m/^([0-9a-f\-]+)$/;
240 my $ceph_bootstrap_osd_keyring = PVE
::CephTools
::get_config
('ceph_bootstrap_osd_keyring');
242 if (! -f
$ceph_bootstrap_osd_keyring) {
243 my $bindata = $rados->mon_command({ prefix
=> 'auth get', entity
=> 'client.bootstrap-osd', format
=> 'plain' });
244 PVE
::Tools
::file_set_contents
($ceph_bootstrap_osd_keyring, $bindata);
250 my $fstype = $param->{fstype
} || 'xfs';
253 my $ccname = PVE
::CephTools
::get_config
('ccname');
255 my $cmd = ['ceph-disk', 'prepare', '--zap-disk',
256 '--cluster', $ccname, '--cluster-uuid', $fsid ];
258 if ($param->{bluestore
}) {
259 print "create OSD on $devpath (bluestore)\n";
260 push @$cmd, '--bluestore';
262 print "create OSD on $devpath ($fstype)\n";
263 push @$cmd, '--filestore', '--fs-type', $fstype;
267 print "using device '$journal_dev' for journal\n";
268 push @$cmd, '--journal-dev', $devpath, $journal_dev;
270 push @$cmd, $devpath;
276 return $rpcenv->fork_worker('cephcreateosd', $devname, $authuser, $worker);
279 __PACKAGE__-
>register_method ({
280 name
=> 'destroyosd',
283 description
=> "Destroy OSD",
287 additionalProperties
=> 0,
289 node
=> get_standard_option
('pve-node'),
291 description
=> 'OSD ID',
295 description
=> "If set, we remove partition table entries.",
302 returns
=> { type
=> 'string' },
306 my $rpcenv = PVE
::RPCEnvironment
::get
();
308 my $authuser = $rpcenv->get_user();
310 PVE
::CephTools
::check_ceph_inited
();
312 my $osdid = $param->{osdid
};
314 my $rados = PVE
::RADOS-
>new();
315 my $osdstat = &$get_osd_status($rados, $osdid);
317 die "osd is in use (in == 1)\n" if $osdstat->{in};
318 #&$run_ceph_cmd(['osd', 'out', $osdid]);
320 die "osd is still runnung (up == 1)\n" if $osdstat->{up
};
322 my $osdsection = "osd.$osdid";
327 # reopen with longer timeout
328 $rados = PVE
::RADOS-
>new(timeout
=> PVE
::CephTools
::get_config
('long_rados_timeout'));
330 print "destroy OSD $osdsection\n";
332 eval { PVE
::CephTools
::ceph_service_cmd
('stop', $osdsection); };
335 print "Remove $osdsection from the CRUSH map\n";
336 $rados->mon_command({ prefix
=> "osd crush remove", name
=> $osdsection, format
=> 'plain' });
338 print "Remove the $osdsection authentication key.\n";
339 $rados->mon_command({ prefix
=> "auth del", entity
=> $osdsection, format
=> 'plain' });
341 print "Remove OSD $osdsection\n";
342 $rados->mon_command({ prefix
=> "osd rm", ids
=> [ $osdsection ], format
=> 'plain' });
344 # try to unmount from standard mount point
345 my $mountpoint = "/var/lib/ceph/osd/ceph-$osdid";
347 my $remove_partition = sub {
350 return if !$part || (! -b
$part );
351 my $partnum = PVE
::Diskmanage
::get_partnum
($part);
352 my $devpath = PVE
::Diskmanage
::get_blockdev
($part);
354 print "remove partition $part (disk '${devpath}', partnum $partnum)\n";
355 eval { run_command
(['/sbin/sgdisk', '-d', $partnum, "${devpath}"]); };
362 if ($param->{cleanup
}) {
363 my $jpath = "$mountpoint/journal";
364 $journal_part = abs_path
($jpath);
366 if (my $fd = IO
::File-
>new("/proc/mounts", "r")) {
367 while (defined(my $line = <$fd>)) {
368 my ($dev, $path, $fstype) = split(/\s+/, $line);
369 next if !($dev && $path && $fstype);
370 next if $dev !~ m
|^/dev/|;
371 if ($path eq $mountpoint) {
372 $data_part = abs_path
($dev);
380 print "Unmount OSD $osdsection from $mountpoint\n";
381 eval { run_command
(['/bin/umount', $mountpoint]); };
384 } elsif ($param->{cleanup
}) {
385 #be aware of the ceph udev rules which can remount.
386 &$remove_partition($data_part);
387 &$remove_partition($journal_part);
391 return $rpcenv->fork_worker('cephdestroyosd', $osdsection, $authuser, $worker);
394 __PACKAGE__-
>register_method ({
396 path
=> '{osdid}/in',
398 description
=> "ceph osd in",
402 check
=> ['perm', '/', [ 'Sys.Modify' ]],
405 additionalProperties
=> 0,
407 node
=> get_standard_option
('pve-node'),
409 description
=> 'OSD ID',
414 returns
=> { type
=> "null" },
418 PVE
::CephTools
::check_ceph_inited
();
420 my $osdid = $param->{osdid
};
422 my $rados = PVE
::RADOS-
>new();
424 my $osdstat = &$get_osd_status($rados, $osdid); # osd exists?
426 my $osdsection = "osd.$osdid";
428 $rados->mon_command({ prefix
=> "osd in", ids
=> [ $osdsection ], format
=> 'plain' });
433 __PACKAGE__-
>register_method ({
435 path
=> '{osdid}/out',
437 description
=> "ceph osd out",
441 check
=> ['perm', '/', [ 'Sys.Modify' ]],
444 additionalProperties
=> 0,
446 node
=> get_standard_option
('pve-node'),
448 description
=> 'OSD ID',
453 returns
=> { type
=> "null" },
457 PVE
::CephTools
::check_ceph_inited
();
459 my $osdid = $param->{osdid
};
461 my $rados = PVE
::RADOS-
>new();
463 my $osdstat = &$get_osd_status($rados, $osdid); # osd exists?
465 my $osdsection = "osd.$osdid";
467 $rados->mon_command({ prefix
=> "osd out", ids
=> [ $osdsection ], format
=> 'plain' });
472 package PVE
::API2
::Ceph
;
478 use POSIX qw
(LONG_MAX
);
479 use Cwd
qw(abs_path);
485 use PVE
::Tools
qw(extract_param run_command file_get_contents file_read_firstline dir_glob_regex dir_glob_foreach);
486 use PVE
::Exception
qw(raise raise_param_exc);
488 use PVE
::Cluster
qw(cfs_lock_file cfs_read_file cfs_write_file);
489 use PVE
::AccessControl
;
491 use PVE
::RESTHandler
;
492 use PVE
::RPCEnvironment
;
493 use PVE
::JSONSchema
qw(get_standard_option);
498 use base
qw(PVE::RESTHandler);
500 use Data
::Dumper
; # fixme: remove
502 my $pve_osd_default_journal_size = 1024*5;
504 __PACKAGE__-
>register_method ({
505 subclass
=> "PVE::API2::CephOSD",
509 __PACKAGE__-
>register_method ({
513 description
=> "Directory index.",
514 permissions
=> { user
=> 'all' },
516 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
519 additionalProperties
=> 0,
521 node
=> get_standard_option
('pve-node'),
530 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
542 { name
=> 'status' },
544 { name
=> 'config' },
553 __PACKAGE__-
>register_method ({
557 description
=> "List local disks.",
561 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
564 additionalProperties
=> 0,
566 node
=> get_standard_option
('pve-node'),
568 description
=> "Only list specific types of disks.",
570 enum
=> ['unused', 'journal_disks'],
580 dev
=> { type
=> 'string' },
581 used
=> { type
=> 'string', optional
=> 1 },
582 gpt
=> { type
=> 'boolean' },
583 size
=> { type
=> 'integer' },
584 osdid
=> { type
=> 'integer' },
585 vendor
=> { type
=> 'string', optional
=> 1 },
586 model
=> { type
=> 'string', optional
=> 1 },
587 serial
=> { type
=> 'string', optional
=> 1 },
590 # links => [ { rel => 'child', href => "{}" } ],
595 PVE
::CephTools
::check_ceph_inited
();
597 my $disks = PVE
::Diskmanage
::get_disks
(undef, 1);
600 foreach my $dev (keys %$disks) {
601 my $d = $disks->{$dev};
602 if ($param->{type
}) {
603 if ($param->{type
} eq 'journal_disks') {
604 next if $d->{osdid
} >= 0;
606 } elsif ($param->{type
} eq 'unused') {
609 die "internal error"; # should not happen
613 $d->{dev
} = "/dev/$dev";
620 __PACKAGE__-
>register_method ({
625 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
627 description
=> "Get Ceph configuration.",
629 additionalProperties
=> 0,
631 node
=> get_standard_option
('pve-node'),
634 returns
=> { type
=> 'string' },
638 PVE
::CephTools
::check_ceph_inited
();
640 my $path = PVE
::CephTools
::get_config
('pve_ceph_cfgpath');
641 return PVE
::Tools
::file_get_contents
($path);
645 __PACKAGE__-
>register_method ({
649 description
=> "Get Ceph monitor list.",
653 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
656 additionalProperties
=> 0,
658 node
=> get_standard_option
('pve-node'),
666 name
=> { type
=> 'string' },
667 addr
=> { type
=> 'string' },
670 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
675 PVE
::CephTools
::check_ceph_inited
();
679 my $cfg = PVE
::CephTools
::parse_ceph_config
();
682 foreach my $section (keys %$cfg) {
683 my $d = $cfg->{$section};
684 if ($section =~ m/^mon\.(\S+)$/) {
686 if ($d->{'mon addr'} && $d->{'host'}) {
687 $monhash->{$monid} = {
688 addr
=> $d->{'mon addr'},
689 host
=> $d->{'host'},
697 my $rados = PVE
::RADOS-
>new();
698 my $monstat = $rados->mon_command({ prefix
=> 'mon_status' });
699 my $mons = $monstat->{monmap
}->{mons
};
700 foreach my $d (@$mons) {
701 next if !defined($d->{name
});
702 $monhash->{$d->{name
}}->{rank
} = $d->{rank
};
703 $monhash->{$d->{name
}}->{addr
} = $d->{addr
};
704 if (grep { $_ eq $d->{rank
} } @{$monstat->{quorum
}}) {
705 $monhash->{$d->{name
}}->{quorum
} = 1;
711 return PVE
::RESTHandler
::hash_to_array
($monhash, 'name');
714 __PACKAGE__-
>register_method ({
718 description
=> "Create initial ceph default configuration and setup symlinks.",
722 check
=> ['perm', '/', [ 'Sys.Modify' ]],
725 additionalProperties
=> 0,
727 node
=> get_standard_option
('pve-node'),
729 description
=> "Use specific network for all ceph related traffic",
730 type
=> 'string', format
=> 'CIDR',
735 description
=> 'Targeted number of replicas per object',
743 description
=> 'Minimum number of available replicas per object to allow I/O',
751 description
=> "Placement group bits, used to specify the " .
752 "default number of placement groups.\n\nNOTE: 'osd pool " .
753 "default pg num' does not work for default pools.",
761 description
=> "Disable cephx authentification.\n\n" .
762 "WARNING: cephx is a security feature protecting against " .
763 "man-in-the-middle attacks. Only consider disabling cephx ".
764 "if your network is private!",
771 returns
=> { type
=> 'null' },
775 PVE
::CephTools
::check_ceph_installed
();
777 # simply load old config if it already exists
778 my $cfg = PVE
::CephTools
::parse_ceph_config
();
780 if (!$cfg->{global
}) {
785 UUID
::generate
($uuid);
786 UUID
::unparse
($uuid, $fsid);
788 my $auth = $param->{disable_cephx
} ?
'none' : 'cephx';
792 'auth cluster required' => $auth,
793 'auth service required' => $auth,
794 'auth client required' => $auth,
795 'osd journal size' => $pve_osd_default_journal_size,
796 'osd pool default size' => $param->{size
} // 3,
797 'osd pool default min size' => $param->{min_size
} // 2,
798 'mon allow pool delete' => 'true',
801 # this does not work for default pools
802 #'osd pool default pg num' => $pg_num,
803 #'osd pool default pgp num' => $pg_num,
806 $cfg->{global
}->{keyring
} = '/etc/pve/priv/$cluster.$name.keyring';
807 $cfg->{osd
}->{keyring
} = '/var/lib/ceph/osd/ceph-$id/keyring';
809 if ($param->{pg_bits
}) {
810 $cfg->{global
}->{'osd pg bits'} = $param->{pg_bits
};
811 $cfg->{global
}->{'osd pgp bits'} = $param->{pg_bits
};
814 if ($param->{network
}) {
815 $cfg->{global
}->{'public network'} = $param->{network
};
816 $cfg->{global
}->{'cluster network'} = $param->{network
};
819 PVE
::CephTools
::write_ceph_config
($cfg);
821 PVE
::CephTools
::setup_pve_symlinks
();
826 my $find_node_ip = sub {
829 my $net = Net
::IP-
>new($cidr) || die Net
::IP
::Error
() . "\n";
830 my $id = $net->version == 6 ?
'address6' : 'address';
832 my $config = PVE
::INotify
::read_file
('interfaces');
833 my $ifaces = $config->{ifaces
};
835 foreach my $iface (keys %$ifaces) {
836 my $d = $ifaces->{$iface};
838 my $a = Net
::IP-
>new($d->{$id});
840 return $d->{$id} if $net->overlaps($a);
843 die "unable to find local address within network '$cidr'\n";
846 my $create_mgr = sub {
847 my ($rados, $id) = @_;
849 my $clustername = PVE
::CephTools
::get_config
('ccname');
850 my $mgrdir = "/var/lib/ceph/mgr/$clustername-$id";
851 my $mgrkeyring = "$mgrdir/keyring";
852 my $mgrname = "mgr.$id";
854 die "ceph manager directory '$mgrdir' already exists\n"
857 print "creating manager directory '$mgrdir'\n";
859 print "creating keys for '$mgrname'\n";
860 my $output = $rados->mon_command({ prefix
=> 'auth get-or-create',
863 mon
=> 'allow profile mgr',
868 PVE
::Tools
::file_set_contents
($mgrkeyring, $output);
870 print "setting owner for directory\n";
871 run_command
(["chown", 'ceph:ceph', '-R', $mgrdir]);
873 print "enabling service 'ceph-mgr\@$id.service'\n";
874 PVE
::CephTools
::ceph_service_cmd
('enable', $mgrname);
875 print "starting service 'ceph-mgr\@$id.service'\n";
876 PVE
::CephTools
::ceph_service_cmd
('start', $mgrname);
879 my $destroy_mgr = sub {
882 my $clustername = PVE
::CephTools
::get_config
('ccname');
883 my $mgrname = "mgr.$mgrid";
884 my $mgrdir = "/var/lib/ceph/mgr/$clustername-$mgrid";
886 die "ceph manager directory '$mgrdir' not found\n"
889 print "disabling service 'ceph-mgr\@$mgrid.service'\n";
890 PVE
::CephTools
::ceph_service_cmd
('disable', $mgrname);
891 print "stopping service 'ceph-mgr\@$mgrid.service'\n";
892 PVE
::CephTools
::ceph_service_cmd
('stop', $mgrname);
894 print "removing manager directory '$mgrdir'\n";
895 File
::Path
::remove_tree
($mgrdir);
898 __PACKAGE__-
>register_method ({
902 description
=> "Create Ceph Monitor and Manager",
906 check
=> ['perm', '/', [ 'Sys.Modify' ]],
909 additionalProperties
=> 0,
911 node
=> get_standard_option
('pve-node'),
915 pattern
=> '[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?',
916 description
=> "The ID for the monitor, when omitted the same as the nodename",
918 'exclude-manager' => {
922 description
=> "When set, only a monitor will be created.",
926 returns
=> { type
=> 'string' },
930 PVE
::CephTools
::check_ceph_inited
();
932 PVE
::CephTools
::setup_pve_symlinks
();
934 my $rpcenv = PVE
::RPCEnvironment
::get
();
936 my $authuser = $rpcenv->get_user();
938 my $cfg = PVE
::CephTools
::parse_ceph_config
();
942 my $monaddrhash = {};
944 my $systemd_managed = PVE
::CephTools
::systemd_managed
();
946 foreach my $section (keys %$cfg) {
947 next if $section eq 'global';
948 my $d = $cfg->{$section};
949 if ($section =~ m/^mon\./) {
951 if ($d->{'mon addr'}) {
952 $monaddrhash->{$d->{'mon addr'}} = $section;
957 my $monid = $param->{id
} // $param->{node
};
959 my $monsection = "mon.$monid";
961 if (my $pubnet = $cfg->{global
}->{'public network'}) {
962 $ip = &$find_node_ip($pubnet);
964 $ip = PVE
::Cluster
::remote_node_ip
($param->{node
});
967 my $monaddr = Net
::IP
::ip_is_ipv6
($ip) ?
"[$ip]:6789" : "$ip:6789";
968 my $monname = $param->{node
};
970 die "monitor '$monsection' already exists\n" if $cfg->{$monsection};
971 die "monitor address '$monaddr' already in use by '$monaddrhash->{$monaddr}'\n"
972 if $monaddrhash->{$monaddr};
977 my $pve_ckeyring_path = PVE
::CephTools
::get_config
('pve_ckeyring_path');
979 if (! -f
$pve_ckeyring_path) {
980 run_command
("ceph-authtool $pve_ckeyring_path --create-keyring " .
981 "--gen-key -n client.admin");
984 my $pve_mon_key_path = PVE
::CephTools
::get_config
('pve_mon_key_path');
985 if (! -f
$pve_mon_key_path) {
986 run_command
("cp $pve_ckeyring_path $pve_mon_key_path.tmp");
987 run_command
("ceph-authtool $pve_mon_key_path.tmp -n client.admin --set-uid=0 " .
988 "--cap mds 'allow' " .
989 "--cap osd 'allow *' " .
990 "--cap mgr 'allow *' " .
991 "--cap mon 'allow *'");
992 run_command
("cp $pve_mon_key_path.tmp /etc/ceph/ceph.client.admin.keyring") if $systemd_managed;
993 run_command
("chown ceph:ceph /etc/ceph/ceph.client.admin.keyring") if $systemd_managed;
994 run_command
("ceph-authtool $pve_mon_key_path.tmp --gen-key -n mon. --cap mon 'allow *'");
995 run_command
("mv $pve_mon_key_path.tmp $pve_mon_key_path");
998 my $ccname = PVE
::CephTools
::get_config
('ccname');
1000 my $mondir = "/var/lib/ceph/mon/$ccname-$monid";
1001 -d
$mondir && die "monitor filesystem '$mondir' already exist\n";
1003 my $monmap = "/tmp/monmap";
1008 run_command
("chown ceph:ceph $mondir") if $systemd_managed;
1010 if ($moncount > 0) {
1011 my $rados = PVE
::RADOS-
>new(timeout
=> PVE
::CephTools
::get_config
('long_rados_timeout'));
1012 my $mapdata = $rados->mon_command({ prefix
=> 'mon getmap', format
=> 'plain' });
1013 PVE
::Tools
::file_set_contents
($monmap, $mapdata);
1015 run_command
("monmaptool --create --clobber --add $monid $monaddr --print $monmap");
1018 run_command
("ceph-mon --mkfs -i $monid --monmap $monmap --keyring $pve_mon_key_path");
1019 run_command
("chown ceph:ceph -R $mondir") if $systemd_managed;
1024 File
::Path
::remove_tree
($mondir);
1028 $cfg->{$monsection} = {
1030 'mon addr' => $monaddr,
1033 PVE
::CephTools
::write_ceph_config
($cfg);
1035 my $create_keys_pid = fork();
1036 if (!defined($create_keys_pid)) {
1037 die "Could not spawn ceph-create-keys to create bootstrap keys\n";
1038 } elsif ($create_keys_pid == 0) {
1039 exit PVE
::Tools
::run_command
(['ceph-create-keys', '-i', $monid]);
1041 PVE
::CephTools
::ceph_service_cmd
('start', $monsection);
1043 if ($systemd_managed) {
1044 #to ensure we have the correct startup order.
1045 eval { PVE
::Tools
::run_command
(['/bin/systemctl', 'enable', "ceph-mon\@${monid}.service"]); };
1046 warn "Enable ceph-mon\@${monid}.service manually"if $@;
1048 waitpid($create_keys_pid, 0);
1052 if (!$param->{'exclude-manager'}) {
1053 my $rados = PVE
::RADOS-
>new(timeout
=> PVE
::CephTools
::get_config
('long_rados_timeout'));
1054 $create_mgr->($rados, $monid);
1058 return $rpcenv->fork_worker('cephcreatemon', $monsection, $authuser, $worker);
1061 __PACKAGE__-
>register_method ({
1062 name
=> 'destroymon',
1063 path
=> 'mon/{monid}',
1065 description
=> "Destroy Ceph Monitor and Manager.",
1069 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1072 additionalProperties
=> 0,
1074 node
=> get_standard_option
('pve-node'),
1076 description
=> 'Monitor ID',
1078 pattern
=> '[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?',
1080 'exclude-manager' => {
1084 description
=> "When set, removes only the monitor, not the manager"
1088 returns
=> { type
=> 'string' },
1092 my $rpcenv = PVE
::RPCEnvironment
::get
();
1094 my $authuser = $rpcenv->get_user();
1096 PVE
::CephTools
::check_ceph_inited
();
1098 my $cfg = PVE
::CephTools
::parse_ceph_config
();
1100 my $monid = $param->{monid
};
1101 my $monsection = "mon.$monid";
1103 my $rados = PVE
::RADOS-
>new();
1104 my $monstat = $rados->mon_command({ prefix
=> 'mon_status' });
1105 my $monlist = $monstat->{monmap
}->{mons
};
1107 die "no such monitor id '$monid'\n"
1108 if !defined($cfg->{$monsection});
1110 my $ccname = PVE
::CephTools
::get_config
('ccname');
1112 my $mondir = "/var/lib/ceph/mon/$ccname-$monid";
1113 -d
$mondir || die "monitor filesystem '$mondir' does not exist on this node\n";
1115 die "can't remove last monitor\n" if scalar(@$monlist) <= 1;
1120 # reopen with longer timeout
1121 $rados = PVE
::RADOS-
>new(timeout
=> PVE
::CephTools
::get_config
('long_rados_timeout'));
1123 $rados->mon_command({ prefix
=> "mon remove", name
=> $monid, format
=> 'plain' });
1125 eval { PVE
::CephTools
::ceph_service_cmd
('stop', $monsection); };
1128 delete $cfg->{$monsection};
1129 PVE
::CephTools
::write_ceph_config
($cfg);
1130 File
::Path
::remove_tree
($mondir);
1133 if (!$param->{'exclude-manager'}) {
1134 eval { $destroy_mgr->($monid); };
1139 return $rpcenv->fork_worker('cephdestroymon', $monsection, $authuser, $worker);
1142 __PACKAGE__-
>register_method ({
1143 name
=> 'createmgr',
1146 description
=> "Create Ceph Manager",
1150 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1153 additionalProperties
=> 0,
1155 node
=> get_standard_option
('pve-node'),
1159 pattern
=> '[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?',
1160 description
=> "The ID for the manager, when omitted the same as the nodename",
1164 returns
=> { type
=> 'string' },
1168 PVE
::CephTools
::check_ceph_inited
();
1170 my $rpcenv = PVE
::RPCEnvironment
::get
();
1172 my $authuser = $rpcenv->get_user();
1174 my $mgrid = $param->{id
} // $param->{node
};
1179 my $rados = PVE
::RADOS-
>new(timeout
=> PVE
::CephTools
::get_config
('long_rados_timeout'));
1181 $create_mgr->($rados, $mgrid);
1184 return $rpcenv->fork_worker('cephcreatemgr', "mgr.$mgrid", $authuser, $worker);
1187 __PACKAGE__-
>register_method ({
1188 name
=> 'destroymgr',
1191 description
=> "Destroy Ceph Manager.",
1195 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1198 additionalProperties
=> 0,
1200 node
=> get_standard_option
('pve-node'),
1202 description
=> 'The ID of the manager',
1204 pattern
=> '[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?',
1208 returns
=> { type
=> 'string' },
1212 my $rpcenv = PVE
::RPCEnvironment
::get
();
1214 my $authuser = $rpcenv->get_user();
1216 PVE
::CephTools
::check_ceph_inited
();
1218 my $mgrid = $param->{id
};
1223 $destroy_mgr->($mgrid);
1226 return $rpcenv->fork_worker('cephdestroymgr', "mgr.$mgrid", $authuser, $worker);
1229 __PACKAGE__-
>register_method ({
1233 description
=> "Stop ceph services.",
1237 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1240 additionalProperties
=> 0,
1242 node
=> get_standard_option
('pve-node'),
1244 description
=> 'Ceph service name.',
1247 pattern
=> '(mon|mds|osd|mgr)\.[A-Za-z0-9\-]{1,32}',
1251 returns
=> { type
=> 'string' },
1255 my $rpcenv = PVE
::RPCEnvironment
::get
();
1257 my $authuser = $rpcenv->get_user();
1259 PVE
::CephTools
::check_ceph_inited
();
1261 my $cfg = PVE
::CephTools
::parse_ceph_config
();
1262 scalar(keys %$cfg) || die "no configuration\n";
1268 if ($param->{service
}) {
1269 push @$cmd, $param->{service
};
1272 PVE
::CephTools
::ceph_service_cmd
(@$cmd);
1275 return $rpcenv->fork_worker('srvstop', $param->{service
} || 'ceph',
1276 $authuser, $worker);
1279 __PACKAGE__-
>register_method ({
1283 description
=> "Start ceph services.",
1287 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1290 additionalProperties
=> 0,
1292 node
=> get_standard_option
('pve-node'),
1294 description
=> 'Ceph service name.',
1297 pattern
=> '(mon|mds|osd|mgr)\.[A-Za-z0-9\-]{1,32}',
1301 returns
=> { type
=> 'string' },
1305 my $rpcenv = PVE
::RPCEnvironment
::get
();
1307 my $authuser = $rpcenv->get_user();
1309 PVE
::CephTools
::check_ceph_inited
();
1311 my $cfg = PVE
::CephTools
::parse_ceph_config
();
1312 scalar(keys %$cfg) || die "no configuration\n";
1317 my $cmd = ['start'];
1318 if ($param->{service
}) {
1319 push @$cmd, $param->{service
};
1322 PVE
::CephTools
::ceph_service_cmd
(@$cmd);
1325 return $rpcenv->fork_worker('srvstart', $param->{service
} || 'ceph',
1326 $authuser, $worker);
1329 __PACKAGE__-
>register_method ({
1333 description
=> "Get ceph status.",
1337 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
1340 additionalProperties
=> 0,
1342 node
=> get_standard_option
('pve-node'),
1345 returns
=> { type
=> 'object' },
1349 PVE
::CephTools
::check_ceph_enabled
();
1351 my $rados = PVE
::RADOS-
>new();
1352 my $status = $rados->mon_command({ prefix
=> 'status' });
1353 $status->{health
} = $rados->mon_command({ prefix
=> 'health', detail
=> 'detail' });
1357 __PACKAGE__-
>register_method ({
1361 description
=> "List all pools.",
1365 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
1368 additionalProperties
=> 0,
1370 node
=> get_standard_option
('pve-node'),
1378 pool
=> { type
=> 'integer' },
1379 pool_name
=> { type
=> 'string' },
1380 size
=> { type
=> 'integer' },
1383 links
=> [ { rel
=> 'child', href
=> "{pool_name}" } ],
1388 PVE
::CephTools
::check_ceph_inited
();
1390 my $rados = PVE
::RADOS-
>new();
1393 my $res = $rados->mon_command({ prefix
=> 'df' });
1394 my $total = $res->{stats
}->{total_avail_bytes
} || 0;
1396 foreach my $d (@{$res->{pools
}}) {
1397 next if !$d->{stats
};
1398 next if !defined($d->{id
});
1399 $stats->{$d->{id
}} = $d->{stats
};
1402 $res = $rados->mon_command({ prefix
=> 'osd dump' });
1405 foreach my $e (@{$res->{pools
}}) {
1407 foreach my $attr (qw(pool pool_name size min_size pg_num crush_ruleset)) {
1408 $d->{$attr} = $e->{$attr} if defined($e->{$attr});
1410 if (my $s = $stats->{$d->{pool
}}) {
1411 $d->{bytes_used
} = $s->{bytes_used
};
1412 $d->{percent_used
} = ($s->{bytes_used
} / $total)*100
1413 if $s->{max_avail
} && $total;
1422 __PACKAGE__-
>register_method ({
1423 name
=> 'createpool',
1426 description
=> "Create POOL",
1430 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1433 additionalProperties
=> 0,
1435 node
=> get_standard_option
('pve-node'),
1437 description
=> "The name of the pool. It must be unique.",
1441 description
=> 'Number of replicas per object',
1449 description
=> 'Minimum number of replicas per object',
1457 description
=> "Number of placement groups.",
1465 description
=> "The ruleset to use for mapping object placement in the cluster.",
1474 returns
=> { type
=> 'null' },
1478 PVE
::CephTools
::check_ceph_inited
();
1480 my $pve_ckeyring_path = PVE
::CephTools
::get_config
('pve_ckeyring_path');
1482 die "not fully configured - missing '$pve_ckeyring_path'\n"
1483 if ! -f
$pve_ckeyring_path;
1485 my $pg_num = $param->{pg_num
} || 64;
1486 my $size = $param->{size
} || 2;
1487 my $min_size = $param->{min_size
} || 1;
1488 my $ruleset = $param->{crush_ruleset
} || 0;
1489 my $rados = PVE
::RADOS-
>new();
1491 $rados->mon_command({
1492 prefix
=> "osd pool create",
1493 pool
=> $param->{name
},
1494 pg_num
=> int($pg_num),
1495 # this does not work for unknown reason
1496 # properties => ["size=$size", "min_size=$min_size", "crush_ruleset=$ruleset"],
1500 $rados->mon_command({
1501 prefix
=> "osd pool set",
1502 pool
=> $param->{name
},
1508 $rados->mon_command({
1509 prefix
=> "osd pool set",
1510 pool
=> $param->{name
},
1516 if (defined($param->{crush_ruleset
})) {
1517 $rados->mon_command({
1518 prefix
=> "osd pool set",
1519 pool
=> $param->{name
},
1520 var
=> 'crush_ruleset',
1521 val
=> $param->{crush_ruleset
},
1529 __PACKAGE__-
>register_method ({
1530 name
=> 'get_flags',
1533 description
=> "get all set ceph flags",
1537 check
=> ['perm', '/', [ 'Sys.Audit' ]],
1540 additionalProperties
=> 0,
1542 node
=> get_standard_option
('pve-node'),
1545 returns
=> { type
=> 'string' },
1549 PVE
::CephTools
::check_ceph_inited
();
1551 my $pve_ckeyring_path = PVE
::CephTools
::get_config
('pve_ckeyring_path');
1553 die "not fully configured - missing '$pve_ckeyring_path'\n"
1554 if ! -f
$pve_ckeyring_path;
1556 my $rados = PVE
::RADOS-
>new();
1558 my $stat = $rados->mon_command({ prefix
=> 'osd dump' });
1560 return $stat->{flags
} // '';
1563 __PACKAGE__-
>register_method ({
1565 path
=> 'flags/{flag}',
1567 description
=> "Set a ceph flag",
1571 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1574 additionalProperties
=> 0,
1576 node
=> get_standard_option
('pve-node'),
1578 description
=> 'The ceph flag to set/unset',
1580 enum
=> [ 'full', 'pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill', 'norebalance', 'norecover', 'noscrub', 'nodeep-scrub', 'notieragent'],
1584 returns
=> { type
=> 'null' },
1588 PVE
::CephTools
::check_ceph_inited
();
1590 my $pve_ckeyring_path = PVE
::CephTools
::get_config
('pve_ckeyring_path');
1592 die "not fully configured - missing '$pve_ckeyring_path'\n"
1593 if ! -f
$pve_ckeyring_path;
1595 my $set = $param->{set
} // !$param->{unset
};
1596 my $rados = PVE
::RADOS-
>new();
1598 $rados->mon_command({
1599 prefix
=> "osd set",
1600 key
=> $param->{flag
},
1606 __PACKAGE__-
>register_method ({
1607 name
=> 'unset_flag',
1608 path
=> 'flags/{flag}',
1610 description
=> "Unset a ceph flag",
1614 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1617 additionalProperties
=> 0,
1619 node
=> get_standard_option
('pve-node'),
1621 description
=> 'The ceph flag to set/unset',
1623 enum
=> [ 'full', 'pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill', 'norebalance', 'norecover', 'noscrub', 'nodeep-scrub', 'notieragent'],
1627 returns
=> { type
=> 'null' },
1631 PVE
::CephTools
::check_ceph_inited
();
1633 my $pve_ckeyring_path = PVE
::CephTools
::get_config
('pve_ckeyring_path');
1635 die "not fully configured - missing '$pve_ckeyring_path'\n"
1636 if ! -f
$pve_ckeyring_path;
1638 my $set = $param->{set
} // !$param->{unset
};
1639 my $rados = PVE
::RADOS-
>new();
1641 $rados->mon_command({
1642 prefix
=> "osd unset",
1643 key
=> $param->{flag
},
1649 __PACKAGE__-
>register_method ({
1650 name
=> 'destroypool',
1651 path
=> 'pools/{name}',
1653 description
=> "Destroy pool",
1657 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1660 additionalProperties
=> 0,
1662 node
=> get_standard_option
('pve-node'),
1664 description
=> "The name of the pool. It must be unique.",
1668 description
=> "If true, destroys pool even if in use",
1675 returns
=> { type
=> 'null' },
1679 PVE
::CephTools
::check_ceph_inited
();
1681 # if not forced, destroy ceph pool only when no
1682 # vm disks are on it anymore
1683 if (!$param->{force
}) {
1684 my $storagecfg = PVE
::Storage
::config
();
1685 foreach my $storageid (keys %{$storagecfg->{ids
}}) {
1686 my $storage = $storagecfg->{ids
}->{$storageid};
1687 next if $storage->{type
} ne 'rbd';
1688 next if $storage->{pool
} ne $param->{name
};
1690 # check if any vm disks are on the pool
1691 my $res = PVE
::Storage
::vdisk_list
($storagecfg, $storageid);
1692 die "ceph pool '$param->{name}' still in use by storage '$storageid'\n"
1693 if @{$res->{$storageid}} != 0;
1697 my $rados = PVE
::RADOS-
>new();
1698 # fixme: '--yes-i-really-really-mean-it'
1699 $rados->mon_command({
1700 prefix
=> "osd pool delete",
1701 pool
=> $param->{name
},
1702 pool2
=> $param->{name
},
1703 sure
=> '--yes-i-really-really-mean-it',
1711 __PACKAGE__-
>register_method ({
1715 description
=> "Get OSD crush map",
1719 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
1722 additionalProperties
=> 0,
1724 node
=> get_standard_option
('pve-node'),
1727 returns
=> { type
=> 'string' },
1731 PVE
::CephTools
::check_ceph_inited
();
1733 # this produces JSON (difficult to read for the user)
1734 # my $txt = &$run_ceph_cmd_text(['osd', 'crush', 'dump'], quiet => 1);
1738 my $mapfile = "/var/tmp/ceph-crush.map.$$";
1739 my $mapdata = "/var/tmp/ceph-crush.txt.$$";
1741 my $rados = PVE
::RADOS-
>new();
1744 my $bindata = $rados->mon_command({ prefix
=> 'osd getcrushmap', format
=> 'plain' });
1745 PVE
::Tools
::file_set_contents
($mapfile, $bindata);
1746 run_command
(['crushtool', '-d', $mapfile, '-o', $mapdata]);
1747 $txt = PVE
::Tools
::file_get_contents
($mapdata);
1759 __PACKAGE__-
>register_method({
1763 description
=> "Read ceph log",
1766 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
1770 additionalProperties
=> 0,
1772 node
=> get_standard_option
('pve-node'),
1791 description
=> "Line number",
1795 description
=> "Line text",
1804 my $rpcenv = PVE
::RPCEnvironment
::get
();
1805 my $user = $rpcenv->get_user();
1806 my $node = $param->{node
};
1808 my $logfile = "/var/log/ceph/ceph.log";
1809 my ($count, $lines) = PVE
::Tools
::dump_logfile
($logfile, $param->{start
}, $param->{limit
});
1811 $rpcenv->set_result_attrib('total', $count);