]>
Commit | Line | Data |
---|---|---|
bf58f8dd DM |
1 | package PVE::API2::VZDump; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
2b5f3f98 TL |
5 | |
6 | use PVE::AccessControl; | |
7 | use PVE::Cluster; | |
31aef761 | 8 | use PVE::Exception qw(raise_param_exc); |
bf58f8dd | 9 | use PVE::INotify; |
bf58f8dd | 10 | use PVE::JSONSchema qw(get_standard_option); |
2b5f3f98 | 11 | use PVE::RPCEnvironment; |
bf58f8dd | 12 | use PVE::Storage; |
2b5f3f98 | 13 | use PVE::Tools qw(extract_param); |
2424074e | 14 | use PVE::VZDump::Common; |
2b5f3f98 TL |
15 | use PVE::VZDump; |
16 | ||
2b233ecc | 17 | use PVE::API2::Backup; |
f3376261 | 18 | use PVE::API2Tools; |
bf58f8dd DM |
19 | |
20 | use Data::Dumper; # fixme: remove | |
21 | ||
bf58f8dd DM |
22 | use base qw(PVE::RESTHandler); |
23 | ||
2b233ecc FE |
24 | my sub assert_param_permission_vzdump { |
25 | my ($rpcenv, $user, $param) = @_; | |
26 | return if $user eq 'root@pam'; # always OK | |
27 | ||
28 | PVE::API2::Backup::assert_param_permission_common($rpcenv, $user, $param); | |
29 | ||
43f83ad9 FE |
30 | if (defined($param->{maxfiles}) || defined($param->{'prune-backups'})) { |
31 | if (my $storeid = PVE::VZDump::get_storage_param($param)) { | |
32 | $rpcenv->check($user, "/storage/$storeid", [ 'Datastore.Allocate' ]); | |
33 | } | |
34 | } | |
2b233ecc FE |
35 | } |
36 | ||
bf58f8dd | 37 | __PACKAGE__->register_method ({ |
60e049c2 | 38 | name => 'vzdump', |
bf58f8dd DM |
39 | path => '', |
40 | method => 'POST', | |
41 | description => "Create backup.", | |
98e84b16 | 42 | permissions => { |
93880785 | 43 | description => "The user needs 'VM.Backup' permissions on any VM, and " |
77266e29 FE |
44 | ."'Datastore.AllocateSpace' on the backup storage (and fleecing storage when fleecing " |
45 | ."is used). The 'tmpdir', 'dumpdir' and 'script' parameters are restricted to the " | |
46 | ."'root\@pam' user. The 'maxfiles' and 'prune-backups' settings require " | |
47 | ."'Datastore.Allocate' on the backup storage. The 'bwlimit', 'performance' and " | |
48 | ."'ionice' parameters require 'Sys.Modify' on '/'.", | |
98e84b16 DM |
49 | user => 'all', |
50 | }, | |
30edfad9 | 51 | protected => 1, |
49046e53 | 52 | proxyto => 'node', |
bf58f8dd | 53 | parameters => { |
ff119724 | 54 | additionalProperties => 0, |
2424074e | 55 | properties => PVE::VZDump::Common::json_config_properties({ |
bf58f8dd DM |
56 | stdout => { |
57 | type => 'boolean', | |
58 | description => "Write tar to stdout, not to a file.", | |
59 | optional => 1, | |
60 | }, | |
ac27b58d | 61 | }), |
bf58f8dd DM |
62 | }, |
63 | returns => { type => 'string' }, | |
64 | code => sub { | |
65 | my ($param) = @_; | |
66 | ||
67 | my $rpcenv = PVE::RPCEnvironment::get(); | |
68 | ||
69 | my $user = $rpcenv->get_user(); | |
70 | ||
71 | my $nodename = PVE::INotify::nodename(); | |
72 | ||
73 | if ($rpcenv->{type} ne 'cli') { | |
74 | raise_param_exc({ node => "option is only allowed on the command line interface."}) | |
75 | if $param->{node} && $param->{node} ne $nodename; | |
76 | ||
77 | raise_param_exc({ stdout => "option is only allowed on the command line interface."}) | |
78 | if $param->{stdout}; | |
79 | } | |
80 | ||
2b233ecc | 81 | assert_param_permission_vzdump($rpcenv, $user, $param); |
6d0507a8 | 82 | |
31aef761 | 83 | PVE::VZDump::verify_vzdump_parameters($param, 1); |
bf58f8dd DM |
84 | |
85 | # silent exit if we run on wrong node | |
eab837c4 | 86 | return 'OK' if $param->{node} && $param->{node} ne $nodename; |
60e049c2 | 87 | |
2424074e | 88 | my $cmdline = PVE::VZDump::Common::command_line($param); |
df5875b4 AL |
89 | |
90 | my $vmids_per_node = PVE::VZDump::get_included_guests($param); | |
91 | ||
92 | my $local_vmids = delete $vmids_per_node->{$nodename} // []; | |
93 | ||
7f874148 FE |
94 | # include IDs for deleted guests, and visibly fail later |
95 | my $orphaned_vmids = delete $vmids_per_node->{''} // []; | |
96 | push @{$local_vmids}, @{$orphaned_vmids}; | |
97 | ||
df5875b4 | 98 | my $skiplist = [ map { @$_ } values $vmids_per_node->%* ]; |
bf58f8dd | 99 | |
eab837c4 DM |
100 | if($param->{stop}){ |
101 | PVE::VZDump::stop_running_backups(); | |
df5875b4 | 102 | return 'OK' if !scalar(@{$local_vmids}); |
eab837c4 DM |
103 | } |
104 | ||
5c4da4c3 | 105 | # silent exit if specified VMs run on other nodes |
df5875b4 | 106 | return "OK" if !scalar(@{$local_vmids}) && !$param->{all}; |
336ec53a | 107 | |
f8ed6af8 | 108 | PVE::VZDump::parse_mailto_exclude_path($param); |
bf58f8dd DM |
109 | |
110 | die "you can only backup a single VM with option --stdout\n" | |
df5875b4 | 111 | if $param->{stdout} && scalar(@{$local_vmids}) != 1; |
bf58f8dd | 112 | |
43f83ad9 FE |
113 | if (my $storeid = PVE::VZDump::get_storage_param($param)) { |
114 | $rpcenv->check($user, "/storage/$storeid", [ 'Datastore.AllocateSpace' ]); | |
115 | } | |
4412265f | 116 | |
bf58f8dd | 117 | my $worker = sub { |
eab837c4 DM |
118 | my $upid = shift; |
119 | ||
bf58f8dd DM |
120 | $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = $SIG{HUP} = $SIG{PIPE} = sub { |
121 | die "interrupted by signal\n"; | |
122 | }; | |
123 | ||
df5875b4 | 124 | $param->{vmids} = $local_vmids; |
403761c4 WB |
125 | my $vzdump = PVE::VZDump->new($cmdline, $param, $skiplist); |
126 | ||
875c2e5a | 127 | my $LOCK_FH = eval { |
eab837c4 | 128 | $vzdump->getlock($upid); # only one process allowed |
6ec9de44 | 129 | }; |
eab837c4 | 130 | if (my $err = $@) { |
c4afde55 | 131 | $vzdump->send_notification([], 0, $err); |
6ec9de44 SP |
132 | exit(-1); |
133 | } | |
bf58f8dd DM |
134 | |
135 | if (defined($param->{ionice})) { | |
136 | if ($param->{ionice} > 7) { | |
137 | PVE::VZDump::run_command(undef, "ionice -c3 -p $$"); | |
138 | } else { | |
139 | PVE::VZDump::run_command(undef, "ionice -c2 -n$param->{ionice} -p $$"); | |
140 | } | |
141 | } | |
60e049c2 | 142 | $vzdump->exec_backup($rpcenv, $user); |
875c2e5a FE |
143 | |
144 | close($LOCK_FH); | |
60e049c2 | 145 | }; |
bf58f8dd DM |
146 | |
147 | open STDOUT, '>/dev/null' if $param->{quiet} && !$param->{stdout}; | |
148 | open STDERR, '>/dev/null' if $param->{quiet}; | |
149 | ||
150 | if ($rpcenv->{type} eq 'cli') { | |
151 | if ($param->{stdout}) { | |
152 | ||
153 | open my $saved_stdout, ">&STDOUT" | |
154 | || die "can't dup STDOUT: $!\n"; | |
155 | ||
156 | open STDOUT, '>&STDERR' || | |
157 | die "unable to redirect STDOUT: $!\n"; | |
158 | ||
159 | $param->{stdout} = $saved_stdout; | |
160 | } | |
161 | } | |
162 | ||
742d2ad2 | 163 | my $taskid; |
df5875b4 | 164 | $taskid = $local_vmids->[0] if scalar(@{$local_vmids}) == 1; |
742d2ad2 FG |
165 | |
166 | return $rpcenv->fork_worker('vzdump', $taskid, $user, $worker); | |
bf58f8dd | 167 | }}); |
7619e4dd | 168 | |
5b9a4030 FE |
169 | __PACKAGE__->register_method ({ |
170 | name => 'defaults', | |
171 | path => 'defaults', | |
172 | method => 'GET', | |
173 | description => "Get the currently configured vzdump defaults.", | |
174 | permissions => { | |
175 | description => "The user needs 'Datastore.Audit' or 'Datastore.AllocateSpace' " . | |
176 | "permissions for the specified storage (or default storage if none specified). Some " . | |
177 | "properties are only returned when the user has 'Sys.Audit' permissions for the node.", | |
178 | user => 'all', | |
179 | }, | |
180 | proxyto => 'node', | |
181 | parameters => { | |
182 | additionalProperties => 0, | |
183 | properties => { | |
184 | node => get_standard_option('pve-node'), | |
185 | storage => get_standard_option('pve-storage-id', { optional => 1 }), | |
186 | }, | |
187 | }, | |
188 | returns => { | |
189 | type => 'object', | |
190 | additionalProperties => 0, | |
191 | properties => PVE::VZDump::Common::json_config_properties(), | |
192 | }, | |
193 | code => sub { | |
194 | my ($param) = @_; | |
195 | ||
196 | my $node = extract_param($param, 'node'); | |
197 | my $storage = extract_param($param, 'storage'); | |
198 | ||
199 | my $rpcenv = PVE::RPCEnvironment::get(); | |
200 | my $authuser = $rpcenv->get_user(); | |
201 | ||
202 | my $res = PVE::VZDump::read_vzdump_defaults(); | |
203 | ||
204 | $res->{storage} = $storage if defined($storage); | |
205 | ||
206 | if (!defined($res->{dumpdir}) && !defined($res->{storage})) { | |
207 | $res->{storage} = 'local'; | |
208 | } | |
209 | ||
210 | if (defined($res->{storage})) { | |
211 | $rpcenv->check_any( | |
212 | $authuser, | |
213 | "/storage/$res->{storage}", | |
214 | ['Datastore.Audit', 'Datastore.AllocateSpace'], | |
215 | ); | |
216 | ||
217 | my $info = PVE::VZDump::storage_info($res->{storage}); | |
218 | for my $key (qw(dumpdir prune-backups)) { | |
219 | $res->{$key} = $info->{$key} if defined($info->{$key}); | |
220 | } | |
221 | } | |
222 | ||
223 | if (defined($res->{'prune-backups'})) { | |
224 | $res->{'prune-backups'} = PVE::JSONSchema::print_property_string( | |
225 | $res->{'prune-backups'}, | |
226 | 'prune-backups', | |
227 | ); | |
228 | } | |
229 | ||
230 | $res->{mailto} = join(",", @{$res->{mailto}}) | |
231 | if defined($res->{mailto}); | |
232 | ||
233 | $res->{'exclude-path'} = join(",", @{$res->{'exclude-path'}}) | |
234 | if defined($res->{'exclude-path'}); | |
235 | ||
236 | # normal backup users don't need to know these | |
237 | if (!$rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'], 1)) { | |
238 | delete $res->{mailto}; | |
239 | delete $res->{tmpdir}; | |
240 | delete $res->{dumpdir}; | |
241 | delete $res->{script}; | |
242 | delete $res->{ionice}; | |
243 | } | |
244 | ||
245 | my $pool = $res->{pool}; | |
246 | if (defined($pool) && | |
91db3ece | 247 | !$rpcenv->check($authuser, "/pool/$pool", ['Pool.Audit'], 1)) { |
5b9a4030 FE |
248 | delete $res->{pool}; |
249 | } | |
250 | ||
5b9a4030 FE |
251 | return $res; |
252 | }}); | |
253 | ||
7619e4dd FG |
254 | __PACKAGE__->register_method ({ |
255 | name => 'extractconfig', | |
256 | path => 'extractconfig', | |
257 | method => 'GET', | |
258 | description => "Extract configuration from vzdump backup archive.", | |
259 | permissions => { | |
260 | description => "The user needs 'VM.Backup' permissions on the backed up guest ID, and 'Datastore.AllocateSpace' on the backup storage.", | |
261 | user => 'all', | |
262 | }, | |
263 | protected => 1, | |
264 | proxyto => 'node', | |
265 | parameters => { | |
266 | additionalProperties => 0, | |
267 | properties => { | |
268 | node => get_standard_option('pve-node'), | |
269 | volume => { | |
270 | description => "Volume identifier", | |
271 | type => 'string', | |
272 | completion => \&PVE::Storage::complete_volume, | |
273 | }, | |
274 | }, | |
275 | }, | |
276 | returns => { type => 'string' }, | |
277 | code => sub { | |
278 | my ($param) = @_; | |
279 | ||
280 | my $volume = extract_param($param, 'volume'); | |
281 | ||
282 | my $rpcenv = PVE::RPCEnvironment::get(); | |
283 | my $authuser = $rpcenv->get_user(); | |
284 | ||
285 | my $storage_cfg = PVE::Storage::config(); | |
c53d5c5e FE |
286 | PVE::Storage::check_volume_access( |
287 | $rpcenv, | |
288 | $authuser, | |
289 | $storage_cfg, | |
290 | undef, | |
291 | $volume, | |
292 | 'backup', | |
293 | ); | |
7619e4dd | 294 | |
0bd224e5 FE |
295 | if (PVE::Storage::parse_volume_id($volume, 1)) { |
296 | my (undef, undef, $ownervm) = PVE::Storage::parse_volname($storage_cfg, $volume); | |
297 | $rpcenv->check($authuser, "/vms/$ownervm", ['VM.Backup']); | |
298 | } | |
299 | ||
7619e4dd FG |
300 | return PVE::Storage::extract_vzdump_config($storage_cfg, $volume); |
301 | }}); | |
302 | ||
303 | 1; |