]> git.proxmox.com Git - pve-storage.git/blob - PVE/Storage/NexentaPlugin.pm
nexenta : list_images
[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 return undef;
322 }
323
324 sub list_images {
325 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
326
327 $cache->{nexenta} = nexenta_list_zvol($scfg) if !$cache->{nexenta};
328 my $nexentapool = $scfg->{pool};
329 my $res = [];
330 if (my $dat = $cache->{nexenta}->{$nexentapool}) {
331 foreach my $image (keys %$dat) {
332
333 my $volname = $dat->{$image}->{name};
334 my $parent = $dat->{$image}->{parent};
335
336 my $volid = undef;
337 if ($parent && $parent =~ m/^(\S+)@(\S+)$/) {
338 my ($basename) = ($1);
339 $volid = "$storeid:$basename/$volname";
340 } else {
341 $volid = "$storeid:$volname";
342 }
343
344 my $owner = $dat->{$volname}->{vmid};
345 if ($vollist) {
346 my $found = grep { $_ eq $volid } @$vollist;
347 next if !$found;
348 } else {
349 next if defined ($vmid) && ($owner ne $vmid);
350 }
351
352 my $info = $dat->{$volname};
353 $info->{volid} = $volid;
354
355 push @$res, $info;
356
357 }
358 }
359
360 return $res;
361 }
362
363 sub nexenta_parse_size {
364 my ($text) = @_;
365
366 return 0 if !$text;
367
368 if ($text =~ m/^(\d+)([TGMK])?$/) {
369 my ($size, $unit) = ($1, $2);
370 return $size if !$unit;
371 if ($unit eq 'K') {
372 $size *= 1024;
373 } elsif ($unit eq 'M') {
374 $size *= 1024*1024;
375 } elsif ($unit eq 'G') {
376 $size *= 1024*1024*1024;
377 } elsif ($unit eq 'T') {
378 $size *= 1024*1024*1024*1024;
379 }
380 return $size;
381 } else {
382 return 0;
383 }
384 }
385 sub status {
386 my ($class, $storeid, $scfg, $cache) = @_;
387
388 my $total = 0;
389 my $free = 0;
390 my $used = 0;
391 my $active = 0;
392
393 eval {
394 my $map = nexenta_request($scfg, 'get_child_props', 'volume', $scfg->{pool}, '');
395 $active = 1;
396 $total = nexenta_parse_size($map->{size});
397 $used = nexenta_parse_size($map->{used});
398 $free = $total - $used;
399 };
400 warn $@ if $@;
401
402 return ($total, $free, $used, $active);
403 }
404
405 sub activate_storage {
406 my ($class, $storeid, $scfg, $cache) = @_;
407 return 1;
408 }
409
410 sub deactivate_storage {
411 my ($class, $storeid, $scfg, $cache) = @_;
412 return 1;
413 }
414
415 sub activate_volume {
416 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
417 return 1;
418 }
419
420 sub deactivate_volume {
421 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
422 return 1;
423 }
424
425 sub volume_size_info {
426 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
427
428 return nexenta_get_zvol_size($scfg, "$scfg->{pool}/$volname"),
429 }
430
431 sub volume_resize {
432 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
433
434 nexenta_request($scfg, 'set_child_prop', 'zvol', "$scfg->{pool}/$volname", 'volsize', ($size/1024) . 'KB');
435 }
436
437 sub volume_snapshot {
438 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
439
440 nexenta_request($scfg, 'create_snapshot', 'zvol', "$scfg->{pool}/$volname", $snap, '');
441 }
442
443 sub volume_snapshot_rollback {
444 my ($class, $scfg, $storeid, $volname, $snap) = @_;
445
446 nexenta_delete_lu($scfg, $volname);
447
448 nexenta_request($scfg, 'rollback', 'snapshot', "$scfg->{pool}/$volname\@$snap", '');
449
450 nexenta_import_lu($scfg, $volname);
451
452 nexenta_add_lun_mapping_entry($scfg, $volname);
453 }
454
455 sub volume_snapshot_delete {
456 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
457
458 nexenta_request($scfg, 'destroy', 'snapshot', "$scfg->{pool}/$volname\@$snap", '');
459 }
460
461 sub volume_has_feature {
462 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
463
464 my $features = {
465 snapshot => { current => 1, snap => 1},
466 clone => { snap => 1},
467 };
468
469 my $snap = $snapname ? 'snap' : 'current';
470 return 1 if $features->{$feature}->{$snap};
471
472 return undef;
473 }
474
475 1;