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