]> git.proxmox.com Git - pve-container.git/blame - src/PVE/API2/LXC.pm
fix #1532: add target-storage support to migration
[pve-container.git] / src / PVE / API2 / LXC.pm
CommitLineData
f76a2828
DM
1package PVE::API2::LXC;
2
3use strict;
4use warnings;
5
6use PVE::SafeSyslog;
7use PVE::Tools qw(extract_param run_command);
0620edda 8use PVE::Exception qw(raise raise_param_exc raise_perm_exc);
f76a2828 9use PVE::INotify;
9c2d4ce9 10use PVE::Cluster qw(cfs_read_file);
d949527f 11use PVE::RRD;
8b076579 12use PVE::DataCenterConfig;
f76a2828 13use PVE::AccessControl;
2f9b5ead 14use PVE::Firewall;
f76a2828
DM
15use PVE::Storage;
16use PVE::RESTHandler;
17use PVE::RPCEnvironment;
c5a8e04f 18use PVE::ReplicationConfig;
f76a2828 19use PVE::LXC;
7af97ad5 20use PVE::LXC::Create;
6f42807e 21use PVE::LXC::Migrate;
56462053 22use PVE::GuestHelpers;
c5a09704 23use PVE::VZDump::Plugin;
52389a07
DM
24use PVE::API2::LXC::Config;
25use PVE::API2::LXC::Status;
26use PVE::API2::LXC::Snapshot;
f76a2828
DM
27use PVE::JSONSchema qw(get_standard_option);
28use base qw(PVE::RESTHandler);
29
6c2e9377
WB
30BEGIN {
31 if (!$ENV{PVE_GENERATING_DOCS}) {
32 require PVE::HA::Env::PVE2;
33 import PVE::HA::Env::PVE2;
34 require PVE::HA::Config;
35 import PVE::HA::Config;
36 }
37}
f76a2828 38
e90ddc4c
FG
39my $check_storage_access_migrate = sub {
40 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
41
42 PVE::Storage::storage_check_enabled($storecfg, $storage, $node);
43
44 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
45
46 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
47 die "storage '$storage' does not support CT rootdirs\n"
48 if !$scfg->{content}->{rootdir};
49};
50
52389a07
DM
51__PACKAGE__->register_method ({
52 subclass => "PVE::API2::LXC::Config",
fa91e46f 53 path => '{vmid}/config',
52389a07 54});
f76a2828 55
52389a07
DM
56__PACKAGE__->register_method ({
57 subclass => "PVE::API2::LXC::Status",
58 path => '{vmid}/status',
59});
f76a2828 60
52389a07
DM
61__PACKAGE__->register_method ({
62 subclass => "PVE::API2::LXC::Snapshot",
63 path => '{vmid}/snapshot',
64});
f76a2828 65
52389a07
DM
66__PACKAGE__->register_method ({
67 subclass => "PVE::API2::Firewall::CT",
68 path => '{vmid}/firewall',
69});
1e6c8d5b 70
f76a2828 71__PACKAGE__->register_method({
5c752bbf
DM
72 name => 'vmlist',
73 path => '',
f76a2828
DM
74 method => 'GET',
75 description => "LXC container index (per node).",
76 permissions => {
77 description => "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
78 user => 'all',
79 },
80 proxyto => 'node',
81 protected => 1, # /proc files are only readable by root
82 parameters => {
83 additionalProperties => 0,
84 properties => {
85 node => get_standard_option('pve-node'),
86 },
87 },
88 returns => {
89 type => 'array',
90 items => {
91 type => "object",
8233d33d 92 properties => $PVE::LXC::vmstatus_return_properties,
f76a2828
DM
93 },
94 links => [ { rel => 'child', href => "{vmid}" } ],
95 },
96 code => sub {
97 my ($param) = @_;
98
99 my $rpcenv = PVE::RPCEnvironment::get();
100 my $authuser = $rpcenv->get_user();
101
102 my $vmstatus = PVE::LXC::vmstatus();
103
104 my $res = [];
105 foreach my $vmid (keys %$vmstatus) {
106 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
107
108 my $data = $vmstatus->{$vmid};
f76a2828
DM
109 push @$res, $data;
110 }
111
112 return $res;
5c752bbf 113
f76a2828
DM
114 }});
115
9c2d4ce9 116__PACKAGE__->register_method({
5c752bbf
DM
117 name => 'create_vm',
118 path => '',
9c2d4ce9
DM
119 method => 'POST',
120 description => "Create or restore a container.",
121 permissions => {
122 user => 'all', # check inside
123 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
124 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
125 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
126 },
127 protected => 1,
128 proxyto => 'node',
129 parameters => {
130 additionalProperties => 0,
1b4cf758 131 properties => PVE::LXC::Config->json_config_properties({
9c2d4ce9 132 node => get_standard_option('pve-node'),
781e26b2 133 vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_next_vmid }),
9c2d4ce9
DM
134 ostemplate => {
135 description => "The OS template or backup file.",
5c752bbf 136 type => 'string',
9c2d4ce9 137 maxLength => 255,
68e8f3c5 138 completion => \&PVE::LXC::complete_os_templates,
9c2d4ce9 139 },
5c752bbf
DM
140 password => {
141 optional => 1,
9c2d4ce9
DM
142 type => 'string',
143 description => "Sets root password inside container.",
168d6b07 144 minLength => 5,
9c2d4ce9
DM
145 },
146 storage => get_standard_option('pve-storage-id', {
eb35f9c0 147 description => "Default Storage.",
9c2d4ce9
DM
148 default => 'local',
149 optional => 1,
c5362cda 150 completion => \&PVE::Storage::complete_storage_enabled,
9c2d4ce9
DM
151 }),
152 force => {
5c752bbf 153 optional => 1,
9c2d4ce9
DM
154 type => 'boolean',
155 description => "Allow to overwrite existing container.",
156 },
157 restore => {
5c752bbf 158 optional => 1,
9c2d4ce9
DM
159 type => 'boolean',
160 description => "Mark this as restore task.",
161 },
43912111
CE
162 unique => {
163 optional => 1,
164 type => 'boolean',
165 description => "Assign a unique random ethernet address.",
166 requires => 'restore',
167 },
5c752bbf 168 pool => {
9c2d4ce9
DM
169 optional => 1,
170 type => 'string', format => 'pve-poolid',
171 description => "Add the VM to the specified pool.",
172 },
7c78b6cc
WB
173 'ignore-unpack-errors' => {
174 optional => 1,
175 type => 'boolean',
176 description => "Ignore errors when extracting the template.",
177 },
34ddbf08
FG
178 'ssh-public-keys' => {
179 optional => 1,
180 type => 'string',
181 description => "Setup public SSH keys (one key per line, " .
182 "OpenSSH format).",
183 },
f9b4407e 184 bwlimit => {
8ead60ef 185 description => "Override I/O bandwidth limit (in KiB/s).",
f9b4407e
WB
186 optional => 1,
187 type => 'number',
188 minimum => '0',
41e9b65c 189 default => 'restore limit from datacenter or storage config',
f9b4407e 190 },
9d0225fb
TL
191 start => {
192 optional => 1,
193 type => 'boolean',
194 default => 0,
195 description => "Start the CT after its creation finished successfully.",
196 },
9c2d4ce9
DM
197 }),
198 },
5c752bbf 199 returns => {
9c2d4ce9
DM
200 type => 'string',
201 },
202 code => sub {
203 my ($param) = @_;
204
61783321
TL
205 PVE::Cluster::check_cfs_quorum();
206
9c2d4ce9 207 my $rpcenv = PVE::RPCEnvironment::get();
9c2d4ce9
DM
208 my $authuser = $rpcenv->get_user();
209
210 my $node = extract_param($param, 'node');
9c2d4ce9 211 my $vmid = extract_param($param, 'vmid');
7c78b6cc 212 my $ignore_unpack_errors = extract_param($param, 'ignore-unpack-errors');
f9b4407e 213 my $bwlimit = extract_param($param, 'bwlimit');
9d0225fb
TL
214 my $start_after_create = extract_param($param, 'start');
215
67afe46e 216 my $basecfg_fn = PVE::LXC::Config->config_file($vmid);
9c2d4ce9
DM
217 my $same_container_exists = -f $basecfg_fn;
218
425b62cb
WB
219 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
220 my $unprivileged = extract_param($param, 'unprivileged');
9c2d4ce9 221 my $restore = extract_param($param, 'restore');
43912111 222 my $unique = extract_param($param, 'unique');
9c2d4ce9 223
39170644
FG
224 # used to skip firewall config restore if user lacks permission
225 my $skip_fw_config_restore = 0;
226
148d1cb4
DM
227 if ($restore) {
228 # fixme: limit allowed parameters
148d1cb4 229 }
351bc0c4 230
9c2d4ce9
DM
231 my $force = extract_param($param, 'force');
232
233 if (!($same_container_exists && $restore && $force)) {
234 PVE::Cluster::check_vmid_unused($vmid);
e22af68f 235 } else {
61783321 236 die "can't overwrite running container\n" if PVE::LXC::check_running($vmid);
67afe46e
FG
237 my $conf = PVE::LXC::Config->load_config($vmid);
238 PVE::LXC::Config->check_protection($conf, "unable to restore CT $vmid");
9c2d4ce9 239 }
5c752bbf 240
9c2d4ce9 241 my $password = extract_param($param, 'password');
34ddbf08 242 my $ssh_keys = extract_param($param, 'ssh-public-keys');
2130286f 243 PVE::Tools::validate_ssh_public_keys($ssh_keys) if defined($ssh_keys);
34ddbf08 244
27916659 245 my $pool = extract_param($param, 'pool');
9c2d4ce9
DM
246 if (defined($pool)) {
247 $rpcenv->check_pool_exist($pool);
248 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
5c752bbf 249 }
9c2d4ce9
DM
250
251 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
252 # OK
253 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
254 # OK
255 } elsif ($restore && $force && $same_container_exists &&
256 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
257 # OK: user has VM.Backup permissions, and want to restore an existing VM
39170644
FG
258
259 # we don't want to restore a container-provided FW conf in this case
260 # since the user is lacking permission to configure the container's FW
261 $skip_fw_config_restore = 1;
b9623ef8
DC
262
263 # error out if a user tries to change from unprivileged to privileged
264 # explicit change is checked here, implicit is checked down below or happening in root-only paths
265 my $conf = PVE::LXC::Config->load_config($vmid);
266 if ($conf->{unprivileged} && defined($unprivileged) && !$unprivileged) {
267 raise_perm_exc("cannot change from unprivileged to privileged without VM.Allocate");
268 }
9c2d4ce9
DM
269 } else {
270 raise_perm_exc();
271 }
272
9eb69152 273 my $ostemplate = extract_param($param, 'ostemplate');
bb6afcb0
DM
274 my $storage = extract_param($param, 'storage') // 'local';
275
de41bced 276 PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, undef, $param, [], $unprivileged);
9eb69152 277
bb6afcb0 278 my $storage_cfg = cfs_read_file("storage.cfg");
9c2d4ce9 279
9c2d4ce9 280 my $archive;
9c2d4ce9 281 if ($ostemplate eq '-') {
351bc0c4
TM
282 die "pipe requires cli environment\n"
283 if $rpcenv->{type} ne 'cli';
284 die "pipe can only be used with restore tasks\n"
148d1cb4
DM
285 if !$restore;
286 $archive = '-';
b51a98d4 287 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs});
9c2d4ce9 288 } else {
f26c66e0 289 PVE::Storage::check_volume_access($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
99a0bcb0 290 $archive = $ostemplate;
9c2d4ce9
DM
291 }
292
f9b4407e 293 my %used_storages;
bb6afcb0
DM
294 my $check_and_activate_storage = sub {
295 my ($sid) = @_;
296
a1c27f86 297 my $scfg = PVE::Storage::storage_check_enabled($storage_cfg, $sid, $node);
bb6afcb0
DM
298
299 raise_param_exc({ storage => "storage '$sid' does not support container directories"})
300 if !$scfg->{content}->{rootdir};
301
302 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
303
304 PVE::Storage::activate_storage($storage_cfg, $sid);
f9b4407e 305 $used_storages{$sid} = 1;
bb6afcb0
DM
306 };
307
9c2d4ce9 308 my $conf = {};
5b4657d0 309
99132ce0
WB
310 my $is_root = $authuser eq 'root@pam';
311
b51a98d4 312 my $no_disk_param = {};
db18c1e4
FG
313 my $mp_param = {};
314 my $storage_only_mode = 1;
b51a98d4 315 foreach my $opt (keys %$param) {
78ccc99b
DM
316 my $value = $param->{$opt};
317 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
318 # allow to use simple numbers (add default storage in that case)
db18c1e4
FG
319 if ($value =~ m/^\d+(\.\d+)?$/) {
320 $mp_param->{$opt} = "$storage:$value";
321 } else {
322 $mp_param->{$opt} = $value;
323 }
324 $storage_only_mode = 0;
a9dd8015
FG
325 } elsif ($opt =~ m/^unused\d+$/) {
326 warn "ignoring '$opt', cannot create/restore with unused volume\n";
327 delete $param->{$opt};
78ccc99b
DM
328 } else {
329 $no_disk_param->{$opt} = $value;
330 }
b51a98d4 331 }
bb6afcb0 332
235dbdf3 333 die "mount points configured, but 'rootfs' not set - aborting\n"
db18c1e4
FG
334 if !$storage_only_mode && !defined($mp_param->{rootfs});
335
bb6afcb0 336 # check storage access, activate storage
db18c1e4 337 my $delayed_mp_param = {};
015740e6 338 PVE::LXC::Config->foreach_volume($mp_param, sub {
bb6afcb0
DM
339 my ($ms, $mountpoint) = @_;
340
341 my $volid = $mountpoint->{volume};
342 my $mp = $mountpoint->{mp};
343
e2007ac2
DM
344 if ($mountpoint->{type} ne 'volume') { # bind or device
345 die "Only root can pass arbitrary filesystem paths.\n"
99132ce0 346 if !$is_root;
e2007ac2
DM
347 } else {
348 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);
349 &$check_and_activate_storage($sid);
350 }
bb6afcb0
DM
351 });
352
353 # check/activate default storage
db18c1e4 354 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs});
bb6afcb0 355
1b4cf758 356 PVE::LXC::Config->update_pct_config($vmid, $conf, 0, $no_disk_param);
9c2d4ce9 357
425b62cb
WB
358 $conf->{unprivileged} = 1 if $unprivileged;
359
61783321 360 my $emsg = $restore ? "unable to restore CT $vmid -" : "unable to create CT $vmid -";
bccaa371 361
61783321
TL
362 eval { PVE::LXC::Config->create_and_lock_config($vmid, $force) };
363 die "$emsg $@" if $@;
bccaa371 364
b9623ef8
DC
365 my $remove_lock = 1;
366
61783321
TL
367 my $code = sub {
368 my $old_conf = PVE::LXC::Config->load_config($vmid);
a2d3e5c3 369 my $was_template;
bccaa371 370
27916659 371 eval {
5a7a51d0
WB
372 my $orig_mp_param; # only used if $restore
373 if ($restore) {
61783321 374 die "can't overwrite running container\n" if PVE::LXC::check_running($vmid);
32e56bed 375 if ($archive ne '-') {
a48e5562 376 my $orig_conf;
bc871ffb 377 print "recovering backed-up configuration from '$archive'\n";
12aec1d2 378 ($orig_conf, $orig_mp_param) = PVE::LXC::Create::recover_config($storage_cfg, $archive, $vmid);
6597f369 379
a2d3e5c3 380 $was_template = delete $orig_conf->{template};
6597f369 381
a67908f1 382 # When we're root call 'restore_configuration' with restricted=0,
5a7a51d0
WB
383 # causing it to restore the raw lxc entries, among which there may be
384 # 'lxc.idmap' entries. We need to make sure that the extracted contents
385 # of the container match up with the restored configuration afterwards:
32e56bed 386 $conf->{lxc} = $orig_conf->{lxc} if $is_root;
8ffdc116
OB
387
388 $conf->{unprivileged} = $orig_conf->{unprivileged}
389 if !defined($unprivileged) && defined($orig_conf->{unprivileged});
b9623ef8
DC
390
391 # implicit privileged change is checked here
392 if ($old_conf->{unprivileged} && !$conf->{unprivileged}) {
393 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Allocate']);
394 }
5a7a51d0 395 }
f360d7f1 396 }
db18c1e4 397 if ($storage_only_mode) {
b51a98d4 398 if ($restore) {
a48e5562 399 if (!defined($orig_mp_param)) {
bc871ffb 400 print "recovering backed-up configuration from '$archive'\n";
12aec1d2 401 (undef, $orig_mp_param) = PVE::LXC::Create::recover_config($storage_cfg, $archive, $vmid);
a5246958 402 }
f360d7f1 403 $mp_param = $orig_mp_param;
db18c1e4
FG
404 die "rootfs configuration could not be recovered, please check and specify manually!\n"
405 if !defined($mp_param->{rootfs});
015740e6 406 PVE::LXC::Config->foreach_volume($mp_param, sub {
db18c1e4
FG
407 my ($ms, $mountpoint) = @_;
408 my $type = $mountpoint->{type};
409 if ($type eq 'volume') {
410 die "unable to detect disk size - please specify $ms (size)\n"
411 if !defined($mountpoint->{size});
412 my $disksize = $mountpoint->{size} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
413 delete $mountpoint->{size};
414 $mountpoint->{volume} = "$storage:$disksize";
415 $mp_param->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
416 } else {
15e4d443 417 my $type = $mountpoint->{type};
7e0e7f38
FG
418 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
419 if ($ms eq 'rootfs');
20f9339d 420 die "restoring '$ms' to $type mount is only possible for root\n"
99132ce0 421 if !$is_root;
7e0e7f38 422
15e4d443
FG
423 if ($mountpoint->{backup}) {
424 warn "WARNING - unsupported configuration!\n";
235dbdf3
FG
425 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
426 warn "mount point configuration will be restored after archive extraction!\n";
15e4d443
FG
427 warn "contained files will be restored to wrong directory!\n";
428 }
136040f4 429 delete $mp_param->{$ms}; # actually delay bind/dev mps
db18c1e4
FG
430 $delayed_mp_param->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
431 }
432 });
b51a98d4 433 } else {
db18c1e4 434 $mp_param->{rootfs} = "$storage:4"; # defaults to 4GB
b51a98d4
DM
435 }
436 }
b9623ef8
DC
437 };
438 die "$emsg $@" if $@;
439
440 # up until here we did not modify the container, besides the lock
441 $remove_lock = 0;
b51a98d4 442
b9623ef8
DC
443 my $vollist = [];
444 eval {
db18c1e4
FG
445 $vollist = PVE::LXC::create_disks($storage_cfg, $vmid, $mp_param, $conf);
446
61783321
TL
447 # we always have the 'create' lock so check for more than 1 entry
448 if (scalar(keys %$old_conf) > 1) {
51665c2d 449 # destroy old container volumes
61783321 450 PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $old_conf, { lock => 'create' });
51665c2d 451 }
51665c2d
FG
452
453 eval {
454 my $rootdir = PVE::LXC::mount_all($vmid, $storage_cfg, $conf, 1);
f9b4407e 455 $bwlimit = PVE::Storage::get_bandwidth_limit('restore', [keys %used_storages], $bwlimit);
bc871ffb
FG
456 print "restoring '$archive' now..\n"
457 if $restore && $archive ne '-';
99a0bcb0 458 PVE::LXC::Create::restore_archive($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
51665c2d
FG
459
460 if ($restore) {
bc871ffb 461 print "merging backed-up and given configuration..\n";
99a0bcb0 462 PVE::LXC::Create::restore_configuration($vmid, $storage_cfg, $archive, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
4b4bbe55
OB
463 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
464 $lxc_setup->template_fixup($conf);
51665c2d
FG
465 } else {
466 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir); # detect OS
467 PVE::LXC::Config->write_config($vmid, $conf); # safe config (after OS detection)
468 $lxc_setup->post_create_hook($password, $ssh_keys);
469 }
470 };
471 my $err = $@;
472 PVE::LXC::umount_all($vmid, $storage_cfg, $conf, $err ? 1 : 0);
473 PVE::Storage::deactivate_volumes($storage_cfg, PVE::LXC::Config->get_vm_volumes($conf));
474 die $err if $err;
27916659
DM
475 # set some defaults
476 $conf->{hostname} ||= "CT$vmid";
477 $conf->{memory} ||= 512;
478 $conf->{swap} //= 512;
db18c1e4
FG
479 foreach my $mp (keys %$delayed_mp_param) {
480 $conf->{$mp} = $delayed_mp_param->{$mp};
481 }
a2d3e5c3
CE
482 # If the template flag was set, we try to convert again to template after restore
483 if ($was_template) {
484 print STDERR "Convert restored container to template...\n";
4c64d0db
FE
485 PVE::LXC::template_create($vmid, $conf);
486 $conf->{template} = 1;
a2d3e5c3 487 }
67afe46e 488 PVE::LXC::Config->write_config($vmid, $conf);
27916659
DM
489 };
490 if (my $err = $@) {
6c871c36 491 PVE::LXC::destroy_disks($storage_cfg, $vollist);
4eeb9af1 492 eval { PVE::LXC::Config->destroy_config($vmid) };
d7d48be6 493 warn $@ if $@;
61783321 494 die "$emsg $err";
6d098bf4 495 }
87273b2b 496 PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
9d0225fb
TL
497
498 PVE::API2::LXC::Status->vm_start({ vmid => $vmid, node => $node })
499 if $start_after_create;
9c2d4ce9 500 };
5c752bbf 501
8a6fc617 502 my $workername = $restore ? 'vzrestore' : 'vzcreate';
b9623ef8
DC
503 my $realcmd = sub {
504 eval {
505 PVE::LXC::Config->lock_config($vmid, $code);
506 };
507 if (my $err = $@) {
508 # if we aborted before changing the container, we must remove the create lock
509 if ($remove_lock) {
510 PVE::LXC::Config->remove_lock($vmid, 'create');
511 }
512 die $err;
513 }
514 };
9c2d4ce9 515
8a6fc617 516 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
9c2d4ce9
DM
517 }});
518
f76a2828
DM
519__PACKAGE__->register_method({
520 name => 'vmdiridx',
5c752bbf 521 path => '{vmid}',
f76a2828
DM
522 method => 'GET',
523 proxyto => 'node',
524 description => "Directory index",
525 permissions => {
526 user => 'all',
527 },
528 parameters => {
529 additionalProperties => 0,
530 properties => {
531 node => get_standard_option('pve-node'),
532 vmid => get_standard_option('pve-vmid'),
533 },
534 },
535 returns => {
536 type => 'array',
537 items => {
538 type => "object",
539 properties => {
540 subdir => { type => 'string' },
541 },
542 },
543 links => [ { rel => 'child', href => "{subdir}" } ],
544 },
545 code => sub {
546 my ($param) = @_;
547
548 # test if VM exists
67afe46e 549 my $conf = PVE::LXC::Config->load_config($param->{vmid});
f76a2828
DM
550
551 my $res = [
552 { subdir => 'config' },
37c2dba8 553 { subdir => 'pending' },
fff3a342
DM
554 { subdir => 'status' },
555 { subdir => 'vncproxy' },
a0226a00 556 { subdir => 'termproxy' },
fff3a342
DM
557 { subdir => 'vncwebsocket' },
558 { subdir => 'spiceproxy' },
559 { subdir => 'migrate' },
c4a33727 560 { subdir => 'clone' },
f76a2828
DM
561# { subdir => 'initlog' },
562 { subdir => 'rrd' },
563 { subdir => 'rrddata' },
564 { subdir => 'firewall' },
cc5392c8 565 { subdir => 'snapshot' },
985b18ed 566 { subdir => 'resize' },
f76a2828 567 ];
5c752bbf 568
f76a2828
DM
569 return $res;
570 }});
571
c4a33727 572
f76a2828 573__PACKAGE__->register_method({
5c752bbf
DM
574 name => 'rrd',
575 path => '{vmid}/rrd',
f76a2828
DM
576 method => 'GET',
577 protected => 1, # fixme: can we avoid that?
578 permissions => {
579 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
580 },
581 description => "Read VM RRD statistics (returns PNG)",
582 parameters => {
583 additionalProperties => 0,
584 properties => {
585 node => get_standard_option('pve-node'),
586 vmid => get_standard_option('pve-vmid'),
587 timeframe => {
588 description => "Specify the time frame you are interested in.",
589 type => 'string',
590 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
591 },
592 ds => {
593 description => "The list of datasources you want to display.",
594 type => 'string', format => 'pve-configid-list',
595 },
596 cf => {
597 description => "The RRD consolidation function",
598 type => 'string',
599 enum => [ 'AVERAGE', 'MAX' ],
600 optional => 1,
601 },
602 },
603 },
604 returns => {
605 type => "object",
606 properties => {
607 filename => { type => 'string' },
608 },
609 },
610 code => sub {
611 my ($param) = @_;
612
d949527f 613 return PVE::RRD::create_rrd_graph(
5c752bbf 614 "pve2-vm/$param->{vmid}", $param->{timeframe},
f76a2828 615 $param->{ds}, $param->{cf});
5c752bbf 616
f76a2828
DM
617 }});
618
619__PACKAGE__->register_method({
5c752bbf
DM
620 name => 'rrddata',
621 path => '{vmid}/rrddata',
f76a2828
DM
622 method => 'GET',
623 protected => 1, # fixme: can we avoid that?
624 permissions => {
625 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
626 },
627 description => "Read VM RRD statistics",
628 parameters => {
629 additionalProperties => 0,
630 properties => {
631 node => get_standard_option('pve-node'),
632 vmid => get_standard_option('pve-vmid'),
633 timeframe => {
634 description => "Specify the time frame you are interested in.",
635 type => 'string',
636 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
637 },
638 cf => {
639 description => "The RRD consolidation function",
640 type => 'string',
641 enum => [ 'AVERAGE', 'MAX' ],
642 optional => 1,
643 },
644 },
645 },
646 returns => {
647 type => "array",
648 items => {
649 type => "object",
650 properties => {},
651 },
652 },
653 code => sub {
654 my ($param) = @_;
655
d949527f 656 return PVE::RRD::create_rrd_data(
f76a2828
DM
657 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
658 }});
659
f76a2828 660__PACKAGE__->register_method({
5c752bbf
DM
661 name => 'destroy_vm',
662 path => '{vmid}',
f76a2828
DM
663 method => 'DELETE',
664 protected => 1,
665 proxyto => 'node',
666 description => "Destroy the container (also delete all uses files).",
667 permissions => {
668 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
669 },
670 parameters => {
671 additionalProperties => 0,
672 properties => {
673 node => get_standard_option('pve-node'),
68e8f3c5 674 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid_stopped }),
d51b84ff
TL
675 force => {
676 type => 'boolean',
677 description => "Force destroy, even if running.",
678 default => 0,
679 optional => 1,
680 },
c5a09704
CE
681 purge => {
682 type => 'boolean',
796e8eee
TL
683 description => "Remove container from all related configurations."
684 ." For example, backup jobs, replication jobs or HA."
685 ." Related ACLs and Firewall entries will *always* be removed.",
686 default => 0,
c5a09704
CE
687 optional => 1,
688 },
3b1c0970
TL
689 'destroy-unreferenced-disks' => {
690 type => 'boolean',
691 description => "If set, destroy additionally all disks with the VMID from all"
692 ." enabled storages which are not referenced in the config.",
693 optional => 1,
694 },
f76a2828
DM
695 },
696 },
5c752bbf 697 returns => {
f76a2828
DM
698 type => 'string',
699 },
700 code => sub {
701 my ($param) = @_;
702
703 my $rpcenv = PVE::RPCEnvironment::get();
f76a2828 704 my $authuser = $rpcenv->get_user();
f76a2828
DM
705 my $vmid = $param->{vmid};
706
707 # test if container exists
0512c360 708
67afe46e 709 my $conf = PVE::LXC::Config->load_config($vmid);
0512c360
FG
710 my $early_checks = sub {
711 my ($conf) = @_;
712 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid");
713 PVE::LXC::Config->check_lock($conf);
7e806596 714
0512c360 715 my $ha_managed = PVE::HA::Config::service_is_configured("ct:$vmid");
b6e0b774 716
0512c360
FG
717 if (!$param->{purge}) {
718 die "unable to remove CT $vmid - used in HA resources and purge parameter not set.\n"
719 if $ha_managed;
3207b81a 720
0512c360
FG
721 # do not allow destroy if there are replication jobs without purge
722 my $repl_conf = PVE::ReplicationConfig->new();
723 $repl_conf->check_for_existing_jobs($vmid);
724 }
725
726 return $ha_managed;
727 };
728
729 $early_checks->($conf);
c5a8e04f 730
d607c17d 731 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
d51b84ff 732 die $running_error_msg if !$param->{force} && PVE::LXC::check_running($vmid); # check early
d607c17d 733
611fe3aa 734 my $code = sub {
673cf209 735 # reload config after lock
67afe46e 736 $conf = PVE::LXC::Config->load_config($vmid);
0512c360 737 my $ha_managed = $early_checks->($conf);
673cf209 738
d51b84ff
TL
739 if (PVE::LXC::check_running($vmid)) {
740 die $running_error_msg if !$param->{force};
741 warn "forced to stop CT $vmid before destroying!\n";
742 if (!$ha_managed) {
743 PVE::LXC::vm_stop($vmid, 1);
744 } else {
745 run_command(['ha-manager', 'crm-command', 'stop', "ct:$vmid", '120']);
746 }
747 }
d607c17d 748
0512c360 749 my $storage_cfg = cfs_read_file("storage.cfg");
3b1c0970
TL
750 PVE::LXC::destroy_lxc_container(
751 $storage_cfg,
752 $vmid,
753 $conf,
754 { lock => 'destroyed' },
755 $param->{'destroy-unreferenced-disks'},
756 );
7fc1d9eb 757
be5fc936 758 PVE::AccessControl::remove_vm_access($vmid);
2f9b5ead 759 PVE::Firewall::remove_vmfw_conf($vmid);
c5a09704 760 if ($param->{purge}) {
3207b81a 761 print "purging CT $vmid from related configurations..\n";
c5a09704
CE
762 PVE::ReplicationConfig::remove_vmid_jobs($vmid);
763 PVE::VZDump::Plugin::remove_vmid_from_backup_jobs($vmid);
3207b81a
TL
764
765 if ($ha_managed) {
766 PVE::HA::Config::delete_service_from_config("ct:$vmid");
767 print "NOTE: removed CT $vmid from HA resource configuration.\n";
768 }
c5a09704 769 }
7fc1d9eb
TL
770
771 # only now remove the zombie config, else we can have reuse race
772 PVE::LXC::Config->destroy_config($vmid);
f76a2828
DM
773 };
774
67afe46e 775 my $realcmd = sub { PVE::LXC::Config->lock_config($vmid, $code); };
351bc0c4 776
f76a2828
DM
777 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
778 }});
779
fff3a342
DM
780my $sslcert;
781
782__PACKAGE__->register_method ({
5b4657d0
DM
783 name => 'vncproxy',
784 path => '{vmid}/vncproxy',
fff3a342
DM
785 method => 'POST',
786 protected => 1,
787 permissions => {
788 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
789 },
790 description => "Creates a TCP VNC proxy connections.",
791 parameters => {
792 additionalProperties => 0,
793 properties => {
794 node => get_standard_option('pve-node'),
795 vmid => get_standard_option('pve-vmid'),
796 websocket => {
797 optional => 1,
798 type => 'boolean',
799 description => "use websocket instead of standard VNC.",
800 },
bd0f4d6d
DC
801 width => {
802 optional => 1,
803 description => "sets the width of the console in pixels.",
804 type => 'integer',
805 minimum => 16,
806 maximum => 4096,
807 },
808 height => {
809 optional => 1,
810 description => "sets the height of the console in pixels.",
811 type => 'integer',
812 minimum => 16,
813 maximum => 2160,
814 },
fff3a342
DM
815 },
816 },
5b4657d0 817 returns => {
fff3a342
DM
818 additionalProperties => 0,
819 properties => {
820 user => { type => 'string' },
821 ticket => { type => 'string' },
822 cert => { type => 'string' },
823 port => { type => 'integer' },
824 upid => { type => 'string' },
825 },
826 },
827 code => sub {
828 my ($param) = @_;
829
830 my $rpcenv = PVE::RPCEnvironment::get();
831
832 my $authuser = $rpcenv->get_user();
833
834 my $vmid = $param->{vmid};
835 my $node = $param->{node};
836
837 my $authpath = "/vms/$vmid";
838
839 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
840
841 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
842 if !$sslcert;
843
ec2c57eb 844 my ($remip, $family);
5b4657d0 845
fff3a342 846 if ($node ne PVE::INotify::nodename()) {
85ae6211 847 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
ec2c57eb
WB
848 } else {
849 $family = PVE::Tools::get_host_address_family($node);
fff3a342
DM
850 }
851
ec2c57eb
WB
852 my $port = PVE::Tools::next_vnc_port($family);
853
fff3a342
DM
854 # NOTE: vncterm VNC traffic is already TLS encrypted,
855 # so we select the fastest chipher here (or 'none'?)
5b4657d0 856 my $remcmd = $remip ?
806eae70 857 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
fff3a342 858
67afe46e 859 my $conf = PVE::LXC::Config->load_config($vmid, $node);
65213b67 860 my $concmd = PVE::LXC::get_console_command($vmid, $conf, -1);
aca816ad 861
5b4657d0
DM
862 my $shcmd = [ '/usr/bin/dtach', '-A',
863 "/var/run/dtach/vzctlconsole$vmid",
aca816ad 864 '-r', 'winch', '-z', @$concmd];
fff3a342
DM
865
866 my $realcmd = sub {
867 my $upid = shift;
868
5b4657d0 869 syslog ('info', "starting lxc vnc proxy $upid\n");
fff3a342 870
5b4657d0 871 my $timeout = 10;
fff3a342
DM
872
873 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
5b4657d0 874 '-timeout', $timeout, '-authpath', $authpath,
fff3a342
DM
875 '-perm', 'VM.Console'];
876
bd0f4d6d
DC
877 if ($param->{width}) {
878 push @$cmd, '-width', $param->{width};
879 }
880
881 if ($param->{height}) {
882 push @$cmd, '-height', $param->{height};
883 }
884
fff3a342 885 if ($param->{websocket}) {
5b4657d0 886 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
fff3a342
DM
887 push @$cmd, '-notls', '-listen', 'localhost';
888 }
889
890 push @$cmd, '-c', @$remcmd, @$shcmd;
891
37634d3d 892 run_command($cmd, keeplocale => 1);
fff3a342
DM
893
894 return;
895 };
896
897 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
898
899 PVE::Tools::wait_for_vnc_port($port);
900
901 return {
902 user => $authuser,
903 ticket => $ticket,
5b4657d0
DM
904 port => $port,
905 upid => $upid,
906 cert => $sslcert,
fff3a342
DM
907 };
908 }});
909
a0226a00
DC
910__PACKAGE__->register_method ({
911 name => 'termproxy',
912 path => '{vmid}/termproxy',
913 method => 'POST',
914 protected => 1,
915 permissions => {
916 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
917 },
918 description => "Creates a TCP proxy connection.",
919 parameters => {
920 additionalProperties => 0,
921 properties => {
922 node => get_standard_option('pve-node'),
923 vmid => get_standard_option('pve-vmid'),
924 },
925 },
926 returns => {
927 additionalProperties => 0,
928 properties => {
929 user => { type => 'string' },
930 ticket => { type => 'string' },
931 port => { type => 'integer' },
932 upid => { type => 'string' },
933 },
934 },
935 code => sub {
936 my ($param) = @_;
937
938 my $rpcenv = PVE::RPCEnvironment::get();
939
940 my $authuser = $rpcenv->get_user();
941
942 my $vmid = $param->{vmid};
943 my $node = $param->{node};
944
945 my $authpath = "/vms/$vmid";
946
947 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
948
949 my ($remip, $family);
950
951 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
952 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
953 } else {
954 $family = PVE::Tools::get_host_address_family($node);
955 }
956
957 my $port = PVE::Tools::next_vnc_port($family);
958
959 my $remcmd = $remip ?
960 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
961
962 my $conf = PVE::LXC::Config->load_config($vmid, $node);
65213b67 963 my $concmd = PVE::LXC::get_console_command($vmid, $conf, -1);
a0226a00
DC
964
965 my $shcmd = [ '/usr/bin/dtach', '-A',
966 "/var/run/dtach/vzctlconsole$vmid",
967 '-r', 'winch', '-z', @$concmd];
968
969 my $realcmd = sub {
970 my $upid = shift;
971
972 syslog ('info', "starting lxc termproxy $upid\n");
973
974 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
975 '--perm', 'VM.Console', '--'];
976 push @$cmd, @$remcmd, @$shcmd;
977
978 PVE::Tools::run_command($cmd);
979 };
980
981 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
982
983 PVE::Tools::wait_for_vnc_port($port);
984
985 return {
986 user => $authuser,
987 ticket => $ticket,
988 port => $port,
989 upid => $upid,
990 };
991 }});
992
fff3a342
DM
993__PACKAGE__->register_method({
994 name => 'vncwebsocket',
995 path => '{vmid}/vncwebsocket',
996 method => 'GET',
5b4657d0 997 permissions => {
fff3a342
DM
998 description => "You also need to pass a valid ticket (vncticket).",
999 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1000 },
1001 description => "Opens a weksocket for VNC traffic.",
1002 parameters => {
1003 additionalProperties => 0,
1004 properties => {
1005 node => get_standard_option('pve-node'),
1006 vmid => get_standard_option('pve-vmid'),
1007 vncticket => {
1008 description => "Ticket from previous call to vncproxy.",
1009 type => 'string',
1010 maxLength => 512,
1011 },
1012 port => {
1013 description => "Port number returned by previous vncproxy call.",
1014 type => 'integer',
1015 minimum => 5900,
1016 maximum => 5999,
1017 },
1018 },
1019 },
1020 returns => {
1021 type => "object",
1022 properties => {
1023 port => { type => 'string' },
1024 },
1025 },
1026 code => sub {
1027 my ($param) = @_;
1028
1029 my $rpcenv = PVE::RPCEnvironment::get();
1030
1031 my $authuser = $rpcenv->get_user();
1032
1033 my $authpath = "/vms/$param->{vmid}";
1034
1035 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
1036
1037 my $port = $param->{port};
5b4657d0 1038
fff3a342
DM
1039 return { port => $port };
1040 }});
1041
1042__PACKAGE__->register_method ({
5b4657d0
DM
1043 name => 'spiceproxy',
1044 path => '{vmid}/spiceproxy',
fff3a342
DM
1045 method => 'POST',
1046 protected => 1,
1047 proxyto => 'node',
1048 permissions => {
1049 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1050 },
1051 description => "Returns a SPICE configuration to connect to the CT.",
1052 parameters => {
1053 additionalProperties => 0,
1054 properties => {
1055 node => get_standard_option('pve-node'),
1056 vmid => get_standard_option('pve-vmid'),
1057 proxy => get_standard_option('spice-proxy', { optional => 1 }),
1058 },
1059 },
1060 returns => get_standard_option('remote-viewer-config'),
1061 code => sub {
1062 my ($param) = @_;
1063
1064 my $vmid = $param->{vmid};
1065 my $node = $param->{node};
1066 my $proxy = $param->{proxy};
1067
1068 my $authpath = "/vms/$vmid";
1069 my $permissions = 'VM.Console';
1070
67afe46e 1071 my $conf = PVE::LXC::Config->load_config($vmid);
da4db334
TL
1072
1073 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
1074
aca816ad
DM
1075 my $concmd = PVE::LXC::get_console_command($vmid, $conf);
1076
5b4657d0
DM
1077 my $shcmd = ['/usr/bin/dtach', '-A',
1078 "/var/run/dtach/vzctlconsole$vmid",
aca816ad 1079 '-r', 'winch', '-z', @$concmd];
fff3a342
DM
1080
1081 my $title = "CT $vmid";
1082
1083 return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1084 }});
5c752bbf 1085
5c752bbf
DM
1086
1087__PACKAGE__->register_method({
52389a07
DM
1088 name => 'migrate_vm',
1089 path => '{vmid}/migrate',
5c752bbf
DM
1090 method => 'POST',
1091 protected => 1,
1092 proxyto => 'node',
52389a07 1093 description => "Migrate the container to another node. Creates a new migration task.",
5c752bbf 1094 permissions => {
52389a07 1095 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
5c752bbf
DM
1096 },
1097 parameters => {
1098 additionalProperties => 0,
1099 properties => {
1100 node => get_standard_option('pve-node'),
68e8f3c5
DM
1101 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
1102 target => get_standard_option('pve-node', {
1103 description => "Target node.",
39bb88df 1104 completion => \&PVE::Cluster::complete_migration_target,
68e8f3c5 1105 }),
e90ddc4c 1106 'target-storage' => get_standard_option('pve-targetstorage'),
52389a07
DM
1107 online => {
1108 type => 'boolean',
1109 description => "Use online/live migration.",
1110 optional => 1,
1111 },
00d8cdc0
DC
1112 restart => {
1113 type => 'boolean',
1114 description => "Use restart migration",
1115 optional => 1,
1116 },
1117 timeout => {
1118 type => 'integer',
1119 description => "Timeout in seconds for shutdown for restart migration",
1120 optional => 1,
1121 default => 180,
1122 },
8ead60ef
SI
1123 bwlimit => {
1124 description => "Override I/O bandwidth limit (in KiB/s).",
1125 optional => 1,
1126 type => 'number',
1127 minimum => '0',
41e9b65c 1128 default => 'migrate limit from datacenter or storage config',
8ead60ef 1129 },
5c752bbf
DM
1130 },
1131 },
1132 returns => {
1133 type => 'string',
52389a07 1134 description => "the task ID.",
5c752bbf
DM
1135 },
1136 code => sub {
1137 my ($param) = @_;
1138
1139 my $rpcenv = PVE::RPCEnvironment::get();
1140
1141 my $authuser = $rpcenv->get_user();
1142
52389a07 1143 my $target = extract_param($param, 'target');
bb1ac2de 1144
52389a07
DM
1145 my $localnode = PVE::INotify::nodename();
1146 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
bb1ac2de 1147
52389a07 1148 PVE::Cluster::check_cfs_quorum();
5c752bbf 1149
52389a07 1150 PVE::Cluster::check_node_exists($target);
27916659 1151
52389a07 1152 my $targetip = PVE::Cluster::remote_node_ip($target);
5c752bbf 1153
52389a07 1154 my $vmid = extract_param($param, 'vmid');
5c752bbf 1155
52389a07 1156 # test if VM exists
67afe46e 1157 PVE::LXC::Config->load_config($vmid);
5c752bbf 1158
52389a07
DM
1159 # try to detect errors early
1160 if (PVE::LXC::check_running($vmid)) {
00d8cdc0
DC
1161 die "can't migrate running container without --online or --restart\n"
1162 if !$param->{online} && !$param->{restart};
5c752bbf 1163 }
5c752bbf 1164
e90ddc4c
FG
1165 if (my $targetstorage = delete $param->{'target-storage'}) {
1166 my $storecfg = PVE::Storage::config();
1167 my $storagemap = eval { PVE::JSONSchema::parse_idmap($targetstorage, 'pve-storage-id') };
1168 raise_param_exc({ targetstorage => "failed to parse storage map: $@" })
1169 if $@;
1170
1171 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
1172 if !defined($storagemap->{identity});
1173
1174 foreach my $target_sid (values %{$storagemap->{entries}}) {
1175 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
1176 }
1177
1178 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
1179 if $storagemap->{default};
1180
1181 $param->{storagemap} = $storagemap;
1182 }
1183
5c752bbf
DM
1184 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1185
1186 my $hacmd = sub {
1187 my $upid = shift;
1188
1189 my $service = "ct:$vmid";
1190
52389a07 1191 my $cmd = ['ha-manager', 'migrate', $service, $target];
5c752bbf 1192
545dd4e1 1193 print "Requesting HA migration for CT $vmid to node $target\n";
5c752bbf
DM
1194
1195 PVE::Tools::run_command($cmd);
1196
1197 return;
1198 };
1199
52389a07 1200 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
5c752bbf
DM
1201
1202 } else {
1203
22557519
DM
1204 my $realcmd = sub {
1205 PVE::LXC::Migrate->migrate($target, $targetip, $vmid, $param);
1206 };
56462053 1207
22557519
DM
1208 my $worker = sub {
1209 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
5c752bbf
DM
1210 };
1211
22557519 1212 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
5c752bbf
DM
1213 }
1214 }});
1215
cc5392c8
WL
1216__PACKAGE__->register_method({
1217 name => 'vm_feature',
1218 path => '{vmid}/feature',
1219 method => 'GET',
1220 proxyto => 'node',
1221 protected => 1,
1222 description => "Check if feature for virtual machine is available.",
1223 permissions => {
1224 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1225 },
1226 parameters => {
1227 additionalProperties => 0,
1228 properties => {
1229 node => get_standard_option('pve-node'),
1230 vmid => get_standard_option('pve-vmid'),
1231 feature => {
1232 description => "Feature to check.",
1233 type => 'string',
d53e5c58 1234 enum => [ 'snapshot', 'clone', 'copy' ],
cc5392c8 1235 },
5ec3949c 1236 snapname => get_standard_option('pve-snapshot-name', {
cc5392c8
WL
1237 optional => 1,
1238 }),
1239 },
1240 },
1241 returns => {
1242 type => "object",
1243 properties => {
1244 hasFeature => { type => 'boolean' },
1245 #nodes => {
1246 #type => 'array',
1247 #items => { type => 'string' },
1248 #}
1249 },
1250 },
1251 code => sub {
1252 my ($param) = @_;
1253
1254 my $node = extract_param($param, 'node');
1255
1256 my $vmid = extract_param($param, 'vmid');
1257
1258 my $snapname = extract_param($param, 'snapname');
1259
1260 my $feature = extract_param($param, 'feature');
1261
67afe46e 1262 my $conf = PVE::LXC::Config->load_config($vmid);
cc5392c8
WL
1263
1264 if($snapname){
1265 my $snap = $conf->{snapshots}->{$snapname};
1266 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1267 $conf = $snap;
1268 }
ef241384 1269 my $storage_cfg = PVE::Storage::config();
cc5392c8 1270 #Maybe include later
ef241384 1271 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
4518000b 1272 my $hasFeature = PVE::LXC::Config->has_feature($feature, $conf, $storage_cfg, $snapname);
cc5392c8
WL
1273
1274 return {
1275 hasFeature => $hasFeature,
1276 #nodes => [ keys %$nodelist ],
1277 };
1278 }});
bb1ac2de
DM
1279
1280__PACKAGE__->register_method({
1281 name => 'template',
1282 path => '{vmid}/template',
1283 method => 'POST',
1284 protected => 1,
1285 proxyto => 'node',
1286 description => "Create a Template.",
1287 permissions => {
1288 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
1289 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1290 },
1291 parameters => {
1292 additionalProperties => 0,
1293 properties => {
1294 node => get_standard_option('pve-node'),
68e8f3c5 1295 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid_stopped }),
bb1ac2de
DM
1296 },
1297 },
1298 returns => { type => 'null'},
1299 code => sub {
1300 my ($param) = @_;
1301
1302 my $rpcenv = PVE::RPCEnvironment::get();
1303
1304 my $authuser = $rpcenv->get_user();
1305
1306 my $node = extract_param($param, 'node');
1307
1308 my $vmid = extract_param($param, 'vmid');
1309
1310 my $updatefn = sub {
1311
67afe46e
FG
1312 my $conf = PVE::LXC::Config->load_config($vmid);
1313 PVE::LXC::Config->check_lock($conf);
bb1ac2de
DM
1314
1315 die "unable to create template, because CT contains snapshots\n"
1316 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
1317
1318 die "you can't convert a template to a template\n"
67afe46e 1319 if PVE::LXC::Config->is_template($conf);
bb1ac2de
DM
1320
1321 die "you can't convert a CT to template if the CT is running\n"
1322 if PVE::LXC::check_running($vmid);
1323
1324 my $realcmd = sub {
1325 PVE::LXC::template_create($vmid, $conf);
bb1ac2de 1326
c2182c49 1327 $conf->{template} = 1;
bb1ac2de 1328
c2182c49
WL
1329 PVE::LXC::Config->write_config($vmid, $conf);
1330 # and remove lxc config
1331 PVE::LXC::update_lxc_config($vmid, $conf);
1332 };
bb1ac2de
DM
1333
1334 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1335 };
1336
67afe46e 1337 PVE::LXC::Config->lock_config($vmid, $updatefn);
bb1ac2de
DM
1338
1339 return undef;
1340 }});
1341
c4a33727
DM
1342__PACKAGE__->register_method({
1343 name => 'clone_vm',
1344 path => '{vmid}/clone',
1345 method => 'POST',
1346 protected => 1,
1347 proxyto => 'node',
1348 description => "Create a container clone/copy",
1349 permissions => {
1350 description => "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1351 "and 'VM.Allocate' permissions " .
1352 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1353 "'Datastore.AllocateSpace' on any used storage.",
1354 check =>
1355 [ 'and',
1356 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1357 [ 'or',
1358 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1359 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
1360 ],
1361 ]
1362 },
1363 parameters => {
1364 additionalProperties => 0,
1365 properties => {
1366 node => get_standard_option('pve-node'),
1367 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
1368 newid => get_standard_option('pve-vmid', {
1369 completion => \&PVE::Cluster::complete_next_vmid,
1370 description => 'VMID for the clone.' }),
1371 hostname => {
1372 optional => 1,
1373 type => 'string', format => 'dns-name',
1374 description => "Set a hostname for the new CT.",
1375 },
1376 description => {
1377 optional => 1,
1378 type => 'string',
1379 description => "Description for the new CT.",
1380 },
1381 pool => {
1382 optional => 1,
1383 type => 'string', format => 'pve-poolid',
1384 description => "Add the new CT to the specified pool.",
1385 },
5ec3949c 1386 snapname => get_standard_option('pve-snapshot-name', {
c4a33727
DM
1387 optional => 1,
1388 }),
1389 storage => get_standard_option('pve-storage-id', {
1390 description => "Target storage for full clone.",
c4a33727
DM
1391 optional => 1,
1392 }),
1393 full => {
1394 optional => 1,
1395 type => 'boolean',
c4b4cb83 1396 description => "Create a full copy of all disks. This is always done when " .
c4a33727 1397 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
c4a33727 1398 },
411ae12c
DM
1399 target => get_standard_option('pve-node', {
1400 description => "Target node. Only allowed if the original VM is on shared storage.",
1401 optional => 1,
1402 }),
8ead60ef
SI
1403 bwlimit => {
1404 description => "Override I/O bandwidth limit (in KiB/s).",
1405 optional => 1,
1406 type => 'number',
1407 minimum => '0',
41e9b65c 1408 default => 'clone limit from datacenter or storage config',
8ead60ef 1409 },
c4a33727
DM
1410 },
1411 },
1412 returns => {
1413 type => 'string',
1414 },
1415 code => sub {
1416 my ($param) = @_;
1417
1418 my $rpcenv = PVE::RPCEnvironment::get();
59b0ce55 1419 my $authuser = $rpcenv->get_user();
c4a33727
DM
1420
1421 my $node = extract_param($param, 'node');
c4a33727 1422 my $vmid = extract_param($param, 'vmid');
c4a33727 1423 my $newid = extract_param($param, 'newid');
c4a33727 1424 my $pool = extract_param($param, 'pool');
c4a33727
DM
1425 if (defined($pool)) {
1426 $rpcenv->check_pool_exist($pool);
1427 }
c4a33727 1428 my $snapname = extract_param($param, 'snapname');
c4a33727 1429 my $storage = extract_param($param, 'storage');
411ae12c 1430 my $target = extract_param($param, 'target');
c4a33727
DM
1431 my $localnode = PVE::INotify::nodename();
1432
59b0ce55 1433 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
411ae12c
DM
1434
1435 PVE::Cluster::check_node_exists($target) if $target;
1436
c4a33727
DM
1437 my $storecfg = PVE::Storage::config();
1438
1439 if ($storage) {
1440 # check if storage is enabled on local node
1441 PVE::Storage::storage_check_enabled($storecfg, $storage);
411ae12c
DM
1442 if ($target) {
1443 # check if storage is available on target node
a1c27f86 1444 PVE::Storage::storage_check_enabled($storecfg, $storage, $target);
411ae12c
DM
1445 # clone only works if target storage is shared
1446 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
1447 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared};
1448 }
c4a33727
DM
1449 }
1450
1451 PVE::Cluster::check_cfs_quorum();
1452
db01d674
WB
1453 my $newconf = {};
1454 my $mountpoints = {};
1455 my $fullclone = {};
1456 my $vollist = [];
411ae12c 1457 my $running;
c4a33727 1458
7b290bf3 1459 PVE::LXC::Config->create_and_lock_config($newid, 0);
5eacc654 1460 PVE::Firewall::clone_vmfw_conf($vmid, $newid);
7b290bf3 1461
b062187a
FG
1462 my $lock_and_reload = sub {
1463 my ($vmid, $code) = @_;
1464 return PVE::LXC::Config->lock_config($vmid, sub {
1465 my $conf = PVE::LXC::Config->load_config($vmid);
1466 die "Lost 'create' config lock, aborting.\n"
1467 if !PVE::LXC::Config->has_lock($conf, 'create');
1468
1469 return $code->($conf);
1470 });
1471 };
e7d553c7 1472
cc1ffe7e 1473 my $src_conf = PVE::LXC::Config->set_lock($vmid, 'disk');
411ae12c 1474
cc1ffe7e 1475 $running = PVE::LXC::check_running($vmid) || 0;
e7d553c7 1476
cc1ffe7e
FG
1477 my $full = extract_param($param, 'full');
1478 if (!defined($full)) {
1479 $full = !PVE::LXC::Config->is_template($src_conf);
1480 }
c4a33727 1481
cc1ffe7e 1482 eval {
5da8f412
FG
1483 die "parameter 'storage' not allowed for linked clones\n"
1484 if defined($storage) && !$full;
1485
cc1ffe7e
FG
1486 die "snapshot '$snapname' does not exist\n"
1487 if $snapname && !defined($src_conf->{snapshots}->{$snapname});
c4a33727 1488
cc1ffe7e 1489 my $src_conf = $snapname ? $src_conf->{snapshots}->{$snapname} : $src_conf;
c4a33727 1490
cc1ffe7e
FG
1491 my $sharedvm = 1;
1492 for my $opt (sort keys %$src_conf) {
1493 next if $opt =~ m/^unused\d+$/;
c4a33727 1494
cc1ffe7e 1495 my $value = $src_conf->{$opt};
c4a33727 1496
cc1ffe7e
FG
1497 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1498 my $mp = PVE::LXC::Config->parse_volume($opt, $value);
09687674 1499
cc1ffe7e
FG
1500 if ($mp->{type} eq 'volume') {
1501 my $volid = $mp->{volume};
09687674 1502
cc1ffe7e
FG
1503 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);
1504 $sid = $storage if defined($storage);
1505 my $scfg = PVE::Storage::storage_config($storecfg, $sid);
1506 if (!$scfg->{shared}) {
1507 $sharedvm = 0;
1508 warn "found non-shared volume: $volid\n" if $target;
1509 }
c4a33727 1510
cc1ffe7e 1511 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
c4a33727 1512
cc1ffe7e
FG
1513 if ($full) {
1514 die "Cannot do full clones on a running container without snapshots\n"
1515 if $running && !defined($snapname);
1516 $fullclone->{$opt} = 1;
c4a33727 1517 } else {
cc1ffe7e
FG
1518 # not full means clone instead of copy
1519 die "Linked clone feature for '$volid' is not available\n"
1520 if !PVE::Storage::volume_has_feature($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
c4a33727 1521 }
cc1ffe7e
FG
1522
1523 $mountpoints->{$opt} = $mp;
1524 push @$vollist, $volid;
1525
c4a33727 1526 } else {
cc1ffe7e
FG
1527 # TODO: allow bind mounts?
1528 die "unable to clone mountpoint '$opt' (type $mp->{type})\n";
c4a33727 1529 }
cc1ffe7e
FG
1530 } elsif ($opt =~ m/^net(\d+)$/) {
1531 # always change MAC! address
1532 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1533 my $net = PVE::LXC::Config->parse_lxc_network($value);
1534 $net->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1535 $newconf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
1536 } else {
1537 # copy everything else
1538 $newconf->{$opt} = $value;
db01d674 1539 }
cc1ffe7e
FG
1540 }
1541 die "can't clone CT to node '$target' (CT uses local storage)\n"
1542 if $target && !$sharedvm;
c4a33727 1543
cc1ffe7e
FG
1544 # Replace the 'disk' lock with a 'create' lock.
1545 $newconf->{lock} = 'create';
db01d674 1546
04a34c56 1547 # delete all snapshot related config options
a0167a5c 1548 delete $newconf->@{qw(snapshots parent snaptime snapstate)};
04a34c56 1549
cc1ffe7e
FG
1550 delete $newconf->{pending};
1551 delete $newconf->{template};
c4a33727 1552
a0167a5c
TL
1553 $newconf->{hostname} = $param->{hostname} if $param->{hostname};
1554 $newconf->{description} = $param->{description} if $param->{description};
c4a33727 1555
b062187a 1556 $lock_and_reload->($newid, sub {
cc1ffe7e
FG
1557 PVE::LXC::Config->write_config($newid, $newconf);
1558 });
1559 };
1560 if (my $err = $@) {
1561 eval { PVE::LXC::Config->remove_lock($vmid, 'disk') };
1562 warn "Failed to remove source CT config lock - $@\n" if $@;
1563
1564 eval {
b062187a 1565 $lock_and_reload->($newid, sub {
cc1ffe7e 1566 PVE::LXC::Config->destroy_config($newid);
5eacc654 1567 PVE::Firewall::remove_vmfw_conf($newid);
7b290bf3 1568 });
db01d674 1569 };
cc1ffe7e 1570 warn "Failed to remove target CT config - $@\n" if $@;
52d287d9 1571
cc1ffe7e
FG
1572 die $err;
1573 }
c4a33727 1574
db01d674
WB
1575 my $update_conf = sub {
1576 my ($key, $value) = @_;
b062187a
FG
1577 return $lock_and_reload->($newid, sub {
1578 my $conf = shift;
db01d674
WB
1579 $conf->{$key} = $value;
1580 PVE::LXC::Config->write_config($newid, $conf);
1581 });
1582 };
c4a33727 1583
db01d674
WB
1584 my $realcmd = sub {
1585 my ($upid) = @_;
c4a33727 1586
db01d674 1587 my $newvollist = [];
c4a33727 1588
411ae12c
DM
1589 my $verify_running = PVE::LXC::check_running($vmid) || 0;
1590 die "unexpected state change\n" if $verify_running != $running;
1591
db01d674
WB
1592 eval {
1593 local $SIG{INT} =
1594 local $SIG{TERM} =
1595 local $SIG{QUIT} =
1596 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
c4a33727 1597
db01d674 1598 PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
8ead60ef 1599 my $bwlimit = extract_param($param, 'bwlimit');
c4a33727 1600
db01d674
WB
1601 foreach my $opt (keys %$mountpoints) {
1602 my $mp = $mountpoints->{$opt};
1603 my $volid = $mp->{volume};
c4a33727 1604
db01d674
WB
1605 my $newvolid;
1606 if ($fullclone->{$opt}) {
1607 print "create full clone of mountpoint $opt ($volid)\n";
8ead60ef
SI
1608 my $source_storage = PVE::Storage::parse_volume_id($volid);
1609 my $target_storage = $storage // $source_storage;
1610 my $clonelimit = PVE::Storage::get_bandwidth_limit('clone', [$source_storage, $target_storage], $bwlimit);
1611 $newvolid = PVE::LXC::copy_volume($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
db01d674
WB
1612 } else {
1613 print "create linked clone of mount point $opt ($volid)\n";
1614 $newvolid = PVE::Storage::vdisk_clone($storecfg, $volid, $newid, $snapname);
c4a33727
DM
1615 }
1616
db01d674
WB
1617 push @$newvollist, $newvolid;
1618 $mp->{volume} = $newvolid;
c4a33727 1619
db01d674 1620 $update_conf->($opt, PVE::LXC::Config->print_ct_mountpoint($mp, $opt eq 'rootfs'));
c4a33727
DM
1621 }
1622
db01d674 1623 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
8e08cd17 1624
b062187a
FG
1625 $lock_and_reload->($newid, sub {
1626 my $conf = shift;
1627 my $rootdir = PVE::LXC::mount_all($newid, $storecfg, $conf, 1);
1628 eval {
1629 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir);
1630 $lxc_setup->post_clone_hook($conf);
1631 };
1632 my $err = $@;
1633 eval { PVE::LXC::umount_all($newid, $storecfg, $conf, 1); };
1634 if ($err) {
1635 warn "$@\n" if $@;
1636 die $err;
1637 } else {
1638 die $@ if $@;
7b290bf3
OB
1639 }
1640 });
c4a33727 1641 };
db01d674 1642 my $err = $@;
db01d674
WB
1643 # Unlock the source config in any case:
1644 eval { PVE::LXC::Config->remove_lock($vmid, 'disk') };
1645 warn $@ if $@;
c4a33727 1646
db01d674
WB
1647 if ($err) {
1648 # Now cleanup the config & disks:
db01d674
WB
1649 sleep 1; # some storages like rbd need to wait before release volume - really?
1650
1651 foreach my $volid (@$newvollist) {
1652 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
1653 warn $@ if $@;
1654 }
52d287d9
FG
1655
1656 eval {
b062187a 1657 $lock_and_reload->($newid, sub {
52d287d9 1658 PVE::LXC::Config->destroy_config($newid);
5eacc654 1659 PVE::Firewall::remove_vmfw_conf($newid);
52d287d9
FG
1660 });
1661 };
1662 warn "Failed to remove target CT config - $@\n" if $@;
1663
db01d674
WB
1664 die "clone failed: $err";
1665 }
1666
b062187a
FG
1667 $lock_and_reload->($newid, sub {
1668 PVE::LXC::Config->remove_lock($newid, 'create');
1669
1670 if ($target) {
1671 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1672 PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
1673 PVE::Storage::deactivate_volumes($storecfg, $newvollist);
1674
1675 PVE::LXC::Config->move_config_to_node($newid, $target);
1676 }
1677 });
1678
db01d674 1679 return;
c4a33727
DM
1680 };
1681
db01d674 1682 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
c4a33727
DM
1683 }});
1684
1685
985b18ed
WL
1686__PACKAGE__->register_method({
1687 name => 'resize_vm',
1688 path => '{vmid}/resize',
1689 method => 'PUT',
1690 protected => 1,
1691 proxyto => 'node',
235dbdf3 1692 description => "Resize a container mount point.",
985b18ed
WL
1693 permissions => {
1694 check => ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any => 1],
1695 },
1696 parameters => {
1697 additionalProperties => 0,
b8c5a95f
WB
1698 properties => {
1699 node => get_standard_option('pve-node'),
1700 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
1701 disk => {
1702 type => 'string',
1703 description => "The disk you want to resize.",
5e5d76cf 1704 enum => [PVE::LXC::Config->valid_volume_keys()],
b8c5a95f
WB
1705 },
1706 size => {
1707 type => 'string',
1708 pattern => '\+?\d+(\.\d+)?[KMGT]?',
1709 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.",
1710 },
1711 digest => {
1712 type => 'string',
1713 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1714 maxLength => 40,
1715 optional => 1,
1716 }
1717 },
985b18ed 1718 },
9f3f7963
WL
1719 returns => {
1720 type => 'string',
1721 description => "the task ID.",
1722 },
985b18ed
WL
1723 code => sub {
1724 my ($param) = @_;
1725
1726 my $rpcenv = PVE::RPCEnvironment::get();
1727
1728 my $authuser = $rpcenv->get_user();
1729
1730 my $node = extract_param($param, 'node');
1731
1732 my $vmid = extract_param($param, 'vmid');
1733
1734 my $digest = extract_param($param, 'digest');
1735
1736 my $sizestr = extract_param($param, 'size');
1737 my $ext = ($sizestr =~ s/^\+//);
1738 my $newsize = PVE::JSONSchema::parse_size($sizestr);
1739 die "invalid size string" if !defined($newsize);
1740
1741 die "no options specified\n" if !scalar(keys %$param);
1742
985b18ed
WL
1743 my $storage_cfg = cfs_read_file("storage.cfg");
1744
985b18ed
WL
1745 my $code = sub {
1746
67afe46e
FG
1747 my $conf = PVE::LXC::Config->load_config($vmid);
1748 PVE::LXC::Config->check_lock($conf);
985b18ed 1749
de41bced
DC
1750 PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, $conf, $param, [], $conf->{unprivileged});
1751
985b18ed
WL
1752 PVE::Tools::assert_if_modified($digest, $conf->{digest});
1753
1754 my $running = PVE::LXC::check_running($vmid);
1755
1756 my $disk = $param->{disk};
e4034859 1757 my $mp = PVE::LXC::Config->parse_volume($disk, $conf->{$disk});
44a9face 1758
985b18ed
WL
1759 my $volid = $mp->{volume};
1760
1761 my (undef, undef, $owner, undef, undef, undef, $format) =
1762 PVE::Storage::parse_volname($storage_cfg, $volid);
1763
235dbdf3 1764 die "can't resize mount point owned by another container ($owner)"
985b18ed
WL
1765 if $vmid != $owner;
1766
1767 die "can't resize volume: $disk if snapshot exists\n"
1768 if %{$conf->{snapshots}} && $format eq 'qcow2';
1769
1770 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
1771
1772 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1773
4e1e1791
WB
1774 PVE::Storage::activate_volumes($storage_cfg, [$volid]);
1775
985b18ed 1776 my $size = PVE::Storage::volume_size_info($storage_cfg, $volid, 5);
2e8bcfef 1777
16f6c7c6 1778 die "Could not determine current size of volume '$volid'\n" if !defined($size);
2e8bcfef 1779
985b18ed
WL
1780 $newsize += $size if $ext;
1781 $newsize = int($newsize);
1782
1783 die "unable to shrink disk size\n" if $newsize < $size;
1784
5c36822a 1785 die "disk is already at specified size\n" if $size == $newsize;
985b18ed
WL
1786
1787 PVE::Cluster::log_msg('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
9f3f7963 1788 my $realcmd = sub {
e72c8b0e
DM
1789 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1790 # we pass 0 here (parameter only makes sense for qemu)
9f3f7963
WL
1791 PVE::Storage::volume_resize($storage_cfg, $volid, $newsize, 0);
1792
1793 $mp->{size} = $newsize;
1b4cf758 1794 $conf->{$disk} = PVE::LXC::Config->print_ct_mountpoint($mp, $disk eq 'rootfs');
9f3f7963 1795
67afe46e 1796 PVE::LXC::Config->write_config($vmid, $conf);
9f3f7963
WL
1797
1798 if ($format eq 'raw') {
46e62d2d
TL
1799 # we need to ensure that the volume is mapped, if not needed this is a NOP
1800 my $path = PVE::Storage::map_volume($storage_cfg, $volid);
1801 $path = PVE::Storage::path($storage_cfg, $volid) if !defined($path);
9f3f7963 1802 if ($running) {
2a993004
WL
1803
1804 $mp->{mp} = '/';
1805 my $use_loopdev = (PVE::LXC::mountpoint_mount_path($mp, $storage_cfg))[1];
21f292ff 1806 $path = PVE::LXC::query_loopdev($path) if $use_loopdev;
235dbdf3 1807 die "internal error: CT running but mount point not attached to a loop device"
2a993004
WL
1808 if !$path;
1809 PVE::Tools::run_command(['losetup', '--set-capacity', $path]) if $use_loopdev;
9f3f7963
WL
1810
1811 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1812 # to be visible to it in its namespace.
1813 # To not interfere with the rest of the system we unshare the current mount namespace,
1814 # mount over /tmp and then run resize2fs.
1815
1816 # interestingly we don't need to e2fsck on mounted systems...
1817 my $quoted = PVE::Tools::shellquote($path);
1818 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
ce9e6458
WB
1819 eval {
1820 PVE::Tools::run_command(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1821 };
1822 warn "Failed to update the container's filesystem: $@\n" if $@;
9f3f7963 1823 } else {
ce9e6458
WB
1824 eval {
1825 PVE::Tools::run_command(['e2fsck', '-f', '-y', $path]);
1826 PVE::Tools::run_command(['resize2fs', $path]);
1827 };
1828 warn "Failed to update the container's filesystem: $@\n" if $@;
80440faa 1829
46ea6368
TL
1830 # always un-map if not running, this is a NOP if not needed
1831 PVE::Storage::unmap_volume($storage_cfg, $volid);
9f3f7963 1832 }
985b18ed 1833 }
9f3f7963 1834 };
985b18ed 1835
9f3f7963
WL
1836 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1837 };
985b18ed 1838
67afe46e 1839 return PVE::LXC::Config->lock_config($vmid, $code);;
985b18ed
WL
1840 }});
1841
3c0f6806
WB
1842__PACKAGE__->register_method({
1843 name => 'move_volume',
1844 path => '{vmid}/move_volume',
1845 method => 'POST',
1846 protected => 1,
1847 proxyto => 'node',
a337a832 1848 description => "Move a rootfs-/mp-volume to a different storage or to a different container.",
3c0f6806
WB
1849 permissions => {
1850 description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
a337a832
AL
1851 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
1852 "a volume to another container, you need the permissions on the ".
1853 "target container as well.",
d568e8db 1854 check => ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
3c0f6806
WB
1855 },
1856 parameters => {
1857 additionalProperties => 0,
1858 properties => {
1859 node => get_standard_option('pve-node'),
1860 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
a337a832
AL
1861 'target-vmid' => get_standard_option('pve-vmid', {
1862 completion => \&PVE::LXC::complete_ctid,
1863 optional => 1,
1864 }),
3c0f6806
WB
1865 volume => {
1866 type => 'string',
a337a832
AL
1867 #TODO: check how to handle unused mount points as the mp parameter is not configured
1868 enum => [ PVE::LXC::Config->valid_volume_keys_with_unused() ],
3c0f6806
WB
1869 description => "Volume which will be moved.",
1870 },
1871 storage => get_standard_option('pve-storage-id', {
1872 description => "Target Storage.",
1873 completion => \&PVE::Storage::complete_storage_enabled,
a337a832 1874 optional => 1,
3c0f6806
WB
1875 }),
1876 delete => {
1877 type => 'boolean',
d2f08c5a
AL
1878 description => "Delete the original volume after successful copy. By default the " .
1879 "original is kept as an unused volume entry.",
3c0f6806
WB
1880 optional => 1,
1881 default => 0,
1882 },
1883 digest => {
1884 type => 'string',
d2f08c5a
AL
1885 description => 'Prevent changes if current configuration file has different SHA1 " .
1886 "digest. This can be used to prevent concurrent modifications.',
3c0f6806
WB
1887 maxLength => 40,
1888 optional => 1,
8ead60ef
SI
1889 },
1890 bwlimit => {
1891 description => "Override I/O bandwidth limit (in KiB/s).",
1892 optional => 1,
1893 type => 'number',
1894 minimum => '0',
41e9b65c 1895 default => 'clone limit from datacenter or storage config',
8ead60ef 1896 },
a337a832
AL
1897 'target-volume' => {
1898 type => 'string',
1899 description => "The config key the volume will be moved to. Default is the " .
1900 "source volume key.",
1901 enum => [PVE::LXC::Config->valid_volume_keys_with_unused()],
1902 optional => 1,
1903 },
1904 'target-digest' => {
1905 type => 'string',
1906 description => 'Prevent changes if current configuration file of the target " .
1907 "container has a different SHA1 digest. This can be used to prevent " .
1908 "concurrent modifications.',
1909 maxLength => 40,
1910 optional => 1,
1911 },
3c0f6806
WB
1912 },
1913 },
1914 returns => {
1915 type => 'string',
1916 },
1917 code => sub {
1918 my ($param) = @_;
1919
1920 my $rpcenv = PVE::RPCEnvironment::get();
1921
1922 my $authuser = $rpcenv->get_user();
1923
1924 my $vmid = extract_param($param, 'vmid');
1925
a337a832
AL
1926 my $target_vmid = extract_param($param, 'target-vmid');
1927
3c0f6806
WB
1928 my $storage = extract_param($param, 'storage');
1929
1930 my $mpkey = extract_param($param, 'volume');
1931
a337a832
AL
1932 my $target_mpkey = extract_param($param, 'target-volume') // $mpkey;
1933
1934 my $digest = extract_param($param, 'digest');
1935
1936 my $target_digest = extract_param($param, 'target-digest');
1937
3c0f6806
WB
1938 my $lockname = 'disk';
1939
1940 my ($mpdata, $old_volid);
1941
a337a832
AL
1942 die "either set storage or target-vmid, but not both\n"
1943 if $storage && $target_vmid;
3c0f6806 1944
a337a832 1945 my $storecfg = PVE::Storage::config();
3c0f6806 1946
a337a832
AL
1947 my $move_to_storage_checks = sub {
1948 PVE::LXC::Config->lock_config($vmid, sub {
1949 my $conf = PVE::LXC::Config->load_config($vmid);
1950 PVE::LXC::Config->check_lock($conf);
3c0f6806 1951
a337a832
AL
1952 die "cannot move volumes of a running container\n"
1953 if PVE::LXC::check_running($vmid);
3c0f6806 1954
a337a832
AL
1955 if ($mpkey =~ m/^unused\d+$/) {
1956 die "cannot move volume '$mpkey', only configured volumes can be moved to ".
1957 "another storage\n";
1958 }
3c0f6806 1959
a337a832
AL
1960 $mpdata = PVE::LXC::Config->parse_volume($mpkey, $conf->{$mpkey});
1961 $old_volid = $mpdata->{volume};
3c0f6806 1962
a337a832
AL
1963 die "you can't move a volume with snapshots and delete the source\n"
1964 if $param->{delete} && PVE::LXC::Config->is_volume_in_use_by_snapshots($conf, $old_volid);
1965
1966 PVE::Tools::assert_if_modified($digest, $conf->{digest});
1967
1968 PVE::LXC::Config->set_lock($vmid, $lockname);
1969 });
1970 };
1971
1972 my $storage_realcmd = sub {
3c0f6806 1973 eval {
d2f08c5a
AL
1974 PVE::Cluster::log_msg(
1975 'info',
1976 $authuser,
1977 "move volume CT $vmid: move --volume $mpkey --storage $storage"
1978 );
3c0f6806
WB
1979
1980 my $conf = PVE::LXC::Config->load_config($vmid);
1981 my $storage_cfg = PVE::Storage::config();
1982
1983 my $new_volid;
1984
1985 eval {
1986 PVE::Storage::activate_volumes($storage_cfg, [ $old_volid ]);
8ead60ef
SI
1987 my $bwlimit = extract_param($param, 'bwlimit');
1988 my $source_storage = PVE::Storage::parse_volume_id($old_volid);
d2f08c5a
AL
1989 my $movelimit = PVE::Storage::get_bandwidth_limit(
1990 'move',
1991 [$source_storage, $storage],
1992 $bwlimit
1993 );
1994 $new_volid = PVE::LXC::copy_volume(
1995 $mpdata,
1996 $vmid,
1997 $storage,
1998 $storage_cfg,
1999 $conf,
2000 undef,
2001 $movelimit
2002 );
3bfbd900
FE
2003 if (PVE::LXC::Config->is_template($conf)) {
2004 PVE::Storage::activate_volumes($storage_cfg, [ $new_volid ]);
2005 my $template_volid = PVE::Storage::vdisk_create_base($storage_cfg, $new_volid);
2006 $mpdata->{volume} = $template_volid;
2007 } else {
2008 $mpdata->{volume} = $new_volid;
2009 }
3c0f6806
WB
2010
2011 PVE::LXC::Config->lock_config($vmid, sub {
2012 my $digest = $conf->{digest};
2013 $conf = PVE::LXC::Config->load_config($vmid);
2014 PVE::Tools::assert_if_modified($digest, $conf->{digest});
2015
d2f08c5a
AL
2016 $conf->{$mpkey} = PVE::LXC::Config->print_ct_mountpoint(
2017 $mpdata,
2018 $mpkey eq 'rootfs'
2019 );
3c0f6806
WB
2020
2021 PVE::LXC::Config->add_unused_volume($conf, $old_volid) if !$param->{delete};
2022
2023 PVE::LXC::Config->write_config($vmid, $conf);
2024 });
2025
2026 eval {
2027 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2028 PVE::Storage::deactivate_volumes($storage_cfg, [ $new_volid ])
2029 };
2030 warn $@ if $@;
2031 };
2032 if (my $err = $@) {
2033 eval {
2034 PVE::Storage::vdisk_free($storage_cfg, $new_volid)
2035 if defined($new_volid);
2036 };
2037 warn $@ if $@;
2038 die $err;
2039 }
2040
2041 if ($param->{delete}) {
2042 eval {
2043 PVE::Storage::deactivate_volumes($storage_cfg, [ $old_volid ]);
2044 PVE::Storage::vdisk_free($storage_cfg, $old_volid);
2045 };
9abb0f8a
FE
2046 if (my $err = $@) {
2047 warn $err;
2048 PVE::LXC::Config->lock_config($vmid, sub {
2049 my $conf = PVE::LXC::Config->load_config($vmid);
2050 PVE::LXC::Config->add_unused_volume($conf, $old_volid);
2051 PVE::LXC::Config->write_config($vmid, $conf);
2052 });
2053 }
3c0f6806
WB
2054 }
2055 };
2056 my $err = $@;
2057 eval { PVE::LXC::Config->remove_lock($vmid, $lockname) };
2058 warn $@ if $@;
2059 die $err if $err;
2060 };
a337a832
AL
2061
2062 my $load_and_check_reassign_configs = sub {
2063 my $vmlist = PVE::Cluster::get_vmlist()->{ids};
2064
2065 die "Cannot move to/from 'rootfs'\n" if $mpkey eq "rootfs" || $target_mpkey eq "rootfs";
2066
2067 if ($mpkey =~ m/^unused\d+$/ && $target_mpkey !~ m/^unused\d+$/) {
2068 die "Moving an unused volume to a used one is not possible\n";
2069 }
2070 die "could not find CT ${vmid}\n" if !exists($vmlist->{$vmid});
27e28f79 2071 die "could not find CT ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
a337a832 2072
27e28f79
FG
2073 my $source_node = $vmlist->{$vmid}->{node};
2074 my $target_node = $vmlist->{$target_vmid}->{node};
2075
2076 die "Both containers need to be on the same node ($source_node != $target_node)\n"
2077 if $source_node ne $target_node;
a337a832
AL
2078
2079 my $source_conf = PVE::LXC::Config->load_config($vmid);
2080 PVE::LXC::Config->check_lock($source_conf);
2081 my $target_conf = PVE::LXC::Config->load_config($target_vmid);
2082 PVE::LXC::Config->check_lock($target_conf);
2083
27e28f79 2084 die "Can't move volumes from or to template CT\n"
a337a832
AL
2085 if ($source_conf->{template} || $target_conf->{template});
2086
2087 if ($digest) {
2088 eval { PVE::Tools::assert_if_modified($digest, $source_conf->{digest}) };
2089 die "Container ${vmid}: $@" if $@;
2090 }
2091
2092 if ($target_digest) {
2093 eval { PVE::Tools::assert_if_modified($target_digest, $target_conf->{digest}) };
2094 die "Container ${target_vmid}: $@" if $@;
2095 }
2096
2097 die "volume '${mpkey}' for container '$vmid' does not exist\n"
2098 if !defined($source_conf->{$mpkey});
2099
2100 die "Target volume key '${target_mpkey}' is already in use for container '$target_vmid'\n"
2101 if exists $target_conf->{$target_mpkey};
2102
2103 my $drive = PVE::LXC::Config->parse_volume(
2104 $mpkey,
2105 $source_conf->{$mpkey},
2106 );
2107
27e28f79 2108 my $source_volid = $drive->{volume};
a337a832
AL
2109
2110 die "Volume '${mpkey}' has no associated image\n"
2111 if !$source_volid;
2112 die "Cannot move volume used by a snapshot to another container\n"
2113 if PVE::LXC::Config->is_volume_in_use_by_snapshots($source_conf, $source_volid);
2114 die "Storage does not support moving of this disk to another container\n"
2115 if !PVE::Storage::volume_has_feature($storecfg, 'rename', $source_volid);
27e28f79 2116 die "Cannot move a bindmount or device mount to another container\n"
a337a832 2117 if $drive->{type} ne "volume";
27e28f79 2118 die "Cannot move volume to another container while the source is running - detach first\n"
a337a832
AL
2119 if PVE::LXC::check_running($vmid) && $mpkey !~ m/^unused\d+$/;
2120
2121 my $repl_conf = PVE::ReplicationConfig->new();
27e28f79
FG
2122 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
2123 my ($storeid, undef) = PVE::Storage::parse_volume_id($source_volid);
2124 my $format = (PVE::Storage::parse_volname($storecfg, $source_volid))[6];
2125
2126 die "Cannot move volume on storage '$storeid' to a replicated container - missing replication support\n"
2127 if !PVE::Storage::storage_can_replicate($storecfg, $storeid, $format);
a337a832 2128 }
27e28f79
FG
2129
2130 return ($source_conf, $target_conf, $drive);
3c0f6806 2131 };
a337a832
AL
2132
2133 my $logfunc = sub {
2134 my ($msg) = @_;
2135 print STDERR "$msg\n";
2136 };
2137
2138 my $volume_reassignfn = sub {
2139 return PVE::LXC::Config->lock_config($vmid, sub {
2140 return PVE::LXC::Config->lock_config($target_vmid, sub {
27e28f79
FG
2141 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
2142 my $source_volid = $drive->{volume};
a337a832
AL
2143
2144 my $target_unused = $target_mpkey =~ m/^unused\d+$/;
2145
a337a832 2146 print "moving volume '$mpkey' from container '$vmid' to '$target_vmid'\n";
27e28f79 2147
a337a832
AL
2148 my ($storage, $source_volname) = PVE::Storage::parse_volume_id($source_volid);
2149
2150 my $fmt = (PVE::Storage::parse_volname($storecfg, $source_volid))[6];
2151
2152 my $new_volid = PVE::Storage::rename_volume(
2153 $storecfg,
2154 $source_volid,
2155 $target_vmid,
2156 );
2157
27e28f79 2158 $drive->{volume} = $new_volid;
a337a832
AL
2159
2160 delete $source_conf->{$mpkey};
2161 print "removing volume '${mpkey}' from container '${vmid}' config\n";
2162 PVE::LXC::Config->write_config($vmid, $source_conf);
2163
2164 my $drive_string;
2165
2166 if ($target_unused) {
2167 $drive_string = $new_volid;
2168 } else {
27e28f79 2169 $drive_string = PVE::LXC::Config->print_volume($target_mpkey, $drive);
a337a832
AL
2170 }
2171
2172 if ($target_unused) {
2173 $target_conf->{$target_mpkey} = $drive_string;
2174 } else {
2175 my $running = PVE::LXC::check_running($target_vmid);
2176 my $param = { $target_mpkey => $drive_string };
2177 my $errors = PVE::LXC::Config->update_pct_config(
2178 $target_vmid,
2179 $target_conf,
2180 $running,
2181 $param
2182 );
2183
2184 foreach my $key (keys %$errors) {
2185 $rpcenv->warn($errors->{$key});
2186 }
2187 }
2188
2189 PVE::LXC::Config->write_config($target_vmid, $target_conf);
2190 $target_conf = PVE::LXC::Config->load_config($target_vmid);
2191
2192 PVE::LXC::update_lxc_config($target_vmid, $target_conf) if !$target_unused;
2193 print "target container '$target_vmid' updated with '$target_mpkey'\n";
2194
2195 # remove possible replication snapshots
2196 if (PVE::Storage::volume_has_feature($storecfg,'replicate', $source_volid)) {
2197 eval {
2198 PVE::Replication::prepare(
2199 $storecfg,
2200 [$new_volid],
2201 undef,
2202 1,
2203 undef,
2204 $logfunc,
2205 )
2206 };
2207 if (my $err = $@) {
2208 $rpcenv->warn("Failed to remove replication snapshots on volume ".
2209 "'${target_mpkey}'. Manual cleanup could be necessary. " .
2210 "Error: ${err}\n");
2211 }
2212 }
2213 });
2214 });
2215 };
2216
27e28f79
FG
2217 if ($target_vmid && $storage) {
2218 my $msg = "either set 'storage' or 'target-vmid', but not both";
2219 raise_param_exc({ 'target-vmid' => $msg, 'storage' => $msg });
2220 } elsif ($target_vmid) {
a337a832
AL
2221 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
2222 if $authuser ne 'root@pam';
2223
27e28f79
FG
2224 if ($vmid eq $target_vmid) {
2225 my $msg = "must be different than source VMID to move disk to another container";
2226 raise_param_exc({ 'target-vmid' => $msg });
2227 }
a337a832 2228
d568e8db
FG
2229 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
2230 my $storeid = PVE::Storage::parse_volume_id($drive->{volume});
2231 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
a337a832
AL
2232 return $rpcenv->fork_worker(
2233 'move_volume',
2234 "${vmid}-${mpkey}>${target_vmid}-${target_mpkey}",
2235 $authuser,
2236 $volume_reassignfn
2237 );
2238 } elsif ($storage) {
d568e8db 2239 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
a337a832
AL
2240 &$move_to_storage_checks();
2241 my $task = eval {
2242 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $storage_realcmd);
2243 };
2244 if (my $err = $@) {
2245 eval { PVE::LXC::Config->remove_lock($vmid, $lockname) };
2246 warn $@ if $@;
2247 die $err;
2248 }
2249 return $task;
2250 } else {
27e28f79
FG
2251 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
2252 raise_param_exc({ 'target-vmid' => $msg, 'storage' => $msg });
3c0f6806 2253 }
3c0f6806
WB
2254 }});
2255
37c2dba8
OB
2256__PACKAGE__->register_method({
2257 name => 'vm_pending',
2258 path => '{vmid}/pending',
2259 method => 'GET',
2260 proxyto => 'node',
2261 description => 'Get container configuration, including pending changes.',
2262 permissions => {
2263 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2264 },
2265 parameters => {
2266 additionalProperties => 0,
2267 properties => {
2268 node => get_standard_option('pve-node'),
2269 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
2270 },
2271 },
2272 returns => {
2273 type => "array",
2274 items => {
2275 type => "object",
2276 properties => {
2277 key => {
2278 description => 'Configuration option name.',
2279 type => 'string',
2280 },
2281 value => {
2282 description => 'Current value.',
2283 type => 'string',
2284 optional => 1,
2285 },
2286 pending => {
2287 description => 'Pending value.',
2288 type => 'string',
2289 optional => 1,
2290 },
2291 delete => {
2292 description => "Indicates a pending delete request if present and not 0.",
2293 type => 'integer',
2294 minimum => 0,
2295 maximum => 2,
2296 optional => 1,
2297 },
2298 },
2299 },
2300 },
2301 code => sub {
2302 my ($param) = @_;
2303
2304 my $conf = PVE::LXC::Config->load_config($param->{vmid});
2305
2306 my $pending_delete_hash = PVE::LXC::Config->parse_pending_delete($conf->{pending}->{delete});
2307
f622e7dc 2308 return PVE::GuestHelpers::config_with_pending_array($conf, $pending_delete_hash);
37c2dba8
OB
2309 }});
2310
f76a2828 23111;