]> git.proxmox.com Git - pve-storage.git/blame - PVE/Storage/ZFSDirPlugin.pm
zfs: move methode list_image
[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
135sub zfs_request {
136 my ($class, $scfg, $timeout, $method, @params) = @_;
137
138 $timeout = 5 if !$timeout;
139
140 my $cmd = [];
141
142 if ($method eq 'zpool_list') {
143 push @$cmd = 'zpool', 'list';
144 } else {
145 push @$cmd, 'zfs', $method;
146 }
147
148 push @$cmd, @params;
149
150 my $msg = '';
151
152 my $output = sub {
153 my $line = shift;
154 $msg .= "$line\n";
155 };
156
157 run_command($cmd, outfunc => $output, timeout => $timeout);
158
159 return $msg;
160}
161
b3ba95e4
WL
162sub alloc_image {
163 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
164
165 die "unsupported format '$fmt'" if $fmt ne 'raw';
166
167 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
168 if $name && $name !~ m/^vm-$vmid-/;
169
170 $name = $class->zfs_find_free_diskname($storeid, $scfg, $vmid) if !$name;
171
172 $class->zfs_create_zvol($scfg, $name, $size);
173
174 return $name;
175}
176
e9565df5
WL
177sub free_image {
178 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
179
180 my (undef, $name, undef) = $class->parse_volname($volname);
181
182 $class->zfs_delete_zvol($scfg, $name);
183
184 return undef;
185}
186
ca04180f
WL
187sub list_images {
188 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
189
190 $cache->{zfs} = $class->zfs_list_zvol($scfg) if !$cache->{zfs};
191 my $zfspool = $scfg->{pool};
192 my $res = [];
193
194 if (my $dat = $cache->{zfs}->{$zfspool}) {
195
196 foreach my $image (keys %$dat) {
197
198 my $volname = $dat->{$image}->{name};
199 my $parent = $dat->{$image}->{parent};
200
201 my $volid = undef;
202 if ($parent && $parent =~ m/^(\S+)@(\S+)$/) {
203 my ($basename) = ($1);
204 $volid = "$storeid:$basename/$volname";
205 } else {
206 $volid = "$storeid:$volname";
207 }
208
209 my $owner = $dat->{$volname}->{vmid};
210 if ($vollist) {
211 my $found = grep { $_ eq $volid } @$vollist;
212 next if !$found;
213 } else {
214 next if defined ($vmid) && ($owner ne $vmid);
215 }
216
217 my $info = $dat->{$volname};
218 $info->{volid} = $volid;
219 push @$res, $info;
220 }
221 }
222
223 return $res;
224}
225
7730694e
DM
226sub zfs_get_pool_stats {
227 my ($class, $scfg) = @_;
228
229 my $available = 0;
230 my $used = 0;
231
232 my $text = $class->zfs_request($scfg, undef, 'get', '-o', 'value', '-Hp',
233 'available,used', $scfg->{pool});
234
235 my @lines = split /\n/, $text;
236
237 if($lines[0] =~ /^(\d+)$/) {
238 $available = $1;
239 }
240
241 if($lines[1] =~ /^(\d+)$/) {
242 $used = $1;
243 }
244
245 return ($available, $used);
246}
247
248sub zfs_get_zvol_size {
249 my ($class, $scfg, $zvol) = @_;
250
251 my $text = $class->zfs_request($scfg, undef, 'get', '-Hp', 'volsize', "$scfg->{pool}/$zvol");
252
253 if ($text =~ /volsize\s(\d+)/) {
254 return $1;
255 }
256
257 die "Could not get zvol size";
258}
259
260sub zfs_create_zvol {
261 my ($class, $scfg, $zvol, $size) = @_;
262
263 my $cmd = ['create'];
264
265 push @$cmd, '-s' if $scfg->{sparse};
266
267 push @$cmd, '-b', $scfg->{blocksize} if $scfg->{blocksize};
268
269 push @$cmd, '-V', "${size}k", "$scfg->{pool}/$zvol";
270
271 $class->zfs_request($scfg, undef, @$cmd);
272}
273
274sub zfs_delete_zvol {
275 my ($class, $scfg, $zvol) = @_;
276
277 $class->zfs_request($scfg, undef, 'destroy', '-r', "$scfg->{pool}/$zvol");
278}
279
280sub zfs_list_zvol {
281 my ($class, $scfg) = @_;
282
283 my $text = $class->zfs_request($scfg, 10, 'list', '-o', 'name,volsize,origin', '-t', 'volume', '-Hr');
284 my $zvols = zfs_parse_zvol_list($text);
285 return undef if !$zvols;
286
287 my $list = ();
288 foreach my $zvol (@$zvols) {
289 my @values = split('/', $zvol->{name});
290
291 my $image = pop @values;
292 my $pool = join('/', @values);
293
294 next if $image !~ m/^((vm|base)-(\d+)-\S+)$/;
295 my $owner = $3;
296
297 my $parent = $zvol->{origin};
298 if($zvol->{origin} && $zvol->{origin} =~ m/^$scfg->{pool}\/(\S+)$/){
299 $parent = $1;
300 }
301
302 $list->{$pool}->{$image} = {
303 name => $image,
304 size => $zvol->{size},
305 parent => $parent,
306 format => 'raw',
307 vmid => $owner
308 };
309 }
310
311 return $list;
312}
313
314sub zfs_find_free_diskname {
315 my ($class, $storeid, $scfg, $vmid) = @_;
316
317 my $name = undef;
318 my $volumes = $class->zfs_list_zvol($scfg);
319
320 my $disk_ids = {};
321 my $dat = $volumes->{$scfg->{pool}};
322
323 foreach my $image (keys %$dat) {
324 my $volname = $dat->{$image}->{name};
325 if ($volname =~ m/(vm|base)-$vmid-disk-(\d+)/){
326 $disk_ids->{$2} = 1;
327 }
328 }
329
330 for (my $i = 1; $i < 100; $i++) {
331 if (!$disk_ids->{$i}) {
332 return "vm-$vmid-disk-$i";
333 }
334 }
335
336 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n";
337}
338
b5e5f7e3
DM
339sub status {
340 my ($class, $storeid, $scfg, $cache) = @_;
341
342 my $total = 0;
343 my $free = 0;
344 my $used = 0;
345 my $active = 0;
346
347 eval {
348 ($free, $used) = $class->zfs_get_pool_stats($scfg);
349 $active = 1;
350 $total = $free + $used;
351 };
352 warn $@ if $@;
353
354 return ($total, $free, $used, $active);
355}
356
357sub volume_size_info {
358 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
359
360 return $class->zfs_get_zvol_size($scfg, $volname);
361}
362
363sub volume_snapshot {
364 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
365
366 $class->zfs_request($scfg, undef, 'snapshot', "$scfg->{pool}/$volname\@$snap");
367}
368
369sub volume_snapshot_delete {
370 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
371
372 $class->zfs_request($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap");
373}
374
5bb8e010
DM
375
3761;