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