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