]> git.proxmox.com Git - pve-storage.git/blob - PVE/Storage/RBDPlugin.pm
use integer size in rbd size parameter
[pve-storage.git] / PVE / Storage / RBDPlugin.pm
1 package PVE::Storage::RBDPlugin;
2
3 use strict;
4 use warnings;
5 use IO::File;
6 use PVE::Tools qw(run_command trim);
7 use PVE::Storage::Plugin;
8 use PVE::JSONSchema qw(get_standard_option);
9
10 use base qw(PVE::Storage::Plugin);
11
12 sub rbd_unittobytes {
13 {
14 "M" => 1024*1024,
15 "G" => 1024*1024*1024,
16 "T" => 1024*1024*1024*1024,
17 }
18 }
19
20 my $rbd_cmd = sub {
21 my ($scfg, $storeid, $op, @options) = @_;
22
23 my $monhost = $scfg->{monhost};
24 $monhost =~ s/;/,/g;
25
26 my $keyring = "/etc/pve/priv/ceph/${storeid}.keyring";
27 my $pool = $scfg->{pool} ? $scfg->{pool} : 'rbd';
28 my $username = $scfg->{username} ? $scfg->{username} : 'admin';
29
30 my $cmd = ['/usr/bin/rbd', '-p', $pool, '-m', $monhost];
31
32 if(-e $keyring){
33 push @$cmd, '-n', "client.$username";
34 push @$cmd, '--keyring', $keyring;
35 push @$cmd, '--auth_supported', 'cephx';
36 }else{
37 push @$cmd, '--auth_supported', 'none';
38 }
39
40 push @$cmd, $op;
41
42 push @$cmd, @options if scalar(@options);
43
44 return $cmd;
45 };
46
47 my $rados_cmd = sub {
48 my ($scfg, $storeid, $op, @options) = @_;
49
50 my $monhost = $scfg->{monhost};
51 $monhost =~ s/;/,/g;
52
53 my $keyring = "/etc/pve/priv/ceph/${storeid}.keyring";
54 my $pool = $scfg->{pool} ? $scfg->{pool} : 'rbd';
55 my $username = $scfg->{username} ? $scfg->{username} : 'admin';
56
57 my $cmd = ['/usr/bin/rados', '-p', $pool, '-m', $monhost];
58
59 if(-e $keyring){
60 push @$cmd, '-n', "client.$username";
61 push @$cmd, '--keyring', $keyring;
62 push @$cmd, '--auth_supported', 'cephx';
63 }else{
64 push @$cmd, '--auth_supported', 'none';
65 }
66
67 push @$cmd, $op;
68
69 push @$cmd, @options if scalar(@options);
70
71 return $cmd;
72 };
73
74 sub rbd_ls {
75 my ($scfg, $storeid) = @_;
76
77 my $cmd = &$rbd_cmd($scfg, $storeid, 'ls', '-l');
78 my $pool = $scfg->{pool} ? $scfg->{pool} : 'rbd';
79
80 my $list = {};
81
82 my $parser = sub {
83 my $line = shift;
84
85 if ($line =~ m/^((vm|base)-(\d+)-disk-\d+)\s+(\d+)(M|G|T)\s((\S+)\/((vm|base)-\d+-\S+@\S+))?/) {
86 my ($image, $owner, $size, $unit, $parent) = ($1, $3, $4, $5, $8);
87
88 $list->{$pool}->{$image} = {
89 name => $image,
90 size => $size*rbd_unittobytes()->{$unit},
91 parent => $parent,
92 vmid => $owner
93 };
94 }
95 };
96
97 eval {
98 run_command($cmd, errmsg => "rbd error", errfunc => sub {}, outfunc => $parser);
99 };
100 my $err = $@;
101
102 die $err if $err && $err !~ m/doesn't contain rbd images/ ;
103
104 return $list;
105 }
106
107 sub rbd_volume_info {
108 my ($scfg, $storeid, $volname, $snap) = @_;
109
110 my $cmd = undef;
111
112 if($snap){
113 $cmd = &$rbd_cmd($scfg, $storeid, 'info', $volname, '--snap', $snap);
114 }else{
115 $cmd = &$rbd_cmd($scfg, $storeid, 'info', $volname);
116 }
117
118 my $size = undef;
119 my $parent = undef;
120 my $format = undef;
121 my $protected = undef;
122
123 my $parser = sub {
124 my $line = shift;
125
126 if ($line =~ m/size (\d+) (M|G|T)B in (\d+) objects/) {
127 $size = $1 * rbd_unittobytes()->{$2} if ($1);
128 } elsif ($line =~ m/parent:\s(\S+)\/(\S+)/) {
129 $parent = $2;
130 } elsif ($line =~ m/format:\s(\d+)/) {
131 $format = $1;
132 } elsif ($line =~ m/protected:\s(\S+)/) {
133 $protected = 1 if $1 eq "True";
134 }
135
136 };
137
138 run_command($cmd, errmsg => "rbd error", errfunc => sub {}, outfunc => $parser);
139
140 return ($size, $parent, $format, $protected);
141 }
142
143 sub addslashes {
144 my $text = shift;
145 $text =~ s/;/\\;/g;
146 $text =~ s/:/\\:/g;
147 return $text;
148 }
149
150 # Configuration
151
152 PVE::JSONSchema::register_format('pve-storage-monhost', \&parse_monhost);
153 sub parse_monhost {
154 my ($name, $noerr) = @_;
155
156 if ($name !~ m/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i) {
157 return undef if $noerr;
158 die "lvm name '$name' contains illegal characters\n";
159 }
160
161 return $name;
162 }
163
164 sub type {
165 return 'rbd';
166 }
167
168 sub plugindata {
169 return {
170 content => [ {images => 1}, { images => 1 }],
171 };
172 }
173
174 sub properties {
175 return {
176 monhost => {
177 description => "Monitors daemon ips.",
178 type => 'string',
179 },
180 pool => {
181 description => "Pool.",
182 type => 'string',
183 },
184 username => {
185 description => "RBD Id.",
186 type => 'string',
187 },
188 authsupported => {
189 description => "Authsupported.",
190 type => 'string',
191 },
192 };
193 }
194
195 sub options {
196 return {
197 nodes => { optional => 1 },
198 disable => { optional => 1 },
199 monhost => { fixed => 1 },
200 pool => { optional => 1 },
201 username => { optional => 1 },
202 content => { optional => 1 },
203 };
204 }
205
206 # Storage implementation
207
208 sub parse_volname {
209 my ($class, $volname) = @_;
210
211 if ($volname =~ m/^((base-(\d+)-\S+)\/)?((base)?(vm)?-(\d+)-\S+)$/) {
212 return ('images', $4, $7, $2, $3, $5);
213 }
214
215 die "unable to parse rbd volume name '$volname'\n";
216 }
217
218 sub path {
219 my ($class, $scfg, $volname, $storeid) = @_;
220
221 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
222
223 my $monhost = addslashes($scfg->{monhost});
224 my $pool = $scfg->{pool} ? $scfg->{pool} : 'rbd';
225 my $username = $scfg->{username} ? $scfg->{username} : 'admin';
226
227 my $path = "rbd:$pool/$name:mon_host=$monhost";
228 my $keyring = "/etc/pve/priv/ceph/${storeid}.keyring";
229
230 if(-e $keyring ){
231 $path .= ":id=$username:auth_supported=cephx:keyring=$keyring";
232 }else{
233 $path .= ":auth_supported=none";
234 }
235
236 return ($path, $vmid, $vtype);
237 }
238
239 my $find_free_diskname = sub {
240 my ($storeid, $scfg, $vmid) = @_;
241
242 my $rbd = rbd_ls($scfg, $storeid);
243 my $pool = $scfg->{pool} ? $scfg->{pool} : 'rbd';
244 my $disk_ids = {};
245 my $dat = $rbd->{$pool};
246
247 foreach my $image (keys %$dat) {
248 my $volname = $dat->{$image}->{name};
249 if ($volname =~ m/(vm|base)-$vmid-disk-(\d+)/){
250 $disk_ids->{$2} = 1;
251 }
252 }
253 #fix: can we search in $rbd hash key with a regex to find (vm|base) ?
254 for (my $i = 1; $i < 100; $i++) {
255 if (!$disk_ids->{$i}) {
256 return "vm-$vmid-disk-$i";
257 }
258 }
259
260 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n";
261 };
262
263 sub create_base {
264 my ($class, $storeid, $scfg, $volname) = @_;
265
266 my $snap = '__base__';
267
268 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
269 $class->parse_volname($volname);
270
271 die "create_base not possible with base image\n" if $isBase;
272
273 my ($size, $parent, $format, undef) = rbd_volume_info($scfg, $storeid, $name);
274 die "rbd volume info on '$name' failed\n" if !($size);
275
276 die "rbd image must be at format V2" if $format ne "2";
277
278 die "volname '$volname' contains wrong information about parent $parent $basename\n"
279 if $basename && (!$parent || $parent ne $basename."@".$snap);
280
281 my $newname = $name;
282 $newname =~ s/^vm-/base-/;
283
284 my $newvolname = $basename ? "$basename/$newname" : "$newname";
285
286 my $cmd = &$rbd_cmd($scfg, $storeid, 'rename', $name, $newname);
287 run_command($cmd, errmsg => "rbd rename $name' error", errfunc => sub {});
288
289 my $running = undef; #fixme : is create_base always offline ?
290
291 $class->volume_snapshot($scfg, $storeid, $newname, $snap, $running);
292
293 my (undef, undef, undef, $protected) = rbd_volume_info($scfg, $storeid, $newname, $snap);
294
295 if (!$protected){
296 my $cmd = &$rbd_cmd($scfg, $storeid, 'snap', 'protect', $newname, '--snap', $snap);
297 run_command($cmd, errmsg => "rbd protect $newname snap $snap' error", errfunc => sub {});
298 }
299
300 return $newvolname;
301
302 }
303
304 sub clone_image {
305 my ($class, $scfg, $storeid, $volname, $vmid) = @_;
306
307 my $snap = '__base__';
308
309 my ($vtype, $basename, $basevmid, undef, undef, $isBase) =
310 $class->parse_volname($volname);
311
312 die "clone_image onyl works on base images\n" if !$isBase;
313
314 my $name = &$find_free_diskname($storeid, $scfg, $vmid);
315
316 warn "clone $volname: $basename to $name\n";
317
318 my $newvol = "$basename/$name";
319
320 my $cmd = &$rbd_cmd($scfg, $storeid, 'clone', $basename, '--snap', $snap, $name);
321 run_command($cmd, errmsg => "rbd clone $basename' error", errfunc => sub {});
322
323 return $newvol;
324 }
325
326 sub alloc_image {
327 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
328
329
330 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
331 if $name && $name !~ m/^vm-$vmid-/;
332
333 $name = &$find_free_diskname($storeid, $scfg, $vmid);
334
335 my $cmd = &$rbd_cmd($scfg, $storeid, 'create', '--format' , 2, '--size', int(($size+1023)/1024), $name);
336 run_command($cmd, errmsg => "rbd create $name' error", errfunc => sub {});
337
338 return $name;
339 }
340
341 sub free_image {
342 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
343
344 my ($vtype, $name, $vmid, undef, undef, undef) =
345 $class->parse_volname($volname);
346
347 if ($isBase) {
348 my $snap = '__base__';
349 my (undef, undef, undef, $protected) = rbd_volume_info($scfg, $storeid, $name, $snap);
350 if ($protected){
351 my $cmd = &$rbd_cmd($scfg, $storeid, 'snap', 'unprotect', $name, '--snap', $snap);
352 run_command($cmd, errmsg => "rbd unprotect $name snap $snap' error", errfunc => sub {});
353 }
354 }
355
356 my $cmd = &$rbd_cmd($scfg, $storeid, 'snap', 'purge', $name);
357 run_command($cmd, errmsg => "rbd snap purge $volname' error", outfunc => sub {}, errfunc => sub {});
358
359 $cmd = &$rbd_cmd($scfg, $storeid, 'rm', $name);
360 run_command($cmd, errmsg => "rbd rm $volname' error", outfunc => sub {}, errfunc => sub {});
361
362 return undef;
363 }
364
365 sub list_images {
366 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
367
368 $cache->{rbd} = rbd_ls($scfg, $storeid) if !$cache->{rbd};
369 my $pool = $scfg->{pool} ? $scfg->{pool} : 'rbd';
370
371 my $res = [];
372
373 if (my $dat = $cache->{rbd}->{$pool}) {
374 foreach my $image (keys %$dat) {
375
376 my $volname = $dat->{$image}->{name};
377
378 my $volid = "$storeid:$volname";
379
380 my $owner = $dat->{$volname}->{vmid};
381 if ($vollist) {
382 my $found = grep { $_ eq $volid } @$vollist;
383 next if !$found;
384 } else {
385 next if defined ($vmid) && ($owner ne $vmid);
386 }
387
388 my $info = $dat->{$volname};
389 $info->{volid} = $volid;
390 $info->{format} = 'raw';
391
392 push @$res, $info;
393 }
394 }
395
396 return $res;
397 }
398
399 sub status {
400 my ($class, $storeid, $scfg, $cache) = @_;
401
402 my $cmd = &$rados_cmd($scfg, $storeid, 'df');
403
404 my $stats = {};
405
406 my $parser = sub {
407 my $line = shift;
408 if ($line =~ m/^\s+total\s(\S+)\s+(\d+)/) {
409 $stats->{$1} = $2;
410 }
411 };
412
413 eval {
414 run_command($cmd, errmsg => "rados error", errfunc => sub {}, outfunc => $parser);
415 };
416
417 my $total = $stats->{space} ? $stats->{space}*1024 : 0;
418 my $free = $stats->{avail} ? $stats->{avail}*1024 : 0;
419 my $used = $stats->{used} ? $stats->{used}*1024: 0;
420 my $active = 1;
421
422 return ($total, $free, $used, $active);
423 }
424
425 sub activate_storage {
426 my ($class, $storeid, $scfg, $cache) = @_;
427 return 1;
428 }
429
430 sub deactivate_storage {
431 my ($class, $storeid, $scfg, $cache) = @_;
432 return 1;
433 }
434
435 sub activate_volume {
436 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
437 return 1;
438 }
439
440 sub deactivate_volume {
441 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
442 return 1;
443 }
444
445 sub volume_size_info {
446 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
447
448 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
449 my ($size, undef) = rbd_volume_info($scfg, $storeid, $name);
450 return $size;
451 }
452
453 sub volume_resize {
454 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
455
456 return 1 if $running;
457
458 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
459
460 my $cmd = &$rbd_cmd($scfg, $storeid, 'resize', '--size', ($size/1024/1024), $name);
461 run_command($cmd, errmsg => "rbd resize $volname' error", errfunc => sub {});
462 return undef;
463 }
464
465 sub volume_snapshot {
466 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
467
468 return 1 if $running;
469
470 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
471
472 my $cmd = &$rbd_cmd($scfg, $storeid, 'snap', 'create', '--snap', $snap, $name);
473 run_command($cmd, errmsg => "rbd snapshot $volname' error", errfunc => sub {});
474 return undef;
475 }
476
477 sub volume_snapshot_rollback {
478 my ($class, $scfg, $storeid, $volname, $snap) = @_;
479
480 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
481
482 my $cmd = &$rbd_cmd($scfg, $storeid, 'snap', 'rollback', '--snap', $snap, $name);
483 run_command($cmd, errmsg => "rbd snapshot $volname to $snap' error", errfunc => sub {});
484 }
485
486 sub volume_snapshot_delete {
487 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
488
489 return 1 if $running;
490
491 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
492
493 my $cmd = &$rbd_cmd($scfg, $storeid, 'snap', 'rm', '--snap', $snap, $name);
494 run_command($cmd, errmsg => "rbd snapshot $volname' error", errfunc => sub {});
495 return undef;
496 }
497
498 sub volume_has_feature {
499 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
500
501 my $features = {
502 snapshot => { current => 1, snap => 1},
503 clone => { base => 1},
504 template => { current => 1},
505 copy => { base => 1, current => 1, snap => 1},
506 };
507
508 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
509 $class->parse_volname($volname);
510
511 my $key = undef;
512 if($snapname){
513 $key = 'snap';
514 }else{
515 $key = $isBase ? 'base' : 'current';
516 }
517 return 1 if $features->{$feature}->{$key};
518
519 return undef;
520 }
521
522 1;