]> git.proxmox.com Git - pve-storage.git/blob - PVE/API2/Storage/Status.pm
fda54bb04e85b32e63f522e068109c553107581c
[pve-storage.git] / PVE / API2 / Storage / Status.pm
1 package PVE::API2::Storage::Status;
2
3 use strict;
4 use warnings;
5
6 use File::Path;
7 use File::Basename;
8 use PVE::Tools;
9 use PVE::INotify;
10 use PVE::Cluster qw(cfs_read_file);
11 use PVE::Storage;
12 use PVE::API2::Storage::Content;
13 use PVE::RESTHandler;
14 use PVE::RPCEnvironment;
15 use PVE::JSONSchema qw(get_standard_option);
16 use PVE::Exception qw(raise_param_exc);
17
18 use base qw(PVE::RESTHandler);
19
20 __PACKAGE__->register_method ({
21 subclass => "PVE::API2::Storage::Content",
22 # set fragment delimiter (no subdirs) - we need that, because volume
23 # IDs may contain a slash '/'
24 fragmentDelimiter => '',
25 path => '{storage}/content',
26 });
27
28 __PACKAGE__->register_method ({
29 name => 'index',
30 path => '',
31 method => 'GET',
32 description => "Get status for all datastores.",
33 permissions => {
34 description => "Only list entries where you have 'Datastore.Audit' or 'Datastore.AllocateSpace' permissions on '/storage/<storage>'",
35 user => 'all',
36 },
37 protected => 1,
38 proxyto => 'node',
39 parameters => {
40 additionalProperties => 0,
41 properties => {
42 node => get_standard_option('pve-node'),
43 storage => get_standard_option
44 ('pve-storage-id', {
45 description => "Only list status for specified storage",
46 optional => 1,
47 }),
48 content => {
49 description => "Only list stores which support this content type.",
50 type => 'string', format => 'pve-storage-content',
51 optional => 1,
52 },
53 },
54 },
55 returns => {
56 type => 'array',
57 items => {
58 type => "object",
59 properties => { storage => { type => 'string' } },
60 },
61 links => [ { rel => 'child', href => "{storage}" } ],
62 },
63 code => sub {
64 my ($param) = @_;
65
66 my $rpcenv = PVE::RPCEnvironment::get();
67 my $authuser = $rpcenv->get_user();
68
69 my $cfg = cfs_read_file("storage.cfg");
70
71 my $info = PVE::Storage::storage_info($cfg, $param->{content});
72
73 raise_param_exc({ storage => "No such storage." })
74 if $param->{storage} && !defined($info->{$param->{storage}});
75
76 my $res = {};
77 my @sids = PVE::Storage::storage_ids($cfg);
78 foreach my $storeid (@sids) {
79 next if !$info->{$storeid};
80 my $privs = [ 'Datastore.Audit', 'Datastore.AllocateSpace' ];
81 next if !$rpcenv->check_any($authuser, "/storage/$storeid", $privs, 1);
82 next if $param->{storage} && $param->{storage} ne $storeid;
83 $res->{$storeid} = $info->{$storeid};
84 }
85
86 return PVE::RESTHandler::hash_to_array($res, 'storage');
87 }});
88
89 __PACKAGE__->register_method ({
90 name => 'diridx',
91 path => '{storage}',
92 method => 'GET',
93 description => "",
94 permissions => {
95 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
96 },
97 parameters => {
98 additionalProperties => 0,
99 properties => {
100 node => get_standard_option('pve-node'),
101 storage => get_standard_option('pve-storage-id'),
102 },
103 },
104 returns => {
105 type => 'array',
106 items => {
107 type => "object",
108 properties => {
109 subdir => { type => 'string' },
110 },
111 },
112 links => [ { rel => 'child', href => "{subdir}" } ],
113 },
114 code => sub {
115 my ($param) = @_;
116
117 my $res = [
118 { subdir => 'status' },
119 { subdir => 'content' },
120 { subdir => 'upload' },
121 { subdir => 'rrd' },
122 { subdir => 'rrddata' },
123 ];
124
125 return $res;
126 }});
127
128 __PACKAGE__->register_method ({
129 name => 'read_status',
130 path => '{storage}/status',
131 method => 'GET',
132 description => "Read storage status.",
133 permissions => {
134 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
135 },
136 protected => 1,
137 proxyto => 'node',
138 parameters => {
139 additionalProperties => 0,
140 properties => {
141 node => get_standard_option('pve-node'),
142 storage => get_standard_option('pve-storage-id'),
143 },
144 },
145 returns => {
146 type => "object",
147 properties => {},
148 },
149 code => sub {
150 my ($param) = @_;
151
152 my $cfg = cfs_read_file("storage.cfg");
153
154 my $info = PVE::Storage::storage_info($cfg, $param->{content});
155
156 my $data = $info->{$param->{storage}};
157
158 raise_param_exc({ storage => "No such storage." })
159 if !defined($data);
160
161 return $data;
162 }});
163
164 __PACKAGE__->register_method ({
165 name => 'rrd',
166 path => '{storage}/rrd',
167 method => 'GET',
168 description => "Read storage RRD statistics (returns PNG).",
169 permissions => {
170 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
171 },
172 protected => 1,
173 proxyto => 'node',
174 parameters => {
175 additionalProperties => 0,
176 properties => {
177 node => get_standard_option('pve-node'),
178 storage => get_standard_option('pve-storage-id'),
179 timeframe => {
180 description => "Specify the time frame you are interested in.",
181 type => 'string',
182 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
183 },
184 ds => {
185 description => "The list of datasources you want to display.",
186 type => 'string', format => 'pve-configid-list',
187 },
188 cf => {
189 description => "The RRD consolidation function",
190 type => 'string',
191 enum => [ 'AVERAGE', 'MAX' ],
192 optional => 1,
193 },
194 },
195 },
196 returns => {
197 type => "object",
198 properties => {
199 filename => { type => 'string' },
200 },
201 },
202 code => sub {
203 my ($param) = @_;
204
205 return PVE::Cluster::create_rrd_graph(
206 "pve2-storage/$param->{node}/$param->{storage}",
207 $param->{timeframe}, $param->{ds}, $param->{cf});
208
209 }});
210
211 __PACKAGE__->register_method ({
212 name => 'rrddata',
213 path => '{storage}/rrddata',
214 method => 'GET',
215 description => "Read storage RRD statistics.",
216 permissions => {
217 check => ['perm', '/storage/{storage}', ['Datastore.Audit', 'Datastore.AllocateSpace'], any => 1],
218 },
219 protected => 1,
220 proxyto => 'node',
221 parameters => {
222 additionalProperties => 0,
223 properties => {
224 node => get_standard_option('pve-node'),
225 storage => get_standard_option('pve-storage-id'),
226 timeframe => {
227 description => "Specify the time frame you are interested in.",
228 type => 'string',
229 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
230 },
231 cf => {
232 description => "The RRD consolidation function",
233 type => 'string',
234 enum => [ 'AVERAGE', 'MAX' ],
235 optional => 1,
236 },
237 },
238 },
239 returns => {
240 type => "array",
241 items => {
242 type => "object",
243 properties => {},
244 },
245 },
246 code => sub {
247 my ($param) = @_;
248
249 return PVE::Cluster::create_rrd_data(
250 "pve2-storage/$param->{node}/$param->{storage}",
251 $param->{timeframe}, $param->{cf});
252 }});
253
254 # makes no sense for big images and backup files (because it
255 # create a copy of the file).
256 __PACKAGE__->register_method ({
257 name => 'upload',
258 path => '{storage}/upload',
259 method => 'POST',
260 description => "Upload templates and ISO images.",
261 permissions => {
262 check => ['perm', '/storage/{storage}', ['Datastore.AllocateTemplate']],
263 },
264 protected => 1,
265 parameters => {
266 additionalProperties => 0,
267 properties => {
268 node => get_standard_option('pve-node'),
269 storage => get_standard_option('pve-storage-id'),
270 content => {
271 description => "Content type.",
272 type => 'string', format => 'pve-storage-content',
273 },
274 filename => {
275 description => "The name of the file to create.",
276 type => 'string',
277 },
278 tmpfilename => {
279 description => "The source file name. This parameter is usually set by the REST handler. You can only overwrite it when connecting to the trustet port on localhost.",
280 type => 'string',
281 optional => 1,
282 },
283 },
284 },
285 returns => { type => "string" },
286 code => sub {
287 my ($param) = @_;
288
289 my $rpcenv = PVE::RPCEnvironment::get();
290
291 my $user = $rpcenv->get_user();
292
293 my $cfg = cfs_read_file("storage.cfg");
294
295 my $node = $param->{node};
296 my $scfg = PVE::Storage::storage_check_enabled($cfg, $param->{storage}, $node);
297
298 die "cant upload to storage type '$scfg->{type}'\n"
299 if !($scfg->{type} eq 'dir' || $scfg->{type} eq 'nfs');
300
301 my $content = $param->{content};
302
303 my $tmpfilename = $param->{tmpfilename};
304 die "missing temporary file name\n" if !$tmpfilename;
305
306 my $size = -s $tmpfilename;
307 die "temporary file '$tmpfilename' does not exists\n" if !defined($size);
308
309 my $filename = $param->{filename};
310
311 chomp $filename;
312 $filename =~ s/^.*[\/\\]//;
313 $filename =~ s/\s/_/g;
314
315 my $path;
316
317 if ($content eq 'iso') {
318 if ($filename !~ m![^/]+\.[Ii][Ss][Oo]$!) {
319 raise_param_exc({ filename => "missing '.iso' extension" });
320 }
321 $path = PVE::Storage::get_iso_dir($cfg, $param->{storage});
322 } elsif ($content eq 'vztmpl') {
323 if ($filename !~ m![^/]+\.tar\.gz$!) {
324 raise_param_exc({ filename => "missing '.tar.gz' extension" });
325 }
326 $path = PVE::Storage::get_vztmpl_dir($cfg, $param->{storage});
327 } else {
328 raise_param_exc({ content => "upload content type '$content' not allowed" });
329 }
330
331 die "storage '$param->{storage}' does not support '$content' content\n"
332 if !$scfg->{content}->{$content};
333
334 my $dest = "$path/$filename";
335 my $dirname = dirname($dest);
336
337 # we simply overwrite when destination when file already exists
338
339 my $cmd;
340 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
341 my $remip = PVE::Cluster::remote_node_ip($node);
342
343 my @ssh_options = ('-o', 'BatchMode=yes');
344
345 my @remcmd = ('/usr/bin/ssh', @ssh_options, $remip);
346
347 eval {
348 # activate remote storage
349 PVE::Tools::run_command([@remcmd, '/usr/sbin/pvesm', 'status',
350 '--storage', $param->{storage}]);
351 };
352 die "can't activate storage '$param->{storage}' on node '$node'\n" if $@;
353
354 PVE::Tools::run_command([@remcmd, '/bin/mkdir', '-p', $dirname],
355 errmsg => "mkdir failed");
356
357 $cmd = ['/usr/bin/scp', @ssh_options, $tmpfilename, "$remip:$dest"];
358 } else {
359 PVE::Storage::activate_storage($cfg, $param->{storage});
360 File::Path::make_path($dirname);
361 $cmd = ['cp', $tmpfilename, $dest];
362 }
363
364 my $worker = sub {
365 my $upid = shift;
366
367 print "starting file import from: $tmpfilename\n";
368 print "target node: $node\n";
369 print "target file: $dest\n";
370 print "file size is: $size\n";
371 print "command: " . join(' ', @$cmd) . "\n";
372
373 eval { PVE::Tools::run_command($cmd, errmsg => 'import failed'); };
374 if (my $err = $@) {
375 unlink $dest;
376 die $err;
377 }
378 print "finished file import successfully\n";
379 };
380
381 return $rpcenv->fork_worker('imgcopy', undef, $user, $worker);
382 }});
383
384 1;