]> git.proxmox.com Git - pve-storage.git/blob - PVE/Storage/ZFSPlugin.pm
PVE::Storage::storage_can_replicate - hew helper
[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 };
209 }
210
211 # Storage implementation
212
213 sub path {
214 my ($class, $scfg, $volname, $storeid, $snapname) = @_;
215
216 die "direct access to snapshots not implemented"
217 if defined($snapname);
218
219 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
220
221 my $target = $scfg->{target};
222 my $portal = $scfg->{portal};
223
224 my $guid = $class->zfs_get_lu_name($scfg, $name);
225 my $lun = $class->zfs_get_lun_number($scfg, $guid);
226
227 my $path = "iscsi://$portal/$target/$lun";
228
229 return ($path, $vmid, $vtype);
230 }
231
232 sub create_base {
233 my ($class, $storeid, $scfg, $volname) = @_;
234
235 my $snap = '__base__';
236
237 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
238 $class->parse_volname($volname);
239
240 die "create_base not possible with base image\n" if $isBase;
241
242 my $newname = $name;
243 $newname =~ s/^vm-/base-/;
244
245 my $newvolname = $basename ? "$basename/$newname" : "$newname";
246
247 $class->zfs_delete_lu($scfg, $name);
248 $class->zfs_request($scfg, undef, 'rename', "$scfg->{pool}/$name", "$scfg->{pool}/$newname");
249
250 my $guid = $class->zfs_create_lu($scfg, $newname);
251 $class->zfs_add_lun_mapping_entry($scfg, $newname, $guid);
252
253 my $running = undef; #fixme : is create_base always offline ?
254
255 $class->volume_snapshot($scfg, $storeid, $newname, $snap, $running);
256
257 return $newvolname;
258 }
259
260 sub clone_image {
261 my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_;
262
263 my $name = $class->SUPER::clone_image($scfg, $storeid, $volname, $vmid, $snap);
264
265 # get ZFS dataset name from PVE volname
266 my (undef, $clonedname) = $class->parse_volname($name);
267
268 my $guid = $class->zfs_create_lu($scfg, $clonedname);
269 $class->zfs_add_lun_mapping_entry($scfg, $clonedname, $guid);
270
271 return $name;
272 }
273
274 sub alloc_image {
275 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
276
277 die "unsupported format '$fmt'" if $fmt ne 'raw';
278
279 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
280 if $name && $name !~ m/^vm-$vmid-/;
281
282 my $volname = $name;
283
284 $volname = $class->zfs_find_free_diskname($storeid, $scfg, $vmid, $fmt) if !$volname;
285
286 $class->zfs_create_zvol($scfg, $volname, $size);
287
288 my $guid = $class->zfs_create_lu($scfg, $volname);
289 $class->zfs_add_lun_mapping_entry($scfg, $volname, $guid);
290
291 return $volname;
292 }
293
294 sub free_image {
295 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
296
297 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
298
299 $class->zfs_delete_lu($scfg, $name);
300
301 eval { $class->zfs_delete_zvol($scfg, $name); };
302 if (my $err = $@) {
303 my $guid = $class->zfs_create_lu($scfg, $name);
304 $class->zfs_add_lun_mapping_entry($scfg, $name, $guid);
305 die $err;
306 }
307
308 return undef;
309 }
310
311 sub volume_resize {
312 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
313
314 my $new_size = $class->SUPER::volume_resize($scfg, $storeid, $volname, $size, $running);
315
316 $class->zfs_resize_lu($scfg, $volname, $new_size);
317
318 return $new_size;
319 }
320
321 sub volume_snapshot_delete {
322 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
323
324 $class->zfs_request($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap");
325 }
326
327 sub volume_snapshot_rollback {
328 my ($class, $scfg, $storeid, $volname, $snap) = @_;
329
330 $class->zfs_delete_lu($scfg, $volname);
331
332 $class->zfs_request($scfg, undef, 'rollback', "$scfg->{pool}/$volname\@$snap");
333
334 $class->zfs_import_lu($scfg, $volname);
335
336 $class->zfs_add_lun_mapping_entry($scfg, $volname);
337 }
338
339 sub storage_can_replicate {
340 my ($class, $scfg, $storeid, $format) = @_;
341
342 return 0;
343 }
344
345 sub volume_has_feature {
346 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
347
348 my $features = {
349 snapshot => { current => 1, snap => 1},
350 clone => { base => 1},
351 template => { current => 1},
352 copy => { base => 1, current => 1},
353 };
354
355 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
356 $class->parse_volname($volname);
357
358 my $key = undef;
359
360 if ($snapname) {
361 $key = 'snap';
362 } else {
363 $key = $isBase ? 'base' : 'current';
364 }
365
366 return 1 if $features->{$feature}->{$key};
367
368 return undef;
369 }
370
371 sub volume_snapshot_list {
372 my ($class, $scfg, $storeid, $volname) = @_;
373 # return an empty array if dataset does not exist.
374 die "Volume_snapshot_list is not implemented for ZFS over iSCSI.\n";
375 }
376
377 sub activate_storage {
378 my ($class, $storeid, $scfg, $cache) = @_;
379
380 return 1;
381 }
382
383 sub deactivate_storage {
384 my ($class, $storeid, $scfg, $cache) = @_;
385
386 return 1;
387 }
388
389 sub activate_volume {
390 my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
391
392 die "unable to activate snapshot from remote zfs storage" if $snapname;
393
394 return 1;
395 }
396
397 sub deactivate_volume {
398 my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
399
400 die "unable to deactivate snapshot from remote zfs storage" if $snapname;
401
402 return 1;
403 }
404
405 1;