]>
Commit | Line | Data |
---|---|---|
40cd7d27 AD |
1 | package PVE::Storage::NexentaPlugin; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | use IO::File; | |
6 | use HTTP::Request; | |
7 | use LWP::UserAgent; | |
8 | use MIME::Base64; | |
9 | use JSON; | |
10 | use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach); | |
11 | use PVE::Storage::Plugin; | |
12 | use PVE::JSONSchema qw(get_standard_option); | |
13 | ||
14 | use base qw(PVE::Storage::Plugin); | |
15 | ||
2677f623 | 16 | sub nexenta_request { |
4b0dea6c DM |
17 | my ($scfg, $method, $object, @params) = @_; |
18 | ||
19 | my $apicall = { method => $method, object => $object, params => [ @params ] }; | |
20 | ||
21 | my $json = encode_json($apicall); | |
40cd7d27 | 22 | |
ac4329f3 DM |
23 | my $uri = ($scfg->{ssl} ? "https" : "http") . "://" . $scfg->{portal} . ":2000/rest/nms/"; |
24 | my $req = HTTP::Request->new('POST', $uri); | |
40cd7d27 | 25 | |
4b0dea6c DM |
26 | $req->header('Content-Type' => 'application/json'); |
27 | $req->content($json); | |
2677f623 | 28 | my $token = encode_base64("$scfg->{login}:$scfg->{password}"); |
ac4329f3 | 29 | $req->header(Authorization => "Basic $token"); |
40cd7d27 | 30 | |
2677f623 DM |
31 | my $ua = LWP::UserAgent->new; # You might want some options here |
32 | my $res = $ua->request($req); | |
4b0dea6c DM |
33 | die $res->content if !$res->is_success; |
34 | ||
120ca009 AD |
35 | my $obj = eval { from_json($res->content); }; |
36 | die "JSON not valid. Content: " . $res->content if ($@); | |
4b0dea6c DM |
37 | die "Nexenta API Error: $obj->{error}->{message}\n" if $obj->{error}->{message}; |
38 | return $obj->{result}; | |
2677f623 | 39 | } |
40cd7d27 | 40 | |
4b0dea6c | 41 | |
d6eee582 | 42 | sub nexenta_get_zvol_size { |
4b0dea6c DM |
43 | my ($scfg, $zvol) = @_; |
44 | ||
45 | return nexenta_request($scfg, 'get_child_prop', 'zvol', $zvol, 'size_bytes'); | |
d6eee582 DM |
46 | } |
47 | ||
4d4f734e AD |
48 | sub nexenta_get_zvol_props { |
49 | my ($scfg, $zvol) = @_; | |
50 | ||
51 | my $props = nexenta_request($scfg, 'get_child_props', 'zvol', $zvol, ''); | |
52 | return $props; | |
53 | } | |
54 | ||
40cd7d27 | 55 | sub nexenta_list_lun_mapping_entries { |
4b0dea6c | 56 | my ($scfg, $zvol) = @_; |
40cd7d27 | 57 | |
4b0dea6c | 58 | return nexenta_request($scfg, 'list_lun_mapping_entries', 'scsidisk', "$scfg->{pool}/$zvol"); |
40cd7d27 AD |
59 | } |
60 | ||
61 | sub nexenta_add_lun_mapping_entry { | |
4b0dea6c | 62 | my ($scfg, $zvol) = @_; |
40cd7d27 | 63 | |
4b0dea6c DM |
64 | nexenta_request($scfg, 'add_lun_mapping_entry', 'scsidisk', |
65 | "$scfg->{pool}/$zvol", { target_group => "All" }); | |
40cd7d27 AD |
66 | } |
67 | ||
40cd7d27 | 68 | sub nexenta_delete_lu { |
4b0dea6c | 69 | my ($scfg, $zvol) = @_; |
40cd7d27 | 70 | |
4b0dea6c | 71 | nexenta_request($scfg, 'delete_lu', 'scsidisk', "$scfg->{pool}/$zvol"); |
40cd7d27 AD |
72 | } |
73 | ||
74 | sub nexenta_create_lu { | |
4b0dea6c | 75 | my ($scfg, $zvol) = @_; |
40cd7d27 | 76 | |
4b0dea6c | 77 | nexenta_request($scfg, 'create_lu', 'scsidisk', "$scfg->{pool}/$zvol", {}); |
40cd7d27 AD |
78 | } |
79 | ||
8914a711 DM |
80 | sub nexenta_import_lu { |
81 | my ($scfg, $zvol) = @_; | |
82 | ||
83 | nexenta_request($scfg, 'import_lu', 'scsidisk', "$scfg->{pool}/$zvol"); | |
84 | } | |
85 | ||
40cd7d27 | 86 | sub nexenta_create_zvol { |
4b0dea6c | 87 | my ($scfg, $zvol, $size) = @_; |
40cd7d27 | 88 | |
4b0dea6c DM |
89 | nexenta_request($scfg, 'create', 'zvol', "$scfg->{pool}/$zvol", "${size}KB", |
90 | $scfg->{blocksize}, 1); | |
40cd7d27 | 91 | } |
2677f623 | 92 | |
40cd7d27 | 93 | sub nexenta_delete_zvol { |
4b0dea6c | 94 | my ($scfg, $zvol) = @_; |
40cd7d27 | 95 | |
2570fb94 | 96 | nexenta_request($scfg, 'destroy', 'zvol', "$scfg->{pool}/$zvol", '-r'); |
40cd7d27 | 97 | } |
40cd7d27 AD |
98 | |
99 | sub nexenta_list_zvol { | |
100 | my ($scfg) = @_; | |
101 | ||
4b0dea6c | 102 | my $zvols = nexenta_request($scfg, 'get_names', 'zvol', ''); |
2677f623 | 103 | return undef if !$zvols; |
40cd7d27 | 104 | |
2677f623 | 105 | my $list = {}; |
2677f623 DM |
106 | foreach my $zvol (@$zvols) { |
107 | my @values = split('/', $zvol); | |
40cd7d27 | 108 | |
2677f623 DM |
109 | my $pool = $values[0]; |
110 | my $image = $values[1]; | |
111 | my $owner; | |
1a259abc AD |
112 | |
113 | if ($image =~ m/^((vm|base)-(\d+)-\S+)$/) { | |
114 | $owner = $3; | |
40cd7d27 AD |
115 | } |
116 | ||
4d4f734e | 117 | my $props = nexenta_get_zvol_props($scfg, $zvol); |
1a259abc AD |
118 | my $parent = $props->{origin}; |
119 | if($parent && $parent =~ m/^$scfg->{pool}\/(\S+)$/){ | |
120 | $parent = $1; | |
121 | } | |
4d4f734e | 122 | |
2677f623 DM |
123 | $list->{$pool}->{$image} = { |
124 | name => $image, | |
4d4f734e | 125 | size => $props->{size_bytes}, |
1a259abc | 126 | parent => $parent, |
d6eee582 | 127 | format => 'raw', |
2677f623 DM |
128 | vmid => $owner |
129 | }; | |
2677f623 | 130 | } |
40cd7d27 | 131 | |
2677f623 | 132 | return $list; |
40cd7d27 AD |
133 | } |
134 | ||
2677f623 | 135 | # Configuration |
40cd7d27 AD |
136 | |
137 | sub type { | |
138 | return 'nexenta'; | |
139 | } | |
140 | ||
141 | sub plugindata { | |
142 | return { | |
143 | content => [ {images => 1}, { images => 1 }], | |
144 | }; | |
145 | } | |
146 | ||
40cd7d27 AD |
147 | sub properties { |
148 | return { | |
40cd7d27 AD |
149 | login => { |
150 | description => "login", | |
151 | type => 'string', | |
152 | }, | |
153 | password => { | |
154 | description => "password", | |
155 | type => 'string', | |
156 | }, | |
157 | blocksize => { | |
158 | description => "block size", | |
159 | type => 'string', | |
160 | }, | |
120ca009 AD |
161 | ssl => { |
162 | description => "ssl", | |
163 | type => 'boolean', | |
164 | }, | |
40cd7d27 AD |
165 | }; |
166 | } | |
167 | ||
168 | sub options { | |
169 | return { | |
4f01611d AD |
170 | nodes => { optional => 1 }, |
171 | disable => { optional => 1 }, | |
40cd7d27 AD |
172 | target => { fixed => 1 }, |
173 | portal => { fixed => 1 }, | |
174 | login => { fixed => 1 }, | |
175 | password => { fixed => 1 }, | |
176 | pool => { fixed => 1 }, | |
177 | blocksize => { fixed => 1 }, | |
0f94c1c9 | 178 | ssl => { optional => 1 }, |
40cd7d27 AD |
179 | content => { optional => 1 }, |
180 | }; | |
181 | } | |
182 | ||
183 | # Storage implementation | |
184 | ||
185 | sub parse_volname { | |
186 | my ($class, $volname) = @_; | |
187 | ||
e7b2953b AD |
188 | if ($volname =~ m/^(((base|vm)-(\d+)-\S+)\/)?((base)?(vm)?-(\d+)-\S+)$/) { |
189 | return ('images', $5, $8, $2, $4, $6); | |
40cd7d27 AD |
190 | } |
191 | ||
ac4329f3 | 192 | die "unable to parse nexenta volume name '$volname'\n"; |
40cd7d27 AD |
193 | } |
194 | ||
195 | sub path { | |
196 | my ($class, $scfg, $volname) = @_; | |
197 | ||
198 | my ($vtype, $name, $vmid) = $class->parse_volname($volname); | |
199 | ||
200 | my $target = $scfg->{target}; | |
201 | my $portal = $scfg->{portal}; | |
202 | ||
4b0dea6c | 203 | my $map = nexenta_list_lun_mapping_entries($scfg, $name); |
40cd7d27 AD |
204 | die "could not find lun number" if !$map; |
205 | my $lun = @$map[0]->{lun}; | |
034e4411 AD |
206 | $lun =~ m/^(\d+)$/ or die "lun is not OK\n"; |
207 | $lun = $1; | |
40cd7d27 AD |
208 | my $path = "iscsi://$portal/$target/$lun"; |
209 | ||
210 | return ($path, $vmid, $vtype); | |
211 | } | |
212 | ||
5b29d458 AD |
213 | my $find_free_diskname = sub { |
214 | my ($storeid, $scfg, $vmid) = @_; | |
215 | ||
216 | my $name = undef; | |
217 | my $volumes = nexenta_list_zvol($scfg); | |
218 | die "unable de get zvol list" if !$volumes; | |
219 | ||
220 | my $disk_ids = {}; | |
221 | my $dat = $volumes->{$scfg->{pool}}; | |
222 | ||
223 | foreach my $image (keys %$dat) { | |
224 | my $volname = $dat->{$image}->{name}; | |
225 | if ($volname =~ m/(vm|base)-$vmid-disk-(\d+)/){ | |
226 | $disk_ids->{$2} = 1; | |
227 | } | |
228 | } | |
229 | ||
230 | #fix: can we search in $rbd hash key with a regex to find (vm|base) ? | |
231 | for (my $i = 1; $i < 100; $i++) { | |
232 | if (!$disk_ids->{$i}) { | |
233 | return "vm-$vmid-disk-$i"; | |
234 | } | |
235 | } | |
236 | ||
237 | die "unable to allocate an image name for VM $vmid in storage '$storeid'\n" | |
238 | ||
239 | }; | |
240 | ||
5eab0272 DM |
241 | sub create_base { |
242 | my ($class, $storeid, $scfg, $volname) = @_; | |
243 | ||
1c0097dd AD |
244 | my $snap = '__base__'; |
245 | ||
246 | my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = | |
247 | $class->parse_volname($volname); | |
248 | ||
249 | die "create_base not possible with base image\n" if $isBase; | |
250 | ||
251 | # die "volname '$volname' contains wrong information about parent $parent $basename\n" | |
252 | # if $basename && (!$parent || $parent ne $basename."@".$snap); | |
253 | ||
254 | my $newname = $name; | |
255 | $newname =~ s/^vm-/base-/; | |
256 | ||
257 | my $newvolname = $basename ? "$basename/$newname" : "$newname"; | |
258 | ||
259 | #we can't rename a nexenta volume, so clone it to a new volname | |
260 | nexenta_request($scfg, 'create_snapshot', 'zvol', "$scfg->{pool}/$name", $snap, ''); | |
261 | nexenta_request($scfg, 'clone', 'zvol', "$scfg->{pool}/$name\@$snap", "$scfg->{pool}/$newname"); | |
262 | nexenta_create_lu($scfg, $newname); | |
263 | nexenta_add_lun_mapping_entry($scfg, $newname); | |
264 | ||
265 | my $running = undef; #fixme : is create_base always offline ? | |
266 | ||
267 | $class->volume_snapshot($scfg, $storeid, $newname, $snap, $running); | |
268 | ||
269 | return $newvolname; | |
5eab0272 DM |
270 | } |
271 | ||
272 | sub clone_image { | |
273 | my ($class, $scfg, $storeid, $volname, $vmid) = @_; | |
274 | ||
32467968 AD |
275 | my $snap = '__base__'; |
276 | ||
277 | my ($vtype, $basename, $basevmid, undef, undef, $isBase) = | |
278 | $class->parse_volname($volname); | |
279 | ||
280 | die "clone_image only works on base images\n" if !$isBase; | |
281 | ||
282 | my $name = &$find_free_diskname($storeid, $scfg, $vmid); | |
283 | ||
284 | warn "clone $volname: $basename to $name\n"; | |
285 | ||
286 | my $newvol = "$basename/$name"; | |
287 | ||
288 | nexenta_request($scfg, 'clone', 'zvol', "$scfg->{pool}/$basename\@$snap", "$scfg->{pool}/$name"); | |
289 | ||
290 | nexenta_create_lu($scfg, $name); | |
291 | nexenta_add_lun_mapping_entry($scfg, $name); | |
292 | ||
293 | return $newvol; | |
5eab0272 | 294 | } |
40cd7d27 AD |
295 | |
296 | sub alloc_image { | |
297 | my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_; | |
298 | ||
299 | die "unsupported format '$fmt'" if $fmt ne 'raw'; | |
300 | ||
2677f623 | 301 | die "illegal name '$name' - sould be 'vm-$vmid-*'\n" |
5b29d458 | 302 | if $name && $name !~ m/^vm-$vmid-/; |
40cd7d27 | 303 | |
5b29d458 | 304 | $name = &$find_free_diskname($storeid, $scfg, $vmid); |
40cd7d27 | 305 | |
4b0dea6c DM |
306 | nexenta_create_zvol($scfg, $name, $size); |
307 | nexenta_create_lu($scfg, $name); | |
308 | nexenta_add_lun_mapping_entry($scfg, $name); | |
40cd7d27 AD |
309 | |
310 | return $name; | |
311 | } | |
312 | ||
313 | sub free_image { | |
32437ed2 | 314 | my ($class, $storeid, $scfg, $volname, $isBase) = @_; |
40cd7d27 AD |
315 | |
316 | my ($vtype, $name, $vmid) = $class->parse_volname($volname); | |
317 | ||
4b0dea6c DM |
318 | nexenta_delete_lu($scfg, $name); |
319 | nexenta_delete_zvol($scfg, $name); | |
40cd7d27 | 320 | |
74822cd7 AD |
321 | #if base volume, we delete also the original cloned volume |
322 | if ($isBase) { | |
323 | $name =~ s/^base-/vm-/; | |
324 | nexenta_delete_lu($scfg, $name); | |
325 | nexenta_delete_zvol($scfg, $name); | |
326 | } | |
327 | ||
40cd7d27 AD |
328 | return undef; |
329 | } | |
330 | ||
331 | sub list_images { | |
332 | my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_; | |
333 | ||
334 | $cache->{nexenta} = nexenta_list_zvol($scfg) if !$cache->{nexenta}; | |
335 | my $nexentapool = $scfg->{pool}; | |
336 | my $res = []; | |
337 | if (my $dat = $cache->{nexenta}->{$nexentapool}) { | |
338 | foreach my $image (keys %$dat) { | |
339 | ||
340 | my $volname = $dat->{$image}->{name}; | |
2e910a0e | 341 | my $parent = $dat->{$image}->{parent}; |
40cd7d27 | 342 | |
2e910a0e AD |
343 | my $volid = undef; |
344 | if ($parent && $parent =~ m/^(\S+)@(\S+)$/) { | |
345 | my ($basename) = ($1); | |
346 | $volid = "$storeid:$basename/$volname"; | |
347 | } else { | |
348 | $volid = "$storeid:$volname"; | |
349 | } | |
40cd7d27 | 350 | |
40cd7d27 AD |
351 | my $owner = $dat->{$volname}->{vmid}; |
352 | if ($vollist) { | |
353 | my $found = grep { $_ eq $volid } @$vollist; | |
354 | next if !$found; | |
355 | } else { | |
356 | next if defined ($vmid) && ($owner ne $vmid); | |
357 | } | |
358 | ||
359 | my $info = $dat->{$volname}; | |
360 | $info->{volid} = $volid; | |
361 | ||
362 | push @$res, $info; | |
363 | ||
364 | } | |
365 | } | |
366 | ||
367 | return $res; | |
368 | } | |
369 | ||
2103bd20 DM |
370 | sub nexenta_parse_size { |
371 | my ($text) = @_; | |
372 | ||
373 | return 0 if !$text; | |
374 | ||
375 | if ($text =~ m/^(\d+)([TGMK])?$/) { | |
376 | my ($size, $unit) = ($1, $2); | |
377 | return $size if !$unit; | |
378 | if ($unit eq 'K') { | |
379 | $size *= 1024; | |
380 | } elsif ($unit eq 'M') { | |
381 | $size *= 1024*1024; | |
382 | } elsif ($unit eq 'G') { | |
383 | $size *= 1024*1024*1024; | |
384 | } elsif ($unit eq 'T') { | |
385 | $size *= 1024*1024*1024*1024; | |
386 | } | |
387 | return $size; | |
388 | } else { | |
389 | return 0; | |
390 | } | |
391 | } | |
40cd7d27 AD |
392 | sub status { |
393 | my ($class, $storeid, $scfg, $cache) = @_; | |
394 | ||
395 | my $total = 0; | |
396 | my $free = 0; | |
397 | my $used = 0; | |
2103bd20 DM |
398 | my $active = 0; |
399 | ||
400 | eval { | |
401 | my $map = nexenta_request($scfg, 'get_child_props', 'volume', $scfg->{pool}, ''); | |
402 | $active = 1; | |
403 | $total = nexenta_parse_size($map->{size}); | |
404 | $used = nexenta_parse_size($map->{used}); | |
405 | $free = $total - $used; | |
406 | }; | |
407 | warn $@ if $@; | |
40cd7d27 | 408 | |
2103bd20 | 409 | return ($total, $free, $used, $active); |
40cd7d27 AD |
410 | } |
411 | ||
412 | sub activate_storage { | |
413 | my ($class, $storeid, $scfg, $cache) = @_; | |
414 | return 1; | |
415 | } | |
416 | ||
417 | sub deactivate_storage { | |
418 | my ($class, $storeid, $scfg, $cache) = @_; | |
419 | return 1; | |
420 | } | |
421 | ||
422 | sub activate_volume { | |
423 | my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_; | |
424 | return 1; | |
425 | } | |
426 | ||
427 | sub deactivate_volume { | |
428 | my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_; | |
429 | return 1; | |
430 | } | |
431 | ||
c3013a8b AD |
432 | sub volume_size_info { |
433 | my ($class, $scfg, $storeid, $volname, $timeout) = @_; | |
434 | ||
d2befd94 AD |
435 | my ($vtype, $name, $vmid) = $class->parse_volname($volname); |
436 | ||
437 | return nexenta_get_zvol_size($scfg, "$scfg->{pool}/$name"), | |
c3013a8b AD |
438 | } |
439 | ||
69971d8b AD |
440 | sub volume_resize { |
441 | my ($class, $scfg, $storeid, $volname, $size, $running) = @_; | |
442 | ||
d6a30aa2 AD |
443 | my ($vtype, $name, $vmid) = $class->parse_volname($volname); |
444 | ||
445 | nexenta_request($scfg, 'set_child_prop', 'zvol', "$scfg->{pool}/$name", 'volsize', ($size/1024) . 'KB'); | |
69971d8b AD |
446 | } |
447 | ||
5223286c AD |
448 | sub volume_snapshot { |
449 | my ($class, $scfg, $storeid, $volname, $snap, $running) = @_; | |
450 | ||
1e507a72 AD |
451 | my ($vtype, $name, $vmid) = $class->parse_volname($volname); |
452 | ||
453 | nexenta_request($scfg, 'create_snapshot', 'zvol', "$scfg->{pool}/$name", $snap, ''); | |
5223286c AD |
454 | } |
455 | ||
4c6c6423 AD |
456 | sub volume_snapshot_rollback { |
457 | my ($class, $scfg, $storeid, $volname, $snap) = @_; | |
458 | ||
851dc880 AD |
459 | my ($vtype, $name, $vmid) = $class->parse_volname($volname); |
460 | ||
461 | nexenta_delete_lu($scfg, $name); | |
4c6c6423 | 462 | |
851dc880 | 463 | nexenta_request($scfg, 'rollback', 'snapshot', "$scfg->{pool}/$name\@$snap", ''); |
4c6c6423 | 464 | |
851dc880 | 465 | nexenta_import_lu($scfg, $name); |
4b0dea6c | 466 | |
851dc880 | 467 | nexenta_add_lun_mapping_entry($scfg, $name); |
4c6c6423 AD |
468 | } |
469 | ||
5d8f5e22 AD |
470 | sub volume_snapshot_delete { |
471 | my ($class, $scfg, $storeid, $volname, $snap, $running) = @_; | |
472 | ||
201345af AD |
473 | my ($vtype, $name, $vmid) = $class->parse_volname($volname); |
474 | ||
475 | nexenta_request($scfg, 'destroy', 'snapshot', "$scfg->{pool}/$name\@$snap", ''); | |
5d8f5e22 AD |
476 | } |
477 | ||
767132f7 AD |
478 | sub volume_has_feature { |
479 | my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_; | |
480 | ||
481 | my $features = { | |
9bc7fa7a AD |
482 | snapshot => { current => 1, snap => 1}, |
483 | clone => { base => 1}, | |
74158ff3 | 484 | copy => { base => 1, current => 1}, |
767132f7 AD |
485 | }; |
486 | ||
9bc7fa7a AD |
487 | my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = |
488 | $class->parse_volname($volname); | |
489 | ||
490 | my $key = undef; | |
491 | if($snapname){ | |
2c5a7097 | 492 | $key = 'snap'; |
9bc7fa7a AD |
493 | }else{ |
494 | $key = $isBase ? 'base' : 'current'; | |
495 | } | |
496 | return 1 if $features->{$feature}->{$key}; | |
767132f7 AD |
497 | |
498 | return undef; | |
499 | } | |
500 | ||
40cd7d27 | 501 | 1; |