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