]> git.proxmox.com Git - pve-storage.git/blame - PVE/Storage/ZFSDirPlugin.pm
zfs: move some code
[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
123# virtual zfs methods (subclass can overwrite them)
124
125sub zfs_request {
126 my ($class, $scfg, $timeout, $method, @params) = @_;
127
128 $timeout = 5 if !$timeout;
129
130 my $cmd = [];
131
132 if ($method eq 'zpool_list') {
133 push @$cmd = 'zpool', 'list';
134 } else {
135 push @$cmd, 'zfs', $method;
136 }
137
138 push @$cmd, @params;
139
140 my $msg = '';
141
142 my $output = sub {
143 my $line = shift;
144 $msg .= "$line\n";
145 };
146
147 run_command($cmd, outfunc => $output, timeout => $timeout);
148
149 return $msg;
150}
151
b3ba95e4
WL
152sub alloc_image {
153 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
154
155 die "unsupported format '$fmt'" if $fmt ne 'raw';
156
157 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
158 if $name && $name !~ m/^vm-$vmid-/;
159
160 $name = $class->zfs_find_free_diskname($storeid, $scfg, $vmid) if !$name;
161
162 $class->zfs_create_zvol($scfg, $name, $size);
163
164 return $name;
165}
166
7730694e
DM
167sub zfs_get_pool_stats {
168 my ($class, $scfg) = @_;
169
170 my $available = 0;
171 my $used = 0;
172
173 my $text = $class->zfs_request($scfg, undef, 'get', '-o', 'value', '-Hp',
174 'available,used', $scfg->{pool});
175
176 my @lines = split /\n/, $text;
177
178 if($lines[0] =~ /^(\d+)$/) {
179 $available = $1;
180 }
181
182 if($lines[1] =~ /^(\d+)$/) {
183 $used = $1;
184 }
185
186 return ($available, $used);
187}
188
189sub zfs_get_zvol_size {
190 my ($class, $scfg, $zvol) = @_;
191
192 my $text = $class->zfs_request($scfg, undef, 'get', '-Hp', 'volsize', "$scfg->{pool}/$zvol");
193
194 if ($text =~ /volsize\s(\d+)/) {
195 return $1;
196 }
197
198 die "Could not get zvol size";
199}
200
201sub zfs_create_zvol {
202 my ($class, $scfg, $zvol, $size) = @_;
203
204 my $cmd = ['create'];
205
206 push @$cmd, '-s' if $scfg->{sparse};
207
208 push @$cmd, '-b', $scfg->{blocksize} if $scfg->{blocksize};
209
210 push @$cmd, '-V', "${size}k", "$scfg->{pool}/$zvol";
211
212 $class->zfs_request($scfg, undef, @$cmd);
213}
214
215sub zfs_delete_zvol {
216 my ($class, $scfg, $zvol) = @_;
217
218 $class->zfs_request($scfg, undef, 'destroy', '-r', "$scfg->{pool}/$zvol");
219}
220
221sub zfs_list_zvol {
222 my ($class, $scfg) = @_;
223
224 my $text = $class->zfs_request($scfg, 10, 'list', '-o', 'name,volsize,origin', '-t', 'volume', '-Hr');
225 my $zvols = zfs_parse_zvol_list($text);
226 return undef if !$zvols;
227
228 my $list = ();
229 foreach my $zvol (@$zvols) {
230 my @values = split('/', $zvol->{name});
231
232 my $image = pop @values;
233 my $pool = join('/', @values);
234
235 next if $image !~ m/^((vm|base)-(\d+)-\S+)$/;
236 my $owner = $3;
237
238 my $parent = $zvol->{origin};
239 if($zvol->{origin} && $zvol->{origin} =~ m/^$scfg->{pool}\/(\S+)$/){
240 $parent = $1;
241 }
242
243 $list->{$pool}->{$image} = {
244 name => $image,
245 size => $zvol->{size},
246 parent => $parent,
247 format => 'raw',
248 vmid => $owner
249 };
250 }
251
252 return $list;
253}
254
255sub zfs_find_free_diskname {
256 my ($class, $storeid, $scfg, $vmid) = @_;
257
258 my $name = undef;
259 my $volumes = $class->zfs_list_zvol($scfg);
260
261 my $disk_ids = {};
262 my $dat = $volumes->{$scfg->{pool}};
263
264 foreach my $image (keys %$dat) {
265 my $volname = $dat->{$image}->{name};
266 if ($volname =~ m/(vm|base)-$vmid-disk-(\d+)/){
267 $disk_ids->{$2} = 1;
268 }
269 }
270
271 for (my $i = 1; $i < 100; $i++) {
272 if (!$disk_ids->{$i}) {
273 return "vm-$vmid-disk-$i";
274 }
275 }
276
277 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n";
278}
279
b5e5f7e3
DM
280sub status {
281 my ($class, $storeid, $scfg, $cache) = @_;
282
283 my $total = 0;
284 my $free = 0;
285 my $used = 0;
286 my $active = 0;
287
288 eval {
289 ($free, $used) = $class->zfs_get_pool_stats($scfg);
290 $active = 1;
291 $total = $free + $used;
292 };
293 warn $@ if $@;
294
295 return ($total, $free, $used, $active);
296}
297
298sub volume_size_info {
299 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
300
301 return $class->zfs_get_zvol_size($scfg, $volname);
302}
303
304sub volume_snapshot {
305 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
306
307 $class->zfs_request($scfg, undef, 'snapshot', "$scfg->{pool}/$volname\@$snap");
308}
309
310sub volume_snapshot_delete {
311 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
312
313 $class->zfs_request($scfg, undef, 'destroy', "$scfg->{pool}/$volname\@$snap");
314}
315
5bb8e010
DM
316
3171;