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