]> git.proxmox.com Git - pve-storage.git/blob - PVE/Storage/NexentaPlugin.pm
f940ada8defdadba5f09f2f999e04788723dff75
[pve-storage.git] / PVE / Storage / NexentaPlugin.pm
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
16 sub nexenta_request {
17 my ($scfg, $method, $object, @params) = @_;
18
19 my $apicall = { method => $method, object => $object, params => [ @params ] };
20
21 my $json = encode_json($apicall);
22
23 my $uri = ($scfg->{ssl} ? "https" : "http") . "://" . $scfg->{portal} . ":2000/rest/nms/";
24 my $req = HTTP::Request->new('POST', $uri);
25
26 $req->header('Content-Type' => 'application/json');
27 $req->content($json);
28 my $token = encode_base64("$scfg->{login}:$scfg->{password}");
29 $req->header(Authorization => "Basic $token");
30
31 my $ua = LWP::UserAgent->new; # You might want some options here
32 my $res = $ua->request($req);
33 die $res->content if !$res->is_success;
34
35 my $obj = eval { from_json($res->content); };
36 die "JSON not valid. Content: " . $res->content if ($@);
37 die "Nexenta API Error: $obj->{error}->{message}\n" if $obj->{error}->{message};
38 return $obj->{result};
39 }
40
41
42 sub nexenta_get_zvol_size {
43 my ($scfg, $zvol) = @_;
44
45 return nexenta_request($scfg, 'get_child_prop', 'zvol', $zvol, 'size_bytes');
46 }
47
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
55 sub nexenta_list_lun_mapping_entries {
56 my ($scfg, $zvol) = @_;
57
58 return nexenta_request($scfg, 'list_lun_mapping_entries', 'scsidisk', "$scfg->{pool}/$zvol");
59 }
60
61 sub nexenta_add_lun_mapping_entry {
62 my ($scfg, $zvol) = @_;
63
64 nexenta_request($scfg, 'add_lun_mapping_entry', 'scsidisk',
65 "$scfg->{pool}/$zvol", { target_group => "All" });
66 }
67
68 sub nexenta_delete_lu {
69 my ($scfg, $zvol) = @_;
70
71 nexenta_request($scfg, 'delete_lu', 'scsidisk', "$scfg->{pool}/$zvol");
72 }
73
74 sub nexenta_create_lu {
75 my ($scfg, $zvol) = @_;
76
77 nexenta_request($scfg, 'create_lu', 'scsidisk', "$scfg->{pool}/$zvol", {});
78 }
79
80 sub nexenta_import_lu {
81 my ($scfg, $zvol) = @_;
82
83 nexenta_request($scfg, 'import_lu', 'scsidisk', "$scfg->{pool}/$zvol");
84 }
85
86 sub nexenta_create_zvol {
87 my ($scfg, $zvol, $size) = @_;
88
89 nexenta_request($scfg, 'create', 'zvol', "$scfg->{pool}/$zvol", "${size}KB",
90 $scfg->{blocksize}, 1);
91 }
92
93 sub nexenta_delete_zvol {
94 my ($scfg, $zvol) = @_;
95
96 nexenta_request($scfg, 'destroy', 'zvol', "$scfg->{pool}/$zvol", '-r');
97 }
98
99 sub nexenta_list_zvol {
100 my ($scfg) = @_;
101
102 my $zvols = nexenta_request($scfg, 'get_names', 'zvol', '');
103 return undef if !$zvols;
104
105 my $list = {};
106 foreach my $zvol (@$zvols) {
107 my @values = split('/', $zvol);
108
109 my $pool = $values[0];
110 my $image = $values[1];
111 my $owner;
112
113 if ($image =~ m/^((vm|base)-(\d+)-\S+)$/) {
114 $owner = $3;
115 }
116
117 my $props = nexenta_get_zvol_props($scfg, $zvol);
118 my $parent = $props->{origin};
119 if($parent && $parent =~ m/^$scfg->{pool}\/(\S+)$/){
120 $parent = $1;
121 }
122
123 $list->{$pool}->{$image} = {
124 name => $image,
125 size => $props->{size_bytes},
126 parent => $parent,
127 format => 'raw',
128 vmid => $owner
129 };
130 }
131
132 return $list;
133 }
134
135 # Configuration
136
137 sub type {
138 return 'nexenta';
139 }
140
141 sub plugindata {
142 return {
143 content => [ {images => 1}, { images => 1 }],
144 };
145 }
146
147 sub properties {
148 return {
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 },
161 ssl => {
162 description => "ssl",
163 type => 'boolean',
164 },
165 };
166 }
167
168 sub options {
169 return {
170 nodes => { optional => 1 },
171 disable => { optional => 1 },
172 target => { fixed => 1 },
173 portal => { fixed => 1 },
174 login => { fixed => 1 },
175 password => { fixed => 1 },
176 pool => { fixed => 1 },
177 blocksize => { fixed => 1 },
178 ssl => { optional => 1 },
179 content => { optional => 1 },
180 };
181 }
182
183 # Storage implementation
184
185 sub parse_volname {
186 my ($class, $volname) = @_;
187
188 if ($volname =~ m/^(((base|vm)-(\d+)-\S+)\/)?((base)?(vm)?-(\d+)-\S+)$/) {
189 return ('images', $5, $8, $2, $4, $6);
190 }
191
192 die "unable to parse nexenta volume name '$volname'\n";
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
203 my $map = nexenta_list_lun_mapping_entries($scfg, $name);
204 die "could not find lun number" if !$map;
205 my $lun = @$map[0]->{lun};
206 $lun =~ m/^(\d+)$/ or die "lun is not OK\n";
207 $lun = $1;
208 my $path = "iscsi://$portal/$target/$lun";
209
210 return ($path, $vmid, $vtype);
211 }
212
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
241 sub create_base {
242 my ($class, $storeid, $scfg, $volname) = @_;
243
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;
270 }
271
272 sub clone_image {
273 my ($class, $scfg, $storeid, $volname, $vmid) = @_;
274
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;
294 }
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
301 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
302 if $name && $name !~ m/^vm-$vmid-/;
303
304 $name = &$find_free_diskname($storeid, $scfg, $vmid);
305
306 nexenta_create_zvol($scfg, $name, $size);
307 nexenta_create_lu($scfg, $name);
308 nexenta_add_lun_mapping_entry($scfg, $name);
309
310 return $name;
311 }
312
313 sub free_image {
314 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
315
316 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
317
318 nexenta_delete_lu($scfg, $name);
319 nexenta_delete_zvol($scfg, $name);
320
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
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};
341 my $parent = $dat->{$image}->{parent};
342
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 }
350
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
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 }
392 sub status {
393 my ($class, $storeid, $scfg, $cache) = @_;
394
395 my $total = 0;
396 my $free = 0;
397 my $used = 0;
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 $@;
408
409 return ($total, $free, $used, $active);
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
432 sub volume_size_info {
433 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
434
435 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
436
437 return nexenta_get_zvol_size($scfg, "$scfg->{pool}/$name"),
438 }
439
440 sub volume_resize {
441 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
442
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');
446 }
447
448 sub volume_snapshot {
449 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
450
451 nexenta_request($scfg, 'create_snapshot', 'zvol', "$scfg->{pool}/$volname", $snap, '');
452 }
453
454 sub volume_snapshot_rollback {
455 my ($class, $scfg, $storeid, $volname, $snap) = @_;
456
457 nexenta_delete_lu($scfg, $volname);
458
459 nexenta_request($scfg, 'rollback', 'snapshot', "$scfg->{pool}/$volname\@$snap", '');
460
461 nexenta_import_lu($scfg, $volname);
462
463 nexenta_add_lun_mapping_entry($scfg, $volname);
464 }
465
466 sub volume_snapshot_delete {
467 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
468
469 nexenta_request($scfg, 'destroy', 'snapshot', "$scfg->{pool}/$volname\@$snap", '');
470 }
471
472 sub volume_has_feature {
473 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
474
475 my $features = {
476 snapshot => { current => 1, snap => 1},
477 clone => { snap => 1},
478 };
479
480 my $snap = $snapname ? 'snap' : 'current';
481 return 1 if $features->{$feature}->{$snap};
482
483 return undef;
484 }
485
486 1;