]>
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);
95 my $osdmetadata_tmp = $rados->mon_command({ prefix
=> 'osd metadata' });
98 foreach my $osd (@$osdmetadata_tmp) {
99 $osdmetadata->{$osd->{id
}} = $osd;
104 foreach my $e (@{$res->{nodes
}}) {
105 $nodes->{$e->{id
}} = $e;
113 foreach my $opt (qw(status crush_weight reweight device_class)) {
114 $new->{$opt} = $e->{$opt} if defined($e->{$opt});
117 if (my $stat = $osdhash->{$e->{id
}}) {
118 $new->{in} = $stat->{in} if defined($stat->{in});
121 if (my $stat = $usagehash->{$e->{id
}}) {
122 $new->{total_space
} = ($stat->{kb
} || 1) * 1024;
123 $new->{bytes_used
} = ($stat->{kb_used
} || 0) * 1024;
124 $new->{percent_used
} = ($new->{bytes_used
}*100)/$new->{total_space
};
125 if (my $d = $stat->{fs_perf_stat
}) {
126 $new->{commit_latency_ms
} = $d->{commit_latency_ms
};
127 $new->{apply_latency_ms
} = $d->{apply_latency_ms
};
131 my $osdmd = $osdmetadata->{$e->{id
}};
132 if ($e->{type
} eq 'osd' && $osdmd) {
133 if ($osdmd->{bluefs
}) {
134 $new->{osdtype
} = 'bluestore';
135 $new->{blfsdev
} = $osdmd->{bluestore_bdev_dev_node
};
136 $new->{dbdev
} = $osdmd->{bluefs_db_dev_node
};
137 $new->{waldev
} = $osdmd->{bluefs_wal_dev_node
};
139 $new->{osdtype
} = 'filestore';
143 $newnodes->{$e->{id
}} = $new;
146 foreach my $e (@{$res->{nodes
}}) {
147 my $new = $newnodes->{$e->{id
}};
148 if ($e->{children
} && scalar(@{$e->{children
}})) {
149 $new->{children
} = [];
151 foreach my $cid (@{$e->{children
}}) {
152 $nodes->{$cid}->{parent
} = $e->{id
};
153 if ($nodes->{$cid}->{type
} eq 'osd' &&
154 $e->{type
} eq 'host') {
155 $newnodes->{$cid}->{host
} = $e->{name
};
157 push @{$new->{children
}}, $newnodes->{$cid};
160 $new->{leaf
} = ($e->{id
} >= 0) ?
1 : 0;
165 foreach my $e (@{$res->{nodes
}}) {
166 if (!$nodes->{$e->{id
}}->{parent
}) {
167 push @$roots, $newnodes->{$e->{id
}};
171 die "no root node\n" if !@$roots;
173 my $data = { root
=> { leaf
=> 0, children
=> $roots } };
175 # we want this for the noout flag
176 $data->{flags
} = $flags if $flags;
181 __PACKAGE__-
>register_method ({
185 description
=> "Create OSD",
189 additionalProperties
=> 0,
191 node
=> get_standard_option
('pve-node'),
193 description
=> "Block device name.",
197 description
=> "Block device name for journal (filestore) or block.db (bluestore).",
202 description
=> "Block device name for block.wal (bluestore only).",
207 description
=> "File system type (filestore only).",
209 enum
=> ['xfs', 'ext4', 'btrfs'],
214 description
=> "Use bluestore instead of filestore.",
221 returns
=> { type
=> 'string' },
225 my $rpcenv = PVE
::RPCEnvironment
::get
();
227 my $authuser = $rpcenv->get_user();
229 raise_param_exc
({ 'bluestore' => "conflicts with parameter 'fstype'" })
230 if (defined($param->{fstype
}) && defined($param->{bluestore
}) && $param->{bluestore
});
232 PVE
::CephTools
::check_ceph_inited
();
234 PVE
::CephTools
::setup_pve_symlinks
();
236 my $bluestore = $param->{bluestore
} // 0;
241 if ($param->{journal_dev
} && ($param->{journal_dev
} ne $param->{dev
})) {
242 $journal_dev = PVE
::Diskmanage
::verify_blockdev_path
($param->{journal_dev
});
243 # if only journal is given, also put the wal there
244 $wal_dev = $journal_dev;
247 if ($param->{wal_dev
} &&
248 ($param->{wal_dev
} ne $param->{dev
}) &&
249 (!$param->{journal_dev
} || $param->{wal_dev
} ne $param->{journal_dev
})) {
250 raise_param_exc
({ 'wal_dev' => "can only be set with paramater 'bluestore'"})
252 $wal_dev = PVE
::Diskmanage
::verify_blockdev_path
($param->{wal_dev
});
255 $param->{dev
} = PVE
::Diskmanage
::verify_blockdev_path
($param->{dev
});
257 my $devname = $param->{dev
};
258 $devname =~ s
|/dev/||;
260 my $disklist = PVE
::Diskmanage
::get_disks
($devname, 1);
262 my $diskinfo = $disklist->{$devname};
263 die "unable to get device info for '$devname'\n"
266 die "device '$param->{dev}' is in use\n"
267 if $diskinfo->{used
};
269 my $devpath = $diskinfo->{devpath
};
270 my $rados = PVE
::RADOS-
>new();
271 my $monstat = $rados->mon_command({ prefix
=> 'mon_status' });
272 die "unable to get fsid\n" if !$monstat->{monmap
} || !$monstat->{monmap
}->{fsid
};
274 my $fsid = $monstat->{monmap
}->{fsid
};
275 $fsid = $1 if $fsid =~ m/^([0-9a-f\-]+)$/;
277 my $ceph_bootstrap_osd_keyring = PVE
::CephTools
::get_config
('ceph_bootstrap_osd_keyring');
279 if (! -f
$ceph_bootstrap_osd_keyring) {
280 my $bindata = $rados->mon_command({ prefix
=> 'auth get', entity
=> 'client.bootstrap-osd', format
=> 'plain' });
281 PVE
::Tools
::file_set_contents
($ceph_bootstrap_osd_keyring, $bindata);
287 my $fstype = $param->{fstype
} || 'xfs';
290 my $ccname = PVE
::CephTools
::get_config
('ccname');
292 my $cmd = ['ceph-disk', 'prepare', '--zap-disk',
293 '--cluster', $ccname, '--cluster-uuid', $fsid ];
296 print "create OSD on $devpath (bluestore)\n";
297 push @$cmd, '--bluestore';
300 print "using device '$journal_dev' for block.db\n";
301 push @$cmd, '--block.db', $journal_dev;
305 print "using device '$wal_dev' for block.wal\n";
306 push @$cmd, '--block.wal', $wal_dev;
309 push @$cmd, $devpath;
311 print "create OSD on $devpath ($fstype)\n";
312 push @$cmd, '--filestore', '--fs-type', $fstype;
314 print "using device '$journal_dev' for journal\n";
315 push @$cmd, '--journal-dev', $devpath, $journal_dev;
317 push @$cmd, $devpath;
325 return $rpcenv->fork_worker('cephcreateosd', $devname, $authuser, $worker);
328 __PACKAGE__-
>register_method ({
329 name
=> 'destroyosd',
332 description
=> "Destroy OSD",
336 additionalProperties
=> 0,
338 node
=> get_standard_option
('pve-node'),
340 description
=> 'OSD ID',
344 description
=> "If set, we remove partition table entries.",
351 returns
=> { type
=> 'string' },
355 my $rpcenv = PVE
::RPCEnvironment
::get
();
357 my $authuser = $rpcenv->get_user();
359 PVE
::CephTools
::check_ceph_inited
();
361 my $osdid = $param->{osdid
};
363 my $rados = PVE
::RADOS-
>new();
364 my $osdstat = &$get_osd_status($rados, $osdid);
366 die "osd is in use (in == 1)\n" if $osdstat->{in};
367 #&$run_ceph_cmd(['osd', 'out', $osdid]);
369 die "osd is still runnung (up == 1)\n" if $osdstat->{up
};
371 my $osdsection = "osd.$osdid";
376 # reopen with longer timeout
377 $rados = PVE
::RADOS-
>new(timeout
=> PVE
::CephTools
::get_config
('long_rados_timeout'));
379 print "destroy OSD $osdsection\n";
381 eval { PVE
::CephTools
::ceph_service_cmd
('stop', $osdsection); };
384 print "Remove $osdsection from the CRUSH map\n";
385 $rados->mon_command({ prefix
=> "osd crush remove", name
=> $osdsection, format
=> 'plain' });
387 print "Remove the $osdsection authentication key.\n";
388 $rados->mon_command({ prefix
=> "auth del", entity
=> $osdsection, format
=> 'plain' });
390 print "Remove OSD $osdsection\n";
391 $rados->mon_command({ prefix
=> "osd rm", ids
=> [ $osdsection ], format
=> 'plain' });
393 # try to unmount from standard mount point
394 my $mountpoint = "/var/lib/ceph/osd/ceph-$osdid";
396 my $remove_partition = sub {
399 return if !$part || (! -b
$part );
400 my $partnum = PVE
::Diskmanage
::get_partnum
($part);
401 my $devpath = PVE
::Diskmanage
::get_blockdev
($part);
403 print "remove partition $part (disk '${devpath}', partnum $partnum)\n";
404 eval { run_command
(['/sbin/sgdisk', '-d', $partnum, "${devpath}"]); };
408 my $partitions_to_remove = [];
410 if ($param->{cleanup
}) {
411 if (my $fd = IO
::File-
>new("/proc/mounts", "r")) {
412 while (defined(my $line = <$fd>)) {
413 my ($dev, $path, $fstype) = split(/\s+/, $line);
414 next if !($dev && $path && $fstype);
415 next if $dev !~ m
|^/dev/|;
416 if ($path eq $mountpoint) {
417 my $data_part = abs_path
($dev);
418 push @$partitions_to_remove, $data_part;
425 foreach my $path (qw(journal block block.db block.wal)) {
426 my $part = abs_path
("$mountpoint/$path");
428 push @$partitions_to_remove, $part;
433 print "Unmount OSD $osdsection from $mountpoint\n";
434 eval { run_command
(['/bin/umount', $mountpoint]); };
437 } elsif ($param->{cleanup
}) {
438 #be aware of the ceph udev rules which can remount.
439 foreach my $part (@$partitions_to_remove) {
440 $remove_partition->($part);
445 return $rpcenv->fork_worker('cephdestroyosd', $osdsection, $authuser, $worker);
448 __PACKAGE__-
>register_method ({
450 path
=> '{osdid}/in',
452 description
=> "ceph osd in",
456 check
=> ['perm', '/', [ 'Sys.Modify' ]],
459 additionalProperties
=> 0,
461 node
=> get_standard_option
('pve-node'),
463 description
=> 'OSD ID',
468 returns
=> { type
=> "null" },
472 PVE
::CephTools
::check_ceph_inited
();
474 my $osdid = $param->{osdid
};
476 my $rados = PVE
::RADOS-
>new();
478 my $osdstat = &$get_osd_status($rados, $osdid); # osd exists?
480 my $osdsection = "osd.$osdid";
482 $rados->mon_command({ prefix
=> "osd in", ids
=> [ $osdsection ], format
=> 'plain' });
487 __PACKAGE__-
>register_method ({
489 path
=> '{osdid}/out',
491 description
=> "ceph osd out",
495 check
=> ['perm', '/', [ 'Sys.Modify' ]],
498 additionalProperties
=> 0,
500 node
=> get_standard_option
('pve-node'),
502 description
=> 'OSD ID',
507 returns
=> { type
=> "null" },
511 PVE
::CephTools
::check_ceph_inited
();
513 my $osdid = $param->{osdid
};
515 my $rados = PVE
::RADOS-
>new();
517 my $osdstat = &$get_osd_status($rados, $osdid); # osd exists?
519 my $osdsection = "osd.$osdid";
521 $rados->mon_command({ prefix
=> "osd out", ids
=> [ $osdsection ], format
=> 'plain' });
526 package PVE
::API2
::Ceph
;
532 use POSIX qw
(LONG_MAX
);
533 use Cwd
qw(abs_path);
539 use PVE
::Tools
qw(extract_param run_command file_get_contents file_read_firstline dir_glob_regex dir_glob_foreach);
540 use PVE
::Exception
qw(raise raise_param_exc);
542 use PVE
::Cluster
qw(cfs_lock_file cfs_read_file cfs_write_file);
543 use PVE
::AccessControl
;
545 use PVE
::RESTHandler
;
546 use PVE
::RPCEnvironment
;
547 use PVE
::JSONSchema
qw(get_standard_option);
552 use base
qw(PVE::RESTHandler);
554 use Data
::Dumper
; # fixme: remove
556 my $pve_osd_default_journal_size = 1024*5;
558 __PACKAGE__-
>register_method ({
559 subclass
=> "PVE::API2::CephOSD",
563 __PACKAGE__-
>register_method ({
567 description
=> "Directory index.",
568 permissions
=> { user
=> 'all' },
570 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
573 additionalProperties
=> 0,
575 node
=> get_standard_option
('pve-node'),
584 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
596 { name
=> 'status' },
598 { name
=> 'config' },
608 __PACKAGE__-
>register_method ({
612 description
=> "List local disks.",
616 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
619 additionalProperties
=> 0,
621 node
=> get_standard_option
('pve-node'),
623 description
=> "Only list specific types of disks.",
625 enum
=> ['unused', 'journal_disks'],
635 dev
=> { type
=> 'string' },
636 used
=> { type
=> 'string', optional
=> 1 },
637 gpt
=> { type
=> 'boolean' },
638 size
=> { type
=> 'integer' },
639 osdid
=> { type
=> 'integer' },
640 vendor
=> { type
=> 'string', optional
=> 1 },
641 model
=> { type
=> 'string', optional
=> 1 },
642 serial
=> { type
=> 'string', optional
=> 1 },
645 # links => [ { rel => 'child', href => "{}" } ],
650 PVE
::CephTools
::check_ceph_inited
();
652 my $disks = PVE
::Diskmanage
::get_disks
(undef, 1);
655 foreach my $dev (keys %$disks) {
656 my $d = $disks->{$dev};
657 if ($param->{type
}) {
658 if ($param->{type
} eq 'journal_disks') {
659 next if $d->{osdid
} >= 0;
661 } elsif ($param->{type
} eq 'unused') {
664 die "internal error"; # should not happen
668 $d->{dev
} = "/dev/$dev";
675 __PACKAGE__-
>register_method ({
680 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
682 description
=> "Get Ceph configuration.",
684 additionalProperties
=> 0,
686 node
=> get_standard_option
('pve-node'),
689 returns
=> { type
=> 'string' },
693 PVE
::CephTools
::check_ceph_inited
();
695 my $path = PVE
::CephTools
::get_config
('pve_ceph_cfgpath');
696 return PVE
::Tools
::file_get_contents
($path);
700 __PACKAGE__-
>register_method ({
704 description
=> "Get Ceph monitor list.",
708 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
711 additionalProperties
=> 0,
713 node
=> get_standard_option
('pve-node'),
721 name
=> { type
=> 'string' },
722 addr
=> { type
=> 'string' },
725 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
730 PVE
::CephTools
::check_ceph_inited
();
734 my $cfg = PVE
::CephTools
::parse_ceph_config
();
737 foreach my $section (keys %$cfg) {
738 my $d = $cfg->{$section};
739 if ($section =~ m/^mon\.(\S+)$/) {
741 if ($d->{'mon addr'} && $d->{'host'}) {
742 $monhash->{$monid} = {
743 addr
=> $d->{'mon addr'},
744 host
=> $d->{'host'},
752 my $rados = PVE
::RADOS-
>new();
753 my $monstat = $rados->mon_command({ prefix
=> 'mon_status' });
754 my $mons = $monstat->{monmap
}->{mons
};
755 foreach my $d (@$mons) {
756 next if !defined($d->{name
});
757 $monhash->{$d->{name
}}->{rank
} = $d->{rank
};
758 $monhash->{$d->{name
}}->{addr
} = $d->{addr
};
759 if (grep { $_ eq $d->{rank
} } @{$monstat->{quorum
}}) {
760 $monhash->{$d->{name
}}->{quorum
} = 1;
766 return PVE
::RESTHandler
::hash_to_array
($monhash, 'name');
769 __PACKAGE__-
>register_method ({
773 description
=> "Create initial ceph default configuration and setup symlinks.",
777 check
=> ['perm', '/', [ 'Sys.Modify' ]],
780 additionalProperties
=> 0,
782 node
=> get_standard_option
('pve-node'),
784 description
=> "Use specific network for all ceph related traffic",
785 type
=> 'string', format
=> 'CIDR',
790 description
=> 'Targeted number of replicas per object',
798 description
=> 'Minimum number of available replicas per object to allow I/O',
806 description
=> "Placement group bits, used to specify the " .
807 "default number of placement groups.\n\nNOTE: 'osd pool " .
808 "default pg num' does not work for default pools.",
816 description
=> "Disable cephx authentification.\n\n" .
817 "WARNING: cephx is a security feature protecting against " .
818 "man-in-the-middle attacks. Only consider disabling cephx ".
819 "if your network is private!",
826 returns
=> { type
=> 'null' },
830 PVE
::CephTools
::check_ceph_installed
();
832 # simply load old config if it already exists
833 my $cfg = PVE
::CephTools
::parse_ceph_config
();
835 if (!$cfg->{global
}) {
840 UUID
::generate
($uuid);
841 UUID
::unparse
($uuid, $fsid);
843 my $auth = $param->{disable_cephx
} ?
'none' : 'cephx';
847 'auth cluster required' => $auth,
848 'auth service required' => $auth,
849 'auth client required' => $auth,
850 'osd journal size' => $pve_osd_default_journal_size,
851 'osd pool default size' => $param->{size
} // 3,
852 'osd pool default min size' => $param->{min_size
} // 2,
853 'mon allow pool delete' => 'true',
856 # this does not work for default pools
857 #'osd pool default pg num' => $pg_num,
858 #'osd pool default pgp num' => $pg_num,
861 $cfg->{global
}->{keyring
} = '/etc/pve/priv/$cluster.$name.keyring';
862 $cfg->{osd
}->{keyring
} = '/var/lib/ceph/osd/ceph-$id/keyring';
864 if ($param->{pg_bits
}) {
865 $cfg->{global
}->{'osd pg bits'} = $param->{pg_bits
};
866 $cfg->{global
}->{'osd pgp bits'} = $param->{pg_bits
};
869 if ($param->{network
}) {
870 $cfg->{global
}->{'public network'} = $param->{network
};
871 $cfg->{global
}->{'cluster network'} = $param->{network
};
874 PVE
::CephTools
::write_ceph_config
($cfg);
876 PVE
::CephTools
::setup_pve_symlinks
();
881 my $find_node_ip = sub {
884 my $net = Net
::IP-
>new($cidr) || die Net
::IP
::Error
() . "\n";
885 my $id = $net->version == 6 ?
'address6' : 'address';
887 my $config = PVE
::INotify
::read_file
('interfaces');
888 my $ifaces = $config->{ifaces
};
890 foreach my $iface (keys %$ifaces) {
891 my $d = $ifaces->{$iface};
893 my $a = Net
::IP-
>new($d->{$id});
895 return $d->{$id} if $net->overlaps($a);
898 die "unable to find local address within network '$cidr'\n";
901 my $create_mgr = sub {
902 my ($rados, $id) = @_;
904 my $clustername = PVE
::CephTools
::get_config
('ccname');
905 my $mgrdir = "/var/lib/ceph/mgr/$clustername-$id";
906 my $mgrkeyring = "$mgrdir/keyring";
907 my $mgrname = "mgr.$id";
909 die "ceph manager directory '$mgrdir' already exists\n"
912 print "creating manager directory '$mgrdir'\n";
914 print "creating keys for '$mgrname'\n";
915 my $output = $rados->mon_command({ prefix
=> 'auth get-or-create',
918 mon
=> 'allow profile mgr',
923 PVE
::Tools
::file_set_contents
($mgrkeyring, $output);
925 print "setting owner for directory\n";
926 run_command
(["chown", 'ceph:ceph', '-R', $mgrdir]);
928 print "enabling service 'ceph-mgr\@$id.service'\n";
929 PVE
::CephTools
::ceph_service_cmd
('enable', $mgrname);
930 print "starting service 'ceph-mgr\@$id.service'\n";
931 PVE
::CephTools
::ceph_service_cmd
('start', $mgrname);
934 my $destroy_mgr = sub {
937 my $clustername = PVE
::CephTools
::get_config
('ccname');
938 my $mgrname = "mgr.$mgrid";
939 my $mgrdir = "/var/lib/ceph/mgr/$clustername-$mgrid";
941 die "ceph manager directory '$mgrdir' not found\n"
944 print "disabling service 'ceph-mgr\@$mgrid.service'\n";
945 PVE
::CephTools
::ceph_service_cmd
('disable', $mgrname);
946 print "stopping service 'ceph-mgr\@$mgrid.service'\n";
947 PVE
::CephTools
::ceph_service_cmd
('stop', $mgrname);
949 print "removing manager directory '$mgrdir'\n";
950 File
::Path
::remove_tree
($mgrdir);
953 __PACKAGE__-
>register_method ({
957 description
=> "Create Ceph Monitor and Manager",
961 check
=> ['perm', '/', [ 'Sys.Modify' ]],
964 additionalProperties
=> 0,
966 node
=> get_standard_option
('pve-node'),
970 pattern
=> '[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?',
971 description
=> "The ID for the monitor, when omitted the same as the nodename",
973 'exclude-manager' => {
977 description
=> "When set, only a monitor will be created.",
981 returns
=> { type
=> 'string' },
985 PVE
::CephTools
::check_ceph_inited
();
987 PVE
::CephTools
::setup_pve_symlinks
();
989 my $rpcenv = PVE
::RPCEnvironment
::get
();
991 my $authuser = $rpcenv->get_user();
993 my $cfg = PVE
::CephTools
::parse_ceph_config
();
997 my $monaddrhash = {};
999 my $systemd_managed = PVE
::CephTools
::systemd_managed
();
1001 foreach my $section (keys %$cfg) {
1002 next if $section eq 'global';
1003 my $d = $cfg->{$section};
1004 if ($section =~ m/^mon\./) {
1006 if ($d->{'mon addr'}) {
1007 $monaddrhash->{$d->{'mon addr'}} = $section;
1012 my $monid = $param->{id
} // $param->{node
};
1014 my $monsection = "mon.$monid";
1016 if (my $pubnet = $cfg->{global
}->{'public network'}) {
1017 $ip = &$find_node_ip($pubnet);
1019 $ip = PVE
::Cluster
::remote_node_ip
($param->{node
});
1022 my $monaddr = Net
::IP
::ip_is_ipv6
($ip) ?
"[$ip]:6789" : "$ip:6789";
1023 my $monname = $param->{node
};
1025 die "monitor '$monsection' already exists\n" if $cfg->{$monsection};
1026 die "monitor address '$monaddr' already in use by '$monaddrhash->{$monaddr}'\n"
1027 if $monaddrhash->{$monaddr};
1032 my $pve_ckeyring_path = PVE
::CephTools
::get_config
('pve_ckeyring_path');
1034 if (! -f
$pve_ckeyring_path) {
1035 run_command
("ceph-authtool $pve_ckeyring_path --create-keyring " .
1036 "--gen-key -n client.admin");
1039 my $pve_mon_key_path = PVE
::CephTools
::get_config
('pve_mon_key_path');
1040 if (! -f
$pve_mon_key_path) {
1041 run_command
("cp $pve_ckeyring_path $pve_mon_key_path.tmp");
1042 run_command
("ceph-authtool $pve_mon_key_path.tmp -n client.admin --set-uid=0 " .
1043 "--cap mds 'allow' " .
1044 "--cap osd 'allow *' " .
1045 "--cap mgr 'allow *' " .
1046 "--cap mon 'allow *'");
1047 run_command
("cp $pve_mon_key_path.tmp /etc/ceph/ceph.client.admin.keyring") if $systemd_managed;
1048 run_command
("chown ceph:ceph /etc/ceph/ceph.client.admin.keyring") if $systemd_managed;
1049 run_command
("ceph-authtool $pve_mon_key_path.tmp --gen-key -n mon. --cap mon 'allow *'");
1050 run_command
("mv $pve_mon_key_path.tmp $pve_mon_key_path");
1053 my $ccname = PVE
::CephTools
::get_config
('ccname');
1055 my $mondir = "/var/lib/ceph/mon/$ccname-$monid";
1056 -d
$mondir && die "monitor filesystem '$mondir' already exist\n";
1058 my $monmap = "/tmp/monmap";
1063 run_command
("chown ceph:ceph $mondir") if $systemd_managed;
1065 if ($moncount > 0) {
1066 my $rados = PVE
::RADOS-
>new(timeout
=> PVE
::CephTools
::get_config
('long_rados_timeout'));
1067 my $mapdata = $rados->mon_command({ prefix
=> 'mon getmap', format
=> 'plain' });
1068 PVE
::Tools
::file_set_contents
($monmap, $mapdata);
1070 run_command
("monmaptool --create --clobber --add $monid $monaddr --print $monmap");
1073 run_command
("ceph-mon --mkfs -i $monid --monmap $monmap --keyring $pve_mon_key_path");
1074 run_command
("chown ceph:ceph -R $mondir") if $systemd_managed;
1079 File
::Path
::remove_tree
($mondir);
1083 $cfg->{$monsection} = {
1085 'mon addr' => $monaddr,
1088 PVE
::CephTools
::write_ceph_config
($cfg);
1090 my $create_keys_pid = fork();
1091 if (!defined($create_keys_pid)) {
1092 die "Could not spawn ceph-create-keys to create bootstrap keys\n";
1093 } elsif ($create_keys_pid == 0) {
1094 exit PVE
::Tools
::run_command
(['ceph-create-keys', '-i', $monid]);
1096 PVE
::CephTools
::ceph_service_cmd
('start', $monsection);
1098 if ($systemd_managed) {
1099 #to ensure we have the correct startup order.
1100 eval { PVE
::Tools
::run_command
(['/bin/systemctl', 'enable', "ceph-mon\@${monid}.service"]); };
1101 warn "Enable ceph-mon\@${monid}.service manually"if $@;
1103 waitpid($create_keys_pid, 0);
1107 if (!$param->{'exclude-manager'}) {
1108 my $rados = PVE
::RADOS-
>new(timeout
=> PVE
::CephTools
::get_config
('long_rados_timeout'));
1109 $create_mgr->($rados, $monid);
1113 return $rpcenv->fork_worker('cephcreatemon', $monsection, $authuser, $worker);
1116 __PACKAGE__-
>register_method ({
1117 name
=> 'destroymon',
1118 path
=> 'mon/{monid}',
1120 description
=> "Destroy Ceph Monitor and Manager.",
1124 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1127 additionalProperties
=> 0,
1129 node
=> get_standard_option
('pve-node'),
1131 description
=> 'Monitor ID',
1133 pattern
=> '[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?',
1135 'exclude-manager' => {
1139 description
=> "When set, removes only the monitor, not the manager"
1143 returns
=> { type
=> 'string' },
1147 my $rpcenv = PVE
::RPCEnvironment
::get
();
1149 my $authuser = $rpcenv->get_user();
1151 PVE
::CephTools
::check_ceph_inited
();
1153 my $cfg = PVE
::CephTools
::parse_ceph_config
();
1155 my $monid = $param->{monid
};
1156 my $monsection = "mon.$monid";
1158 my $rados = PVE
::RADOS-
>new();
1159 my $monstat = $rados->mon_command({ prefix
=> 'mon_status' });
1160 my $monlist = $monstat->{monmap
}->{mons
};
1162 die "no such monitor id '$monid'\n"
1163 if !defined($cfg->{$monsection});
1165 my $ccname = PVE
::CephTools
::get_config
('ccname');
1167 my $mondir = "/var/lib/ceph/mon/$ccname-$monid";
1168 -d
$mondir || die "monitor filesystem '$mondir' does not exist on this node\n";
1170 die "can't remove last monitor\n" if scalar(@$monlist) <= 1;
1175 # reopen with longer timeout
1176 $rados = PVE
::RADOS-
>new(timeout
=> PVE
::CephTools
::get_config
('long_rados_timeout'));
1178 $rados->mon_command({ prefix
=> "mon remove", name
=> $monid, format
=> 'plain' });
1180 eval { PVE
::CephTools
::ceph_service_cmd
('stop', $monsection); };
1183 delete $cfg->{$monsection};
1184 PVE
::CephTools
::write_ceph_config
($cfg);
1185 File
::Path
::remove_tree
($mondir);
1188 if (!$param->{'exclude-manager'}) {
1189 eval { $destroy_mgr->($monid); };
1194 return $rpcenv->fork_worker('cephdestroymon', $monsection, $authuser, $worker);
1197 __PACKAGE__-
>register_method ({
1198 name
=> 'createmgr',
1201 description
=> "Create Ceph Manager",
1205 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1208 additionalProperties
=> 0,
1210 node
=> get_standard_option
('pve-node'),
1214 pattern
=> '[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?',
1215 description
=> "The ID for the manager, when omitted the same as the nodename",
1219 returns
=> { type
=> 'string' },
1223 PVE
::CephTools
::check_ceph_inited
();
1225 my $rpcenv = PVE
::RPCEnvironment
::get
();
1227 my $authuser = $rpcenv->get_user();
1229 my $mgrid = $param->{id
} // $param->{node
};
1234 my $rados = PVE
::RADOS-
>new(timeout
=> PVE
::CephTools
::get_config
('long_rados_timeout'));
1236 $create_mgr->($rados, $mgrid);
1239 return $rpcenv->fork_worker('cephcreatemgr', "mgr.$mgrid", $authuser, $worker);
1242 __PACKAGE__-
>register_method ({
1243 name
=> 'destroymgr',
1246 description
=> "Destroy Ceph Manager.",
1250 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1253 additionalProperties
=> 0,
1255 node
=> get_standard_option
('pve-node'),
1257 description
=> 'The ID of the manager',
1259 pattern
=> '[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?',
1263 returns
=> { type
=> 'string' },
1267 my $rpcenv = PVE
::RPCEnvironment
::get
();
1269 my $authuser = $rpcenv->get_user();
1271 PVE
::CephTools
::check_ceph_inited
();
1273 my $mgrid = $param->{id
};
1278 $destroy_mgr->($mgrid);
1281 return $rpcenv->fork_worker('cephdestroymgr', "mgr.$mgrid", $authuser, $worker);
1284 __PACKAGE__-
>register_method ({
1288 description
=> "Stop ceph services.",
1292 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1295 additionalProperties
=> 0,
1297 node
=> get_standard_option
('pve-node'),
1299 description
=> 'Ceph service name.',
1302 pattern
=> '(mon|mds|osd|mgr)\.[A-Za-z0-9\-]{1,32}',
1306 returns
=> { type
=> 'string' },
1310 my $rpcenv = PVE
::RPCEnvironment
::get
();
1312 my $authuser = $rpcenv->get_user();
1314 PVE
::CephTools
::check_ceph_inited
();
1316 my $cfg = PVE
::CephTools
::parse_ceph_config
();
1317 scalar(keys %$cfg) || die "no configuration\n";
1323 if ($param->{service
}) {
1324 push @$cmd, $param->{service
};
1327 PVE
::CephTools
::ceph_service_cmd
(@$cmd);
1330 return $rpcenv->fork_worker('srvstop', $param->{service
} || 'ceph',
1331 $authuser, $worker);
1334 __PACKAGE__-
>register_method ({
1338 description
=> "Start ceph services.",
1342 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1345 additionalProperties
=> 0,
1347 node
=> get_standard_option
('pve-node'),
1349 description
=> 'Ceph service name.',
1352 pattern
=> '(mon|mds|osd|mgr)\.[A-Za-z0-9\-]{1,32}',
1356 returns
=> { type
=> 'string' },
1360 my $rpcenv = PVE
::RPCEnvironment
::get
();
1362 my $authuser = $rpcenv->get_user();
1364 PVE
::CephTools
::check_ceph_inited
();
1366 my $cfg = PVE
::CephTools
::parse_ceph_config
();
1367 scalar(keys %$cfg) || die "no configuration\n";
1372 my $cmd = ['start'];
1373 if ($param->{service
}) {
1374 push @$cmd, $param->{service
};
1377 PVE
::CephTools
::ceph_service_cmd
(@$cmd);
1380 return $rpcenv->fork_worker('srvstart', $param->{service
} || 'ceph',
1381 $authuser, $worker);
1384 __PACKAGE__-
>register_method ({
1388 description
=> "Get ceph status.",
1392 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
1395 additionalProperties
=> 0,
1397 node
=> get_standard_option
('pve-node'),
1400 returns
=> { type
=> 'object' },
1404 PVE
::CephTools
::check_ceph_enabled
();
1406 my $rados = PVE
::RADOS-
>new();
1407 my $status = $rados->mon_command({ prefix
=> 'status' });
1408 $status->{health
} = $rados->mon_command({ prefix
=> 'health', detail
=> 'detail' });
1412 __PACKAGE__-
>register_method ({
1416 description
=> "List all pools.",
1420 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
1423 additionalProperties
=> 0,
1425 node
=> get_standard_option
('pve-node'),
1433 pool
=> { type
=> 'integer' },
1434 pool_name
=> { type
=> 'string' },
1435 size
=> { type
=> 'integer' },
1438 links
=> [ { rel
=> 'child', href
=> "{pool_name}" } ],
1443 PVE
::CephTools
::check_ceph_inited
();
1445 my $rados = PVE
::RADOS-
>new();
1448 my $res = $rados->mon_command({ prefix
=> 'df' });
1449 my $total = $res->{stats
}->{total_avail_bytes
} || 0;
1451 foreach my $d (@{$res->{pools
}}) {
1452 next if !$d->{stats
};
1453 next if !defined($d->{id
});
1454 $stats->{$d->{id
}} = $d->{stats
};
1457 $res = $rados->mon_command({ prefix
=> 'osd dump' });
1458 my $rulestmp = $rados->mon_command({ prefix
=> 'osd crush rule dump'});
1461 for my $rule (@$rulestmp) {
1462 $rules->{$rule->{rule_id
}} = $rule->{rule_name
};
1466 foreach my $e (@{$res->{pools
}}) {
1468 foreach my $attr (qw(pool pool_name size min_size pg_num crush_rule)) {
1469 $d->{$attr} = $e->{$attr} if defined($e->{$attr});
1472 if (defined($d->{crush_rule
}) && defined($rules->{$d->{crush_rule
}})) {
1473 $d->{crush_rule_name
} = $rules->{$d->{crush_rule
}};
1476 if (my $s = $stats->{$d->{pool
}}) {
1477 $d->{bytes_used
} = $s->{bytes_used
};
1478 $d->{percent_used
} = ($s->{bytes_used
} / $total)*100
1479 if $s->{max_avail
} && $total;
1488 __PACKAGE__-
>register_method ({
1489 name
=> 'createpool',
1492 description
=> "Create POOL",
1496 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1499 additionalProperties
=> 0,
1501 node
=> get_standard_option
('pve-node'),
1503 description
=> "The name of the pool. It must be unique.",
1507 description
=> 'Number of replicas per object',
1515 description
=> 'Minimum number of replicas per object',
1523 description
=> "Number of placement groups.",
1531 description
=> "The rule to use for mapping object placement in the cluster.",
1537 returns
=> { type
=> 'null' },
1541 PVE
::CephTools
::check_ceph_inited
();
1543 my $pve_ckeyring_path = PVE
::CephTools
::get_config
('pve_ckeyring_path');
1545 die "not fully configured - missing '$pve_ckeyring_path'\n"
1546 if ! -f
$pve_ckeyring_path;
1548 my $pg_num = $param->{pg_num
} || 64;
1549 my $size = $param->{size
} || 2;
1550 my $min_size = $param->{min_size
} || 1;
1551 my $rados = PVE
::RADOS-
>new();
1553 $rados->mon_command({
1554 prefix
=> "osd pool create",
1555 pool
=> $param->{name
},
1556 pg_num
=> int($pg_num),
1560 $rados->mon_command({
1561 prefix
=> "osd pool set",
1562 pool
=> $param->{name
},
1568 $rados->mon_command({
1569 prefix
=> "osd pool set",
1570 pool
=> $param->{name
},
1576 if (defined($param->{crush_rule
})) {
1577 $rados->mon_command({
1578 prefix
=> "osd pool set",
1579 pool
=> $param->{name
},
1580 var
=> 'crush_rule',
1581 val
=> $param->{crush_rule
},
1589 __PACKAGE__-
>register_method ({
1590 name
=> 'get_flags',
1593 description
=> "get all set ceph flags",
1597 check
=> ['perm', '/', [ 'Sys.Audit' ]],
1600 additionalProperties
=> 0,
1602 node
=> get_standard_option
('pve-node'),
1605 returns
=> { type
=> 'string' },
1609 PVE
::CephTools
::check_ceph_inited
();
1611 my $pve_ckeyring_path = PVE
::CephTools
::get_config
('pve_ckeyring_path');
1613 die "not fully configured - missing '$pve_ckeyring_path'\n"
1614 if ! -f
$pve_ckeyring_path;
1616 my $rados = PVE
::RADOS-
>new();
1618 my $stat = $rados->mon_command({ prefix
=> 'osd dump' });
1620 return $stat->{flags
} // '';
1623 __PACKAGE__-
>register_method ({
1625 path
=> 'flags/{flag}',
1627 description
=> "Set a ceph flag",
1631 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1634 additionalProperties
=> 0,
1636 node
=> get_standard_option
('pve-node'),
1638 description
=> 'The ceph flag to set/unset',
1640 enum
=> [ 'full', 'pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill', 'norebalance', 'norecover', 'noscrub', 'nodeep-scrub', 'notieragent'],
1644 returns
=> { type
=> 'null' },
1648 PVE
::CephTools
::check_ceph_inited
();
1650 my $pve_ckeyring_path = PVE
::CephTools
::get_config
('pve_ckeyring_path');
1652 die "not fully configured - missing '$pve_ckeyring_path'\n"
1653 if ! -f
$pve_ckeyring_path;
1655 my $set = $param->{set
} // !$param->{unset
};
1656 my $rados = PVE
::RADOS-
>new();
1658 $rados->mon_command({
1659 prefix
=> "osd set",
1660 key
=> $param->{flag
},
1666 __PACKAGE__-
>register_method ({
1667 name
=> 'unset_flag',
1668 path
=> 'flags/{flag}',
1670 description
=> "Unset a ceph flag",
1674 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1677 additionalProperties
=> 0,
1679 node
=> get_standard_option
('pve-node'),
1681 description
=> 'The ceph flag to set/unset',
1683 enum
=> [ 'full', 'pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill', 'norebalance', 'norecover', 'noscrub', 'nodeep-scrub', 'notieragent'],
1687 returns
=> { type
=> 'null' },
1691 PVE
::CephTools
::check_ceph_inited
();
1693 my $pve_ckeyring_path = PVE
::CephTools
::get_config
('pve_ckeyring_path');
1695 die "not fully configured - missing '$pve_ckeyring_path'\n"
1696 if ! -f
$pve_ckeyring_path;
1698 my $set = $param->{set
} // !$param->{unset
};
1699 my $rados = PVE
::RADOS-
>new();
1701 $rados->mon_command({
1702 prefix
=> "osd unset",
1703 key
=> $param->{flag
},
1709 __PACKAGE__-
>register_method ({
1710 name
=> 'destroypool',
1711 path
=> 'pools/{name}',
1713 description
=> "Destroy pool",
1717 check
=> ['perm', '/', [ 'Sys.Modify' ]],
1720 additionalProperties
=> 0,
1722 node
=> get_standard_option
('pve-node'),
1724 description
=> "The name of the pool. It must be unique.",
1728 description
=> "If true, destroys pool even if in use",
1735 returns
=> { type
=> 'null' },
1739 PVE
::CephTools
::check_ceph_inited
();
1741 # if not forced, destroy ceph pool only when no
1742 # vm disks are on it anymore
1743 if (!$param->{force
}) {
1744 my $storagecfg = PVE
::Storage
::config
();
1745 foreach my $storageid (keys %{$storagecfg->{ids
}}) {
1746 my $storage = $storagecfg->{ids
}->{$storageid};
1747 next if $storage->{type
} ne 'rbd';
1748 next if $storage->{pool
} ne $param->{name
};
1750 # check if any vm disks are on the pool
1751 my $res = PVE
::Storage
::vdisk_list
($storagecfg, $storageid);
1752 die "ceph pool '$param->{name}' still in use by storage '$storageid'\n"
1753 if @{$res->{$storageid}} != 0;
1757 my $rados = PVE
::RADOS-
>new();
1758 # fixme: '--yes-i-really-really-mean-it'
1759 $rados->mon_command({
1760 prefix
=> "osd pool delete",
1761 pool
=> $param->{name
},
1762 pool2
=> $param->{name
},
1763 sure
=> '--yes-i-really-really-mean-it',
1771 __PACKAGE__-
>register_method ({
1775 description
=> "Get OSD crush map",
1779 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
1782 additionalProperties
=> 0,
1784 node
=> get_standard_option
('pve-node'),
1787 returns
=> { type
=> 'string' },
1791 PVE
::CephTools
::check_ceph_inited
();
1793 # this produces JSON (difficult to read for the user)
1794 # my $txt = &$run_ceph_cmd_text(['osd', 'crush', 'dump'], quiet => 1);
1798 my $mapfile = "/var/tmp/ceph-crush.map.$$";
1799 my $mapdata = "/var/tmp/ceph-crush.txt.$$";
1801 my $rados = PVE
::RADOS-
>new();
1804 my $bindata = $rados->mon_command({ prefix
=> 'osd getcrushmap', format
=> 'plain' });
1805 PVE
::Tools
::file_set_contents
($mapfile, $bindata);
1806 run_command
(['crushtool', '-d', $mapfile, '-o', $mapdata]);
1807 $txt = PVE
::Tools
::file_get_contents
($mapdata);
1819 __PACKAGE__-
>register_method({
1823 description
=> "Read ceph log",
1826 check
=> ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
1830 additionalProperties
=> 0,
1832 node
=> get_standard_option
('pve-node'),
1851 description
=> "Line number",
1855 description
=> "Line text",
1864 my $rpcenv = PVE
::RPCEnvironment
::get
();
1865 my $user = $rpcenv->get_user();
1866 my $node = $param->{node
};
1868 my $logfile = "/var/log/ceph/ceph.log";
1869 my ($count, $lines) = PVE
::Tools
::dump_logfile
($logfile, $param->{start
}, $param->{limit
});
1871 $rpcenv->set_result_attrib('total', $count);
1876 __PACKAGE__-
>register_method ({
1880 description
=> "List ceph rules.",
1884 check
=> ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any
=> 1],
1887 additionalProperties
=> 0,
1889 node
=> get_standard_option
('pve-node'),
1898 links
=> [ { rel
=> 'child', href
=> "{name}" } ],
1903 PVE
::CephTools
::check_ceph_inited
();
1905 my $rados = PVE
::RADOS-
>new();
1907 my $rules = $rados->mon_command({ prefix
=> 'osd crush rule ls' });
1911 foreach my $rule (@$rules) {
1912 push @$res, { name
=> $rule };