]> git.proxmox.com Git - pve-manager.git/blob - PVE/API2/Ceph/Pools.pm
fac21301e69647052f7020c3f7e39483d757854c
[pve-manager.git] / PVE / API2 / Ceph / Pools.pm
1 package PVE::API2::Ceph::Pools;
2
3 use strict;
4 use warnings;
5
6 use PVE::Ceph::Tools;
7 use PVE::Ceph::Services;
8 use PVE::JSONSchema qw(get_standard_option);
9 use PVE::RADOS;
10 use PVE::RESTHandler;
11 use PVE::RPCEnvironment;
12 use PVE::Storage;
13 use PVE::Tools qw(extract_param);
14
15 use PVE::API2::Storage::Config;
16
17 use base qw(PVE::RESTHandler);
18
19 __PACKAGE__->register_method ({
20 name => 'lspools',
21 path => '',
22 method => 'GET',
23 description => "List all pools.",
24 proxyto => 'node',
25 protected => 1,
26 permissions => {
27 check => ['perm', '/', [ 'Sys.Audit', 'Datastore.Audit' ], any => 1],
28 },
29 parameters => {
30 additionalProperties => 0,
31 properties => {
32 node => get_standard_option('pve-node'),
33 },
34 },
35 returns => {
36 type => 'array',
37 items => {
38 type => "object",
39 properties => {
40 pool => { type => 'integer', title => 'ID' },
41 pool_name => { type => 'string', title => 'Name' },
42 size => { type => 'integer', title => 'Size' },
43 min_size => { type => 'integer', title => 'Min Size' },
44 pg_num => { type => 'integer', title => 'PG Num' },
45 pg_autoscale_mode => { type => 'string', optional => 1, title => 'PG Autoscale Mode' },
46 crush_rule => { type => 'integer', title => 'Crush Rule' },
47 crush_rule_name => { type => 'string', title => 'Crush Rule Name' },
48 percent_used => { type => 'number', title => '%-Used' },
49 bytes_used => { type => 'integer', title => 'Used' },
50 },
51 },
52 links => [ { rel => 'child', href => "{pool_name}" } ],
53 },
54 code => sub {
55 my ($param) = @_;
56
57 PVE::Ceph::Tools::check_ceph_inited();
58
59 my $rados = PVE::RADOS->new();
60
61 my $stats = {};
62 my $res = $rados->mon_command({ prefix => 'df' });
63
64 foreach my $d (@{$res->{pools}}) {
65 next if !$d->{stats};
66 next if !defined($d->{id});
67 $stats->{$d->{id}} = $d->{stats};
68 }
69
70 $res = $rados->mon_command({ prefix => 'osd dump' });
71 my $rulestmp = $rados->mon_command({ prefix => 'osd crush rule dump'});
72
73 my $rules = {};
74 for my $rule (@$rulestmp) {
75 $rules->{$rule->{rule_id}} = $rule->{rule_name};
76 }
77
78 my $data = [];
79 my $attr_list = [
80 'pool',
81 'pool_name',
82 'size',
83 'min_size',
84 'pg_num',
85 'crush_rule',
86 'pg_autoscale_mode',
87 ];
88
89 foreach my $e (@{$res->{pools}}) {
90 my $d = {};
91 foreach my $attr (@$attr_list) {
92 $d->{$attr} = $e->{$attr} if defined($e->{$attr});
93 }
94
95 if (defined($d->{crush_rule}) && defined($rules->{$d->{crush_rule}})) {
96 $d->{crush_rule_name} = $rules->{$d->{crush_rule}};
97 }
98
99 if (my $s = $stats->{$d->{pool}}) {
100 $d->{bytes_used} = $s->{bytes_used};
101 $d->{percent_used} = $s->{percent_used};
102 }
103 push @$data, $d;
104 }
105
106
107 return $data;
108 }});
109
110
111 my $ceph_pool_common_options = sub {
112 my ($nodefault) = shift;
113 my $options = {
114 name => {
115 description => "The name of the pool. It must be unique.",
116 type => 'string',
117 },
118 size => {
119 description => 'Number of replicas per object',
120 type => 'integer',
121 default => 3,
122 optional => 1,
123 minimum => 1,
124 maximum => 7,
125 },
126 min_size => {
127 description => 'Minimum number of replicas per object',
128 type => 'integer',
129 default => 2,
130 optional => 1,
131 minimum => 1,
132 maximum => 7,
133 },
134 pg_num => {
135 description => "Number of placement groups.",
136 type => 'integer',
137 default => 128,
138 optional => 1,
139 minimum => 8,
140 maximum => 32768,
141 },
142 crush_rule => {
143 description => "The rule to use for mapping object placement in the cluster.",
144 type => 'string',
145 optional => 1,
146 },
147 application => {
148 description => "The application of the pool.",
149 default => 'rbd',
150 type => 'string',
151 enum => ['rbd', 'cephfs', 'rgw'],
152 optional => 1,
153 },
154 pg_autoscale_mode => {
155 description => "The automatic PG scaling mode of the pool.",
156 type => 'string',
157 enum => ['on', 'off', 'warn'],
158 default => 'warn',
159 optional => 1,
160 },
161 };
162
163 if ($nodefault) {
164 delete $options->{$_}->{default} for keys %$options;
165 }
166 return $options;
167 };
168
169
170 my $add_storage = sub {
171 my ($pool, $storeid) = @_;
172
173 my $storage_params = {
174 type => 'rbd',
175 pool => $pool,
176 storage => $storeid,
177 krbd => 0,
178 content => 'rootdir,images',
179 };
180
181 PVE::API2::Storage::Config->create($storage_params);
182 };
183
184 my $get_storages = sub {
185 my ($pool) = @_;
186
187 my $cfg = PVE::Storage::config();
188
189 my $storages = $cfg->{ids};
190 my $res = {};
191 foreach my $storeid (keys %$storages) {
192 my $curr = $storages->{$storeid};
193 $res->{$storeid} = $storages->{$storeid}
194 if $curr->{type} eq 'rbd' && $pool eq $curr->{pool};
195 }
196
197 return $res;
198 };
199
200
201 __PACKAGE__->register_method ({
202 name => 'createpool',
203 path => '',
204 method => 'POST',
205 description => "Create POOL",
206 proxyto => 'node',
207 protected => 1,
208 permissions => {
209 check => ['perm', '/', [ 'Sys.Modify' ]],
210 },
211 parameters => {
212 additionalProperties => 0,
213 properties => {
214 node => get_standard_option('pve-node'),
215 add_storages => {
216 description => "Configure VM and CT storage using the new pool.",
217 type => 'boolean',
218 optional => 1,
219 },
220 %{ $ceph_pool_common_options->() },
221 },
222 },
223 returns => { type => 'string' },
224 code => sub {
225 my ($param) = @_;
226
227 PVE::Cluster::check_cfs_quorum();
228 PVE::Ceph::Tools::check_ceph_configured();
229
230 my $pool = extract_param($param, 'name');
231 my $node = extract_param($param, 'node');
232 my $add_storages = extract_param($param, 'add_storages');
233
234 my $rpcenv = PVE::RPCEnvironment::get();
235 my $user = $rpcenv->get_user();
236
237 if ($add_storages) {
238 $rpcenv->check($user, '/storage', ['Datastore.Allocate']);
239 die "pool name contains characters which are illegal for storage naming\n"
240 if !PVE::JSONSchema::parse_storage_id($pool);
241 }
242
243 # pool defaults
244 $param->{pg_num} //= 128;
245 $param->{size} //= 3;
246 $param->{min_size} //= 2;
247 $param->{application} //= 'rbd';
248 $param->{pg_autoscale_mode} //= 'warn';
249
250 my $worker = sub {
251
252 PVE::Ceph::Tools::create_pool($pool, $param);
253
254 if ($add_storages) {
255 my $err;
256 eval { $add_storage->($pool, "${pool}"); };
257 if ($@) {
258 warn "failed to add storage: $@";
259 $err = 1;
260 }
261 die "adding storage for pool '$pool' failed, check log and add manually!\n"
262 if $err;
263 }
264 };
265
266 return $rpcenv->fork_worker('cephcreatepool', $pool, $user, $worker);
267 }});
268
269
270 __PACKAGE__->register_method ({
271 name => 'destroypool',
272 path => '{name}',
273 method => 'DELETE',
274 description => "Destroy pool",
275 proxyto => 'node',
276 protected => 1,
277 permissions => {
278 check => ['perm', '/', [ 'Sys.Modify' ]],
279 },
280 parameters => {
281 additionalProperties => 0,
282 properties => {
283 node => get_standard_option('pve-node'),
284 name => {
285 description => "The name of the pool. It must be unique.",
286 type => 'string',
287 },
288 force => {
289 description => "If true, destroys pool even if in use",
290 type => 'boolean',
291 optional => 1,
292 default => 0,
293 },
294 remove_storages => {
295 description => "Remove all pveceph-managed storages configured for this pool",
296 type => 'boolean',
297 optional => 1,
298 default => 0,
299 },
300 },
301 },
302 returns => { type => 'string' },
303 code => sub {
304 my ($param) = @_;
305
306 PVE::Ceph::Tools::check_ceph_inited();
307
308 my $rpcenv = PVE::RPCEnvironment::get();
309 my $user = $rpcenv->get_user();
310 $rpcenv->check($user, '/storage', ['Datastore.Allocate'])
311 if $param->{remove_storages};
312
313 my $pool = $param->{name};
314
315 my $worker = sub {
316 my $storages = $get_storages->($pool);
317
318 # if not forced, destroy ceph pool only when no
319 # vm disks are on it anymore
320 if (!$param->{force}) {
321 my $storagecfg = PVE::Storage::config();
322 foreach my $storeid (keys %$storages) {
323 my $storage = $storages->{$storeid};
324
325 # check if any vm disks are on the pool
326 print "checking storage '$storeid' for RBD images..\n";
327 my $res = PVE::Storage::vdisk_list($storagecfg, $storeid);
328 die "ceph pool '$pool' still in use by storage '$storeid'\n"
329 if @{$res->{$storeid}} != 0;
330 }
331 }
332
333 PVE::Ceph::Tools::destroy_pool($pool);
334
335 if ($param->{remove_storages}) {
336 my $err;
337 foreach my $storeid (keys %$storages) {
338 # skip external clusters, not managed by pveceph
339 next if $storages->{$storeid}->{monhost};
340 eval { PVE::API2::Storage::Config->delete({storage => $storeid}) };
341 if ($@) {
342 warn "failed to remove storage '$storeid': $@\n";
343 $err = 1;
344 }
345 }
346 die "failed to remove (some) storages - check log and remove manually!\n"
347 if $err;
348 }
349 };
350 return $rpcenv->fork_worker('cephdestroypool', $pool, $user, $worker);
351 }});
352
353
354 __PACKAGE__->register_method ({
355 name => 'setpool',
356 path => '{name}',
357 method => 'PUT',
358 description => "Change POOL settings",
359 proxyto => 'node',
360 protected => 1,
361 permissions => {
362 check => ['perm', '/', [ 'Sys.Modify' ]],
363 },
364 parameters => {
365 additionalProperties => 0,
366 properties => {
367 node => get_standard_option('pve-node'),
368 %{ $ceph_pool_common_options->('nodefault') },
369 },
370 },
371 returns => { type => 'string' },
372 code => sub {
373 my ($param) = @_;
374
375 PVE::Ceph::Tools::check_ceph_configured();
376
377 my $rpcenv = PVE::RPCEnvironment::get();
378 my $authuser = $rpcenv->get_user();
379
380 my $pool = $param->{name};
381 my $ceph_param = \%$param;
382 for my $item ('name', 'node') {
383 # not ceph parameters
384 delete $ceph_param->{$item};
385 }
386
387 my $worker = sub {
388 PVE::Ceph::Tools::set_pool($pool, $ceph_param);
389 };
390
391 return $rpcenv->fork_worker('cephsetpool', $pool, $authuser, $worker);
392 }});
393
394
395 1;