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