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