]>
Commit | Line | Data |
---|---|---|
c669f42d DM |
1 | package PVE::CLI::pvesm; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
9559a62a | 6 | use POSIX qw(O_RDONLY O_WRONLY O_CREAT O_TRUNC); |
c669f42d DM |
7 | use Fcntl ':flock'; |
8 | use File::Path; | |
9 | ||
10 | use PVE::SafeSyslog; | |
11 | use PVE::Cluster; | |
12 | use PVE::INotify; | |
13 | use PVE::RPCEnvironment; | |
14 | use PVE::Storage; | |
15 | use PVE::API2::Storage::Config; | |
16 | use PVE::API2::Storage::Content; | |
17 | use PVE::API2::Storage::Status; | |
c669f42d | 18 | use PVE::JSONSchema qw(get_standard_option); |
c26f3a71 | 19 | use PVE::PTY; |
c669f42d DM |
20 | |
21 | use PVE::CLIHandler; | |
22 | ||
23 | use base qw(PVE::CLIHandler); | |
24 | ||
9559a62a | 25 | my $KNOWN_EXPORT_FORMATS = ['raw+size', 'tar+size', 'qcow2+size', 'vmdk+size', 'zfs']; |
47f37b53 | 26 | |
c669f42d DM |
27 | my $nodename = PVE::INotify::nodename(); |
28 | ||
42f2c57d DC |
29 | sub param_mapping { |
30 | my ($name) = @_; | |
31 | ||
32 | my $password_map = PVE::CLIHandler::get_standard_mapping('pve-password', { | |
33 | func => sub { | |
34 | my ($value) = @_; | |
35 | return $value if $value; | |
36 | return PVE::PTY::read_password("Enter Password: "); | |
37 | }, | |
38 | }); | |
39 | my $mapping = { | |
40 | 'cifsscan' => [ $password_map ], | |
41 | 'create' => [ $password_map ], | |
42 | }; | |
43 | return $mapping->{$name}; | |
c26f3a71 WL |
44 | } |
45 | ||
f984732e DM |
46 | sub setup_environment { |
47 | PVE::RPCEnvironment->setup_default_cli_env(); | |
48 | } | |
49 | ||
5f184292 FE |
50 | __PACKAGE__->register_method ({ |
51 | name => 'apiinfo', | |
52 | path => 'apiinfo', | |
53 | method => 'GET', | |
54 | description => "Returns APIVER and APIAGE.", | |
55 | parameters => { | |
56 | additionalProperties => 0, | |
57 | properties => {}, | |
58 | }, | |
59 | returns => { | |
60 | type => 'object', | |
61 | properties => { | |
62 | apiver => { type => 'integer' }, | |
63 | apiage => { type => 'integer' }, | |
64 | }, | |
65 | }, | |
66 | code => sub { | |
67 | return { | |
68 | apiver => PVE::Storage::APIVER, | |
69 | apiage => PVE::Storage::APIAGE, | |
70 | }; | |
71 | } | |
72 | }); | |
73 | ||
c669f42d DM |
74 | __PACKAGE__->register_method ({ |
75 | name => 'path', | |
76 | path => 'path', | |
77 | method => 'GET', | |
78 | description => "Get filesystem path for specified volume", | |
79 | parameters => { | |
80 | additionalProperties => 0, | |
81 | properties => { | |
82 | volume => { | |
83 | description => "Volume identifier", | |
84 | type => 'string', format => 'pve-volume-id', | |
f3bd890d | 85 | completion => \&PVE::Storage::complete_volume, |
c669f42d DM |
86 | }, |
87 | }, | |
88 | }, | |
89 | returns => { type => 'null' }, | |
90 | ||
91 | code => sub { | |
92 | my ($param) = @_; | |
93 | ||
94 | my $cfg = PVE::Storage::config(); | |
95 | ||
96 | my $path = PVE::Storage::path ($cfg, $param->{volume}); | |
97 | ||
98 | print "$path\n"; | |
99 | ||
100 | return undef; | |
101 | ||
102 | }}); | |
103 | ||
fa017b96 FG |
104 | __PACKAGE__->register_method ({ |
105 | name => 'extractconfig', | |
106 | path => 'extractconfig', | |
107 | method => 'GET', | |
108 | description => "Extract configuration from vzdump backup archive.", | |
109 | permissions => { | |
110 | description => "The user needs 'VM.Backup' permissions on the backed up guest ID, and 'Datastore.AllocateSpace' on the backup storage.", | |
111 | user => 'all', | |
112 | }, | |
113 | protected => 1, | |
114 | parameters => { | |
115 | additionalProperties => 0, | |
116 | properties => { | |
117 | volume => { | |
118 | description => "Volume identifier", | |
119 | type => 'string', | |
120 | completion => \&PVE::Storage::complete_volume, | |
121 | }, | |
122 | }, | |
123 | }, | |
124 | returns => { type => 'null' }, | |
125 | code => sub { | |
126 | my ($param) = @_; | |
127 | my $volume = $param->{volume}; | |
128 | ||
129 | my $rpcenv = PVE::RPCEnvironment::get(); | |
130 | my $authuser = $rpcenv->get_user(); | |
131 | ||
132 | my $storage_cfg = PVE::Storage::config(); | |
04a13668 | 133 | PVE::Storage::check_volume_access($rpcenv, $authuser, $storage_cfg, undef, $volume); |
fa017b96 FG |
134 | |
135 | my $config_raw = PVE::Storage::extract_vzdump_config($storage_cfg, $volume); | |
136 | ||
137 | print "$config_raw\n"; | |
138 | return; | |
139 | }}); | |
140 | ||
c669f42d DM |
141 | my $print_content = sub { |
142 | my ($list) = @_; | |
143 | ||
61c261e7 | 144 | my ($maxlenname, $maxsize) = (0, 0); |
c669f42d | 145 | foreach my $info (@$list) { |
c669f42d DM |
146 | my $volid = $info->{volid}; |
147 | my $sidlen = length ($volid); | |
148 | $maxlenname = $sidlen if $sidlen > $maxlenname; | |
61c261e7 | 149 | $maxsize = $info->{size} if ($info->{size} // 0) > $maxsize; |
c669f42d | 150 | } |
61c261e7 TL |
151 | my $sizemaxdigits = length($maxsize); |
152 | ||
153 | my $basefmt = "%-${maxlenname}s %-7s %-9s %${sizemaxdigits}s"; | |
154 | printf "$basefmt %s\n", "Volid", "Format", "Type", "Size", "VMID"; | |
c669f42d DM |
155 | |
156 | foreach my $info (@$list) { | |
157 | next if !$info->{vmid}; | |
158 | my $volid = $info->{volid}; | |
159 | ||
61c261e7 | 160 | printf "$basefmt %d\n", $volid, $info->{format}, $info->{content}, $info->{size}, $info->{vmid}; |
c669f42d DM |
161 | } |
162 | ||
163 | foreach my $info (sort { $a->{format} cmp $b->{format} } @$list) { | |
164 | next if $info->{vmid}; | |
165 | my $volid = $info->{volid}; | |
166 | ||
61c261e7 | 167 | printf "$basefmt\n", $volid, $info->{format}, $info->{content}, $info->{size}; |
c669f42d DM |
168 | } |
169 | }; | |
170 | ||
171 | my $print_status = sub { | |
172 | my $res = shift; | |
173 | ||
174 | my $maxlen = 0; | |
175 | foreach my $res (@$res) { | |
176 | my $storeid = $res->{storage}; | |
177 | $maxlen = length ($storeid) if length ($storeid) > $maxlen; | |
178 | } | |
179 | $maxlen+=1; | |
180 | ||
d40e27de TL |
181 | printf "%-${maxlen}s %10s %10s %15s %15s %15s %8s\n", 'Name', 'Type', |
182 | 'Status', 'Total', 'Used', 'Available', '%'; | |
183 | ||
c669f42d DM |
184 | foreach my $res (sort { $a->{storage} cmp $b->{storage} } @$res) { |
185 | my $storeid = $res->{storage}; | |
186 | ||
d40e27de TL |
187 | my $active = $res->{active} ? 'active' : 'inactive'; |
188 | my ($per, $per_fmt) = (0, '% 7.2f%%'); | |
189 | $per = ($res->{used}*100)/$res->{total} if $res->{total} > 0; | |
190 | ||
191 | if (!$res->{enabled}) { | |
04301013 | 192 | $per = 'N/A'; |
d40e27de TL |
193 | $per_fmt = '% 8s'; |
194 | $active = 'disabled'; | |
195 | } | |
c669f42d | 196 | |
d40e27de TL |
197 | printf "%-${maxlen}s %10s %10s %15d %15d %15d $per_fmt\n", $storeid, |
198 | $res->{type}, $active, $res->{total}/1024, $res->{used}/1024, | |
199 | $res->{avail}/1024, $per; | |
c669f42d DM |
200 | } |
201 | }; | |
202 | ||
47f37b53 WB |
203 | __PACKAGE__->register_method ({ |
204 | name => 'export', | |
205 | path => 'export', | |
206 | method => 'GET', | |
a43a796c | 207 | description => "Used internally to export a volume.", |
47f37b53 WB |
208 | protected => 1, |
209 | parameters => { | |
210 | additionalProperties => 0, | |
211 | properties => { | |
212 | volume => { | |
213 | description => "Volume identifier", | |
214 | type => 'string', | |
215 | completion => \&PVE::Storage::complete_volume, | |
216 | }, | |
217 | format => { | |
218 | description => "Export stream format", | |
219 | type => 'string', | |
220 | enum => $KNOWN_EXPORT_FORMATS, | |
221 | }, | |
222 | filename => { | |
223 | description => "Destination file name", | |
224 | type => 'string', | |
225 | }, | |
226 | base => { | |
227 | description => "Snapshot to start an incremental stream from", | |
228 | type => 'string', | |
229 | pattern => qr/[a-z0-9_\-]{1,40}/, | |
230 | maxLength => 40, | |
231 | optional => 1, | |
232 | }, | |
233 | snapshot => { | |
234 | description => "Snapshot to export", | |
235 | type => 'string', | |
236 | pattern => qr/[a-z0-9_\-]{1,40}/, | |
237 | maxLength => 40, | |
238 | optional => 1, | |
239 | }, | |
240 | 'with-snapshots' => { | |
241 | description => | |
242 | "Whether to include intermediate snapshots in the stream", | |
243 | type => 'boolean', | |
244 | optional => 1, | |
245 | default => 0, | |
246 | }, | |
247 | }, | |
248 | }, | |
249 | returns => { type => 'null' }, | |
250 | code => sub { | |
251 | my ($param) = @_; | |
252 | ||
253 | my $filename = $param->{filename}; | |
254 | ||
255 | my $outfh; | |
256 | if ($filename eq '-') { | |
257 | $outfh = \*STDOUT; | |
258 | } else { | |
9559a62a | 259 | sysopen($outfh, $filename, O_CREAT|O_WRONLY|O_TRUNC) |
47f37b53 WB |
260 | or die "open($filename): $!\n"; |
261 | } | |
262 | ||
263 | eval { | |
264 | my $cfg = PVE::Storage::config(); | |
265 | PVE::Storage::volume_export($cfg, $outfh, $param->{volume}, $param->{format}, | |
266 | $param->{snapshot}, $param->{base}, $param->{'with-snapshots'}); | |
267 | }; | |
268 | my $err = $@; | |
269 | if ($filename ne '-') { | |
270 | close($outfh); | |
271 | unlink($filename) if $err; | |
272 | } | |
273 | die $err if $err; | |
274 | return; | |
275 | } | |
276 | }); | |
277 | ||
278 | __PACKAGE__->register_method ({ | |
279 | name => 'import', | |
280 | path => 'import', | |
281 | method => 'PUT', | |
a43a796c | 282 | description => "Used internally to import a volume.", |
47f37b53 WB |
283 | protected => 1, |
284 | parameters => { | |
285 | additionalProperties => 0, | |
286 | properties => { | |
287 | volume => { | |
288 | description => "Volume identifier", | |
289 | type => 'string', | |
290 | completion => \&PVE::Storage::complete_volume, | |
291 | }, | |
292 | format => { | |
293 | description => "Import stream format", | |
294 | type => 'string', | |
295 | enum => $KNOWN_EXPORT_FORMATS, | |
296 | }, | |
297 | filename => { | |
228e5be9 TL |
298 | description => "Source file name. For '-' stdin is used, the " . |
299 | "tcp://<IP-or-CIDR> format allows to use a TCP connection as input. " . | |
300 | "Else, the file is treated as common file.", | |
47f37b53 WB |
301 | type => 'string', |
302 | }, | |
303 | base => { | |
304 | description => "Base snapshot of an incremental stream", | |
305 | type => 'string', | |
306 | pattern => qr/[a-z0-9_\-]{1,40}/, | |
307 | maxLength => 40, | |
308 | optional => 1, | |
309 | }, | |
310 | 'with-snapshots' => { | |
311 | description => | |
312 | "Whether the stream includes intermediate snapshots", | |
313 | type => 'boolean', | |
314 | optional => 1, | |
315 | default => 0, | |
316 | }, | |
52595938 WB |
317 | 'delete-snapshot' => { |
318 | description => "A snapshot to delete on success", | |
319 | type => 'string', | |
320 | pattern => qr/[a-z0-9_\-]{1,80}/, | |
321 | maxLength => 80, | |
322 | optional => 1, | |
323 | }, | |
a97d3ee4 FE |
324 | 'allow-rename' => { |
325 | description => "Choose a new volume ID if the requested " . | |
326 | "volume ID already exists, instead of throwing an error.", | |
327 | type => 'boolean', | |
328 | optional => 1, | |
329 | default => 0, | |
330 | }, | |
47f37b53 WB |
331 | }, |
332 | }, | |
a97d3ee4 | 333 | returns => { type => 'string' }, |
47f37b53 WB |
334 | code => sub { |
335 | my ($param) = @_; | |
336 | ||
337 | my $filename = $param->{filename}; | |
338 | ||
339 | my $infh; | |
340 | if ($filename eq '-') { | |
341 | $infh = \*STDIN; | |
228e5be9 TL |
342 | } elsif ($filename =~ m!^tcp://(([^/]+)(/\d+)?)$!) { |
343 | my ($cidr, $ip, $subnet) = ($1, $2, $3); | |
344 | if ($subnet) { # got real CIDR notation, not just IP | |
a2aae38c | 345 | my $ips = PVE::Network::get_local_ip_from_cidr($cidr); |
ed2df8e3 TL |
346 | die "Unable to get any local IP address in network '$cidr'\n" |
347 | if scalar(@$ips) < 1; | |
348 | die "Got multiple local IP address in network '$cidr'\n" | |
349 | if scalar(@$ips) > 1; | |
350 | ||
351 | $ip = $ips->[0]; | |
228e5be9 TL |
352 | } |
353 | my $family = PVE::Tools::get_host_address_family($ip); | |
354 | my $port = PVE::Tools::next_migrate_port($family, $ip); | |
355 | ||
356 | my $sock_params = { | |
357 | Listen => 1, | |
358 | ReuseAddr => 1, | |
359 | Proto => &Socket::IPPROTO_TCP, | |
360 | GetAddrInfoFlags => 0, | |
361 | LocalAddr => $ip, | |
362 | LocalPort => $port, | |
363 | }; | |
364 | my $socket = IO::Socket::IP->new(%$sock_params) | |
365 | or die "failed to open socket: $!\n"; | |
366 | ||
367 | print "$ip\n$port\n"; # tell remote where to connect | |
368 | *STDOUT->flush(); | |
369 | ||
370 | my $prev_alarm = alarm 0; | |
371 | local $SIG{ALRM} = sub { die "timed out waiting for client\n" }; | |
372 | alarm 30; | |
373 | my $client = $socket->accept; # Wait for a client | |
374 | alarm $prev_alarm; | |
375 | close($socket); | |
376 | ||
377 | $infh = \*$client; | |
47f37b53 | 378 | } else { |
9559a62a | 379 | sysopen($infh, $filename, O_RDONLY) |
47f37b53 WB |
380 | or die "open($filename): $!\n"; |
381 | } | |
382 | ||
383 | my $cfg = PVE::Storage::config(); | |
52595938 WB |
384 | my $volume = $param->{volume}; |
385 | my $delete = $param->{'delete-snapshot'}; | |
a97d3ee4 FE |
386 | my $imported_volid = PVE::Storage::volume_import($cfg, $infh, $volume, $param->{format}, |
387 | $param->{base}, $param->{'with-snapshots'}, $param->{'allow-rename'}); | |
388 | PVE::Storage::volume_snapshot_delete($cfg, $imported_volid, $delete) | |
52595938 | 389 | if defined($delete); |
a97d3ee4 | 390 | return $imported_volid; |
47f37b53 WB |
391 | } |
392 | }); | |
393 | ||
7963ba74 DC |
394 | __PACKAGE__->register_method ({ |
395 | name => 'nfsscan', | |
396 | path => 'nfs', | |
397 | method => 'GET', | |
398 | description => "Scan remote NFS server.", | |
399 | protected => 1, | |
400 | proxyto => "node", | |
401 | permissions => { | |
402 | check => ['perm', '/storage', ['Datastore.Allocate']], | |
403 | }, | |
404 | parameters => { | |
405 | additionalProperties => 0, | |
406 | properties => { | |
407 | node => get_standard_option('pve-node'), | |
408 | server => { | |
409 | description => "The server address (name or IP).", | |
410 | type => 'string', format => 'pve-storage-server', | |
411 | }, | |
412 | }, | |
413 | }, | |
414 | returns => { | |
415 | type => 'array', | |
416 | items => { | |
417 | type => "object", | |
418 | properties => { | |
419 | path => { | |
420 | description => "The exported path.", | |
421 | type => 'string', | |
422 | }, | |
423 | options => { | |
424 | description => "NFS export options.", | |
425 | type => 'string', | |
426 | }, | |
427 | }, | |
428 | }, | |
429 | }, | |
430 | code => sub { | |
431 | my ($param) = @_; | |
432 | ||
433 | my $server = $param->{server}; | |
434 | my $res = PVE::Storage::scan_nfs($server); | |
435 | ||
436 | my $data = []; | |
437 | foreach my $k (keys %$res) { | |
438 | push @$data, { path => $k, options => $res->{$k} }; | |
439 | } | |
440 | return $data; | |
441 | }}); | |
442 | ||
443 | __PACKAGE__->register_method ({ | |
444 | name => 'cifsscan', | |
445 | path => 'cifs', | |
446 | method => 'GET', | |
447 | description => "Scan remote CIFS server.", | |
448 | protected => 1, | |
449 | proxyto => "node", | |
450 | permissions => { | |
451 | check => ['perm', '/storage', ['Datastore.Allocate']], | |
452 | }, | |
453 | parameters => { | |
454 | additionalProperties => 0, | |
455 | properties => { | |
456 | node => get_standard_option('pve-node'), | |
457 | server => { | |
458 | description => "The server address (name or IP).", | |
459 | type => 'string', format => 'pve-storage-server', | |
460 | }, | |
461 | username => { | |
462 | description => "User name.", | |
463 | type => 'string', | |
464 | optional => 1, | |
465 | }, | |
466 | password => { | |
467 | description => "User password.", | |
468 | type => 'string', | |
469 | optional => 1, | |
470 | }, | |
471 | domain => { | |
472 | description => "SMB domain (Workgroup).", | |
473 | type => 'string', | |
474 | optional => 1, | |
475 | }, | |
476 | }, | |
477 | }, | |
478 | returns => { | |
479 | type => 'array', | |
480 | items => { | |
481 | type => "object", | |
482 | properties => { | |
483 | share => { | |
484 | description => "The cifs share name.", | |
485 | type => 'string', | |
486 | }, | |
487 | description => { | |
488 | description => "Descriptive text from server.", | |
489 | type => 'string', | |
490 | }, | |
491 | }, | |
492 | }, | |
493 | }, | |
494 | code => sub { | |
495 | my ($param) = @_; | |
496 | ||
497 | my $server = $param->{server}; | |
498 | ||
499 | my $username = $param->{username}; | |
500 | my $password = $param->{password}; | |
501 | my $domain = $param->{domain}; | |
502 | ||
503 | my $res = PVE::Storage::scan_cifs($server, $username, $password, $domain); | |
504 | ||
505 | my $data = []; | |
506 | foreach my $k (keys %$res) { | |
7963ba74 DC |
507 | push @$data, { share => $k, description => $res->{$k} }; |
508 | } | |
509 | ||
510 | return $data; | |
511 | }}); | |
512 | ||
513 | # Note: GlusterFS currently does not have an equivalent of showmount. | |
514 | # As workaround, we simply use nfs showmount. | |
515 | # see http://www.gluster.org/category/volumes/ | |
516 | ||
517 | __PACKAGE__->register_method ({ | |
518 | name => 'glusterfsscan', | |
519 | path => 'glusterfs', | |
520 | method => 'GET', | |
521 | description => "Scan remote GlusterFS server.", | |
522 | protected => 1, | |
523 | proxyto => "node", | |
524 | permissions => { | |
525 | check => ['perm', '/storage', ['Datastore.Allocate']], | |
526 | }, | |
527 | parameters => { | |
528 | additionalProperties => 0, | |
529 | properties => { | |
530 | node => get_standard_option('pve-node'), | |
531 | server => { | |
532 | description => "The server address (name or IP).", | |
533 | type => 'string', format => 'pve-storage-server', | |
534 | }, | |
535 | }, | |
536 | }, | |
537 | returns => { | |
538 | type => 'array', | |
539 | items => { | |
540 | type => "object", | |
2e2e11db | 541 | properties => { |
7963ba74 DC |
542 | volname => { |
543 | description => "The volume name.", | |
544 | type => 'string', | |
545 | }, | |
546 | }, | |
547 | }, | |
548 | }, | |
549 | code => sub { | |
550 | my ($param) = @_; | |
551 | ||
552 | my $server = $param->{server}; | |
553 | my $res = PVE::Storage::scan_nfs($server); | |
554 | ||
555 | my $data = []; | |
556 | foreach my $path (keys %$res) { | |
557 | if ($path =~ m!^/([^\s/]+)$!) { | |
558 | push @$data, { volname => $1 }; | |
559 | } | |
560 | } | |
561 | return $data; | |
562 | }}); | |
563 | ||
564 | __PACKAGE__->register_method ({ | |
565 | name => 'iscsiscan', | |
566 | path => 'iscsi', | |
567 | method => 'GET', | |
568 | description => "Scan remote iSCSI server.", | |
569 | protected => 1, | |
570 | proxyto => "node", | |
571 | permissions => { | |
572 | check => ['perm', '/storage', ['Datastore.Allocate']], | |
573 | }, | |
574 | parameters => { | |
575 | additionalProperties => 0, | |
576 | properties => { | |
577 | node => get_standard_option('pve-node'), | |
578 | portal => { | |
579 | description => "The iSCSI portal (IP or DNS name with optional port).", | |
580 | type => 'string', format => 'pve-storage-portal-dns', | |
581 | }, | |
582 | }, | |
583 | }, | |
584 | returns => { | |
585 | type => 'array', | |
586 | items => { | |
587 | type => "object", | |
588 | properties => { | |
589 | target => { | |
590 | description => "The iSCSI target name.", | |
591 | type => 'string', | |
592 | }, | |
593 | portal => { | |
594 | description => "The iSCSI portal name.", | |
595 | type => 'string', | |
596 | }, | |
597 | }, | |
598 | }, | |
599 | }, | |
600 | code => sub { | |
601 | my ($param) = @_; | |
602 | ||
603 | my $res = PVE::Storage::scan_iscsi($param->{portal}); | |
604 | ||
605 | my $data = []; | |
606 | foreach my $k (keys %$res) { | |
607 | push @$data, { target => $k, portal => join(',', @{$res->{$k}}) }; | |
608 | } | |
609 | ||
610 | return $data; | |
611 | }}); | |
612 | ||
613 | __PACKAGE__->register_method ({ | |
614 | name => 'lvmscan', | |
615 | path => 'lvm', | |
616 | method => 'GET', | |
617 | description => "List local LVM volume groups.", | |
618 | protected => 1, | |
619 | proxyto => "node", | |
620 | permissions => { | |
621 | check => ['perm', '/storage', ['Datastore.Allocate']], | |
622 | }, | |
623 | parameters => { | |
624 | additionalProperties => 0, | |
625 | properties => { | |
626 | node => get_standard_option('pve-node'), | |
627 | }, | |
628 | }, | |
629 | returns => { | |
630 | type => 'array', | |
631 | items => { | |
632 | type => "object", | |
633 | properties => { | |
634 | vg => { | |
635 | description => "The LVM logical volume group name.", | |
636 | type => 'string', | |
637 | }, | |
638 | }, | |
639 | }, | |
640 | }, | |
641 | code => sub { | |
642 | my ($param) = @_; | |
643 | ||
644 | my $res = PVE::Storage::LVMPlugin::lvm_vgs(); | |
645 | return PVE::RESTHandler::hash_to_array($res, 'vg'); | |
646 | }}); | |
647 | ||
648 | __PACKAGE__->register_method ({ | |
649 | name => 'lvmthinscan', | |
650 | path => 'lvmthin', | |
651 | method => 'GET', | |
652 | description => "List local LVM Thin Pools.", | |
653 | protected => 1, | |
654 | proxyto => "node", | |
655 | permissions => { | |
656 | check => ['perm', '/storage', ['Datastore.Allocate']], | |
657 | }, | |
658 | parameters => { | |
659 | additionalProperties => 0, | |
660 | properties => { | |
661 | node => get_standard_option('pve-node'), | |
662 | vg => { | |
663 | type => 'string', | |
664 | pattern => '[a-zA-Z0-9\.\+\_][a-zA-Z0-9\.\+\_\-]+', # see lvm(8) manpage | |
665 | maxLength => 100, | |
666 | }, | |
667 | }, | |
668 | }, | |
669 | returns => { | |
670 | type => 'array', | |
671 | items => { | |
672 | type => "object", | |
673 | properties => { | |
674 | lv => { | |
675 | description => "The LVM Thin Pool name (LVM logical volume).", | |
676 | type => 'string', | |
677 | }, | |
678 | }, | |
679 | }, | |
680 | }, | |
681 | code => sub { | |
682 | my ($param) = @_; | |
683 | ||
684 | return PVE::Storage::LvmThinPlugin::list_thinpools($param->{vg}); | |
685 | }}); | |
686 | ||
687 | __PACKAGE__->register_method ({ | |
688 | name => 'zfsscan', | |
689 | path => 'zfs', | |
690 | method => 'GET', | |
691 | description => "Scan zfs pool list on local node.", | |
692 | protected => 1, | |
693 | proxyto => "node", | |
694 | permissions => { | |
695 | check => ['perm', '/storage', ['Datastore.Allocate']], | |
696 | }, | |
697 | parameters => { | |
698 | additionalProperties => 0, | |
699 | properties => { | |
700 | node => get_standard_option('pve-node'), | |
701 | }, | |
702 | }, | |
703 | returns => { | |
704 | type => 'array', | |
705 | items => { | |
706 | type => "object", | |
707 | properties => { | |
708 | pool => { | |
709 | description => "ZFS pool name.", | |
710 | type => 'string', | |
711 | }, | |
712 | }, | |
713 | }, | |
714 | }, | |
715 | code => sub { | |
716 | my ($param) = @_; | |
717 | ||
718 | return PVE::Storage::scan_zfs(); | |
719 | }}); | |
720 | ||
c669f42d DM |
721 | our $cmddef = { |
722 | add => [ "PVE::API2::Storage::Config", 'create', ['type', 'storage'] ], | |
723 | set => [ "PVE::API2::Storage::Config", 'update', ['storage'] ], | |
724 | remove => [ "PVE::API2::Storage::Config", 'delete', ['storage'] ], | |
725 | status => [ "PVE::API2::Storage::Status", 'index', [], | |
726 | { node => $nodename }, $print_status ], | |
727 | list => [ "PVE::API2::Storage::Content", 'index', ['storage'], | |
728 | { node => $nodename }, $print_content ], | |
729 | alloc => [ "PVE::API2::Storage::Content", 'create', ['storage', 'vmid', 'filename', 'size'], | |
730 | { node => $nodename }, sub { | |
731 | my $volid = shift; | |
e967e0ef | 732 | print "successfully created '$volid'\n"; |
c669f42d DM |
733 | }], |
734 | free => [ "PVE::API2::Storage::Content", 'delete', ['volume'], | |
735 | { node => $nodename } ], | |
957321a8 TL |
736 | scan => { |
737 | nfs => [ __PACKAGE__, 'nfsscan', ['server'], { node => $nodename }, sub { | |
738 | my $res = shift; | |
739 | ||
740 | my $maxlen = 0; | |
741 | foreach my $rec (@$res) { | |
742 | my $len = length ($rec->{path}); | |
743 | $maxlen = $len if $len > $maxlen; | |
744 | } | |
745 | foreach my $rec (@$res) { | |
746 | printf "%-${maxlen}s %s\n", $rec->{path}, $rec->{options}; | |
747 | } | |
748 | }], | |
749 | cifs => [ __PACKAGE__, 'cifsscan', ['server'], { node => $nodename }, sub { | |
750 | my $res = shift; | |
751 | ||
752 | my $maxlen = 0; | |
753 | foreach my $rec (@$res) { | |
754 | my $len = length ($rec->{share}); | |
755 | $maxlen = $len if $len > $maxlen; | |
756 | } | |
757 | foreach my $rec (@$res) { | |
758 | printf "%-${maxlen}s %s\n", $rec->{share}, $rec->{description}; | |
759 | } | |
760 | }], | |
761 | glusterfs => [ __PACKAGE__, 'glusterfsscan', ['server'], { node => $nodename }, sub { | |
762 | my $res = shift; | |
763 | ||
764 | foreach my $rec (@$res) { | |
765 | printf "%s\n", $rec->{volname}; | |
766 | } | |
767 | }], | |
768 | iscsi => [ __PACKAGE__, 'iscsiscan', ['portal'], { node => $nodename }, sub { | |
769 | my $res = shift; | |
770 | ||
771 | my $maxlen = 0; | |
772 | foreach my $rec (@$res) { | |
773 | my $len = length ($rec->{target}); | |
774 | $maxlen = $len if $len > $maxlen; | |
775 | } | |
776 | foreach my $rec (@$res) { | |
777 | printf "%-${maxlen}s %s\n", $rec->{target}, $rec->{portal}; | |
778 | } | |
779 | }], | |
780 | lvm => [ __PACKAGE__, 'lvmscan', [], { node => $nodename }, sub { | |
781 | my $res = shift; | |
782 | foreach my $rec (@$res) { | |
783 | printf "$rec->{vg}\n"; | |
784 | } | |
785 | }], | |
786 | lvmthin => [ __PACKAGE__, 'lvmthinscan', ['vg'], { node => $nodename }, sub { | |
787 | my $res = shift; | |
788 | foreach my $rec (@$res) { | |
789 | printf "$rec->{lv}\n"; | |
790 | } | |
791 | }], | |
792 | zfs => [ __PACKAGE__, 'zfsscan', [], { node => $nodename }, sub { | |
793 | my $res = shift; | |
794 | ||
795 | foreach my $rec (@$res) { | |
796 | printf "$rec->{pool}\n"; | |
797 | } | |
798 | }], | |
799 | }, | |
800 | nfsscan => { alias => 'scan nfs' }, | |
801 | cifsscan => { alias => 'scan cifs' }, | |
802 | glusterfsscan => { alias => 'scan glusterfs' }, | |
803 | iscsiscan => { alias => 'scan iscsi' }, | |
804 | lvmscan => { alias => 'scan lvm' }, | |
805 | lvmthinscan => { alias => 'scan lvmthin' }, | |
806 | zfsscan => { alias => 'scan zfs' }, | |
c669f42d | 807 | path => [ __PACKAGE__, 'path', ['volume']], |
fa017b96 | 808 | extractconfig => [__PACKAGE__, 'extractconfig', ['volume']], |
47f37b53 | 809 | export => [ __PACKAGE__, 'export', ['volume', 'format', 'filename']], |
a97d3ee4 FE |
810 | import => [ __PACKAGE__, 'import', ['volume', 'format', 'filename'], {}, sub { |
811 | my $volid = shift; | |
812 | print PVE::Storage::volume_imported_message($volid); | |
813 | }], | |
5f184292 FE |
814 | apiinfo => [ __PACKAGE__, 'apiinfo', [], {}, sub { |
815 | my $res = shift; | |
816 | ||
817 | print "APIVER $res->{apiver}\n"; | |
818 | print "APIAGE $res->{apiage}\n"; | |
819 | }], | |
c669f42d DM |
820 | }; |
821 | ||
822 | 1; |