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