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