]> git.proxmox.com Git - pve-storage.git/blame - PVE/Storage/ZFSDirPlugin.pm
zfs: ZFSDirPlugin add methode path
[pve-storage.git] / PVE / Storage / ZFSDirPlugin.pm
CommitLineData
5bb8e010
DM
1package PVE::Storage::ZFSDirPlugin;
2
3use strict;
4use warnings;
5use IO::File;
6use POSIX;
7use PVE::Tools qw(run_command);
8use PVE::Storage::Plugin;
9
10
11use base qw(PVE::Storage::Plugin);
12
5bb8e010
DM
13sub type {
14 return 'zfsdir';
15}
16
17sub plugindata {
18 return {
19 content => [ { images => 1, rootdir => 1, vztmpl => 1, iso => 1, backup => 1},
20 { images => 1 }],
21 };
22}
23
7730694e
DM
24sub properties {
25 return {
26 blocksize => {
27 description => "block size",
28 type => 'string',
29 },
30 sparse => {
31 description => "use sparse volumes",
32 type => 'boolean',
33 },
34 };
35}
36
5bb8e010
DM
37sub options {
38 return {
39 path => { fixed => 1 },
7730694e
DM
40 pool => { fixed => 1 },
41 blocksize => { optional => 1 },
42 sparse => { optional => 1 },
43 nodes => { optional => 1 },
5bb8e010
DM
44 disable => { optional => 1 },
45 maxfiles => { optional => 1 },
46 content => { optional => 1 },
47 };
48}
49
7730694e
DM
50# static zfs helper methods
51
060ef890
DM
52sub zfs_parse_size {
53 my ($text) = @_;
54
55 return 0 if !$text;
56
57 if ($text =~ m/^(\d+(\.\d+)?)([TGMK])?$/) {
58
59 my ($size, $reminder, $unit) = ($1, $2, $3);
60
61 if ($unit) {
62 if ($unit eq 'K') {
63 $size *= 1024;
64 } elsif ($unit eq 'M') {
65 $size *= 1024*1024;
66 } elsif ($unit eq 'G') {
67 $size *= 1024*1024*1024;
68 } elsif ($unit eq 'T') {
69 $size *= 1024*1024*1024*1024;
70 } else {
71 die "got unknown zfs size unit '$unit'\n";
72 }
73 }
74
75 if ($reminder) {
76 $size = ceil($size);
77 }
78
79 return $size;
80
81 }
82
83 warn "unable to parse zfs size '$text'\n";
84
85 return 0;
86}
87
7730694e
DM
88sub zfs_parse_zvol_list {
89 my ($text) = @_;
90
91 my $list = ();
92
93 return $list if !$text;
94
95 my @lines = split /\n/, $text;
96 foreach my $line (@lines) {
97 if ($line =~ /^(.+)\s+([a-zA-Z0-9\.]+|\-)\s+(.+)$/) {
98 my $zvol = {};
99 my @parts = split /\//, $1;
100 my $name = pop @parts;
101 my $pool = join('/', @parts);
102
103 if ($pool !~ /^rpool$/) {
104 next unless $name =~ m!^(\w+)-(\d+)-(\w+)-(\d+)$!;
105 $name = $pool . '/' . $name;
106 } else {
107 next;
108 }
109
110 $zvol->{pool} = $pool;
111 $zvol->{name} = $name;
112 $zvol->{size} = zfs_parse_size($2);
113 if ($3 !~ /^-$/) {
114 $zvol->{origin} = $3;
115 }
116 push @$list, $zvol;
117 }
118 }
119
120 return $list;
121}
122
cc80ed9c
WL
123sub parse_volname {
124 my ($class, $volname) = @_;
125
126 if ($volname =~ m/^(((base|vm)-(\d+)-\S+)\/)?((base)?(vm)?-(\d+)-\S+)$/) {
127 return ('images', $5, $8, $2, $4, $6);
128 }
129
130 die "unable to parse zfs volume name '$volname'\n";
131}
132
7730694e
DM
133# virtual zfs methods (subclass can overwrite them)
134
f3e632d0
WL
135sub path {
136 my ($class, $scfg, $volname) = @_;
137
138 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
139
140 my $path = '';
141
142 if($vtype eq "images"){
143 $path = "/dev/zvol/$scfg->{pool}/$volname";
144 } else {
145 die "$vtype is not allowed in ZFSDir!";
146 }
147
148 return ($path, $vmid, $vtype);
149}
150
7730694e
DM
151sub zfs_request {
152 my ($class, $scfg, $timeout, $method, @params) = @_;
153
154 $timeout = 5 if !$timeout;
155
156 my $cmd = [];
157
158 if ($method eq 'zpool_list') {
159 push @$cmd = 'zpool', 'list';
160 } else {
161 push @$cmd, 'zfs', $method;
162 }
163
164 push @$cmd, @params;
165
166 my $msg = '';
167
168 my $output = sub {
169 my $line = shift;
170 $msg .= "$line\n";
171 };
172
173 run_command($cmd, outfunc => $output, timeout => $timeout);
174
175 return $msg;
176}
177
b3ba95e4
WL
178sub alloc_image {
179 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
180
181 die "unsupported format '$fmt'" if $fmt ne 'raw';
182
183 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
184 if $name && $name !~ m/^vm-$vmid-/;
185
186 $name = $class->zfs_find_free_diskname($storeid, $scfg, $vmid) if !$name;
187
188 $class->zfs_create_zvol($scfg, $name, $size);
189
190 return $name;
191}
192
e9565df5
WL
193sub free_image {
194 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
195
196 my (undef, $name, undef) = $class->parse_volname($volname);
197
198 $class->zfs_delete_zvol($scfg, $name);
199
200 return undef;
201}
202
ca04180f
WL
203sub list_images {
204 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
205
206 $cache->{zfs} = $class->zfs_list_zvol($scfg) if !$cache->{zfs};
207 my $zfspool = $scfg->{pool};
208 my $res = [];
209
210 if (my $dat = $cache->{zfs}->{$zfspool}) {
211
212 foreach my $image (keys %$dat) {
213
214 my $volname = $dat->{$image}->{name};
215 my $parent = $dat->{$image}->{parent};
216
217 my $volid = undef;
218 if ($parent && $parent =~ m/^(\S+)@(\S+)$/) {
219 my ($basename) = ($1);
220 $volid = "$storeid:$basename/$volname";
221 } else {
222 $volid = "$storeid:$volname";
223 }
224
225 my $owner = $dat->{$volname}->{vmid};
226 if ($vollist) {
227 my $found = grep { $_ eq $volid } @$vollist;
228 next if !$found;
229 } else {
230 next if defined ($vmid) && ($owner ne $vmid);
231 }
232
233 my $info = $dat->{$volname};
234 $info->{volid} = $volid;
235 push @$res, $info;
236 }
237 }
238
239 return $res;
240}
241
7730694e
DM
242sub zfs_get_pool_stats {
243 my ($class, $scfg) = @_;
244
245 my $available = 0;
246 my $used = 0;
247
248 my $text = $class->zfs_request($scfg, undef, 'get', '-o', 'value', '-Hp',
249 'available,used', $scfg->{pool});
250
251 my @lines = split /\n/, $text;
252
253 if($lines[0] =~ /^(\d+)$/) {
254 $available = $1;
255 }
256
257 if($lines[1] =~ /^(\d+)$/) {
258 $used = $1;
259 }
260
261 return ($available, $used);
262}
263
264sub zfs_get_zvol_size {
265 my ($class, $scfg, $zvol) = @_;
266
267 my $text = $class->zfs_request($scfg, undef, 'get', '-Hp', 'volsize', "$scfg->{pool}/$zvol");
268
269 if ($text =~ /volsize\s(\d+)/) {
270 return $1;
271 }
272
273 die "Could not get zvol size";
274}
275
276sub zfs_create_zvol {
277 my ($class, $scfg, $zvol, $size) = @_;
278
279 my $cmd = ['create'];
280
281 push @$cmd, '-s' if $scfg->{sparse};
282
283 push @$cmd, '-b', $scfg->{blocksize} if $scfg->{blocksize};
284
285 push @$cmd, '-V', "${size}k", "$scfg->{pool}/$zvol";
286
287 $class->zfs_request($scfg, undef, @$cmd);
288}
289
290sub zfs_delete_zvol {
291 my ($class, $scfg, $zvol) = @_;
292
293 $class->zfs_request($scfg, undef, 'destroy', '-r', "$scfg->{pool}/$zvol");
294}
295
296sub zfs_list_zvol {
297 my ($class, $scfg) = @_;
298
299 my $text = $class->zfs_request($scfg, 10, 'list', '-o', 'name,volsize,origin', '-t', 'volume', '-Hr');
300 my $zvols = zfs_parse_zvol_list($text);
301 return undef if !$zvols;
302
303 my $list = ();
304 foreach my $zvol (@$zvols) {
305 my @values = split('/', $zvol->{name});
306
307 my $image = pop @values;
308 my $pool = join('/', @values);
309
310 next if $image !~ m/^((vm|base)-(\d+)-\S+)$/;
311 my $owner = $3;
312
313 my $parent = $zvol->{origin};
314 if($zvol->{origin} && $zvol->{origin} =~ m/^$scfg->{pool}\/(\S+)$/){
315 $parent = $1;
316 }
317
318 $list->{$pool}->{$image} = {
319 name => $image,
320 size => $zvol->{size},
321 parent => $parent,
322 format => 'raw',
323 vmid => $owner
324 };
325 }
326
327 return $list;
328}
329
330sub zfs_find_free_diskname {
331 my ($class, $storeid, $scfg, $vmid) = @_;
332
333 my $name = undef;
334 my $volumes = $class->zfs_list_zvol($scfg);
335
336 my $disk_ids = {};
337 my $dat = $volumes->{$scfg->{pool}};
338
339 foreach my $image (keys %$dat) {
340 my $volname = $dat->{$image}->{name};
341 if ($volname =~ m/(vm|base)-$vmid-disk-(\d+)/){
342 $disk_ids->{$2} = 1;
343 }
344 }
345
346 for (my $i = 1; $i < 100; $i++) {
347 if (!$disk_ids->{$i}) {
348 return "vm-$vmid-disk-$i";
349 }
350 }
351
352 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n";
353}
354
b5e5f7e3
DM
355sub status {
356 my ($class, $storeid, $scfg, $cache) = @_;
357
358 my $total = 0;
359 my $free = 0;
360 my $used = 0;
361 my $active = 0;
362
363 eval {
364 ($free, $used) = $class->zfs_get_pool_stats($scfg);
365 $active = 1;
366 $total = $free + $used;
367 };
368 warn $@ if $@;
369
370 return ($total, $free, $used, $active);
371}
372
373sub volume_size_info {
374 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
375
376 return $class->zfs_get_zvol_size($scfg, $volname);
377}
378
379sub volume_snapshot {
380 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
381
382 $class->zfs_request($scfg, undef, 'snapshot', "$scfg->{pool}/$volname\@$snap");
383}
384
385sub volume_snapshot_delete {
386 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
387
388 $class->zfs_request($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap");
389}
390
5bb8e010
DM
391
3921;