]> git.proxmox.com Git - pmg-api.git/blob - src/PMG/API2/PBS/Job.pm
Add API2 module for per-node backups to PBS
[pmg-api.git] / src / PMG / API2 / PBS / Job.pm
1 package PMG::API2::PBS::Job;
2
3 use strict;
4 use warnings;
5
6 use POSIX qw(strftime);
7
8 use PVE::JSONSchema qw(get_standard_option);
9 use PVE::RESTHandler;
10 use PVE::SafeSyslog;
11 use PVE::Tools qw(extract_param);
12 use PVE::PBSClient;
13
14 use PMG::RESTEnvironment;
15 use PMG::Backup;
16 use PMG::PBSConfig;
17
18 use base qw(PVE::RESTHandler);
19
20 __PACKAGE__->register_method ({
21 name => 'list',
22 path => '',
23 method => 'GET',
24 description => "List all configured Proxmox Backup Server jobs.",
25 permissions => { check => [ 'admin', 'audit' ] },
26 proxyto => 'node',
27 protected => 1,
28 parameters => {
29 additionalProperties => 0,
30 properties => {
31 node => get_standard_option('pve-node'),
32 },
33 },
34 returns => {
35 type => "array",
36 items => PMG::PBSConfig->createSchema(1),
37 links => [ { rel => 'child', href => "{remote}" } ],
38 },
39 code => sub {
40 my ($param) = @_;
41
42 my $res = [];
43
44 my $conf = PMG::PBSConfig->new();
45 if (defined($conf)) {
46 foreach my $remote (keys %{$conf->{ids}}) {
47 my $d = $conf->{ids}->{$remote};
48 my $entry = {
49 remote => $remote,
50 server => $d->{server},
51 datastore => $d->{datastore},
52 };
53 push @$res, $entry;
54 }
55 }
56
57 return $res;
58 }});
59
60 __PACKAGE__->register_method ({
61 name => 'remote_index',
62 path => '{remote}',
63 method => 'GET',
64 description => "Backup Job index.",
65 parameters => {
66 additionalProperties => 0,
67 properties => {
68 node => get_standard_option('pve-node'),
69 remote => {
70 description => "Proxmox Backup Server ID.",
71 type => 'string', format => 'pve-configid',
72 },
73 },
74 },
75 returns => {
76 type => 'array',
77 items => {
78 type => "object",
79 properties => { section => { type => 'string'} },
80 },
81 links => [ { rel => 'child', href => "{section}" } ],
82 },
83 code => sub {
84 my ($param) = @_;
85
86 my $result = [
87 { section => 'snapshots' },
88 { section => 'backup' },
89 { section => 'restore' },
90 { section => 'timer' },
91 ];
92 return $result;
93 }});
94
95 __PACKAGE__->register_method ({
96 name => 'get_snapshots',
97 path => '{remote}/snapshots',
98 method => 'GET',
99 description => "Get snapshots stored on remote.",
100 proxyto => 'node',
101 protected => 1,
102 permissions => { check => [ 'admin', 'audit' ] },
103 parameters => {
104 additionalProperties => 0,
105 properties => {
106 node => get_standard_option('pve-node'),
107 remote => {
108 description => "Proxmox Backup Server ID.",
109 type => 'string', format => 'pve-configid',
110 },
111 },
112 },
113 returns => {
114 type => 'array',
115 items => {
116 type => "object",
117 properties => {
118 time => { type => 'string'},
119 ctime => { type => 'string'},
120 size => { type => 'integer'},
121 },
122 },
123 links => [ { rel => 'child', href => "{time}" } ],
124 },
125 code => sub {
126 my ($param) = @_;
127
128 my $remote = $param->{remote};
129 my $node = $param->{node};
130
131 my $conf = PMG::PBSConfig->new();
132
133 my $remote_config = $conf->{ids}->{$remote};
134 die "PBS remote '$remote' does not exist\n" if !$remote_config;
135
136 return [] if $remote_config->{disable};
137
138 my $snap_param = {
139 group => "host/$node",
140 };
141
142 my $pbs = PVE::PBSClient->new($remote_config, $remote, $conf->{secret_dir});
143 my $snapshots = $pbs->get_snapshots($snap_param);
144 my $res = [];
145 foreach my $item (@$snapshots) {
146 my $btype = $item->{"backup-type"};
147 my $bid = $item->{"backup-id"};
148 my $epoch = $item->{"backup-time"};
149 my $size = $item->{size} // 1;
150
151 my @pxar = grep { $_->{filename} eq 'pmgbackup.pxar.didx' } @{$item->{files}};
152 die "unexpected number of pmgbackup archives in snapshot\n" if (scalar(@pxar) != 1);
153
154
155 next if !($btype eq 'host');
156 next if !($bid eq $node);
157
158 my $time = strftime("%FT%TZ", gmtime($epoch));
159
160 my $info = {
161 time => $time,
162 ctime => $epoch,
163 size => $size,
164 };
165
166 push @$res, $info;
167 }
168
169 return $res;
170 }});
171
172 __PACKAGE__->register_method ({
173 name => 'forget_snapshot',
174 path => '{remote}/snapshots/{time}',
175 method => 'DELETE',
176 description => "Forget a snapshot",
177 proxyto => 'node',
178 protected => 1,
179 permissions => { check => [ 'admin', 'audit' ] },
180 parameters => {
181 additionalProperties => 0,
182 properties => {
183 node => get_standard_option('pve-node'),
184 remote => {
185 description => "Proxmox Backup Server ID.",
186 type => 'string', format => 'pve-configid',
187 },
188 time => {
189 description => "Backup time in RFC 3399 format",
190 type => 'string',
191 },
192 },
193 },
194 returns => {type => 'null' },
195 code => sub {
196 my ($param) = @_;
197
198 my $remote = $param->{remote};
199 my $node = $param->{node};
200 my $time = $param->{time};
201
202 my $snapshot = "host/$node/$time";
203
204 my $conf = PMG::PBSConfig->new();
205
206 my $remote_config = $conf->{ids}->{$remote};
207 die "PBS remote '$remote' does not exist\n" if !$remote_config;
208 die "PBS remote '$remote' is disabled\n" if $remote_config->{disable};
209
210 my $pbs = PVE::PBSClient->new($remote_config, $remote, $conf->{secret_dir});
211
212 eval {
213 $pbs->forget_snapshot($snapshot);
214 };
215 die "Forgetting backup failed: $@" if $@;
216
217 return;
218
219 }});
220
221 __PACKAGE__->register_method ({
222 name => 'run_backup',
223 path => '{remote}/backup',
224 method => 'POST',
225 description => "run backup and prune the backupgroup afterwards.",
226 proxyto => 'node',
227 protected => 1,
228 permissions => { check => [ 'admin', 'audit' ] },
229 parameters => {
230 additionalProperties => 0,
231 properties => {
232 node => get_standard_option('pve-node'),
233 remote => {
234 description => "Proxmox Backup Server ID.",
235 type => 'string', format => 'pve-configid',
236 },
237 },
238 },
239 returns => { type => "string" },
240 code => sub {
241 my ($param) = @_;
242
243 my $rpcenv = PMG::RESTEnvironment->get();
244 my $authuser = $rpcenv->get_user();
245
246 my $remote = $param->{remote};
247 my $node = $param->{node};
248
249 my $conf = PMG::PBSConfig->new();
250
251 my $remote_config = $conf->{ids}->{$remote};
252 die "PBS remote '$remote' does not exist\n" if !$remote_config;
253 die "PBS remote '$remote' is disabled\n" if $remote_config->{disable};
254
255 my $pbs = PVE::PBSClient->new($remote_config, $remote, $conf->{secret_dir});
256 my $backup_dir = "/var/lib/pmg/backup/current";
257
258 my $worker = sub {
259 my $upid = shift;
260
261 print "starting update of current backup state\n";
262
263 -d $backup_dir || mkdir $backup_dir;
264 PMG::Backup::pmg_backup($backup_dir, $param->{statistic});
265 my $pbs_opts = {
266 type => 'host',
267 id => $node,
268 pxarname => 'pmgbackup',
269 root => $backup_dir,
270 };
271
272 $pbs->backup_tree($pbs_opts);
273
274 print "backup finished\n";
275
276 my $group = "host/$node";
277 print "starting prune of $group\n";
278 my $prune_opts = $conf->prune_options($remote);
279 my $res = $pbs->prune_group(undef, $prune_opts, $group);
280
281 foreach my $pruned (@$res){
282 my $time = strftime("%FT%TZ", gmtime($pruned->{'backup-time'}));
283 my $snap = $pruned->{'backup-type'} . '/' . $pruned->{'backup-id'} . '/' . $time;
284 print "pruned snapshot: $snap\n";
285 }
286
287 print "prune finished\n";
288
289 return;
290 };
291
292 return $rpcenv->fork_worker('pbs_backup', undef, $authuser, $worker);
293
294 }});
295
296 __PACKAGE__->register_method ({
297 name => 'restore',
298 path => '{remote}/restore',
299 method => 'POST',
300 description => "Restore the system configuration.",
301 permissions => { check => [ 'admin' ] },
302 proxyto => 'node',
303 protected => 1,
304 parameters => {
305 additionalProperties => 0,
306 properties => {
307 PMG::Backup::get_restore_options(),
308 remote => {
309 description => "Proxmox Backup Server ID.",
310 type => 'string', format => 'pve-configid',
311 },
312 'backup-time' => {description=> "backup-time to restore",
313 optional => 1, type => 'string'
314 },
315 'backup-id' => {description => "backup-id (hostname) of backup snapshot",
316 optional => 1, type => 'string'
317 },
318 },
319 },
320 returns => { type => "string" },
321 code => sub {
322 my ($param) = @_;
323
324 my $rpcenv = PMG::RESTEnvironment->get();
325 my $authuser = $rpcenv->get_user();
326
327 my $remote = $param->{remote};
328 my $backup_id = $param->{'backup-id'} // $param->{node};
329 my $snapshot = "host/$backup_id";
330 $snapshot .= "/$param->{'backup-time'}" if defined($param->{'backup-time'});
331
332 my $conf = PMG::PBSConfig->new();
333
334 my $remote_config = $conf->{ids}->{$remote};
335 die "PBS remote '$remote' does not exist\n" if !$remote_config;
336 die "PBS remote '$remote' is disabled\n" if $remote_config->{disable};
337
338 my $pbs = PVE::PBSClient->new($remote_config, $remote, $conf->{secret_dir});
339
340 my $time = time;
341 my $dirname = "/tmp/proxrestore_$$.$time";
342
343 $param->{database} //= 1;
344
345 die "nothing selected - please select what you want to restore (config or database?)\n"
346 if !($param->{database} || $param->{config});
347
348 my $pbs_opts = {
349 pxarname => 'pmgbackup',
350 target => $dirname,
351 snapshot => $snapshot,
352 };
353
354 my $worker = sub {
355 my $upid = shift;
356
357 print "starting restore of $snapshot from $remote\n";
358
359 $pbs->restore_pxar($pbs_opts);
360 print "starting restore of PMG config\n";
361 PMG::Backup::pmg_restore($dirname, $param->{database},
362 $param->{config}, $param->{statistic});
363 print "restore finished\n";
364
365 return;
366 };
367
368 return $rpcenv->fork_worker('pbs_restore', undef, $authuser, $worker);
369 }});
370
371 1;