]> git.proxmox.com Git - pve-storage.git/blob - PVE/Storage/NexentaPlugin.pm
nexenta : create_base
[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 die "not implemented";
276 }
277
278 sub alloc_image {
279 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
280
281 die "unsupported format '$fmt'" if $fmt ne 'raw';
282
283 die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
284 if $name && $name !~ m/^vm-$vmid-/;
285
286 $name = &$find_free_diskname($storeid, $scfg, $vmid);
287
288 nexenta_create_zvol($scfg, $name, $size);
289 nexenta_create_lu($scfg, $name);
290 nexenta_add_lun_mapping_entry($scfg, $name);
291
292 return $name;
293 }
294
295 sub free_image {
296 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
297
298 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
299
300 nexenta_delete_lu($scfg, $name);
301 nexenta_delete_zvol($scfg, $name);
302
303 return undef;
304 }
305
306 sub list_images {
307 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
308
309 $cache->{nexenta} = nexenta_list_zvol($scfg) if !$cache->{nexenta};
310 my $nexentapool = $scfg->{pool};
311 my $res = [];
312 if (my $dat = $cache->{nexenta}->{$nexentapool}) {
313 foreach my $image (keys %$dat) {
314
315 my $volname = $dat->{$image}->{name};
316
317 my $volid = "$storeid:$volname";
318
319 my $owner = $dat->{$volname}->{vmid};
320 if ($vollist) {
321 my $found = grep { $_ eq $volid } @$vollist;
322 next if !$found;
323 } else {
324 next if defined ($vmid) && ($owner ne $vmid);
325 }
326
327 my $info = $dat->{$volname};
328 $info->{volid} = $volid;
329
330 push @$res, $info;
331
332 }
333 }
334
335 return $res;
336 }
337
338 sub nexenta_parse_size {
339 my ($text) = @_;
340
341 return 0 if !$text;
342
343 if ($text =~ m/^(\d+)([TGMK])?$/) {
344 my ($size, $unit) = ($1, $2);
345 return $size if !$unit;
346 if ($unit eq 'K') {
347 $size *= 1024;
348 } elsif ($unit eq 'M') {
349 $size *= 1024*1024;
350 } elsif ($unit eq 'G') {
351 $size *= 1024*1024*1024;
352 } elsif ($unit eq 'T') {
353 $size *= 1024*1024*1024*1024;
354 }
355 return $size;
356 } else {
357 return 0;
358 }
359 }
360 sub status {
361 my ($class, $storeid, $scfg, $cache) = @_;
362
363 my $total = 0;
364 my $free = 0;
365 my $used = 0;
366 my $active = 0;
367
368 eval {
369 my $map = nexenta_request($scfg, 'get_child_props', 'volume', $scfg->{pool}, '');
370 $active = 1;
371 $total = nexenta_parse_size($map->{size});
372 $used = nexenta_parse_size($map->{used});
373 $free = $total - $used;
374 };
375 warn $@ if $@;
376
377 return ($total, $free, $used, $active);
378 }
379
380 sub activate_storage {
381 my ($class, $storeid, $scfg, $cache) = @_;
382 return 1;
383 }
384
385 sub deactivate_storage {
386 my ($class, $storeid, $scfg, $cache) = @_;
387 return 1;
388 }
389
390 sub activate_volume {
391 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
392 return 1;
393 }
394
395 sub deactivate_volume {
396 my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
397 return 1;
398 }
399
400 sub volume_size_info {
401 my ($class, $scfg, $storeid, $volname, $timeout) = @_;
402
403 return nexenta_get_zvol_size($scfg, "$scfg->{pool}/$volname"),
404 }
405
406 sub volume_resize {
407 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
408
409 nexenta_request($scfg, 'set_child_prop', 'zvol', "$scfg->{pool}/$volname", 'volsize', ($size/1024) . 'KB');
410 }
411
412 sub volume_snapshot {
413 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
414
415 nexenta_request($scfg, 'create_snapshot', 'zvol', "$scfg->{pool}/$volname", $snap, '');
416 }
417
418 sub volume_snapshot_rollback {
419 my ($class, $scfg, $storeid, $volname, $snap) = @_;
420
421 nexenta_delete_lu($scfg, $volname);
422
423 nexenta_request($scfg, 'rollback', 'snapshot', "$scfg->{pool}/$volname\@$snap", '');
424
425 nexenta_import_lu($scfg, $volname);
426
427 nexenta_add_lun_mapping_entry($scfg, $volname);
428 }
429
430 sub volume_snapshot_delete {
431 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
432
433 nexenta_request($scfg, 'destroy', 'snapshot', "$scfg->{pool}/$volname\@$snap", '');
434 }
435
436 sub volume_has_feature {
437 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
438
439 my $features = {
440 snapshot => { current => 1, snap => 1},
441 clone => { snap => 1},
442 };
443
444 my $snap = $snapname ? 'snap' : 'current';
445 return 1 if $features->{$feature}->{$snap};
446
447 return undef;
448 }
449
450 1;