]> git.proxmox.com Git - pve-container.git/blame - src/PVE/API2/LXC.pm
vm_create|restore: create ips in ipam
[pve-container.git] / src / PVE / API2 / LXC.pm
CommitLineData
f76a2828
DM
1package PVE::API2::LXC;
2
3use strict;
4use warnings;
5
0e0958a5
FG
6use Socket qw(SOCK_STREAM);
7
f76a2828
DM
8use PVE::SafeSyslog;
9use PVE::Tools qw(extract_param run_command);
0620edda 10use PVE::Exception qw(raise raise_param_exc raise_perm_exc);
f76a2828 11use PVE::INotify;
9c2d4ce9 12use PVE::Cluster qw(cfs_read_file);
d949527f 13use PVE::RRD;
8b076579 14use PVE::DataCenterConfig;
f76a2828 15use PVE::AccessControl;
2f9b5ead 16use PVE::Firewall;
f76a2828
DM
17use PVE::Storage;
18use PVE::RESTHandler;
19use PVE::RPCEnvironment;
c5a8e04f 20use PVE::ReplicationConfig;
f76a2828 21use PVE::LXC;
7af97ad5 22use PVE::LXC::Create;
6f42807e 23use PVE::LXC::Migrate;
56462053 24use PVE::GuestHelpers;
c5a09704 25use PVE::VZDump::Plugin;
52389a07
DM
26use PVE::API2::LXC::Config;
27use PVE::API2::LXC::Status;
28use PVE::API2::LXC::Snapshot;
f76a2828
DM
29use PVE::JSONSchema qw(get_standard_option);
30use base qw(PVE::RESTHandler);
31
6c2e9377
WB
32BEGIN {
33 if (!$ENV{PVE_GENERATING_DOCS}) {
34 require PVE::HA::Env::PVE2;
35 import PVE::HA::Env::PVE2;
36 require PVE::HA::Config;
37 import PVE::HA::Config;
38 }
39}
f76a2828 40
e90ddc4c
FG
41my $check_storage_access_migrate = sub {
42 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
43
44 PVE::Storage::storage_check_enabled($storecfg, $storage, $node);
45
46 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
47
48 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
49 die "storage '$storage' does not support CT rootdirs\n"
50 if !$scfg->{content}->{rootdir};
51};
52
52389a07
DM
53__PACKAGE__->register_method ({
54 subclass => "PVE::API2::LXC::Config",
fa91e46f 55 path => '{vmid}/config',
52389a07 56});
f76a2828 57
52389a07
DM
58__PACKAGE__->register_method ({
59 subclass => "PVE::API2::LXC::Status",
60 path => '{vmid}/status',
61});
f76a2828 62
52389a07
DM
63__PACKAGE__->register_method ({
64 subclass => "PVE::API2::LXC::Snapshot",
65 path => '{vmid}/snapshot',
66});
f76a2828 67
52389a07
DM
68__PACKAGE__->register_method ({
69 subclass => "PVE::API2::Firewall::CT",
70 path => '{vmid}/firewall',
71});
1e6c8d5b 72
f76a2828 73__PACKAGE__->register_method({
5c752bbf
DM
74 name => 'vmlist',
75 path => '',
f76a2828
DM
76 method => 'GET',
77 description => "LXC container index (per node).",
78 permissions => {
79 description => "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
80 user => 'all',
81 },
82 proxyto => 'node',
83 protected => 1, # /proc files are only readable by root
84 parameters => {
85 additionalProperties => 0,
86 properties => {
87 node => get_standard_option('pve-node'),
88 },
89 },
90 returns => {
91 type => 'array',
92 items => {
93 type => "object",
8233d33d 94 properties => $PVE::LXC::vmstatus_return_properties,
f76a2828
DM
95 },
96 links => [ { rel => 'child', href => "{vmid}" } ],
97 },
98 code => sub {
99 my ($param) = @_;
100
101 my $rpcenv = PVE::RPCEnvironment::get();
102 my $authuser = $rpcenv->get_user();
103
104 my $vmstatus = PVE::LXC::vmstatus();
105
106 my $res = [];
107 foreach my $vmid (keys %$vmstatus) {
108 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
109
110 my $data = $vmstatus->{$vmid};
f76a2828
DM
111 push @$res, $data;
112 }
113
114 return $res;
5c752bbf 115
f76a2828
DM
116 }});
117
9c2d4ce9 118__PACKAGE__->register_method({
5c752bbf
DM
119 name => 'create_vm',
120 path => '',
9c2d4ce9
DM
121 method => 'POST',
122 description => "Create or restore a container.",
123 permissions => {
124 user => 'all', # check inside
125 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
126 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
127 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
128 },
129 protected => 1,
130 proxyto => 'node',
131 parameters => {
132 additionalProperties => 0,
1b4cf758 133 properties => PVE::LXC::Config->json_config_properties({
9c2d4ce9 134 node => get_standard_option('pve-node'),
781e26b2 135 vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_next_vmid }),
9c2d4ce9
DM
136 ostemplate => {
137 description => "The OS template or backup file.",
5c752bbf 138 type => 'string',
9c2d4ce9 139 maxLength => 255,
68e8f3c5 140 completion => \&PVE::LXC::complete_os_templates,
9c2d4ce9 141 },
5c752bbf
DM
142 password => {
143 optional => 1,
9c2d4ce9
DM
144 type => 'string',
145 description => "Sets root password inside container.",
168d6b07 146 minLength => 5,
9c2d4ce9
DM
147 },
148 storage => get_standard_option('pve-storage-id', {
eb35f9c0 149 description => "Default Storage.",
9c2d4ce9
DM
150 default => 'local',
151 optional => 1,
c5362cda 152 completion => \&PVE::Storage::complete_storage_enabled,
9c2d4ce9
DM
153 }),
154 force => {
5c752bbf 155 optional => 1,
9c2d4ce9
DM
156 type => 'boolean',
157 description => "Allow to overwrite existing container.",
158 },
159 restore => {
5c752bbf 160 optional => 1,
9c2d4ce9
DM
161 type => 'boolean',
162 description => "Mark this as restore task.",
163 },
43912111
CE
164 unique => {
165 optional => 1,
166 type => 'boolean',
167 description => "Assign a unique random ethernet address.",
168 requires => 'restore',
169 },
5c752bbf 170 pool => {
9c2d4ce9
DM
171 optional => 1,
172 type => 'string', format => 'pve-poolid',
173 description => "Add the VM to the specified pool.",
174 },
7c78b6cc
WB
175 'ignore-unpack-errors' => {
176 optional => 1,
177 type => 'boolean',
178 description => "Ignore errors when extracting the template.",
179 },
34ddbf08
FG
180 'ssh-public-keys' => {
181 optional => 1,
182 type => 'string',
183 description => "Setup public SSH keys (one key per line, " .
184 "OpenSSH format).",
185 },
f9b4407e 186 bwlimit => {
8ead60ef 187 description => "Override I/O bandwidth limit (in KiB/s).",
f9b4407e
WB
188 optional => 1,
189 type => 'number',
190 minimum => '0',
41e9b65c 191 default => 'restore limit from datacenter or storage config',
f9b4407e 192 },
9d0225fb
TL
193 start => {
194 optional => 1,
195 type => 'boolean',
196 default => 0,
197 description => "Start the CT after its creation finished successfully.",
198 },
9c2d4ce9
DM
199 }),
200 },
5c752bbf 201 returns => {
9c2d4ce9
DM
202 type => 'string',
203 },
204 code => sub {
205 my ($param) = @_;
206
61783321
TL
207 PVE::Cluster::check_cfs_quorum();
208
9c2d4ce9 209 my $rpcenv = PVE::RPCEnvironment::get();
9c2d4ce9
DM
210 my $authuser = $rpcenv->get_user();
211
212 my $node = extract_param($param, 'node');
9c2d4ce9 213 my $vmid = extract_param($param, 'vmid');
7c78b6cc 214 my $ignore_unpack_errors = extract_param($param, 'ignore-unpack-errors');
f9b4407e 215 my $bwlimit = extract_param($param, 'bwlimit');
9d0225fb
TL
216 my $start_after_create = extract_param($param, 'start');
217
67afe46e 218 my $basecfg_fn = PVE::LXC::Config->config_file($vmid);
9c2d4ce9
DM
219 my $same_container_exists = -f $basecfg_fn;
220
425b62cb
WB
221 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
222 my $unprivileged = extract_param($param, 'unprivileged');
9c2d4ce9 223 my $restore = extract_param($param, 'restore');
43912111 224 my $unique = extract_param($param, 'unique');
9c2d4ce9 225
8fe13f0c
FE
226 $param->{cpuunits} = PVE::CGroup::clamp_cpu_shares($param->{cpuunits})
227 if defined($param->{cpuunits}); # clamp value depending on cgroup version
228
39170644
FG
229 # used to skip firewall config restore if user lacks permission
230 my $skip_fw_config_restore = 0;
231
148d1cb4
DM
232 if ($restore) {
233 # fixme: limit allowed parameters
148d1cb4 234 }
351bc0c4 235
9c2d4ce9
DM
236 my $force = extract_param($param, 'force');
237
238 if (!($same_container_exists && $restore && $force)) {
239 PVE::Cluster::check_vmid_unused($vmid);
e22af68f 240 } else {
61783321 241 die "can't overwrite running container\n" if PVE::LXC::check_running($vmid);
67afe46e
FG
242 my $conf = PVE::LXC::Config->load_config($vmid);
243 PVE::LXC::Config->check_protection($conf, "unable to restore CT $vmid");
9c2d4ce9 244 }
5c752bbf 245
9c2d4ce9 246 my $password = extract_param($param, 'password');
34ddbf08 247 my $ssh_keys = extract_param($param, 'ssh-public-keys');
2130286f 248 PVE::Tools::validate_ssh_public_keys($ssh_keys) if defined($ssh_keys);
34ddbf08 249
27916659 250 my $pool = extract_param($param, 'pool');
f4b4443d 251 $rpcenv->check_pool_exist($pool) if defined($pool);
9c2d4ce9
DM
252
253 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
254 # OK
255 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
256 # OK
257 } elsif ($restore && $force && $same_container_exists &&
258 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
259 # OK: user has VM.Backup permissions, and want to restore an existing VM
39170644
FG
260
261 # we don't want to restore a container-provided FW conf in this case
262 # since the user is lacking permission to configure the container's FW
263 $skip_fw_config_restore = 1;
b9623ef8
DC
264
265 # error out if a user tries to change from unprivileged to privileged
266 # explicit change is checked here, implicit is checked down below or happening in root-only paths
267 my $conf = PVE::LXC::Config->load_config($vmid);
268 if ($conf->{unprivileged} && defined($unprivileged) && !$unprivileged) {
269 raise_perm_exc("cannot change from unprivileged to privileged without VM.Allocate");
270 }
9c2d4ce9
DM
271 } else {
272 raise_perm_exc();
273 }
274
9eb69152 275 my $ostemplate = extract_param($param, 'ostemplate');
bb6afcb0
DM
276 my $storage = extract_param($param, 'storage') // 'local';
277
de41bced 278 PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, undef, $param, [], $unprivileged);
9eb69152 279
bb6afcb0 280 my $storage_cfg = cfs_read_file("storage.cfg");
9c2d4ce9 281
9c2d4ce9 282 my $archive;
9c2d4ce9 283 if ($ostemplate eq '-') {
351bc0c4
TM
284 die "pipe requires cli environment\n"
285 if $rpcenv->{type} ne 'cli';
286 die "pipe can only be used with restore tasks\n"
148d1cb4
DM
287 if !$restore;
288 $archive = '-';
b51a98d4 289 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs});
9c2d4ce9 290 } else {
d387c0be
FE
291 my $content_type = $restore ? 'backup' : 'vztmpl';
292 PVE::Storage::check_volume_access(
293 $rpcenv,
294 $authuser,
295 $storage_cfg,
296 $vmid,
297 $ostemplate,
298 $content_type,
299 );
99a0bcb0 300 $archive = $ostemplate;
9c2d4ce9
DM
301 }
302
f9b4407e 303 my %used_storages;
bb6afcb0
DM
304 my $check_and_activate_storage = sub {
305 my ($sid) = @_;
306
a1c27f86 307 my $scfg = PVE::Storage::storage_check_enabled($storage_cfg, $sid, $node);
bb6afcb0
DM
308
309 raise_param_exc({ storage => "storage '$sid' does not support container directories"})
310 if !$scfg->{content}->{rootdir};
311
312 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
313
314 PVE::Storage::activate_storage($storage_cfg, $sid);
f9b4407e 315 $used_storages{$sid} = 1;
bb6afcb0
DM
316 };
317
9c2d4ce9 318 my $conf = {};
5b4657d0 319
99132ce0
WB
320 my $is_root = $authuser eq 'root@pam';
321
b51a98d4 322 my $no_disk_param = {};
db18c1e4
FG
323 my $mp_param = {};
324 my $storage_only_mode = 1;
b51a98d4 325 foreach my $opt (keys %$param) {
78ccc99b
DM
326 my $value = $param->{$opt};
327 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
328 # allow to use simple numbers (add default storage in that case)
db18c1e4
FG
329 if ($value =~ m/^\d+(\.\d+)?$/) {
330 $mp_param->{$opt} = "$storage:$value";
331 } else {
332 $mp_param->{$opt} = $value;
333 }
334 $storage_only_mode = 0;
a9dd8015
FG
335 } elsif ($opt =~ m/^unused\d+$/) {
336 warn "ignoring '$opt', cannot create/restore with unused volume\n";
337 delete $param->{$opt};
78ccc99b
DM
338 } else {
339 $no_disk_param->{$opt} = $value;
340 }
b51a98d4 341 }
bb6afcb0 342
235dbdf3 343 die "mount points configured, but 'rootfs' not set - aborting\n"
db18c1e4
FG
344 if !$storage_only_mode && !defined($mp_param->{rootfs});
345
bb6afcb0 346 # check storage access, activate storage
db18c1e4 347 my $delayed_mp_param = {};
015740e6 348 PVE::LXC::Config->foreach_volume($mp_param, sub {
bb6afcb0
DM
349 my ($ms, $mountpoint) = @_;
350
351 my $volid = $mountpoint->{volume};
352 my $mp = $mountpoint->{mp};
353
e2007ac2
DM
354 if ($mountpoint->{type} ne 'volume') { # bind or device
355 die "Only root can pass arbitrary filesystem paths.\n"
99132ce0 356 if !$is_root;
e2007ac2
DM
357 } else {
358 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);
359 &$check_and_activate_storage($sid);
360 }
bb6afcb0
DM
361 });
362
363 # check/activate default storage
db18c1e4 364 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs});
bb6afcb0 365
1b4cf758 366 PVE::LXC::Config->update_pct_config($vmid, $conf, 0, $no_disk_param);
9c2d4ce9 367
425b62cb
WB
368 $conf->{unprivileged} = 1 if $unprivileged;
369
61783321 370 my $emsg = $restore ? "unable to restore CT $vmid -" : "unable to create CT $vmid -";
bccaa371 371
61783321
TL
372 eval { PVE::LXC::Config->create_and_lock_config($vmid, $force) };
373 die "$emsg $@" if $@;
bccaa371 374
0730b005 375 my $destroy_config_on_error = !$same_container_exists;
b9623ef8 376
61783321
TL
377 my $code = sub {
378 my $old_conf = PVE::LXC::Config->load_config($vmid);
a2d3e5c3 379 my $was_template;
bccaa371 380
0730b005 381 my $vollist = [];
27916659 382 eval {
5a7a51d0
WB
383 my $orig_mp_param; # only used if $restore
384 if ($restore) {
61783321 385 die "can't overwrite running container\n" if PVE::LXC::check_running($vmid);
32e56bed 386 if ($archive ne '-') {
a48e5562 387 my $orig_conf;
bc871ffb 388 print "recovering backed-up configuration from '$archive'\n";
12aec1d2 389 ($orig_conf, $orig_mp_param) = PVE::LXC::Create::recover_config($storage_cfg, $archive, $vmid);
6597f369 390
ee81952f
FG
391 for my $opt (keys %$orig_conf) {
392 # early check before disks are created
393 # the "real" check is in later on when actually merging the configs
394 if ($opt =~ /^net\d+$/ && !defined($param->{$opt})) {
395 PVE::LXC::check_bridge_access($rpcenv, $authuser, $orig_conf->{$opt});
396 }
397 }
398
a2d3e5c3 399 $was_template = delete $orig_conf->{template};
6597f369 400
a67908f1 401 # When we're root call 'restore_configuration' with restricted=0,
5a7a51d0
WB
402 # causing it to restore the raw lxc entries, among which there may be
403 # 'lxc.idmap' entries. We need to make sure that the extracted contents
404 # of the container match up with the restored configuration afterwards:
32e56bed 405 $conf->{lxc} = $orig_conf->{lxc} if $is_root;
8ffdc116
OB
406
407 $conf->{unprivileged} = $orig_conf->{unprivileged}
408 if !defined($unprivileged) && defined($orig_conf->{unprivileged});
b9623ef8
DC
409
410 # implicit privileged change is checked here
411 if ($old_conf->{unprivileged} && !$conf->{unprivileged}) {
412 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Allocate']);
413 }
5a7a51d0 414 }
f360d7f1 415 }
db18c1e4 416 if ($storage_only_mode) {
b51a98d4 417 if ($restore) {
a48e5562 418 if (!defined($orig_mp_param)) {
bc871ffb 419 print "recovering backed-up configuration from '$archive'\n";
12aec1d2 420 (undef, $orig_mp_param) = PVE::LXC::Create::recover_config($storage_cfg, $archive, $vmid);
a5246958 421 }
f360d7f1 422 $mp_param = $orig_mp_param;
db18c1e4
FG
423 die "rootfs configuration could not be recovered, please check and specify manually!\n"
424 if !defined($mp_param->{rootfs});
015740e6 425 PVE::LXC::Config->foreach_volume($mp_param, sub {
db18c1e4
FG
426 my ($ms, $mountpoint) = @_;
427 my $type = $mountpoint->{type};
428 if ($type eq 'volume') {
429 die "unable to detect disk size - please specify $ms (size)\n"
430 if !defined($mountpoint->{size});
431 my $disksize = $mountpoint->{size} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
432 delete $mountpoint->{size};
433 $mountpoint->{volume} = "$storage:$disksize";
434 $mp_param->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
435 } else {
15e4d443 436 my $type = $mountpoint->{type};
7e0e7f38
FG
437 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
438 if ($ms eq 'rootfs');
20f9339d 439 die "restoring '$ms' to $type mount is only possible for root\n"
99132ce0 440 if !$is_root;
7e0e7f38 441
15e4d443
FG
442 if ($mountpoint->{backup}) {
443 warn "WARNING - unsupported configuration!\n";
235dbdf3
FG
444 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
445 warn "mount point configuration will be restored after archive extraction!\n";
15e4d443
FG
446 warn "contained files will be restored to wrong directory!\n";
447 }
136040f4 448 delete $mp_param->{$ms}; # actually delay bind/dev mps
db18c1e4
FG
449 $delayed_mp_param->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
450 }
451 });
b51a98d4 452 } else {
db18c1e4 453 $mp_param->{rootfs} = "$storage:4"; # defaults to 4GB
b51a98d4
DM
454 }
455 }
b9623ef8 456
0730b005
DT
457 # up until here we did not modify the container, besides the lock
458 $destroy_config_on_error = 1;
b51a98d4 459
db18c1e4
FG
460 $vollist = PVE::LXC::create_disks($storage_cfg, $vmid, $mp_param, $conf);
461
61783321
TL
462 # we always have the 'create' lock so check for more than 1 entry
463 if (scalar(keys %$old_conf) > 1) {
51665c2d 464 # destroy old container volumes
61783321 465 PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $old_conf, { lock => 'create' });
51665c2d 466 }
51665c2d
FG
467
468 eval {
469 my $rootdir = PVE::LXC::mount_all($vmid, $storage_cfg, $conf, 1);
f9b4407e 470 $bwlimit = PVE::Storage::get_bandwidth_limit('restore', [keys %used_storages], $bwlimit);
bc871ffb
FG
471 print "restoring '$archive' now..\n"
472 if $restore && $archive ne '-';
99a0bcb0 473 PVE::LXC::Create::restore_archive($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
51665c2d
FG
474
475 if ($restore) {
bc871ffb 476 print "merging backed-up and given configuration..\n";
99a0bcb0 477 PVE::LXC::Create::restore_configuration($vmid, $storage_cfg, $archive, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
9fd103b2 478 PVE::LXC::create_ifaces_ipams_ips($conf, $vmid) if $unique;
4b4bbe55
OB
479 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
480 $lxc_setup->template_fixup($conf);
51665c2d 481 } else {
9fd103b2 482 PVE::LXC::create_ifaces_ipams_ips($conf, $vmid);
51665c2d
FG
483 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir); # detect OS
484 PVE::LXC::Config->write_config($vmid, $conf); # safe config (after OS detection)
485 $lxc_setup->post_create_hook($password, $ssh_keys);
486 }
487 };
488 my $err = $@;
489 PVE::LXC::umount_all($vmid, $storage_cfg, $conf, $err ? 1 : 0);
490 PVE::Storage::deactivate_volumes($storage_cfg, PVE::LXC::Config->get_vm_volumes($conf));
491 die $err if $err;
27916659
DM
492 # set some defaults
493 $conf->{hostname} ||= "CT$vmid";
494 $conf->{memory} ||= 512;
495 $conf->{swap} //= 512;
db18c1e4
FG
496 foreach my $mp (keys %$delayed_mp_param) {
497 $conf->{$mp} = $delayed_mp_param->{$mp};
498 }
a2d3e5c3
CE
499 # If the template flag was set, we try to convert again to template after restore
500 if ($was_template) {
501 print STDERR "Convert restored container to template...\n";
4c64d0db
FE
502 PVE::LXC::template_create($vmid, $conf);
503 $conf->{template} = 1;
a2d3e5c3 504 }
67afe46e 505 PVE::LXC::Config->write_config($vmid, $conf);
27916659
DM
506 };
507 if (my $err = $@) {
9fd103b2
AD
508 eval { PVE::LXC::delete_ifaces_ipams_ips($conf, $vmid) };
509 warn $@ if $@;
6c871c36 510 PVE::LXC::destroy_disks($storage_cfg, $vollist);
0730b005
DT
511 if ($destroy_config_on_error) {
512 eval { PVE::LXC::Config->destroy_config($vmid) };
513 warn $@ if $@;
62a991db
DT
514
515 if (!$skip_fw_config_restore) { # Only if user has permission to change the fw
516 PVE::Firewall::remove_vmfw_conf($vmid);
517 warn $@ if $@;
518 }
0730b005 519 }
61783321 520 die "$emsg $err";
6d098bf4 521 }
87273b2b 522 PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
9d0225fb
TL
523
524 PVE::API2::LXC::Status->vm_start({ vmid => $vmid, node => $node })
525 if $start_after_create;
9c2d4ce9 526 };
5c752bbf 527
8a6fc617 528 my $workername = $restore ? 'vzrestore' : 'vzcreate';
b9623ef8
DC
529 my $realcmd = sub {
530 eval {
531 PVE::LXC::Config->lock_config($vmid, $code);
532 };
533 if (my $err = $@) {
534 # if we aborted before changing the container, we must remove the create lock
0730b005 535 if (!$destroy_config_on_error) {
b9623ef8
DC
536 PVE::LXC::Config->remove_lock($vmid, 'create');
537 }
538 die $err;
539 }
540 };
9c2d4ce9 541
8a6fc617 542 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
9c2d4ce9
DM
543 }});
544
f76a2828
DM
545__PACKAGE__->register_method({
546 name => 'vmdiridx',
5c752bbf 547 path => '{vmid}',
f76a2828
DM
548 method => 'GET',
549 proxyto => 'node',
550 description => "Directory index",
551 permissions => {
552 user => 'all',
553 },
554 parameters => {
555 additionalProperties => 0,
556 properties => {
557 node => get_standard_option('pve-node'),
558 vmid => get_standard_option('pve-vmid'),
559 },
560 },
561 returns => {
562 type => 'array',
563 items => {
564 type => "object",
565 properties => {
566 subdir => { type => 'string' },
567 },
568 },
569 links => [ { rel => 'child', href => "{subdir}" } ],
570 },
571 code => sub {
572 my ($param) = @_;
573
574 # test if VM exists
67afe46e 575 my $conf = PVE::LXC::Config->load_config($param->{vmid});
f76a2828
DM
576
577 my $res = [
578 { subdir => 'config' },
37c2dba8 579 { subdir => 'pending' },
fff3a342
DM
580 { subdir => 'status' },
581 { subdir => 'vncproxy' },
a0226a00 582 { subdir => 'termproxy' },
fff3a342
DM
583 { subdir => 'vncwebsocket' },
584 { subdir => 'spiceproxy' },
585 { subdir => 'migrate' },
c4a33727 586 { subdir => 'clone' },
f76a2828
DM
587# { subdir => 'initlog' },
588 { subdir => 'rrd' },
589 { subdir => 'rrddata' },
590 { subdir => 'firewall' },
cc5392c8 591 { subdir => 'snapshot' },
985b18ed 592 { subdir => 'resize' },
f76a2828 593 ];
5c752bbf 594
f76a2828
DM
595 return $res;
596 }});
597
c4a33727 598
f76a2828 599__PACKAGE__->register_method({
5c752bbf
DM
600 name => 'rrd',
601 path => '{vmid}/rrd',
f76a2828
DM
602 method => 'GET',
603 protected => 1, # fixme: can we avoid that?
604 permissions => {
605 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
606 },
607 description => "Read VM RRD statistics (returns PNG)",
608 parameters => {
609 additionalProperties => 0,
610 properties => {
611 node => get_standard_option('pve-node'),
612 vmid => get_standard_option('pve-vmid'),
613 timeframe => {
614 description => "Specify the time frame you are interested in.",
615 type => 'string',
616 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
617 },
618 ds => {
619 description => "The list of datasources you want to display.",
620 type => 'string', format => 'pve-configid-list',
621 },
622 cf => {
623 description => "The RRD consolidation function",
624 type => 'string',
625 enum => [ 'AVERAGE', 'MAX' ],
626 optional => 1,
627 },
628 },
629 },
630 returns => {
631 type => "object",
632 properties => {
633 filename => { type => 'string' },
634 },
635 },
636 code => sub {
637 my ($param) = @_;
638
d949527f 639 return PVE::RRD::create_rrd_graph(
5c752bbf 640 "pve2-vm/$param->{vmid}", $param->{timeframe},
f76a2828 641 $param->{ds}, $param->{cf});
5c752bbf 642
f76a2828
DM
643 }});
644
645__PACKAGE__->register_method({
5c752bbf
DM
646 name => 'rrddata',
647 path => '{vmid}/rrddata',
f76a2828
DM
648 method => 'GET',
649 protected => 1, # fixme: can we avoid that?
650 permissions => {
651 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
652 },
653 description => "Read VM RRD statistics",
654 parameters => {
655 additionalProperties => 0,
656 properties => {
657 node => get_standard_option('pve-node'),
658 vmid => get_standard_option('pve-vmid'),
659 timeframe => {
660 description => "Specify the time frame you are interested in.",
661 type => 'string',
662 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
663 },
664 cf => {
665 description => "The RRD consolidation function",
666 type => 'string',
667 enum => [ 'AVERAGE', 'MAX' ],
668 optional => 1,
669 },
670 },
671 },
672 returns => {
673 type => "array",
674 items => {
675 type => "object",
676 properties => {},
677 },
678 },
679 code => sub {
680 my ($param) = @_;
681
d949527f 682 return PVE::RRD::create_rrd_data(
f76a2828
DM
683 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
684 }});
685
f76a2828 686__PACKAGE__->register_method({
5c752bbf
DM
687 name => 'destroy_vm',
688 path => '{vmid}',
f76a2828
DM
689 method => 'DELETE',
690 protected => 1,
691 proxyto => 'node',
692 description => "Destroy the container (also delete all uses files).",
693 permissions => {
694 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
695 },
696 parameters => {
697 additionalProperties => 0,
698 properties => {
699 node => get_standard_option('pve-node'),
68e8f3c5 700 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid_stopped }),
d51b84ff
TL
701 force => {
702 type => 'boolean',
703 description => "Force destroy, even if running.",
704 default => 0,
705 optional => 1,
706 },
c5a09704
CE
707 purge => {
708 type => 'boolean',
796e8eee
TL
709 description => "Remove container from all related configurations."
710 ." For example, backup jobs, replication jobs or HA."
711 ." Related ACLs and Firewall entries will *always* be removed.",
712 default => 0,
c5a09704
CE
713 optional => 1,
714 },
3b1c0970
TL
715 'destroy-unreferenced-disks' => {
716 type => 'boolean',
717 description => "If set, destroy additionally all disks with the VMID from all"
718 ." enabled storages which are not referenced in the config.",
719 optional => 1,
720 },
f76a2828
DM
721 },
722 },
5c752bbf 723 returns => {
f76a2828
DM
724 type => 'string',
725 },
726 code => sub {
727 my ($param) = @_;
728
729 my $rpcenv = PVE::RPCEnvironment::get();
f76a2828 730 my $authuser = $rpcenv->get_user();
f76a2828
DM
731 my $vmid = $param->{vmid};
732
733 # test if container exists
0512c360 734
67afe46e 735 my $conf = PVE::LXC::Config->load_config($vmid);
0512c360
FG
736 my $early_checks = sub {
737 my ($conf) = @_;
738 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid");
739 PVE::LXC::Config->check_lock($conf);
7e806596 740
0512c360 741 my $ha_managed = PVE::HA::Config::service_is_configured("ct:$vmid");
b6e0b774 742
0512c360
FG
743 if (!$param->{purge}) {
744 die "unable to remove CT $vmid - used in HA resources and purge parameter not set.\n"
745 if $ha_managed;
3207b81a 746
0512c360
FG
747 # do not allow destroy if there are replication jobs without purge
748 my $repl_conf = PVE::ReplicationConfig->new();
749 $repl_conf->check_for_existing_jobs($vmid);
750 }
751
752 return $ha_managed;
753 };
754
755 $early_checks->($conf);
c5a8e04f 756
d607c17d 757 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
d51b84ff 758 die $running_error_msg if !$param->{force} && PVE::LXC::check_running($vmid); # check early
d607c17d 759
611fe3aa 760 my $code = sub {
673cf209 761 # reload config after lock
67afe46e 762 $conf = PVE::LXC::Config->load_config($vmid);
0512c360 763 my $ha_managed = $early_checks->($conf);
673cf209 764
d51b84ff
TL
765 if (PVE::LXC::check_running($vmid)) {
766 die $running_error_msg if !$param->{force};
767 warn "forced to stop CT $vmid before destroying!\n";
768 if (!$ha_managed) {
769 PVE::LXC::vm_stop($vmid, 1);
770 } else {
771 run_command(['ha-manager', 'crm-command', 'stop', "ct:$vmid", '120']);
772 }
773 }
d607c17d 774
0512c360 775 my $storage_cfg = cfs_read_file("storage.cfg");
3b1c0970
TL
776 PVE::LXC::destroy_lxc_container(
777 $storage_cfg,
778 $vmid,
779 $conf,
780 { lock => 'destroyed' },
781 $param->{'destroy-unreferenced-disks'},
782 );
7fc1d9eb 783
be5fc936 784 PVE::AccessControl::remove_vm_access($vmid);
2f9b5ead 785 PVE::Firewall::remove_vmfw_conf($vmid);
c5a09704 786 if ($param->{purge}) {
3207b81a 787 print "purging CT $vmid from related configurations..\n";
c5a09704
CE
788 PVE::ReplicationConfig::remove_vmid_jobs($vmid);
789 PVE::VZDump::Plugin::remove_vmid_from_backup_jobs($vmid);
3207b81a
TL
790
791 if ($ha_managed) {
792 PVE::HA::Config::delete_service_from_config("ct:$vmid");
793 print "NOTE: removed CT $vmid from HA resource configuration.\n";
794 }
c5a09704 795 }
7fc1d9eb
TL
796
797 # only now remove the zombie config, else we can have reuse race
798 PVE::LXC::Config->destroy_config($vmid);
f76a2828
DM
799 };
800
67afe46e 801 my $realcmd = sub { PVE::LXC::Config->lock_config($vmid, $code); };
351bc0c4 802
f76a2828
DM
803 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
804 }});
805
fff3a342
DM
806my $sslcert;
807
808__PACKAGE__->register_method ({
5b4657d0
DM
809 name => 'vncproxy',
810 path => '{vmid}/vncproxy',
fff3a342
DM
811 method => 'POST',
812 protected => 1,
813 permissions => {
814 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
815 },
816 description => "Creates a TCP VNC proxy connections.",
817 parameters => {
818 additionalProperties => 0,
819 properties => {
820 node => get_standard_option('pve-node'),
821 vmid => get_standard_option('pve-vmid'),
822 websocket => {
823 optional => 1,
824 type => 'boolean',
825 description => "use websocket instead of standard VNC.",
826 },
bd0f4d6d
DC
827 width => {
828 optional => 1,
829 description => "sets the width of the console in pixels.",
830 type => 'integer',
831 minimum => 16,
832 maximum => 4096,
833 },
834 height => {
835 optional => 1,
836 description => "sets the height of the console in pixels.",
837 type => 'integer',
838 minimum => 16,
839 maximum => 2160,
840 },
fff3a342
DM
841 },
842 },
5b4657d0 843 returns => {
fff3a342
DM
844 additionalProperties => 0,
845 properties => {
846 user => { type => 'string' },
847 ticket => { type => 'string' },
848 cert => { type => 'string' },
849 port => { type => 'integer' },
850 upid => { type => 'string' },
851 },
852 },
853 code => sub {
854 my ($param) = @_;
855
856 my $rpcenv = PVE::RPCEnvironment::get();
857
858 my $authuser = $rpcenv->get_user();
859
860 my $vmid = $param->{vmid};
861 my $node = $param->{node};
862
863 my $authpath = "/vms/$vmid";
864
865 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
866
867 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
868 if !$sslcert;
869
ec2c57eb 870 my ($remip, $family);
5b4657d0 871
fff3a342 872 if ($node ne PVE::INotify::nodename()) {
85ae6211 873 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
ec2c57eb
WB
874 } else {
875 $family = PVE::Tools::get_host_address_family($node);
fff3a342
DM
876 }
877
ec2c57eb
WB
878 my $port = PVE::Tools::next_vnc_port($family);
879
fff3a342
DM
880 # NOTE: vncterm VNC traffic is already TLS encrypted,
881 # so we select the fastest chipher here (or 'none'?)
5b4657d0 882 my $remcmd = $remip ?
806eae70 883 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
fff3a342 884
67afe46e 885 my $conf = PVE::LXC::Config->load_config($vmid, $node);
65213b67 886 my $concmd = PVE::LXC::get_console_command($vmid, $conf, -1);
aca816ad 887
5b4657d0
DM
888 my $shcmd = [ '/usr/bin/dtach', '-A',
889 "/var/run/dtach/vzctlconsole$vmid",
aca816ad 890 '-r', 'winch', '-z', @$concmd];
fff3a342
DM
891
892 my $realcmd = sub {
893 my $upid = shift;
894
5b4657d0 895 syslog ('info', "starting lxc vnc proxy $upid\n");
fff3a342 896
5b4657d0 897 my $timeout = 10;
fff3a342
DM
898
899 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
5b4657d0 900 '-timeout', $timeout, '-authpath', $authpath,
fff3a342
DM
901 '-perm', 'VM.Console'];
902
bd0f4d6d
DC
903 if ($param->{width}) {
904 push @$cmd, '-width', $param->{width};
905 }
906
907 if ($param->{height}) {
908 push @$cmd, '-height', $param->{height};
909 }
910
fff3a342 911 if ($param->{websocket}) {
5b4657d0 912 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
fff3a342
DM
913 push @$cmd, '-notls', '-listen', 'localhost';
914 }
915
916 push @$cmd, '-c', @$remcmd, @$shcmd;
917
37634d3d 918 run_command($cmd, keeplocale => 1);
fff3a342
DM
919
920 return;
921 };
922
923 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
924
925 PVE::Tools::wait_for_vnc_port($port);
926
927 return {
928 user => $authuser,
929 ticket => $ticket,
5b4657d0
DM
930 port => $port,
931 upid => $upid,
932 cert => $sslcert,
fff3a342
DM
933 };
934 }});
935
a0226a00
DC
936__PACKAGE__->register_method ({
937 name => 'termproxy',
938 path => '{vmid}/termproxy',
939 method => 'POST',
940 protected => 1,
941 permissions => {
942 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
943 },
944 description => "Creates a TCP proxy connection.",
945 parameters => {
946 additionalProperties => 0,
947 properties => {
948 node => get_standard_option('pve-node'),
949 vmid => get_standard_option('pve-vmid'),
950 },
951 },
952 returns => {
953 additionalProperties => 0,
954 properties => {
955 user => { type => 'string' },
956 ticket => { type => 'string' },
957 port => { type => 'integer' },
958 upid => { type => 'string' },
959 },
960 },
961 code => sub {
962 my ($param) = @_;
963
964 my $rpcenv = PVE::RPCEnvironment::get();
965
966 my $authuser = $rpcenv->get_user();
967
968 my $vmid = $param->{vmid};
969 my $node = $param->{node};
970
971 my $authpath = "/vms/$vmid";
972
973 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
974
975 my ($remip, $family);
976
977 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
978 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
979 } else {
980 $family = PVE::Tools::get_host_address_family($node);
981 }
982
983 my $port = PVE::Tools::next_vnc_port($family);
984
985 my $remcmd = $remip ?
986 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
987
988 my $conf = PVE::LXC::Config->load_config($vmid, $node);
65213b67 989 my $concmd = PVE::LXC::get_console_command($vmid, $conf, -1);
a0226a00
DC
990
991 my $shcmd = [ '/usr/bin/dtach', '-A',
992 "/var/run/dtach/vzctlconsole$vmid",
993 '-r', 'winch', '-z', @$concmd];
994
995 my $realcmd = sub {
996 my $upid = shift;
997
998 syslog ('info', "starting lxc termproxy $upid\n");
999
1000 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1001 '--perm', 'VM.Console', '--'];
1002 push @$cmd, @$remcmd, @$shcmd;
1003
1004 PVE::Tools::run_command($cmd);
1005 };
1006
1007 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1008
1009 PVE::Tools::wait_for_vnc_port($port);
1010
1011 return {
1012 user => $authuser,
1013 ticket => $ticket,
1014 port => $port,
1015 upid => $upid,
1016 };
1017 }});
1018
fff3a342
DM
1019__PACKAGE__->register_method({
1020 name => 'vncwebsocket',
1021 path => '{vmid}/vncwebsocket',
1022 method => 'GET',
5b4657d0 1023 permissions => {
fff3a342
DM
1024 description => "You also need to pass a valid ticket (vncticket).",
1025 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1026 },
1027 description => "Opens a weksocket for VNC traffic.",
1028 parameters => {
1029 additionalProperties => 0,
1030 properties => {
1031 node => get_standard_option('pve-node'),
1032 vmid => get_standard_option('pve-vmid'),
1033 vncticket => {
1034 description => "Ticket from previous call to vncproxy.",
1035 type => 'string',
1036 maxLength => 512,
1037 },
1038 port => {
1039 description => "Port number returned by previous vncproxy call.",
1040 type => 'integer',
1041 minimum => 5900,
1042 maximum => 5999,
1043 },
1044 },
1045 },
1046 returns => {
1047 type => "object",
1048 properties => {
1049 port => { type => 'string' },
1050 },
1051 },
1052 code => sub {
1053 my ($param) = @_;
1054
1055 my $rpcenv = PVE::RPCEnvironment::get();
1056
1057 my $authuser = $rpcenv->get_user();
1058
1059 my $authpath = "/vms/$param->{vmid}";
1060
1061 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
1062
1063 my $port = $param->{port};
5b4657d0 1064
fff3a342
DM
1065 return { port => $port };
1066 }});
1067
1068__PACKAGE__->register_method ({
5b4657d0
DM
1069 name => 'spiceproxy',
1070 path => '{vmid}/spiceproxy',
fff3a342
DM
1071 method => 'POST',
1072 protected => 1,
1073 proxyto => 'node',
1074 permissions => {
1075 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1076 },
1077 description => "Returns a SPICE configuration to connect to the CT.",
1078 parameters => {
1079 additionalProperties => 0,
1080 properties => {
1081 node => get_standard_option('pve-node'),
1082 vmid => get_standard_option('pve-vmid'),
1083 proxy => get_standard_option('spice-proxy', { optional => 1 }),
1084 },
1085 },
1086 returns => get_standard_option('remote-viewer-config'),
1087 code => sub {
1088 my ($param) = @_;
1089
1090 my $vmid = $param->{vmid};
1091 my $node = $param->{node};
1092 my $proxy = $param->{proxy};
1093
1094 my $authpath = "/vms/$vmid";
1095 my $permissions = 'VM.Console';
1096
67afe46e 1097 my $conf = PVE::LXC::Config->load_config($vmid);
da4db334
TL
1098
1099 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1100
aca816ad
DM
1101 my $concmd = PVE::LXC::get_console_command($vmid, $conf);
1102
5b4657d0
DM
1103 my $shcmd = ['/usr/bin/dtach', '-A',
1104 "/var/run/dtach/vzctlconsole$vmid",
aca816ad 1105 '-r', 'winch', '-z', @$concmd];
fff3a342
DM
1106
1107 my $title = "CT $vmid";
1108
1109 return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1110 }});
5c752bbf 1111
5c752bbf 1112
0e0958a5
FG
1113__PACKAGE__->register_method({
1114 name => 'remote_migrate_vm',
1115 path => '{vmid}/remote_migrate',
1116 method => 'POST',
1117 protected => 1,
1118 proxyto => 'node',
1119 description => "Migrate the container to another cluster. Creates a new migration task. EXPERIMENTAL feature!",
1120 permissions => {
1121 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1122 },
1123 parameters => {
1124 additionalProperties => 0,
1125 properties => {
1126 node => get_standard_option('pve-node'),
1127 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
1128 'target-vmid' => get_standard_option('pve-vmid', { optional => 1 }),
1129 'target-endpoint' => get_standard_option('proxmox-remote', {
1130 description => "Remote target endpoint",
1131 }),
1132 online => {
1133 type => 'boolean',
1134 description => "Use online/live migration.",
1135 optional => 1,
1136 },
1137 restart => {
1138 type => 'boolean',
1139 description => "Use restart migration",
1140 optional => 1,
1141 },
1142 timeout => {
1143 type => 'integer',
1144 description => "Timeout in seconds for shutdown for restart migration",
1145 optional => 1,
1146 default => 180,
1147 },
1148 delete => {
1149 type => 'boolean',
1150 description => "Delete the original CT and related data after successful migration. By default the original CT is kept on the source cluster in a stopped state.",
1151 optional => 1,
1152 default => 0,
1153 },
1154 'target-storage' => get_standard_option('pve-targetstorage', {
1155 optional => 0,
1156 }),
1157 'target-bridge' => {
1158 type => 'string',
1159 description => "Mapping from source to target bridges. Providing only a single bridge ID maps all source bridges to that bridge. Providing the special value '1' will map each source bridge to itself.",
1160 format => 'bridge-pair-list',
1161 },
1162 bwlimit => {
1163 description => "Override I/O bandwidth limit (in KiB/s).",
1164 optional => 1,
1165 type => 'number',
1166 minimum => '0',
1167 default => 'migrate limit from datacenter or storage config',
1168 },
1169 },
1170 },
1171 returns => {
1172 type => 'string',
1173 description => "the task ID.",
1174 },
1175 code => sub {
1176 my ($param) = @_;
1177
1178 my $rpcenv = PVE::RPCEnvironment::get();
1179 my $authuser = $rpcenv->get_user();
1180
1181 my $source_vmid = extract_param($param, 'vmid');
1182 my $target_endpoint = extract_param($param, 'target-endpoint');
1183 my $target_vmid = extract_param($param, 'target-vmid') // $source_vmid;
1184
1185 my $delete = extract_param($param, 'delete') // 0;
1186
1187 PVE::Cluster::check_cfs_quorum();
1188
1189 # test if CT exists
1190 my $conf = PVE::LXC::Config->load_config($source_vmid);
1191 PVE::LXC::Config->check_lock($conf);
1192
1193 # try to detect errors early
1194 if (PVE::LXC::check_running($source_vmid)) {
1195 die "can't migrate running container without --online or --restart\n"
1196 if !$param->{online} && !$param->{restart};
1197 }
1198
1199 raise_param_exc({ vmid => "cannot migrate HA-managed CT to remote cluster" })
1200 if PVE::HA::Config::vm_is_ha_managed($source_vmid);
1201
1202 my $remote = PVE::JSONSchema::parse_property_string('proxmox-remote', $target_endpoint);
1203
1204 # TODO: move this as helper somewhere appropriate?
1205 my $conn_args = {
1206 protocol => 'https',
1207 host => $remote->{host},
1208 port => $remote->{port} // 8006,
1209 apitoken => $remote->{apitoken},
1210 };
1211
1212 my $fp;
1213 if ($fp = $remote->{fingerprint}) {
1214 $conn_args->{cached_fingerprints} = { uc($fp) => 1 };
1215 }
1216
1217 print "Establishing API connection with remote at '$remote->{host}'\n";
1218
1219 my $api_client = PVE::APIClient::LWP->new(%$conn_args);
1220
1221 if (!defined($fp)) {
1222 my $cert_info = $api_client->get("/nodes/localhost/certificates/info");
1223 foreach my $cert (@$cert_info) {
1224 my $filename = $cert->{filename};
1225 next if $filename ne 'pveproxy-ssl.pem' && $filename ne 'pve-ssl.pem';
1226 $fp = $cert->{fingerprint} if !$fp || $filename eq 'pveproxy-ssl.pem';
1227 }
1228 $conn_args->{cached_fingerprints} = { uc($fp) => 1 }
1229 if defined($fp);
1230 }
1231
1232 my $storecfg = PVE::Storage::config();
1233 my $target_storage = extract_param($param, 'target-storage');
1234 my $storagemap = eval { PVE::JSONSchema::parse_idmap($target_storage, 'pve-storage-id') };
1235 raise_param_exc({ 'target-storage' => "failed to parse storage map: $@" })
1236 if $@;
1237
1238 my $target_bridge = extract_param($param, 'target-bridge');
1239 my $bridgemap = eval { PVE::JSONSchema::parse_idmap($target_bridge, 'pve-bridge-id') };
1240 raise_param_exc({ 'target-bridge' => "failed to parse bridge map: $@" })
1241 if $@;
1242
1243 die "remote migration requires explicit storage mapping!\n"
1244 if $storagemap->{identity};
1245
1246 $param->{storagemap} = $storagemap;
1247 $param->{bridgemap} = $bridgemap;
1248 $param->{remote} = {
1249 conn => $conn_args, # re-use fingerprint for tunnel
1250 client => $api_client,
1251 vmid => $target_vmid,
1252 };
1253 $param->{migration_type} = 'websocket';
1254 $param->{delete} = $delete if $delete;
1255
1256 my $cluster_status = $api_client->get("/cluster/status");
1257 my $target_node;
1258 foreach my $entry (@$cluster_status) {
1259 next if $entry->{type} ne 'node';
1260 if ($entry->{local}) {
1261 $target_node = $entry->{name};
1262 last;
1263 }
1264 }
1265
1266 die "couldn't determine endpoint's node name\n"
1267 if !defined($target_node);
1268
1269 my $realcmd = sub {
1270 PVE::LXC::Migrate->migrate($target_node, $remote->{host}, $source_vmid, $param);
1271 };
1272
1273 my $worker = sub {
1274 return PVE::GuestHelpers::guest_migration_lock($source_vmid, 10, $realcmd);
1275 };
1276
1277 return $rpcenv->fork_worker('vzmigrate', $source_vmid, $authuser, $worker);
1278 }});
1279
1280
5c752bbf 1281__PACKAGE__->register_method({
52389a07
DM
1282 name => 'migrate_vm',
1283 path => '{vmid}/migrate',
5c752bbf
DM
1284 method => 'POST',
1285 protected => 1,
1286 proxyto => 'node',
52389a07 1287 description => "Migrate the container to another node. Creates a new migration task.",
5c752bbf 1288 permissions => {
52389a07 1289 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
5c752bbf
DM
1290 },
1291 parameters => {
1292 additionalProperties => 0,
1293 properties => {
1294 node => get_standard_option('pve-node'),
68e8f3c5
DM
1295 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
1296 target => get_standard_option('pve-node', {
1297 description => "Target node.",
39bb88df 1298 completion => \&PVE::Cluster::complete_migration_target,
68e8f3c5 1299 }),
e90ddc4c 1300 'target-storage' => get_standard_option('pve-targetstorage'),
52389a07
DM
1301 online => {
1302 type => 'boolean',
1303 description => "Use online/live migration.",
1304 optional => 1,
1305 },
00d8cdc0
DC
1306 restart => {
1307 type => 'boolean',
1308 description => "Use restart migration",
1309 optional => 1,
1310 },
1311 timeout => {
1312 type => 'integer',
1313 description => "Timeout in seconds for shutdown for restart migration",
1314 optional => 1,
1315 default => 180,
1316 },
8ead60ef
SI
1317 bwlimit => {
1318 description => "Override I/O bandwidth limit (in KiB/s).",
1319 optional => 1,
1320 type => 'number',
1321 minimum => '0',
41e9b65c 1322 default => 'migrate limit from datacenter or storage config',
8ead60ef 1323 },
5c752bbf
DM
1324 },
1325 },
1326 returns => {
1327 type => 'string',
52389a07 1328 description => "the task ID.",
5c752bbf
DM
1329 },
1330 code => sub {
1331 my ($param) = @_;
1332
1333 my $rpcenv = PVE::RPCEnvironment::get();
1334
1335 my $authuser = $rpcenv->get_user();
1336
52389a07 1337 my $target = extract_param($param, 'target');
bb1ac2de 1338
52389a07
DM
1339 my $localnode = PVE::INotify::nodename();
1340 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
bb1ac2de 1341
52389a07 1342 PVE::Cluster::check_cfs_quorum();
5c752bbf 1343
52389a07 1344 PVE::Cluster::check_node_exists($target);
27916659 1345
52389a07 1346 my $targetip = PVE::Cluster::remote_node_ip($target);
5c752bbf 1347
52389a07 1348 my $vmid = extract_param($param, 'vmid');
5c752bbf 1349
52389a07 1350 # test if VM exists
67afe46e 1351 PVE::LXC::Config->load_config($vmid);
5c752bbf 1352
52389a07
DM
1353 # try to detect errors early
1354 if (PVE::LXC::check_running($vmid)) {
00d8cdc0
DC
1355 die "can't migrate running container without --online or --restart\n"
1356 if !$param->{online} && !$param->{restart};
5c752bbf 1357 }
5c752bbf 1358
e90ddc4c
FG
1359 if (my $targetstorage = delete $param->{'target-storage'}) {
1360 my $storecfg = PVE::Storage::config();
1361 my $storagemap = eval { PVE::JSONSchema::parse_idmap($targetstorage, 'pve-storage-id') };
26115ef2 1362 raise_param_exc({ 'target-storage' => "failed to parse storage map: $@" })
e90ddc4c
FG
1363 if $@;
1364
1365 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
1366 if !defined($storagemap->{identity});
1367
1368 foreach my $target_sid (values %{$storagemap->{entries}}) {
1369 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
1370 }
1371
1372 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
1373 if $storagemap->{default};
1374
1375 $param->{storagemap} = $storagemap;
1376 }
1377
5c752bbf
DM
1378 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1379
1380 my $hacmd = sub {
1381 my $upid = shift;
1382
1383 my $service = "ct:$vmid";
1384
52389a07 1385 my $cmd = ['ha-manager', 'migrate', $service, $target];
5c752bbf 1386
545dd4e1 1387 print "Requesting HA migration for CT $vmid to node $target\n";
5c752bbf
DM
1388
1389 PVE::Tools::run_command($cmd);
1390
1391 return;
1392 };
1393
52389a07 1394 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
5c752bbf
DM
1395
1396 } else {
1397
22557519
DM
1398 my $realcmd = sub {
1399 PVE::LXC::Migrate->migrate($target, $targetip, $vmid, $param);
1400 };
56462053 1401
22557519
DM
1402 my $worker = sub {
1403 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
5c752bbf
DM
1404 };
1405
22557519 1406 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
5c752bbf
DM
1407 }
1408 }});
1409
cc5392c8
WL
1410__PACKAGE__->register_method({
1411 name => 'vm_feature',
1412 path => '{vmid}/feature',
1413 method => 'GET',
1414 proxyto => 'node',
1415 protected => 1,
1416 description => "Check if feature for virtual machine is available.",
1417 permissions => {
1418 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1419 },
1420 parameters => {
1421 additionalProperties => 0,
1422 properties => {
1423 node => get_standard_option('pve-node'),
1424 vmid => get_standard_option('pve-vmid'),
1425 feature => {
1426 description => "Feature to check.",
1427 type => 'string',
d53e5c58 1428 enum => [ 'snapshot', 'clone', 'copy' ],
cc5392c8 1429 },
5ec3949c 1430 snapname => get_standard_option('pve-snapshot-name', {
cc5392c8
WL
1431 optional => 1,
1432 }),
1433 },
1434 },
1435 returns => {
1436 type => "object",
1437 properties => {
1438 hasFeature => { type => 'boolean' },
1439 #nodes => {
1440 #type => 'array',
1441 #items => { type => 'string' },
1442 #}
1443 },
1444 },
1445 code => sub {
1446 my ($param) = @_;
1447
1448 my $node = extract_param($param, 'node');
1449
1450 my $vmid = extract_param($param, 'vmid');
1451
1452 my $snapname = extract_param($param, 'snapname');
1453
1454 my $feature = extract_param($param, 'feature');
1455
67afe46e 1456 my $conf = PVE::LXC::Config->load_config($vmid);
cc5392c8
WL
1457
1458 if($snapname){
1459 my $snap = $conf->{snapshots}->{$snapname};
1460 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1461 $conf = $snap;
1462 }
ef241384 1463 my $storage_cfg = PVE::Storage::config();
cc5392c8 1464 #Maybe include later
ef241384 1465 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
4518000b 1466 my $hasFeature = PVE::LXC::Config->has_feature($feature, $conf, $storage_cfg, $snapname);
cc5392c8
WL
1467
1468 return {
1469 hasFeature => $hasFeature,
1470 #nodes => [ keys %$nodelist ],
1471 };
1472 }});
bb1ac2de
DM
1473
1474__PACKAGE__->register_method({
1475 name => 'template',
1476 path => '{vmid}/template',
1477 method => 'POST',
1478 protected => 1,
1479 proxyto => 'node',
1480 description => "Create a Template.",
1481 permissions => {
1482 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
1483 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1484 },
1485 parameters => {
1486 additionalProperties => 0,
1487 properties => {
1488 node => get_standard_option('pve-node'),
68e8f3c5 1489 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid_stopped }),
bb1ac2de
DM
1490 },
1491 },
1492 returns => { type => 'null'},
1493 code => sub {
1494 my ($param) = @_;
1495
1496 my $rpcenv = PVE::RPCEnvironment::get();
1497
1498 my $authuser = $rpcenv->get_user();
1499
1500 my $node = extract_param($param, 'node');
1501
1502 my $vmid = extract_param($param, 'vmid');
1503
1504 my $updatefn = sub {
1505
67afe46e
FG
1506 my $conf = PVE::LXC::Config->load_config($vmid);
1507 PVE::LXC::Config->check_lock($conf);
bb1ac2de
DM
1508
1509 die "unable to create template, because CT contains snapshots\n"
1510 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
1511
1512 die "you can't convert a template to a template\n"
67afe46e 1513 if PVE::LXC::Config->is_template($conf);
bb1ac2de
DM
1514
1515 die "you can't convert a CT to template if the CT is running\n"
1516 if PVE::LXC::check_running($vmid);
1517
1518 my $realcmd = sub {
1519 PVE::LXC::template_create($vmid, $conf);
bb1ac2de 1520
c2182c49 1521 $conf->{template} = 1;
bb1ac2de 1522
c2182c49
WL
1523 PVE::LXC::Config->write_config($vmid, $conf);
1524 # and remove lxc config
1525 PVE::LXC::update_lxc_config($vmid, $conf);
1526 };
bb1ac2de
DM
1527
1528 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1529 };
1530
67afe46e 1531 PVE::LXC::Config->lock_config($vmid, $updatefn);
bb1ac2de
DM
1532
1533 return undef;
1534 }});
1535
c4a33727
DM
1536__PACKAGE__->register_method({
1537 name => 'clone_vm',
1538 path => '{vmid}/clone',
1539 method => 'POST',
1540 protected => 1,
1541 proxyto => 'node',
1542 description => "Create a container clone/copy",
1543 permissions => {
1544 description => "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1545 "and 'VM.Allocate' permissions " .
1546 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
ee81952f 1547 "'Datastore.AllocateSpace' on any used storage, and 'SDN.Use' on any bridge.",
c4a33727
DM
1548 check =>
1549 [ 'and',
1550 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1551 [ 'or',
1552 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1553 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
1554 ],
1555 ]
1556 },
1557 parameters => {
1558 additionalProperties => 0,
1559 properties => {
1560 node => get_standard_option('pve-node'),
1561 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
1562 newid => get_standard_option('pve-vmid', {
1563 completion => \&PVE::Cluster::complete_next_vmid,
1564 description => 'VMID for the clone.' }),
1565 hostname => {
1566 optional => 1,
1567 type => 'string', format => 'dns-name',
1568 description => "Set a hostname for the new CT.",
1569 },
1570 description => {
1571 optional => 1,
1572 type => 'string',
1573 description => "Description for the new CT.",
1574 },
1575 pool => {
1576 optional => 1,
1577 type => 'string', format => 'pve-poolid',
1578 description => "Add the new CT to the specified pool.",
1579 },
5ec3949c 1580 snapname => get_standard_option('pve-snapshot-name', {
c4a33727
DM
1581 optional => 1,
1582 }),
1583 storage => get_standard_option('pve-storage-id', {
1584 description => "Target storage for full clone.",
c4a33727
DM
1585 optional => 1,
1586 }),
1587 full => {
1588 optional => 1,
1589 type => 'boolean',
c4b4cb83 1590 description => "Create a full copy of all disks. This is always done when " .
c4a33727 1591 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
c4a33727 1592 },
411ae12c
DM
1593 target => get_standard_option('pve-node', {
1594 description => "Target node. Only allowed if the original VM is on shared storage.",
1595 optional => 1,
1596 }),
8ead60ef
SI
1597 bwlimit => {
1598 description => "Override I/O bandwidth limit (in KiB/s).",
1599 optional => 1,
1600 type => 'number',
1601 minimum => '0',
41e9b65c 1602 default => 'clone limit from datacenter or storage config',
8ead60ef 1603 },
c4a33727
DM
1604 },
1605 },
1606 returns => {
1607 type => 'string',
1608 },
1609 code => sub {
1610 my ($param) = @_;
1611
1612 my $rpcenv = PVE::RPCEnvironment::get();
59b0ce55 1613 my $authuser = $rpcenv->get_user();
c4a33727
DM
1614
1615 my $node = extract_param($param, 'node');
c4a33727 1616 my $vmid = extract_param($param, 'vmid');
c4a33727 1617 my $newid = extract_param($param, 'newid');
c4a33727 1618 my $pool = extract_param($param, 'pool');
c4a33727
DM
1619 if (defined($pool)) {
1620 $rpcenv->check_pool_exist($pool);
1621 }
c4a33727 1622 my $snapname = extract_param($param, 'snapname');
c4a33727 1623 my $storage = extract_param($param, 'storage');
411ae12c 1624 my $target = extract_param($param, 'target');
c4a33727
DM
1625 my $localnode = PVE::INotify::nodename();
1626
59b0ce55 1627 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
411ae12c
DM
1628
1629 PVE::Cluster::check_node_exists($target) if $target;
1630
c4a33727
DM
1631 my $storecfg = PVE::Storage::config();
1632
1633 if ($storage) {
1634 # check if storage is enabled on local node
1635 PVE::Storage::storage_check_enabled($storecfg, $storage);
411ae12c
DM
1636 if ($target) {
1637 # check if storage is available on target node
a1c27f86 1638 PVE::Storage::storage_check_enabled($storecfg, $storage, $target);
411ae12c
DM
1639 # clone only works if target storage is shared
1640 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
1641 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared};
1642 }
c4a33727
DM
1643 }
1644
1645 PVE::Cluster::check_cfs_quorum();
1646
db01d674
WB
1647 my $newconf = {};
1648 my $mountpoints = {};
1649 my $fullclone = {};
1650 my $vollist = [];
411ae12c 1651 my $running;
c4a33727 1652
b062187a
FG
1653 my $lock_and_reload = sub {
1654 my ($vmid, $code) = @_;
1655 return PVE::LXC::Config->lock_config($vmid, sub {
1656 my $conf = PVE::LXC::Config->load_config($vmid);
1657 die "Lost 'create' config lock, aborting.\n"
1658 if !PVE::LXC::Config->has_lock($conf, 'create');
1659
1660 return $code->($conf);
1661 });
1662 };
e7d553c7 1663
cc1ffe7e 1664 my $src_conf = PVE::LXC::Config->set_lock($vmid, 'disk');
411ae12c 1665
becea45d
DT
1666 eval {
1667 PVE::LXC::Config->create_and_lock_config($newid, 0);
1668 };
1669 if (my $err = $@) {
1670 eval { PVE::LXC::Config->remove_lock($vmid, 'disk') };
1671 warn "Failed to remove source CT config lock - $@\n" if $@;
e7d553c7 1672
becea45d 1673 die $err;
cc1ffe7e 1674 }
c4a33727 1675
cc1ffe7e 1676 eval {
becea45d
DT
1677 $running = PVE::LXC::check_running($vmid) || 0;
1678
1679 my $full = extract_param($param, 'full');
1680 if (!defined($full)) {
1681 $full = !PVE::LXC::Config->is_template($src_conf);
1682 }
1683
1684 PVE::Firewall::clone_vmfw_conf($vmid, $newid);
1685
5da8f412
FG
1686 die "parameter 'storage' not allowed for linked clones\n"
1687 if defined($storage) && !$full;
1688
cc1ffe7e
FG
1689 die "snapshot '$snapname' does not exist\n"
1690 if $snapname && !defined($src_conf->{snapshots}->{$snapname});
c4a33727 1691
cc1ffe7e 1692 my $src_conf = $snapname ? $src_conf->{snapshots}->{$snapname} : $src_conf;
c4a33727 1693
cc1ffe7e
FG
1694 my $sharedvm = 1;
1695 for my $opt (sort keys %$src_conf) {
1696 next if $opt =~ m/^unused\d+$/;
c4a33727 1697
cc1ffe7e 1698 my $value = $src_conf->{$opt};
c4a33727 1699
cc1ffe7e
FG
1700 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1701 my $mp = PVE::LXC::Config->parse_volume($opt, $value);
09687674 1702
cc1ffe7e
FG
1703 if ($mp->{type} eq 'volume') {
1704 my $volid = $mp->{volume};
09687674 1705
cc1ffe7e
FG
1706 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);
1707 $sid = $storage if defined($storage);
1708 my $scfg = PVE::Storage::storage_config($storecfg, $sid);
1709 if (!$scfg->{shared}) {
1710 $sharedvm = 0;
1711 warn "found non-shared volume: $volid\n" if $target;
1712 }
c4a33727 1713
cc1ffe7e 1714 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
c4a33727 1715
cc1ffe7e
FG
1716 if ($full) {
1717 die "Cannot do full clones on a running container without snapshots\n"
1718 if $running && !defined($snapname);
1719 $fullclone->{$opt} = 1;
c4a33727 1720 } else {
cc1ffe7e
FG
1721 # not full means clone instead of copy
1722 die "Linked clone feature for '$volid' is not available\n"
1723 if !PVE::Storage::volume_has_feature($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
c4a33727 1724 }
cc1ffe7e
FG
1725
1726 $mountpoints->{$opt} = $mp;
1727 push @$vollist, $volid;
1728
c4a33727 1729 } else {
cc1ffe7e
FG
1730 # TODO: allow bind mounts?
1731 die "unable to clone mountpoint '$opt' (type $mp->{type})\n";
c4a33727 1732 }
cc1ffe7e
FG
1733 } elsif ($opt =~ m/^net(\d+)$/) {
1734 # always change MAC! address
1735 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1736 my $net = PVE::LXC::Config->parse_lxc_network($value);
1737 $net->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1738 $newconf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
ee81952f
FG
1739
1740 PVE::LXC::check_bridge_access($rpcenv, $authuser, $newconf->{$opt});
cc1ffe7e
FG
1741 } else {
1742 # copy everything else
1743 $newconf->{$opt} = $value;
db01d674 1744 }
cc1ffe7e
FG
1745 }
1746 die "can't clone CT to node '$target' (CT uses local storage)\n"
1747 if $target && !$sharedvm;
c4a33727 1748
cc1ffe7e
FG
1749 # Replace the 'disk' lock with a 'create' lock.
1750 $newconf->{lock} = 'create';
db01d674 1751
04a34c56 1752 # delete all snapshot related config options
a0167a5c 1753 delete $newconf->@{qw(snapshots parent snaptime snapstate)};
04a34c56 1754
cc1ffe7e
FG
1755 delete $newconf->{pending};
1756 delete $newconf->{template};
c4a33727 1757
a0167a5c
TL
1758 $newconf->{hostname} = $param->{hostname} if $param->{hostname};
1759 $newconf->{description} = $param->{description} if $param->{description};
c4a33727 1760
b062187a 1761 $lock_and_reload->($newid, sub {
cc1ffe7e
FG
1762 PVE::LXC::Config->write_config($newid, $newconf);
1763 });
1764 };
1765 if (my $err = $@) {
1766 eval { PVE::LXC::Config->remove_lock($vmid, 'disk') };
1767 warn "Failed to remove source CT config lock - $@\n" if $@;
1768
1769 eval {
b062187a 1770 $lock_and_reload->($newid, sub {
cc1ffe7e 1771 PVE::LXC::Config->destroy_config($newid);
5eacc654 1772 PVE::Firewall::remove_vmfw_conf($newid);
7b290bf3 1773 });
db01d674 1774 };
cc1ffe7e 1775 warn "Failed to remove target CT config - $@\n" if $@;
52d287d9 1776
cc1ffe7e
FG
1777 die $err;
1778 }
c4a33727 1779
db01d674
WB
1780 my $update_conf = sub {
1781 my ($key, $value) = @_;
b062187a
FG
1782 return $lock_and_reload->($newid, sub {
1783 my $conf = shift;
db01d674
WB
1784 $conf->{$key} = $value;
1785 PVE::LXC::Config->write_config($newid, $conf);
1786 });
1787 };
c4a33727 1788
db01d674
WB
1789 my $realcmd = sub {
1790 my ($upid) = @_;
c4a33727 1791
db01d674 1792 my $newvollist = [];
c4a33727 1793
411ae12c
DM
1794 my $verify_running = PVE::LXC::check_running($vmid) || 0;
1795 die "unexpected state change\n" if $verify_running != $running;
1796
db01d674
WB
1797 eval {
1798 local $SIG{INT} =
1799 local $SIG{TERM} =
1800 local $SIG{QUIT} =
1801 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
c4a33727 1802
db01d674 1803 PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
8ead60ef 1804 my $bwlimit = extract_param($param, 'bwlimit');
c4a33727 1805
db01d674
WB
1806 foreach my $opt (keys %$mountpoints) {
1807 my $mp = $mountpoints->{$opt};
1808 my $volid = $mp->{volume};
c4a33727 1809
db01d674
WB
1810 my $newvolid;
1811 if ($fullclone->{$opt}) {
1812 print "create full clone of mountpoint $opt ($volid)\n";
8ead60ef
SI
1813 my $source_storage = PVE::Storage::parse_volume_id($volid);
1814 my $target_storage = $storage // $source_storage;
1815 my $clonelimit = PVE::Storage::get_bandwidth_limit('clone', [$source_storage, $target_storage], $bwlimit);
1816 $newvolid = PVE::LXC::copy_volume($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
db01d674
WB
1817 } else {
1818 print "create linked clone of mount point $opt ($volid)\n";
1819 $newvolid = PVE::Storage::vdisk_clone($storecfg, $volid, $newid, $snapname);
c4a33727
DM
1820 }
1821
db01d674
WB
1822 push @$newvollist, $newvolid;
1823 $mp->{volume} = $newvolid;
c4a33727 1824
db01d674 1825 $update_conf->($opt, PVE::LXC::Config->print_ct_mountpoint($mp, $opt eq 'rootfs'));
c4a33727
DM
1826 }
1827
db01d674 1828 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
8e08cd17 1829
b062187a
FG
1830 $lock_and_reload->($newid, sub {
1831 my $conf = shift;
1832 my $rootdir = PVE::LXC::mount_all($newid, $storecfg, $conf, 1);
1833 eval {
1834 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
1835 $lxc_setup->post_clone_hook($conf);
1836 };
1837 my $err = $@;
1838 eval { PVE::LXC::umount_all($newid, $storecfg, $conf, 1); };
1839 if ($err) {
1840 warn "$@\n" if $@;
1841 die $err;
1842 } else {
1843 die $@ if $@;
7b290bf3
OB
1844 }
1845 });
c4a33727 1846 };
db01d674 1847 my $err = $@;
db01d674
WB
1848 # Unlock the source config in any case:
1849 eval { PVE::LXC::Config->remove_lock($vmid, 'disk') };
1850 warn $@ if $@;
c4a33727 1851
db01d674
WB
1852 if ($err) {
1853 # Now cleanup the config & disks:
db01d674
WB
1854 sleep 1; # some storages like rbd need to wait before release volume - really?
1855
1856 foreach my $volid (@$newvollist) {
1857 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
1858 warn $@ if $@;
1859 }
52d287d9
FG
1860
1861 eval {
b062187a 1862 $lock_and_reload->($newid, sub {
52d287d9 1863 PVE::LXC::Config->destroy_config($newid);
5eacc654 1864 PVE::Firewall::remove_vmfw_conf($newid);
52d287d9
FG
1865 });
1866 };
1867 warn "Failed to remove target CT config - $@\n" if $@;
1868
db01d674
WB
1869 die "clone failed: $err";
1870 }
1871
b062187a
FG
1872 $lock_and_reload->($newid, sub {
1873 PVE::LXC::Config->remove_lock($newid, 'create');
1874
1875 if ($target) {
1876 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1877 PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
1878 PVE::Storage::deactivate_volumes($storecfg, $newvollist);
1879
1880 PVE::LXC::Config->move_config_to_node($newid, $target);
1881 }
1882 });
1883
db01d674 1884 return;
c4a33727
DM
1885 };
1886
db01d674 1887 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
c4a33727
DM
1888 }});
1889
1890
985b18ed
WL
1891__PACKAGE__->register_method({
1892 name => 'resize_vm',
1893 path => '{vmid}/resize',
1894 method => 'PUT',
1895 protected => 1,
1896 proxyto => 'node',
235dbdf3 1897 description => "Resize a container mount point.",
985b18ed
WL
1898 permissions => {
1899 check => ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any => 1],
1900 },
1901 parameters => {
1902 additionalProperties => 0,
b8c5a95f
WB
1903 properties => {
1904 node => get_standard_option('pve-node'),
1905 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
1906 disk => {
1907 type => 'string',
1908 description => "The disk you want to resize.",
5e5d76cf 1909 enum => [PVE::LXC::Config->valid_volume_keys()],
b8c5a95f
WB
1910 },
1911 size => {
1912 type => 'string',
1913 pattern => '\+?\d+(\.\d+)?[KMGT]?',
1914 description => "The new size. With the '+' sign the value is added to the actual size of the volume and without it, the value is taken as an absolute one. Shrinking disk size is not supported.",
1915 },
1916 digest => {
1917 type => 'string',
1918 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1919 maxLength => 40,
1920 optional => 1,
1921 }
1922 },
985b18ed 1923 },
9f3f7963
WL
1924 returns => {
1925 type => 'string',
1926 description => "the task ID.",
1927 },
985b18ed
WL
1928 code => sub {
1929 my ($param) = @_;
1930
1931 my $rpcenv = PVE::RPCEnvironment::get();
1932
1933 my $authuser = $rpcenv->get_user();
1934
1935 my $node = extract_param($param, 'node');
1936
1937 my $vmid = extract_param($param, 'vmid');
1938
1939 my $digest = extract_param($param, 'digest');
1940
1941 my $sizestr = extract_param($param, 'size');
1942 my $ext = ($sizestr =~ s/^\+//);
f0595e61
FE
1943 my $request_size = PVE::JSONSchema::parse_size($sizestr);
1944 die "invalid size string" if !defined($request_size);
985b18ed
WL
1945
1946 die "no options specified\n" if !scalar(keys %$param);
1947
985b18ed
WL
1948 my $storage_cfg = cfs_read_file("storage.cfg");
1949
f0595e61 1950 my $load_and_check = sub {
67afe46e
FG
1951 my $conf = PVE::LXC::Config->load_config($vmid);
1952 PVE::LXC::Config->check_lock($conf);
985b18ed 1953
de41bced
DC
1954 PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, $conf, $param, [], $conf->{unprivileged});
1955
985b18ed
WL
1956 PVE::Tools::assert_if_modified($digest, $conf->{digest});
1957
985b18ed 1958 my $disk = $param->{disk};
e4034859 1959 my $mp = PVE::LXC::Config->parse_volume($disk, $conf->{$disk});
44a9face 1960
985b18ed
WL
1961 my $volid = $mp->{volume};
1962
1963 my (undef, undef, $owner, undef, undef, undef, $format) =
1964 PVE::Storage::parse_volname($storage_cfg, $volid);
1965
235dbdf3 1966 die "can't resize mount point owned by another container ($owner)"
985b18ed
WL
1967 if $vmid != $owner;
1968
985b18ed
WL
1969 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
1970
1971 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1972
4e1e1791
WB
1973 PVE::Storage::activate_volumes($storage_cfg, [$volid]);
1974
985b18ed 1975 my $size = PVE::Storage::volume_size_info($storage_cfg, $volid, 5);
2e8bcfef 1976
16f6c7c6 1977 die "Could not determine current size of volume '$volid'\n" if !defined($size);
2e8bcfef 1978
f0595e61 1979 my $newsize = $ext ? $size + $request_size : $request_size;
985b18ed
WL
1980 $newsize = int($newsize);
1981
1982 die "unable to shrink disk size\n" if $newsize < $size;
1983
5c36822a 1984 die "disk is already at specified size\n" if $size == $newsize;
985b18ed 1985
f0595e61
FE
1986 return ($conf, $disk, $mp, $volid, $format, $newsize);
1987 };
1988
1989 my $code = sub {
1990 my ($conf, $disk, $mp, $volid, $format, $newsize) = $load_and_check->();
1991
1992 my $running = PVE::LXC::check_running($vmid);
1993
985b18ed 1994 PVE::Cluster::log_msg('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
9f3f7963 1995
f0595e61
FE
1996 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1997 # we pass 0 here (parameter only makes sense for qemu)
1998 PVE::Storage::volume_resize($storage_cfg, $volid, $newsize, 0);
9f3f7963 1999
f0595e61
FE
2000 $mp->{size} = $newsize;
2001 $conf->{$disk} = PVE::LXC::Config->print_ct_mountpoint($mp, $disk eq 'rootfs');
9f3f7963 2002
f0595e61
FE
2003 PVE::LXC::Config->write_config($vmid, $conf);
2004
2005 if ($format eq 'raw') {
2006 # we need to ensure that the volume is mapped, if not needed this is a NOP
2007 my $path = PVE::Storage::map_volume($storage_cfg, $volid);
2008 $path = PVE::Storage::path($storage_cfg, $volid) if !defined($path);
2009 if ($running) {
2010
2011 $mp->{mp} = '/';
2012 my $use_loopdev = (PVE::LXC::mountpoint_mount_path($mp, $storage_cfg))[1];
2013 $path = PVE::LXC::query_loopdev($path) if $use_loopdev;
2014 die "internal error: CT running but mount point not attached to a loop device"
2015 if !$path;
2016 PVE::Tools::run_command(['losetup', '--set-capacity', $path]) if $use_loopdev;
2017
2018 # In order for resize2fs to know that we need online-resizing a mountpoint needs
2019 # to be visible to it in its namespace.
2020 # To not interfere with the rest of the system we unshare the current mount namespace,
2021 # mount over /tmp and then run resize2fs.
2022
2023 # interestingly we don't need to e2fsck on mounted systems...
2024 my $quoted = PVE::Tools::shellquote($path);
2025 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
2026 eval {
2027 PVE::Tools::run_command(['unshare', '-m', '--', 'sh', '-c', $cmd]);
2028 };
2029 warn "Failed to update the container's filesystem: $@\n" if $@;
2030 } else {
2031 eval {
2032 PVE::Tools::run_command(['e2fsck', '-f', '-y', $path]);
2033 PVE::Tools::run_command(['resize2fs', $path]);
2034 };
2035 warn "Failed to update the container's filesystem: $@\n" if $@;
2036
2037 # always un-map if not running, this is a NOP if not needed
2038 PVE::Storage::unmap_volume($storage_cfg, $volid);
985b18ed 2039 }
f0595e61
FE
2040 }
2041 };
985b18ed 2042
f0595e61
FE
2043 my $worker = sub {
2044 PVE::LXC::Config->lock_config($vmid, $code);;
9f3f7963 2045 };
985b18ed 2046
f0595e61
FE
2047 $load_and_check->(); # early checks before forking+locking
2048
2049 return $rpcenv->fork_worker('resize', $vmid, $authuser, $worker);
985b18ed
WL
2050 }});
2051
3c0f6806
WB
2052__PACKAGE__->register_method({
2053 name => 'move_volume',
2054 path => '{vmid}/move_volume',
2055 method => 'POST',
2056 protected => 1,
2057 proxyto => 'node',
a337a832 2058 description => "Move a rootfs-/mp-volume to a different storage or to a different container.",
3c0f6806
WB
2059 permissions => {
2060 description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
a337a832
AL
2061 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
2062 "a volume to another container, you need the permissions on the ".
2063 "target container as well.",
d568e8db 2064 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3c0f6806
WB
2065 },
2066 parameters => {
2067 additionalProperties => 0,
2068 properties => {
2069 node => get_standard_option('pve-node'),
2070 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
a337a832
AL
2071 'target-vmid' => get_standard_option('pve-vmid', {
2072 completion => \&PVE::LXC::complete_ctid,
2073 optional => 1,
2074 }),
3c0f6806
WB
2075 volume => {
2076 type => 'string',
a337a832
AL
2077 #TODO: check how to handle unused mount points as the mp parameter is not configured
2078 enum => [ PVE::LXC::Config->valid_volume_keys_with_unused() ],
3c0f6806
WB
2079 description => "Volume which will be moved.",
2080 },
2081 storage => get_standard_option('pve-storage-id', {
2082 description => "Target Storage.",
2083 completion => \&PVE::Storage::complete_storage_enabled,
a337a832 2084 optional => 1,
3c0f6806
WB
2085 }),
2086 delete => {
2087 type => 'boolean',
d2f08c5a
AL
2088 description => "Delete the original volume after successful copy. By default the " .
2089 "original is kept as an unused volume entry.",
3c0f6806
WB
2090 optional => 1,
2091 default => 0,
2092 },
2093 digest => {
2094 type => 'string',
d2f08c5a
AL
2095 description => 'Prevent changes if current configuration file has different SHA1 " .
2096 "digest. This can be used to prevent concurrent modifications.',
3c0f6806
WB
2097 maxLength => 40,
2098 optional => 1,
8ead60ef
SI
2099 },
2100 bwlimit => {
2101 description => "Override I/O bandwidth limit (in KiB/s).",
2102 optional => 1,
2103 type => 'number',
2104 minimum => '0',
41e9b65c 2105 default => 'clone limit from datacenter or storage config',
8ead60ef 2106 },
a337a832
AL
2107 'target-volume' => {
2108 type => 'string',
2109 description => "The config key the volume will be moved to. Default is the " .
2110 "source volume key.",
2111 enum => [PVE::LXC::Config->valid_volume_keys_with_unused()],
2112 optional => 1,
2113 },
2114 'target-digest' => {
2115 type => 'string',
2116 description => 'Prevent changes if current configuration file of the target " .
2117 "container has a different SHA1 digest. This can be used to prevent " .
2118 "concurrent modifications.',
2119 maxLength => 40,
2120 optional => 1,
2121 },
3c0f6806
WB
2122 },
2123 },
2124 returns => {
2125 type => 'string',
2126 },
2127 code => sub {
2128 my ($param) = @_;
2129
2130 my $rpcenv = PVE::RPCEnvironment::get();
2131
2132 my $authuser = $rpcenv->get_user();
2133
2134 my $vmid = extract_param($param, 'vmid');
2135
a337a832
AL
2136 my $target_vmid = extract_param($param, 'target-vmid');
2137
3c0f6806
WB
2138 my $storage = extract_param($param, 'storage');
2139
2140 my $mpkey = extract_param($param, 'volume');
2141
a337a832
AL
2142 my $target_mpkey = extract_param($param, 'target-volume') // $mpkey;
2143
2144 my $digest = extract_param($param, 'digest');
2145
2146 my $target_digest = extract_param($param, 'target-digest');
2147
3c0f6806
WB
2148 my $lockname = 'disk';
2149
2150 my ($mpdata, $old_volid);
2151
a337a832
AL
2152 die "either set storage or target-vmid, but not both\n"
2153 if $storage && $target_vmid;
3c0f6806 2154
a337a832 2155 my $storecfg = PVE::Storage::config();
3c0f6806 2156
a337a832
AL
2157 my $move_to_storage_checks = sub {
2158 PVE::LXC::Config->lock_config($vmid, sub {
2159 my $conf = PVE::LXC::Config->load_config($vmid);
2160 PVE::LXC::Config->check_lock($conf);
3c0f6806 2161
a337a832
AL
2162 die "cannot move volumes of a running container\n"
2163 if PVE::LXC::check_running($vmid);
3c0f6806 2164
a337a832
AL
2165 if ($mpkey =~ m/^unused\d+$/) {
2166 die "cannot move volume '$mpkey', only configured volumes can be moved to ".
2167 "another storage\n";
2168 }
3c0f6806 2169
a337a832
AL
2170 $mpdata = PVE::LXC::Config->parse_volume($mpkey, $conf->{$mpkey});
2171 $old_volid = $mpdata->{volume};
3c0f6806 2172
a337a832
AL
2173 die "you can't move a volume with snapshots and delete the source\n"
2174 if $param->{delete} && PVE::LXC::Config->is_volume_in_use_by_snapshots($conf, $old_volid);
2175
2176 PVE::Tools::assert_if_modified($digest, $conf->{digest});
2177
2178 PVE::LXC::Config->set_lock($vmid, $lockname);
2179 });
2180 };
2181
2182 my $storage_realcmd = sub {
3c0f6806 2183 eval {
d2f08c5a
AL
2184 PVE::Cluster::log_msg(
2185 'info',
2186 $authuser,
2187 "move volume CT $vmid: move --volume $mpkey --storage $storage"
2188 );
3c0f6806
WB
2189
2190 my $conf = PVE::LXC::Config->load_config($vmid);
2191 my $storage_cfg = PVE::Storage::config();
2192
2193 my $new_volid;
2194
2195 eval {
2196 PVE::Storage::activate_volumes($storage_cfg, [ $old_volid ]);
8ead60ef
SI
2197 my $bwlimit = extract_param($param, 'bwlimit');
2198 my $source_storage = PVE::Storage::parse_volume_id($old_volid);
d2f08c5a
AL
2199 my $movelimit = PVE::Storage::get_bandwidth_limit(
2200 'move',
2201 [$source_storage, $storage],
2202 $bwlimit
2203 );
2204 $new_volid = PVE::LXC::copy_volume(
2205 $mpdata,
2206 $vmid,
2207 $storage,
2208 $storage_cfg,
2209 $conf,
2210 undef,
2211 $movelimit
2212 );
3bfbd900
FE
2213 if (PVE::LXC::Config->is_template($conf)) {
2214 PVE::Storage::activate_volumes($storage_cfg, [ $new_volid ]);
2215 my $template_volid = PVE::Storage::vdisk_create_base($storage_cfg, $new_volid);
2216 $mpdata->{volume} = $template_volid;
2217 } else {
2218 $mpdata->{volume} = $new_volid;
2219 }
3c0f6806
WB
2220
2221 PVE::LXC::Config->lock_config($vmid, sub {
2222 my $digest = $conf->{digest};
2223 $conf = PVE::LXC::Config->load_config($vmid);
2224 PVE::Tools::assert_if_modified($digest, $conf->{digest});
2225
d2f08c5a
AL
2226 $conf->{$mpkey} = PVE::LXC::Config->print_ct_mountpoint(
2227 $mpdata,
2228 $mpkey eq 'rootfs'
2229 );
3c0f6806
WB
2230
2231 PVE::LXC::Config->add_unused_volume($conf, $old_volid) if !$param->{delete};
2232
2233 PVE::LXC::Config->write_config($vmid, $conf);
2234 });
2235
2236 eval {
2237 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2238 PVE::Storage::deactivate_volumes($storage_cfg, [ $new_volid ])
2239 };
2240 warn $@ if $@;
2241 };
2242 if (my $err = $@) {
2243 eval {
2244 PVE::Storage::vdisk_free($storage_cfg, $new_volid)
2245 if defined($new_volid);
2246 };
2247 warn $@ if $@;
2248 die $err;
2249 }
2250
4be329cb
DC
2251 my $deactivated = 0;
2252 eval {
2253 PVE::Storage::deactivate_volumes($storage_cfg, [ $old_volid ]);
2254 $deactivated = 1;
2255 };
2256 warn $@ if $@;
2257
3c0f6806 2258 if ($param->{delete}) {
4be329cb
DC
2259 my $removed = 0;
2260 if ($deactivated) {
2261 eval {
2262 PVE::Storage::vdisk_free($storage_cfg, $old_volid);
2263 $removed = 1;
2264 };
2265 warn $@ if $@;
2266 }
2267 if (!$removed) {
9abb0f8a
FE
2268 PVE::LXC::Config->lock_config($vmid, sub {
2269 my $conf = PVE::LXC::Config->load_config($vmid);
2270 PVE::LXC::Config->add_unused_volume($conf, $old_volid);
2271 PVE::LXC::Config->write_config($vmid, $conf);
2272 });
2273 }
3c0f6806
WB
2274 }
2275 };
2276 my $err = $@;
2277 eval { PVE::LXC::Config->remove_lock($vmid, $lockname) };
2278 warn $@ if $@;
2279 die $err if $err;
2280 };
a337a832
AL
2281
2282 my $load_and_check_reassign_configs = sub {
2283 my $vmlist = PVE::Cluster::get_vmlist()->{ids};
2284
2285 die "Cannot move to/from 'rootfs'\n" if $mpkey eq "rootfs" || $target_mpkey eq "rootfs";
2286
2287 if ($mpkey =~ m/^unused\d+$/ && $target_mpkey !~ m/^unused\d+$/) {
2288 die "Moving an unused volume to a used one is not possible\n";
2289 }
2290 die "could not find CT ${vmid}\n" if !exists($vmlist->{$vmid});
27e28f79 2291 die "could not find CT ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
a337a832 2292
27e28f79
FG
2293 my $source_node = $vmlist->{$vmid}->{node};
2294 my $target_node = $vmlist->{$target_vmid}->{node};
2295
2296 die "Both containers need to be on the same node ($source_node != $target_node)\n"
2297 if $source_node ne $target_node;
a337a832
AL
2298
2299 my $source_conf = PVE::LXC::Config->load_config($vmid);
2300 PVE::LXC::Config->check_lock($source_conf);
1e5a3948
TL
2301 my $target_conf;
2302 if ($target_vmid eq $vmid) {
2303 $target_conf = $source_conf;
2304 } else {
2305 $target_conf = PVE::LXC::Config->load_config($target_vmid);
2306 PVE::LXC::Config->check_lock($target_conf);
2307 }
a337a832 2308
27e28f79 2309 die "Can't move volumes from or to template CT\n"
a337a832
AL
2310 if ($source_conf->{template} || $target_conf->{template});
2311
2312 if ($digest) {
2313 eval { PVE::Tools::assert_if_modified($digest, $source_conf->{digest}) };
2314 die "Container ${vmid}: $@" if $@;
2315 }
2316
2317 if ($target_digest) {
2318 eval { PVE::Tools::assert_if_modified($target_digest, $target_conf->{digest}) };
2319 die "Container ${target_vmid}: $@" if $@;
2320 }
2321
2322 die "volume '${mpkey}' for container '$vmid' does not exist\n"
2323 if !defined($source_conf->{$mpkey});
2324
2325 die "Target volume key '${target_mpkey}' is already in use for container '$target_vmid'\n"
2326 if exists $target_conf->{$target_mpkey};
2327
3785fe32
TL
2328 my $drive = PVE::LXC::Config->parse_volume($mpkey, $source_conf->{$mpkey});
2329 my $source_volid = $drive->{volume} or die "Volume '${mpkey}' has no associated image\n";
a337a832
AL
2330 die "Cannot move volume used by a snapshot to another container\n"
2331 if PVE::LXC::Config->is_volume_in_use_by_snapshots($source_conf, $source_volid);
2332 die "Storage does not support moving of this disk to another container\n"
2333 if !PVE::Storage::volume_has_feature($storecfg, 'rename', $source_volid);
27e28f79 2334 die "Cannot move a bindmount or device mount to another container\n"
a337a832 2335 if $drive->{type} ne "volume";
3785fe32 2336 die "Cannot move in-use volume while the source CT is running - detach or shutdown first\n"
a337a832
AL
2337 if PVE::LXC::check_running($vmid) && $mpkey !~ m/^unused\d+$/;
2338
2339 my $repl_conf = PVE::ReplicationConfig->new();
27e28f79
FG
2340 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
2341 my ($storeid, undef) = PVE::Storage::parse_volume_id($source_volid);
2342 my $format = (PVE::Storage::parse_volname($storecfg, $source_volid))[6];
2343
2344 die "Cannot move volume on storage '$storeid' to a replicated container - missing replication support\n"
2345 if !PVE::Storage::storage_can_replicate($storecfg, $storeid, $format);
a337a832 2346 }
27e28f79
FG
2347
2348 return ($source_conf, $target_conf, $drive);
3c0f6806 2349 };
a337a832 2350
3785fe32 2351 my $logfunc = sub { print STDERR "$_[0]\n"; };
a337a832
AL
2352
2353 my $volume_reassignfn = sub {
2354 return PVE::LXC::Config->lock_config($vmid, sub {
2355 return PVE::LXC::Config->lock_config($target_vmid, sub {
3785fe32 2356 my ($source_conf, $target_conf, $drive) = $load_and_check_reassign_configs->();
27e28f79 2357 my $source_volid = $drive->{volume};
a337a832
AL
2358
2359 my $target_unused = $target_mpkey =~ m/^unused\d+$/;
2360
a337a832 2361 print "moving volume '$mpkey' from container '$vmid' to '$target_vmid'\n";
27e28f79 2362
a337a832
AL
2363 my ($storage, $source_volname) = PVE::Storage::parse_volume_id($source_volid);
2364
2365 my $fmt = (PVE::Storage::parse_volname($storecfg, $source_volid))[6];
2366
2367 my $new_volid = PVE::Storage::rename_volume(
2368 $storecfg,
2369 $source_volid,
2370 $target_vmid,
2371 );
2372
27e28f79 2373 $drive->{volume} = $new_volid;
a337a832
AL
2374
2375 delete $source_conf->{$mpkey};
2376 print "removing volume '${mpkey}' from container '${vmid}' config\n";
2377 PVE::LXC::Config->write_config($vmid, $source_conf);
2378
2379 my $drive_string;
a337a832
AL
2380 if ($target_unused) {
2381 $drive_string = $new_volid;
2382 } else {
27e28f79 2383 $drive_string = PVE::LXC::Config->print_volume($target_mpkey, $drive);
a337a832
AL
2384 }
2385
2386 if ($target_unused) {
2387 $target_conf->{$target_mpkey} = $drive_string;
2388 } else {
2389 my $running = PVE::LXC::check_running($target_vmid);
2390 my $param = { $target_mpkey => $drive_string };
2391 my $errors = PVE::LXC::Config->update_pct_config(
2392 $target_vmid,
2393 $target_conf,
2394 $running,
2395 $param
2396 );
3785fe32 2397 $rpcenv->warn($errors->{$_}) for keys $errors->%*;
a337a832
AL
2398 }
2399
2400 PVE::LXC::Config->write_config($target_vmid, $target_conf);
2401 $target_conf = PVE::LXC::Config->load_config($target_vmid);
2402
2403 PVE::LXC::update_lxc_config($target_vmid, $target_conf) if !$target_unused;
2404 print "target container '$target_vmid' updated with '$target_mpkey'\n";
2405
2406 # remove possible replication snapshots
2407 if (PVE::Storage::volume_has_feature($storecfg,'replicate', $source_volid)) {
2408 eval {
2409 PVE::Replication::prepare(
2410 $storecfg,
2411 [$new_volid],
2412 undef,
2413 1,
2414 undef,
2415 $logfunc,
2416 )
2417 };
2418 if (my $err = $@) {
2419 $rpcenv->warn("Failed to remove replication snapshots on volume ".
2420 "'${target_mpkey}'. Manual cleanup could be necessary. " .
2421 "Error: ${err}\n");
2422 }
2423 }
2424 });
2425 });
2426 };
2427
27e28f79
FG
2428 if ($target_vmid && $storage) {
2429 my $msg = "either set 'storage' or 'target-vmid', but not both";
2430 raise_param_exc({ 'target-vmid' => $msg, 'storage' => $msg });
2431 } elsif ($target_vmid) {
a337a832
AL
2432 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
2433 if $authuser ne 'root@pam';
2434
3785fe32 2435 my (undef, undef, $drive) = $load_and_check_reassign_configs->();
d568e8db
FG
2436 my $storeid = PVE::Storage::parse_volume_id($drive->{volume});
2437 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
a337a832
AL
2438 return $rpcenv->fork_worker(
2439 'move_volume',
2440 "${vmid}-${mpkey}>${target_vmid}-${target_mpkey}",
2441 $authuser,
2442 $volume_reassignfn
2443 );
2444 } elsif ($storage) {
d568e8db 2445 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
a337a832
AL
2446 &$move_to_storage_checks();
2447 my $task = eval {
2448 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $storage_realcmd);
2449 };
2450 if (my $err = $@) {
2451 eval { PVE::LXC::Config->remove_lock($vmid, $lockname) };
2452 warn $@ if $@;
2453 die $err;
2454 }
2455 return $task;
2456 } else {
27e28f79
FG
2457 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
2458 raise_param_exc({ 'target-vmid' => $msg, 'storage' => $msg });
3c0f6806 2459 }
3c0f6806
WB
2460 }});
2461
37c2dba8
OB
2462__PACKAGE__->register_method({
2463 name => 'vm_pending',
2464 path => '{vmid}/pending',
2465 method => 'GET',
2466 proxyto => 'node',
2467 description => 'Get container configuration, including pending changes.',
2468 permissions => {
2469 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2470 },
2471 parameters => {
2472 additionalProperties => 0,
2473 properties => {
2474 node => get_standard_option('pve-node'),
2475 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
2476 },
2477 },
2478 returns => {
2479 type => "array",
2480 items => {
2481 type => "object",
2482 properties => {
2483 key => {
2484 description => 'Configuration option name.',
2485 type => 'string',
2486 },
2487 value => {
2488 description => 'Current value.',
2489 type => 'string',
2490 optional => 1,
2491 },
2492 pending => {
2493 description => 'Pending value.',
2494 type => 'string',
2495 optional => 1,
2496 },
2497 delete => {
2498 description => "Indicates a pending delete request if present and not 0.",
2499 type => 'integer',
2500 minimum => 0,
2501 maximum => 2,
2502 optional => 1,
2503 },
2504 },
2505 },
2506 },
2507 code => sub {
2508 my ($param) = @_;
2509
2510 my $conf = PVE::LXC::Config->load_config($param->{vmid});
2511
2512 my $pending_delete_hash = PVE::LXC::Config->parse_pending_delete($conf->{pending}->{delete});
2513
f622e7dc 2514 return PVE::GuestHelpers::config_with_pending_array($conf, $pending_delete_hash);
37c2dba8
OB
2515 }});
2516
3d56c9c0
LN
2517__PACKAGE__->register_method({
2518 name => 'ip',
2519 path => '{vmid}/interfaces',
2520 method => 'GET',
2521 protected => 1,
2522 permissions => {
2523 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2524 },
2525 description => 'Get IP addresses of the specified container interface.',
2526 parameters => {
2527 additionalProperties => 0,
2528 properties => {
2529 node => get_standard_option('pve-node'),
2530 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
2531 },
2532 },
2533 returns => {
2534 type => "array",
2535 items => {
2536 type => 'object',
2537 properties => {
2538 name => {
2539 type => 'string',
2540 description => 'The name of the interface',
2541 optional => 0,
2542 },
2543 hwaddr => {
2544 type => 'string',
2545 description => 'The MAC address of the interface',
2546 optional => 0,
2547 },
2548 inet => {
2549 type => 'string',
2550 description => 'The IPv4 address of the interface',
2551 optional => 1,
2552 },
2553 inet6 => {
2554 type => 'string',
2555 description => 'The IPv6 address of the interface',
2556 optional => 1,
2557 },
2558 }
2559 },
2560 },
2561 code => sub {
2562 my ($param) = @_;
2563
2564 return PVE::LXC::get_interfaces($param->{vmid});
2565 }});
2566
0e0958a5
FG
2567__PACKAGE__->register_method({
2568 name => 'mtunnel',
2569 path => '{vmid}/mtunnel',
2570 method => 'POST',
2571 protected => 1,
2572 description => 'Migration tunnel endpoint - only for internal use by CT migration.',
2573 permissions => {
2574 check =>
2575 [ 'and',
2576 ['perm', '/vms/{vmid}', [ 'VM.Allocate' ]],
2577 ['perm', '/', [ 'Sys.Incoming' ]],
2578 ],
2579 description => "You need 'VM.Allocate' permissions on '/vms/{vmid}' and Sys.Incoming" .
2580 " on '/'. Further permission checks happen during the actual migration.",
2581 },
2582 parameters => {
2583 additionalProperties => 0,
2584 properties => {
2585 node => get_standard_option('pve-node'),
2586 vmid => get_standard_option('pve-vmid'),
2587 storages => {
2588 type => 'string',
2589 format => 'pve-storage-id-list',
2590 optional => 1,
2591 description => 'List of storages to check permission and availability. Will be checked again for all actually used storages during migration.',
2592 },
2593 bridges => {
2594 type => 'string',
2595 format => 'pve-bridge-id-list',
2596 optional => 1,
2597 description => 'List of network bridges to check availability. Will be checked again for actually used bridges during migration.',
2598 },
2599 },
2600 },
2601 returns => {
2602 additionalProperties => 0,
2603 properties => {
2604 upid => { type => 'string' },
2605 ticket => { type => 'string' },
2606 socket => { type => 'string' },
2607 },
2608 },
2609 code => sub {
2610 my ($param) = @_;
2611
2612 my $rpcenv = PVE::RPCEnvironment::get();
2613 my $authuser = $rpcenv->get_user();
2614
2615 my $node = extract_param($param, 'node');
2616 my $vmid = extract_param($param, 'vmid');
2617
2618 my $storages = extract_param($param, 'storages');
2619 my $bridges = extract_param($param, 'bridges');
2620
2621 my $nodename = PVE::INotify::nodename();
2622
2623 raise_param_exc({ node => "node needs to be 'localhost' or local hostname '$nodename'" })
2624 if $node ne 'localhost' && $node ne $nodename;
2625
2626 $node = $nodename;
2627
2628 my $storecfg = PVE::Storage::config();
2629 foreach my $storeid (PVE::Tools::split_list($storages)) {
2630 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storeid, $node);
2631 }
2632
2633 foreach my $bridge (PVE::Tools::split_list($bridges)) {
2634 PVE::Network::read_bridge_mtu($bridge);
2635 }
2636
2637 PVE::Cluster::check_cfs_quorum();
2638
2639 my $socket_addr = "/run/pve/ct-$vmid.mtunnel";
2640
2641 my $lock = 'create';
2642 eval { PVE::LXC::Config->create_and_lock_config($vmid, 0, $lock); };
2643
2644 raise_param_exc({ vmid => "unable to create empty CT config - $@"})
2645 if $@;
2646
2647 my $realcmd = sub {
2648 my $state = {
2649 storecfg => PVE::Storage::config(),
2650 lock => $lock,
2651 vmid => $vmid,
2652 };
2653
2654 my $run_locked = sub {
2655 my ($code, $params) = @_;
2656 return PVE::LXC::Config->lock_config($state->{vmid}, sub {
2657 my $conf = PVE::LXC::Config->load_config($state->{vmid});
2658
2659 $state->{conf} = $conf;
2660
2661 die "Encountered wrong lock - aborting mtunnel command handling.\n"
2662 if $state->{lock} && !PVE::LXC::Config->has_lock($conf, $state->{lock});
2663
2664 return $code->($params);
2665 });
2666 };
2667
2668 my $cmd_desc = {
2669 config => {
2670 conf => {
2671 type => 'string',
2672 description => 'Full CT config, adapted for target cluster/node',
2673 },
2674 'firewall-config' => {
2675 type => 'string',
2676 description => 'CT firewall config',
2677 optional => 1,
2678 },
2679 },
2680 ticket => {
2681 path => {
2682 type => 'string',
2683 description => 'socket path for which the ticket should be valid. must be known to current mtunnel instance.',
2684 },
2685 },
2686 quit => {
2687 cleanup => {
2688 type => 'boolean',
2689 description => 'remove CT config and volumes, aborting migration',
2690 default => 0,
2691 },
2692 },
2693 'disk-import' => $PVE::StorageTunnel::cmd_schema->{'disk-import'},
2694 'query-disk-import' => $PVE::StorageTunnel::cmd_schema->{'query-disk-import'},
2695 bwlimit => $PVE::StorageTunnel::cmd_schema->{bwlimit},
2696 };
2697
2698 my $cmd_handlers = {
2699 'version' => sub {
2700 # compared against other end's version
2701 # bump/reset for breaking changes
2702 # bump/bump for opt-in changes
2703 return {
2704 api => $PVE::LXC::Migrate::WS_TUNNEL_VERSION,
2705 age => 0,
2706 };
2707 },
2708 'config' => sub {
2709 my ($params) = @_;
2710
2711 # parse and write out VM FW config if given
2712 if (my $fw_conf = $params->{'firewall-config'}) {
2713 my ($path, $fh) = PVE::Tools::tempfile_contents($fw_conf, 700);
2714
2715 my $empty_conf = {
2716 rules => [],
2717 options => {},
2718 aliases => {},
2719 ipset => {} ,
2720 ipset_comments => {},
2721 };
2722 my $cluster_fw_conf = PVE::Firewall::load_clusterfw_conf();
2723
2724 # TODO: add flag for strict parsing?
2725 # TODO: add import sub that does all this given raw content?
2726 my $vmfw_conf = PVE::Firewall::generic_fw_config_parser($path, $cluster_fw_conf, $empty_conf, 'vm');
2727 $vmfw_conf->{vmid} = $state->{vmid};
2728 PVE::Firewall::save_vmfw_conf($state->{vmid}, $vmfw_conf);
2729
2730 $state->{cleanup}->{fw} = 1;
2731 }
2732
2733 my $conf_fn = "incoming/lxc/$state->{vmid}.conf";
2734 my $new_conf = PVE::LXC::Config::parse_pct_config($conf_fn, $params->{conf}, 1);
2735 delete $new_conf->{lock};
2736 delete $new_conf->{digest};
2737
2738 my $unprivileged = delete $new_conf->{unprivileged};
2739 my $arch = delete $new_conf->{arch};
2740
2741 # TODO handle properly?
2742 delete $new_conf->{snapshots};
2743 delete $new_conf->{parent};
2744 delete $new_conf->{pending};
2745 delete $new_conf->{lxc};
2746
2747 PVE::LXC::Config->remove_lock($state->{vmid}, 'create');
2748
2749 eval {
2750 my $conf = {
2751 unprivileged => $unprivileged,
2752 arch => $arch,
2753 };
2754 PVE::LXC::check_ct_modify_config_perm(
2755 $rpcenv,
2756 $authuser,
2757 $state->{vmid},
2758 undef,
2759 $conf,
2760 $new_conf,
2761 undef,
2762 $unprivileged,
2763 );
2764 my $errors = PVE::LXC::Config->update_pct_config(
2765 $state->{vmid},
2766 $conf,
2767 0,
2768 $new_conf,
2769 [],
2770 [],
2771 );
2772 raise_param_exc($errors) if scalar(keys %$errors);
2773 PVE::LXC::Config->write_config($state->{vmid}, $conf);
2774 PVE::LXC::update_lxc_config($vmid, $conf);
2775 };
2776 if (my $err = $@) {
2777 # revert to locked previous config
2778 my $conf = PVE::LXC::Config->load_config($state->{vmid});
2779 $conf->{lock} = 'create';
2780 PVE::LXC::Config->write_config($state->{vmid}, $conf);
2781
2782 die $err;
2783 }
2784
2785 my $conf = PVE::LXC::Config->load_config($state->{vmid});
2786 $conf->{lock} = 'migrate';
2787 PVE::LXC::Config->write_config($state->{vmid}, $conf);
2788
2789 $state->{lock} = 'migrate';
2790
2791 return;
2792 },
2793 'bwlimit' => sub {
2794 my ($params) = @_;
2795 return PVE::StorageTunnel::handle_bwlimit($params);
2796 },
2797 'disk-import' => sub {
2798 my ($params) = @_;
2799
2800 $check_storage_access_migrate->(
2801 $rpcenv,
2802 $authuser,
2803 $state->{storecfg},
2804 $params->{storage},
2805 $node
2806 );
2807
2808 $params->{unix} = "/run/pve/ct-$state->{vmid}.storage";
2809
2810 return PVE::StorageTunnel::handle_disk_import($state, $params);
2811 },
2812 'query-disk-import' => sub {
2813 my ($params) = @_;
2814
2815 return PVE::StorageTunnel::handle_query_disk_import($state, $params);
2816 },
2817 'unlock' => sub {
2818 PVE::LXC::Config->remove_lock($state->{vmid}, $state->{lock});
2819 delete $state->{lock};
2820 return;
2821 },
2822 'start' => sub {
2823 PVE::LXC::vm_start(
2824 $state->{vmid},
2825 $state->{conf},
2826 0
2827 );
2828
2829 return;
2830 },
2831 'stop' => sub {
2832 PVE::LXC::vm_stop($state->{vmid}, 1, 10, 1);
2833 return;
2834 },
2835 'ticket' => sub {
2836 my ($params) = @_;
2837
2838 my $path = $params->{path};
2839
2840 die "Not allowed to generate ticket for unknown socket '$path'\n"
2841 if !defined($state->{sockets}->{$path});
2842
2843 return { ticket => PVE::AccessControl::assemble_tunnel_ticket($authuser, "/socket/$path") };
2844 },
2845 'quit' => sub {
2846 my ($params) = @_;
2847
2848 if ($params->{cleanup}) {
2849 if ($state->{cleanup}->{fw}) {
2850 PVE::Firewall::remove_vmfw_conf($state->{vmid});
2851 }
2852
2853 for my $volid (keys $state->{cleanup}->{volumes}->%*) {
2854 print "freeing volume '$volid' as part of cleanup\n";
2855 eval { PVE::Storage::vdisk_free($state->{storecfg}, $volid) };
2856 warn $@ if $@;
2857 }
2858
2859 PVE::LXC::destroy_lxc_container(
2860 $state->{storecfg},
2861 $state->{vmid},
2862 $state->{conf},
2863 undef,
2864 0,
2865 );
2866 }
2867
2868 print "switching to exit-mode, waiting for client to disconnect\n";
2869 $state->{exit} = 1;
2870 return;
2871 },
2872 };
2873
2874 $run_locked->(sub {
2875 my $socket_addr = "/run/pve/ct-$state->{vmid}.mtunnel";
2876 unlink $socket_addr;
2877
2878 $state->{socket} = IO::Socket::UNIX->new(
2879 Type => SOCK_STREAM(),
2880 Local => $socket_addr,
2881 Listen => 1,
2882 );
2883
2884 $state->{socket_uid} = getpwnam('www-data')
2885 or die "Failed to resolve user 'www-data' to numeric UID\n";
2886 chown $state->{socket_uid}, -1, $socket_addr;
2887 });
2888
2889 print "mtunnel started\n";
2890
2891 my $conn = eval { PVE::Tools::run_with_timeout(300, sub { $state->{socket}->accept() }) };
2892 if ($@) {
2893 warn "Failed to accept tunnel connection - $@\n";
2894
2895 warn "Removing tunnel socket..\n";
2896 unlink $state->{socket};
2897
2898 warn "Removing temporary VM config..\n";
2899 $run_locked->(sub {
2900 PVE::LXC::destroy_config($state->{vmid});
2901 });
2902
2903 die "Exiting mtunnel\n";
2904 }
2905
2906 $state->{conn} = $conn;
2907
2908 my $reply_err = sub {
2909 my ($msg) = @_;
2910
2911 my $reply = JSON::encode_json({
2912 success => JSON::false,
2913 msg => $msg,
2914 });
2915 $conn->print("$reply\n");
2916 $conn->flush();
2917 };
2918
2919 my $reply_ok = sub {
2920 my ($res) = @_;
2921
2922 $res->{success} = JSON::true;
2923 my $reply = JSON::encode_json($res);
2924 $conn->print("$reply\n");
2925 $conn->flush();
2926 };
2927
2928 while (my $line = <$conn>) {
2929 chomp $line;
2930
2931 # untaint, we validate below if needed
2932 ($line) = $line =~ /^(.*)$/;
2933 my $parsed = eval { JSON::decode_json($line) };
2934 if ($@) {
2935 $reply_err->("failed to parse command - $@");
2936 next;
2937 }
2938
2939 my $cmd = delete $parsed->{cmd};
2940 if (!defined($cmd)) {
2941 $reply_err->("'cmd' missing");
2942 } elsif ($state->{exit}) {
2943 $reply_err->("tunnel is in exit-mode, processing '$cmd' cmd not possible");
2944 next;
2945 } elsif (my $handler = $cmd_handlers->{$cmd}) {
2946 print "received command '$cmd'\n";
2947 eval {
2948 if ($cmd_desc->{$cmd}) {
2949 PVE::JSONSchema::validate($parsed, $cmd_desc->{$cmd});
2950 } else {
2951 $parsed = {};
2952 }
2953 my $res = $run_locked->($handler, $parsed);
2954 $reply_ok->($res);
2955 };
2956 $reply_err->("failed to handle '$cmd' command - $@")
2957 if $@;
2958 } else {
2959 $reply_err->("unknown command '$cmd' given");
2960 }
2961 }
2962
2963 if ($state->{exit}) {
2964 print "mtunnel exited\n";
2965 } else {
2966 die "mtunnel exited unexpectedly\n";
2967 }
2968 };
2969
2970 my $ticket = PVE::AccessControl::assemble_tunnel_ticket($authuser, "/socket/$socket_addr");
2971 my $upid = $rpcenv->fork_worker('vzmtunnel', $vmid, $authuser, $realcmd);
2972
2973 return {
2974 ticket => $ticket,
2975 upid => $upid,
2976 socket => $socket_addr,
2977 };
2978 }});
2979
2980__PACKAGE__->register_method({
2981 name => 'mtunnelwebsocket',
2982 path => '{vmid}/mtunnelwebsocket',
2983 method => 'GET',
2984 permissions => {
2985 description => "You need to pass a ticket valid for the selected socket. Tickets can be created via the mtunnel API call, which will check permissions accordingly.",
2986 user => 'all', # check inside
2987 },
2988 description => 'Migration tunnel endpoint for websocket upgrade - only for internal use by VM migration.',
2989 parameters => {
2990 additionalProperties => 0,
2991 properties => {
2992 node => get_standard_option('pve-node'),
2993 vmid => get_standard_option('pve-vmid'),
2994 socket => {
2995 type => "string",
2996 description => "unix socket to forward to",
2997 },
2998 ticket => {
2999 type => "string",
3000 description => "ticket return by initial 'mtunnel' API call, or retrieved via 'ticket' tunnel command",
3001 },
3002 },
3003 },
3004 returns => {
3005 type => "object",
3006 properties => {
3007 port => { type => 'string', optional => 1 },
3008 socket => { type => 'string', optional => 1 },
3009 },
3010 },
3011 code => sub {
3012 my ($param) = @_;
3013
3014 my $rpcenv = PVE::RPCEnvironment::get();
3015 my $authuser = $rpcenv->get_user();
3016
3017 my $nodename = PVE::INotify::nodename();
3018 my $node = extract_param($param, 'node');
3019
3020 raise_param_exc({ node => "node needs to be 'localhost' or local hostname '$nodename'" })
3021 if $node ne 'localhost' && $node ne $nodename;
3022
3023 my $vmid = $param->{vmid};
3024 # check VM exists
3025 PVE::LXC::Config->load_config($vmid);
3026
3027 my $socket = $param->{socket};
3028 PVE::AccessControl::verify_tunnel_ticket($param->{ticket}, $authuser, "/socket/$socket");
3029
3030 return { socket => $socket };
3031 }});
f76a2828 30321;