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