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