]> git.proxmox.com Git - pve-manager.git/blame - PVE/API2/Ceph.pm
new IPProtocolSelector implementation.
[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
DM
223 if (! -f $ceph_bootstrap_osd_keyring) {
224 my $bindata = $rados->mon_command({ prefix => 'auth get client.bootstrap-osd', format => 'plain' });
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
768 my $config = PVE::INotify::read_file('interfaces');
769
770 my $net = Net::IP->new($cidr) || die Net::IP::Error() . "\n";
771
772 foreach my $iface (keys %$config) {
773 my $d = $config->{$iface};
774 next if !$d->{address};
775 my $a = Net::IP->new($d->{address});
776 next if !$a;
777 return $d->{address} if $net->overlaps($a);
778 }
779
780 die "unable to find local address within network '$cidr'\n";
781};
782
783__PACKAGE__->register_method ({
784 name => 'createmon',
785 path => 'mon',
786 method => 'POST',
787 description => "Create Ceph Monitor",
788 proxyto => 'node',
789 protected => 1,
790 parameters => {
791 additionalProperties => 0,
792 properties => {
793 node => get_standard_option('pve-node'),
794 },
795 },
796 returns => { type => 'string' },
797 code => sub {
798 my ($param) = @_;
799
800 PVE::CephTools::check_ceph_inited();
801
802 PVE::CephTools::setup_pve_symlinks();
803
804 my $rpcenv = PVE::RPCEnvironment::get();
805
806 my $authuser = $rpcenv->get_user();
807
808 my $cfg = PVE::CephTools::parse_ceph_config();
809
810 my $moncount = 0;
811
812 my $monaddrhash = {};
813
814 foreach my $section (keys %$cfg) {
815 next if $section eq 'global';
816 my $d = $cfg->{$section};
817 if ($section =~ m/^mon\./) {
818 $moncount++;
819 if ($d->{'mon addr'}) {
820 $monaddrhash->{$d->{'mon addr'}} = $section;
821 }
822 }
823 }
38db610a
DM
824
825 my $monid;
826 for (my $i = 0; $i < 7; $i++) {
827 if (!$cfg->{"mon.$i"}) {
828 $monid = $i;
829 last;
830 }
831 }
832 die "unable to find usable monitor id\n" if !defined($monid);
833
f7e342ea
DM
834 my $monsection = "mon.$monid";
835 my $ip;
836 if (my $pubnet = $cfg->{global}->{'public network'}) {
837 $ip = &$find_node_ip($pubnet);
838 } else {
839 $ip = PVE::Cluster::remote_node_ip($param->{node});
840 }
841
842 my $monaddr = "$ip:6789";
38db610a
DM
843 my $monname = $param->{node};
844
845 die "monitor '$monsection' already exists\n" if $cfg->{$monsection};
846 die "monitor address '$monaddr' already in use by '$monaddrhash->{$monaddr}'\n"
847 if $monaddrhash->{$monaddr};
848
52d7be41
DM
849 my $worker = sub {
850 my $upid = shift;
38db610a 851
a34866f0
DM
852 my $pve_ckeyring_path = PVE::CephTools::get_config('pve_ckeyring_path');
853
52d7be41
DM
854 if (! -f $pve_ckeyring_path) {
855 run_command("ceph-authtool $pve_ckeyring_path --create-keyring " .
856 "--gen-key -n client.admin");
857 }
38db610a 858
a34866f0 859 my $pve_mon_key_path = PVE::CephTools::get_config('pve_mon_key_path');
52d7be41
DM
860 if (! -f $pve_mon_key_path) {
861 run_command("cp $pve_ckeyring_path $pve_mon_key_path.tmp");
862 run_command("ceph-authtool $pve_mon_key_path.tmp -n client.admin --set-uid=0 " .
863 "--cap mds 'allow' " .
864 "--cap osd 'allow *' " .
865 "--cap mon 'allow *'");
866 run_command("ceph-authtool $pve_mon_key_path.tmp --gen-key -n mon. --cap mon 'allow *'");
867 run_command("mv $pve_mon_key_path.tmp $pve_mon_key_path");
38db610a
DM
868 }
869
a34866f0
DM
870 my $ccname = PVE::CephTools::get_config('ccname');
871
52d7be41
DM
872 my $mondir = "/var/lib/ceph/mon/$ccname-$monid";
873 -d $mondir && die "monitor filesystem '$mondir' already exist\n";
874
875 my $monmap = "/tmp/monmap";
876
877 eval {
878 mkdir $mondir;
879
880 if ($moncount > 0) {
7d4fc5ef 881 my $rados = PVE::RADOS->new(timeout => PVE::CephTools::get_config('long_rados_timeout'));
970236b3
DM
882 my $mapdata = $rados->mon_command({ prefix => 'mon getmap', format => 'plain' });
883 PVE::Tools::file_set_contents($monmap, $mapdata);
52d7be41
DM
884 } else {
885 run_command("monmaptool --create --clobber --add $monid $monaddr --print $monmap");
886 }
38db610a 887
52d7be41
DM
888 run_command("ceph-mon --mkfs -i $monid --monmap $monmap --keyring $pve_mon_key_path");
889 };
890 my $err = $@;
891 unlink $monmap;
892 if ($err) {
893 File::Path::remove_tree($mondir);
894 die $err;
895 }
38db610a 896
52d7be41
DM
897 $cfg->{$monsection} = {
898 'host' => $monname,
899 'mon addr' => $monaddr,
900 };
38db610a 901
a34866f0 902 PVE::CephTools::write_ceph_config($cfg);
38db610a 903
a34866f0 904 PVE::CephTools::ceph_service_cmd('start', $monsection);
52d7be41 905 };
38db610a 906
52d7be41 907 return $rpcenv->fork_worker('cephcreatemon', $monsection, $authuser, $worker);
38db610a
DM
908 }});
909
910__PACKAGE__->register_method ({
911 name => 'destroymon',
39e1ad70
DM
912 path => 'mon/{monid}',
913 method => 'DELETE',
38db610a
DM
914 description => "Destroy Ceph monitor.",
915 proxyto => 'node',
916 protected => 1,
917 parameters => {
918 additionalProperties => 0,
919 properties => {
920 node => get_standard_option('pve-node'),
921 monid => {
922 description => 'Monitor ID',
923 type => 'integer',
924 },
925 },
926 },
52d7be41 927 returns => { type => 'string' },
38db610a
DM
928 code => sub {
929 my ($param) = @_;
930
52d7be41
DM
931 my $rpcenv = PVE::RPCEnvironment::get();
932
933 my $authuser = $rpcenv->get_user();
934
a34866f0 935 PVE::CephTools::check_ceph_inited();
38db610a 936
a34866f0 937 my $cfg = PVE::CephTools::parse_ceph_config();
38db610a
DM
938
939 my $monid = $param->{monid};
940 my $monsection = "mon.$monid";
941
36fd0190 942 my $rados = PVE::RADOS->new();
970236b3 943 my $monstat = $rados->mon_command({ prefix => 'mon_status' });
38db610a
DM
944 my $monlist = $monstat->{monmap}->{mons};
945
946 die "no such monitor id '$monid'\n"
947 if !defined($cfg->{$monsection});
948
a34866f0
DM
949 my $ccname = PVE::CephTools::get_config('ccname');
950
38db610a
DM
951 my $mondir = "/var/lib/ceph/mon/$ccname-$monid";
952 -d $mondir || die "monitor filesystem '$mondir' does not exist on this node\n";
953
954 die "can't remove last monitor\n" if scalar(@$monlist) <= 1;
955
52d7be41
DM
956 my $worker = sub {
957 my $upid = shift;
38db610a 958
2f804640 959 # reopen with longer timeout
7d4fc5ef 960 $rados = PVE::RADOS->new(timeout => PVE::CephTools::get_config('long_rados_timeout'));
f26b46db 961
6e3c2f47 962 $rados->mon_command({ prefix => "mon remove", name => $monid, format => 'plain' });
38db610a 963
a34866f0 964 eval { PVE::CephTools::ceph_service_cmd('stop', $monsection); };
52d7be41 965 warn $@ if $@;
38db610a 966
52d7be41 967 delete $cfg->{$monsection};
a34866f0 968 PVE::CephTools::write_ceph_config($cfg);
52d7be41
DM
969 File::Path::remove_tree($mondir);
970 };
971
972 return $rpcenv->fork_worker('cephdestroymon', $monsection, $authuser, $worker);
38db610a
DM
973 }});
974
975__PACKAGE__->register_method ({
976 name => 'stop',
977 path => 'stop',
978 method => 'POST',
979 description => "Stop ceph services.",
980 proxyto => 'node',
981 protected => 1,
982 parameters => {
983 additionalProperties => 0,
984 properties => {
985 node => get_standard_option('pve-node'),
68e0c4bd
DM
986 service => {
987 description => 'Ceph service name.',
988 type => 'string',
989 optional => 1,
990 pattern => '(mon|mds|osd)\.[A-Za-z0-9]{1,32}',
991 },
38db610a
DM
992 },
993 },
68e0c4bd 994 returns => { type => 'string' },
38db610a
DM
995 code => sub {
996 my ($param) = @_;
997
68e0c4bd
DM
998 my $rpcenv = PVE::RPCEnvironment::get();
999
1000 my $authuser = $rpcenv->get_user();
1001
a34866f0 1002 PVE::CephTools::check_ceph_inited();
38db610a 1003
a34866f0 1004 my $cfg = PVE::CephTools::parse_ceph_config();
38db610a
DM
1005 scalar(keys %$cfg) || die "no configuration\n";
1006
68e0c4bd
DM
1007 my $worker = sub {
1008 my $upid = shift;
38db610a 1009
68e0c4bd
DM
1010 my $cmd = ['stop'];
1011 if ($param->{service}) {
1012 push @$cmd, $param->{service};
1013 }
1014
a34866f0 1015 PVE::CephTools::ceph_service_cmd(@$cmd);
68e0c4bd
DM
1016 };
1017
1018 return $rpcenv->fork_worker('srvstop', $param->{service} || 'ceph',
1019 $authuser, $worker);
38db610a
DM
1020 }});
1021
1022__PACKAGE__->register_method ({
1023 name => 'start',
1024 path => 'start',
1025 method => 'POST',
1026 description => "Start ceph services.",
1027 proxyto => 'node',
1028 protected => 1,
1029 parameters => {
1030 additionalProperties => 0,
1031 properties => {
1032 node => get_standard_option('pve-node'),
68e0c4bd
DM
1033 service => {
1034 description => 'Ceph service name.',
1035 type => 'string',
1036 optional => 1,
1037 pattern => '(mon|mds|osd)\.[A-Za-z0-9]{1,32}',
1038 },
38db610a
DM
1039 },
1040 },
68e0c4bd 1041 returns => { type => 'string' },
38db610a
DM
1042 code => sub {
1043 my ($param) = @_;
1044
68e0c4bd
DM
1045 my $rpcenv = PVE::RPCEnvironment::get();
1046
1047 my $authuser = $rpcenv->get_user();
1048
a34866f0 1049 PVE::CephTools::check_ceph_inited();
38db610a 1050
a34866f0 1051 my $cfg = PVE::CephTools::parse_ceph_config();
38db610a
DM
1052 scalar(keys %$cfg) || die "no configuration\n";
1053
68e0c4bd
DM
1054 my $worker = sub {
1055 my $upid = shift;
38db610a 1056
68e0c4bd
DM
1057 my $cmd = ['start'];
1058 if ($param->{service}) {
1059 push @$cmd, $param->{service};
1060 }
1061
a34866f0 1062 PVE::CephTools::ceph_service_cmd(@$cmd);
68e0c4bd
DM
1063 };
1064
1065 return $rpcenv->fork_worker('srvstart', $param->{service} || 'ceph',
1066 $authuser, $worker);
38db610a
DM
1067 }});
1068
1069__PACKAGE__->register_method ({
1070 name => 'status',
1071 path => 'status',
1072 method => 'GET',
1073 description => "Get ceph status.",
1074 proxyto => 'node',
1075 protected => 1,
1076 parameters => {
1077 additionalProperties => 0,
1078 properties => {
1079 node => get_standard_option('pve-node'),
1080 },
1081 },
1082 returns => { type => 'object' },
1083 code => sub {
1084 my ($param) = @_;
1085
a34866f0 1086 PVE::CephTools::check_ceph_enabled();
38db610a 1087
36fd0190 1088 my $rados = PVE::RADOS->new();
970236b3 1089 return $rados->mon_command({ prefix => 'status' });
38db610a
DM
1090 }});
1091
b0537f7b
DM
1092__PACKAGE__->register_method ({
1093 name => 'lspools',
1094 path => 'pools',
1095 method => 'GET',
1096 description => "List all pools.",
1097 proxyto => 'node',
1098 protected => 1,
1099 parameters => {
1100 additionalProperties => 0,
1101 properties => {
1102 node => get_standard_option('pve-node'),
1103 },
1104 },
1105 returns => {
1106 type => 'array',
1107 items => {
1108 type => "object",
1109 properties => {
1110 pool => { type => 'integer' },
1111 pool_name => { type => 'string' },
1112 size => { type => 'integer' },
1113 },
1114 },
1115 links => [ { rel => 'child', href => "{pool_name}" } ],
1116 },
1117 code => sub {
1118 my ($param) = @_;
1119
a34866f0 1120 PVE::CephTools::check_ceph_inited();
b0537f7b 1121
36fd0190 1122 my $rados = PVE::RADOS->new();
d54f1126
DM
1123
1124 my $stats = {};
1125 my $res = $rados->mon_command({ prefix => 'df' });
1126 my $total = $res->{stats}->{total_space} || 0;
1127 $total = $total * 1024;
1128 foreach my $d (@{$res->{pools}}) {
1129 next if !$d->{stats};
1130 next if !defined($d->{id});
1131 $stats->{$d->{id}} = $d->{stats};
1132 }
1133
1134 $res = $rados->mon_command({ prefix => 'osd dump' });
b0537f7b
DM
1135
1136 my $data = [];
1137 foreach my $e (@{$res->{pools}}) {
1138 my $d = {};
1139 foreach my $attr (qw(pool pool_name size min_size pg_num crush_ruleset)) {
1140 $d->{$attr} = $e->{$attr} if defined($e->{$attr});
1141 }
d54f1126
DM
1142 if (my $s = $stats->{$d->{pool}}) {
1143 $d->{bytes_used} = $s->{bytes_used};
5374d54f
DM
1144 $d->{percent_used} = ($d->{bytes_used}*$d->{size}*100)/$total
1145 if $d->{size} && $total;
d54f1126 1146 }
b0537f7b
DM
1147 push @$data, $d;
1148 }
1149
d54f1126 1150
b0537f7b
DM
1151 return $data;
1152 }});
1153
1154__PACKAGE__->register_method ({
1155 name => 'createpool',
7d4fc5ef 1156 path => 'pools',
38db610a 1157 method => 'POST',
7d4fc5ef 1158 description => "Create POOL",
38db610a
DM
1159 proxyto => 'node',
1160 protected => 1,
1161 parameters => {
1162 additionalProperties => 0,
1163 properties => {
1164 node => get_standard_option('pve-node'),
7d4fc5ef
DM
1165 name => {
1166 description => "The name of the pool. It must be unique.",
38db610a 1167 type => 'string',
43d85563 1168 },
7d4fc5ef
DM
1169 size => {
1170 description => 'Number of replicas per object',
1171 type => 'integer',
1172 default => 2,
0e5816e4 1173 optional => 1,
7d4fc5ef
DM
1174 minimum => 1,
1175 maximum => 3,
0e5816e4 1176 },
7d4fc5ef
DM
1177 min_size => {
1178 description => 'Minimum number of replicas per object',
1179 type => 'integer',
1180 default => 1,
1181 optional => 1,
1182 minimum => 1,
1183 maximum => 3,
1184 },
1185 pg_num => {
1186 description => "Number of placement groups.",
1187 type => 'integer',
1188 default => 64,
1189 optional => 1,
1190 minimum => 8,
1191 maximum => 32768,
1192 },
1193 crush_ruleset => {
1194 description => "The ruleset to use for mapping object placement in the cluster.",
1195 type => 'integer',
1196 minimum => 0,
1197 maximum => 32768,
1198 default => 0,
43d85563
DM
1199 optional => 1,
1200 },
38db610a
DM
1201 },
1202 },
7d4fc5ef 1203 returns => { type => 'null' },
38db610a
DM
1204 code => sub {
1205 my ($param) = @_;
1206
a34866f0 1207 PVE::CephTools::check_ceph_inited();
38db610a 1208
7d4fc5ef 1209 my $pve_ckeyring_path = PVE::CephTools::get_config('pve_ckeyring_path');
13f4d762 1210
7d4fc5ef
DM
1211 die "not fully configured - missing '$pve_ckeyring_path'\n"
1212 if ! -f $pve_ckeyring_path;
13f4d762 1213
7d4fc5ef
DM
1214 my $pg_num = $param->{pg_num} || 64;
1215 my $size = $param->{size} || 2;
1216 my $min_size = $param->{min_size} || 1;
1217 my $ruleset = $param->{crush_ruleset} || 0;
36fd0190 1218 my $rados = PVE::RADOS->new();
38db610a 1219
7d4fc5ef
DM
1220 $rados->mon_command({
1221 prefix => "osd pool create",
1222 pool => $param->{name},
1223 pg_num => int($pg_num),
1224# this does not work for unknown reason
1225# properties => ["size=$size", "min_size=$min_size", "crush_ruleset=$ruleset"],
1226 format => 'plain',
1227 });
52d7be41 1228
7d4fc5ef
DM
1229 $rados->mon_command({
1230 prefix => "osd pool set",
1231 pool => $param->{name},
1232 var => 'min_size',
1233 val => $min_size,
1234 format => 'plain',
1235 });
a34866f0 1236
7d4fc5ef
DM
1237 $rados->mon_command({
1238 prefix => "osd pool set",
1239 pool => $param->{name},
1240 var => 'size',
1241 val => $size,
1242 format => 'plain',
1243 });
0e5816e4 1244
7d4fc5ef
DM
1245 if (defined($param->{crush_ruleset})) {
1246 $rados->mon_command({
1247 prefix => "osd pool set",
1248 pool => $param->{name},
1249 var => 'crush_ruleset',
1250 val => $param->{crush_ruleset},
1251 format => 'plain',
1252 });
1253 }
52d7be41 1254
7d4fc5ef 1255 return undef;
38db610a
DM
1256 }});
1257
1258__PACKAGE__->register_method ({
7d4fc5ef
DM
1259 name => 'destroypool',
1260 path => 'pools/{name}',
39e1ad70 1261 method => 'DELETE',
7d4fc5ef 1262 description => "Destroy pool",
38db610a
DM
1263 proxyto => 'node',
1264 protected => 1,
1265 parameters => {
1266 additionalProperties => 0,
1267 properties => {
1268 node => get_standard_option('pve-node'),
7d4fc5ef
DM
1269 name => {
1270 description => "The name of the pool. It must be unique.",
1271 type => 'string',
0e5816e4 1272 },
38db610a
DM
1273 },
1274 },
7d4fc5ef 1275 returns => { type => 'null' },
38db610a
DM
1276 code => sub {
1277 my ($param) = @_;
1278
a34866f0 1279 PVE::CephTools::check_ceph_inited();
38db610a 1280
36fd0190 1281 my $rados = PVE::RADOS->new();
7d4fc5ef
DM
1282 # fixme: '--yes-i-really-really-mean-it'
1283 $rados->mon_command({
1284 prefix => "osd pool delete",
1285 pool => $param->{name},
1286 pool2 => $param->{name},
1287 sure => '--yes-i-really-really-mean-it',
1288 format => 'plain',
1289 });
52d7be41 1290
7d4fc5ef 1291 return undef;
38db610a 1292 }});
2f692121 1293
a34866f0 1294
2f692121
DM
1295__PACKAGE__->register_method ({
1296 name => 'crush',
1297 path => 'crush',
1298 method => 'GET',
1299 description => "Get OSD crush map",
1300 proxyto => 'node',
1301 protected => 1,
1302 parameters => {
1303 additionalProperties => 0,
1304 properties => {
1305 node => get_standard_option('pve-node'),
1306 },
1307 },
1308 returns => { type => 'string' },
1309 code => sub {
1310 my ($param) = @_;
1311
a34866f0 1312 PVE::CephTools::check_ceph_inited();
2f692121 1313
8b336060
DM
1314 # this produces JSON (difficult to read for the user)
1315 # my $txt = &$run_ceph_cmd_text(['osd', 'crush', 'dump'], quiet => 1);
2f692121 1316
8b336060
DM
1317 my $txt = '';
1318
1319 my $mapfile = "/var/tmp/ceph-crush.map.$$";
1320 my $mapdata = "/var/tmp/ceph-crush.txt.$$";
1321
36fd0190 1322 my $rados = PVE::RADOS->new();
970236b3 1323
8b336060 1324 eval {
970236b3
DM
1325 my $bindata = $rados->mon_command({ prefix => 'osd getcrushmap', format => 'plain' });
1326 PVE::Tools::file_set_contents($mapfile, $bindata);
8b336060
DM
1327 run_command(['crushtool', '-d', $mapfile, '-o', $mapdata]);
1328 $txt = PVE::Tools::file_get_contents($mapdata);
1329 };
1330 my $err = $@;
1331
1332 unlink $mapfile;
1333 unlink $mapdata;
1334
1335 die $err if $err;
1336
2f692121
DM
1337 return $txt;
1338 }});
1339
570278fa
DM
1340__PACKAGE__->register_method({
1341 name => 'log',
1342 path => 'log',
1343 method => 'GET',
1344 description => "Read ceph log",
1345 proxyto => 'node',
1346 permissions => {
1347 check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
1348 },
1349 protected => 1,
1350 parameters => {
1351 additionalProperties => 0,
1352 properties => {
1353 node => get_standard_option('pve-node'),
1354 start => {
1355 type => 'integer',
1356 minimum => 0,
1357 optional => 1,
1358 },
1359 limit => {
1360 type => 'integer',
1361 minimum => 0,
1362 optional => 1,
1363 },
1364 },
1365 },
1366 returns => {
1367 type => 'array',
1368 items => {
1369 type => "object",
1370 properties => {
1371 n => {
1372 description=> "Line number",
1373 type=> 'integer',
1374 },
1375 t => {
1376 description=> "Line text",
1377 type => 'string',
1378 }
1379 }
1380 }
1381 },
1382 code => sub {
1383 my ($param) = @_;
1384
1385 my $rpcenv = PVE::RPCEnvironment::get();
1386 my $user = $rpcenv->get_user();
1387 my $node = $param->{node};
1388
1389 my $logfile = "/var/log/ceph/ceph.log";
1390 my ($count, $lines) = PVE::Tools::dump_logfile($logfile, $param->{start}, $param->{limit});
1391
1392 $rpcenv->set_result_attrib('total', $count);
1393
1394 return $lines;
1395 }});
1396
2f692121 1397