]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Ceph.pm
API: ceph: fix typo in permissions
[pve-manager.git] / PVE / API2 / Ceph.pm
1 package PVE::API2::Ceph;
2
3 use strict;
4 use warnings;
5
6 use File::Path;
7 use Net::IP;
8 use UUID;
9
10 use PVE::Ceph::Tools;
11 use PVE::Ceph::Services;
12 use PVE::Cluster qw(cfs_read_file cfs_write_file);
13 use PVE::JSONSchema qw(get_standard_option);
14 use PVE::Network;
15 use PVE::RADOS;
16 use PVE::RESTHandler;
17 use PVE::RPCEnvironment;
18 use PVE::Storage;
19 use PVE::Tools qw(run_command file_get_contents file_set_contents extract_param);
20
21 use PVE::API2::Ceph::OSD;
22 use PVE::API2::Ceph::FS;
23 use PVE::API2::Ceph::MDS;
24 use PVE::API2::Ceph::MGR;
25 use PVE::API2::Ceph::MON;
26 use PVE::API2::Ceph::Pools;
27 use PVE::API2::Storage::Config;
28
29 use base qw(PVE::RESTHandler);
30
31 my $pve_osd_default_journal_size = 1024*5;
32
33 __PACKAGE__->register_method ({
34 subclass => "PVE::API2::Ceph::OSD",
35 path => 'osd',
36 });
37
38 __PACKAGE__->register_method ({
39 subclass => "PVE::API2::Ceph::MDS",
40 path => 'mds',
41 });
42
43 __PACKAGE__->register_method ({
44 subclass => "PVE::API2::Ceph::MGR",
45 path => 'mgr',
46 });
47
48 __PACKAGE__->register_method ({
49 subclass => "PVE::API2::Ceph::MON",
50 path => 'mon',
51 });
52
53 __PACKAGE__->register_method ({
54 subclass => "PVE::API2::Ceph::FS",
55 path => 'fs',
56 });
57
58 __PACKAGE__->register_method ({
59 subclass => "PVE::API2::Ceph::Pools",
60 path => 'pools',
61 });
62
63 __PACKAGE__->register_method ({
64 name => 'index',
65 path => '',
66 method => 'GET',
67 description => "Directory index.",
68 permissions => { user => 'all' },
69 permissions => {
70 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
71 },
72 parameters => {
73 additionalProperties => 0,
74 properties => {
75 node => get_standard_option('pve-node'),
76 },
77 },
78 returns => {
79 type => 'array',
80 items => {
81 type => "object",
82 properties => {},
83 },
84 links => [ { rel => 'child', href => "{name}" } ],
85 },
86 code => sub {
87 my ($param) = @_;
88
89 my $result = [
90 { name => 'cmd-safety' },
91 { name => 'config' },
92 { name => 'configdb' },
93 { name => 'crush' },
94 { name => 'fs' },
95 { name => 'init' },
96 { name => 'log' },
97 { name => 'mds' },
98 { name => 'mgr' },
99 { name => 'mon' },
100 { name => 'osd' },
101 { name => 'pools' },
102 { name => 'restart' },
103 { name => 'rules' },
104 { name => 'start' },
105 { name => 'status' },
106 { name => 'stop' },
107 ];
108
109 return $result;
110 }});
111
112 __PACKAGE__->register_method ({
113 name => 'config',
114 path => 'config',
115 method => 'GET',
116 proxyto => 'node',
117 permissions => {
118 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
119 },
120 description => "Get the Ceph configuration file.",
121 parameters => {
122 additionalProperties => 0,
123 properties => {
124 node => get_standard_option('pve-node'),
125 },
126 },
127 returns => { type => 'string' },
128 code => sub {
129 my ($param) = @_;
130
131 PVE::Ceph::Tools::check_ceph_inited();
132
133 my $path = PVE::Ceph::Tools::get_config('pve_ceph_cfgpath');
134 return file_get_contents($path);
135
136 }});
137
138 __PACKAGE__->register_method ({
139 name => 'configdb',
140 path => 'configdb',
141 method => 'GET',
142 proxyto => 'node',
143 protected => 1,
144 permissions => {
145 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
146 },
147 description => "Get the Ceph configuration database.",
148 parameters => {
149 additionalProperties => 0,
150 properties => {
151 node => get_standard_option('pve-node'),
152 },
153 },
154 returns => {
155 type => 'array',
156 items => {
157 type => 'object',
158 properties => {
159 section => { type => "string", },
160 name => { type => "string", },
161 value => { type => "string", },
162 level => { type => "string", },
163 'can_update_at_runtime' => { type => "boolean", },
164 mask => { type => "string" },
165 },
166 },
167 },
168 code => sub {
169 my ($param) = @_;
170
171 PVE::Ceph::Tools::check_ceph_inited();
172
173 my $rados = PVE::RADOS->new();
174 my $res = $rados->mon_command( { prefix => 'config dump', format => 'json' });
175 foreach my $entry (@$res) {
176 $entry->{can_update_at_runtime} = $entry->{can_update_at_runtime}? 1 : 0; # JSON::true/false -> 1/0
177 }
178
179 return $res;
180 }});
181
182
183 __PACKAGE__->register_method ({
184 name => 'init',
185 path => 'init',
186 method => 'POST',
187 description => "Create initial ceph default configuration and setup symlinks.",
188 proxyto => 'node',
189 protected => 1,
190 permissions => {
191 check => ['perm', '/', [ 'Sys.Modify' ]],
192 },
193 parameters => {
194 additionalProperties => 0,
195 properties => {
196 node => get_standard_option('pve-node'),
197 network => {
198 description => "Use specific network for all ceph related traffic",
199 type => 'string', format => 'CIDR',
200 optional => 1,
201 maxLength => 128,
202 },
203 'cluster-network' => {
204 description => "Declare a separate cluster network, OSDs will route" .
205 "heartbeat, object replication and recovery traffic over it",
206 type => 'string', format => 'CIDR',
207 requires => 'network',
208 optional => 1,
209 maxLength => 128,
210 },
211 size => {
212 description => 'Targeted number of replicas per object',
213 type => 'integer',
214 default => 3,
215 optional => 1,
216 minimum => 1,
217 maximum => 7,
218 },
219 min_size => {
220 description => 'Minimum number of available replicas per object to allow I/O',
221 type => 'integer',
222 default => 2,
223 optional => 1,
224 minimum => 1,
225 maximum => 7,
226 },
227 pg_bits => {
228 description => "Placement group bits, used to specify the " .
229 "default number of placement groups.\n\nNOTE: 'osd pool " .
230 "default pg num' does not work for default pools.",
231 type => 'integer',
232 default => 6,
233 optional => 1,
234 minimum => 6,
235 maximum => 14,
236 },
237 disable_cephx => {
238 description => "Disable cephx authentication.\n\n" .
239 "WARNING: cephx is a security feature protecting against " .
240 "man-in-the-middle attacks. Only consider disabling cephx ".
241 "if your network is private!",
242 type => 'boolean',
243 optional => 1,
244 default => 0,
245 },
246 },
247 },
248 returns => { type => 'null' },
249 code => sub {
250 my ($param) = @_;
251
252 my $version = PVE::Ceph::Tools::get_local_version(1);
253
254 if (!$version || $version < 14) {
255 die "Ceph Nautilus required - please run 'pveceph install'\n";
256 } else {
257 PVE::Ceph::Tools::check_ceph_installed('ceph_bin');
258 }
259
260 my $auth = $param->{disable_cephx} ? 'none' : 'cephx';
261
262 # simply load old config if it already exists
263 PVE::Cluster::cfs_lock_file('ceph.conf', undef, sub {
264 my $cfg = cfs_read_file('ceph.conf');
265
266 if (!$cfg->{global}) {
267
268 my $fsid;
269 my $uuid;
270
271 UUID::generate($uuid);
272 UUID::unparse($uuid, $fsid);
273
274 $cfg->{global} = {
275 'fsid' => $fsid,
276 'auth cluster required' => $auth,
277 'auth service required' => $auth,
278 'auth client required' => $auth,
279 'osd pool default size' => $param->{size} // 3,
280 'osd pool default min size' => $param->{min_size} // 2,
281 'mon allow pool delete' => 'true',
282 };
283
284 # this does not work for default pools
285 #'osd pool default pg num' => $pg_num,
286 #'osd pool default pgp num' => $pg_num,
287 }
288
289 if ($auth eq 'cephx') {
290 $cfg->{client}->{keyring} = '/etc/pve/priv/$cluster.$name.keyring';
291 }
292
293 if ($param->{pg_bits}) {
294 $cfg->{global}->{'osd pg bits'} = $param->{pg_bits};
295 $cfg->{global}->{'osd pgp bits'} = $param->{pg_bits};
296 }
297
298 if ($param->{network}) {
299 $cfg->{global}->{'public network'} = $param->{network};
300 $cfg->{global}->{'cluster network'} = $param->{network};
301 }
302
303 if ($param->{'cluster-network'}) {
304 $cfg->{global}->{'cluster network'} = $param->{'cluster-network'};
305 }
306
307 cfs_write_file('ceph.conf', $cfg);
308
309 if ($auth eq 'cephx') {
310 PVE::Ceph::Tools::get_or_create_admin_keyring();
311 }
312 PVE::Ceph::Tools::setup_pve_symlinks();
313 });
314 die $@ if $@;
315
316 return undef;
317 }});
318
319 __PACKAGE__->register_method ({
320 name => 'stop',
321 path => 'stop',
322 method => 'POST',
323 description => "Stop ceph services.",
324 proxyto => 'node',
325 protected => 1,
326 permissions => {
327 check => ['perm', '/', [ 'Sys.Modify' ]],
328 },
329 parameters => {
330 additionalProperties => 0,
331 properties => {
332 node => get_standard_option('pve-node'),
333 service => {
334 description => 'Ceph service name.',
335 type => 'string',
336 optional => 1,
337 default => 'ceph.target',
338 pattern => '(ceph|mon|mds|osd|mgr)(\.'.PVE::Ceph::Services::SERVICE_REGEX.')?',
339 },
340 },
341 },
342 returns => { type => 'string' },
343 code => sub {
344 my ($param) = @_;
345
346 my $rpcenv = PVE::RPCEnvironment::get();
347
348 my $authuser = $rpcenv->get_user();
349
350 PVE::Ceph::Tools::check_ceph_inited();
351
352 my $cfg = cfs_read_file('ceph.conf');
353 scalar(keys %$cfg) || die "no configuration\n";
354
355 my $worker = sub {
356 my $upid = shift;
357
358 my $cmd = ['stop'];
359 if ($param->{service}) {
360 push @$cmd, $param->{service};
361 }
362
363 PVE::Ceph::Services::ceph_service_cmd(@$cmd);
364 };
365
366 return $rpcenv->fork_worker('srvstop', $param->{service} || 'ceph',
367 $authuser, $worker);
368 }});
369
370 __PACKAGE__->register_method ({
371 name => 'start',
372 path => 'start',
373 method => 'POST',
374 description => "Start ceph services.",
375 proxyto => 'node',
376 protected => 1,
377 permissions => {
378 check => ['perm', '/', [ 'Sys.Modify' ]],
379 },
380 parameters => {
381 additionalProperties => 0,
382 properties => {
383 node => get_standard_option('pve-node'),
384 service => {
385 description => 'Ceph service name.',
386 type => 'string',
387 optional => 1,
388 default => 'ceph.target',
389 pattern => '(ceph|mon|mds|osd|mgr)(\.'.PVE::Ceph::Services::SERVICE_REGEX.')?',
390 },
391 },
392 },
393 returns => { type => 'string' },
394 code => sub {
395 my ($param) = @_;
396
397 my $rpcenv = PVE::RPCEnvironment::get();
398
399 my $authuser = $rpcenv->get_user();
400
401 PVE::Ceph::Tools::check_ceph_inited();
402
403 my $cfg = cfs_read_file('ceph.conf');
404 scalar(keys %$cfg) || die "no configuration\n";
405
406 my $worker = sub {
407 my $upid = shift;
408
409 my $cmd = ['start'];
410 if ($param->{service}) {
411 push @$cmd, $param->{service};
412 }
413
414 PVE::Ceph::Services::ceph_service_cmd(@$cmd);
415 };
416
417 return $rpcenv->fork_worker('srvstart', $param->{service} || 'ceph',
418 $authuser, $worker);
419 }});
420
421 __PACKAGE__->register_method ({
422 name => 'restart',
423 path => 'restart',
424 method => 'POST',
425 description => "Restart ceph services.",
426 proxyto => 'node',
427 protected => 1,
428 permissions => {
429 check => ['perm', '/', [ 'Sys.Modify' ]],
430 },
431 parameters => {
432 additionalProperties => 0,
433 properties => {
434 node => get_standard_option('pve-node'),
435 service => {
436 description => 'Ceph service name.',
437 type => 'string',
438 optional => 1,
439 default => 'ceph.target',
440 pattern => '(mon|mds|osd|mgr)(\.'.PVE::Ceph::Services::SERVICE_REGEX.')?',
441 },
442 },
443 },
444 returns => { type => 'string' },
445 code => sub {
446 my ($param) = @_;
447
448 my $rpcenv = PVE::RPCEnvironment::get();
449
450 my $authuser = $rpcenv->get_user();
451
452 PVE::Ceph::Tools::check_ceph_inited();
453
454 my $cfg = cfs_read_file('ceph.conf');
455 scalar(keys %$cfg) || die "no configuration\n";
456
457 my $worker = sub {
458 my $upid = shift;
459
460 my $cmd = ['restart'];
461 if ($param->{service}) {
462 push @$cmd, $param->{service};
463 }
464
465 PVE::Ceph::Services::ceph_service_cmd(@$cmd);
466 };
467
468 return $rpcenv->fork_worker('srvrestart', $param->{service} || 'ceph',
469 $authuser, $worker);
470 }});
471
472 __PACKAGE__->register_method ({
473 name => 'status',
474 path => 'status',
475 method => 'GET',
476 description => "Get ceph status.",
477 proxyto => 'node',
478 protected => 1,
479 permissions => {
480 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
481 },
482 parameters => {
483 additionalProperties => 0,
484 properties => {
485 node => get_standard_option('pve-node'),
486 },
487 },
488 returns => { type => 'object' },
489 code => sub {
490 my ($param) = @_;
491
492 PVE::Ceph::Tools::check_ceph_inited();
493
494 return PVE::Ceph::Tools::ceph_cluster_status();
495 }});
496
497
498 __PACKAGE__->register_method ({
499 name => 'crush',
500 path => 'crush',
501 method => 'GET',
502 description => "Get OSD crush map",
503 proxyto => 'node',
504 protected => 1,
505 permissions => {
506 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
507 },
508 parameters => {
509 additionalProperties => 0,
510 properties => {
511 node => get_standard_option('pve-node'),
512 },
513 },
514 returns => { type => 'string' },
515 code => sub {
516 my ($param) = @_;
517
518 PVE::Ceph::Tools::check_ceph_inited();
519
520 # this produces JSON (difficult to read for the user)
521 # my $txt = &$run_ceph_cmd_text(['osd', 'crush', 'dump'], quiet => 1);
522
523 my $txt = '';
524
525 my $mapfile = "/var/tmp/ceph-crush.map.$$";
526 my $mapdata = "/var/tmp/ceph-crush.txt.$$";
527
528 my $rados = PVE::RADOS->new();
529
530 eval {
531 my $bindata = $rados->mon_command({ prefix => 'osd getcrushmap', format => 'plain' });
532 file_set_contents($mapfile, $bindata);
533 run_command(['crushtool', '-d', $mapfile, '-o', $mapdata]);
534 $txt = file_get_contents($mapdata);
535 };
536 my $err = $@;
537
538 unlink $mapfile;
539 unlink $mapdata;
540
541 die $err if $err;
542
543 return $txt;
544 }});
545
546 __PACKAGE__->register_method({
547 name => 'log',
548 path => 'log',
549 method => 'GET',
550 description => "Read ceph log",
551 proxyto => 'node',
552 permissions => {
553 check => ['perm', '/nodes/{node}', [ 'Sys.Syslog' ]],
554 },
555 protected => 1,
556 parameters => {
557 additionalProperties => 0,
558 properties => {
559 node => get_standard_option('pve-node'),
560 start => {
561 type => 'integer',
562 minimum => 0,
563 optional => 1,
564 },
565 limit => {
566 type => 'integer',
567 minimum => 0,
568 optional => 1,
569 },
570 },
571 },
572 returns => {
573 type => 'array',
574 items => {
575 type => "object",
576 properties => {
577 n => {
578 description=> "Line number",
579 type=> 'integer',
580 },
581 t => {
582 description=> "Line text",
583 type => 'string',
584 }
585 }
586 }
587 },
588 code => sub {
589 my ($param) = @_;
590
591 PVE::Ceph::Tools::check_ceph_inited();
592
593 my $rpcenv = PVE::RPCEnvironment::get();
594 my $user = $rpcenv->get_user();
595 my $node = $param->{node};
596
597 my $logfile = "/var/log/ceph/ceph.log";
598 my ($count, $lines) = PVE::Tools::dump_logfile($logfile, $param->{start}, $param->{limit});
599
600 $rpcenv->set_result_attrib('total', $count);
601
602 return $lines;
603 }});
604
605 __PACKAGE__->register_method ({
606 name => 'rules',
607 path => 'rules',
608 method => 'GET',
609 description => "List ceph rules.",
610 proxyto => 'node',
611 protected => 1,
612 permissions => {
613 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
614 },
615 parameters => {
616 additionalProperties => 0,
617 properties => {
618 node => get_standard_option('pve-node'),
619 },
620 },
621 returns => {
622 type => 'array',
623 items => {
624 type => "object",
625 properties => {},
626 },
627 links => [ { rel => 'child', href => "{name}" } ],
628 },
629 code => sub {
630 my ($param) = @_;
631
632 PVE::Ceph::Tools::check_ceph_inited();
633
634 my $rados = PVE::RADOS->new();
635
636 my $rules = $rados->mon_command({ prefix => 'osd crush rule ls' });
637
638 my $res = [];
639
640 foreach my $rule (@$rules) {
641 push @$res, { name => $rule };
642 }
643
644 return $res;
645 }});
646
647 __PACKAGE__->register_method ({
648 name => 'cmd_safety',
649 path => 'cmd-safety',
650 method => 'GET',
651 description => "Heuristical check if it is safe to perform an action.",
652 proxyto => 'node',
653 protected => 1,
654 permissions => {
655 check => ['perm', '/', [ 'Sys.Audit' ]],
656 },
657 parameters => {
658 additionalProperties => 0,
659 properties => {
660 node => get_standard_option('pve-node'),
661 service => {
662 description => 'Service type',
663 type => 'string',
664 enum => ['osd', 'mon', 'mds'],
665 },
666 id => {
667 description => 'ID of the service',
668 type => 'string',
669 },
670 action => {
671 description => 'Action to check',
672 type => 'string',
673 enum => ['stop', 'destroy'],
674 },
675 },
676 },
677 returns => {
678 type => 'object',
679 properties => {
680 safe => {
681 type => 'boolean',
682 description => 'If it is safe to run the command.',
683 },
684 status => {
685 type => 'string',
686 optional => 1,
687 description => 'Status message given by Ceph.'
688 },
689 },
690 },
691 code => sub {
692 my ($param) = @_;
693
694 PVE::Ceph::Tools::check_ceph_inited();
695
696 my $id = $param->{id};
697 my $service = $param->{service};
698 my $action = $param->{action};
699
700 my $rados = PVE::RADOS->new();
701
702 my $supported_actions = {
703 osd => {
704 stop => 'ok-to-stop',
705 destroy => 'safe-to-destroy',
706 },
707 mon => {
708 stop => 'ok-to-stop',
709 destroy => 'ok-to-rm',
710 },
711 mds => {
712 stop => 'ok-to-stop',
713 },
714 };
715
716 die "Service does not support this action: ${service}: ${action}\n"
717 if !$supported_actions->{$service}->{$action};
718
719 my $result = {
720 safe => 0,
721 status => '',
722 };
723
724 my $params = {
725 prefix => "${service} $supported_actions->{$service}->{$action}",
726 format => 'plain',
727 };
728 if ($service eq 'mon' && $action eq 'destroy') {
729 $params->{id} = $id;
730 } else {
731 $params->{ids} = [ $id ];
732 }
733
734 $result = $rados->mon_cmd($params, 1);
735 die $@ if $@;
736
737 $result->{safe} = $result->{return_code} == 0 ? 1 : 0;
738 $result->{status} = $result->{status_message};
739
740 return $result;
741 }});
742
743 1;