]> git.proxmox.com Git - pve-storage.git/blob - PVE/CLI/pvesm.pm
pvesm status: improve output and its format
[pve-storage.git] / PVE / CLI / pvesm.pm
1 package PVE::CLI::pvesm;
2
3 use strict;
4 use warnings;
5
6 use POSIX qw(O_RDONLY O_WRONLY O_CREAT O_TRUNC);
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;
18 use PVE::API2::Storage::Scan;
19 use PVE::JSONSchema qw(get_standard_option);
20
21 use PVE::CLIHandler;
22
23 use base qw(PVE::CLIHandler);
24
25 my $KNOWN_EXPORT_FORMATS = ['raw+size', 'tar+size', 'qcow2+size', 'vmdk+size', 'zfs'];
26
27 my $nodename = PVE::INotify::nodename();
28
29 sub setup_environment {
30 PVE::RPCEnvironment->setup_default_cli_env();
31 }
32
33 __PACKAGE__->register_method ({
34 name => 'path',
35 path => 'path',
36 method => 'GET',
37 description => "Get filesystem path for specified volume",
38 parameters => {
39 additionalProperties => 0,
40 properties => {
41 volume => {
42 description => "Volume identifier",
43 type => 'string', format => 'pve-volume-id',
44 completion => \&PVE::Storage::complete_volume,
45 },
46 },
47 },
48 returns => { type => 'null' },
49
50 code => sub {
51 my ($param) = @_;
52
53 my $cfg = PVE::Storage::config();
54
55 my $path = PVE::Storage::path ($cfg, $param->{volume});
56
57 print "$path\n";
58
59 return undef;
60
61 }});
62
63 __PACKAGE__->register_method ({
64 name => 'extractconfig',
65 path => 'extractconfig',
66 method => 'GET',
67 description => "Extract configuration from vzdump backup archive.",
68 permissions => {
69 description => "The user needs 'VM.Backup' permissions on the backed up guest ID, and 'Datastore.AllocateSpace' on the backup storage.",
70 user => 'all',
71 },
72 protected => 1,
73 parameters => {
74 additionalProperties => 0,
75 properties => {
76 volume => {
77 description => "Volume identifier",
78 type => 'string',
79 completion => \&PVE::Storage::complete_volume,
80 },
81 },
82 },
83 returns => { type => 'null' },
84 code => sub {
85 my ($param) = @_;
86 my $volume = $param->{volume};
87
88 my $rpcenv = PVE::RPCEnvironment::get();
89 my $authuser = $rpcenv->get_user();
90
91 my $storage_cfg = PVE::Storage::config();
92 PVE::Storage::check_volume_access($rpcenv, $authuser, $storage_cfg, undef, $volume);
93
94 my $config_raw = PVE::Storage::extract_vzdump_config($storage_cfg, $volume);
95
96 print "$config_raw\n";
97 return;
98 }});
99
100 my $print_content = sub {
101 my ($list) = @_;
102
103 my $maxlenname = 0;
104 foreach my $info (@$list) {
105
106 my $volid = $info->{volid};
107 my $sidlen = length ($volid);
108 $maxlenname = $sidlen if $sidlen > $maxlenname;
109 }
110
111 foreach my $info (@$list) {
112 next if !$info->{vmid};
113 my $volid = $info->{volid};
114
115 printf "%-${maxlenname}s %5s %10d %d\n", $volid,
116 $info->{format}, $info->{size}, $info->{vmid};
117 }
118
119 foreach my $info (sort { $a->{format} cmp $b->{format} } @$list) {
120 next if $info->{vmid};
121 my $volid = $info->{volid};
122
123 printf "%-${maxlenname}s %5s %10d\n", $volid,
124 $info->{format}, $info->{size};
125 }
126 };
127
128 my $print_status = sub {
129 my $res = shift;
130
131 my $maxlen = 0;
132 foreach my $res (@$res) {
133 my $storeid = $res->{storage};
134 $maxlen = length ($storeid) if length ($storeid) > $maxlen;
135 }
136 $maxlen+=1;
137
138 printf "%-${maxlen}s %10s %10s %15s %15s %15s %8s\n", 'Name', 'Type',
139 'Status', 'Total', 'Used', 'Available', '%';
140
141 foreach my $res (sort { $a->{storage} cmp $b->{storage} } @$res) {
142 my $storeid = $res->{storage};
143
144 my $active = $res->{active} ? 'active' : 'inactive';
145 my ($per, $per_fmt) = (0, '% 7.2f%%');
146 $per = ($res->{used}*100)/$res->{total} if $res->{total} > 0;
147
148 if (!$res->{enabled}) {
149 $per = 'N/A ';
150 $per_fmt = '% 8s';
151 $active = 'disabled';
152 }
153
154 printf "%-${maxlen}s %10s %10s %15d %15d %15d $per_fmt\n", $storeid,
155 $res->{type}, $active, $res->{total}/1024, $res->{used}/1024,
156 $res->{avail}/1024, $per;
157 }
158 };
159
160 __PACKAGE__->register_method ({
161 name => 'export',
162 path => 'export',
163 method => 'GET',
164 description => "Export a volume.",
165 protected => 1,
166 parameters => {
167 additionalProperties => 0,
168 properties => {
169 volume => {
170 description => "Volume identifier",
171 type => 'string',
172 completion => \&PVE::Storage::complete_volume,
173 },
174 format => {
175 description => "Export stream format",
176 type => 'string',
177 enum => $KNOWN_EXPORT_FORMATS,
178 },
179 filename => {
180 description => "Destination file name",
181 type => 'string',
182 },
183 base => {
184 description => "Snapshot to start an incremental stream from",
185 type => 'string',
186 pattern => qr/[a-z0-9_\-]{1,40}/,
187 maxLength => 40,
188 optional => 1,
189 },
190 snapshot => {
191 description => "Snapshot to export",
192 type => 'string',
193 pattern => qr/[a-z0-9_\-]{1,40}/,
194 maxLength => 40,
195 optional => 1,
196 },
197 'with-snapshots' => {
198 description =>
199 "Whether to include intermediate snapshots in the stream",
200 type => 'boolean',
201 optional => 1,
202 default => 0,
203 },
204 },
205 },
206 returns => { type => 'null' },
207 code => sub {
208 my ($param) = @_;
209
210 my $filename = $param->{filename};
211
212 my $outfh;
213 if ($filename eq '-') {
214 $outfh = \*STDOUT;
215 } else {
216 sysopen($outfh, $filename, O_CREAT|O_WRONLY|O_TRUNC)
217 or die "open($filename): $!\n";
218 }
219
220 eval {
221 my $cfg = PVE::Storage::config();
222 PVE::Storage::volume_export($cfg, $outfh, $param->{volume}, $param->{format},
223 $param->{snapshot}, $param->{base}, $param->{'with-snapshots'});
224 };
225 my $err = $@;
226 if ($filename ne '-') {
227 close($outfh);
228 unlink($filename) if $err;
229 }
230 die $err if $err;
231 return;
232 }
233 });
234
235 __PACKAGE__->register_method ({
236 name => 'import',
237 path => 'import',
238 method => 'PUT',
239 description => "Import a volume.",
240 protected => 1,
241 parameters => {
242 additionalProperties => 0,
243 properties => {
244 volume => {
245 description => "Volume identifier",
246 type => 'string',
247 completion => \&PVE::Storage::complete_volume,
248 },
249 format => {
250 description => "Import stream format",
251 type => 'string',
252 enum => $KNOWN_EXPORT_FORMATS,
253 },
254 filename => {
255 description => "Source file name",
256 type => 'string',
257 },
258 base => {
259 description => "Base snapshot of an incremental stream",
260 type => 'string',
261 pattern => qr/[a-z0-9_\-]{1,40}/,
262 maxLength => 40,
263 optional => 1,
264 },
265 'with-snapshots' => {
266 description =>
267 "Whether the stream includes intermediate snapshots",
268 type => 'boolean',
269 optional => 1,
270 default => 0,
271 },
272 'delete-snapshot' => {
273 description => "A snapshot to delete on success",
274 type => 'string',
275 pattern => qr/[a-z0-9_\-]{1,80}/,
276 maxLength => 80,
277 optional => 1,
278 },
279 },
280 },
281 returns => { type => 'null' },
282 code => sub {
283 my ($param) = @_;
284
285 my $filename = $param->{filename};
286
287 my $infh;
288 if ($filename eq '-') {
289 $infh = \*STDIN;
290 } else {
291 sysopen($infh, $filename, O_RDONLY)
292 or die "open($filename): $!\n";
293 }
294
295 my $cfg = PVE::Storage::config();
296 my $volume = $param->{volume};
297 my $delete = $param->{'delete-snapshot'};
298 PVE::Storage::volume_import($cfg, $infh, $volume, $param->{format},
299 $param->{base}, $param->{'with-snapshots'});
300 PVE::Storage::volume_snapshot_delete($cfg, $volume, $delete)
301 if defined($delete);
302 return;
303 }
304 });
305
306 our $cmddef = {
307 add => [ "PVE::API2::Storage::Config", 'create', ['type', 'storage'] ],
308 set => [ "PVE::API2::Storage::Config", 'update', ['storage'] ],
309 remove => [ "PVE::API2::Storage::Config", 'delete', ['storage'] ],
310 status => [ "PVE::API2::Storage::Status", 'index', [],
311 { node => $nodename }, $print_status ],
312 list => [ "PVE::API2::Storage::Content", 'index', ['storage'],
313 { node => $nodename }, $print_content ],
314 alloc => [ "PVE::API2::Storage::Content", 'create', ['storage', 'vmid', 'filename', 'size'],
315 { node => $nodename }, sub {
316 my $volid = shift;
317 print "successfully created '$volid'\n";
318 }],
319 free => [ "PVE::API2::Storage::Content", 'delete', ['volume'],
320 { node => $nodename } ],
321 nfsscan => [ "PVE::API2::Storage::Scan", 'nfsscan', ['server'],
322 { node => $nodename }, sub {
323 my $res = shift;
324
325 my $maxlen = 0;
326 foreach my $rec (@$res) {
327 my $len = length ($rec->{path});
328 $maxlen = $len if $len > $maxlen;
329 }
330 foreach my $rec (@$res) {
331 printf "%-${maxlen}s %s\n", $rec->{path}, $rec->{options};
332 }
333 }],
334 glusterfsscan => [ "PVE::API2::Storage::Scan", 'glusterfsscan', ['server'],
335 { node => $nodename }, sub {
336 my $res = shift;
337
338 foreach my $rec (@$res) {
339 printf "%s\n", $rec->{volname};
340 }
341 }],
342 iscsiscan => [ "PVE::API2::Storage::Scan", 'iscsiscan', ['server'],
343 { node => $nodename }, sub {
344 my $res = shift;
345
346 my $maxlen = 0;
347 foreach my $rec (@$res) {
348 my $len = length ($rec->{target});
349 $maxlen = $len if $len > $maxlen;
350 }
351 foreach my $rec (@$res) {
352 printf "%-${maxlen}s %s\n", $rec->{target}, $rec->{portal};
353 }
354 }],
355 lvmscan => [ "PVE::API2::Storage::Scan", 'lvmscan', [],
356 { node => $nodename }, sub {
357 my $res = shift;
358 foreach my $rec (@$res) {
359 printf "$rec->{vg}\n";
360 }
361 }],
362 lvmthinscan => [ "PVE::API2::Storage::Scan", 'lvmthinscan', ['vg'],
363 { node => $nodename }, sub {
364 my $res = shift;
365 foreach my $rec (@$res) {
366 printf "$rec->{lv}\n";
367 }
368 }],
369 zfsscan => [ "PVE::API2::Storage::Scan", 'zfsscan', [],
370 { node => $nodename }, sub {
371 my $res = shift;
372
373 foreach my $rec (@$res) {
374 printf "$rec->{pool}\n";
375 }
376 }],
377 path => [ __PACKAGE__, 'path', ['volume']],
378 extractconfig => [__PACKAGE__, 'extractconfig', ['volume']],
379 export => [ __PACKAGE__, 'export', ['volume', 'format', 'filename']],
380 import => [ __PACKAGE__, 'import', ['volume', 'format', 'filename']],
381 };
382
383 1;