]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Ceph.pm
fix #1465: use a combobox for the crush rule instead of the id
[pve-manager.git] / PVE / API2 / Ceph.pm
CommitLineData
7d4fc5ef 1package PVE::API2::CephOSD;
38db610a
DM
2
3use strict;
4use warnings;
13f4d762 5use Cwd qw(abs_path);
98901f1d 6use Net::IP;
38db610a
DM
7
8use PVE::SafeSyslog;
13f4d762 9use PVE::Tools qw(extract_param run_command file_get_contents file_read_firstline dir_glob_regex dir_glob_foreach);
38db610a
DM
10use PVE::Exception qw(raise raise_param_exc);
11use PVE::INotify;
12use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file);
13use PVE::AccessControl;
14use PVE::Storage;
15use PVE::RESTHandler;
16use PVE::RPCEnvironment;
17use PVE::JSONSchema qw(get_standard_option);
970236b3 18use PVE::RADOS;
a34866f0 19use PVE::CephTools;
7f4924bd 20use PVE::Diskmanage;
38db610a
DM
21
22use base qw(PVE::RESTHandler);
23
24use Data::Dumper; # fixme: remove
25
7d4fc5ef
DM
26my $get_osd_status = sub {
27 my ($rados, $osdid) = @_;
0e5816e4 28
7d4fc5ef 29 my $stat = $rados->mon_command({ prefix => 'osd dump' });
2f804640 30
7d4fc5ef 31 my $osdlist = $stat->{osds} || [];
38db610a 32
85c17d96
DC
33 my $flags = $stat->{flags} || undef;
34
7d4fc5ef
DM
35 my $osdstat;
36 foreach my $d (@$osdlist) {
be753927 37 $osdstat->{$d->{osd}} = $d if defined($d->{osd});
7d4fc5ef
DM
38 }
39 if (defined($osdid)) {
40 die "no such OSD '$osdid'\n" if !$osdstat->{$osdid};
41 return $osdstat->{$osdid};
42 }
38db610a 43
85c17d96 44 return wantarray? ($osdstat, $flags):$osdstat;
7d4fc5ef 45};
13f4d762 46
941c0195
DM
47my $get_osd_usage = sub {
48 my ($rados) = @_;
49
be753927 50 my $osdlist = $rados->mon_command({ prefix => 'pg dump',
941c0195
DM
51 dumpcontents => [ 'osds' ]}) || [];
52
53 my $osdstat;
54 foreach my $d (@$osdlist) {
be753927 55 $osdstat->{$d->{osd}} = $d if defined($d->{osd});
941c0195
DM
56 }
57
58 return $osdstat;
59};
60
7d4fc5ef
DM
61__PACKAGE__->register_method ({
62 name => 'index',
63 path => '',
64 method => 'GET',
65 description => "Get Ceph osd list/tree.",
66 proxyto => 'node',
67 protected => 1,
90c75580
TL
68 permissions => {
69 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
70 },
7d4fc5ef 71 parameters => {
be753927 72 additionalProperties => 0,
7d4fc5ef
DM
73 properties => {
74 node => get_standard_option('pve-node'),
75 },
76 },
77 # fixme: return a list instead of extjs tree format ?
78 returns => {
79 type => "object",
80 },
81 code => sub {
82 my ($param) = @_;
13f4d762 83
7d4fc5ef 84 PVE::CephTools::check_ceph_inited();
13f4d762 85
7d4fc5ef
DM
86 my $rados = PVE::RADOS->new();
87 my $res = $rados->mon_command({ prefix => 'osd tree' });
13f4d762 88
7d4fc5ef 89 die "no tree nodes found\n" if !($res && $res->{nodes});
13f4d762 90
85c17d96 91 my ($osdhash, $flags) = &$get_osd_status($rados);
13f4d762 92
941c0195
DM
93 my $usagehash = &$get_osd_usage($rados);
94
7d4fc5ef
DM
95 my $nodes = {};
96 my $newnodes = {};
97 foreach my $e (@{$res->{nodes}}) {
98 $nodes->{$e->{id}} = $e;
be753927
DC
99
100 my $new = {
101 id => $e->{id},
102 name => $e->{name},
7d4fc5ef
DM
103 type => $e->{type}
104 };
13f4d762 105
33a7e157 106 foreach my $opt (qw(status crush_weight reweight device_class)) {
7d4fc5ef
DM
107 $new->{$opt} = $e->{$opt} if defined($e->{$opt});
108 }
13f4d762 109
7d4fc5ef
DM
110 if (my $stat = $osdhash->{$e->{id}}) {
111 $new->{in} = $stat->{in} if defined($stat->{in});
112 }
13f4d762 113
941c0195
DM
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};
cc81005a
DM
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};
121 }
941c0195
DM
122 }
123
7d4fc5ef 124 $newnodes->{$e->{id}} = $new;
13f4d762
DM
125 }
126
7d4fc5ef
DM
127 foreach my $e (@{$res->{nodes}}) {
128 my $new = $newnodes->{$e->{id}};
129 if ($e->{children} && scalar(@{$e->{children}})) {
130 $new->{children} = [];
131 $new->{leaf} = 0;
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};
137 }
138 push @{$new->{children}}, $newnodes->{$cid};
139 }
140 } else {
141 $new->{leaf} = ($e->{id} >= 0) ? 1 : 0;
142 }
0e5816e4
DM
143 }
144
4aed5e3e 145 my $roots = [];
7d4fc5ef
DM
146 foreach my $e (@{$res->{nodes}}) {
147 if (!$nodes->{$e->{id}}->{parent}) {
4aed5e3e 148 push @$roots, $newnodes->{$e->{id}};
7d4fc5ef 149 }
0e5816e4
DM
150 }
151
4aed5e3e 152 die "no root node\n" if !@$roots;
13f4d762 153
4aed5e3e 154 my $data = { root => { leaf => 0, children => $roots } };
0e5816e4 155
85c17d96
DC
156 # we want this for the noout flag
157 $data->{flags} = $flags if $flags;
158
7d4fc5ef
DM
159 return $data;
160 }});
13f4d762 161
7d4fc5ef
DM
162__PACKAGE__->register_method ({
163 name => 'createosd',
164 path => '',
165 method => 'POST',
166 description => "Create OSD",
167 proxyto => 'node',
168 protected => 1,
169 parameters => {
be753927 170 additionalProperties => 0,
7d4fc5ef
DM
171 properties => {
172 node => get_standard_option('pve-node'),
173 dev => {
174 description => "Block device name.",
175 type => 'string',
176 },
177 journal_dev => {
8d64bd8c
DC
178 description => "Block device name for journal (filestore) or block.db (bluestore).",
179 optional => 1,
180 type => 'string',
181 },
182 wal_dev => {
183 description => "Block device name for block.wal (bluestore only).",
7d4fc5ef
DM
184 optional => 1,
185 type => 'string',
186 },
187 fstype => {
50239dba 188 description => "File system type (filestore only).",
7d4fc5ef
DM
189 type => 'string',
190 enum => ['xfs', 'ext4', 'btrfs'],
191 default => 'xfs',
192 optional => 1,
193 },
50239dba
FG
194 bluestore => {
195 description => "Use bluestore instead of filestore.",
196 type => 'boolean',
197 default => 0,
198 optional => 1,
199 },
7d4fc5ef
DM
200 },
201 },
202 returns => { type => 'string' },
203 code => sub {
204 my ($param) = @_;
13f4d762 205
7d4fc5ef 206 my $rpcenv = PVE::RPCEnvironment::get();
13f4d762 207
7d4fc5ef 208 my $authuser = $rpcenv->get_user();
13f4d762 209
50239dba
FG
210 raise_param_exc({ 'bluestore' => "conflicts with parameter 'fstype'" })
211 if (defined($param->{fstype}) && defined($param->{bluestore}) && $param->{bluestore});
212
7d4fc5ef 213 PVE::CephTools::check_ceph_inited();
0e5816e4 214
7d4fc5ef 215 PVE::CephTools::setup_pve_symlinks();
a7a7fb00 216
8d64bd8c
DC
217 my $bluestore = $param->{bluestore} // 0;
218
7d4fc5ef 219 my $journal_dev;
8d64bd8c 220 my $wal_dev;
a7a7fb00 221
7d4fc5ef 222 if ($param->{journal_dev} && ($param->{journal_dev} ne $param->{dev})) {
7f4924bd 223 $journal_dev = PVE::Diskmanage::verify_blockdev_path($param->{journal_dev});
8d64bd8c
DC
224 # if only journal is given, also put the wal there
225 $wal_dev = $journal_dev;
226 }
227
228 if ($param->{wal_dev} &&
229 ($param->{wal_dev} ne $param->{dev}) &&
230 (!$param->{journal_dev} || $param->{wal_dev} ne $param->{journal_dev})) {
231 raise_param_exc({ 'wal_dev' => "can only be set with paramater 'bluestore'"})
232 if !$bluestore;
233 $wal_dev = PVE::Diskmanage::verify_blockdev_path($param->{wal_dev});
7d4fc5ef 234 }
13f4d762 235
7f4924bd 236 $param->{dev} = PVE::Diskmanage::verify_blockdev_path($param->{dev});
a7a7fb00 237
7d4fc5ef
DM
238 my $devname = $param->{dev};
239 $devname =~ s|/dev/||;
929376d7
DC
240
241 my $disklist = PVE::Diskmanage::get_disks($devname, 1);
242
7d4fc5ef
DM
243 my $diskinfo = $disklist->{$devname};
244 die "unable to get device info for '$devname'\n"
245 if !$diskinfo;
13f4d762 246
be753927 247 die "device '$param->{dev}' is in use\n"
7d4fc5ef 248 if $diskinfo->{used};
0e5816e4 249
929376d7 250 my $devpath = $diskinfo->{devpath};
7d4fc5ef
DM
251 my $rados = PVE::RADOS->new();
252 my $monstat = $rados->mon_command({ prefix => 'mon_status' });
253 die "unable to get fsid\n" if !$monstat->{monmap} || !$monstat->{monmap}->{fsid};
0e5816e4 254
7d4fc5ef
DM
255 my $fsid = $monstat->{monmap}->{fsid};
256 $fsid = $1 if $fsid =~ m/^([0-9a-f\-]+)$/;
0e5816e4 257
7d4fc5ef 258 my $ceph_bootstrap_osd_keyring = PVE::CephTools::get_config('ceph_bootstrap_osd_keyring');
0e5816e4 259
7d4fc5ef 260 if (! -f $ceph_bootstrap_osd_keyring) {
1f3e956a 261 my $bindata = $rados->mon_command({ prefix => 'auth get', entity => 'client.bootstrap-osd', format => 'plain' });
7d4fc5ef
DM
262 PVE::Tools::file_set_contents($ceph_bootstrap_osd_keyring, $bindata);
263 };
be753927 264
7d4fc5ef
DM
265 my $worker = sub {
266 my $upid = shift;
0e5816e4 267
7d4fc5ef 268 my $fstype = $param->{fstype} || 'xfs';
0e5816e4 269
0e5816e4 270
7d4fc5ef 271 my $ccname = PVE::CephTools::get_config('ccname');
0e5816e4 272
50239dba 273 my $cmd = ['ceph-disk', 'prepare', '--zap-disk',
7d4fc5ef 274 '--cluster', $ccname, '--cluster-uuid', $fsid ];
38db610a 275
8d64bd8c 276 if ($bluestore) {
50239dba
FG
277 print "create OSD on $devpath (bluestore)\n";
278 push @$cmd, '--bluestore';
8d64bd8c
DC
279
280 if ($journal_dev) {
281 print "using device '$journal_dev' for block.db\n";
282 push @$cmd, '--block.db', $journal_dev;
283 }
284
285 if ($wal_dev) {
286 print "using device '$wal_dev' for block.wal\n";
287 push @$cmd, '--block.wal', $wal_dev;
288 }
289
290 push @$cmd, $devpath;
50239dba
FG
291 } else {
292 print "create OSD on $devpath ($fstype)\n";
b6c42726 293 push @$cmd, '--filestore', '--fs-type', $fstype;
8d64bd8c
DC
294 if ($journal_dev) {
295 print "using device '$journal_dev' for journal\n";
296 push @$cmd, '--journal-dev', $devpath, $journal_dev;
297 } else {
298 push @$cmd, $devpath;
299 }
50239dba
FG
300 }
301
be753927 302
7d4fc5ef
DM
303 run_command($cmd);
304 };
38db610a 305
7d4fc5ef 306 return $rpcenv->fork_worker('cephcreateosd', $devname, $authuser, $worker);
38db610a
DM
307 }});
308
13f4d762 309__PACKAGE__->register_method ({
7d4fc5ef
DM
310 name => 'destroyosd',
311 path => '{osdid}',
312 method => 'DELETE',
313 description => "Destroy OSD",
13f4d762
DM
314 proxyto => 'node',
315 protected => 1,
316 parameters => {
be753927 317 additionalProperties => 0,
13f4d762
DM
318 properties => {
319 node => get_standard_option('pve-node'),
7d4fc5ef
DM
320 osdid => {
321 description => 'OSD ID',
322 type => 'integer',
0e5816e4 323 },
7d4fc5ef
DM
324 cleanup => {
325 description => "If set, we remove partition table entries.",
326 type => 'boolean',
327 optional => 1,
328 default => 0,
13f4d762
DM
329 },
330 },
13f4d762 331 },
7d4fc5ef 332 returns => { type => 'string' },
13f4d762
DM
333 code => sub {
334 my ($param) = @_;
335
7d4fc5ef 336 my $rpcenv = PVE::RPCEnvironment::get();
13f4d762 337
7d4fc5ef 338 my $authuser = $rpcenv->get_user();
13f4d762 339
7d4fc5ef 340 PVE::CephTools::check_ceph_inited();
0e5816e4 341
7d4fc5ef 342 my $osdid = $param->{osdid};
0e5816e4 343
7d4fc5ef
DM
344 my $rados = PVE::RADOS->new();
345 my $osdstat = &$get_osd_status($rados, $osdid);
13f4d762 346
7d4fc5ef
DM
347 die "osd is in use (in == 1)\n" if $osdstat->{in};
348 #&$run_ceph_cmd(['osd', 'out', $osdid]);
68e0c4bd 349
7d4fc5ef 350 die "osd is still runnung (up == 1)\n" if $osdstat->{up};
68e0c4bd 351
7d4fc5ef 352 my $osdsection = "osd.$osdid";
68e0c4bd 353
7d4fc5ef
DM
354 my $worker = sub {
355 my $upid = shift;
68e0c4bd 356
7d4fc5ef 357 # reopen with longer timeout
be753927 358 $rados = PVE::RADOS->new(timeout => PVE::CephTools::get_config('long_rados_timeout'));
68e0c4bd 359
7d4fc5ef 360 print "destroy OSD $osdsection\n";
68e0c4bd 361
7d4fc5ef
DM
362 eval { PVE::CephTools::ceph_service_cmd('stop', $osdsection); };
363 warn $@ if $@;
68e0c4bd 364
7d4fc5ef
DM
365 print "Remove $osdsection from the CRUSH map\n";
366 $rados->mon_command({ prefix => "osd crush remove", name => $osdsection, format => 'plain' });
68e0c4bd 367
7d4fc5ef
DM
368 print "Remove the $osdsection authentication key.\n";
369 $rados->mon_command({ prefix => "auth del", entity => $osdsection, format => 'plain' });
370
371 print "Remove OSD $osdsection\n";
372 $rados->mon_command({ prefix => "osd rm", ids => [ $osdsection ], format => 'plain' });
373
374 # try to unmount from standard mount point
375 my $mountpoint = "/var/lib/ceph/osd/ceph-$osdid";
376
377 my $remove_partition = sub {
84aed461 378 my ($part) = @_;
7d4fc5ef
DM
379
380 return if !$part || (! -b $part );
84aed461
WL
381 my $partnum = PVE::Diskmanage::get_partnum($part);
382 my $devpath = PVE::Diskmanage::get_blockdev($part);
383
384 print "remove partition $part (disk '${devpath}', partnum $partnum)\n";
385 eval { run_command(['/sbin/sgdisk', '-d', $partnum, "${devpath}"]); };
386 warn $@ if $@;
7d4fc5ef 387 };
68e0c4bd 388
bb7d5aa9 389 my $partitions_to_remove = [];
be753927 390
7d4fc5ef 391 if ($param->{cleanup}) {
7d4fc5ef
DM
392 if (my $fd = IO::File->new("/proc/mounts", "r")) {
393 while (defined(my $line = <$fd>)) {
394 my ($dev, $path, $fstype) = split(/\s+/, $line);
395 next if !($dev && $path && $fstype);
396 next if $dev !~ m|^/dev/|;
397 if ($path eq $mountpoint) {
bb7d5aa9
DC
398 my $data_part = abs_path($dev);
399 push @$partitions_to_remove, $data_part;
7d4fc5ef
DM
400 last;
401 }
402 }
403 close($fd);
68e0c4bd 404 }
bb7d5aa9
DC
405
406 foreach my $path (qw(journal block block.db block.wal)) {
407 my $part = abs_path("$mountpoint/$path");
408 if ($part) {
409 push @$partitions_to_remove, $part;
410 }
411 }
68e0c4bd 412 }
7d4fc5ef
DM
413
414 print "Unmount OSD $osdsection from $mountpoint\n";
84aed461 415 eval { run_command(['/bin/umount', $mountpoint]); };
7d4fc5ef
DM
416 if (my $err = $@) {
417 warn $err;
418 } elsif ($param->{cleanup}) {
ef3d095b 419 #be aware of the ceph udev rules which can remount.
bb7d5aa9
DC
420 foreach my $part (@$partitions_to_remove) {
421 $remove_partition->($part);
422 }
7d4fc5ef 423 }
68e0c4bd 424 };
68e0c4bd 425
7d4fc5ef 426 return $rpcenv->fork_worker('cephdestroyosd', $osdsection, $authuser, $worker);
68e0c4bd
DM
427 }});
428
38db610a 429__PACKAGE__->register_method ({
7d4fc5ef
DM
430 name => 'in',
431 path => '{osdid}/in',
38db610a 432 method => 'POST',
7d4fc5ef 433 description => "ceph osd in",
38db610a
DM
434 proxyto => 'node',
435 protected => 1,
90c75580
TL
436 permissions => {
437 check => ['perm', '/', [ 'Sys.Modify' ]],
438 },
38db610a 439 parameters => {
be753927 440 additionalProperties => 0,
38db610a
DM
441 properties => {
442 node => get_standard_option('pve-node'),
7d4fc5ef
DM
443 osdid => {
444 description => 'OSD ID',
38db610a 445 type => 'integer',
38db610a
DM
446 },
447 },
448 },
7d4fc5ef 449 returns => { type => "null" },
38db610a
DM
450 code => sub {
451 my ($param) = @_;
452
7d4fc5ef 453 PVE::CephTools::check_ceph_inited();
38db610a 454
7d4fc5ef 455 my $osdid = $param->{osdid};
0e5816e4 456
7d4fc5ef 457 my $rados = PVE::RADOS->new();
f7e342ea 458
7d4fc5ef 459 my $osdstat = &$get_osd_status($rados, $osdid); # osd exists?
f7e342ea 460
7d4fc5ef 461 my $osdsection = "osd.$osdid";
38db610a 462
7d4fc5ef 463 $rados->mon_command({ prefix => "osd in", ids => [ $osdsection ], format => 'plain' });
38db610a
DM
464
465 return undef;
466 }});
467
468__PACKAGE__->register_method ({
7d4fc5ef
DM
469 name => 'out',
470 path => '{osdid}/out',
38db610a 471 method => 'POST',
7d4fc5ef 472 description => "ceph osd out",
38db610a
DM
473 proxyto => 'node',
474 protected => 1,
90c75580
TL
475 permissions => {
476 check => ['perm', '/', [ 'Sys.Modify' ]],
477 },
38db610a 478 parameters => {
be753927 479 additionalProperties => 0,
38db610a
DM
480 properties => {
481 node => get_standard_option('pve-node'),
7d4fc5ef
DM
482 osdid => {
483 description => 'OSD ID',
484 type => 'integer',
485 },
38db610a
DM
486 },
487 },
7d4fc5ef 488 returns => { type => "null" },
38db610a
DM
489 code => sub {
490 my ($param) = @_;
491
a34866f0 492 PVE::CephTools::check_ceph_inited();
38db610a 493
7d4fc5ef 494 my $osdid = $param->{osdid};
38db610a 495
7d4fc5ef 496 my $rados = PVE::RADOS->new();
38db610a 497
7d4fc5ef 498 my $osdstat = &$get_osd_status($rados, $osdid); # osd exists?
38db610a 499
7d4fc5ef 500 my $osdsection = "osd.$osdid";
38db610a 501
7d4fc5ef 502 $rados->mon_command({ prefix => "osd out", ids => [ $osdsection ], format => 'plain' });
38db610a 503
7d4fc5ef
DM
504 return undef;
505 }});
38db610a 506
7d4fc5ef
DM
507package PVE::API2::Ceph;
508
509use strict;
510use warnings;
511use File::Basename;
512use File::Path;
513use POSIX qw (LONG_MAX);
514use Cwd qw(abs_path);
515use IO::Dir;
516use UUID;
517use Net::IP;
518
519use PVE::SafeSyslog;
520use PVE::Tools qw(extract_param run_command file_get_contents file_read_firstline dir_glob_regex dir_glob_foreach);
521use PVE::Exception qw(raise raise_param_exc);
522use PVE::INotify;
523use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file);
524use PVE::AccessControl;
525use PVE::Storage;
526use PVE::RESTHandler;
527use PVE::RPCEnvironment;
528use PVE::JSONSchema qw(get_standard_option);
529use JSON;
530use PVE::RADOS;
531use PVE::CephTools;
532
533use base qw(PVE::RESTHandler);
534
535use Data::Dumper; # fixme: remove
536
537my $pve_osd_default_journal_size = 1024*5;
538
539__PACKAGE__->register_method ({
be753927 540 subclass => "PVE::API2::CephOSD",
7d4fc5ef
DM
541 path => 'osd',
542});
543
544__PACKAGE__->register_method ({
545 name => 'index',
546 path => '',
547 method => 'GET',
548 description => "Directory index.",
549 permissions => { user => 'all' },
90c75580
TL
550 permissions => {
551 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
552 },
7d4fc5ef 553 parameters => {
be753927 554 additionalProperties => 0,
7d4fc5ef
DM
555 properties => {
556 node => get_standard_option('pve-node'),
557 },
558 },
559 returns => {
560 type => 'array',
561 items => {
562 type => "object",
563 properties => {},
564 },
565 links => [ { rel => 'child', href => "{name}" } ],
566 },
567 code => sub {
568 my ($param) = @_;
569
570 my $result = [
571 { name => 'init' },
572 { name => 'mon' },
573 { name => 'osd' },
574 { name => 'pools' },
575 { name => 'stop' },
576 { name => 'start' },
577 { name => 'status' },
578 { name => 'crush' },
579 { name => 'config' },
580 { name => 'log' },
581 { name => 'disks' },
a46ad02a 582 { name => 'flags' },
d2692b86 583 { name => 'rules' },
7d4fc5ef
DM
584 ];
585
586 return $result;
587 }});
588
589__PACKAGE__->register_method ({
590 name => 'disks',
591 path => 'disks',
592 method => 'GET',
593 description => "List local disks.",
594 proxyto => 'node',
595 protected => 1,
90c75580
TL
596 permissions => {
597 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
598 },
7d4fc5ef 599 parameters => {
be753927 600 additionalProperties => 0,
7d4fc5ef
DM
601 properties => {
602 node => get_standard_option('pve-node'),
603 type => {
604 description => "Only list specific types of disks.",
605 type => 'string',
606 enum => ['unused', 'journal_disks'],
607 optional => 1,
608 },
609 },
610 },
611 returns => {
612 type => 'array',
613 items => {
614 type => "object",
615 properties => {
616 dev => { type => 'string' },
617 used => { type => 'string', optional => 1 },
618 gpt => { type => 'boolean' },
619 size => { type => 'integer' },
620 osdid => { type => 'integer' },
621 vendor => { type => 'string', optional => 1 },
622 model => { type => 'string', optional => 1 },
623 serial => { type => 'string', optional => 1 },
624 },
625 },
626 # links => [ { rel => 'child', href => "{}" } ],
627 },
628 code => sub {
629 my ($param) = @_;
630
631 PVE::CephTools::check_ceph_inited();
632
5fd5c30d 633 my $disks = PVE::Diskmanage::get_disks(undef, 1);
7d4fc5ef
DM
634
635 my $res = [];
636 foreach my $dev (keys %$disks) {
637 my $d = $disks->{$dev};
638 if ($param->{type}) {
639 if ($param->{type} eq 'journal_disks') {
640 next if $d->{osdid} >= 0;
641 next if !$d->{gpt};
642 } elsif ($param->{type} eq 'unused') {
643 next if $d->{used};
644 } else {
645 die "internal error"; # should not happen
646 }
647 }
648
649 $d->{dev} = "/dev/$dev";
be753927 650 push @$res, $d;
7d4fc5ef
DM
651 }
652
653 return $res;
654 }});
655
656__PACKAGE__->register_method ({
657 name => 'config',
658 path => 'config',
659 method => 'GET',
90c75580
TL
660 permissions => {
661 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
662 },
7d4fc5ef
DM
663 description => "Get Ceph configuration.",
664 parameters => {
be753927 665 additionalProperties => 0,
7d4fc5ef
DM
666 properties => {
667 node => get_standard_option('pve-node'),
668 },
669 },
670 returns => { type => 'string' },
671 code => sub {
672 my ($param) = @_;
673
674 PVE::CephTools::check_ceph_inited();
675
676 my $path = PVE::CephTools::get_config('pve_ceph_cfgpath');
677 return PVE::Tools::file_get_contents($path);
678
679 }});
680
681__PACKAGE__->register_method ({
682 name => 'listmon',
683 path => 'mon',
684 method => 'GET',
685 description => "Get Ceph monitor list.",
686 proxyto => 'node',
687 protected => 1,
90c75580
TL
688 permissions => {
689 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
690 },
7d4fc5ef 691 parameters => {
be753927 692 additionalProperties => 0,
7d4fc5ef
DM
693 properties => {
694 node => get_standard_option('pve-node'),
695 },
696 },
697 returns => {
698 type => 'array',
699 items => {
700 type => "object",
701 properties => {
702 name => { type => 'string' },
703 addr => { type => 'string' },
704 },
705 },
706 links => [ { rel => 'child', href => "{name}" } ],
707 },
708 code => sub {
709 my ($param) = @_;
710
711 PVE::CephTools::check_ceph_inited();
712
713 my $res = [];
714
715 my $cfg = PVE::CephTools::parse_ceph_config();
716
717 my $monhash = {};
718 foreach my $section (keys %$cfg) {
719 my $d = $cfg->{$section};
720 if ($section =~ m/^mon\.(\S+)$/) {
721 my $monid = $1;
722 if ($d->{'mon addr'} && $d->{'host'}) {
723 $monhash->{$monid} = {
724 addr => $d->{'mon addr'},
725 host => $d->{'host'},
726 name => $monid,
727 }
728 }
729 }
730 }
731
732 eval {
733 my $rados = PVE::RADOS->new();
734 my $monstat = $rados->mon_command({ prefix => 'mon_status' });
735 my $mons = $monstat->{monmap}->{mons};
736 foreach my $d (@$mons) {
737 next if !defined($d->{name});
be753927 738 $monhash->{$d->{name}}->{rank} = $d->{rank};
7d4fc5ef
DM
739 $monhash->{$d->{name}}->{addr} = $d->{addr};
740 if (grep { $_ eq $d->{rank} } @{$monstat->{quorum}}) {
741 $monhash->{$d->{name}}->{quorum} = 1;
742 }
743 }
744 };
745 warn $@ if $@;
746
747 return PVE::RESTHandler::hash_to_array($monhash, 'name');
748 }});
749
750__PACKAGE__->register_method ({
751 name => 'init',
752 path => 'init',
753 method => 'POST',
754 description => "Create initial ceph default configuration and setup symlinks.",
755 proxyto => 'node',
756 protected => 1,
90c75580
TL
757 permissions => {
758 check => ['perm', '/', [ 'Sys.Modify' ]],
759 },
7d4fc5ef 760 parameters => {
be753927 761 additionalProperties => 0,
7d4fc5ef
DM
762 properties => {
763 node => get_standard_option('pve-node'),
764 network => {
be753927 765 description => "Use specific network for all ceph related traffic",
7d4fc5ef
DM
766 type => 'string', format => 'CIDR',
767 optional => 1,
768 maxLength => 128,
769 },
770 size => {
207f4932
FG
771 description => 'Targeted number of replicas per object',
772 type => 'integer',
773 default => 3,
774 optional => 1,
775 minimum => 1,
776 maximum => 7,
777 },
778 min_size => {
779 description => 'Minimum number of available replicas per object to allow I/O',
7d4fc5ef
DM
780 type => 'integer',
781 default => 2,
782 optional => 1,
783 minimum => 1,
83663637 784 maximum => 7,
7d4fc5ef
DM
785 },
786 pg_bits => {
3cba09d5
FG
787 description => "Placement group bits, used to specify the " .
788 "default number of placement groups.\n\nNOTE: 'osd pool " .
789 "default pg num' does not work for default pools.",
7d4fc5ef
DM
790 type => 'integer',
791 default => 6,
792 optional => 1,
793 minimum => 6,
794 maximum => 14,
795 },
4280f25c 796 disable_cephx => {
97f050bb
FG
797 description => "Disable cephx authentification.\n\n" .
798 "WARNING: cephx is a security feature protecting against " .
799 "man-in-the-middle attacks. Only consider disabling cephx ".
800 "if your network is private!",
77bb90b0
AD
801 type => 'boolean',
802 optional => 1,
803 default => 0,
4280f25c 804 },
7d4fc5ef
DM
805 },
806 },
807 returns => { type => 'null' },
808 code => sub {
809 my ($param) = @_;
810
811 PVE::CephTools::check_ceph_installed();
812
813 # simply load old config if it already exists
814 my $cfg = PVE::CephTools::parse_ceph_config();
815
816 if (!$cfg->{global}) {
817
818 my $fsid;
819 my $uuid;
820
821 UUID::generate($uuid);
822 UUID::unparse($uuid, $fsid);
823
4280f25c 824 my $auth = $param->{disable_cephx} ? 'none' : 'cephx';
77bb90b0 825
7d4fc5ef
DM
826 $cfg->{global} = {
827 'fsid' => $fsid,
77bb90b0
AD
828 'auth cluster required' => $auth,
829 'auth service required' => $auth,
830 'auth client required' => $auth,
7d4fc5ef 831 'osd journal size' => $pve_osd_default_journal_size,
207f4932
FG
832 'osd pool default size' => $param->{size} // 3,
833 'osd pool default min size' => $param->{min_size} // 2,
2e9d791e 834 'mon allow pool delete' => 'true',
7d4fc5ef
DM
835 };
836
be753927 837 # this does not work for default pools
7d4fc5ef 838 #'osd pool default pg num' => $pg_num,
be753927 839 #'osd pool default pgp num' => $pg_num,
7d4fc5ef 840 }
be753927 841
7d4fc5ef
DM
842 $cfg->{global}->{keyring} = '/etc/pve/priv/$cluster.$name.keyring';
843 $cfg->{osd}->{keyring} = '/var/lib/ceph/osd/ceph-$id/keyring';
844
7d4fc5ef
DM
845 if ($param->{pg_bits}) {
846 $cfg->{global}->{'osd pg bits'} = $param->{pg_bits};
847 $cfg->{global}->{'osd pgp bits'} = $param->{pg_bits};
848 }
849
850 if ($param->{network}) {
851 $cfg->{global}->{'public network'} = $param->{network};
852 $cfg->{global}->{'cluster network'} = $param->{network};
853 }
854
855 PVE::CephTools::write_ceph_config($cfg);
856
857 PVE::CephTools::setup_pve_symlinks();
858
859 return undef;
860 }});
861
862my $find_node_ip = sub {
863 my ($cidr) = @_;
864
7d4fc5ef 865 my $net = Net::IP->new($cidr) || die Net::IP::Error() . "\n";
905c2f51
WB
866 my $id = $net->version == 6 ? 'address6' : 'address';
867
868 my $config = PVE::INotify::read_file('interfaces');
869 my $ifaces = $config->{ifaces};
7d4fc5ef 870
905c2f51 871 foreach my $iface (keys %$ifaces) {
957a59b3 872 my $d = $ifaces->{$iface};
905c2f51
WB
873 next if !$d->{$id};
874 my $a = Net::IP->new($d->{$id});
7d4fc5ef 875 next if !$a;
905c2f51 876 return $d->{$id} if $net->overlaps($a);
7d4fc5ef
DM
877 }
878
879 die "unable to find local address within network '$cidr'\n";
880};
881
c05ff7b4
DC
882my $create_mgr = sub {
883 my ($rados, $id) = @_;
884
885 my $clustername = PVE::CephTools::get_config('ccname');
886 my $mgrdir = "/var/lib/ceph/mgr/$clustername-$id";
887 my $mgrkeyring = "$mgrdir/keyring";
888 my $mgrname = "mgr.$id";
889
890 die "ceph manager directory '$mgrdir' already exists\n"
891 if -d $mgrdir;
892
893 print "creating manager directory '$mgrdir'\n";
894 mkdir $mgrdir;
895 print "creating keys for '$mgrname'\n";
896 my $output = $rados->mon_command({ prefix => 'auth get-or-create',
897 entity => $mgrname,
898 caps => [
899 mon => 'allow profile mgr',
900 osd => 'allow *',
901 mds => 'allow *',
902 ],
903 format => 'plain'});
904 PVE::Tools::file_set_contents($mgrkeyring, $output);
905
906 print "setting owner for directory\n";
907 run_command(["chown", 'ceph:ceph', '-R', $mgrdir]);
908
909 print "enabling service 'ceph-mgr\@$id.service'\n";
910 PVE::CephTools::ceph_service_cmd('enable', $mgrname);
911 print "starting service 'ceph-mgr\@$id.service'\n";
912 PVE::CephTools::ceph_service_cmd('start', $mgrname);
913};
914
915my $destroy_mgr = sub {
916 my ($mgrid) = @_;
917
918 my $clustername = PVE::CephTools::get_config('ccname');
919 my $mgrname = "mgr.$mgrid";
920 my $mgrdir = "/var/lib/ceph/mgr/$clustername-$mgrid";
921
922 die "ceph manager directory '$mgrdir' not found\n"
923 if ! -d $mgrdir;
924
925 print "disabling service 'ceph-mgr\@$mgrid.service'\n";
926 PVE::CephTools::ceph_service_cmd('disable', $mgrname);
927 print "stopping service 'ceph-mgr\@$mgrid.service'\n";
928 PVE::CephTools::ceph_service_cmd('stop', $mgrname);
929
930 print "removing manager directory '$mgrdir'\n";
931 File::Path::remove_tree($mgrdir);
932};
933
7d4fc5ef
DM
934__PACKAGE__->register_method ({
935 name => 'createmon',
936 path => 'mon',
937 method => 'POST',
c05ff7b4 938 description => "Create Ceph Monitor and Manager",
7d4fc5ef
DM
939 proxyto => 'node',
940 protected => 1,
90c75580
TL
941 permissions => {
942 check => ['perm', '/', [ 'Sys.Modify' ]],
943 },
7d4fc5ef 944 parameters => {
be753927 945 additionalProperties => 0,
7d4fc5ef
DM
946 properties => {
947 node => get_standard_option('pve-node'),
c05ff7b4
DC
948 id => {
949 type => 'string',
950 optional => 1,
951 pattern => '[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?',
952 description => "The ID for the monitor, when omitted the same as the nodename",
953 },
954 'exclude-manager' => {
955 type => 'boolean',
956 optional => 1,
957 default => 0,
958 description => "When set, only a monitor will be created.",
959 },
7d4fc5ef
DM
960 },
961 },
962 returns => { type => 'string' },
963 code => sub {
964 my ($param) = @_;
965
966 PVE::CephTools::check_ceph_inited();
967
968 PVE::CephTools::setup_pve_symlinks();
969
970 my $rpcenv = PVE::RPCEnvironment::get();
971
972 my $authuser = $rpcenv->get_user();
973
974 my $cfg = PVE::CephTools::parse_ceph_config();
975
976 my $moncount = 0;
977
be753927 978 my $monaddrhash = {};
7d4fc5ef 979
3279b1d2
WL
980 my $systemd_managed = PVE::CephTools::systemd_managed();
981
7d4fc5ef
DM
982 foreach my $section (keys %$cfg) {
983 next if $section eq 'global';
984 my $d = $cfg->{$section};
985 if ($section =~ m/^mon\./) {
986 $moncount++;
987 if ($d->{'mon addr'}) {
988 $monaddrhash->{$d->{'mon addr'}} = $section;
989 }
990 }
991 }
38db610a 992
c05ff7b4 993 my $monid = $param->{id} // $param->{node};
38db610a 994
be753927 995 my $monsection = "mon.$monid";
f7e342ea
DM
996 my $ip;
997 if (my $pubnet = $cfg->{global}->{'public network'}) {
998 $ip = &$find_node_ip($pubnet);
999 } else {
1000 $ip = PVE::Cluster::remote_node_ip($param->{node});
1001 }
1002
98901f1d 1003 my $monaddr = Net::IP::ip_is_ipv6($ip) ? "[$ip]:6789" : "$ip:6789";
38db610a
DM
1004 my $monname = $param->{node};
1005
1006 die "monitor '$monsection' already exists\n" if $cfg->{$monsection};
be753927 1007 die "monitor address '$monaddr' already in use by '$monaddrhash->{$monaddr}'\n"
38db610a
DM
1008 if $monaddrhash->{$monaddr};
1009
52d7be41
DM
1010 my $worker = sub {
1011 my $upid = shift;
38db610a 1012
a34866f0
DM
1013 my $pve_ckeyring_path = PVE::CephTools::get_config('pve_ckeyring_path');
1014
52d7be41
DM
1015 if (! -f $pve_ckeyring_path) {
1016 run_command("ceph-authtool $pve_ckeyring_path --create-keyring " .
1017 "--gen-key -n client.admin");
1018 }
38db610a 1019
a34866f0 1020 my $pve_mon_key_path = PVE::CephTools::get_config('pve_mon_key_path');
52d7be41
DM
1021 if (! -f $pve_mon_key_path) {
1022 run_command("cp $pve_ckeyring_path $pve_mon_key_path.tmp");
1023 run_command("ceph-authtool $pve_mon_key_path.tmp -n client.admin --set-uid=0 " .
1024 "--cap mds 'allow' " .
1025 "--cap osd 'allow *' " .
d197634b 1026 "--cap mgr 'allow *' " .
52d7be41 1027 "--cap mon 'allow *'");
3279b1d2
WL
1028 run_command("cp $pve_mon_key_path.tmp /etc/ceph/ceph.client.admin.keyring") if $systemd_managed;
1029 run_command("chown ceph:ceph /etc/ceph/ceph.client.admin.keyring") if $systemd_managed;
52d7be41
DM
1030 run_command("ceph-authtool $pve_mon_key_path.tmp --gen-key -n mon. --cap mon 'allow *'");
1031 run_command("mv $pve_mon_key_path.tmp $pve_mon_key_path");
38db610a
DM
1032 }
1033
a34866f0
DM
1034 my $ccname = PVE::CephTools::get_config('ccname');
1035
52d7be41
DM
1036 my $mondir = "/var/lib/ceph/mon/$ccname-$monid";
1037 -d $mondir && die "monitor filesystem '$mondir' already exist\n";
be753927 1038
52d7be41 1039 my $monmap = "/tmp/monmap";
c05ff7b4 1040
52d7be41
DM
1041 eval {
1042 mkdir $mondir;
1043
3279b1d2
WL
1044 run_command("chown ceph:ceph $mondir") if $systemd_managed;
1045
52d7be41 1046 if ($moncount > 0) {
87eb0fc2 1047 my $rados = PVE::RADOS->new(timeout => PVE::CephTools::get_config('long_rados_timeout'));
970236b3
DM
1048 my $mapdata = $rados->mon_command({ prefix => 'mon getmap', format => 'plain' });
1049 PVE::Tools::file_set_contents($monmap, $mapdata);
52d7be41
DM
1050 } else {
1051 run_command("monmaptool --create --clobber --add $monid $monaddr --print $monmap");
1052 }
38db610a 1053
52d7be41 1054 run_command("ceph-mon --mkfs -i $monid --monmap $monmap --keyring $pve_mon_key_path");
3279b1d2 1055 run_command("chown ceph:ceph -R $mondir") if $systemd_managed;
52d7be41
DM
1056 };
1057 my $err = $@;
1058 unlink $monmap;
1059 if ($err) {
1060 File::Path::remove_tree($mondir);
1061 die $err;
1062 }
38db610a 1063
52d7be41
DM
1064 $cfg->{$monsection} = {
1065 'host' => $monname,
1066 'mon addr' => $monaddr,
1067 };
38db610a 1068
a34866f0 1069 PVE::CephTools::write_ceph_config($cfg);
38db610a 1070
cc5bb515
FG
1071 my $create_keys_pid = fork();
1072 if (!defined($create_keys_pid)) {
1073 die "Could not spawn ceph-create-keys to create bootstrap keys\n";
1074 } elsif ($create_keys_pid == 0) {
1075 exit PVE::Tools::run_command(['ceph-create-keys', '-i', $monid]);
1076 } else {
1077 PVE::CephTools::ceph_service_cmd('start', $monsection);
19bada0c 1078
cc5bb515
FG
1079 if ($systemd_managed) {
1080 #to ensure we have the correct startup order.
1081 eval { PVE::Tools::run_command(['/bin/systemctl', 'enable', "ceph-mon\@${monid}.service"]); };
1082 warn "Enable ceph-mon\@${monid}.service manually"if $@;
1083 }
1084 waitpid($create_keys_pid, 0);
19bada0c 1085 }
c05ff7b4
DC
1086
1087 # create manager
1088 if (!$param->{'exclude-manager'}) {
87eb0fc2 1089 my $rados = PVE::RADOS->new(timeout => PVE::CephTools::get_config('long_rados_timeout'));
c05ff7b4
DC
1090 $create_mgr->($rados, $monid);
1091 }
52d7be41 1092 };
38db610a 1093
52d7be41 1094 return $rpcenv->fork_worker('cephcreatemon', $monsection, $authuser, $worker);
38db610a
DM
1095 }});
1096
1097__PACKAGE__->register_method ({
1098 name => 'destroymon',
39e1ad70
DM
1099 path => 'mon/{monid}',
1100 method => 'DELETE',
c05ff7b4 1101 description => "Destroy Ceph Monitor and Manager.",
38db610a
DM
1102 proxyto => 'node',
1103 protected => 1,
90c75580
TL
1104 permissions => {
1105 check => ['perm', '/', [ 'Sys.Modify' ]],
1106 },
38db610a 1107 parameters => {
be753927 1108 additionalProperties => 0,
38db610a
DM
1109 properties => {
1110 node => get_standard_option('pve-node'),
1111 monid => {
1112 description => 'Monitor ID',
c05ff7b4
DC
1113 type => 'string',
1114 pattern => '[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?',
38db610a 1115 },
c05ff7b4
DC
1116 'exclude-manager' => {
1117 type => 'boolean',
1118 default => 0,
1119 optional => 1,
1120 description => "When set, removes only the monitor, not the manager"
1121 }
38db610a
DM
1122 },
1123 },
52d7be41 1124 returns => { type => 'string' },
38db610a
DM
1125 code => sub {
1126 my ($param) = @_;
1127
52d7be41
DM
1128 my $rpcenv = PVE::RPCEnvironment::get();
1129
1130 my $authuser = $rpcenv->get_user();
1131
a34866f0 1132 PVE::CephTools::check_ceph_inited();
38db610a 1133
a34866f0 1134 my $cfg = PVE::CephTools::parse_ceph_config();
be753927 1135
38db610a 1136 my $monid = $param->{monid};
be753927 1137 my $monsection = "mon.$monid";
38db610a 1138
36fd0190 1139 my $rados = PVE::RADOS->new();
970236b3 1140 my $monstat = $rados->mon_command({ prefix => 'mon_status' });
38db610a
DM
1141 my $monlist = $monstat->{monmap}->{mons};
1142
be753927 1143 die "no such monitor id '$monid'\n"
38db610a
DM
1144 if !defined($cfg->{$monsection});
1145
a34866f0
DM
1146 my $ccname = PVE::CephTools::get_config('ccname');
1147
38db610a
DM
1148 my $mondir = "/var/lib/ceph/mon/$ccname-$monid";
1149 -d $mondir || die "monitor filesystem '$mondir' does not exist on this node\n";
1150
1151 die "can't remove last monitor\n" if scalar(@$monlist) <= 1;
1152
52d7be41
DM
1153 my $worker = sub {
1154 my $upid = shift;
38db610a 1155
2f804640 1156 # reopen with longer timeout
be753927 1157 $rados = PVE::RADOS->new(timeout => PVE::CephTools::get_config('long_rados_timeout'));
f26b46db 1158
6e3c2f47 1159 $rados->mon_command({ prefix => "mon remove", name => $monid, format => 'plain' });
38db610a 1160
a34866f0 1161 eval { PVE::CephTools::ceph_service_cmd('stop', $monsection); };
52d7be41 1162 warn $@ if $@;
38db610a 1163
52d7be41 1164 delete $cfg->{$monsection};
a34866f0 1165 PVE::CephTools::write_ceph_config($cfg);
52d7be41 1166 File::Path::remove_tree($mondir);
c05ff7b4
DC
1167
1168 # remove manager
1169 if (!$param->{'exclude-manager'}) {
1170 eval { $destroy_mgr->($monid); };
1171 warn $@ if $@;
1172 }
52d7be41
DM
1173 };
1174
1175 return $rpcenv->fork_worker('cephdestroymon', $monsection, $authuser, $worker);
38db610a
DM
1176 }});
1177
ca68ac3e
DC
1178__PACKAGE__->register_method ({
1179 name => 'createmgr',
1180 path => 'mgr',
1181 method => 'POST',
1182 description => "Create Ceph Manager",
1183 proxyto => 'node',
1184 protected => 1,
1185 permissions => {
1186 check => ['perm', '/', [ 'Sys.Modify' ]],
1187 },
1188 parameters => {
1189 additionalProperties => 0,
1190 properties => {
1191 node => get_standard_option('pve-node'),
1192 id => {
1193 type => 'string',
1194 optional => 1,
1195 pattern => '[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?',
1196 description => "The ID for the manager, when omitted the same as the nodename",
1197 },
1198 },
1199 },
1200 returns => { type => 'string' },
1201 code => sub {
1202 my ($param) = @_;
1203
1204 PVE::CephTools::check_ceph_inited();
1205
1206 my $rpcenv = PVE::RPCEnvironment::get();
1207
1208 my $authuser = $rpcenv->get_user();
1209
1210 my $mgrid = $param->{id} // $param->{node};
1211
1212 my $worker = sub {
1213 my $upid = shift;
1214
1215 my $rados = PVE::RADOS->new(timeout => PVE::CephTools::get_config('long_rados_timeout'));
1216
1217 $create_mgr->($rados, $mgrid);
1218 };
1219
1220 return $rpcenv->fork_worker('cephcreatemgr', "mgr.$mgrid", $authuser, $worker);
1221 }});
1222
1223__PACKAGE__->register_method ({
1224 name => 'destroymgr',
1225 path => 'mgr/{id}',
1226 method => 'DELETE',
1227 description => "Destroy Ceph Manager.",
1228 proxyto => 'node',
1229 protected => 1,
1230 permissions => {
1231 check => ['perm', '/', [ 'Sys.Modify' ]],
1232 },
1233 parameters => {
1234 additionalProperties => 0,
1235 properties => {
1236 node => get_standard_option('pve-node'),
1237 id => {
1238 description => 'The ID of the manager',
1239 type => 'string',
1240 pattern => '[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?',
1241 },
1242 },
1243 },
1244 returns => { type => 'string' },
1245 code => sub {
1246 my ($param) = @_;
1247
1248 my $rpcenv = PVE::RPCEnvironment::get();
1249
1250 my $authuser = $rpcenv->get_user();
1251
1252 PVE::CephTools::check_ceph_inited();
1253
1254 my $mgrid = $param->{id};
1255
1256 my $worker = sub {
1257 my $upid = shift;
1258
1259 $destroy_mgr->($mgrid);
1260 };
1261
1262 return $rpcenv->fork_worker('cephdestroymgr', "mgr.$mgrid", $authuser, $worker);
1263 }});
1264
38db610a
DM
1265__PACKAGE__->register_method ({
1266 name => 'stop',
1267 path => 'stop',
1268 method => 'POST',
1269 description => "Stop ceph services.",
1270 proxyto => 'node',
1271 protected => 1,
90c75580
TL
1272 permissions => {
1273 check => ['perm', '/', [ 'Sys.Modify' ]],
1274 },
38db610a 1275 parameters => {
be753927 1276 additionalProperties => 0,
38db610a
DM
1277 properties => {
1278 node => get_standard_option('pve-node'),
68e0c4bd
DM
1279 service => {
1280 description => 'Ceph service name.',
1281 type => 'string',
1282 optional => 1,
b0e5ae21 1283 pattern => '(mon|mds|osd|mgr)\.[A-Za-z0-9\-]{1,32}',
68e0c4bd 1284 },
38db610a
DM
1285 },
1286 },
68e0c4bd 1287 returns => { type => 'string' },
38db610a
DM
1288 code => sub {
1289 my ($param) = @_;
1290
68e0c4bd
DM
1291 my $rpcenv = PVE::RPCEnvironment::get();
1292
1293 my $authuser = $rpcenv->get_user();
1294
a34866f0 1295 PVE::CephTools::check_ceph_inited();
38db610a 1296
a34866f0 1297 my $cfg = PVE::CephTools::parse_ceph_config();
38db610a
DM
1298 scalar(keys %$cfg) || die "no configuration\n";
1299
68e0c4bd
DM
1300 my $worker = sub {
1301 my $upid = shift;
38db610a 1302
68e0c4bd
DM
1303 my $cmd = ['stop'];
1304 if ($param->{service}) {
1305 push @$cmd, $param->{service};
1306 }
1307
a34866f0 1308 PVE::CephTools::ceph_service_cmd(@$cmd);
68e0c4bd
DM
1309 };
1310
1311 return $rpcenv->fork_worker('srvstop', $param->{service} || 'ceph',
1312 $authuser, $worker);
38db610a
DM
1313 }});
1314
1315__PACKAGE__->register_method ({
1316 name => 'start',
1317 path => 'start',
1318 method => 'POST',
1319 description => "Start ceph services.",
1320 proxyto => 'node',
1321 protected => 1,
90c75580
TL
1322 permissions => {
1323 check => ['perm', '/', [ 'Sys.Modify' ]],
1324 },
38db610a 1325 parameters => {
be753927 1326 additionalProperties => 0,
38db610a
DM
1327 properties => {
1328 node => get_standard_option('pve-node'),
68e0c4bd
DM
1329 service => {
1330 description => 'Ceph service name.',
1331 type => 'string',
1332 optional => 1,
b0e5ae21 1333 pattern => '(mon|mds|osd|mgr)\.[A-Za-z0-9\-]{1,32}',
68e0c4bd 1334 },
38db610a
DM
1335 },
1336 },
68e0c4bd 1337 returns => { type => 'string' },
38db610a
DM
1338 code => sub {
1339 my ($param) = @_;
1340
68e0c4bd
DM
1341 my $rpcenv = PVE::RPCEnvironment::get();
1342
1343 my $authuser = $rpcenv->get_user();
1344
a34866f0 1345 PVE::CephTools::check_ceph_inited();
38db610a 1346
a34866f0 1347 my $cfg = PVE::CephTools::parse_ceph_config();
38db610a
DM
1348 scalar(keys %$cfg) || die "no configuration\n";
1349
68e0c4bd
DM
1350 my $worker = sub {
1351 my $upid = shift;
38db610a 1352
68e0c4bd
DM
1353 my $cmd = ['start'];
1354 if ($param->{service}) {
1355 push @$cmd, $param->{service};
1356 }
1357
a34866f0 1358 PVE::CephTools::ceph_service_cmd(@$cmd);
68e0c4bd
DM
1359 };
1360
1361 return $rpcenv->fork_worker('srvstart', $param->{service} || 'ceph',
1362 $authuser, $worker);
38db610a
DM
1363 }});
1364
1365__PACKAGE__->register_method ({
1366 name => 'status',
1367 path => 'status',
1368 method => 'GET',
1369 description => "Get ceph status.",
1370 proxyto => 'node',
1371 protected => 1,
90c75580
TL
1372 permissions => {
1373 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
1374 },
38db610a 1375 parameters => {
be753927 1376 additionalProperties => 0,
38db610a
DM
1377 properties => {
1378 node => get_standard_option('pve-node'),
1379 },
1380 },
1381 returns => { type => 'object' },
1382 code => sub {
1383 my ($param) = @_;
1384
a34866f0 1385 PVE::CephTools::check_ceph_enabled();
38db610a 1386
36fd0190 1387 my $rados = PVE::RADOS->new();
84caf265
DC
1388 my $status = $rados->mon_command({ prefix => 'status' });
1389 $status->{health} = $rados->mon_command({ prefix => 'health', detail => 'detail' });
1390 return $status;
38db610a
DM
1391 }});
1392
b0537f7b
DM
1393__PACKAGE__->register_method ({
1394 name => 'lspools',
1395 path => 'pools',
1396 method => 'GET',
1397 description => "List all pools.",
1398 proxyto => 'node',
1399 protected => 1,
90c75580
TL
1400 permissions => {
1401 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
1402 },
b0537f7b 1403 parameters => {
be753927 1404 additionalProperties => 0,
b0537f7b
DM
1405 properties => {
1406 node => get_standard_option('pve-node'),
1407 },
1408 },
1409 returns => {
1410 type => 'array',
1411 items => {
1412 type => "object",
1413 properties => {
1414 pool => { type => 'integer' },
1415 pool_name => { type => 'string' },
1416 size => { type => 'integer' },
1417 },
1418 },
1419 links => [ { rel => 'child', href => "{pool_name}" } ],
1420 },
1421 code => sub {
1422 my ($param) = @_;
1423
a34866f0 1424 PVE::CephTools::check_ceph_inited();
b0537f7b 1425
36fd0190 1426 my $rados = PVE::RADOS->new();
d54f1126
DM
1427
1428 my $stats = {};
1429 my $res = $rados->mon_command({ prefix => 'df' });
6f9ea1c1
WL
1430 my $total = $res->{stats}->{total_avail_bytes} || 0;
1431
d54f1126
DM
1432 foreach my $d (@{$res->{pools}}) {
1433 next if !$d->{stats};
1434 next if !defined($d->{id});
1435 $stats->{$d->{id}} = $d->{stats};
1436 }
1437
1438 $res = $rados->mon_command({ prefix => 'osd dump' });
b0537f7b
DM
1439
1440 my $data = [];
1441 foreach my $e (@{$res->{pools}}) {
1442 my $d = {};
2db28c03 1443 foreach my $attr (qw(pool pool_name size min_size pg_num crush_rule)) {
b0537f7b
DM
1444 $d->{$attr} = $e->{$attr} if defined($e->{$attr});
1445 }
d54f1126
DM
1446 if (my $s = $stats->{$d->{pool}}) {
1447 $d->{bytes_used} = $s->{bytes_used};
6f9ea1c1
WL
1448 $d->{percent_used} = ($s->{bytes_used} / $total)*100
1449 if $s->{max_avail} && $total;
d54f1126 1450 }
b0537f7b
DM
1451 push @$data, $d;
1452 }
1453
d54f1126 1454
b0537f7b
DM
1455 return $data;
1456 }});
1457
1458__PACKAGE__->register_method ({
1459 name => 'createpool',
7d4fc5ef 1460 path => 'pools',
38db610a 1461 method => 'POST',
7d4fc5ef 1462 description => "Create POOL",
38db610a
DM
1463 proxyto => 'node',
1464 protected => 1,
90c75580
TL
1465 permissions => {
1466 check => ['perm', '/', [ 'Sys.Modify' ]],
1467 },
38db610a 1468 parameters => {
be753927 1469 additionalProperties => 0,
38db610a
DM
1470 properties => {
1471 node => get_standard_option('pve-node'),
7d4fc5ef
DM
1472 name => {
1473 description => "The name of the pool. It must be unique.",
38db610a 1474 type => 'string',
43d85563 1475 },
7d4fc5ef
DM
1476 size => {
1477 description => 'Number of replicas per object',
1478 type => 'integer',
1479 default => 2,
0e5816e4 1480 optional => 1,
7d4fc5ef 1481 minimum => 1,
83663637 1482 maximum => 7,
0e5816e4 1483 },
7d4fc5ef
DM
1484 min_size => {
1485 description => 'Minimum number of replicas per object',
1486 type => 'integer',
1487 default => 1,
1488 optional => 1,
1489 minimum => 1,
83663637 1490 maximum => 7,
7d4fc5ef
DM
1491 },
1492 pg_num => {
1493 description => "Number of placement groups.",
1494 type => 'integer',
1495 default => 64,
1496 optional => 1,
1497 minimum => 8,
1498 maximum => 32768,
1499 },
2db28c03
DC
1500 crush_rule => {
1501 description => "The rule to use for mapping object placement in the cluster.",
1502 type => 'string',
43d85563
DM
1503 optional => 1,
1504 },
38db610a
DM
1505 },
1506 },
7d4fc5ef 1507 returns => { type => 'null' },
38db610a
DM
1508 code => sub {
1509 my ($param) = @_;
1510
a34866f0 1511 PVE::CephTools::check_ceph_inited();
38db610a 1512
7d4fc5ef 1513 my $pve_ckeyring_path = PVE::CephTools::get_config('pve_ckeyring_path');
13f4d762 1514
be753927 1515 die "not fully configured - missing '$pve_ckeyring_path'\n"
7d4fc5ef 1516 if ! -f $pve_ckeyring_path;
13f4d762 1517
7d4fc5ef
DM
1518 my $pg_num = $param->{pg_num} || 64;
1519 my $size = $param->{size} || 2;
1520 my $min_size = $param->{min_size} || 1;
36fd0190 1521 my $rados = PVE::RADOS->new();
38db610a 1522
be753927 1523 $rados->mon_command({
7d4fc5ef
DM
1524 prefix => "osd pool create",
1525 pool => $param->{name},
1526 pg_num => int($pg_num),
7d4fc5ef
DM
1527 format => 'plain',
1528 });
52d7be41 1529
be753927 1530 $rados->mon_command({
7d4fc5ef
DM
1531 prefix => "osd pool set",
1532 pool => $param->{name},
1533 var => 'min_size',
1534 val => $min_size,
1535 format => 'plain',
1536 });
a34866f0 1537
be753927 1538 $rados->mon_command({
7d4fc5ef
DM
1539 prefix => "osd pool set",
1540 pool => $param->{name},
1541 var => 'size',
1542 val => $size,
1543 format => 'plain',
1544 });
0e5816e4 1545
2db28c03 1546 if (defined($param->{crush_rule})) {
be753927 1547 $rados->mon_command({
7d4fc5ef
DM
1548 prefix => "osd pool set",
1549 pool => $param->{name},
2db28c03
DC
1550 var => 'crush_rule',
1551 val => $param->{crush_rule},
1552 format => 'plain',
7d4fc5ef
DM
1553 });
1554 }
52d7be41 1555
7d4fc5ef 1556 return undef;
38db610a
DM
1557 }});
1558
a46ad02a
DC
1559__PACKAGE__->register_method ({
1560 name => 'get_flags',
1561 path => 'flags',
1562 method => 'GET',
1563 description => "get all set ceph flags",
1564 proxyto => 'node',
1565 protected => 1,
1566 permissions => {
1567 check => ['perm', '/', [ 'Sys.Audit' ]],
1568 },
1569 parameters => {
1570 additionalProperties => 0,
1571 properties => {
1572 node => get_standard_option('pve-node'),
1573 },
1574 },
1575 returns => { type => 'string' },
1576 code => sub {
1577 my ($param) = @_;
1578
1579 PVE::CephTools::check_ceph_inited();
1580
1581 my $pve_ckeyring_path = PVE::CephTools::get_config('pve_ckeyring_path');
1582
1583 die "not fully configured - missing '$pve_ckeyring_path'\n"
1584 if ! -f $pve_ckeyring_path;
1585
1586 my $rados = PVE::RADOS->new();
1587
1588 my $stat = $rados->mon_command({ prefix => 'osd dump' });
1589
1590 return $stat->{flags} // '';
1591 }});
1592
1593__PACKAGE__->register_method ({
1594 name => 'set_flag',
1595 path => 'flags/{flag}',
1596 method => 'POST',
1597 description => "Set a ceph flag",
1598 proxyto => 'node',
1599 protected => 1,
1600 permissions => {
1601 check => ['perm', '/', [ 'Sys.Modify' ]],
1602 },
1603 parameters => {
1604 additionalProperties => 0,
1605 properties => {
1606 node => get_standard_option('pve-node'),
1607 flag => {
1608 description => 'The ceph flag to set/unset',
1609 type => 'string',
1610 enum => [ 'full', 'pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill', 'norebalance', 'norecover', 'noscrub', 'nodeep-scrub', 'notieragent'],
1611 },
1612 },
1613 },
1614 returns => { type => 'null' },
1615 code => sub {
1616 my ($param) = @_;
1617
1618 PVE::CephTools::check_ceph_inited();
1619
1620 my $pve_ckeyring_path = PVE::CephTools::get_config('pve_ckeyring_path');
1621
1622 die "not fully configured - missing '$pve_ckeyring_path'\n"
1623 if ! -f $pve_ckeyring_path;
1624
1625 my $set = $param->{set} // !$param->{unset};
1626 my $rados = PVE::RADOS->new();
1627
1628 $rados->mon_command({
1629 prefix => "osd set",
1630 key => $param->{flag},
1631 });
1632
1633 return undef;
1634 }});
1635
1636__PACKAGE__->register_method ({
1637 name => 'unset_flag',
1638 path => 'flags/{flag}',
1639 method => 'DELETE',
1640 description => "Unset a ceph flag",
1641 proxyto => 'node',
1642 protected => 1,
1643 permissions => {
1644 check => ['perm', '/', [ 'Sys.Modify' ]],
1645 },
1646 parameters => {
1647 additionalProperties => 0,
1648 properties => {
1649 node => get_standard_option('pve-node'),
1650 flag => {
1651 description => 'The ceph flag to set/unset',
1652 type => 'string',
1653 enum => [ 'full', 'pause', 'noup', 'nodown', 'noout', 'noin', 'nobackfill', 'norebalance', 'norecover', 'noscrub', 'nodeep-scrub', 'notieragent'],
1654 },
1655 },
1656 },
1657 returns => { type => 'null' },
1658 code => sub {
1659 my ($param) = @_;
1660
1661 PVE::CephTools::check_ceph_inited();
1662
1663 my $pve_ckeyring_path = PVE::CephTools::get_config('pve_ckeyring_path');
1664
1665 die "not fully configured - missing '$pve_ckeyring_path'\n"
1666 if ! -f $pve_ckeyring_path;
1667
1668 my $set = $param->{set} // !$param->{unset};
1669 my $rados = PVE::RADOS->new();
1670
1671 $rados->mon_command({
1672 prefix => "osd unset",
1673 key => $param->{flag},
1674 });
1675
1676 return undef;
1677 }});
1678
38db610a 1679__PACKAGE__->register_method ({
7d4fc5ef
DM
1680 name => 'destroypool',
1681 path => 'pools/{name}',
39e1ad70 1682 method => 'DELETE',
7d4fc5ef 1683 description => "Destroy pool",
38db610a
DM
1684 proxyto => 'node',
1685 protected => 1,
90c75580
TL
1686 permissions => {
1687 check => ['perm', '/', [ 'Sys.Modify' ]],
1688 },
38db610a 1689 parameters => {
be753927 1690 additionalProperties => 0,
38db610a
DM
1691 properties => {
1692 node => get_standard_option('pve-node'),
7d4fc5ef
DM
1693 name => {
1694 description => "The name of the pool. It must be unique.",
1695 type => 'string',
0e5816e4 1696 },
76dc2ad0
DC
1697 force => {
1698 description => "If true, destroys pool even if in use",
1699 type => 'boolean',
1700 optional => 1,
1701 default => 0,
1702 }
38db610a
DM
1703 },
1704 },
7d4fc5ef 1705 returns => { type => 'null' },
38db610a
DM
1706 code => sub {
1707 my ($param) = @_;
1708
a34866f0 1709 PVE::CephTools::check_ceph_inited();
38db610a 1710
76dc2ad0
DC
1711 # if not forced, destroy ceph pool only when no
1712 # vm disks are on it anymore
1713 if (!$param->{force}) {
1714 my $storagecfg = PVE::Storage::config();
1715 foreach my $storageid (keys %{$storagecfg->{ids}}) {
1716 my $storage = $storagecfg->{ids}->{$storageid};
1717 next if $storage->{type} ne 'rbd';
1718 next if $storage->{pool} ne $param->{name};
1719
1720 # check if any vm disks are on the pool
1721 my $res = PVE::Storage::vdisk_list($storagecfg, $storageid);
1722 die "ceph pool '$param->{name}' still in use by storage '$storageid'\n"
1723 if @{$res->{$storageid}} != 0;
1724 }
1725 }
1726
36fd0190 1727 my $rados = PVE::RADOS->new();
7d4fc5ef 1728 # fixme: '--yes-i-really-really-mean-it'
be753927 1729 $rados->mon_command({
7d4fc5ef
DM
1730 prefix => "osd pool delete",
1731 pool => $param->{name},
1732 pool2 => $param->{name},
1733 sure => '--yes-i-really-really-mean-it',
1734 format => 'plain',
1735 });
52d7be41 1736
7d4fc5ef 1737 return undef;
38db610a 1738 }});
2f692121 1739
a34866f0 1740
2f692121
DM
1741__PACKAGE__->register_method ({
1742 name => 'crush',
1743 path => 'crush',
1744 method => 'GET',
1745 description => "Get OSD crush map",
1746 proxyto => 'node',
1747 protected => 1,
90c75580
TL
1748 permissions => {
1749 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
1750 },
2f692121 1751 parameters => {
be753927 1752 additionalProperties => 0,
2f692121
DM
1753 properties => {
1754 node => get_standard_option('pve-node'),
1755 },
1756 },
1757 returns => { type => 'string' },
1758 code => sub {
1759 my ($param) = @_;
1760
a34866f0 1761 PVE::CephTools::check_ceph_inited();
2f692121 1762
8b336060
DM
1763 # this produces JSON (difficult to read for the user)
1764 # my $txt = &$run_ceph_cmd_text(['osd', 'crush', 'dump'], quiet => 1);
2f692121 1765
8b336060
DM
1766 my $txt = '';
1767
1768 my $mapfile = "/var/tmp/ceph-crush.map.$$";
1769 my $mapdata = "/var/tmp/ceph-crush.txt.$$";
1770
36fd0190 1771 my $rados = PVE::RADOS->new();
be753927 1772
8b336060 1773 eval {
970236b3
DM
1774 my $bindata = $rados->mon_command({ prefix => 'osd getcrushmap', format => 'plain' });
1775 PVE::Tools::file_set_contents($mapfile, $bindata);
8b336060
DM
1776 run_command(['crushtool', '-d', $mapfile, '-o', $mapdata]);
1777 $txt = PVE::Tools::file_get_contents($mapdata);
1778 };
1779 my $err = $@;
1780
1781 unlink $mapfile;
1782 unlink $mapdata;
1783
1784 die $err if $err;
be753927 1785
2f692121
DM
1786 return $txt;
1787 }});
1788
570278fa 1789__PACKAGE__->register_method({
be753927
DC
1790 name => 'log',
1791 path => 'log',
570278fa
DM
1792 method => 'GET',
1793 description => "Read ceph log",
1794 proxyto => 'node',
1795 permissions => {
1796 check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
1797 },
1798 protected => 1,
1799 parameters => {
be753927 1800 additionalProperties => 0,
570278fa
DM
1801 properties => {
1802 node => get_standard_option('pve-node'),
1803 start => {
1804 type => 'integer',
1805 minimum => 0,
1806 optional => 1,
1807 },
1808 limit => {
1809 type => 'integer',
1810 minimum => 0,
1811 optional => 1,
1812 },
1813 },
1814 },
1815 returns => {
1816 type => 'array',
be753927 1817 items => {
570278fa
DM
1818 type => "object",
1819 properties => {
1820 n => {
1821 description=> "Line number",
1822 type=> 'integer',
1823 },
1824 t => {
1825 description=> "Line text",
1826 type => 'string',
1827 }
1828 }
1829 }
1830 },
1831 code => sub {
1832 my ($param) = @_;
1833
1834 my $rpcenv = PVE::RPCEnvironment::get();
1835 my $user = $rpcenv->get_user();
1836 my $node = $param->{node};
1837
1838 my $logfile = "/var/log/ceph/ceph.log";
1839 my ($count, $lines) = PVE::Tools::dump_logfile($logfile, $param->{start}, $param->{limit});
1840
1841 $rpcenv->set_result_attrib('total', $count);
be753927
DC
1842
1843 return $lines;
570278fa
DM
1844 }});
1845
d2692b86
DC
1846__PACKAGE__->register_method ({
1847 name => 'rules',
1848 path => 'rules',
1849 method => 'GET',
1850 description => "List ceph rules.",
1851 proxyto => 'node',
1852 protected => 1,
1853 permissions => {
1854 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
1855 },
1856 parameters => {
1857 additionalProperties => 0,
1858 properties => {
1859 node => get_standard_option('pve-node'),
1860 },
1861 },
1862 returns => {
1863 type => 'array',
1864 items => {
1865 type => "object",
1866 properties => {},
1867 },
1868 links => [ { rel => 'child', href => "{name}" } ],
1869 },
1870 code => sub {
1871 my ($param) = @_;
1872
1873 PVE::CephTools::check_ceph_inited();
2f692121 1874
d2692b86
DC
1875 my $rados = PVE::RADOS->new();
1876
1877 my $rules = $rados->mon_command({ prefix => 'osd crush rule ls' });
1878
1879 my $res = [];
1880
1881 foreach my $rule (@$rules) {
1882 push @$res, { name => $rule };
1883 }
1884
1885 return $res;
1886 }});