]> git.proxmox.com Git - pve-storage.git/blame - PVE/Storage/NexentaPlugin.pm
has_feature : template
[pve-storage.git] / PVE / Storage / NexentaPlugin.pm
CommitLineData
40cd7d27
AD
1package PVE::Storage::NexentaPlugin;
2
3use strict;
4use warnings;
5use IO::File;
6use HTTP::Request;
7use LWP::UserAgent;
8use MIME::Base64;
9use JSON;
10use PVE::Tools qw(run_command file_read_firstline trim dir_glob_regex dir_glob_foreach);
11use PVE::Storage::Plugin;
12use PVE::JSONSchema qw(get_standard_option);
13
14use base qw(PVE::Storage::Plugin);
15
2677f623 16sub 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 42sub 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
48sub 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 55sub 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
61sub 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 68sub 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
74sub 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
80sub nexenta_import_lu {
81 my ($scfg, $zvol) = @_;
82
83 nexenta_request($scfg, 'import_lu', 'scsidisk', "$scfg->{pool}/$zvol");
84}
85
40cd7d27 86sub 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 93sub 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
99sub 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
137sub type {
138 return 'nexenta';
139}
140
141sub plugindata {
142 return {
143 content => [ {images => 1}, { images => 1 }],
144 };
145}
146
40cd7d27
AD
147sub 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
168sub 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
185sub 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
195sub 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
213my $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
241sub 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
272sub 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
296sub 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
313sub 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
331sub 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
370sub 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
392sub 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
412sub activate_storage {
413 my ($class, $storeid, $scfg, $cache) = @_;
414 return 1;
415}
416
417sub deactivate_storage {
418 my ($class, $storeid, $scfg, $cache) = @_;
419 return 1;
420}
421
422sub activate_volume {
423 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
424 return 1;
425}
426
427sub deactivate_volume {
428 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
429 return 1;
430}
431
c3013a8b
AD
432sub 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
440sub 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
448sub 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
456sub 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
470sub 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
478sub 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},
5649ccfe
AD
484 template => { current => 1},
485 copy => { base => 1, current => 1},
767132f7
AD
486 };
487
9bc7fa7a
AD
488 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
489 $class->parse_volname($volname);
490
491 my $key = undef;
492 if($snapname){
2c5a7097 493 $key = 'snap';
9bc7fa7a
AD
494 }else{
495 $key = $isBase ? 'base' : 'current';
496 }
497 return 1 if $features->{$feature}->{$key};
767132f7
AD
498
499 return undef;
500}
501
40cd7d27 5021;