]> git.proxmox.com Git - pve-storage.git/blame - PVE/Storage/ZFSPlugin.pm
zfs: cleanup zfs_request
[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);
5bb8e010 8use PVE::Storage::ZFSDirPlugin;
4f914e6e 9
5bb8e010 10use base qw(PVE::Storage::ZFSDirPlugin);
a7d56be6
MR
11use PVE::Storage::LunCmd::Comstar;
12use PVE::Storage::LunCmd::Istgt;
78a64432 13use PVE::Storage::LunCmd::Iet;
4f914e6e
MR
14
15my @ssh_opts = ('-o', 'BatchMode=yes');
16my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
3b219e80 17my $id_rsa_path = '/etc/pve/priv/zfs';
4f914e6e 18
a7d56be6 19my $lun_cmds = {
3b219e80
MR
20 create_lu => 1,
21 delete_lu => 1,
22 import_lu => 1,
23 modify_lu => 1,
24 add_view => 1,
25 list_view => 1,
26 list_lu => 1,
a7d56be6
MR
27};
28
29my $zfs_unknown_scsi_provider = sub {
3b219e80 30 my ($provider) = @_;
a7d56be6 31
3b219e80 32 die "$provider: unknown iscsi provider. Available [comstar, istgt, iet]";
a7d56be6
MR
33};
34
35my $zfs_get_base = sub {
3b219e80
MR
36 my ($scfg) = @_;
37
38 if ($scfg->{iscsiprovider} eq 'comstar') {
39 return PVE::Storage::LunCmd::Comstar::get_base;
40 } elsif ($scfg->{iscsiprovider} eq 'istgt') {
41 return PVE::Storage::LunCmd::Istgt::get_base;
42 } elsif ($scfg->{iscsiprovider} eq 'iet') {
43 return PVE::Storage::LunCmd::Iet::get_base;
44 } else {
45 $zfs_unknown_scsi_provider->($scfg->{iscsiprovider});
46 }
a7d56be6
MR
47};
48
4f914e6e 49sub zfs_request {
7730694e 50 my ($class, $scfg, $timeout, $method, @params) = @_;
4f914e6e 51
5332e6c9 52 $timeout = 5 if !$timeout;
4f914e6e 53
e8004c9c
DM
54 my $msg = '';
55
3b219e80
MR
56 if ($lun_cmds->{$method}) {
57 if ($scfg->{iscsiprovider} eq 'comstar') {
58 $msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
59 } elsif ($scfg->{iscsiprovider} eq 'istgt') {
60 $msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
61 } elsif ($scfg->{iscsiprovider} eq 'iet') {
62 $msg = PVE::Storage::LunCmd::Iet::run_lun_command($scfg, $timeout, $method, @params);
63 } else {
64 $zfs_unknown_scsi_provider->($scfg->{iscsiprovider});
65 }
66 } else {
3b219e80 67
e8004c9c
DM
68 my $target = 'root@' . $scfg->{portal};
69
70 my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target];
3b219e80 71
e8004c9c
DM
72 if ($method eq 'zpool_list') {
73 push @$cmd, 'zpool', 'list';
74 } else {
75 push @$cmd, 'zfs', $method;
76 }
3b219e80 77
e8004c9c 78 push @$cmd, @params;
3b219e80 79
e8004c9c
DM
80 my $output = sub {
81 my $line = shift;
82 $msg .= "$line\n";
3b219e80
MR
83 };
84
85 run_command($cmd, outfunc => $output, timeout => $timeout);
86 }
4f914e6e
MR
87
88 return $msg;
89}
90
4f914e6e 91sub zfs_get_lu_name {
7730694e 92 my ($class, $scfg, $zvol) = @_;
4f914e6e
MR
93 my $object;
94
3b219e80 95 my $base = $zfs_get_base->($scfg);
4f914e6e 96 if ($zvol =~ /^.+\/.+/) {
a7d56be6 97 $object = "$base/$zvol";
5332e6c9 98 } else {
a7d56be6 99 $object = "$base/$scfg->{pool}/$zvol";
4f914e6e
MR
100 }
101
7730694e 102 my $lu_name = $class->zfs_request($scfg, undef, 'list_lu', $object);
a7d56be6 103
3b219e80
MR
104 return $lu_name if $lu_name;
105
4f914e6e
MR
106 die "Could not find lu_name for zvol $zvol";
107}
108
4f914e6e 109sub zfs_add_lun_mapping_entry {
7730694e 110 my ($class, $scfg, $zvol, $guid) = @_;
4f914e6e
MR
111
112 if (! defined($guid)) {
7730694e 113 $guid = $class->zfs_get_lu_name($scfg, $zvol);
4f914e6e 114 }
3b219e80 115
7730694e 116 $class->zfs_request($scfg, undef, 'add_view', $guid);
4f914e6e
MR
117}
118
119sub zfs_delete_lu {
7730694e 120 my ($class, $scfg, $zvol) = @_;
4f914e6e 121
7730694e 122 my $guid = $class->zfs_get_lu_name($scfg, $zvol);
4f914e6e 123
7730694e 124 $class->zfs_request($scfg, undef, 'delete_lu', $guid);
4f914e6e
MR
125}
126
127sub zfs_create_lu {
7730694e 128 my ($class, $scfg, $zvol) = @_;
4f914e6e 129
3b219e80 130 my $base = $zfs_get_base->($scfg);
7730694e 131 my $guid = $class->zfs_request($scfg, undef, 'create_lu', "$base/$scfg->{pool}/$zvol");
4f914e6e
MR
132
133 return $guid;
134}
135
136sub zfs_import_lu {
7730694e 137 my ($class, $scfg, $zvol) = @_;
4f914e6e 138
3b219e80 139 my $base = $zfs_get_base->($scfg);
7730694e 140 $class->zfs_request($scfg, undef, 'import_lu', "$base/$scfg->{pool}/$zvol");
4f914e6e
MR
141}
142
143sub zfs_resize_lu {
7730694e 144 my ($class, $scfg, $zvol, $size) = @_;
4f914e6e 145
7730694e 146 my $guid = $class->zfs_get_lu_name($scfg, $zvol);
4f914e6e 147
7730694e 148 $class->zfs_request($scfg, undef, 'modify_lu', "${size}K", $guid);
4f914e6e
MR
149}
150
151sub zfs_get_lun_number {
7730694e 152 my ($class, $scfg, $guid) = @_;
4f914e6e
MR
153
154 die "could not find lun_number for guid $guid" if !$guid;
155
7730694e 156 return $class->zfs_request($scfg, undef, 'list_view', $guid);
4f914e6e
MR
157}
158
159# Configuration
160
161sub type {
162 return 'zfs';
163}
164
165sub plugindata {
166 return {
3b219e80 167 content => [ {images => 1}, { images => 1 }],
4f914e6e
MR
168 };
169}
170
171sub properties {
172 return {
3b219e80
MR
173 iscsiprovider => {
174 description => "iscsi provider",
175 type => 'string',
176 },
70986fd9
CA
177 # this will disable write caching on comstar and istgt.
178 # it is not implemented for iet. iet blockio always operates with
179 # writethrough caching when not in readonly mode
180 nowritecache => {
181 description => "disable write caching on the target",
182 type => 'boolean',
183 },
454c15e2
CA
184 comstar_tg => {
185 description => "target group for comstar views",
186 type => 'string',
187 },
188 comstar_hg => {
189 description => "host group for comstar views",
190 type => 'string',
191 },
4f914e6e
MR
192 };
193}
194
195sub options {
196 return {
a7d56be6
MR
197 nodes => { optional => 1 },
198 disable => { optional => 1 },
199 portal => { fixed => 1 },
3b219e80 200 target => { fixed => 1 },
a7d56be6 201 pool => { fixed => 1 },
3b219e80
MR
202 blocksize => { fixed => 1 },
203 iscsiprovider => { fixed => 1 },
70986fd9 204 nowritecache => { optional => 1 },
a9bd7bdf 205 sparse => { optional => 1 },
454c15e2
CA
206 comstar_hg => { optional => 1 },
207 comstar_tg => { optional => 1 },
3b219e80 208 content => { optional => 1 },
4f914e6e
MR
209 };
210}
211
212# Storage implementation
213
214sub parse_volname {
215 my ($class, $volname) = @_;
216
217 if ($volname =~ m/^(((base|vm)-(\d+)-\S+)\/)?((base)?(vm)?-(\d+)-\S+)$/) {
3b219e80 218 return ('images', $5, $8, $2, $4, $6);
4f914e6e
MR
219 }
220
221 die "unable to parse zfs volume name '$volname'\n";
222}
223
224sub path {
225 my ($class, $scfg, $volname) = @_;
226
227 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
228
229 my $target = $scfg->{target};
230 my $portal = $scfg->{portal};
231
7730694e
DM
232 my $guid = $class->zfs_get_lu_name($scfg, $name);
233 my $lun = $class->zfs_get_lun_number($scfg, $guid);
3b219e80 234
4f914e6e 235 my $path = "iscsi://$portal/$target/$lun";
3b219e80 236
4f914e6e
MR
237 return ($path, $vmid, $vtype);
238}
239
4f914e6e
MR
240sub create_base {
241 my ($class, $storeid, $scfg, $volname) = @_;
242
243 my $snap = '__base__';
244
245 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
246 $class->parse_volname($volname);
247
248 die "create_base not possible with base image\n" if $isBase;
249
250 my $newname = $name;
251 $newname =~ s/^vm-/base-/;
252
253 my $newvolname = $basename ? "$basename/$newname" : "$newname";
254
7730694e
DM
255 $class->zfs_delete_lu($scfg, $name);
256 $class->zfs_request($scfg, undef, 'rename', "$scfg->{pool}/$name", "$scfg->{pool}/$newname");
4f914e6e 257
7730694e
DM
258 my $guid = $class->zfs_create_lu($scfg, $newname);
259 $class->zfs_add_lun_mapping_entry($scfg, $newname, $guid);
4f914e6e
MR
260
261 my $running = undef; #fixme : is create_base always offline ?
262
263 $class->volume_snapshot($scfg, $storeid, $newname, $snap, $running);
264
265 return $newvolname;
266}
267
268sub clone_image {
f236eaf8 269 my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_;
4f914e6e 270
f236eaf8 271 $snap ||= '__base__';
4f914e6e
MR
272
273 my ($vtype, $basename, $basevmid, undef, undef, $isBase) =
274 $class->parse_volname($volname);
275
276 die "clone_image only works on base images\n" if !$isBase;
277
7730694e 278 my $name = $class->zfs_find_free_diskname($storeid, $scfg, $vmid);
4f914e6e
MR
279
280 warn "clone $volname: $basename to $name\n";
281
7730694e 282 $class->zfs_request($scfg, undef, 'clone', "$scfg->{pool}/$basename\@$snap", "$scfg->{pool}/$name");
4f914e6e 283
7730694e
DM
284 my $guid = $class->zfs_create_lu($scfg, $name);
285 $class->zfs_add_lun_mapping_entry($scfg, $name, $guid);
4f914e6e
MR
286
287 return $name;
288}
289
290sub alloc_image {
291 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
292
293 die "unsupported format '$fmt'" if $fmt ne 'raw';
294
295 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
3b219e80 296 if $name && $name !~ m/^vm-$vmid-/;
4f914e6e 297
7730694e 298 $name = $class->zfs_find_free_diskname($storeid, $scfg, $vmid) if !$name;
4f914e6e 299
7730694e
DM
300 $class->zfs_create_zvol($scfg, $name, $size);
301 my $guid = $class->zfs_create_lu($scfg, $name);
302 $class->zfs_add_lun_mapping_entry($scfg, $name, $guid);
4f914e6e
MR
303
304 return $name;
305}
306
307sub free_image {
308 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
309
310 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
311
7730694e 312 $class->zfs_delete_lu($scfg, $name);
4f914e6e 313 eval {
7730694e 314 $class->zfs_delete_zvol($scfg, $name);
4f914e6e
MR
315 };
316 do {
317 my $err = $@;
7730694e
DM
318 my $guid = $class->zfs_create_lu($scfg, $name);
319 $class->zfs_add_lun_mapping_entry($scfg, $name, $guid);
4f914e6e
MR
320 die $err;
321 } if $@;
322
323 return undef;
324}
325
326sub list_images {
327 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
328
7730694e 329 $cache->{zfs} = $class->zfs_list_zvol($scfg) if !$cache->{zfs};
4f914e6e
MR
330 my $zfspool = $scfg->{pool};
331 my $res = [];
332
333 if (my $dat = $cache->{zfs}->{$zfspool}) {
334
3b219e80 335 foreach my $image (keys %$dat) {
4f914e6e 336
3b219e80
MR
337 my $volname = $dat->{$image}->{name};
338 my $parent = $dat->{$image}->{parent};
4f914e6e 339
3b219e80 340 my $volid = undef;
4f914e6e 341 if ($parent && $parent =~ m/^(\S+)@(\S+)$/) {
3b219e80
MR
342 my ($basename) = ($1);
343 $volid = "$storeid:$basename/$volname";
344 } else {
345 $volid = "$storeid:$volname";
346 }
347
348 my $owner = $dat->{$volname}->{vmid};
349 if ($vollist) {
350 my $found = grep { $_ eq $volid } @$vollist;
351 next if !$found;
352 } else {
353 next if defined ($vmid) && ($owner ne $vmid);
354 }
355
356 my $info = $dat->{$volname};
357 $info->{volid} = $volid;
358 push @$res, $info;
359 }
4f914e6e
MR
360 }
361
362 return $res;
363}
364
365sub status {
366 my ($class, $storeid, $scfg, $cache) = @_;
367
368 my $total = 0;
369 my $free = 0;
370 my $used = 0;
371 my $active = 0;
372
373 eval {
7730694e 374 ($free, $used) = $class->zfs_get_pool_stats($scfg);
3b219e80
MR
375 $active = 1;
376 $total = $free + $used;
4f914e6e
MR
377 };
378 warn $@ if $@;
379
380 return ($total, $free, $used, $active);
381}
382
383sub activate_storage {
384 my ($class, $storeid, $scfg, $cache) = @_;
385 return 1;
386}
387
388sub deactivate_storage {
389 my ($class, $storeid, $scfg, $cache) = @_;
390 return 1;
391}
392
393sub activate_volume {
394 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
395 return 1;
396}
397
398sub deactivate_volume {
399 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
400 return 1;
401}
402
403sub volume_size_info {
404 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
405
7730694e 406 return $class->zfs_get_zvol_size($scfg, $volname);
4f914e6e
MR
407}
408
409sub volume_resize {
410 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
411
412 my $new_size = ($size/1024);
413
7730694e
DM
414 $class->zfs_request($scfg, undef, 'set', 'volsize=' . $new_size . 'k', "$scfg->{pool}/$volname");
415 $class->zfs_resize_lu($scfg, $volname, $new_size);
4f914e6e
MR
416}
417
418sub volume_snapshot {
419 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
420
7730694e 421 $class->zfs_request($scfg, undef, 'snapshot', "$scfg->{pool}/$volname\@$snap");
4f914e6e
MR
422}
423
424sub volume_snapshot_rollback {
425 my ($class, $scfg, $storeid, $volname, $snap) = @_;
426
a315b9ff
CA
427 # abort rollback if snapshot is not the latest
428 my @params = ('-t', 'snapshot', '-o', 'name', '-s', 'creation');
7730694e 429 my $text = $class->zfs_request($scfg, undef, 'list', @params);
a315b9ff
CA
430 my @snapshots = split(/\n/, $text);
431 my $recentsnap = undef;
432 foreach (@snapshots) {
433 if (/$scfg->{pool}\/$volname/) {
434 s/^.*@//;
435 $recentsnap = $_;
436 }
437 }
438 if ($snap ne $recentsnap) {
439 die "cannot rollback, more recent snapshots exist\n";
440 }
441
7730694e 442 $class->zfs_delete_lu($scfg, $volname);
4f914e6e 443
7730694e 444 $class->zfs_request($scfg, undef, 'rollback', "$scfg->{pool}/$volname\@$snap");
4f914e6e 445
7730694e 446 $class->zfs_import_lu($scfg, $volname);
4f914e6e 447
7730694e 448 $class->zfs_add_lun_mapping_entry($scfg, $volname);
4f914e6e
MR
449}
450
451sub volume_snapshot_delete {
452 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
453
7730694e 454 $class->zfs_request($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap");
4f914e6e
MR
455}
456
457sub volume_has_feature {
458 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
459
460 my $features = {
3b219e80
MR
461 snapshot => { current => 1, snap => 1},
462 clone => { base => 1},
463 template => { current => 1},
464 copy => { base => 1, current => 1},
4f914e6e
MR
465 };
466
467 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
3b219e80 468 $class->parse_volname($volname);
4f914e6e
MR
469
470 my $key = undef;
5332e6c9
DM
471
472 if ($snapname) {
3b219e80 473 $key = 'snap';
4f914e6e 474 } else {
3b219e80 475 $key = $isBase ? 'base' : 'current';
4f914e6e 476 }
5332e6c9 477
4f914e6e
MR
478 return 1 if $features->{$feature}->{$key};
479
480 return undef;
481}
482
4831;