]> git.proxmox.com Git - pve-storage.git/blob - PVE/Storage/ZFSPlugin.pm
bump version to 8.2.1
[pve-storage.git] / PVE / Storage / ZFSPlugin.pm
1 package PVE::Storage::ZFSPlugin;
2
3 use strict;
4 use warnings;
5 use IO::File;
6 use POSIX;
7 use PVE::Tools qw(run_command);
8 use PVE::Storage::ZFSPoolPlugin;
9 use PVE::RPCEnvironment;
10
11 use base qw(PVE::Storage::ZFSPoolPlugin);
12 use PVE::Storage::LunCmd::Comstar;
13 use PVE::Storage::LunCmd::Istgt;
14 use PVE::Storage::LunCmd::Iet;
15 use PVE::Storage::LunCmd::LIO;
16
17
18 my @ssh_opts = ('-o', 'BatchMode=yes');
19 my @ssh_cmd = ('/usr/bin/ssh', @ssh_opts);
20 my $id_rsa_path = '/etc/pve/priv/zfs';
21
22 my $lun_cmds = {
23 create_lu => 1,
24 delete_lu => 1,
25 import_lu => 1,
26 modify_lu => 1,
27 add_view => 1,
28 list_view => 1,
29 list_lu => 1,
30 };
31
32 my $zfs_unknown_scsi_provider = sub {
33 my ($provider) = @_;
34
35 die "$provider: unknown iscsi provider. Available [comstar, istgt, iet, LIO]";
36 };
37
38 my $zfs_get_base = sub {
39 my ($scfg) = @_;
40
41 if ($scfg->{iscsiprovider} eq 'comstar') {
42 return PVE::Storage::LunCmd::Comstar::get_base;
43 } elsif ($scfg->{iscsiprovider} eq 'istgt') {
44 return PVE::Storage::LunCmd::Istgt::get_base;
45 } elsif ($scfg->{iscsiprovider} eq 'iet') {
46 return PVE::Storage::LunCmd::Iet::get_base;
47 } elsif ($scfg->{iscsiprovider} eq 'LIO') {
48 return PVE::Storage::LunCmd::LIO::get_base;
49 } else {
50 $zfs_unknown_scsi_provider->($scfg->{iscsiprovider});
51 }
52 };
53
54 sub zfs_request {
55 my ($class, $scfg, $timeout, $method, @params) = @_;
56
57 $timeout = PVE::RPCEnvironment->is_worker() ? 60*60 : 10
58 if !$timeout;
59
60 my $msg = '';
61
62 if ($lun_cmds->{$method}) {
63 if ($scfg->{iscsiprovider} eq 'comstar') {
64 $msg = PVE::Storage::LunCmd::Comstar::run_lun_command($scfg, $timeout, $method, @params);
65 } elsif ($scfg->{iscsiprovider} eq 'istgt') {
66 $msg = PVE::Storage::LunCmd::Istgt::run_lun_command($scfg, $timeout, $method, @params);
67 } elsif ($scfg->{iscsiprovider} eq 'iet') {
68 $msg = PVE::Storage::LunCmd::Iet::run_lun_command($scfg, $timeout, $method, @params);
69 } elsif ($scfg->{iscsiprovider} eq 'LIO') {
70 $msg = PVE::Storage::LunCmd::LIO::run_lun_command($scfg, $timeout, $method, @params);
71 } else {
72 $zfs_unknown_scsi_provider->($scfg->{iscsiprovider});
73 }
74 } else {
75
76 my $target = 'root@' . $scfg->{portal};
77
78 my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target];
79
80 if ($method eq 'zpool_list') {
81 push @$cmd, 'zpool', 'list';
82 } else {
83 push @$cmd, 'zfs', $method;
84 }
85
86 push @$cmd, @params;
87
88 my $output = sub {
89 my $line = shift;
90 $msg .= "$line\n";
91 };
92
93 run_command($cmd, outfunc => $output, timeout => $timeout);
94 }
95
96 return $msg;
97 }
98
99 sub zfs_get_lu_name {
100 my ($class, $scfg, $zvol) = @_;
101
102 my $base = $zfs_get_base->($scfg);
103
104 $zvol = ($class->parse_volname($zvol))[1];
105
106 my $object = ($zvol =~ /^.+\/.+/) ? "$base/$zvol" : "$base/$scfg->{pool}/$zvol";
107
108 my $lu_name = $class->zfs_request($scfg, undef, 'list_lu', $object);
109
110 return $lu_name if $lu_name;
111
112 die "Could not find lu_name for zvol $zvol";
113 }
114
115 sub zfs_add_lun_mapping_entry {
116 my ($class, $scfg, $zvol, $guid) = @_;
117
118 if (!defined($guid)) {
119 $guid = $class->zfs_get_lu_name($scfg, $zvol);
120 }
121
122 $class->zfs_request($scfg, undef, 'add_view', $guid);
123 }
124
125 sub zfs_delete_lu {
126 my ($class, $scfg, $zvol) = @_;
127
128 my $guid = $class->zfs_get_lu_name($scfg, $zvol);
129
130 $class->zfs_request($scfg, undef, 'delete_lu', $guid);
131 }
132
133 sub zfs_create_lu {
134 my ($class, $scfg, $zvol) = @_;
135
136 my $base = $zfs_get_base->($scfg);
137 my $guid = $class->zfs_request($scfg, undef, 'create_lu', "$base/$scfg->{pool}/$zvol");
138
139 return $guid;
140 }
141
142 sub zfs_import_lu {
143 my ($class, $scfg, $zvol) = @_;
144
145 my $base = $zfs_get_base->($scfg);
146 $class->zfs_request($scfg, undef, 'import_lu', "$base/$scfg->{pool}/$zvol");
147 }
148
149 sub zfs_resize_lu {
150 my ($class, $scfg, $zvol, $size) = @_;
151
152 my $guid = $class->zfs_get_lu_name($scfg, $zvol);
153
154 $class->zfs_request($scfg, undef, 'modify_lu', "${size}K", $guid);
155 }
156
157 sub zfs_get_lun_number {
158 my ($class, $scfg, $guid) = @_;
159
160 die "could not find lun_number for guid $guid" if !$guid;
161
162 if ($class->zfs_request($scfg, undef, 'list_view', $guid) =~ /^(\d+)$/) {
163 return $1;
164 }
165
166 die "lun_number for guid $guid is not a number";
167 }
168
169 # Configuration
170
171 sub type {
172 return 'zfs';
173 }
174
175 sub plugindata {
176 return {
177 content => [ {images => 1}, { images => 1 }],
178 };
179 }
180
181 sub properties {
182 return {
183 iscsiprovider => {
184 description => "iscsi provider",
185 type => 'string',
186 },
187 # this will disable write caching on comstar and istgt.
188 # it is not implemented for iet. iet blockio always operates with
189 # writethrough caching when not in readonly mode
190 nowritecache => {
191 description => "disable write caching on the target",
192 type => 'boolean',
193 },
194 comstar_tg => {
195 description => "target group for comstar views",
196 type => 'string',
197 },
198 comstar_hg => {
199 description => "host group for comstar views",
200 type => 'string',
201 },
202 lio_tpg => {
203 description => "target portal group for Linux LIO targets",
204 type => 'string',
205 },
206 };
207 }
208
209 sub options {
210 return {
211 nodes => { optional => 1 },
212 disable => { optional => 1 },
213 portal => { fixed => 1 },
214 target => { fixed => 1 },
215 pool => { fixed => 1 },
216 blocksize => { fixed => 1 },
217 iscsiprovider => { fixed => 1 },
218 nowritecache => { optional => 1 },
219 sparse => { optional => 1 },
220 comstar_hg => { optional => 1 },
221 comstar_tg => { optional => 1 },
222 lio_tpg => { optional => 1 },
223 content => { optional => 1 },
224 bwlimit => { optional => 1 },
225 };
226 }
227
228 # Storage implementation
229
230 sub path {
231 my ($class, $scfg, $volname, $storeid, $snapname) = @_;
232
233 die "direct access to snapshots not implemented"
234 if defined($snapname);
235
236 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
237
238 my $target = $scfg->{target};
239 my $portal = $scfg->{portal};
240
241 my $guid = $class->zfs_get_lu_name($scfg, $name);
242 my $lun = $class->zfs_get_lun_number($scfg, $guid);
243
244 my $path = "iscsi://$portal/$target/$lun";
245
246 return ($path, $vmid, $vtype);
247 }
248
249 sub create_base {
250 my ($class, $storeid, $scfg, $volname) = @_;
251
252 my $snap = '__base__';
253
254 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
255 $class->parse_volname($volname);
256
257 die "create_base not possible with base image\n" if $isBase;
258
259 my $newname = $name;
260 $newname =~ s/^vm-/base-/;
261
262 my $newvolname = $basename ? "$basename/$newname" : "$newname";
263
264 $class->zfs_delete_lu($scfg, $name);
265 $class->zfs_request($scfg, undef, 'rename', "$scfg->{pool}/$name", "$scfg->{pool}/$newname");
266
267 my $guid = $class->zfs_create_lu($scfg, $newname);
268 $class->zfs_add_lun_mapping_entry($scfg, $newname, $guid);
269
270 my $running = undef; #fixme : is create_base always offline ?
271
272 $class->volume_snapshot($scfg, $storeid, $newname, $snap, $running);
273
274 return $newvolname;
275 }
276
277 sub clone_image {
278 my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_;
279
280 my $name = $class->SUPER::clone_image($scfg, $storeid, $volname, $vmid, $snap);
281
282 # get ZFS dataset name from PVE volname
283 my (undef, $clonedname) = $class->parse_volname($name);
284
285 my $guid = $class->zfs_create_lu($scfg, $clonedname);
286 $class->zfs_add_lun_mapping_entry($scfg, $clonedname, $guid);
287
288 return $name;
289 }
290
291 sub alloc_image {
292 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
293
294 die "unsupported format '$fmt'" if $fmt ne 'raw';
295
296 die "illegal name '$name' - should be 'vm-$vmid-*'\n"
297 if $name && $name !~ m/^vm-$vmid-/;
298
299 my $volname = $name;
300
301 $volname = $class->find_free_diskname($storeid, $scfg, $vmid, $fmt) if !$volname;
302
303 $class->zfs_create_zvol($scfg, $volname, $size);
304
305 my $guid = $class->zfs_create_lu($scfg, $volname);
306 $class->zfs_add_lun_mapping_entry($scfg, $volname, $guid);
307
308 return $volname;
309 }
310
311 sub free_image {
312 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
313
314 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
315
316 $class->zfs_delete_lu($scfg, $name);
317
318 eval { $class->zfs_delete_zvol($scfg, $name); };
319 if (my $err = $@) {
320 my $guid = $class->zfs_create_lu($scfg, $name);
321 $class->zfs_add_lun_mapping_entry($scfg, $name, $guid);
322 die $err;
323 }
324
325 return undef;
326 }
327
328 sub volume_resize {
329 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
330
331 $volname = ($class->parse_volname($volname))[1];
332
333 my $new_size = $class->SUPER::volume_resize($scfg, $storeid, $volname, $size, $running);
334
335 $class->zfs_resize_lu($scfg, $volname, $new_size);
336
337 return $new_size;
338 }
339
340 sub volume_snapshot_delete {
341 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
342
343 $volname = ($class->parse_volname($volname))[1];
344
345 $class->zfs_request($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap");
346 }
347
348 sub volume_snapshot_rollback {
349 my ($class, $scfg, $storeid, $volname, $snap) = @_;
350
351 $volname = ($class->parse_volname($volname))[1];
352
353 $class->zfs_delete_lu($scfg, $volname);
354
355 $class->zfs_request($scfg, undef, 'rollback', "$scfg->{pool}/$volname\@$snap");
356
357 $class->zfs_import_lu($scfg, $volname);
358
359 $class->zfs_add_lun_mapping_entry($scfg, $volname);
360 }
361
362 sub storage_can_replicate {
363 my ($class, $scfg, $storeid, $format) = @_;
364
365 return 0;
366 }
367
368 sub volume_has_feature {
369 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
370
371 my $features = {
372 snapshot => { current => 1, snap => 1},
373 clone => { base => 1},
374 template => { current => 1},
375 copy => { base => 1, current => 1},
376 };
377
378 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
379 $class->parse_volname($volname);
380
381 my $key = undef;
382
383 if ($snapname) {
384 $key = 'snap';
385 } else {
386 $key = $isBase ? 'base' : 'current';
387 }
388
389 return 1 if $features->{$feature}->{$key};
390
391 return undef;
392 }
393
394 sub volume_snapshot_list {
395 my ($class, $scfg, $storeid, $volname) = @_;
396 # return an empty array if dataset does not exist.
397 die "Volume_snapshot_list is not implemented for ZFS over iSCSI.\n";
398 }
399
400 sub activate_storage {
401 my ($class, $storeid, $scfg, $cache) = @_;
402
403 return 1;
404 }
405
406 sub deactivate_storage {
407 my ($class, $storeid, $scfg, $cache) = @_;
408
409 return 1;
410 }
411
412 sub activate_volume {
413 my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
414
415 die "unable to activate snapshot from remote zfs storage" if $snapname;
416
417 return 1;
418 }
419
420 sub deactivate_volume {
421 my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
422
423 die "unable to deactivate snapshot from remote zfs storage" if $snapname;
424
425 return 1;
426 }
427
428 1;