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