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