]> git.proxmox.com Git - pve-container.git/blob - src/PVE/API2/LXC/Snapshot.pm
Add a migration lock to avoid a replication on rollback-time.
[pve-container.git] / src / PVE / API2 / LXC / Snapshot.pm
1 package PVE::API2::LXC::Snapshot;
2
3 use strict;
4 use warnings;
5
6 use PVE::SafeSyslog;
7 use PVE::Tools qw(extract_param run_command);
8 use PVE::Exception qw(raise raise_param_exc);
9 use PVE::INotify;
10 use PVE::Cluster qw(cfs_read_file);
11 use PVE::AccessControl;
12 use PVE::Firewall;
13 use PVE::Storage;
14 use PVE::RESTHandler;
15 use PVE::RPCEnvironment;
16 use PVE::LXC;
17 use PVE::LXC::Create;
18 use PVE::JSONSchema qw(get_standard_option);
19 use base qw(PVE::RESTHandler);
20
21 __PACKAGE__->register_method({
22 name => 'list',
23 path => '',
24 method => 'GET',
25 description => "List all snapshots.",
26 permissions => {
27 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
28 },
29 proxyto => 'node',
30 protected => 1, # lxc pid files are only readable by root
31 parameters => {
32 additionalProperties => 0,
33 properties => {
34 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
35 node => get_standard_option('pve-node'),
36 },
37 },
38 returns => {
39 type => 'array',
40 items => {
41 type => "object",
42 properties => {},
43 },
44 links => [ { rel => 'child', href => "{name}" } ],
45 },
46 code => sub {
47 my ($param) = @_;
48
49 my $vmid = $param->{vmid};
50
51 my $conf = PVE::LXC::Config->load_config($vmid);
52 my $snaphash = $conf->{snapshots} || {};
53
54 my $res = [];
55
56 foreach my $name (keys %$snaphash) {
57 my $d = $snaphash->{$name};
58 my $item = {
59 name => $name,
60 snaptime => $d->{snaptime} || 0,
61 description => $d->{description} || '',
62 };
63 $item->{parent} = $d->{parent} if defined($d->{parent});
64 $item->{snapstate} = $d->{snapstate} if $d->{snapstate};
65 push @$res, $item;
66 }
67
68 my $running = PVE::LXC::check_running($vmid) ? 1 : 0;
69 my $current = { name => 'current', digest => $conf->{digest}, running => $running };
70 $current->{parent} = $conf->{parent} if defined($conf->{parent});
71
72 push @$res, $current;
73
74 return $res;
75 }});
76
77 use Data::Dumper; # fixme: remove
78 __PACKAGE__->register_method({
79 name => 'snapshot',
80 path => '',
81 method => 'POST',
82 protected => 1,
83 proxyto => 'node',
84 description => "Snapshot a container.",
85 permissions => {
86 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
87 },
88 parameters => {
89 additionalProperties => 0,
90 properties => {
91 node => get_standard_option('pve-node'),
92 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
93 snapname => get_standard_option('pve-lxc-snapshot-name'),
94 # vmstate => {
95 # optional => 1,
96 # type => 'boolean',
97 # description => "Save the vmstate",
98 # },
99 description => {
100 optional => 1,
101 type => 'string',
102 description => "A textual description or comment.",
103 },
104 },
105 },
106 returns => {
107 type => 'string',
108 description => "the task ID.",
109 },
110 code => sub {
111 my ($param) = @_;
112
113 my $rpcenv = PVE::RPCEnvironment::get();
114
115 my $authuser = $rpcenv->get_user();
116
117 my $node = extract_param($param, 'node');
118
119 my $vmid = extract_param($param, 'vmid');
120
121 my $snapname = extract_param($param, 'snapname');
122
123 die "unable to use snapshot name 'current' (reserved name)\n"
124 if $snapname eq 'current';
125
126 die "unable to use snapshot name 'vzdump' (reserved name)\n"
127 if $snapname eq 'vzdump';
128
129 my $realcmd = sub {
130 PVE::Cluster::log_msg('info', $authuser, "snapshot container $vmid: $snapname");
131 PVE::LXC::Config->snapshot_create($vmid, $snapname, 0, $param->{description});
132 };
133
134 return $rpcenv->fork_worker('vzsnapshot', $vmid, $authuser, $realcmd);
135 }});
136
137 __PACKAGE__->register_method({
138 name => 'delsnapshot',
139 path => '{snapname}',
140 method => 'DELETE',
141 protected => 1,
142 proxyto => 'node',
143 description => "Delete a LXC snapshot.",
144 permissions => {
145 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
146 },
147 parameters => {
148 additionalProperties => 0,
149 properties => {
150 node => get_standard_option('pve-node'),
151 vmid => get_standard_option('pve-vmid'),
152 snapname => get_standard_option('pve-lxc-snapshot-name'),
153 force => {
154 optional => 1,
155 type => 'boolean',
156 description => "For removal from config file, even if removing disk snapshots fails.",
157 },
158 },
159 },
160 returns => {
161 type => 'string',
162 description => "the task ID.",
163 },
164 code => sub {
165 my ($param) = @_;
166
167 my $rpcenv = PVE::RPCEnvironment::get();
168
169 my $authuser = $rpcenv->get_user();
170
171 my $node = extract_param($param, 'node');
172
173 my $vmid = extract_param($param, 'vmid');
174
175 my $snapname = extract_param($param, 'snapname');
176
177 my $realcmd = sub {
178 PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname");
179 PVE::LXC::Config->snapshot_delete($vmid, $snapname, $param->{force});
180 };
181
182 return $rpcenv->fork_worker('vzdelsnapshot', $vmid, $authuser, $realcmd);
183 }});
184
185 __PACKAGE__->register_method({
186 name => 'snapshot_cmd_idx',
187 path => '{snapname}',
188 description => '',
189 method => 'GET',
190 permissions => {
191 user => 'all',
192 },
193 parameters => {
194 additionalProperties => 0,
195 properties => {
196 vmid => get_standard_option('pve-vmid'),
197 node => get_standard_option('pve-node'),
198 snapname => get_standard_option('pve-lxc-snapshot-name'),
199 },
200 },
201 returns => {
202 type => 'array',
203 items => {
204 type => "object",
205 properties => {},
206 },
207 links => [ { rel => 'child', href => "{cmd}" } ],
208 },
209 code => sub {
210 my ($param) = @_;
211
212 my $res = [];
213
214 push @$res, { cmd => 'rollback' };
215 push @$res, { cmd => 'config' };
216
217 return $res;
218 }});
219
220 __PACKAGE__->register_method({
221 name => 'rollback',
222 path => '{snapname}/rollback',
223 method => 'POST',
224 protected => 1,
225 proxyto => 'node',
226 description => "Rollback LXC state to specified snapshot.",
227 permissions => {
228 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
229 },
230 parameters => {
231 additionalProperties => 0,
232 properties => {
233 node => get_standard_option('pve-node'),
234 vmid => get_standard_option('pve-vmid'),
235 snapname => get_standard_option('pve-lxc-snapshot-name'),
236 },
237 },
238 returns => {
239 type => 'string',
240 description => "the task ID.",
241 },
242 code => sub {
243 my ($param) = @_;
244
245 my $rpcenv = PVE::RPCEnvironment::get();
246
247 my $authuser = $rpcenv->get_user();
248
249 my $node = extract_param($param, 'node');
250
251 my $vmid = extract_param($param, 'vmid');
252
253 my $snapname = extract_param($param, 'snapname');
254
255 my $realcmd = sub {
256 PVE::Cluster::log_msg('info', $authuser, "rollback snapshot LXC $vmid: $snapname");
257 PVE::LXC::Config->snapshot_rollback($vmid, $snapname);
258 };
259
260 my $worker = sub {
261 # hold migration lock, this makes sure that nobody create replication snapshots
262 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
263 };
264
265 return $rpcenv->fork_worker('vzrollback', $vmid, $authuser, $worker);
266 }});
267
268 __PACKAGE__->register_method({
269 name => 'update_snapshot_config',
270 path => '{snapname}/config',
271 method => 'PUT',
272 protected => 1,
273 proxyto => 'node',
274 description => "Update snapshot metadata.",
275 permissions => {
276 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
277 },
278 parameters => {
279 additionalProperties => 0,
280 properties => {
281 node => get_standard_option('pve-node'),
282 vmid => get_standard_option('pve-vmid'),
283 snapname => get_standard_option('pve-lxc-snapshot-name'),
284 description => {
285 optional => 1,
286 type => 'string',
287 description => "A textual description or comment.",
288 },
289 },
290 },
291 returns => { type => 'null' },
292 code => sub {
293 my ($param) = @_;
294
295 my $rpcenv = PVE::RPCEnvironment::get();
296
297 my $authuser = $rpcenv->get_user();
298
299 my $vmid = extract_param($param, 'vmid');
300
301 my $snapname = extract_param($param, 'snapname');
302
303 return undef if !defined($param->{description});
304
305 my $updatefn = sub {
306
307 my $conf = PVE::LXC::Config->load_config($vmid);
308 PVE::LXC::Config->check_lock($conf);
309
310 my $snap = $conf->{snapshots}->{$snapname};
311
312 die "snapshot '$snapname' does not exist\n" if !defined($snap);
313
314 $snap->{description} = $param->{description} if defined($param->{description});
315
316 PVE::LXC::Config->write_config($vmid, $conf, 1);
317 };
318
319 PVE::LXC::Config->lock_config($vmid, $updatefn);
320
321 return undef;
322 }});
323
324 __PACKAGE__->register_method({
325 name => 'get_snapshot_config',
326 path => '{snapname}/config',
327 method => 'GET',
328 proxyto => 'node',
329 description => "Get snapshot configuration",
330 permissions => {
331 check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]],
332 },
333 parameters => {
334 additionalProperties => 0,
335 properties => {
336 node => get_standard_option('pve-node'),
337 vmid => get_standard_option('pve-vmid'),
338 snapname => get_standard_option('pve-lxc-snapshot-name'),
339 },
340 },
341 returns => { type => "object" },
342 code => sub {
343 my ($param) = @_;
344
345 my $rpcenv = PVE::RPCEnvironment::get();
346
347 my $authuser = $rpcenv->get_user();
348
349 my $vmid = extract_param($param, 'vmid');
350
351 my $snapname = extract_param($param, 'snapname');
352
353 my $conf = PVE::LXC::Config->load_config($vmid);
354
355 my $snap = $conf->{snapshots}->{$snapname};
356
357 die "snapshot '$snapname' does not exist\n" if !defined($snap);
358
359 delete $snap->{lxc};
360
361 return $snap;
362 }});
363
364 1;