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