]> git.proxmox.com Git - pve-storage.git/blame - PVE/Storage/ZFSDirPlugin.pm
zfsdir: implement free_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
7730694e
DM
187sub zfs_get_pool_stats {
188 my ($class, $scfg) = @_;
189
190 my $available = 0;
191 my $used = 0;
192
193 my $text = $class->zfs_request($scfg, undef, 'get', '-o', 'value', '-Hp',
194 'available,used', $scfg->{pool});
195
196 my @lines = split /\n/, $text;
197
198 if($lines[0] =~ /^(\d+)$/) {
199 $available = $1;
200 }
201
202 if($lines[1] =~ /^(\d+)$/) {
203 $used = $1;
204 }
205
206 return ($available, $used);
207}
208
209sub zfs_get_zvol_size {
210 my ($class, $scfg, $zvol) = @_;
211
212 my $text = $class->zfs_request($scfg, undef, 'get', '-Hp', 'volsize', "$scfg->{pool}/$zvol");
213
214 if ($text =~ /volsize\s(\d+)/) {
215 return $1;
216 }
217
218 die "Could not get zvol size";
219}
220
221sub zfs_create_zvol {
222 my ($class, $scfg, $zvol, $size) = @_;
223
224 my $cmd = ['create'];
225
226 push @$cmd, '-s' if $scfg->{sparse};
227
228 push @$cmd, '-b', $scfg->{blocksize} if $scfg->{blocksize};
229
230 push @$cmd, '-V', "${size}k", "$scfg->{pool}/$zvol";
231
232 $class->zfs_request($scfg, undef, @$cmd);
233}
234
235sub zfs_delete_zvol {
236 my ($class, $scfg, $zvol) = @_;
237
238 $class->zfs_request($scfg, undef, 'destroy', '-r', "$scfg->{pool}/$zvol");
239}
240
241sub zfs_list_zvol {
242 my ($class, $scfg) = @_;
243
244 my $text = $class->zfs_request($scfg, 10, 'list', '-o', 'name,volsize,origin', '-t', 'volume', '-Hr');
245 my $zvols = zfs_parse_zvol_list($text);
246 return undef if !$zvols;
247
248 my $list = ();
249 foreach my $zvol (@$zvols) {
250 my @values = split('/', $zvol->{name});
251
252 my $image = pop @values;
253 my $pool = join('/', @values);
254
255 next if $image !~ m/^((vm|base)-(\d+)-\S+)$/;
256 my $owner = $3;
257
258 my $parent = $zvol->{origin};
259 if($zvol->{origin} && $zvol->{origin} =~ m/^$scfg->{pool}\/(\S+)$/){
260 $parent = $1;
261 }
262
263 $list->{$pool}->{$image} = {
264 name => $image,
265 size => $zvol->{size},
266 parent => $parent,
267 format => 'raw',
268 vmid => $owner
269 };
270 }
271
272 return $list;
273}
274
275sub zfs_find_free_diskname {
276 my ($class, $storeid, $scfg, $vmid) = @_;
277
278 my $name = undef;
279 my $volumes = $class->zfs_list_zvol($scfg);
280
281 my $disk_ids = {};
282 my $dat = $volumes->{$scfg->{pool}};
283
284 foreach my $image (keys %$dat) {
285 my $volname = $dat->{$image}->{name};
286 if ($volname =~ m/(vm|base)-$vmid-disk-(\d+)/){
287 $disk_ids->{$2} = 1;
288 }
289 }
290
291 for (my $i = 1; $i < 100; $i++) {
292 if (!$disk_ids->{$i}) {
293 return "vm-$vmid-disk-$i";
294 }
295 }
296
297 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n";
298}
299
b5e5f7e3
DM
300sub status {
301 my ($class, $storeid, $scfg, $cache) = @_;
302
303 my $total = 0;
304 my $free = 0;
305 my $used = 0;
306 my $active = 0;
307
308 eval {
309 ($free, $used) = $class->zfs_get_pool_stats($scfg);
310 $active = 1;
311 $total = $free + $used;
312 };
313 warn $@ if $@;
314
315 return ($total, $free, $used, $active);
316}
317
318sub volume_size_info {
319 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
320
321 return $class->zfs_get_zvol_size($scfg, $volname);
322}
323
324sub volume_snapshot {
325 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
326
327 $class->zfs_request($scfg, undef, 'snapshot', "$scfg->{pool}/$volname\@$snap");
328}
329
330sub volume_snapshot_delete {
331 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
332
333 $class->zfs_request($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap");
334}
335
5bb8e010
DM
336
3371;