]>
Commit | Line | Data |
---|---|---|
f76a2828 DM |
1 | package PVE::API2::LXC; |
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; | |
9c2d4ce9 | 10 | use PVE::Cluster qw(cfs_read_file); |
f76a2828 DM |
11 | use PVE::AccessControl; |
12 | use PVE::Storage; | |
13 | use PVE::RESTHandler; | |
14 | use PVE::RPCEnvironment; | |
15 | use PVE::LXC; | |
5b4657d0 | 16 | use PVE::LXCCreate; |
5c752bbf | 17 | use PVE::HA::Config; |
f76a2828 DM |
18 | use PVE::JSONSchema qw(get_standard_option); |
19 | use base qw(PVE::RESTHandler); | |
20 | ||
21 | use Data::Dumper; # fixme: remove | |
22 | ||
23 | my $get_container_storage = sub { | |
24 | my ($stcfg, $vmid, $lxc_conf) = @_; | |
25 | ||
23223d90 DM |
26 | if (my $volid = $lxc_conf->{'pve.volid'}) { |
27 | my ($sid, $volname) = PVE::Storage::parse_volume_id($volid); | |
28 | return wantarray ? ($sid, $volname) : $sid; | |
29 | } else { | |
30 | my $path = $lxc_conf->{'lxc.rootfs'}; | |
31 | my ($vtype, $volid) = PVE::Storage::path_to_volume_id($stcfg, $path); | |
32 | my ($sid, $volname) = PVE::Storage::parse_volume_id($volid, 1) if $volid; | |
33 | return wantarray ? ($sid, $volname, $path) : $sid; | |
34 | } | |
f76a2828 DM |
35 | }; |
36 | ||
37 | my $check_ct_modify_config_perm = sub { | |
38 | my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_; | |
5c752bbf | 39 | |
f76a2828 DM |
40 | return 1 if $authuser ne 'root@pam'; |
41 | ||
42 | foreach my $opt (@$key_list) { | |
43 | ||
44 | if ($opt eq 'cpus' || $opt eq 'cpuunits' || $opt eq 'cpulimit') { | |
45 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.CPU']); | |
46 | } elsif ($opt eq 'disk') { | |
47 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']); | |
48 | } elsif ($opt eq 'memory' || $opt eq 'swap') { | |
49 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Memory']); | |
5c752bbf | 50 | } elsif ($opt =~ m/^net\d+$/ || $opt eq 'nameserver' || |
f76a2828 DM |
51 | $opt eq 'searchdomain' || $opt eq 'hostname') { |
52 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Network']); | |
53 | } else { | |
54 | $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Options']); | |
55 | } | |
56 | } | |
57 | ||
58 | return 1; | |
59 | }; | |
60 | ||
489e960d WL |
61 | PVE::JSONSchema::register_standard_option('pve-lxc-snapshot-name', { |
62 | description => "The name of the snapshot.", | |
63 | type => 'string', format => 'pve-configid', | |
64 | maxLength => 40, | |
65 | }); | |
f76a2828 DM |
66 | |
67 | __PACKAGE__->register_method({ | |
5c752bbf DM |
68 | name => 'vmlist', |
69 | path => '', | |
f76a2828 DM |
70 | method => 'GET', |
71 | description => "LXC container index (per node).", | |
72 | permissions => { | |
73 | description => "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.", | |
74 | user => 'all', | |
75 | }, | |
76 | proxyto => 'node', | |
77 | protected => 1, # /proc files are only readable by root | |
78 | parameters => { | |
79 | additionalProperties => 0, | |
80 | properties => { | |
81 | node => get_standard_option('pve-node'), | |
82 | }, | |
83 | }, | |
84 | returns => { | |
85 | type => 'array', | |
86 | items => { | |
87 | type => "object", | |
88 | properties => {}, | |
89 | }, | |
90 | links => [ { rel => 'child', href => "{vmid}" } ], | |
91 | }, | |
92 | code => sub { | |
93 | my ($param) = @_; | |
94 | ||
95 | my $rpcenv = PVE::RPCEnvironment::get(); | |
96 | my $authuser = $rpcenv->get_user(); | |
97 | ||
98 | my $vmstatus = PVE::LXC::vmstatus(); | |
99 | ||
100 | my $res = []; | |
101 | foreach my $vmid (keys %$vmstatus) { | |
102 | next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1); | |
103 | ||
104 | my $data = $vmstatus->{$vmid}; | |
105 | $data->{vmid} = $vmid; | |
106 | push @$res, $data; | |
107 | } | |
108 | ||
109 | return $res; | |
5c752bbf | 110 | |
f76a2828 DM |
111 | }}); |
112 | ||
9c2d4ce9 | 113 | __PACKAGE__->register_method({ |
5c752bbf DM |
114 | name => 'create_vm', |
115 | path => '', | |
9c2d4ce9 DM |
116 | method => 'POST', |
117 | description => "Create or restore a container.", | |
118 | permissions => { | |
119 | user => 'all', # check inside | |
120 | description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " . | |
121 | "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " . | |
122 | "You also need 'Datastore.AllocateSpace' permissions on the storage.", | |
123 | }, | |
124 | protected => 1, | |
125 | proxyto => 'node', | |
126 | parameters => { | |
127 | additionalProperties => 0, | |
128 | properties => PVE::LXC::json_config_properties({ | |
129 | node => get_standard_option('pve-node'), | |
130 | vmid => get_standard_option('pve-vmid'), | |
131 | ostemplate => { | |
132 | description => "The OS template or backup file.", | |
5c752bbf | 133 | type => 'string', |
9c2d4ce9 DM |
134 | maxLength => 255, |
135 | }, | |
5c752bbf DM |
136 | password => { |
137 | optional => 1, | |
9c2d4ce9 DM |
138 | type => 'string', |
139 | description => "Sets root password inside container.", | |
168d6b07 | 140 | minLength => 5, |
9c2d4ce9 DM |
141 | }, |
142 | storage => get_standard_option('pve-storage-id', { | |
143 | description => "Target storage.", | |
144 | default => 'local', | |
145 | optional => 1, | |
146 | }), | |
147 | force => { | |
5c752bbf | 148 | optional => 1, |
9c2d4ce9 DM |
149 | type => 'boolean', |
150 | description => "Allow to overwrite existing container.", | |
151 | }, | |
152 | restore => { | |
5c752bbf | 153 | optional => 1, |
9c2d4ce9 DM |
154 | type => 'boolean', |
155 | description => "Mark this as restore task.", | |
156 | }, | |
5c752bbf | 157 | pool => { |
9c2d4ce9 DM |
158 | optional => 1, |
159 | type => 'string', format => 'pve-poolid', | |
160 | description => "Add the VM to the specified pool.", | |
161 | }, | |
162 | }), | |
163 | }, | |
5c752bbf | 164 | returns => { |
9c2d4ce9 DM |
165 | type => 'string', |
166 | }, | |
167 | code => sub { | |
168 | my ($param) = @_; | |
169 | ||
170 | my $rpcenv = PVE::RPCEnvironment::get(); | |
171 | ||
172 | my $authuser = $rpcenv->get_user(); | |
173 | ||
174 | my $node = extract_param($param, 'node'); | |
175 | ||
176 | my $vmid = extract_param($param, 'vmid'); | |
177 | ||
178 | my $basecfg_fn = PVE::LXC::config_file($vmid); | |
179 | ||
180 | my $same_container_exists = -f $basecfg_fn; | |
181 | ||
182 | my $restore = extract_param($param, 'restore'); | |
183 | ||
148d1cb4 DM |
184 | if ($restore) { |
185 | # fixme: limit allowed parameters | |
186 | ||
187 | } | |
188 | ||
9c2d4ce9 DM |
189 | my $force = extract_param($param, 'force'); |
190 | ||
191 | if (!($same_container_exists && $restore && $force)) { | |
192 | PVE::Cluster::check_vmid_unused($vmid); | |
193 | } | |
5c752bbf | 194 | |
9c2d4ce9 DM |
195 | my $password = extract_param($param, 'password'); |
196 | ||
197 | my $storage = extract_param($param, 'storage') || 'local'; | |
198 | ||
199 | my $pool = extract_param($param, 'pool'); | |
5c752bbf | 200 | |
9c2d4ce9 DM |
201 | my $storage_cfg = cfs_read_file("storage.cfg"); |
202 | ||
203 | my $scfg = PVE::Storage::storage_check_node($storage_cfg, $storage, $node); | |
204 | ||
205 | raise_param_exc({ storage => "storage '$storage' does not support container root directories"}) | |
206 | if !$scfg->{content}->{rootdir}; | |
207 | ||
9c2d4ce9 DM |
208 | if (defined($pool)) { |
209 | $rpcenv->check_pool_exist($pool); | |
210 | $rpcenv->check_perm_modify($authuser, "/pool/$pool"); | |
5c752bbf | 211 | } |
9c2d4ce9 DM |
212 | |
213 | if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) { | |
214 | # OK | |
215 | } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) { | |
216 | # OK | |
217 | } elsif ($restore && $force && $same_container_exists && | |
218 | $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) { | |
219 | # OK: user has VM.Backup permissions, and want to restore an existing VM | |
220 | } else { | |
221 | raise_perm_exc(); | |
222 | } | |
223 | ||
224 | &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]); | |
225 | ||
226 | PVE::Storage::activate_storage($storage_cfg, $storage); | |
227 | ||
228 | my $ostemplate = extract_param($param, 'ostemplate'); | |
5c752bbf | 229 | |
9c2d4ce9 DM |
230 | my $archive; |
231 | ||
232 | if ($ostemplate eq '-') { | |
148d1cb4 DM |
233 | die "pipe requires cli environment\n" |
234 | if $rpcenv->{type} ne 'cli'; | |
235 | die "pipe can only be used with restore tasks\n" | |
236 | if !$restore; | |
237 | $archive = '-'; | |
9c2d4ce9 DM |
238 | } else { |
239 | $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate); | |
240 | $archive = PVE::Storage::abs_filesystem_path($storage_cfg, $ostemplate); | |
241 | } | |
242 | ||
9c2d4ce9 | 243 | my $conf = {}; |
5c752bbf | 244 | |
93285df8 DM |
245 | $param->{hostname} ||= "CT$vmid"; |
246 | $param->{memory} ||= 512; | |
44da0641 | 247 | $param->{swap} = 512 if !defined($param->{swap}); |
5b4657d0 DM |
248 | |
249 | PVE::LXC::update_lxc_config($vmid, $conf, 0, $param); | |
93285df8 DM |
250 | |
251 | # assigng default names, so that we can configure network with LXCSetup | |
252 | foreach my $k (keys %$conf) { | |
253 | next if $k !~ m/^net(\d+)$/; | |
254 | my $d = $conf->{$k}; | |
255 | my $ind = $1; | |
5b4657d0 | 256 | $d->{name} = "eth$ind"; # fixme: do not overwrite settings! |
93285df8 | 257 | } |
9c2d4ce9 | 258 | |
5b4657d0 DM |
259 | # use user namespace ? |
260 | # disable for now, because kernel 3.10.0 does not support it | |
261 | #$conf->{'lxc.id_map'} = ["u 0 100000 65536", "g 0 100000 65536"]; | |
9c2d4ce9 | 262 | |
148d1cb4 DM |
263 | my $check_vmid_usage = sub { |
264 | if ($force) { | |
265 | die "cant overwrite running container\n" | |
266 | if PVE::LXC::check_running($vmid); | |
267 | } else { | |
268 | PVE::Cluster::check_vmid_unused($vmid); | |
269 | } | |
270 | }; | |
271 | ||
5b4657d0 | 272 | my $code = sub { |
10fc3ba5 | 273 | |
148d1cb4 DM |
274 | &$check_vmid_usage(); # final check after locking |
275 | ||
276 | PVE::Cluster::check_cfs_quorum(); | |
277 | ||
278 | PVE::LXCCreate::create_rootfs($storage_cfg, $storage, $param->{disk}, $vmid, $conf, | |
279 | $archive, $password, $restore); | |
9c2d4ce9 | 280 | }; |
5c752bbf | 281 | |
9c2d4ce9 DM |
282 | my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); }; |
283 | ||
148d1cb4 DM |
284 | &$check_vmid_usage(); # first check before locking |
285 | ||
286 | return $rpcenv->fork_worker($restore ? 'vzrestore' : 'vzcreate', | |
9c2d4ce9 | 287 | $vmid, $authuser, $realcmd); |
5c752bbf | 288 | |
9c2d4ce9 DM |
289 | }}); |
290 | ||
f76a2828 | 291 | my $vm_config_perm_list = [ |
5c752bbf DM |
292 | 'VM.Config.Disk', |
293 | 'VM.Config.CPU', | |
294 | 'VM.Config.Memory', | |
295 | 'VM.Config.Network', | |
f76a2828 DM |
296 | 'VM.Config.Options', |
297 | ]; | |
298 | ||
299 | __PACKAGE__->register_method({ | |
5c752bbf DM |
300 | name => 'update_vm', |
301 | path => '{vmid}/config', | |
f76a2828 DM |
302 | method => 'PUT', |
303 | protected => 1, | |
304 | proxyto => 'node', | |
305 | description => "Set container options.", | |
306 | permissions => { | |
307 | check => ['perm', '/vms/{vmid}', $vm_config_perm_list, any => 1], | |
308 | }, | |
309 | parameters => { | |
310 | additionalProperties => 0, | |
311 | properties => PVE::LXC::json_config_properties( | |
312 | { | |
313 | node => get_standard_option('pve-node'), | |
314 | vmid => get_standard_option('pve-vmid'), | |
ec52ac21 DM |
315 | delete => { |
316 | type => 'string', format => 'pve-configid-list', | |
317 | description => "A list of settings you want to delete.", | |
318 | optional => 1, | |
319 | }, | |
f76a2828 DM |
320 | digest => { |
321 | type => 'string', | |
322 | description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.', | |
323 | maxLength => 40, | |
5c752bbf | 324 | optional => 1, |
f76a2828 DM |
325 | } |
326 | }), | |
327 | }, | |
328 | returns => { type => 'null'}, | |
329 | code => sub { | |
330 | my ($param) = @_; | |
331 | ||
332 | my $rpcenv = PVE::RPCEnvironment::get(); | |
333 | ||
334 | my $authuser = $rpcenv->get_user(); | |
335 | ||
336 | my $node = extract_param($param, 'node'); | |
337 | ||
338 | my $vmid = extract_param($param, 'vmid'); | |
339 | ||
340 | my $digest = extract_param($param, 'digest'); | |
341 | ||
342 | die "no options specified\n" if !scalar(keys %$param); | |
343 | ||
ec52ac21 DM |
344 | my $delete_str = extract_param($param, 'delete'); |
345 | my @delete = PVE::Tools::split_list($delete_str); | |
5c752bbf | 346 | |
ec52ac21 | 347 | &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [@delete]); |
5c752bbf | 348 | |
ec52ac21 DM |
349 | foreach my $opt (@delete) { |
350 | raise_param_exc({ delete => "you can't use '-$opt' and " . | |
351 | "-delete $opt' at the same time" }) | |
352 | if defined($param->{$opt}); | |
5c752bbf | 353 | |
ec52ac21 DM |
354 | if (!PVE::LXC::option_exists($opt)) { |
355 | raise_param_exc({ delete => "unknown option '$opt'" }); | |
356 | } | |
357 | } | |
358 | ||
f76a2828 DM |
359 | &$check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]); |
360 | ||
361 | my $code = sub { | |
362 | ||
363 | my $conf = PVE::LXC::load_config($vmid); | |
364 | ||
365 | PVE::Tools::assert_if_modified($digest, $conf->{digest}); | |
366 | ||
93285df8 | 367 | my $running = PVE::LXC::check_running($vmid); |
ec52ac21 | 368 | |
5b4657d0 | 369 | PVE::LXC::update_lxc_config($vmid, $conf, $running, $param, \@delete); |
ec52ac21 DM |
370 | |
371 | PVE::LXC::write_config($vmid, $conf); | |
f76a2828 DM |
372 | }; |
373 | ||
374 | PVE::LXC::lock_container($vmid, undef, $code); | |
375 | ||
376 | return undef; | |
377 | }}); | |
378 | ||
379 | __PACKAGE__->register_method ({ | |
5c752bbf | 380 | subclass => "PVE::API2::Firewall::CT", |
f76a2828 DM |
381 | path => '{vmid}/firewall', |
382 | }); | |
383 | ||
384 | __PACKAGE__->register_method({ | |
385 | name => 'vmdiridx', | |
5c752bbf | 386 | path => '{vmid}', |
f76a2828 DM |
387 | method => 'GET', |
388 | proxyto => 'node', | |
389 | description => "Directory index", | |
390 | permissions => { | |
391 | user => 'all', | |
392 | }, | |
393 | parameters => { | |
394 | additionalProperties => 0, | |
395 | properties => { | |
396 | node => get_standard_option('pve-node'), | |
397 | vmid => get_standard_option('pve-vmid'), | |
398 | }, | |
399 | }, | |
400 | returns => { | |
401 | type => 'array', | |
402 | items => { | |
403 | type => "object", | |
404 | properties => { | |
405 | subdir => { type => 'string' }, | |
406 | }, | |
407 | }, | |
408 | links => [ { rel => 'child', href => "{subdir}" } ], | |
409 | }, | |
410 | code => sub { | |
411 | my ($param) = @_; | |
412 | ||
413 | # test if VM exists | |
e901d418 | 414 | my $conf = PVE::LXC::load_config($param->{vmid}); |
f76a2828 DM |
415 | |
416 | my $res = [ | |
417 | { subdir => 'config' }, | |
fff3a342 DM |
418 | { subdir => 'status' }, |
419 | { subdir => 'vncproxy' }, | |
420 | { subdir => 'vncwebsocket' }, | |
421 | { subdir => 'spiceproxy' }, | |
422 | { subdir => 'migrate' }, | |
f76a2828 DM |
423 | # { subdir => 'initlog' }, |
424 | { subdir => 'rrd' }, | |
425 | { subdir => 'rrddata' }, | |
426 | { subdir => 'firewall' }, | |
cc5392c8 | 427 | { subdir => 'snapshot' }, |
f76a2828 | 428 | ]; |
5c752bbf | 429 | |
f76a2828 DM |
430 | return $res; |
431 | }}); | |
432 | ||
433 | __PACKAGE__->register_method({ | |
5c752bbf DM |
434 | name => 'rrd', |
435 | path => '{vmid}/rrd', | |
f76a2828 DM |
436 | method => 'GET', |
437 | protected => 1, # fixme: can we avoid that? | |
438 | permissions => { | |
439 | check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], | |
440 | }, | |
441 | description => "Read VM RRD statistics (returns PNG)", | |
442 | parameters => { | |
443 | additionalProperties => 0, | |
444 | properties => { | |
445 | node => get_standard_option('pve-node'), | |
446 | vmid => get_standard_option('pve-vmid'), | |
447 | timeframe => { | |
448 | description => "Specify the time frame you are interested in.", | |
449 | type => 'string', | |
450 | enum => [ 'hour', 'day', 'week', 'month', 'year' ], | |
451 | }, | |
452 | ds => { | |
453 | description => "The list of datasources you want to display.", | |
454 | type => 'string', format => 'pve-configid-list', | |
455 | }, | |
456 | cf => { | |
457 | description => "The RRD consolidation function", | |
458 | type => 'string', | |
459 | enum => [ 'AVERAGE', 'MAX' ], | |
460 | optional => 1, | |
461 | }, | |
462 | }, | |
463 | }, | |
464 | returns => { | |
465 | type => "object", | |
466 | properties => { | |
467 | filename => { type => 'string' }, | |
468 | }, | |
469 | }, | |
470 | code => sub { | |
471 | my ($param) = @_; | |
472 | ||
473 | return PVE::Cluster::create_rrd_graph( | |
5c752bbf | 474 | "pve2-vm/$param->{vmid}", $param->{timeframe}, |
f76a2828 | 475 | $param->{ds}, $param->{cf}); |
5c752bbf | 476 | |
f76a2828 DM |
477 | }}); |
478 | ||
479 | __PACKAGE__->register_method({ | |
5c752bbf DM |
480 | name => 'rrddata', |
481 | path => '{vmid}/rrddata', | |
f76a2828 DM |
482 | method => 'GET', |
483 | protected => 1, # fixme: can we avoid that? | |
484 | permissions => { | |
485 | check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], | |
486 | }, | |
487 | description => "Read VM RRD statistics", | |
488 | parameters => { | |
489 | additionalProperties => 0, | |
490 | properties => { | |
491 | node => get_standard_option('pve-node'), | |
492 | vmid => get_standard_option('pve-vmid'), | |
493 | timeframe => { | |
494 | description => "Specify the time frame you are interested in.", | |
495 | type => 'string', | |
496 | enum => [ 'hour', 'day', 'week', 'month', 'year' ], | |
497 | }, | |
498 | cf => { | |
499 | description => "The RRD consolidation function", | |
500 | type => 'string', | |
501 | enum => [ 'AVERAGE', 'MAX' ], | |
502 | optional => 1, | |
503 | }, | |
504 | }, | |
505 | }, | |
506 | returns => { | |
507 | type => "array", | |
508 | items => { | |
509 | type => "object", | |
510 | properties => {}, | |
511 | }, | |
512 | }, | |
513 | code => sub { | |
514 | my ($param) = @_; | |
515 | ||
516 | return PVE::Cluster::create_rrd_data( | |
517 | "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf}); | |
518 | }}); | |
519 | ||
520 | ||
521 | __PACKAGE__->register_method({ | |
5c752bbf DM |
522 | name => 'vm_config', |
523 | path => '{vmid}/config', | |
f76a2828 DM |
524 | method => 'GET', |
525 | proxyto => 'node', | |
526 | description => "Get container configuration.", | |
527 | permissions => { | |
528 | check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], | |
529 | }, | |
530 | parameters => { | |
531 | additionalProperties => 0, | |
532 | properties => { | |
533 | node => get_standard_option('pve-node'), | |
534 | vmid => get_standard_option('pve-vmid'), | |
535 | }, | |
536 | }, | |
5c752bbf | 537 | returns => { |
f76a2828 DM |
538 | type => "object", |
539 | properties => { | |
540 | digest => { | |
541 | type => 'string', | |
542 | description => 'SHA1 digest of configuration file. This can be used to prevent concurrent modifications.', | |
543 | } | |
544 | }, | |
545 | }, | |
546 | code => sub { | |
547 | my ($param) = @_; | |
548 | ||
549 | my $lxc_conf = PVE::LXC::load_config($param->{vmid}); | |
550 | ||
551 | # NOTE: we only return selected/converted values | |
5c752bbf | 552 | |
b80dd50a | 553 | my $conf = PVE::LXC::lxc_conf_to_pve($param->{vmid}, $lxc_conf); |
f76a2828 DM |
554 | |
555 | my $stcfg = PVE::Cluster::cfs_read_file("storage.cfg"); | |
556 | ||
557 | my ($sid, undef, $path) = &$get_container_storage($stcfg, $param->{vmid}, $lxc_conf); | |
558 | $conf->{storage} = $sid || $path; | |
559 | ||
f76a2828 DM |
560 | return $conf; |
561 | }}); | |
562 | ||
563 | __PACKAGE__->register_method({ | |
5c752bbf DM |
564 | name => 'destroy_vm', |
565 | path => '{vmid}', | |
f76a2828 DM |
566 | method => 'DELETE', |
567 | protected => 1, | |
568 | proxyto => 'node', | |
569 | description => "Destroy the container (also delete all uses files).", | |
570 | permissions => { | |
571 | check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']], | |
572 | }, | |
573 | parameters => { | |
574 | additionalProperties => 0, | |
575 | properties => { | |
576 | node => get_standard_option('pve-node'), | |
577 | vmid => get_standard_option('pve-vmid'), | |
578 | }, | |
579 | }, | |
5c752bbf | 580 | returns => { |
f76a2828 DM |
581 | type => 'string', |
582 | }, | |
583 | code => sub { | |
584 | my ($param) = @_; | |
585 | ||
586 | my $rpcenv = PVE::RPCEnvironment::get(); | |
587 | ||
588 | my $authuser = $rpcenv->get_user(); | |
589 | ||
590 | my $vmid = $param->{vmid}; | |
591 | ||
592 | # test if container exists | |
593 | my $conf = PVE::LXC::load_config($param->{vmid}); | |
594 | ||
611fe3aa DM |
595 | my $storage_cfg = cfs_read_file("storage.cfg"); |
596 | ||
597 | my $code = sub { | |
148d1cb4 | 598 | PVE::LXC::destory_lxc_container($storage_cfg, $vmid, $conf); |
f76a2828 DM |
599 | PVE::AccessControl::remove_vm_from_pool($vmid); |
600 | }; | |
601 | ||
611fe3aa DM |
602 | my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); }; |
603 | ||
f76a2828 DM |
604 | return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd); |
605 | }}); | |
606 | ||
fff3a342 DM |
607 | my $sslcert; |
608 | ||
609 | __PACKAGE__->register_method ({ | |
5b4657d0 DM |
610 | name => 'vncproxy', |
611 | path => '{vmid}/vncproxy', | |
fff3a342 DM |
612 | method => 'POST', |
613 | protected => 1, | |
614 | permissions => { | |
615 | check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]], | |
616 | }, | |
617 | description => "Creates a TCP VNC proxy connections.", | |
618 | parameters => { | |
619 | additionalProperties => 0, | |
620 | properties => { | |
621 | node => get_standard_option('pve-node'), | |
622 | vmid => get_standard_option('pve-vmid'), | |
623 | websocket => { | |
624 | optional => 1, | |
625 | type => 'boolean', | |
626 | description => "use websocket instead of standard VNC.", | |
627 | }, | |
628 | }, | |
629 | }, | |
5b4657d0 | 630 | returns => { |
fff3a342 DM |
631 | additionalProperties => 0, |
632 | properties => { | |
633 | user => { type => 'string' }, | |
634 | ticket => { type => 'string' }, | |
635 | cert => { type => 'string' }, | |
636 | port => { type => 'integer' }, | |
637 | upid => { type => 'string' }, | |
638 | }, | |
639 | }, | |
640 | code => sub { | |
641 | my ($param) = @_; | |
642 | ||
643 | my $rpcenv = PVE::RPCEnvironment::get(); | |
644 | ||
645 | my $authuser = $rpcenv->get_user(); | |
646 | ||
647 | my $vmid = $param->{vmid}; | |
648 | my $node = $param->{node}; | |
649 | ||
650 | my $authpath = "/vms/$vmid"; | |
651 | ||
652 | my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath); | |
653 | ||
654 | $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192) | |
655 | if !$sslcert; | |
656 | ||
ec2c57eb | 657 | my ($remip, $family); |
5b4657d0 | 658 | |
fff3a342 | 659 | if ($node ne PVE::INotify::nodename()) { |
85ae6211 | 660 | ($remip, $family) = PVE::Cluster::remote_node_ip($node); |
ec2c57eb WB |
661 | } else { |
662 | $family = PVE::Tools::get_host_address_family($node); | |
fff3a342 DM |
663 | } |
664 | ||
ec2c57eb WB |
665 | my $port = PVE::Tools::next_vnc_port($family); |
666 | ||
fff3a342 DM |
667 | # NOTE: vncterm VNC traffic is already TLS encrypted, |
668 | # so we select the fastest chipher here (or 'none'?) | |
5b4657d0 | 669 | my $remcmd = $remip ? |
fff3a342 DM |
670 | ['/usr/bin/ssh', '-t', $remip] : []; |
671 | ||
5b4657d0 DM |
672 | my $shcmd = [ '/usr/bin/dtach', '-A', |
673 | "/var/run/dtach/vzctlconsole$vmid", | |
674 | '-r', 'winch', '-z', | |
fff3a342 DM |
675 | 'lxc-console', '-n', $vmid ]; |
676 | ||
677 | my $realcmd = sub { | |
678 | my $upid = shift; | |
679 | ||
5b4657d0 | 680 | syslog ('info', "starting lxc vnc proxy $upid\n"); |
fff3a342 | 681 | |
5b4657d0 | 682 | my $timeout = 10; |
fff3a342 DM |
683 | |
684 | my $cmd = ['/usr/bin/vncterm', '-rfbport', $port, | |
5b4657d0 | 685 | '-timeout', $timeout, '-authpath', $authpath, |
fff3a342 DM |
686 | '-perm', 'VM.Console']; |
687 | ||
688 | if ($param->{websocket}) { | |
5b4657d0 | 689 | $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm |
fff3a342 DM |
690 | push @$cmd, '-notls', '-listen', 'localhost'; |
691 | } | |
692 | ||
693 | push @$cmd, '-c', @$remcmd, @$shcmd; | |
694 | ||
695 | run_command($cmd); | |
696 | ||
697 | return; | |
698 | }; | |
699 | ||
700 | my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd); | |
701 | ||
702 | PVE::Tools::wait_for_vnc_port($port); | |
703 | ||
704 | return { | |
705 | user => $authuser, | |
706 | ticket => $ticket, | |
5b4657d0 DM |
707 | port => $port, |
708 | upid => $upid, | |
709 | cert => $sslcert, | |
fff3a342 DM |
710 | }; |
711 | }}); | |
712 | ||
713 | __PACKAGE__->register_method({ | |
714 | name => 'vncwebsocket', | |
715 | path => '{vmid}/vncwebsocket', | |
716 | method => 'GET', | |
5b4657d0 | 717 | permissions => { |
fff3a342 DM |
718 | description => "You also need to pass a valid ticket (vncticket).", |
719 | check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]], | |
720 | }, | |
721 | description => "Opens a weksocket for VNC traffic.", | |
722 | parameters => { | |
723 | additionalProperties => 0, | |
724 | properties => { | |
725 | node => get_standard_option('pve-node'), | |
726 | vmid => get_standard_option('pve-vmid'), | |
727 | vncticket => { | |
728 | description => "Ticket from previous call to vncproxy.", | |
729 | type => 'string', | |
730 | maxLength => 512, | |
731 | }, | |
732 | port => { | |
733 | description => "Port number returned by previous vncproxy call.", | |
734 | type => 'integer', | |
735 | minimum => 5900, | |
736 | maximum => 5999, | |
737 | }, | |
738 | }, | |
739 | }, | |
740 | returns => { | |
741 | type => "object", | |
742 | properties => { | |
743 | port => { type => 'string' }, | |
744 | }, | |
745 | }, | |
746 | code => sub { | |
747 | my ($param) = @_; | |
748 | ||
749 | my $rpcenv = PVE::RPCEnvironment::get(); | |
750 | ||
751 | my $authuser = $rpcenv->get_user(); | |
752 | ||
753 | my $authpath = "/vms/$param->{vmid}"; | |
754 | ||
755 | PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath); | |
756 | ||
757 | my $port = $param->{port}; | |
5b4657d0 | 758 | |
fff3a342 DM |
759 | return { port => $port }; |
760 | }}); | |
761 | ||
762 | __PACKAGE__->register_method ({ | |
5b4657d0 DM |
763 | name => 'spiceproxy', |
764 | path => '{vmid}/spiceproxy', | |
fff3a342 DM |
765 | method => 'POST', |
766 | protected => 1, | |
767 | proxyto => 'node', | |
768 | permissions => { | |
769 | check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]], | |
770 | }, | |
771 | description => "Returns a SPICE configuration to connect to the CT.", | |
772 | parameters => { | |
773 | additionalProperties => 0, | |
774 | properties => { | |
775 | node => get_standard_option('pve-node'), | |
776 | vmid => get_standard_option('pve-vmid'), | |
777 | proxy => get_standard_option('spice-proxy', { optional => 1 }), | |
778 | }, | |
779 | }, | |
780 | returns => get_standard_option('remote-viewer-config'), | |
781 | code => sub { | |
782 | my ($param) = @_; | |
783 | ||
784 | my $vmid = $param->{vmid}; | |
785 | my $node = $param->{node}; | |
786 | my $proxy = $param->{proxy}; | |
787 | ||
788 | my $authpath = "/vms/$vmid"; | |
789 | my $permissions = 'VM.Console'; | |
790 | ||
5b4657d0 DM |
791 | my $shcmd = ['/usr/bin/dtach', '-A', |
792 | "/var/run/dtach/vzctlconsole$vmid", | |
793 | '-r', 'winch', '-z', | |
fff3a342 DM |
794 | 'lxc-console', '-n', $vmid]; |
795 | ||
796 | my $title = "CT $vmid"; | |
797 | ||
798 | return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd); | |
799 | }}); | |
5c752bbf DM |
800 | |
801 | __PACKAGE__->register_method({ | |
802 | name => 'vmcmdidx', | |
803 | path => '{vmid}/status', | |
804 | method => 'GET', | |
805 | proxyto => 'node', | |
806 | description => "Directory index", | |
807 | permissions => { | |
808 | user => 'all', | |
809 | }, | |
810 | parameters => { | |
811 | additionalProperties => 0, | |
812 | properties => { | |
813 | node => get_standard_option('pve-node'), | |
814 | vmid => get_standard_option('pve-vmid'), | |
815 | }, | |
816 | }, | |
817 | returns => { | |
818 | type => 'array', | |
819 | items => { | |
820 | type => "object", | |
821 | properties => { | |
822 | subdir => { type => 'string' }, | |
823 | }, | |
824 | }, | |
825 | links => [ { rel => 'child', href => "{subdir}" } ], | |
826 | }, | |
827 | code => sub { | |
828 | my ($param) = @_; | |
829 | ||
830 | # test if VM exists | |
831 | my $conf = PVE::OpenVZ::load_config($param->{vmid}); | |
832 | ||
833 | my $res = [ | |
834 | { subdir => 'current' }, | |
835 | { subdir => 'start' }, | |
836 | { subdir => 'stop' }, | |
837 | { subdir => 'shutdown' }, | |
838 | { subdir => 'migrate' }, | |
839 | ]; | |
840 | ||
841 | return $res; | |
842 | }}); | |
843 | ||
844 | __PACKAGE__->register_method({ | |
845 | name => 'vm_status', | |
846 | path => '{vmid}/status/current', | |
847 | method => 'GET', | |
848 | proxyto => 'node', | |
849 | protected => 1, # openvz /proc entries are only readable by root | |
850 | description => "Get virtual machine status.", | |
851 | permissions => { | |
852 | check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], | |
853 | }, | |
854 | parameters => { | |
855 | additionalProperties => 0, | |
856 | properties => { | |
857 | node => get_standard_option('pve-node'), | |
858 | vmid => get_standard_option('pve-vmid'), | |
859 | }, | |
860 | }, | |
861 | returns => { type => 'object' }, | |
862 | code => sub { | |
863 | my ($param) = @_; | |
864 | ||
865 | # test if VM exists | |
866 | my $conf = PVE::LXC::load_config($param->{vmid}); | |
867 | ||
868 | my $vmstatus = PVE::LXC::vmstatus($param->{vmid}); | |
869 | my $status = $vmstatus->{$param->{vmid}}; | |
870 | ||
871 | $status->{ha} = PVE::HA::Config::vm_is_ha_managed($param->{vmid}) ? 1 : 0; | |
872 | ||
873 | return $status; | |
874 | }}); | |
875 | ||
876 | __PACKAGE__->register_method({ | |
877 | name => 'vm_start', | |
878 | path => '{vmid}/status/start', | |
879 | method => 'POST', | |
880 | protected => 1, | |
881 | proxyto => 'node', | |
882 | description => "Start the container.", | |
883 | permissions => { | |
884 | check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]], | |
885 | }, | |
886 | parameters => { | |
887 | additionalProperties => 0, | |
888 | properties => { | |
889 | node => get_standard_option('pve-node'), | |
890 | vmid => get_standard_option('pve-vmid'), | |
891 | }, | |
892 | }, | |
893 | returns => { | |
894 | type => 'string', | |
895 | }, | |
896 | code => sub { | |
897 | my ($param) = @_; | |
898 | ||
899 | my $rpcenv = PVE::RPCEnvironment::get(); | |
900 | ||
901 | my $authuser = $rpcenv->get_user(); | |
902 | ||
903 | my $node = extract_param($param, 'node'); | |
904 | ||
905 | my $vmid = extract_param($param, 'vmid'); | |
906 | ||
907 | die "CT $vmid already running\n" if PVE::LXC::check_running($vmid); | |
908 | ||
909 | if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') { | |
910 | ||
911 | my $hacmd = sub { | |
912 | my $upid = shift; | |
913 | ||
914 | my $service = "ct:$vmid"; | |
915 | ||
916 | my $cmd = ['ha-manager', 'enable', $service]; | |
917 | ||
918 | print "Executing HA start for CT $vmid\n"; | |
919 | ||
920 | PVE::Tools::run_command($cmd); | |
921 | ||
922 | return; | |
923 | }; | |
924 | ||
925 | return $rpcenv->fork_worker('hastart', $vmid, $authuser, $hacmd); | |
926 | ||
927 | } else { | |
928 | ||
929 | my $realcmd = sub { | |
930 | my $upid = shift; | |
931 | ||
932 | syslog('info', "starting CT $vmid: $upid\n"); | |
933 | ||
934 | my $conf = PVE::LXC::load_config($vmid); | |
935 | my $stcfg = cfs_read_file("storage.cfg"); | |
936 | if (my $sid = &$get_container_storage($stcfg, $vmid, $conf)) { | |
937 | PVE::Storage::activate_storage($stcfg, $sid); | |
938 | } | |
939 | ||
22b9057a | 940 | my $cmd = ['lxc-start', '-n', $vmid]; |
5c752bbf | 941 | |
22b9057a | 942 | run_command($cmd); |
5c752bbf DM |
943 | |
944 | return; | |
945 | }; | |
946 | ||
947 | return $rpcenv->fork_worker('vzstart', $vmid, $authuser, $realcmd); | |
948 | } | |
949 | }}); | |
950 | ||
951 | __PACKAGE__->register_method({ | |
952 | name => 'vm_stop', | |
953 | path => '{vmid}/status/stop', | |
954 | method => 'POST', | |
955 | protected => 1, | |
956 | proxyto => 'node', | |
957 | description => "Stop the container.", | |
958 | permissions => { | |
959 | check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]], | |
960 | }, | |
961 | parameters => { | |
962 | additionalProperties => 0, | |
963 | properties => { | |
964 | node => get_standard_option('pve-node'), | |
965 | vmid => get_standard_option('pve-vmid'), | |
966 | }, | |
967 | }, | |
968 | returns => { | |
969 | type => 'string', | |
970 | }, | |
971 | code => sub { | |
972 | my ($param) = @_; | |
973 | ||
974 | my $rpcenv = PVE::RPCEnvironment::get(); | |
975 | ||
976 | my $authuser = $rpcenv->get_user(); | |
977 | ||
978 | my $node = extract_param($param, 'node'); | |
979 | ||
980 | my $vmid = extract_param($param, 'vmid'); | |
981 | ||
982 | die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid); | |
983 | ||
984 | if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') { | |
985 | ||
986 | my $hacmd = sub { | |
987 | my $upid = shift; | |
988 | ||
989 | my $service = "ct:$vmid"; | |
990 | ||
991 | my $cmd = ['ha-manager', 'disable', $service]; | |
992 | ||
993 | print "Executing HA stop for CT $vmid\n"; | |
994 | ||
995 | PVE::Tools::run_command($cmd); | |
996 | ||
997 | return; | |
998 | }; | |
999 | ||
1000 | return $rpcenv->fork_worker('hastop', $vmid, $authuser, $hacmd); | |
1001 | ||
1002 | } else { | |
1003 | ||
1004 | my $realcmd = sub { | |
1005 | my $upid = shift; | |
1006 | ||
1007 | syslog('info', "stoping CT $vmid: $upid\n"); | |
1008 | ||
1009 | my $cmd = ['lxc-stop', '-n', $vmid, '--kill']; | |
1010 | ||
1011 | run_command($cmd); | |
1012 | ||
1013 | return; | |
1014 | }; | |
1015 | ||
1016 | return $rpcenv->fork_worker('vzstop', $vmid, $authuser, $realcmd); | |
1017 | } | |
1018 | }}); | |
1019 | ||
1020 | __PACKAGE__->register_method({ | |
1021 | name => 'vm_shutdown', | |
1022 | path => '{vmid}/status/shutdown', | |
1023 | method => 'POST', | |
1024 | protected => 1, | |
1025 | proxyto => 'node', | |
1026 | description => "Shutdown the container.", | |
1027 | permissions => { | |
1028 | check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]], | |
1029 | }, | |
1030 | parameters => { | |
1031 | additionalProperties => 0, | |
1032 | properties => { | |
1033 | node => get_standard_option('pve-node'), | |
1034 | vmid => get_standard_option('pve-vmid'), | |
1035 | timeout => { | |
1036 | description => "Wait maximal timeout seconds.", | |
1037 | type => 'integer', | |
1038 | minimum => 0, | |
1039 | optional => 1, | |
1040 | default => 60, | |
1041 | }, | |
1042 | forceStop => { | |
1043 | description => "Make sure the Container stops.", | |
1044 | type => 'boolean', | |
1045 | optional => 1, | |
1046 | default => 0, | |
1047 | } | |
1048 | }, | |
1049 | }, | |
1050 | returns => { | |
1051 | type => 'string', | |
1052 | }, | |
1053 | code => sub { | |
1054 | my ($param) = @_; | |
1055 | ||
1056 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1057 | ||
1058 | my $authuser = $rpcenv->get_user(); | |
1059 | ||
1060 | my $node = extract_param($param, 'node'); | |
1061 | ||
1062 | my $vmid = extract_param($param, 'vmid'); | |
1063 | ||
1064 | my $timeout = extract_param($param, 'timeout'); | |
1065 | ||
1066 | die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid); | |
1067 | ||
1068 | my $realcmd = sub { | |
1069 | my $upid = shift; | |
1070 | ||
1071 | syslog('info', "shutdown CT $vmid: $upid\n"); | |
1072 | ||
1073 | my $cmd = ['lxc-stop', '-n', $vmid]; | |
1074 | ||
1075 | $timeout = 60 if !defined($timeout); | |
1076 | ||
1077 | push @$cmd, '--timeout', $timeout; | |
1078 | ||
1079 | eval { run_command($cmd, timeout => $timeout+5); }; | |
1080 | my $err = $@; | |
1081 | return if !$err; | |
1082 | ||
1083 | die $err if !$param->{forceStop}; | |
1084 | ||
1085 | warn "shutdown failed - forcing stop now\n"; | |
1086 | ||
1087 | push @$cmd, '--kill'; | |
1088 | run_command($cmd); | |
1089 | ||
1090 | return; | |
1091 | }; | |
1092 | ||
1093 | my $upid = $rpcenv->fork_worker('vzshutdown', $vmid, $authuser, $realcmd); | |
1094 | ||
1095 | return $upid; | |
1096 | }}); | |
1097 | ||
1098 | __PACKAGE__->register_method({ | |
1099 | name => 'vm_suspend', | |
1100 | path => '{vmid}/status/suspend', | |
1101 | method => 'POST', | |
1102 | protected => 1, | |
1103 | proxyto => 'node', | |
1104 | description => "Suspend the container.", | |
1105 | permissions => { | |
1106 | check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]], | |
1107 | }, | |
1108 | parameters => { | |
1109 | additionalProperties => 0, | |
1110 | properties => { | |
1111 | node => get_standard_option('pve-node'), | |
1112 | vmid => get_standard_option('pve-vmid'), | |
1113 | }, | |
1114 | }, | |
1115 | returns => { | |
1116 | type => 'string', | |
1117 | }, | |
1118 | code => sub { | |
1119 | my ($param) = @_; | |
1120 | ||
1121 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1122 | ||
1123 | my $authuser = $rpcenv->get_user(); | |
1124 | ||
1125 | my $node = extract_param($param, 'node'); | |
1126 | ||
1127 | my $vmid = extract_param($param, 'vmid'); | |
1128 | ||
1129 | die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid); | |
1130 | ||
1131 | my $realcmd = sub { | |
1132 | my $upid = shift; | |
1133 | ||
1134 | syslog('info', "suspend CT $vmid: $upid\n"); | |
1135 | ||
1136 | my $cmd = ['lxc-checkpoint', '-n', $vmid, '-s', '-D', '/var/liv/vz/dump']; | |
1137 | ||
1138 | run_command($cmd); | |
1139 | ||
1140 | return; | |
1141 | }; | |
1142 | ||
1143 | my $upid = $rpcenv->fork_worker('vzsuspend', $vmid, $authuser, $realcmd); | |
1144 | ||
1145 | return $upid; | |
1146 | }}); | |
1147 | ||
1148 | __PACKAGE__->register_method({ | |
1149 | name => 'vm_resume', | |
1150 | path => '{vmid}/status/resume', | |
1151 | method => 'POST', | |
1152 | protected => 1, | |
1153 | proxyto => 'node', | |
1154 | description => "Resume the container.", | |
1155 | permissions => { | |
1156 | check => ['perm', '/vms/{vmid}', [ 'VM.PowerMgmt' ]], | |
1157 | }, | |
1158 | parameters => { | |
1159 | additionalProperties => 0, | |
1160 | properties => { | |
1161 | node => get_standard_option('pve-node'), | |
1162 | vmid => get_standard_option('pve-vmid'), | |
1163 | }, | |
1164 | }, | |
1165 | returns => { | |
1166 | type => 'string', | |
1167 | }, | |
1168 | code => sub { | |
1169 | my ($param) = @_; | |
1170 | ||
1171 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1172 | ||
1173 | my $authuser = $rpcenv->get_user(); | |
1174 | ||
1175 | my $node = extract_param($param, 'node'); | |
1176 | ||
1177 | my $vmid = extract_param($param, 'vmid'); | |
1178 | ||
1179 | die "CT $vmid already running\n" if PVE::LXC::check_running($vmid); | |
1180 | ||
1181 | my $realcmd = sub { | |
1182 | my $upid = shift; | |
1183 | ||
1184 | syslog('info', "resume CT $vmid: $upid\n"); | |
1185 | ||
1186 | my $cmd = ['lxc-checkpoint', '-n', $vmid, '-r', '--foreground', | |
1187 | '-D', '/var/liv/vz/dump']; | |
1188 | ||
1189 | run_command($cmd); | |
1190 | ||
1191 | return; | |
1192 | }; | |
1193 | ||
1194 | my $upid = $rpcenv->fork_worker('vzresume', $vmid, $authuser, $realcmd); | |
1195 | ||
1196 | return $upid; | |
1197 | }}); | |
1198 | ||
1199 | __PACKAGE__->register_method({ | |
1200 | name => 'migrate_vm', | |
1201 | path => '{vmid}/migrate', | |
1202 | method => 'POST', | |
1203 | protected => 1, | |
1204 | proxyto => 'node', | |
1205 | description => "Migrate the container to another node. Creates a new migration task.", | |
1206 | permissions => { | |
1207 | check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]], | |
1208 | }, | |
1209 | parameters => { | |
1210 | additionalProperties => 0, | |
1211 | properties => { | |
1212 | node => get_standard_option('pve-node'), | |
1213 | vmid => get_standard_option('pve-vmid'), | |
1214 | target => get_standard_option('pve-node', { description => "Target node." }), | |
1215 | online => { | |
1216 | type => 'boolean', | |
1217 | description => "Use online/live migration.", | |
1218 | optional => 1, | |
1219 | }, | |
1220 | }, | |
1221 | }, | |
1222 | returns => { | |
1223 | type => 'string', | |
1224 | description => "the task ID.", | |
1225 | }, | |
1226 | code => sub { | |
1227 | my ($param) = @_; | |
1228 | ||
1229 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1230 | ||
1231 | my $authuser = $rpcenv->get_user(); | |
1232 | ||
1233 | my $target = extract_param($param, 'target'); | |
1234 | ||
1235 | my $localnode = PVE::INotify::nodename(); | |
1236 | raise_param_exc({ target => "target is local node."}) if $target eq $localnode; | |
1237 | ||
1238 | PVE::Cluster::check_cfs_quorum(); | |
1239 | ||
1240 | PVE::Cluster::check_node_exists($target); | |
1241 | ||
1242 | my $targetip = PVE::Cluster::remote_node_ip($target); | |
1243 | ||
1244 | my $vmid = extract_param($param, 'vmid'); | |
1245 | ||
1246 | # test if VM exists | |
1247 | PVE::LXC::load_config($vmid); | |
1248 | ||
1249 | # try to detect errors early | |
1250 | if (PVE::LXC::check_running($vmid)) { | |
1251 | die "cant migrate running container without --online\n" | |
1252 | if !$param->{online}; | |
1253 | } | |
1254 | ||
1255 | if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') { | |
1256 | ||
1257 | my $hacmd = sub { | |
1258 | my $upid = shift; | |
1259 | ||
1260 | my $service = "ct:$vmid"; | |
1261 | ||
1262 | my $cmd = ['ha-manager', 'migrate', $service, $target]; | |
1263 | ||
1264 | print "Executing HA migrate for CT $vmid to node $target\n"; | |
1265 | ||
1266 | PVE::Tools::run_command($cmd); | |
1267 | ||
1268 | return; | |
1269 | }; | |
1270 | ||
1271 | return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd); | |
1272 | ||
1273 | } else { | |
1274 | ||
1275 | my $realcmd = sub { | |
1276 | my $upid = shift; | |
1277 | ||
1278 | # fixme: implement lxc container migration | |
1279 | die "lxc container migration not implemented\n"; | |
1280 | ||
1281 | return; | |
1282 | }; | |
1283 | ||
1284 | return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd); | |
1285 | } | |
1286 | }}); | |
1287 | ||
489e960d WL |
1288 | __PACKAGE__->register_method({ |
1289 | name => 'snapshot', | |
1290 | path => '{vmid}/snapshot', | |
1291 | method => 'POST', | |
1292 | protected => 1, | |
1293 | proxyto => 'node', | |
1294 | description => "Snapshot a container.", | |
1295 | permissions => { | |
1296 | check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]], | |
1297 | }, | |
1298 | parameters => { | |
1299 | additionalProperties => 0, | |
1300 | properties => { | |
1301 | node => get_standard_option('pve-node'), | |
1302 | vmid => get_standard_option('pve-vmid'), | |
1303 | snapname => get_standard_option('pve-lxc-snapshot-name'), | |
1304 | vmstate => { | |
1305 | optional => 1, | |
1306 | type => 'boolean', | |
1307 | description => "Save the vmstate", | |
1308 | }, | |
1309 | description => { | |
1310 | optional => 1, | |
1311 | type => 'string', | |
1312 | description => "A textual description or comment.", | |
1313 | }, | |
1314 | }, | |
1315 | }, | |
1316 | returns => { | |
1317 | type => 'string', | |
1318 | description => "the task ID.", | |
1319 | }, | |
1320 | code => sub { | |
1321 | my ($param) = @_; | |
1322 | ||
1323 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1324 | ||
1325 | my $authuser = $rpcenv->get_user(); | |
1326 | ||
1327 | my $node = extract_param($param, 'node'); | |
1328 | ||
1329 | my $vmid = extract_param($param, 'vmid'); | |
1330 | ||
1331 | my $snapname = extract_param($param, 'snapname'); | |
1332 | ||
1333 | die "unable to use snapshot name 'current' (reserved name)\n" | |
1334 | if $snapname eq 'current'; | |
1335 | ||
1336 | my $realcmd = sub { | |
1337 | PVE::Cluster::log_msg('info', $authuser, "snapshot container $vmid: $snapname"); | |
1338 | PVE::LXC::snapshot_create($vmid, $snapname, $param->{description}); | |
1339 | }; | |
1340 | ||
1341 | return $rpcenv->fork_worker('pctsnapshot', $vmid, $authuser, $realcmd); | |
1342 | }}); | |
57ccb3f8 WL |
1343 | |
1344 | __PACKAGE__->register_method({ | |
1345 | name => 'delsnapshot', | |
1346 | path => '{vmid}/snapshot/{snapname}', | |
1347 | method => 'DELETE', | |
1348 | protected => 1, | |
1349 | proxyto => 'node', | |
1350 | description => "Delete a LXC snapshot.", | |
1351 | permissions => { | |
1352 | check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]], | |
1353 | }, | |
1354 | parameters => { | |
1355 | additionalProperties => 0, | |
1356 | properties => { | |
1357 | node => get_standard_option('pve-node'), | |
1358 | vmid => get_standard_option('pve-vmid'), | |
1359 | snapname => get_standard_option('pve-lxc-snapshot-name'), | |
1360 | force => { | |
1361 | optional => 1, | |
1362 | type => 'boolean', | |
1363 | description => "For removal from config file, even if removing disk snapshots fails.", | |
1364 | }, | |
1365 | }, | |
1366 | }, | |
1367 | returns => { | |
1368 | type => 'string', | |
1369 | description => "the task ID.", | |
1370 | }, | |
1371 | code => sub { | |
1372 | my ($param) = @_; | |
1373 | ||
1374 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1375 | ||
1376 | my $authuser = $rpcenv->get_user(); | |
1377 | ||
1378 | my $node = extract_param($param, 'node'); | |
1379 | ||
1380 | my $vmid = extract_param($param, 'vmid'); | |
1381 | ||
1382 | my $snapname = extract_param($param, 'snapname'); | |
1383 | ||
1384 | my $realcmd = sub { | |
1385 | PVE::Cluster::log_msg('info', $authuser, "delete snapshot VM $vmid: $snapname"); | |
1386 | PVE::LXC::snapshot_delete($vmid, $snapname, $param->{force}); | |
1387 | }; | |
1388 | ||
1389 | return $rpcenv->fork_worker('lxcdelsnapshot', $vmid, $authuser, $realcmd); | |
1390 | }}); | |
cc5392c8 | 1391 | |
723157f6 WL |
1392 | __PACKAGE__->register_method({ |
1393 | name => 'rollback', | |
1394 | path => '{vmid}/snapshot/{snapname}/rollback', | |
1395 | method => 'POST', | |
1396 | protected => 1, | |
1397 | proxyto => 'node', | |
1398 | description => "Rollback LXC state to specified snapshot.", | |
1399 | permissions => { | |
1400 | check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]], | |
1401 | }, | |
1402 | parameters => { | |
1403 | additionalProperties => 0, | |
1404 | properties => { | |
1405 | node => get_standard_option('pve-node'), | |
1406 | vmid => get_standard_option('pve-vmid'), | |
1407 | snapname => get_standard_option('pve-lxc-snapshot-name'), | |
1408 | }, | |
1409 | }, | |
1410 | returns => { | |
1411 | type => 'string', | |
1412 | description => "the task ID.", | |
1413 | }, | |
1414 | code => sub { | |
1415 | my ($param) = @_; | |
1416 | ||
1417 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1418 | ||
1419 | my $authuser = $rpcenv->get_user(); | |
1420 | ||
1421 | my $node = extract_param($param, 'node'); | |
1422 | ||
1423 | my $vmid = extract_param($param, 'vmid'); | |
1424 | ||
1425 | my $snapname = extract_param($param, 'snapname'); | |
1426 | ||
1427 | my $realcmd = sub { | |
1428 | PVE::Cluster::log_msg('info', $authuser, "rollback snapshot LXC $vmid: $snapname"); | |
1429 | PVE::LXC::snapshot_rollback($vmid, $snapname); | |
1430 | }; | |
1431 | ||
1432 | return $rpcenv->fork_worker('lxcrollback', $vmid, $authuser, $realcmd); | |
1433 | }}); | |
1434 | ||
cc5392c8 WL |
1435 | __PACKAGE__->register_method({ |
1436 | name => 'snapshot_list', | |
1437 | path => '{vmid}/snapshot', | |
1438 | method => 'GET', | |
1439 | description => "List all snapshots.", | |
1440 | permissions => { | |
1441 | check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], | |
1442 | }, | |
1443 | proxyto => 'node', | |
1444 | protected => 1, # lxc pid files are only readable by root | |
1445 | parameters => { | |
1446 | additionalProperties => 0, | |
1447 | properties => { | |
1448 | vmid => get_standard_option('pve-vmid'), | |
1449 | node => get_standard_option('pve-node'), | |
1450 | }, | |
1451 | }, | |
1452 | returns => { | |
1453 | type => 'array', | |
1454 | items => { | |
1455 | type => "object", | |
1456 | properties => {}, | |
1457 | }, | |
1458 | links => [ { rel => 'child', href => "{name}" } ], | |
1459 | }, | |
1460 | code => sub { | |
1461 | my ($param) = @_; | |
1462 | ||
1463 | my $vmid = $param->{vmid}; | |
1464 | ||
1465 | my $conf = PVE::LXC::load_config($vmid); | |
1466 | my $snaphash = $conf->{snapshots} || {}; | |
1467 | ||
1468 | my $res = []; | |
1469 | ||
1470 | foreach my $name (keys %$snaphash) { | |
1471 | my $d = $snaphash->{$name}; | |
1472 | my $item = { | |
1473 | name => $name, | |
1474 | snaptime => $d->{'pve.snaptime'} || 0, | |
1475 | description => $d->{'pve.snapcomment'} || '', | |
1476 | }; | |
1477 | $item->{parent} = $d->{'pve.parent'} if $d->{'pve.parent'}; | |
1478 | $item->{snapstate} = $d->{'pve.snapstate'} if $d->{'pve.snapstate'}; | |
1479 | push @$res, $item; | |
1480 | } | |
1481 | ||
1482 | my $running = PVE::LXC::check_running($vmid) ? 1 : 0; | |
1483 | my $current = { name => 'current', digest => $conf->{digest}, running => $running }; | |
1484 | $current->{parent} = $conf->{'pve.parent'} if $conf->{'pve.parent'}; | |
1485 | ||
1486 | push @$res, $current; | |
1487 | ||
1488 | return $res; | |
1489 | }}); | |
1490 | ||
1491 | __PACKAGE__->register_method({ | |
1492 | name => 'snapshot_cmd_idx', | |
1493 | path => '{vmid}/snapshot/{snapname}', | |
1494 | description => '', | |
1495 | method => 'GET', | |
1496 | permissions => { | |
1497 | user => 'all', | |
1498 | }, | |
1499 | parameters => { | |
1500 | additionalProperties => 0, | |
1501 | properties => { | |
1502 | vmid => get_standard_option('pve-vmid'), | |
1503 | node => get_standard_option('pve-node'), | |
1504 | snapname => get_standard_option('pve-lxc-snapshot-name'), | |
1505 | }, | |
1506 | }, | |
1507 | returns => { | |
1508 | type => 'array', | |
1509 | items => { | |
1510 | type => "object", | |
1511 | properties => {}, | |
1512 | }, | |
1513 | links => [ { rel => 'child', href => "{cmd}" } ], | |
1514 | }, | |
1515 | code => sub { | |
1516 | my ($param) = @_; | |
1517 | ||
1518 | my $res = []; | |
1519 | ||
1520 | push @$res, { cmd => 'rollback' }; | |
1521 | push @$res, { cmd => 'config' }; | |
1522 | ||
1523 | return $res; | |
1524 | }}); | |
1525 | ||
1526 | __PACKAGE__->register_method({ | |
1527 | name => 'update_snapshot_config', | |
1528 | path => '{vmid}/snapshot/{snapname}/config', | |
1529 | method => 'PUT', | |
1530 | protected => 1, | |
1531 | proxyto => 'node', | |
1532 | description => "Update snapshot metadata.", | |
1533 | permissions => { | |
1534 | check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]], | |
1535 | }, | |
1536 | parameters => { | |
1537 | additionalProperties => 0, | |
1538 | properties => { | |
1539 | node => get_standard_option('pve-node'), | |
1540 | vmid => get_standard_option('pve-vmid'), | |
1541 | snapname => get_standard_option('pve-lxc-snapshot-name'), | |
1542 | description => { | |
1543 | optional => 1, | |
1544 | type => 'string', | |
1545 | description => "A textual description or comment.", | |
1546 | }, | |
1547 | }, | |
1548 | }, | |
1549 | returns => { type => 'null' }, | |
1550 | code => sub { | |
1551 | my ($param) = @_; | |
1552 | ||
1553 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1554 | ||
1555 | my $authuser = $rpcenv->get_user(); | |
1556 | ||
1557 | my $vmid = extract_param($param, 'vmid'); | |
1558 | ||
1559 | my $snapname = extract_param($param, 'snapname'); | |
1560 | ||
1561 | return undef if !defined($param->{description}); | |
1562 | ||
1563 | my $updatefn = sub { | |
1564 | ||
1565 | my $conf = PVE::LXC::load_config($vmid); | |
1566 | ||
1567 | PVE::LXC::check_lock($conf); | |
1568 | ||
1569 | my $snap = $conf->{snapshots}->{$snapname}; | |
1570 | ||
1571 | die "snapshot '$snapname' does not exist\n" if !defined($snap); | |
1572 | ||
1573 | $snap->{'pve.snapcomment'} = $param->{description} if defined($param->{description}); | |
1574 | ||
1575 | PVE::LXC::write_config($vmid, $conf, 1); | |
1576 | }; | |
1577 | ||
1578 | PVE::LXC::lock_container($vmid, 10, $updatefn); | |
1579 | ||
1580 | return undef; | |
1581 | }}); | |
1582 | ||
1583 | __PACKAGE__->register_method({ | |
1584 | name => 'get_snapshot_config', | |
1585 | path => '{vmid}/snapshot/{snapname}/config', | |
1586 | method => 'GET', | |
1587 | proxyto => 'node', | |
1588 | description => "Get snapshot configuration", | |
1589 | permissions => { | |
1590 | check => ['perm', '/vms/{vmid}', [ 'VM.Snapshot' ]], | |
1591 | }, | |
1592 | parameters => { | |
1593 | additionalProperties => 0, | |
1594 | properties => { | |
1595 | node => get_standard_option('pve-node'), | |
1596 | vmid => get_standard_option('pve-vmid'), | |
1597 | snapname => get_standard_option('pve-lxc-snapshot-name'), | |
1598 | }, | |
1599 | }, | |
1600 | returns => { type => "object" }, | |
1601 | code => sub { | |
1602 | my ($param) = @_; | |
1603 | ||
1604 | my $rpcenv = PVE::RPCEnvironment::get(); | |
1605 | ||
1606 | my $authuser = $rpcenv->get_user(); | |
1607 | ||
1608 | my $vmid = extract_param($param, 'vmid'); | |
1609 | ||
1610 | my $snapname = extract_param($param, 'snapname'); | |
1611 | ||
1612 | my $lxc_conf = PVE::LXC::load_config($vmid); | |
1613 | ||
1614 | my $snap = $lxc_conf->{snapshots}->{$snapname}; | |
1615 | ||
1616 | die "snapshot '$snapname' does not exist\n" if !defined($snap); | |
1617 | ||
1618 | my $conf = PVE::LXC::lxc_conf_to_pve($param->{vmid}, $snap); | |
1619 | ||
1620 | return $conf; | |
1621 | }}); | |
1622 | ||
1623 | __PACKAGE__->register_method({ | |
1624 | name => 'vm_feature', | |
1625 | path => '{vmid}/feature', | |
1626 | method => 'GET', | |
1627 | proxyto => 'node', | |
1628 | protected => 1, | |
1629 | description => "Check if feature for virtual machine is available.", | |
1630 | permissions => { | |
1631 | check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]], | |
1632 | }, | |
1633 | parameters => { | |
1634 | additionalProperties => 0, | |
1635 | properties => { | |
1636 | node => get_standard_option('pve-node'), | |
1637 | vmid => get_standard_option('pve-vmid'), | |
1638 | feature => { | |
1639 | description => "Feature to check.", | |
1640 | type => 'string', | |
1641 | enum => [ 'snapshot' ], | |
1642 | }, | |
1643 | snapname => get_standard_option('pve-lxc-snapshot-name', { | |
1644 | optional => 1, | |
1645 | }), | |
1646 | }, | |
1647 | }, | |
1648 | returns => { | |
1649 | type => "object", | |
1650 | properties => { | |
1651 | hasFeature => { type => 'boolean' }, | |
1652 | #nodes => { | |
1653 | #type => 'array', | |
1654 | #items => { type => 'string' }, | |
1655 | #} | |
1656 | }, | |
1657 | }, | |
1658 | code => sub { | |
1659 | my ($param) = @_; | |
1660 | ||
1661 | my $node = extract_param($param, 'node'); | |
1662 | ||
1663 | my $vmid = extract_param($param, 'vmid'); | |
1664 | ||
1665 | my $snapname = extract_param($param, 'snapname'); | |
1666 | ||
1667 | my $feature = extract_param($param, 'feature'); | |
1668 | ||
1669 | my $conf = PVE::LXC::load_config($vmid); | |
1670 | ||
1671 | if($snapname){ | |
1672 | my $snap = $conf->{snapshots}->{$snapname}; | |
1673 | die "snapshot '$snapname' does not exist\n" if !defined($snap); | |
1674 | $conf = $snap; | |
1675 | } | |
1676 | my $storecfg = PVE::Storage::config(); | |
1677 | #Maybe include later | |
1678 | #my $nodelist = PVE::LXC::shared_nodes($conf, $storecfg); | |
1679 | my $hasFeature = PVE::LXC::has_feature($feature, $conf, $storecfg, $snapname); | |
1680 | ||
1681 | return { | |
1682 | hasFeature => $hasFeature, | |
1683 | #nodes => [ keys %$nodelist ], | |
1684 | }; | |
1685 | }}); | |
f76a2828 | 1686 | 1; |