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