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