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