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