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