]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Ceph.pm
pveceph: allow to set min_size for pools
[pve-manager.git] / PVE / API2 / Ceph.pm
CommitLineData
38db610a
DM
1package PVE::API2::Ceph;
2
3use strict;
4use warnings;
5use File::Basename;
6use File::Path;
7use POSIX qw (LONG_MAX);
13f4d762
DM
8use Cwd qw(abs_path);
9use IO::Dir;
eac465a5 10use UUID;
f7e342ea 11use Net::IP;
38db610a
DM
12
13use PVE::SafeSyslog;
13f4d762 14use PVE::Tools qw(extract_param run_command file_get_contents file_read_firstline dir_glob_regex dir_glob_foreach);
38db610a
DM
15use PVE::Exception qw(raise raise_param_exc);
16use PVE::INotify;
17use PVE::Cluster qw(cfs_lock_file cfs_read_file cfs_write_file);
18use PVE::AccessControl;
19use PVE::Storage;
20use PVE::RESTHandler;
21use PVE::RPCEnvironment;
22use PVE::JSONSchema qw(get_standard_option);
23use JSON;
24
25use base qw(PVE::RESTHandler);
26
27use Data::Dumper; # fixme: remove
28
29my $ccname = 'ceph'; # ceph cluster name
30my $ceph_cfgdir = "/etc/ceph";
31my $pve_ceph_cfgpath = "/etc/pve/$ccname.conf";
32my $ceph_cfgpath = "$ceph_cfgdir/$ccname.conf";
33my $pve_mon_key_path = "/etc/pve/priv/$ccname.mon.keyring";
f3224487 34my $pve_ckeyring_path = "/etc/pve/priv/$ccname.client.admin.keyring";
38db610a
DM
35
36my $ceph_bootstrap_osd_keyring = "/var/lib/ceph/bootstrap-osd/$ccname.keyring";
37my $ceph_bootstrap_mds_keyring = "/var/lib/ceph/bootstrap-mds/$ccname.keyring";
38
39my $ceph_bin = "/usr/bin/ceph";
40
41sub purge_all_ceph_files {
42 # fixme: this is very dangerous - should we really support this function?
43
44 unlink $ceph_cfgpath;
38db610a
DM
45
46 unlink $pve_ceph_cfgpath;
47 unlink $pve_ckeyring_path;
48 unlink $pve_mon_key_path;
49
50 unlink $ceph_bootstrap_osd_keyring;
51 unlink $ceph_bootstrap_mds_keyring;
52
53 system("rm -rf /var/lib/ceph/mon/ceph-*");
54
55 # remove osd?
56
57}
58
59my $check_ceph_installed = sub {
60 my ($noerr) = @_;
61
62 if (! -x $ceph_bin) {
63 die "ceph binaries not installed\n" if !$noerr;
64 return undef;
65 }
66
67 return 1;
68};
69
70my $check_ceph_inited = sub {
71 my ($noerr) = @_;
72
73 return undef if !&$check_ceph_installed($noerr);
74
75 if (! -f $pve_ceph_cfgpath) {
76 die "pveceph configuration not initialized\n" if !$noerr;
77 return undef;
78 }
79
80 return 1;
81};
82
9662c195
DM
83my $check_ceph_enabled = sub {
84 my ($noerr) = @_;
85
86 return undef if !&$check_ceph_inited($noerr);
87
88 if (! -f $ceph_cfgpath) {
89 die "pveceph configuration not enabled\n" if !$noerr;
90 return undef;
91 }
92
93 return 1;
94};
95
38db610a
DM
96my $parse_ceph_config = sub {
97 my ($filename) = @_;
98
99 my $cfg = {};
100
101 return $cfg if ! -f $filename;
102
103 my $fh = IO::File->new($filename, "r") ||
104 die "unable to open '$filename' - $!\n";
105
106 my $section;
107
108 while (defined(my $line = <$fh>)) {
109 $line =~ s/[;#].*$//;
110 $line =~ s/^\s+//;
111 $line =~ s/\s+$//;
112 next if !$line;
113
114 $section = $1 if $line =~ m/^\[(\S+)\]$/;
115 if (!$section) {
116 warn "no section - skip: $line\n";
117 next;
118 }
119
120 if ($line =~ m/^(.*\S)\s*=\s*(\S.*)$/) {
121 $cfg->{$section}->{$1} = $2;
122 }
123
124 }
125
126 return $cfg;
127};
128
129my $run_ceph_cmd = sub {
130 my ($cmd, %params) = @_;
c56a583f
DM
131
132 my $timeout = 5;
38db610a 133
c56a583f
DM
134 run_command(['ceph', '-c', $pve_ceph_cfgpath,
135 '--connect-timeout', $timeout,
136 @$cmd], %params);
38db610a
DM
137};
138
2f692121 139my $run_ceph_cmd_text = sub {
9662c195 140 my ($cmd, %opts) = @_;
38db610a 141
2f692121 142 my $out = '';
9662c195
DM
143
144 my $quiet = delete $opts{quiet};
145
38db610a
DM
146 my $parser = sub {
147 my $line = shift;
2f692121 148 $out .= "$line\n";
38db610a
DM
149 };
150
151 my $errfunc = sub {
152 my $line = shift;
153 print "$line\n" if !$quiet;
154 };
155
2f692121
DM
156 &$run_ceph_cmd($cmd, outfunc => $parser, errfunc => $errfunc);
157
158 return $out;
159};
160
161my $run_ceph_cmd_json = sub {
162 my ($cmd, %opts) = @_;
38db610a 163
2f692121 164 my $json = &$run_ceph_cmd_text([@$cmd, '--format', 'json'], %opts);
38db610a 165
2f692121 166 return decode_json($json);
9662c195 167};
38db610a 168
9662c195 169sub ceph_mon_status {
38db610a 170 my ($quiet) = @_;
9662c195
DM
171
172 return &$run_ceph_cmd_json(['mon_status'], quiet => $quiet);
38db610a 173
9662c195 174}
38db610a 175
9662c195
DM
176my $ceph_osd_status = sub {
177 my ($quiet) = @_;
38db610a 178
9662c195 179 return &$run_ceph_cmd_json(['osd', 'dump'], quiet => $quiet);
38db610a
DM
180};
181
182my $write_ceph_config = sub {
183 my ($cfg) = @_;
184
185 my $out = '';
e9ea4677
DM
186
187 my $cond_write_sec = sub {
188 my $re = shift;
189
190 foreach my $section (keys %$cfg) {
191 next if $section !~ m/^$re$/;
192 $out .= "[$section]\n";
193 foreach my $key (sort keys %{$cfg->{$section}}) {
194 $out .= "\t $key = $cfg->{$section}->{$key}\n";
195 }
196 $out .= "\n";
38db610a 197 }
e9ea4677
DM
198 };
199
200 &$cond_write_sec('global');
201 &$cond_write_sec('mon');
202 &$cond_write_sec('osd');
203 &$cond_write_sec('mon\..*');
204 &$cond_write_sec('osd\..*');
38db610a
DM
205
206 PVE::Tools::file_set_contents($pve_ceph_cfgpath, $out);
207};
208
209my $setup_pve_symlinks = sub {
210 # fail if we find a real file instead of a link
211 if (-f $ceph_cfgpath) {
212 my $lnk = readlink($ceph_cfgpath);
213 die "file '$ceph_cfgpath' already exists\n"
214 if !$lnk || $lnk ne $pve_ceph_cfgpath;
f3224487
DM
215 } else {
216 symlink($pve_ceph_cfgpath, $ceph_cfgpath) ||
217 die "unable to create symlink '$ceph_cfgpath' - $!\n";
38db610a 218 }
38db610a
DM
219};
220
221my $ceph_service_cmd = sub {
f3224487 222 run_command(['service', 'ceph', '-c', $pve_ceph_cfgpath, @_]);
38db610a
DM
223};
224
13f4d762
DM
225
226sub list_disks {
227 my $disklist = {};
228
229 my $fd = IO::File->new("/proc/mounts", "r") ||
230 die "unable to open /proc/mounts - $!\n";
231
232 my $mounted = {};
233
234 while (defined(my $line = <$fd>)) {
235 my ($dev, $path, $fstype) = split(/\s+/, $line);
236 next if !($dev && $path && $fstype);
237 next if $dev !~ m|^/dev/|;
238 my $real_dev = abs_path($dev);
239 $mounted->{$real_dev} = $path;
240 }
241 close($fd);
242
243 my $dev_is_mounted = sub {
244 my ($dev) = @_;
245 return $mounted->{$dev};
246 };
247
248 my $dir_is_epmty = sub {
249 my ($dir) = @_;
250
251 my $dh = IO::Dir->new ($dir);
252 return 1 if !$dh;
253
254 while (defined(my $tmp = $dh->read)) {
255 next if $tmp eq '.' || $tmp eq '..';
256 $dh->close;
257 return 0;
258 }
259 $dh->close;
260 return 1;
261 };
262
263 dir_glob_foreach('/sys/block', '.*', sub {
264 my ($dev) = @_;
265
266 return if $dev eq '.';
267 return if $dev eq '..';
268
269 return if $dev =~ m|^ram\d+$|; # skip ram devices
270 return if $dev =~ m|^loop\d+$|; # skip loop devices
271 return if $dev =~ m|^md\d+$|; # skip md devices
272 return if $dev =~ m|^dm-.*$|; # skip dm related things
273 return if $dev =~ m|^fd\d+$|; # skip Floppy
274 return if $dev =~ m|^sr\d+$|; # skip CDs
275
276 my $devdir = "/sys/block/$dev/device";
277 return if ! -d $devdir;
278
279 my $size = file_read_firstline("/sys/block/$dev/size");
280 return if !$size;
281
282 $size = $size * 512;
283
284 my $info = `udevadm info --path /sys/block/$dev --query all`;
285 return if !$info;
286
287 return if $info !~ m/^E: DEVTYPE=disk$/m;
288 return if $info =~ m/^E: ID_CDROM/m;
289
290 my $serial = 'unknown';
291 if ($info =~ m/^E: ID_SERIAL_SHORT=(\S+)$/m) {
292 $serial = $1;
293 }
294
295 my $vendor = file_read_firstline("$devdir/vendor") || 'unknown';
296 my $model = file_read_firstline("$devdir/model") || 'unknown';
297
298 my $used = &$dir_is_epmty("/sys/block/$dev/holders") ? 0 : 1;
299
300 $used = 1 if &$dev_is_mounted("/dev/$dev");
301
302 $disklist->{$dev} = {
303 vendor => $vendor,
304 model => $model,
305 size => $size,
306 serial => $serial,
307 };
308
309 my $osdid = -1;
310
311 dir_glob_foreach("/sys/block/$dev", "$dev.+", sub {
312 my ($part) = @_;
313 if (!&$dir_is_epmty("/sys/block/$dev/$part/holders")) {
314 $used = 1;
315 }
316 if (my $mp = &$dev_is_mounted("/dev/$part")) {
317 $used = 1;
318 if ($mp =~ m|^/var/lib/ceph/osd/ceph-(\d+)$|) {
319 $osdid = $1;
320 }
321 }
322 });
323
324 $disklist->{$dev}->{used} = $used;
325 $disklist->{$dev}->{osdid} = $osdid;
326 });
327
328 return $disklist;
329}
330
38db610a
DM
331__PACKAGE__->register_method ({
332 name => 'index',
333 path => '',
334 method => 'GET',
335 description => "Directory index.",
336 permissions => { user => 'all' },
337 parameters => {
338 additionalProperties => 0,
339 properties => {
340 node => get_standard_option('pve-node'),
341 },
342 },
343 returns => {
344 type => 'array',
345 items => {
346 type => "object",
347 properties => {},
348 },
349 links => [ { rel => 'child', href => "{name}" } ],
350 },
351 code => sub {
352 my ($param) = @_;
353
354 my $result = [
355 { name => 'init' },
39e1ad70
DM
356 { name => 'mon' },
357 { name => 'osd' },
b0537f7b 358 { name => 'pools' },
38db610a
DM
359 { name => 'stop' },
360 { name => 'start' },
361 { name => 'status' },
2f692121 362 { name => 'crush' },
68e0c4bd 363 { name => 'config' },
570278fa 364 { name => 'log' },
13f4d762 365 { name => 'disks' },
38db610a
DM
366 ];
367
368 return $result;
369 }});
370
13f4d762
DM
371__PACKAGE__->register_method ({
372 name => 'disks',
373 path => 'disks',
374 method => 'GET',
375 description => "List local disks.",
376 proxyto => 'node',
377 protected => 1,
378 parameters => {
379 additionalProperties => 0,
380 properties => {
381 node => get_standard_option('pve-node'),
382 },
383 },
384 returns => {
385 type => 'array',
386 items => {
387 type => "object",
388 properties => {
389 dev => { type => 'string' },
390 used => { type => 'boolean' },
391 size => { type => 'integer' },
392 osdid => { type => 'integer' },
393 vendor => { type => 'string', optional => 1 },
394 model => { type => 'string', optional => 1 },
395 serial => { type => 'string', optional => 1 },
396 },
397 },
398 # links => [ { rel => 'child', href => "{}" } ],
399 },
400 code => sub {
401 my ($param) = @_;
402
403 &$check_ceph_inited();
404
405 my $res = list_disks();
406
407 return PVE::RESTHandler::hash_to_array($res, 'dev');
408 }});
409
68e0c4bd
DM
410__PACKAGE__->register_method ({
411 name => 'config',
412 path => 'config',
413 method => 'GET',
414 description => "Get Ceph configuration.",
415 parameters => {
416 additionalProperties => 0,
417 properties => {
418 node => get_standard_option('pve-node'),
419 },
420 },
421 returns => { type => 'string' },
422 code => sub {
423 my ($param) = @_;
424
425 &$check_ceph_inited();
426
427 return PVE::Tools::file_get_contents($pve_ceph_cfgpath);
428
429 }});
430
431__PACKAGE__->register_method ({
432 name => 'listmon',
39e1ad70 433 path => 'mon',
68e0c4bd
DM
434 method => 'GET',
435 description => "Get Ceph monitor list.",
436 proxyto => 'node',
437 protected => 1,
438 parameters => {
439 additionalProperties => 0,
440 properties => {
441 node => get_standard_option('pve-node'),
442 },
443 },
444 returns => {
445 type => 'array',
446 items => {
447 type => "object",
448 properties => {
449 name => { type => 'string' },
450 addr => { type => 'string' },
451 },
452 },
39e1ad70 453 links => [ { rel => 'child', href => "{name}" } ],
68e0c4bd
DM
454 },
455 code => sub {
456 my ($param) = @_;
457
458 &$check_ceph_inited();
459
460 my $res = [];
461
462 my $cfg = &$parse_ceph_config($pve_ceph_cfgpath);
463
464 my $monhash = {};
465 foreach my $section (keys %$cfg) {
466 my $d = $cfg->{$section};
467 if ($section =~ m/^mon\.(\S+)$/) {
468 my $monid = $1;
469 if ($d->{'mon addr'} && $d->{'host'}) {
470 $monhash->{$monid} = {
471 addr => $d->{'mon addr'},
472 host => $d->{'host'},
473 name => $monid,
474 }
475 }
476 }
477 }
478
479 eval {
480 my $monstat = ceph_mon_status();
481 my $mons = $monstat->{monmap}->{mons};
482 foreach my $d (@$mons) {
483 next if !defined($d->{name});
484 $monhash->{$d->{name}}->{rank} = $d->{rank};
485 $monhash->{$d->{name}}->{addr} = $d->{addr};
486 if (grep { $_ eq $d->{rank} } @{$monstat->{quorum}}) {
487 $monhash->{$d->{name}}->{quorum} = 1;
488 }
489 }
490 };
491 warn $@ if $@;
492
493 return PVE::RESTHandler::hash_to_array($monhash, 'name');
494 }});
495
38db610a
DM
496__PACKAGE__->register_method ({
497 name => 'init',
498 path => 'init',
499 method => 'POST',
a08988dc 500 description => "Create initial ceph default configuration and setup symlinks.",
38db610a
DM
501 proxyto => 'node',
502 protected => 1,
503 parameters => {
504 additionalProperties => 0,
505 properties => {
506 node => get_standard_option('pve-node'),
f7e342ea
DM
507 network => {
508 description => "Use specific network for all ceph related traffic",
509 type => 'string', format => 'CIDR',
510 optional => 1,
511 maxLength => 128,
512 },
38db610a
DM
513 size => {
514 description => 'Number of replicas per object',
515 type => 'integer',
516 default => 2,
517 optional => 1,
518 minimum => 1,
519 maximum => 3,
520 },
521 pg_bits => {
522 description => "Placement group bits, used to specify the default number of placement groups (Note: 'osd pool default pg num' does not work for deafult pools)",
523 type => 'integer',
a08988dc 524 default => 6,
38db610a
DM
525 optional => 1,
526 minimum => 6,
527 maximum => 14,
528 },
529 },
530 },
531 returns => { type => 'null' },
532 code => sub {
533 my ($param) = @_;
534
535 &$check_ceph_installed();
536
a08988dc
DM
537 # simply load old config if it already exists
538 my $cfg = &$parse_ceph_config($pve_ceph_cfgpath);
38db610a 539
a08988dc
DM
540 if (!$cfg->{global}) {
541
542 my $fsid;
543 my $uuid;
38db610a 544
a08988dc
DM
545 UUID::generate($uuid);
546 UUID::unparse($uuid, $fsid);
547
548 $cfg->{global} = {
549 'fsid' => $fsid,
550 'auth supported' => 'cephx',
551 'auth cluster required' => 'cephx',
552 'auth service required' => 'cephx',
553 'auth client required' => 'cephx',
554 'filestore xattr use omap' => 'true',
555 'osd journal size' => '1024',
556 'osd pool default min size' => 1,
557 };
558
559 # this does not work for default pools
560 #'osd pool default pg num' => $pg_num,
561 #'osd pool default pgp num' => $pg_num,
562 }
563
88a6e29a 564 $cfg->{global}->{keyring} = '/etc/pve/priv/$cluster.$name.keyring';
52d7be41 565 $cfg->{osd}->{keyring} = '/var/lib/ceph/osd/ceph-$id/keyring';
38db610a 566
a08988dc
DM
567 $cfg->{global}->{'osd pool default size'} = $param->{size} if $param->{size};
568
569 if ($param->{pg_bits}) {
570 $cfg->{global}->{'osd pg bits'} = $param->{pg_bits};
571 $cfg->{global}->{'osd pgp bits'} = $param->{pg_bits};
572 }
f7e342ea
DM
573
574 if ($param->{network}) {
575 $cfg->{global}->{'public network'} = $param->{network};
576 $cfg->{global}->{'cluster network'} = $param->{network};
577 }
578
a08988dc 579 &$write_ceph_config($cfg);
38db610a
DM
580
581 &$setup_pve_symlinks();
582
583 return undef;
584 }});
585
f7e342ea
DM
586my $find_node_ip = sub {
587 my ($cidr) = @_;
588
589 my $config = PVE::INotify::read_file('interfaces');
590
591 my $net = Net::IP->new($cidr) || die Net::IP::Error() . "\n";
592
593 foreach my $iface (keys %$config) {
594 my $d = $config->{$iface};
595 next if !$d->{address};
596 my $a = Net::IP->new($d->{address});
597 next if !$a;
598 return $d->{address} if $net->overlaps($a);
599 }
600
601 die "unable to find local address within network '$cidr'\n";
602};
603
38db610a
DM
604__PACKAGE__->register_method ({
605 name => 'createmon',
39e1ad70 606 path => 'mon',
38db610a
DM
607 method => 'POST',
608 description => "Create Ceph Monitor",
609 proxyto => 'node',
610 protected => 1,
611 parameters => {
612 additionalProperties => 0,
613 properties => {
614 node => get_standard_option('pve-node'),
615 },
616 },
52d7be41 617 returns => { type => 'string' },
38db610a
DM
618 code => sub {
619 my ($param) = @_;
620
621 &$check_ceph_inited();
622
623 &$setup_pve_symlinks();
624
52d7be41 625 my $rpcenv = PVE::RPCEnvironment::get();
38db610a 626
52d7be41 627 my $authuser = $rpcenv->get_user();
38db610a 628
38db610a
DM
629 my $cfg = &$parse_ceph_config($pve_ceph_cfgpath);
630
631 my $moncount = 0;
632
633 my $monaddrhash = {};
634
635 foreach my $section (keys %$cfg) {
636 next if $section eq 'global';
637 my $d = $cfg->{$section};
638 if ($section =~ m/^mon\./) {
639 $moncount++;
640 if ($d->{'mon addr'}) {
641 $monaddrhash->{$d->{'mon addr'}} = $section;
642 }
643 }
644 }
645
646 my $monid;
647 for (my $i = 0; $i < 7; $i++) {
648 if (!$cfg->{"mon.$i"}) {
649 $monid = $i;
650 last;
651 }
652 }
653 die "unable to find usable monitor id\n" if !defined($monid);
654
f7e342ea
DM
655 my $monsection = "mon.$monid";
656 my $ip;
657 if (my $pubnet = $cfg->{global}->{'public network'}) {
658 $ip = &$find_node_ip($pubnet);
659 } else {
660 $ip = PVE::Cluster::remote_node_ip($param->{node});
661 }
662
663 my $monaddr = "$ip:6789";
38db610a
DM
664 my $monname = $param->{node};
665
666 die "monitor '$monsection' already exists\n" if $cfg->{$monsection};
667 die "monitor address '$monaddr' already in use by '$monaddrhash->{$monaddr}'\n"
668 if $monaddrhash->{$monaddr};
669
52d7be41
DM
670 my $worker = sub {
671 my $upid = shift;
38db610a 672
52d7be41
DM
673 if (! -f $pve_ckeyring_path) {
674 run_command("ceph-authtool $pve_ckeyring_path --create-keyring " .
675 "--gen-key -n client.admin");
676 }
38db610a 677
52d7be41
DM
678 if (! -f $pve_mon_key_path) {
679 run_command("cp $pve_ckeyring_path $pve_mon_key_path.tmp");
680 run_command("ceph-authtool $pve_mon_key_path.tmp -n client.admin --set-uid=0 " .
681 "--cap mds 'allow' " .
682 "--cap osd 'allow *' " .
683 "--cap mon 'allow *'");
684 run_command("ceph-authtool $pve_mon_key_path.tmp --gen-key -n mon. --cap mon 'allow *'");
685 run_command("mv $pve_mon_key_path.tmp $pve_mon_key_path");
38db610a
DM
686 }
687
52d7be41
DM
688 my $mondir = "/var/lib/ceph/mon/$ccname-$monid";
689 -d $mondir && die "monitor filesystem '$mondir' already exist\n";
690
691 my $monmap = "/tmp/monmap";
692
693 eval {
694 mkdir $mondir;
695
696 if ($moncount > 0) {
697 my $monstat = ceph_mon_status(); # online test
698 &$run_ceph_cmd(['mon', 'getmap', '-o', $monmap]);
699 } else {
700 run_command("monmaptool --create --clobber --add $monid $monaddr --print $monmap");
701 }
38db610a 702
52d7be41
DM
703 run_command("ceph-mon --mkfs -i $monid --monmap $monmap --keyring $pve_mon_key_path");
704 };
705 my $err = $@;
706 unlink $monmap;
707 if ($err) {
708 File::Path::remove_tree($mondir);
709 die $err;
710 }
38db610a 711
52d7be41
DM
712 $cfg->{$monsection} = {
713 'host' => $monname,
714 'mon addr' => $monaddr,
715 };
38db610a 716
52d7be41 717 &$write_ceph_config($cfg);
38db610a 718
52d7be41
DM
719 &$ceph_service_cmd('start', $monsection);
720 };
38db610a 721
52d7be41 722 return $rpcenv->fork_worker('cephcreatemon', $monsection, $authuser, $worker);
38db610a
DM
723 }});
724
725__PACKAGE__->register_method ({
726 name => 'destroymon',
39e1ad70
DM
727 path => 'mon/{monid}',
728 method => 'DELETE',
38db610a
DM
729 description => "Destroy Ceph monitor.",
730 proxyto => 'node',
731 protected => 1,
732 parameters => {
733 additionalProperties => 0,
734 properties => {
735 node => get_standard_option('pve-node'),
736 monid => {
737 description => 'Monitor ID',
738 type => 'integer',
739 },
740 },
741 },
52d7be41 742 returns => { type => 'string' },
38db610a
DM
743 code => sub {
744 my ($param) = @_;
745
52d7be41
DM
746 my $rpcenv = PVE::RPCEnvironment::get();
747
748 my $authuser = $rpcenv->get_user();
749
38db610a
DM
750 &$check_ceph_inited();
751
752 my $cfg = &$parse_ceph_config($pve_ceph_cfgpath);
753
754 my $monid = $param->{monid};
755 my $monsection = "mon.$monid";
756
757 my $monstat = ceph_mon_status();
758 my $monlist = $monstat->{monmap}->{mons};
759
760 die "no such monitor id '$monid'\n"
761 if !defined($cfg->{$monsection});
762
763
764 my $mondir = "/var/lib/ceph/mon/$ccname-$monid";
765 -d $mondir || die "monitor filesystem '$mondir' does not exist on this node\n";
766
767 die "can't remove last monitor\n" if scalar(@$monlist) <= 1;
768
52d7be41
DM
769 my $worker = sub {
770 my $upid = shift;
38db610a 771
52d7be41 772 &$run_ceph_cmd(['mon', 'remove', $monid]);
38db610a 773
52d7be41
DM
774 eval { &$ceph_service_cmd('stop', $monsection); };
775 warn $@ if $@;
38db610a 776
52d7be41
DM
777 delete $cfg->{$monsection};
778 &$write_ceph_config($cfg);
779 File::Path::remove_tree($mondir);
780 };
781
782 return $rpcenv->fork_worker('cephdestroymon', $monsection, $authuser, $worker);
38db610a
DM
783 }});
784
785__PACKAGE__->register_method ({
786 name => 'stop',
787 path => 'stop',
788 method => 'POST',
789 description => "Stop ceph services.",
790 proxyto => 'node',
791 protected => 1,
792 parameters => {
793 additionalProperties => 0,
794 properties => {
795 node => get_standard_option('pve-node'),
68e0c4bd
DM
796 service => {
797 description => 'Ceph service name.',
798 type => 'string',
799 optional => 1,
800 pattern => '(mon|mds|osd)\.[A-Za-z0-9]{1,32}',
801 },
38db610a
DM
802 },
803 },
68e0c4bd 804 returns => { type => 'string' },
38db610a
DM
805 code => sub {
806 my ($param) = @_;
807
68e0c4bd
DM
808 my $rpcenv = PVE::RPCEnvironment::get();
809
810 my $authuser = $rpcenv->get_user();
811
38db610a
DM
812 &$check_ceph_inited();
813
814 my $cfg = &$parse_ceph_config($pve_ceph_cfgpath);
815 scalar(keys %$cfg) || die "no configuration\n";
816
68e0c4bd
DM
817 my $worker = sub {
818 my $upid = shift;
38db610a 819
68e0c4bd
DM
820 my $cmd = ['stop'];
821 if ($param->{service}) {
822 push @$cmd, $param->{service};
823 }
824
825 &$ceph_service_cmd(@$cmd);
826 };
827
828 return $rpcenv->fork_worker('srvstop', $param->{service} || 'ceph',
829 $authuser, $worker);
38db610a
DM
830 }});
831
832__PACKAGE__->register_method ({
833 name => 'start',
834 path => 'start',
835 method => 'POST',
836 description => "Start ceph services.",
837 proxyto => 'node',
838 protected => 1,
839 parameters => {
840 additionalProperties => 0,
841 properties => {
842 node => get_standard_option('pve-node'),
68e0c4bd
DM
843 service => {
844 description => 'Ceph service name.',
845 type => 'string',
846 optional => 1,
847 pattern => '(mon|mds|osd)\.[A-Za-z0-9]{1,32}',
848 },
38db610a
DM
849 },
850 },
68e0c4bd 851 returns => { type => 'string' },
38db610a
DM
852 code => sub {
853 my ($param) = @_;
854
68e0c4bd
DM
855 my $rpcenv = PVE::RPCEnvironment::get();
856
857 my $authuser = $rpcenv->get_user();
858
38db610a
DM
859 &$check_ceph_inited();
860
861 my $cfg = &$parse_ceph_config($pve_ceph_cfgpath);
862 scalar(keys %$cfg) || die "no configuration\n";
863
68e0c4bd
DM
864 my $worker = sub {
865 my $upid = shift;
38db610a 866
68e0c4bd
DM
867 my $cmd = ['start'];
868 if ($param->{service}) {
869 push @$cmd, $param->{service};
870 }
871
872 &$ceph_service_cmd(@$cmd);
873 };
874
875 return $rpcenv->fork_worker('srvstart', $param->{service} || 'ceph',
876 $authuser, $worker);
38db610a
DM
877 }});
878
879__PACKAGE__->register_method ({
880 name => 'status',
881 path => 'status',
882 method => 'GET',
883 description => "Get ceph status.",
884 proxyto => 'node',
885 protected => 1,
886 parameters => {
887 additionalProperties => 0,
888 properties => {
889 node => get_standard_option('pve-node'),
890 },
891 },
892 returns => { type => 'object' },
893 code => sub {
894 my ($param) = @_;
895
9662c195 896 &$check_ceph_enabled();
38db610a 897
9662c195 898 return &$run_ceph_cmd_json(['status'], quiet => 1);
38db610a
DM
899 }});
900
b0537f7b
DM
901__PACKAGE__->register_method ({
902 name => 'lspools',
903 path => 'pools',
904 method => 'GET',
905 description => "List all pools.",
906 proxyto => 'node',
907 protected => 1,
908 parameters => {
909 additionalProperties => 0,
910 properties => {
911 node => get_standard_option('pve-node'),
912 },
913 },
914 returns => {
915 type => 'array',
916 items => {
917 type => "object",
918 properties => {
919 pool => { type => 'integer' },
920 pool_name => { type => 'string' },
921 size => { type => 'integer' },
922 },
923 },
924 links => [ { rel => 'child', href => "{pool_name}" } ],
925 },
926 code => sub {
927 my ($param) = @_;
928
929 &$check_ceph_inited();
930
931 my $res = &$run_ceph_cmd_json(['osd', 'dump'], quiet => 1);
932
933 my $data = [];
934 foreach my $e (@{$res->{pools}}) {
935 my $d = {};
936 foreach my $attr (qw(pool pool_name size min_size pg_num crush_ruleset)) {
937 $d->{$attr} = $e->{$attr} if defined($e->{$attr});
938 }
939 push @$data, $d;
940 }
941
942 return $data;
943 }});
944
945__PACKAGE__->register_method ({
946 name => 'createpool',
947 path => 'pools',
948 method => 'POST',
949 description => "Create POOL",
950 proxyto => 'node',
951 protected => 1,
952 parameters => {
953 additionalProperties => 0,
954 properties => {
955 node => get_standard_option('pve-node'),
956 name => {
957 description => "The name of the pool. It must be unique.",
958 type => 'string',
959 },
c7881bf6
DM
960 size => {
961 description => 'Number of replicas per object',
962 type => 'integer',
963 default => 2,
964 optional => 1,
965 minimum => 1,
966 maximum => 3,
967 },
74089559
DM
968 min_size => {
969 description => 'Minimum number of replicas per object',
970 type => 'integer',
971 default => 1,
972 optional => 1,
973 minimum => 1,
974 maximum => 3,
975 },
b0537f7b
DM
976 pg_num => {
977 description => "Number of placement groups.",
978 type => 'integer',
979 default => 512,
980 optional => 1,
981 minimum => 8,
982 maximum => 32768,
983 },
984 },
985 },
986 returns => { type => 'null' },
987 code => sub {
988 my ($param) = @_;
989
990 &$check_ceph_inited();
991
992 die "not fully configured - missing '$pve_ckeyring_path'\n"
993 if ! -f $pve_ckeyring_path;
994
995 my $pg_num = $param->{pg_num} || 512;
c7881bf6 996 my $size = $param->{size} || 2;
74089559 997 my $min_size = $param->{min_size} || 1;
b0537f7b
DM
998
999 &$run_ceph_cmd(['osd', 'pool', 'create', $param->{name}, $pg_num]);
1000
74089559
DM
1001 &$run_ceph_cmd(['osd', 'pool', 'set', $param->{name}, 'min_size', $min_size]);
1002
c7881bf6
DM
1003 &$run_ceph_cmd(['osd', 'pool', 'set', $param->{name}, 'size', $size]);
1004
b0537f7b
DM
1005 return undef;
1006 }});
1007
1008__PACKAGE__->register_method ({
1009 name => 'destroypool',
1010 path => 'pools/{name}',
1011 method => 'DELETE',
1012 description => "Destroy pool",
1013 proxyto => 'node',
1014 protected => 1,
1015 parameters => {
1016 additionalProperties => 0,
1017 properties => {
1018 node => get_standard_option('pve-node'),
1019 name => {
1020 description => "The name of the pool. It must be unique.",
1021 type => 'string',
1022 },
1023 },
1024 },
1025 returns => { type => 'null' },
1026 code => sub {
1027 my ($param) = @_;
1028
1029 &$check_ceph_inited();
1030
1031 &$run_ceph_cmd(['osd', 'pool', 'delete', $param->{name}, $param->{name}, '--yes-i-really-really-mean-it']);
1032
1033 return undef;
1034 }});
1035
dd7e2a94
DM
1036__PACKAGE__->register_method ({
1037 name => 'listosd',
1038 path => 'osd',
1039 method => 'GET',
1040 description => "Get Ceph osd list/tree.",
1041 proxyto => 'node',
1042 protected => 1,
1043 parameters => {
1044 additionalProperties => 0,
1045 properties => {
1046 node => get_standard_option('pve-node'),
1047 },
1048 },
1049 returns => {
1050 type => "object",
1051 },
1052 code => sub {
1053 my ($param) = @_;
1054
1055 &$check_ceph_inited();
1056
1057 my $res = &$run_ceph_cmd_json(['osd', 'tree'], quiet => 1);
1058
1059 die "no tree nodes found\n" if !($res && $res->{nodes});
1060
1061 my $nodes = {};
1062 my $newnodes = {};
1063 foreach my $e (@{$res->{nodes}}) {
1064 $nodes->{$e->{id}} = $e;
1065
1066 my $new = {
1067 id => $e->{id},
1068 name => $e->{name},
1069 type => $e->{type}
1070 };
1071
1072 foreach my $opt (qw(status crush_weight reweight)) {
1073 $new->{$opt} = $e->{$opt} if defined($e->{$opt});
1074 }
1075
1076 $newnodes->{$e->{id}} = $new;
1077 }
1078
1079 foreach my $e (@{$res->{nodes}}) {
1080 my $new = $newnodes->{$e->{id}};
1081 if ($e->{children} && scalar(@{$e->{children}})) {
1082 $new->{children} = [];
1083 $new->{leaf} = 0;
1084 foreach my $cid (@{$e->{children}}) {
1085 $nodes->{$cid}->{parent} = $e->{id};
1086 if ($nodes->{$cid}->{type} eq 'osd' &&
1087 $e->{type} eq 'host') {
1088 $newnodes->{$cid}->{host} = $e->{name};
1089 }
1090 push @{$new->{children}}, $newnodes->{$cid};
1091 }
1092 } else {
1093 $new->{leaf} = ($e->{id} >= 0) ? 1 : 0;
1094 }
1095 }
1096
1097 my $rootnode;
1098 foreach my $e (@{$res->{nodes}}) {
1099 if (!$nodes->{$e->{id}}->{parent}) {
1100 $rootnode = $newnodes->{$e->{id}};
1101 last;
1102 }
1103 }
1104
1105 die "no root node\n" if !$rootnode;
1106
1107 my $data = { root => $rootnode };
1108
1109 return $data;
1110 }});
1111
38db610a
DM
1112__PACKAGE__->register_method ({
1113 name => 'createosd',
39e1ad70 1114 path => 'osd',
38db610a
DM
1115 method => 'POST',
1116 description => "Create OSD",
1117 proxyto => 'node',
1118 protected => 1,
1119 parameters => {
1120 additionalProperties => 0,
1121 properties => {
1122 node => get_standard_option('pve-node'),
1123 dev => {
1124 description => "Block device name.",
1125 type => 'string',
1126 }
1127 },
1128 },
52d7be41 1129 returns => { type => 'string' },
38db610a
DM
1130 code => sub {
1131 my ($param) = @_;
1132
52d7be41
DM
1133 my $rpcenv = PVE::RPCEnvironment::get();
1134
1135 my $authuser = $rpcenv->get_user();
1136
38db610a
DM
1137 &$check_ceph_inited();
1138
38db610a
DM
1139 &$setup_pve_symlinks();
1140
38db610a
DM
1141 -b $param->{dev} || die "no such block device '$param->{dev}'\n";
1142
13f4d762
DM
1143 my $disklist = list_disks();
1144
1145 my $devname = $param->{dev};
1146 $devname =~ s|/dev/||;
1147
1148 my $diskinfo = $disklist->{$devname};
1149 die "unable to get device info for '$devname'\n"
1150 if !$diskinfo;
1151
1152 die "device '$param->{dev}' is in use\n"
1153 if $diskinfo->{used};
1154
38db610a
DM
1155 my $monstat = ceph_mon_status(1);
1156 die "unable to get fsid\n" if !$monstat->{monmap} || !$monstat->{monmap}->{fsid};
1157 my $fsid = $monstat->{monmap}->{fsid};
1158
1159 if (! -f $ceph_bootstrap_osd_keyring) {
1160 &$run_ceph_cmd(['auth', 'get', 'client.bootstrap-osd', '-o', $ceph_bootstrap_osd_keyring]);
1161 };
1162
52d7be41
DM
1163 my $worker = sub {
1164 my $upid = shift;
38db610a 1165
52d7be41
DM
1166 print "create OSD on $param->{dev}\n";
1167
1168 run_command(['ceph-disk', 'prepare', '--zap-disk', '--fs-type', 'xfs',
1169 '--cluster', $ccname, '--cluster-uuid', $fsid,
1170 '--', $param->{dev}]);
1171 };
1172
cee8e2b6 1173 return $rpcenv->fork_worker('cephcreateosd', $param->{dev}, $authuser, $worker);
38db610a
DM
1174 }});
1175
1176__PACKAGE__->register_method ({
1177 name => 'destroyosd',
39e1ad70
DM
1178 path => 'osd/{osdid}',
1179 method => 'DELETE',
38db610a
DM
1180 description => "Destroy OSD",
1181 proxyto => 'node',
1182 protected => 1,
1183 parameters => {
1184 additionalProperties => 0,
1185 properties => {
1186 node => get_standard_option('pve-node'),
1187 osdid => {
1188 description => 'OSD ID',
1189 type => 'integer',
1190 },
1191 },
1192 },
52d7be41 1193 returns => { type => 'string' },
38db610a
DM
1194 code => sub {
1195 my ($param) = @_;
1196
52d7be41
DM
1197 my $rpcenv = PVE::RPCEnvironment::get();
1198
1199 my $authuser = $rpcenv->get_user();
1200
38db610a
DM
1201 &$check_ceph_inited();
1202
1203 my $osdid = $param->{osdid};
1204
52d7be41 1205 # fixme: not 100% sure what we should do here
38db610a
DM
1206
1207 my $stat = &$ceph_osd_status();
1208
1209 my $osdlist = $stat->{osds} || [];
1210
1211 my $osdstat;
1212 foreach my $d (@$osdlist) {
1213 if ($d->{osd} == $osdid) {
1214 $osdstat = $d;
1215 last;
1216 }
1217 }
1218 die "no such OSD '$osdid'\n" if !$osdstat;
1219
1220 die "osd is in use (in == 1)\n" if $osdstat->{in};
1221 #&$run_ceph_cmd(['osd', 'out', $osdid]);
1222
1223 die "osd is still runnung (up == 1)\n" if $osdstat->{up};
1224
cee8e2b6 1225 my $osdsection = "osd.$osdid";
38db610a 1226
52d7be41
DM
1227 my $worker = sub {
1228 my $upid = shift;
38db610a 1229
cee8e2b6 1230 print "destroy OSD $osdsection\n";
38db610a 1231
52d7be41
DM
1232 eval { &$ceph_service_cmd('stop', $osdsection); };
1233 warn $@ if $@;
38db610a 1234
52d7be41 1235 print "Remove $osdsection from the CRUSH map\n";
cee8e2b6 1236 &$run_ceph_cmd(['osd', 'crush', 'remove', $osdsection]);
38db610a 1237
52d7be41
DM
1238 print "Remove the $osdsection authentication key.\n";
1239 &$run_ceph_cmd(['auth', 'del', $osdsection]);
1240
1241 print "Remove OSD $osdsection\n";
1242 &$run_ceph_cmd(['osd', 'rm', $osdid]);
1243 };
1244
cee8e2b6 1245 return $rpcenv->fork_worker('cephdestroyosd', $osdsection, $authuser, $worker);
38db610a 1246 }});
2f692121
DM
1247
1248__PACKAGE__->register_method ({
1249 name => 'crush',
1250 path => 'crush',
1251 method => 'GET',
1252 description => "Get OSD crush map",
1253 proxyto => 'node',
1254 protected => 1,
1255 parameters => {
1256 additionalProperties => 0,
1257 properties => {
1258 node => get_standard_option('pve-node'),
1259 },
1260 },
1261 returns => { type => 'string' },
1262 code => sub {
1263 my ($param) = @_;
1264
1265 &$check_ceph_inited();
1266
1267 my $txt = &$run_ceph_cmd_text(['osd', 'crush', 'dump'], quiet => 1);
1268
1269 return $txt;
1270 }});
1271
570278fa
DM
1272__PACKAGE__->register_method({
1273 name => 'log',
1274 path => 'log',
1275 method => 'GET',
1276 description => "Read ceph log",
1277 proxyto => 'node',
1278 permissions => {
1279 check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
1280 },
1281 protected => 1,
1282 parameters => {
1283 additionalProperties => 0,
1284 properties => {
1285 node => get_standard_option('pve-node'),
1286 start => {
1287 type => 'integer',
1288 minimum => 0,
1289 optional => 1,
1290 },
1291 limit => {
1292 type => 'integer',
1293 minimum => 0,
1294 optional => 1,
1295 },
1296 },
1297 },
1298 returns => {
1299 type => 'array',
1300 items => {
1301 type => "object",
1302 properties => {
1303 n => {
1304 description=> "Line number",
1305 type=> 'integer',
1306 },
1307 t => {
1308 description=> "Line text",
1309 type => 'string',
1310 }
1311 }
1312 }
1313 },
1314 code => sub {
1315 my ($param) = @_;
1316
1317 my $rpcenv = PVE::RPCEnvironment::get();
1318 my $user = $rpcenv->get_user();
1319 my $node = $param->{node};
1320
1321 my $logfile = "/var/log/ceph/ceph.log";
1322 my ($count, $lines) = PVE::Tools::dump_logfile($logfile, $param->{start}, $param->{limit});
1323
1324 $rpcenv->set_result_attrib('total', $count);
1325
1326 return $lines;
1327 }});
1328
2f692121 1329