]> git.proxmox.com Git - pve-storage.git/blame - PVE/Storage/ZFSPlugin.pm
bump version to 3.0-17
[pve-storage.git] / PVE / Storage / ZFSPlugin.pm
CommitLineData
4f914e6e
MR
1package PVE::Storage::ZFSPlugin;
2
3use strict;
4use warnings;
5use IO::File;
6use POSIX;
5332e6c9 7use PVE::Tools qw(run_command);
4f914e6e 8use PVE::Storage::Plugin;
4f914e6e
MR
9use Digest::MD5 qw(md5_hex);
10
11use base qw(PVE::Storage::Plugin);
12
13my @ssh_opts = ('-o', 'BatchMode=yes');
14my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
15
16sub zfs_request {
17 my ($scfg, $timeout, $method, @params) = @_;
18
5332e6c9 19 my $cmdmap;
4f914e6e
MR
20 my $zfscmd;
21 my $target;
4f914e6e 22
5332e6c9 23 $timeout = 5 if !$timeout;
4f914e6e
MR
24
25 if ($scfg->{iscsiprovider} eq 'comstar') {
26 my $stmfadmcmd = "/usr/sbin/stmfadm";
27 my $sbdadmcmd = "/usr/sbin/sbdadm";
28
29 $cmdmap = {
30 create_lu => { cmd => $stmfadmcmd, method => 'create-lu' },
31 delete_lu => { cmd => $stmfadmcmd, method => 'delete-lu' },
32 import_lu => { cmd => $stmfadmcmd, method => 'import-lu' },
33 modify_lu => { cmd => $stmfadmcmd, method => 'modify-lu' },
34 add_view => { cmd => $stmfadmcmd, method => 'add-view' },
35 list_view => { cmd => $stmfadmcmd, method => 'list-view' },
36 list_lu => { cmd => $sbdadmcmd, method => 'list-lu' },
37 zpool_list => { cmd => 'zpool', method => 'list' },
38 };
39 } else {
40 die 'unknown iscsi provider. Available [comstar]';
41 }
42
43 if ($cmdmap->{$method}) {
44 $zfscmd = $cmdmap->{$method}->{cmd};
45 $method = $cmdmap->{$method}->{method};
46 } else {
47 $zfscmd = 'zfs';
48 }
49
98735f82 50 $target = 'root@' . $scfg->{portal};
4f914e6e
MR
51
52 my $cmd = [@ssh_cmd, $target, $zfscmd, $method, @params];
53
5332e6c9
DM
54 my $msg = '';
55
56 my $output = sub {
57 my $line = shift;
58 $msg .= "$line\n";
59 };
60
4f914e6e
MR
61 run_command($cmd, outfunc => $output, timeout => $timeout);
62
63 return $msg;
64}
65
66sub zfs_parse_size {
67 my ($text) = @_;
68
69 return 0 if !$text;
70
71 if ($text =~ m/^(\d+(\.\d+)?)([TGMK])?$/) {
72 my ($size, $reminder, $unit) = ($1, $2, $3);
73 return $size if !$unit;
74 if ($unit eq 'K') {
75 $size *= 1024;
76 } elsif ($unit eq 'M') {
77 $size *= 1024*1024;
78 } elsif ($unit eq 'G') {
79 $size *= 1024*1024*1024;
80 } elsif ($unit eq 'T') {
81 $size *= 1024*1024*1024*1024;
82 }
83
84 if ($reminder) {
85 $size = ceil($size);
86 }
87 return $size;
88 } else {
89 return 0;
90 }
91}
92
93sub zfs_get_pool_stats {
5332e6c9 94 my ($scfg) = @_;
4f914e6e 95
1fca1464 96 my $available = 0;
5332e6c9 97 my $used = 0;
4f914e6e 98
5332e6c9
DM
99 my $text = zfs_request($scfg, undef, 'get', '-o', 'value', '-Hp',
100 'available,used', $scfg->{pool});
4f914e6e 101
5332e6c9 102 my @lines = split /\n/, $text;
4f914e6e 103
5332e6c9 104 if($lines[0] =~ /^(\d+)$/) {
1fca1464 105 $available = $1;
5332e6c9 106 }
4f914e6e 107
5332e6c9
DM
108 if($lines[1] =~ /^(\d+)$/) {
109 $used = $1;
110 }
4f914e6e 111
1fca1464 112 return ($available, $used);
4f914e6e
MR
113}
114
115sub zfs_parse_zvol_list {
116 my ($text) = @_;
117
118 my $list = ();
119
120 return $list if !$text;
121
122 my @lines = split /\n/, $text;
123 foreach my $line (@lines) {
124 if ($line =~ /^(.+)\s+([a-zA-Z0-9\.]+|\-)\s+(.+)$/) {
125 my $zvol = {};
126 my $name;
127 my $disk;
128 my @zvols = split /\//, $1;
129 my $pool = $zvols[0];
130
131 if (scalar(@zvols) == 2 && $zvols[0] !~ /^rpool$/) {
132 $disk = $zvols[1];
133 next unless $disk =~ m!^(\w+)-(\d+)-(\w+)-(\d+)$!;
134 $name = $pool . '/' . $disk;
5332e6c9 135 } else {
4f914e6e
MR
136 next;
137 }
138
139 $zvol->{name} = $name;
140 $zvol->{size} = zfs_parse_size($2);
141 if ($3 !~ /^-$/) {
142 $zvol->{origin} = $3;
143 }
144 push @$list, $zvol;
145 }
146 }
147
148 return $list;
149}
150
151sub zfs_get_lu_name {
152 my ($scfg, $zvol) = @_;
153 my $object;
154
155 if ($zvol =~ /^.+\/.+/) {
156 $object = "/dev/zvol/rdsk/$zvol";
5332e6c9 157 } else {
4f914e6e
MR
158 $object = "/dev/zvol/rdsk/$scfg->{pool}/$zvol";
159 }
160
161 my $text = zfs_request($scfg, undef, 'list_lu');
162 my @lines = split /\n/, $text;
163 foreach my $line (@lines) {
164 return $1 if ($line =~ /(\w+)\s+\d+\s+$object$/);
165 }
166 die "Could not find lu_name for zvol $zvol";
167}
168
169sub zfs_get_zvol_size {
170 my ($scfg, $zvol) = @_;
171
172 my $text = zfs_request($scfg, undef, 'get', '-Hp', 'volsize', "$scfg->{pool}/$zvol");
173
174 if($text =~ /volsize\s(\d+)/){
175 return $1;
176 }
177
178 die "Could not get zvol size";
179}
180
181sub zfs_add_lun_mapping_entry {
182 my ($scfg, $zvol, $guid) = @_;
183
184 if (! defined($guid)) {
185 $guid = zfs_get_lu_name($scfg, $zvol);
186 }
187
188 zfs_request($scfg, undef, 'add_view', $guid);
189}
190
191sub zfs_delete_lu {
192 my ($scfg, $zvol) = @_;
193
194 my $guid = zfs_get_lu_name($scfg, $zvol);
195
196 zfs_request($scfg, undef, 'delete_lu', $guid);
197}
198
199sub zfs_create_lu {
200 my ($scfg, $zvol) = @_;
201
202 my $prefix = '600144f';
203 my $digest = md5_hex($zvol);
204 $digest =~ /(\w{7}(.*))/;
205 my $guid = "$prefix$2";
206
207 zfs_request($scfg, undef, 'create_lu', '-p', 'wcd=false', '-p', "guid=$guid", "/dev/zvol/rdsk/$scfg->{pool}/$zvol");
208
209 return $guid;
210}
211
212sub zfs_import_lu {
213 my ($scfg, $zvol) = @_;
214
215 zfs_request($scfg, undef, 'import_lu', "/dev/zvol/rdsk/$scfg->{pool}/$zvol");
216}
217
218sub zfs_resize_lu {
219 my ($scfg, $zvol, $size) = @_;
220
221 my $guid = zfs_get_lu_name($scfg, $zvol);
222
223 zfs_request($scfg, undef, 'modify_lu', '-s', "${size}K", $guid);
224}
225
226sub zfs_create_zvol {
227 my ($scfg, $zvol, $size) = @_;
228
229 zfs_request($scfg, undef, 'create', '-b', $scfg->{blocksize}, '-V', "${size}k", "$scfg->{pool}/$zvol");
230}
231
232sub zfs_delete_zvol {
233 my ($scfg, $zvol) = @_;
234
235 zfs_request($scfg, undef, 'destroy', '-r', "$scfg->{pool}/$zvol");
236}
237
238sub zfs_get_lun_number {
239 my ($scfg, $guid) = @_;
240 my $lunnum = undef;
241
242 die "could not find lun_number for guid $guid" if !$guid;
243
244 my $text = zfs_request($scfg, undef, 'list_view', '-l', $guid);
245 my @lines = split /\n/, $text;
246 foreach my $line (@lines) {
247 if ($line =~ /^\s*LUN\s*:\s*(\d+)$/) {
248 $lunnum = $1;
249 last;
250 }
251 }
252
253 return $lunnum;
254}
255
256sub zfs_list_zvol {
257 my ($scfg) = @_;
258
259 my $text = zfs_request($scfg, 10, 'list', '-o', 'name,volsize,origin', '-Hr');
260 my $zvols = zfs_parse_zvol_list($text);
261 return undef if !$zvols;
262
263 my $list = ();
264 foreach my $zvol (@$zvols) {
265 my @values = split('/', $zvol->{name});
266
267 my $pool = $values[0];
268 my $image = $values[1];
5332e6c9
DM
269
270 next if $image !~ m/^((vm|base)-(\d+)-\S+)$/;
271 my $owner = $3;
4f914e6e
MR
272
273 my $parent = $zvol->{origin};
274 if($zvol->{origin} && $zvol->{origin} =~ m/^$scfg->{pool}\/(\S+)$/){
275 $parent = $1;
276 }
277
278 $list->{$pool}->{$image} = {
279 name => $image,
280 size => $zvol->{size},
281 parent => $parent,
282 format => 'raw',
283 vmid => $owner
284 };
285 }
286
287 return $list;
288}
289
290# Configuration
291
292sub type {
293 return 'zfs';
294}
295
296sub plugindata {
297 return {
298 content => [ {images => 1}, { images => 1 }],
299 };
300}
301
302sub properties {
303 return {
4f914e6e
MR
304 iscsiprovider => {
305 description => "iscsi provider",
306 type => 'string',
307 },
7ecc43ed
AD
308 blocksize => {
309 description => "block size",
310 type => 'string',
311 }
4f914e6e
MR
312 };
313}
314
315sub options {
316 return {
317 nodes => { optional => 1 },
318 disable => { optional => 1 },
319 portal => { fixed => 1 },
320 target => { fixed => 1 },
321 pool => { fixed => 1 },
4f914e6e
MR
322 blocksize => { fixed => 1 },
323 iscsiprovider => { fixed => 1 },
4f914e6e
MR
324 content => { optional => 1 },
325 };
326}
327
328# Storage implementation
329
330sub parse_volname {
331 my ($class, $volname) = @_;
332
333 if ($volname =~ m/^(((base|vm)-(\d+)-\S+)\/)?((base)?(vm)?-(\d+)-\S+)$/) {
334 return ('images', $5, $8, $2, $4, $6);
335 }
336
337 die "unable to parse zfs volume name '$volname'\n";
338}
339
340sub path {
341 my ($class, $scfg, $volname) = @_;
342
343 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
344
345 my $target = $scfg->{target};
346 my $portal = $scfg->{portal};
347
348 my $guid = zfs_get_lu_name($scfg, $name);
349 my $lun = zfs_get_lun_number($scfg, $guid);
350
351 my $path = "iscsi://$portal/$target/$lun";
352
353 return ($path, $vmid, $vtype);
354}
355
356my $find_free_diskname = sub {
357 my ($storeid, $scfg, $vmid) = @_;
358
359 my $name = undef;
360 my $volumes = zfs_list_zvol($scfg);
361
362 my $disk_ids = {};
363 my $dat = $volumes->{$scfg->{pool}};
364
365 foreach my $image (keys %$dat) {
366 my $volname = $dat->{$image}->{name};
367 if ($volname =~ m/(vm|base)-$vmid-disk-(\d+)/){
368 $disk_ids->{$2} = 1;
369 }
370 }
371
372 for (my $i = 1; $i < 100; $i++) {
373 if (!$disk_ids->{$i}) {
374 return "vm-$vmid-disk-$i";
375 }
376 }
377
378 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n";
379};
380
381sub create_base {
382 my ($class, $storeid, $scfg, $volname) = @_;
383
384 my $snap = '__base__';
385
386 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
387 $class->parse_volname($volname);
388
389 die "create_base not possible with base image\n" if $isBase;
390
391 my $newname = $name;
392 $newname =~ s/^vm-/base-/;
393
394 my $newvolname = $basename ? "$basename/$newname" : "$newname";
395
396 zfs_delete_lu($scfg, $name);
397 zfs_request($scfg, undef, 'rename', "$scfg->{pool}/$name", "$scfg->{pool}/$newname");
398
399 my $guid = zfs_create_lu($scfg, $newname);
400 zfs_add_lun_mapping_entry($scfg, $newname, $guid);
401
402 my $running = undef; #fixme : is create_base always offline ?
403
404 $class->volume_snapshot($scfg, $storeid, $newname, $snap, $running);
405
406 return $newvolname;
407}
408
409sub clone_image {
410 my ($class, $scfg, $storeid, $volname, $vmid) = @_;
411
412 my $snap = '__base__';
413
414 my ($vtype, $basename, $basevmid, undef, undef, $isBase) =
415 $class->parse_volname($volname);
416
417 die "clone_image only works on base images\n" if !$isBase;
418
419 my $name = &$find_free_diskname($storeid, $scfg, $vmid);
420
421 warn "clone $volname: $basename to $name\n";
422
423 zfs_request($scfg, undef, 'clone', "$scfg->{pool}/$basename\@$snap", "$scfg->{pool}/$name");
424
425 my $guid = zfs_create_lu($scfg, $name);
426 zfs_add_lun_mapping_entry($scfg, $name, $guid);
427
428 return $name;
429}
430
431sub alloc_image {
432 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
433
434 die "unsupported format '$fmt'" if $fmt ne 'raw';
435
436 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
437 if $name && $name !~ m/^vm-$vmid-/;
438
439 $name = &$find_free_diskname($storeid, $scfg, $vmid);
440
441 zfs_create_zvol($scfg, $name, $size);
442 my $guid = zfs_create_lu($scfg, $name);
443 zfs_add_lun_mapping_entry($scfg, $name, $guid);
444
445 return $name;
446}
447
448sub free_image {
449 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
450
451 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
452
453 zfs_delete_lu($scfg, $name);
454 eval {
455 zfs_delete_zvol($scfg, $name);
456 };
457 do {
458 my $err = $@;
459 my $guid = zfs_create_lu($scfg, $name);
460 zfs_add_lun_mapping_entry($scfg, $name, $guid);
461 die $err;
462 } if $@;
463
464 return undef;
465}
466
467sub list_images {
468 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
469
470 $cache->{zfs} = zfs_list_zvol($scfg) if !$cache->{zfs};
471 my $zfspool = $scfg->{pool};
472 my $res = [];
473
474 if (my $dat = $cache->{zfs}->{$zfspool}) {
475
476 foreach my $image (keys %$dat) {
477
478 my $volname = $dat->{$image}->{name};
479 my $parent = $dat->{$image}->{parent};
480
481 my $volid = undef;
482 if ($parent && $parent =~ m/^(\S+)@(\S+)$/) {
483 my ($basename) = ($1);
484 $volid = "$storeid:$basename/$volname";
485 } else {
486 $volid = "$storeid:$volname";
487 }
488
489 my $owner = $dat->{$volname}->{vmid};
490 if ($vollist) {
491 my $found = grep { $_ eq $volid } @$vollist;
492 next if !$found;
493 } else {
494 next if defined ($vmid) && ($owner ne $vmid);
495 }
496
497 my $info = $dat->{$volname};
498 $info->{volid} = $volid;
499 push @$res, $info;
500 }
501 }
502
503 return $res;
504}
505
506sub status {
507 my ($class, $storeid, $scfg, $cache) = @_;
508
509 my $total = 0;
510 my $free = 0;
511 my $used = 0;
512 my $active = 0;
513
514 eval {
1fca1464 515 ($free, $used) = zfs_get_pool_stats($scfg);
4f914e6e 516 $active = 1;
1fca1464 517 $total = $free + $used;
4f914e6e
MR
518 };
519 warn $@ if $@;
520
521 return ($total, $free, $used, $active);
522}
523
524sub activate_storage {
525 my ($class, $storeid, $scfg, $cache) = @_;
526 return 1;
527}
528
529sub deactivate_storage {
530 my ($class, $storeid, $scfg, $cache) = @_;
531 return 1;
532}
533
534sub activate_volume {
535 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
536 return 1;
537}
538
539sub deactivate_volume {
540 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
541 return 1;
542}
543
544sub volume_size_info {
545 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
546
547 return zfs_get_zvol_size($scfg, $volname);
548}
549
550sub volume_resize {
551 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
552
553 my $new_size = ($size/1024);
554
555 zfs_request($scfg, undef, 'set', 'volsize=' . $new_size . 'k', "$scfg->{pool}/$volname");
556 zfs_resize_lu($scfg, $volname, $new_size);
557}
558
559sub volume_snapshot {
560 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
561
562 zfs_request($scfg, undef, 'snapshot', "$scfg->{pool}/$volname\@$snap");
563}
564
565sub volume_snapshot_rollback {
566 my ($class, $scfg, $storeid, $volname, $snap) = @_;
567
568 zfs_delete_lu($scfg, $volname);
569
570 zfs_request($scfg, undef, 'rollback', "$scfg->{pool}/$volname\@$snap");
571
572 zfs_import_lu($scfg, $volname);
573
574 zfs_add_lun_mapping_entry($scfg, $volname);
575}
576
577sub volume_snapshot_delete {
578 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
579
580 zfs_request($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap");
581}
582
583sub volume_has_feature {
584 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
585
586 my $features = {
587 snapshot => { current => 1, snap => 1},
588 clone => { base => 1},
589 template => { current => 1},
590 copy => { base => 1, current => 1},
591 };
592
593 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
594 $class->parse_volname($volname);
595
596 my $key = undef;
5332e6c9
DM
597
598 if ($snapname) {
4f914e6e
MR
599 $key = 'snap';
600 } else {
601 $key = $isBase ? 'base' : 'current';
602 }
5332e6c9 603
4f914e6e
MR
604 return 1 if $features->{$feature}->{$key};
605
606 return undef;
607}
608
6091;