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