]>
Commit | Line | Data |
---|---|---|
40cd7d27 AD |
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 | ||
2677f623 DM |
16 | sub nexenta_request { |
17 | my ($scfg, $json) = @_; | |
40cd7d27 | 18 | |
120ca009 | 19 | my $uri = ( $scfg->{ssl} ? "https" : "http" ) . "://" . $scfg->{portal} . ":2000/rest/nms/"; |
2677f623 | 20 | my $req = HTTP::Request->new( 'POST', $uri ); |
40cd7d27 | 21 | |
2677f623 DM |
22 | $req->header( 'Content-Type' => 'application/json' ); |
23 | $req->content( $json ); | |
24 | my $token = encode_base64("$scfg->{login}:$scfg->{password}"); | |
25 | $req->header( Authorization => "Basic $token" ); | |
40cd7d27 | 26 | |
2677f623 DM |
27 | my $ua = LWP::UserAgent->new; # You might want some options here |
28 | my $res = $ua->request($req); | |
29 | if (!$res->is_success) { | |
30 | die $res->content; | |
31 | } | |
120ca009 AD |
32 | my $obj = eval { from_json($res->content); }; |
33 | die "JSON not valid. Content: " . $res->content if ($@); | |
34 | die "Nexenta API Error: " . $obj->{error}->{message} if $obj->{error}->{message}; | |
2677f623 DM |
35 | return $obj->{result} if $obj->{result}; |
36 | return 1; | |
37 | } | |
40cd7d27 | 38 | |
d6eee582 DM |
39 | sub nexenta_get_zvol_size { |
40 | my ($zvol, $scfg) = @_; | |
41 | ||
42 | my $json = '{"method": "get_child_prop", "object": "zvol", "params": ["' . $zvol . '", "volsize"]}'; | |
43 | my $volsize = nexenta_request($scfg, $json); | |
44 | if ($volsize =~ /^(\d+)([KMGT])$/) { | |
45 | my ($size, $unit) = ($1, $2); | |
46 | if ($unit eq 'K') { | |
47 | $size *= 1024; | |
48 | } elsif ($unit eq 'M') { | |
49 | $size *= 1024*1024; | |
50 | } elsif ($unit eq 'G') { | |
51 | $size *= 1024*1024*1024; | |
52 | } elsif ($unit eq 'T') { | |
53 | $size *= 1024*1024*1024*1024; | |
54 | } | |
55 | return $size; | |
56 | } | |
57 | die "got undefined size '$volsize'\n"; | |
58 | } | |
59 | ||
40cd7d27 | 60 | sub nexenta_list_lun_mapping_entries { |
2677f623 | 61 | my ($zvol, $scfg) = @_; |
40cd7d27 | 62 | |
2677f623 DM |
63 | my $json = '{"method": "list_lun_mapping_entries","object" : "scsidisk","params": ["'.$scfg->{pool}.'/'.$zvol.'"]}'; |
64 | my $map = nexenta_request($scfg,$json); | |
65 | return $map if $map; | |
66 | return undef; | |
40cd7d27 AD |
67 | } |
68 | ||
69 | sub nexenta_add_lun_mapping_entry { | |
2677f623 | 70 | my ($zvol, $scfg) = @_; |
40cd7d27 | 71 | |
2677f623 | 72 | my $json = '{"method": "add_lun_mapping_entry","object" : "scsidisk","params": ["'.$scfg->{pool}.'/'.$zvol.'",{"target_group": "All"}]}'; |
40cd7d27 | 73 | |
120ca009 | 74 | nexenta_request($scfg, $json); |
2677f623 | 75 | return 1; |
40cd7d27 AD |
76 | } |
77 | ||
40cd7d27 | 78 | sub nexenta_delete_lu { |
2677f623 | 79 | my ($zvol, $scfg) = @_; |
40cd7d27 | 80 | |
2677f623 | 81 | my $json = '{"method": "delete_lu","object" : "scsidisk","params": ["'.$scfg->{pool}.'/'.$zvol.'"]}'; |
120ca009 | 82 | nexenta_request($scfg, $json); |
2677f623 | 83 | return 1; |
40cd7d27 AD |
84 | } |
85 | ||
86 | sub nexenta_create_lu { | |
87 | my ($zvol, $scfg) = @_; | |
88 | ||
2677f623 | 89 | my $json = '{"method": "create_lu","object" : "scsidisk","params": ["'.$scfg->{pool}.'/'.$zvol.'",{}]}'; |
40cd7d27 | 90 | |
120ca009 | 91 | nexenta_request($scfg, $json); |
2677f623 | 92 | return 1; |
40cd7d27 AD |
93 | } |
94 | ||
95 | sub nexenta_create_zvol { | |
2677f623 | 96 | my ($zvol, $size, $scfg) = @_; |
40cd7d27 | 97 | |
2677f623 DM |
98 | my $blocksize = $scfg->{blocksize}; |
99 | my $nexentapool = $scfg->{pool}; | |
40cd7d27 | 100 | |
2677f623 | 101 | my $json = '{"method": "create","object" : "zvol","params": ["'.$nexentapool.'/'.$zvol.'", "'.$size.'KB", "'.$blocksize.'", "1"]}'; |
40cd7d27 | 102 | |
120ca009 | 103 | nexenta_request($scfg, $json); |
2677f623 | 104 | return 1; |
40cd7d27 | 105 | } |
2677f623 | 106 | |
40cd7d27 AD |
107 | sub nexenta_delete_zvol { |
108 | my ($zvol, $scfg) = @_; | |
40cd7d27 | 109 | |
2677f623 DM |
110 | sleep 5; |
111 | my $json = '{"method": "destroy","object" : "zvol","params": ["'.$scfg->{pool}.'/'.$zvol.'", ""]}'; | |
120ca009 | 112 | nexenta_request($scfg, $json); |
2677f623 | 113 | return 1; |
40cd7d27 | 114 | } |
40cd7d27 AD |
115 | |
116 | sub nexenta_list_zvol { | |
117 | my ($scfg) = @_; | |
118 | ||
2677f623 DM |
119 | my $json = '{"method": "get_names","object" : "zvol","params": [""]}'; |
120 | my $volumes = {}; | |
40cd7d27 | 121 | |
2677f623 DM |
122 | my $zvols = nexenta_request($scfg, $json); |
123 | return undef if !$zvols; | |
40cd7d27 | 124 | |
2677f623 | 125 | my $list = {}; |
40cd7d27 | 126 | |
2677f623 DM |
127 | foreach my $zvol (@$zvols) { |
128 | my @values = split('/', $zvol); | |
129 | #$volumes->{$values[0]}->{$values[1]}->{volname} = $values[1]; | |
40cd7d27 | 130 | |
2677f623 DM |
131 | my $pool = $values[0]; |
132 | my $image = $values[1]; | |
133 | my $owner; | |
134 | if ($image =~ m/^(vm-(\d+)-\S+)$/) { | |
135 | $owner = $2; | |
40cd7d27 AD |
136 | } |
137 | ||
2677f623 DM |
138 | $list->{$pool}->{$image} = { |
139 | name => $image, | |
d6eee582 DM |
140 | size => nexenta_get_zvol_size($zvol, $scfg), |
141 | format => 'raw', | |
2677f623 DM |
142 | vmid => $owner |
143 | }; | |
40cd7d27 | 144 | |
2677f623 | 145 | } |
40cd7d27 | 146 | |
2677f623 | 147 | return $list; |
40cd7d27 AD |
148 | } |
149 | ||
2677f623 | 150 | # Configuration |
40cd7d27 AD |
151 | |
152 | sub type { | |
153 | return 'nexenta'; | |
154 | } | |
155 | ||
156 | sub plugindata { | |
157 | return { | |
158 | content => [ {images => 1}, { images => 1 }], | |
159 | }; | |
160 | } | |
161 | ||
40cd7d27 AD |
162 | sub properties { |
163 | return { | |
40cd7d27 AD |
164 | login => { |
165 | description => "login", | |
166 | type => 'string', | |
167 | }, | |
168 | password => { | |
169 | description => "password", | |
170 | type => 'string', | |
171 | }, | |
172 | blocksize => { | |
173 | description => "block size", | |
174 | type => 'string', | |
175 | }, | |
120ca009 AD |
176 | ssl => { |
177 | description => "ssl", | |
178 | type => 'boolean', | |
179 | }, | |
40cd7d27 AD |
180 | }; |
181 | } | |
182 | ||
183 | sub options { | |
184 | return { | |
4f01611d AD |
185 | nodes => { optional => 1 }, |
186 | disable => { optional => 1 }, | |
40cd7d27 AD |
187 | target => { fixed => 1 }, |
188 | portal => { fixed => 1 }, | |
189 | login => { fixed => 1 }, | |
190 | password => { fixed => 1 }, | |
191 | pool => { fixed => 1 }, | |
192 | blocksize => { fixed => 1 }, | |
0f94c1c9 | 193 | ssl => { optional => 1 }, |
40cd7d27 AD |
194 | content => { optional => 1 }, |
195 | }; | |
120ca009 | 196 | |
40cd7d27 AD |
197 | } |
198 | ||
199 | # Storage implementation | |
200 | ||
201 | sub parse_volname { | |
202 | my ($class, $volname) = @_; | |
203 | ||
40cd7d27 AD |
204 | if ($volname =~ m/^(vm-(\d+)-\S+)$/) { |
205 | return ('images', $1, $2); | |
206 | } | |
207 | ||
208 | return('images',$volname,''); | |
209 | #die "unable to parse lvm volume name '$volname'\n"; | |
210 | } | |
211 | ||
212 | sub path { | |
213 | my ($class, $scfg, $volname) = @_; | |
214 | ||
215 | my ($vtype, $name, $vmid) = $class->parse_volname($volname); | |
216 | ||
217 | my $target = $scfg->{target}; | |
218 | my $portal = $scfg->{portal}; | |
219 | ||
220 | my $map = nexenta_list_lun_mapping_entries($name,$scfg); | |
221 | die "could not find lun number" if !$map; | |
222 | my $lun = @$map[0]->{lun}; | |
034e4411 AD |
223 | $lun =~ m/^(\d+)$/ or die "lun is not OK\n"; |
224 | $lun = $1; | |
40cd7d27 AD |
225 | my $path = "iscsi://$portal/$target/$lun"; |
226 | ||
227 | return ($path, $vmid, $vtype); | |
228 | } | |
229 | ||
230 | ||
231 | sub alloc_image { | |
232 | my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_; | |
233 | ||
234 | die "unsupported format '$fmt'" if $fmt ne 'raw'; | |
235 | ||
2677f623 | 236 | die "illegal name '$name' - sould be 'vm-$vmid-*'\n" |
40cd7d27 AD |
237 | if $name && $name !~ m/^vm-$vmid-/; |
238 | ||
40cd7d27 AD |
239 | my $nexentapool = $scfg->{'pool'}; |
240 | ||
241 | if (!$name) { | |
40cd7d27 AD |
242 | my $volumes = nexenta_list_zvol($scfg); |
243 | die "unable de get zvol list" if !$volumes; | |
244 | ||
245 | for (my $i = 1; $i < 100; $i++) { | |
246 | ||
247 | my $tn = "vm-$vmid-disk-$i"; | |
248 | if (!defined ($volumes->{$nexentapool}->{$tn})) { | |
249 | $name = $tn; | |
250 | last; | |
251 | } | |
252 | } | |
253 | } | |
254 | ||
255 | die "unable to allocate an image name for VM $vmid in storage '$storeid'\n" | |
256 | if !$name; | |
257 | ||
120ca009 | 258 | eval { nexenta_create_zvol($name, $size, $scfg); }; |
40cd7d27 | 259 | sleep 1; |
120ca009 | 260 | eval { nexenta_create_lu($name, $scfg); }; |
40cd7d27 | 261 | sleep 1; |
120ca009 | 262 | nexenta_add_lun_mapping_entry($name, $scfg); |
40cd7d27 AD |
263 | |
264 | return $name; | |
265 | } | |
266 | ||
267 | sub free_image { | |
268 | my ($class, $storeid, $scfg, $volname) = @_; | |
269 | ||
270 | my ($vtype, $name, $vmid) = $class->parse_volname($volname); | |
271 | ||
120ca009 | 272 | eval { nexenta_delete_lu($name, $scfg); }; |
40cd7d27 | 273 | sleep 5; |
120ca009 | 274 | nexenta_delete_zvol($name, $scfg); |
40cd7d27 AD |
275 | |
276 | ||
277 | return undef; | |
278 | } | |
279 | ||
280 | sub list_images { | |
281 | my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_; | |
282 | ||
283 | $cache->{nexenta} = nexenta_list_zvol($scfg) if !$cache->{nexenta}; | |
284 | my $nexentapool = $scfg->{pool}; | |
285 | my $res = []; | |
286 | if (my $dat = $cache->{nexenta}->{$nexentapool}) { | |
287 | foreach my $image (keys %$dat) { | |
288 | ||
289 | my $volname = $dat->{$image}->{name}; | |
290 | ||
291 | my $volid = "$storeid:$volname"; | |
292 | ||
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 status { | |
314 | my ($class, $storeid, $scfg, $cache) = @_; | |
315 | ||
316 | my $total = 0; | |
317 | my $free = 0; | |
318 | my $used = 0; | |
319 | my $active = 1; | |
320 | return ($total,$free,$used,$active); | |
321 | ||
322 | return undef; | |
323 | } | |
324 | ||
325 | sub activate_storage { | |
326 | my ($class, $storeid, $scfg, $cache) = @_; | |
327 | return 1; | |
328 | } | |
329 | ||
330 | sub deactivate_storage { | |
331 | my ($class, $storeid, $scfg, $cache) = @_; | |
332 | return 1; | |
333 | } | |
334 | ||
335 | sub activate_volume { | |
336 | my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_; | |
337 | return 1; | |
338 | } | |
339 | ||
340 | sub deactivate_volume { | |
341 | my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_; | |
342 | return 1; | |
343 | } | |
344 | ||
c3013a8b AD |
345 | sub volume_size_info { |
346 | my ($class, $scfg, $storeid, $volname, $timeout) = @_; | |
347 | ||
60301ee0 AD |
348 | my $json = '{"method": "get_child_prop","object" : "zvol","params": ["'.$scfg->{pool}.'/'.$volname.'", "size_bytes"]}'; |
349 | my $size = nexenta_request($scfg, $json); | |
350 | return $size; | |
c3013a8b AD |
351 | } |
352 | ||
69971d8b AD |
353 | sub volume_resize { |
354 | my ($class, $scfg, $storeid, $volname, $size, $running) = @_; | |
355 | ||
356 | my $json = '{"method": "set_child_prop","object" : "zvol","params": ["'.$scfg->{pool}.'/'.$volname.'", "volsize", "'.($size/1024).'KB"]}'; | |
120ca009 | 357 | nexenta_request($scfg, $json); |
69971d8b AD |
358 | return undef; |
359 | } | |
360 | ||
5223286c AD |
361 | sub volume_snapshot { |
362 | my ($class, $scfg, $storeid, $volname, $snap, $running) = @_; | |
363 | ||
364 | my $json = '{"method": "create_snapshot","object" : "zvol","params": ["'.$scfg->{pool}.'/'.$volname.'", "'.$snap.'", ""]}'; | |
365 | nexenta_request($scfg, $json); | |
366 | return undef; | |
367 | } | |
368 | ||
4c6c6423 AD |
369 | sub volume_snapshot_rollback { |
370 | my ($class, $scfg, $storeid, $volname, $snap) = @_; | |
371 | ||
372 | eval { nexenta_delete_lu($volname, $scfg); }; | |
373 | ||
374 | my $json = '{"method": "rollback","object" : "snapshot","params": ["'.$scfg->{pool}.'/'.$volname.'@'.$snap.'", ""]}'; | |
375 | nexenta_request($scfg, $json); | |
376 | ||
377 | eval { nexenta_create_lu($volname, $scfg); }; | |
378 | ||
379 | nexenta_add_lun_mapping_entry($volname, $scfg); | |
380 | } | |
381 | ||
5d8f5e22 AD |
382 | sub volume_snapshot_delete { |
383 | my ($class, $scfg, $storeid, $volname, $snap, $running) = @_; | |
384 | ||
385 | my $json = '{"method": "destroy","object" : "snapshot","params": ["'.$scfg->{pool}.'/'.$volname.'@'.$snap.'"]}'; | |
386 | nexenta_request($scfg, $json); | |
387 | } | |
388 | ||
40cd7d27 | 389 | 1; |