]> git.proxmox.com Git - pve-container.git/blame - src/PVE/API2/LXC.pm
fix #889: api create: reserver config with create lock early
[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);
f76a2828 11use PVE::AccessControl;
2f9b5ead 12use PVE::Firewall;
f76a2828
DM
13use PVE::Storage;
14use PVE::RESTHandler;
15use PVE::RPCEnvironment;
c5a8e04f 16use PVE::ReplicationConfig;
f76a2828 17use PVE::LXC;
7af97ad5 18use PVE::LXC::Create;
6f42807e 19use PVE::LXC::Migrate;
56462053 20use PVE::GuestHelpers;
52389a07
DM
21use PVE::API2::LXC::Config;
22use PVE::API2::LXC::Status;
23use PVE::API2::LXC::Snapshot;
f76a2828
DM
24use PVE::JSONSchema qw(get_standard_option);
25use base qw(PVE::RESTHandler);
26
6c2e9377
WB
27BEGIN {
28 if (!$ENV{PVE_GENERATING_DOCS}) {
29 require PVE::HA::Env::PVE2;
30 import PVE::HA::Env::PVE2;
31 require PVE::HA::Config;
32 import PVE::HA::Config;
33 }
34}
f76a2828 35
52389a07
DM
36__PACKAGE__->register_method ({
37 subclass => "PVE::API2::LXC::Config",
38 path => '{vmid}/config',
39});
f76a2828 40
52389a07
DM
41__PACKAGE__->register_method ({
42 subclass => "PVE::API2::LXC::Status",
43 path => '{vmid}/status',
44});
f76a2828 45
52389a07
DM
46__PACKAGE__->register_method ({
47 subclass => "PVE::API2::LXC::Snapshot",
48 path => '{vmid}/snapshot',
49});
f76a2828 50
52389a07
DM
51__PACKAGE__->register_method ({
52 subclass => "PVE::API2::Firewall::CT",
53 path => '{vmid}/firewall',
54});
1e6c8d5b 55
f76a2828 56__PACKAGE__->register_method({
5c752bbf
DM
57 name => 'vmlist',
58 path => '',
f76a2828
DM
59 method => 'GET',
60 description => "LXC container index (per node).",
61 permissions => {
62 description => "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
63 user => 'all',
64 },
65 proxyto => 'node',
66 protected => 1, # /proc files are only readable by root
67 parameters => {
68 additionalProperties => 0,
69 properties => {
70 node => get_standard_option('pve-node'),
71 },
72 },
73 returns => {
74 type => 'array',
75 items => {
76 type => "object",
8233d33d 77 properties => $PVE::LXC::vmstatus_return_properties,
f76a2828
DM
78 },
79 links => [ { rel => 'child', href => "{vmid}" } ],
80 },
81 code => sub {
82 my ($param) = @_;
83
84 my $rpcenv = PVE::RPCEnvironment::get();
85 my $authuser = $rpcenv->get_user();
86
87 my $vmstatus = PVE::LXC::vmstatus();
88
89 my $res = [];
90 foreach my $vmid (keys %$vmstatus) {
91 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
92
93 my $data = $vmstatus->{$vmid};
f76a2828
DM
94 push @$res, $data;
95 }
96
97 return $res;
5c752bbf 98
f76a2828
DM
99 }});
100
9c2d4ce9 101__PACKAGE__->register_method({
5c752bbf
DM
102 name => 'create_vm',
103 path => '',
9c2d4ce9
DM
104 method => 'POST',
105 description => "Create or restore a container.",
106 permissions => {
107 user => 'all', # check inside
108 description => "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
109 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
110 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
111 },
112 protected => 1,
113 proxyto => 'node',
114 parameters => {
115 additionalProperties => 0,
1b4cf758 116 properties => PVE::LXC::Config->json_config_properties({
9c2d4ce9 117 node => get_standard_option('pve-node'),
781e26b2 118 vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_next_vmid }),
9c2d4ce9
DM
119 ostemplate => {
120 description => "The OS template or backup file.",
5c752bbf 121 type => 'string',
9c2d4ce9 122 maxLength => 255,
68e8f3c5 123 completion => \&PVE::LXC::complete_os_templates,
9c2d4ce9 124 },
5c752bbf
DM
125 password => {
126 optional => 1,
9c2d4ce9
DM
127 type => 'string',
128 description => "Sets root password inside container.",
168d6b07 129 minLength => 5,
9c2d4ce9
DM
130 },
131 storage => get_standard_option('pve-storage-id', {
eb35f9c0 132 description => "Default Storage.",
9c2d4ce9
DM
133 default => 'local',
134 optional => 1,
c5362cda 135 completion => \&PVE::Storage::complete_storage_enabled,
9c2d4ce9
DM
136 }),
137 force => {
5c752bbf 138 optional => 1,
9c2d4ce9
DM
139 type => 'boolean',
140 description => "Allow to overwrite existing container.",
141 },
142 restore => {
5c752bbf 143 optional => 1,
9c2d4ce9
DM
144 type => 'boolean',
145 description => "Mark this as restore task.",
146 },
5c752bbf 147 pool => {
9c2d4ce9
DM
148 optional => 1,
149 type => 'string', format => 'pve-poolid',
150 description => "Add the VM to the specified pool.",
151 },
7c78b6cc
WB
152 'ignore-unpack-errors' => {
153 optional => 1,
154 type => 'boolean',
155 description => "Ignore errors when extracting the template.",
156 },
34ddbf08
FG
157 'ssh-public-keys' => {
158 optional => 1,
159 type => 'string',
160 description => "Setup public SSH keys (one key per line, " .
161 "OpenSSH format).",
162 },
f9b4407e
WB
163 bwlimit => {
164 description => "Override i/o bandwidth limit (in KiB/s).",
165 optional => 1,
166 type => 'number',
167 minimum => '0',
168 },
9d0225fb
TL
169 start => {
170 optional => 1,
171 type => 'boolean',
172 default => 0,
173 description => "Start the CT after its creation finished successfully.",
174 },
9c2d4ce9
DM
175 }),
176 },
5c752bbf 177 returns => {
9c2d4ce9
DM
178 type => 'string',
179 },
180 code => sub {
181 my ($param) = @_;
182
61783321
TL
183 PVE::Cluster::check_cfs_quorum();
184
9c2d4ce9 185 my $rpcenv = PVE::RPCEnvironment::get();
9c2d4ce9
DM
186 my $authuser = $rpcenv->get_user();
187
188 my $node = extract_param($param, 'node');
9c2d4ce9 189 my $vmid = extract_param($param, 'vmid');
7c78b6cc 190 my $ignore_unpack_errors = extract_param($param, 'ignore-unpack-errors');
f9b4407e 191 my $bwlimit = extract_param($param, 'bwlimit');
9d0225fb
TL
192 my $start_after_create = extract_param($param, 'start');
193
67afe46e 194 my $basecfg_fn = PVE::LXC::Config->config_file($vmid);
9c2d4ce9
DM
195 my $same_container_exists = -f $basecfg_fn;
196
425b62cb
WB
197 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
198 my $unprivileged = extract_param($param, 'unprivileged');
9c2d4ce9
DM
199 my $restore = extract_param($param, 'restore');
200
148d1cb4
DM
201 if ($restore) {
202 # fixme: limit allowed parameters
148d1cb4
DM
203 }
204
9c2d4ce9
DM
205 my $force = extract_param($param, 'force');
206
207 if (!($same_container_exists && $restore && $force)) {
208 PVE::Cluster::check_vmid_unused($vmid);
e22af68f 209 } else {
61783321 210 die "can't overwrite running container\n" if PVE::LXC::check_running($vmid);
67afe46e
FG
211 my $conf = PVE::LXC::Config->load_config($vmid);
212 PVE::LXC::Config->check_protection($conf, "unable to restore CT $vmid");
9c2d4ce9 213 }
5c752bbf 214
9c2d4ce9 215 my $password = extract_param($param, 'password');
34ddbf08 216 my $ssh_keys = extract_param($param, 'ssh-public-keys');
2130286f 217 PVE::Tools::validate_ssh_public_keys($ssh_keys) if defined($ssh_keys);
34ddbf08 218
27916659 219 my $pool = extract_param($param, 'pool');
9c2d4ce9
DM
220 if (defined($pool)) {
221 $rpcenv->check_pool_exist($pool);
222 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
5c752bbf 223 }
9c2d4ce9
DM
224
225 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
226 # OK
227 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
228 # OK
229 } elsif ($restore && $force && $same_container_exists &&
230 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
231 # OK: user has VM.Backup permissions, and want to restore an existing VM
232 } else {
233 raise_perm_exc();
234 }
235
9eb69152 236 my $ostemplate = extract_param($param, 'ostemplate');
bb6afcb0
DM
237 my $storage = extract_param($param, 'storage') // 'local';
238
9eb69152
FG
239 PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, $param, []);
240
bb6afcb0 241 my $storage_cfg = cfs_read_file("storage.cfg");
9c2d4ce9 242
9c2d4ce9 243 my $archive;
9c2d4ce9 244 if ($ostemplate eq '-') {
148d1cb4
DM
245 die "pipe requires cli environment\n"
246 if $rpcenv->{type} ne 'cli';
247 die "pipe can only be used with restore tasks\n"
248 if !$restore;
249 $archive = '-';
b51a98d4 250 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs});
9c2d4ce9 251 } else {
f26c66e0 252 PVE::Storage::check_volume_access($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
9c2d4ce9
DM
253 $archive = PVE::Storage::abs_filesystem_path($storage_cfg, $ostemplate);
254 }
255
f9b4407e 256 my %used_storages;
bb6afcb0
DM
257 my $check_and_activate_storage = sub {
258 my ($sid) = @_;
259
260 my $scfg = PVE::Storage::storage_check_node($storage_cfg, $sid, $node);
261
262 raise_param_exc({ storage => "storage '$sid' does not support container directories"})
263 if !$scfg->{content}->{rootdir};
264
265 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
266
267 PVE::Storage::activate_storage($storage_cfg, $sid);
f9b4407e 268 $used_storages{$sid} = 1;
bb6afcb0
DM
269 };
270
9c2d4ce9 271 my $conf = {};
5b4657d0 272
99132ce0
WB
273 my $is_root = $authuser eq 'root@pam';
274
b51a98d4 275 my $no_disk_param = {};
db18c1e4
FG
276 my $mp_param = {};
277 my $storage_only_mode = 1;
b51a98d4 278 foreach my $opt (keys %$param) {
78ccc99b
DM
279 my $value = $param->{$opt};
280 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
281 # allow to use simple numbers (add default storage in that case)
db18c1e4
FG
282 if ($value =~ m/^\d+(\.\d+)?$/) {
283 $mp_param->{$opt} = "$storage:$value";
284 } else {
285 $mp_param->{$opt} = $value;
286 }
287 $storage_only_mode = 0;
a9dd8015
FG
288 } elsif ($opt =~ m/^unused\d+$/) {
289 warn "ignoring '$opt', cannot create/restore with unused volume\n";
290 delete $param->{$opt};
78ccc99b
DM
291 } else {
292 $no_disk_param->{$opt} = $value;
293 }
b51a98d4 294 }
bb6afcb0 295
235dbdf3 296 die "mount points configured, but 'rootfs' not set - aborting\n"
db18c1e4
FG
297 if !$storage_only_mode && !defined($mp_param->{rootfs});
298
bb6afcb0 299 # check storage access, activate storage
db18c1e4
FG
300 my $delayed_mp_param = {};
301 PVE::LXC::Config->foreach_mountpoint($mp_param, sub {
bb6afcb0
DM
302 my ($ms, $mountpoint) = @_;
303
304 my $volid = $mountpoint->{volume};
305 my $mp = $mountpoint->{mp};
306
e2007ac2
DM
307 if ($mountpoint->{type} ne 'volume') { # bind or device
308 die "Only root can pass arbitrary filesystem paths.\n"
99132ce0 309 if !$is_root;
e2007ac2
DM
310 } else {
311 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);
312 &$check_and_activate_storage($sid);
313 }
bb6afcb0
DM
314 });
315
316 # check/activate default storage
db18c1e4 317 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs});
bb6afcb0 318
1b4cf758 319 PVE::LXC::Config->update_pct_config($vmid, $conf, 0, $no_disk_param);
9c2d4ce9 320
425b62cb
WB
321 $conf->{unprivileged} = 1 if $unprivileged;
322
61783321 323 my $emsg = $restore ? "unable to restore CT $vmid -" : "unable to create CT $vmid -";
bccaa371 324
61783321
TL
325 eval { PVE::LXC::Config->create_and_lock_config($vmid, $force) };
326 die "$emsg $@" if $@;
bccaa371 327
61783321
TL
328 my $code = sub {
329 my $old_conf = PVE::LXC::Config->load_config($vmid);
bccaa371 330
eb35f9c0 331 my $vollist = [];
27916659 332 eval {
5a7a51d0
WB
333 my $orig_mp_param; # only used if $restore
334 if ($restore) {
61783321 335 die "can't overwrite running container\n" if PVE::LXC::check_running($vmid);
5a7a51d0
WB
336 (my $orig_conf, $orig_mp_param) = PVE::LXC::Create::recover_config($archive);
337 if ($is_root) {
338 # When we're root call 'restore_configuration' with ristricted=0,
339 # causing it to restore the raw lxc entries, among which there may be
340 # 'lxc.idmap' entries. We need to make sure that the extracted contents
341 # of the container match up with the restored configuration afterwards:
342 $conf->{lxc} = [grep { $_->[0] eq 'lxc.idmap' } @{$orig_conf->{lxc}}];
343 }
f360d7f1 344 }
db18c1e4 345 if ($storage_only_mode) {
b51a98d4 346 if ($restore) {
f360d7f1 347 $mp_param = $orig_mp_param;
db18c1e4
FG
348 die "rootfs configuration could not be recovered, please check and specify manually!\n"
349 if !defined($mp_param->{rootfs});
350 PVE::LXC::Config->foreach_mountpoint($mp_param, sub {
351 my ($ms, $mountpoint) = @_;
352 my $type = $mountpoint->{type};
353 if ($type eq 'volume') {
354 die "unable to detect disk size - please specify $ms (size)\n"
355 if !defined($mountpoint->{size});
356 my $disksize = $mountpoint->{size} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
357 delete $mountpoint->{size};
358 $mountpoint->{volume} = "$storage:$disksize";
359 $mp_param->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
360 } else {
15e4d443 361 my $type = $mountpoint->{type};
7e0e7f38
FG
362 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
363 if ($ms eq 'rootfs');
20f9339d 364 die "restoring '$ms' to $type mount is only possible for root\n"
99132ce0 365 if !$is_root;
7e0e7f38 366
15e4d443
FG
367 if ($mountpoint->{backup}) {
368 warn "WARNING - unsupported configuration!\n";
235dbdf3
FG
369 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
370 warn "mount point configuration will be restored after archive extraction!\n";
15e4d443
FG
371 warn "contained files will be restored to wrong directory!\n";
372 }
136040f4 373 delete $mp_param->{$ms}; # actually delay bind/dev mps
db18c1e4
FG
374 $delayed_mp_param->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
375 }
376 });
b51a98d4 377 } else {
db18c1e4 378 $mp_param->{rootfs} = "$storage:4"; # defaults to 4GB
b51a98d4
DM
379 }
380 }
381
db18c1e4
FG
382 $vollist = PVE::LXC::create_disks($storage_cfg, $vmid, $mp_param, $conf);
383
61783321
TL
384 # we always have the 'create' lock so check for more than 1 entry
385 if (scalar(keys %$old_conf) > 1) {
51665c2d 386 # destroy old container volumes
61783321 387 PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $old_conf, { lock => 'create' });
51665c2d 388 }
51665c2d
FG
389
390 eval {
391 my $rootdir = PVE::LXC::mount_all($vmid, $storage_cfg, $conf, 1);
f9b4407e
WB
392 $bwlimit = PVE::Storage::get_bandwidth_limit('restore', [keys %used_storages], $bwlimit);
393 PVE::LXC::Create::restore_archive($archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
51665c2d
FG
394
395 if ($restore) {
99132ce0 396 PVE::LXC::Create::restore_configuration($vmid, $rootdir, $conf, !$is_root);
51665c2d
FG
397 } else {
398 my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir); # detect OS
399 PVE::LXC::Config->write_config($vmid, $conf); # safe config (after OS detection)
400 $lxc_setup->post_create_hook($password, $ssh_keys);
401 }
402 };
403 my $err = $@;
404 PVE::LXC::umount_all($vmid, $storage_cfg, $conf, $err ? 1 : 0);
405 PVE::Storage::deactivate_volumes($storage_cfg, PVE::LXC::Config->get_vm_volumes($conf));
406 die $err if $err;
27916659
DM
407 # set some defaults
408 $conf->{hostname} ||= "CT$vmid";
409 $conf->{memory} ||= 512;
410 $conf->{swap} //= 512;
db18c1e4
FG
411 foreach my $mp (keys %$delayed_mp_param) {
412 $conf->{$mp} = $delayed_mp_param->{$mp};
413 }
67afe46e 414 PVE::LXC::Config->write_config($vmid, $conf);
27916659
DM
415 };
416 if (my $err = $@) {
6c871c36 417 PVE::LXC::destroy_disks($storage_cfg, $vollist);
d7d48be6
TL
418 eval { PVE::LXC::destroy_config($vmid) };
419 warn $@ if $@;
61783321 420 die "$emsg $err";
6d098bf4 421 }
87273b2b 422 PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
9d0225fb
TL
423
424 PVE::API2::LXC::Status->vm_start({ vmid => $vmid, node => $node })
425 if $start_after_create;
9c2d4ce9 426 };
5c752bbf 427
8a6fc617 428 my $workername = $restore ? 'vzrestore' : 'vzcreate';
67afe46e 429 my $realcmd = sub { PVE::LXC::Config->lock_config($vmid, $code); };
9c2d4ce9 430
8a6fc617 431 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
9c2d4ce9
DM
432 }});
433
f76a2828
DM
434__PACKAGE__->register_method({
435 name => 'vmdiridx',
5c752bbf 436 path => '{vmid}',
f76a2828
DM
437 method => 'GET',
438 proxyto => 'node',
439 description => "Directory index",
440 permissions => {
441 user => 'all',
442 },
443 parameters => {
444 additionalProperties => 0,
445 properties => {
446 node => get_standard_option('pve-node'),
447 vmid => get_standard_option('pve-vmid'),
448 },
449 },
450 returns => {
451 type => 'array',
452 items => {
453 type => "object",
454 properties => {
455 subdir => { type => 'string' },
456 },
457 },
458 links => [ { rel => 'child', href => "{subdir}" } ],
459 },
460 code => sub {
461 my ($param) = @_;
462
463 # test if VM exists
67afe46e 464 my $conf = PVE::LXC::Config->load_config($param->{vmid});
f76a2828
DM
465
466 my $res = [
467 { subdir => 'config' },
fff3a342
DM
468 { subdir => 'status' },
469 { subdir => 'vncproxy' },
a0226a00 470 { subdir => 'termproxy' },
fff3a342
DM
471 { subdir => 'vncwebsocket' },
472 { subdir => 'spiceproxy' },
473 { subdir => 'migrate' },
c4a33727 474 { subdir => 'clone' },
f76a2828
DM
475# { subdir => 'initlog' },
476 { subdir => 'rrd' },
477 { subdir => 'rrddata' },
478 { subdir => 'firewall' },
cc5392c8 479 { subdir => 'snapshot' },
985b18ed 480 { subdir => 'resize' },
f76a2828 481 ];
5c752bbf 482
f76a2828
DM
483 return $res;
484 }});
485
c4a33727 486
f76a2828 487__PACKAGE__->register_method({
5c752bbf
DM
488 name => 'rrd',
489 path => '{vmid}/rrd',
f76a2828
DM
490 method => 'GET',
491 protected => 1, # fixme: can we avoid that?
492 permissions => {
493 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
494 },
495 description => "Read VM RRD statistics (returns PNG)",
496 parameters => {
497 additionalProperties => 0,
498 properties => {
499 node => get_standard_option('pve-node'),
500 vmid => get_standard_option('pve-vmid'),
501 timeframe => {
502 description => "Specify the time frame you are interested in.",
503 type => 'string',
504 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
505 },
506 ds => {
507 description => "The list of datasources you want to display.",
508 type => 'string', format => 'pve-configid-list',
509 },
510 cf => {
511 description => "The RRD consolidation function",
512 type => 'string',
513 enum => [ 'AVERAGE', 'MAX' ],
514 optional => 1,
515 },
516 },
517 },
518 returns => {
519 type => "object",
520 properties => {
521 filename => { type => 'string' },
522 },
523 },
524 code => sub {
525 my ($param) = @_;
526
527 return PVE::Cluster::create_rrd_graph(
5c752bbf 528 "pve2-vm/$param->{vmid}", $param->{timeframe},
f76a2828 529 $param->{ds}, $param->{cf});
5c752bbf 530
f76a2828
DM
531 }});
532
533__PACKAGE__->register_method({
5c752bbf
DM
534 name => 'rrddata',
535 path => '{vmid}/rrddata',
f76a2828
DM
536 method => 'GET',
537 protected => 1, # fixme: can we avoid that?
538 permissions => {
539 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
540 },
541 description => "Read VM RRD statistics",
542 parameters => {
543 additionalProperties => 0,
544 properties => {
545 node => get_standard_option('pve-node'),
546 vmid => get_standard_option('pve-vmid'),
547 timeframe => {
548 description => "Specify the time frame you are interested in.",
549 type => 'string',
550 enum => [ 'hour', 'day', 'week', 'month', 'year' ],
551 },
552 cf => {
553 description => "The RRD consolidation function",
554 type => 'string',
555 enum => [ 'AVERAGE', 'MAX' ],
556 optional => 1,
557 },
558 },
559 },
560 returns => {
561 type => "array",
562 items => {
563 type => "object",
564 properties => {},
565 },
566 },
567 code => sub {
568 my ($param) = @_;
569
570 return PVE::Cluster::create_rrd_data(
571 "pve2-vm/$param->{vmid}", $param->{timeframe}, $param->{cf});
572 }});
573
f76a2828 574__PACKAGE__->register_method({
5c752bbf
DM
575 name => 'destroy_vm',
576 path => '{vmid}',
f76a2828
DM
577 method => 'DELETE',
578 protected => 1,
579 proxyto => 'node',
580 description => "Destroy the container (also delete all uses files).",
581 permissions => {
582 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
583 },
584 parameters => {
585 additionalProperties => 0,
586 properties => {
587 node => get_standard_option('pve-node'),
68e8f3c5 588 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid_stopped }),
f76a2828
DM
589 },
590 },
5c752bbf 591 returns => {
f76a2828
DM
592 type => 'string',
593 },
594 code => sub {
595 my ($param) = @_;
596
597 my $rpcenv = PVE::RPCEnvironment::get();
598
599 my $authuser = $rpcenv->get_user();
600
601 my $vmid = $param->{vmid};
602
603 # test if container exists
67afe46e 604 my $conf = PVE::LXC::Config->load_config($vmid);
f76a2828 605
611fe3aa
DM
606 my $storage_cfg = cfs_read_file("storage.cfg");
607
67afe46e 608 PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid");
7e806596 609
9d87e069 610 die "unable to remove CT $vmid - used in HA resources\n"
b6e0b774
AG
611 if PVE::HA::Config::vm_is_ha_managed($vmid);
612
c5a8e04f
DM
613 # do not allow destroy if there are replication jobs
614 my $repl_conf = PVE::ReplicationConfig->new();
615 $repl_conf->check_for_existing_jobs($vmid);
616
d607c17d
DM
617 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
618
619 die $running_error_msg if PVE::LXC::check_running($vmid); # check early
620
611fe3aa 621 my $code = sub {
673cf209 622 # reload config after lock
67afe46e
FG
623 $conf = PVE::LXC::Config->load_config($vmid);
624 PVE::LXC::Config->check_lock($conf);
673cf209 625
d607c17d
DM
626 die $running_error_msg if PVE::LXC::check_running($vmid);
627
27916659 628 PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $conf);
be5fc936 629 PVE::AccessControl::remove_vm_access($vmid);
2f9b5ead 630 PVE::Firewall::remove_vmfw_conf($vmid);
f76a2828
DM
631 };
632
67afe46e 633 my $realcmd = sub { PVE::LXC::Config->lock_config($vmid, $code); };
611fe3aa 634
f76a2828
DM
635 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
636 }});
637
fff3a342
DM
638my $sslcert;
639
640__PACKAGE__->register_method ({
5b4657d0
DM
641 name => 'vncproxy',
642 path => '{vmid}/vncproxy',
fff3a342
DM
643 method => 'POST',
644 protected => 1,
645 permissions => {
646 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
647 },
648 description => "Creates a TCP VNC proxy connections.",
649 parameters => {
650 additionalProperties => 0,
651 properties => {
652 node => get_standard_option('pve-node'),
653 vmid => get_standard_option('pve-vmid'),
654 websocket => {
655 optional => 1,
656 type => 'boolean',
657 description => "use websocket instead of standard VNC.",
658 },
bd0f4d6d
DC
659 width => {
660 optional => 1,
661 description => "sets the width of the console in pixels.",
662 type => 'integer',
663 minimum => 16,
664 maximum => 4096,
665 },
666 height => {
667 optional => 1,
668 description => "sets the height of the console in pixels.",
669 type => 'integer',
670 minimum => 16,
671 maximum => 2160,
672 },
fff3a342
DM
673 },
674 },
5b4657d0 675 returns => {
fff3a342
DM
676 additionalProperties => 0,
677 properties => {
678 user => { type => 'string' },
679 ticket => { type => 'string' },
680 cert => { type => 'string' },
681 port => { type => 'integer' },
682 upid => { type => 'string' },
683 },
684 },
685 code => sub {
686 my ($param) = @_;
687
688 my $rpcenv = PVE::RPCEnvironment::get();
689
690 my $authuser = $rpcenv->get_user();
691
692 my $vmid = $param->{vmid};
693 my $node = $param->{node};
694
695 my $authpath = "/vms/$vmid";
696
697 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
698
699 $sslcert = PVE::Tools::file_get_contents("/etc/pve/pve-root-ca.pem", 8192)
700 if !$sslcert;
701
ec2c57eb 702 my ($remip, $family);
5b4657d0 703
fff3a342 704 if ($node ne PVE::INotify::nodename()) {
85ae6211 705 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
ec2c57eb
WB
706 } else {
707 $family = PVE::Tools::get_host_address_family($node);
fff3a342
DM
708 }
709
ec2c57eb
WB
710 my $port = PVE::Tools::next_vnc_port($family);
711
fff3a342
DM
712 # NOTE: vncterm VNC traffic is already TLS encrypted,
713 # so we select the fastest chipher here (or 'none'?)
5b4657d0 714 my $remcmd = $remip ?
806eae70 715 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
fff3a342 716
67afe46e 717 my $conf = PVE::LXC::Config->load_config($vmid, $node);
65213b67 718 my $concmd = PVE::LXC::get_console_command($vmid, $conf, -1);
aca816ad 719
5b4657d0
DM
720 my $shcmd = [ '/usr/bin/dtach', '-A',
721 "/var/run/dtach/vzctlconsole$vmid",
aca816ad 722 '-r', 'winch', '-z', @$concmd];
fff3a342
DM
723
724 my $realcmd = sub {
725 my $upid = shift;
726
5b4657d0 727 syslog ('info', "starting lxc vnc proxy $upid\n");
fff3a342 728
5b4657d0 729 my $timeout = 10;
fff3a342
DM
730
731 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
5b4657d0 732 '-timeout', $timeout, '-authpath', $authpath,
fff3a342
DM
733 '-perm', 'VM.Console'];
734
bd0f4d6d
DC
735 if ($param->{width}) {
736 push @$cmd, '-width', $param->{width};
737 }
738
739 if ($param->{height}) {
740 push @$cmd, '-height', $param->{height};
741 }
742
fff3a342 743 if ($param->{websocket}) {
5b4657d0 744 $ENV{PVE_VNC_TICKET} = $ticket; # pass ticket to vncterm
fff3a342
DM
745 push @$cmd, '-notls', '-listen', 'localhost';
746 }
747
748 push @$cmd, '-c', @$remcmd, @$shcmd;
749
37634d3d 750 run_command($cmd, keeplocale => 1);
fff3a342
DM
751
752 return;
753 };
754
755 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
756
757 PVE::Tools::wait_for_vnc_port($port);
758
759 return {
760 user => $authuser,
761 ticket => $ticket,
5b4657d0
DM
762 port => $port,
763 upid => $upid,
764 cert => $sslcert,
fff3a342
DM
765 };
766 }});
767
a0226a00
DC
768__PACKAGE__->register_method ({
769 name => 'termproxy',
770 path => '{vmid}/termproxy',
771 method => 'POST',
772 protected => 1,
773 permissions => {
774 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
775 },
776 description => "Creates a TCP proxy connection.",
777 parameters => {
778 additionalProperties => 0,
779 properties => {
780 node => get_standard_option('pve-node'),
781 vmid => get_standard_option('pve-vmid'),
782 },
783 },
784 returns => {
785 additionalProperties => 0,
786 properties => {
787 user => { type => 'string' },
788 ticket => { type => 'string' },
789 port => { type => 'integer' },
790 upid => { type => 'string' },
791 },
792 },
793 code => sub {
794 my ($param) = @_;
795
796 my $rpcenv = PVE::RPCEnvironment::get();
797
798 my $authuser = $rpcenv->get_user();
799
800 my $vmid = $param->{vmid};
801 my $node = $param->{node};
802
803 my $authpath = "/vms/$vmid";
804
805 my $ticket = PVE::AccessControl::assemble_vnc_ticket($authuser, $authpath);
806
807 my ($remip, $family);
808
809 if ($node ne 'localhost' && $node ne PVE::INotify::nodename()) {
810 ($remip, $family) = PVE::Cluster::remote_node_ip($node);
811 } else {
812 $family = PVE::Tools::get_host_address_family($node);
813 }
814
815 my $port = PVE::Tools::next_vnc_port($family);
816
817 my $remcmd = $remip ?
818 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
819
820 my $conf = PVE::LXC::Config->load_config($vmid, $node);
65213b67 821 my $concmd = PVE::LXC::get_console_command($vmid, $conf, -1);
a0226a00
DC
822
823 my $shcmd = [ '/usr/bin/dtach', '-A',
824 "/var/run/dtach/vzctlconsole$vmid",
825 '-r', 'winch', '-z', @$concmd];
826
827 my $realcmd = sub {
828 my $upid = shift;
829
830 syslog ('info', "starting lxc termproxy $upid\n");
831
832 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
833 '--perm', 'VM.Console', '--'];
834 push @$cmd, @$remcmd, @$shcmd;
835
836 PVE::Tools::run_command($cmd);
837 };
838
839 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
840
841 PVE::Tools::wait_for_vnc_port($port);
842
843 return {
844 user => $authuser,
845 ticket => $ticket,
846 port => $port,
847 upid => $upid,
848 };
849 }});
850
fff3a342
DM
851__PACKAGE__->register_method({
852 name => 'vncwebsocket',
853 path => '{vmid}/vncwebsocket',
854 method => 'GET',
5b4657d0 855 permissions => {
fff3a342
DM
856 description => "You also need to pass a valid ticket (vncticket).",
857 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
858 },
859 description => "Opens a weksocket for VNC traffic.",
860 parameters => {
861 additionalProperties => 0,
862 properties => {
863 node => get_standard_option('pve-node'),
864 vmid => get_standard_option('pve-vmid'),
865 vncticket => {
866 description => "Ticket from previous call to vncproxy.",
867 type => 'string',
868 maxLength => 512,
869 },
870 port => {
871 description => "Port number returned by previous vncproxy call.",
872 type => 'integer',
873 minimum => 5900,
874 maximum => 5999,
875 },
876 },
877 },
878 returns => {
879 type => "object",
880 properties => {
881 port => { type => 'string' },
882 },
883 },
884 code => sub {
885 my ($param) = @_;
886
887 my $rpcenv = PVE::RPCEnvironment::get();
888
889 my $authuser = $rpcenv->get_user();
890
891 my $authpath = "/vms/$param->{vmid}";
892
893 PVE::AccessControl::verify_vnc_ticket($param->{vncticket}, $authuser, $authpath);
894
895 my $port = $param->{port};
5b4657d0 896
fff3a342
DM
897 return { port => $port };
898 }});
899
900__PACKAGE__->register_method ({
5b4657d0
DM
901 name => 'spiceproxy',
902 path => '{vmid}/spiceproxy',
fff3a342
DM
903 method => 'POST',
904 protected => 1,
905 proxyto => 'node',
906 permissions => {
907 check => ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
908 },
909 description => "Returns a SPICE configuration to connect to the CT.",
910 parameters => {
911 additionalProperties => 0,
912 properties => {
913 node => get_standard_option('pve-node'),
914 vmid => get_standard_option('pve-vmid'),
915 proxy => get_standard_option('spice-proxy', { optional => 1 }),
916 },
917 },
918 returns => get_standard_option('remote-viewer-config'),
919 code => sub {
920 my ($param) = @_;
921
922 my $vmid = $param->{vmid};
923 my $node = $param->{node};
924 my $proxy = $param->{proxy};
925
926 my $authpath = "/vms/$vmid";
927 my $permissions = 'VM.Console';
928
67afe46e 929 my $conf = PVE::LXC::Config->load_config($vmid);
da4db334
TL
930
931 die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid);
932
aca816ad
DM
933 my $concmd = PVE::LXC::get_console_command($vmid, $conf);
934
5b4657d0
DM
935 my $shcmd = ['/usr/bin/dtach', '-A',
936 "/var/run/dtach/vzctlconsole$vmid",
aca816ad 937 '-r', 'winch', '-z', @$concmd];
fff3a342
DM
938
939 my $title = "CT $vmid";
940
941 return PVE::API2Tools::run_spiceterm($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
942 }});
5c752bbf 943
5c752bbf
DM
944
945__PACKAGE__->register_method({
52389a07
DM
946 name => 'migrate_vm',
947 path => '{vmid}/migrate',
5c752bbf
DM
948 method => 'POST',
949 protected => 1,
950 proxyto => 'node',
52389a07 951 description => "Migrate the container to another node. Creates a new migration task.",
5c752bbf 952 permissions => {
52389a07 953 check => ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
5c752bbf
DM
954 },
955 parameters => {
956 additionalProperties => 0,
957 properties => {
958 node => get_standard_option('pve-node'),
68e8f3c5
DM
959 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
960 target => get_standard_option('pve-node', {
961 description => "Target node.",
39bb88df 962 completion => \&PVE::Cluster::complete_migration_target,
68e8f3c5 963 }),
52389a07
DM
964 online => {
965 type => 'boolean',
966 description => "Use online/live migration.",
967 optional => 1,
968 },
00d8cdc0
DC
969 restart => {
970 type => 'boolean',
971 description => "Use restart migration",
972 optional => 1,
973 },
974 timeout => {
975 type => 'integer',
976 description => "Timeout in seconds for shutdown for restart migration",
977 optional => 1,
978 default => 180,
979 },
9746c095
FG
980 force => {
981 type => 'boolean',
982 description => "Force migration despite local bind / device" .
552e168f 983 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
9746c095
FG
984 optional => 1,
985 },
5c752bbf
DM
986 },
987 },
988 returns => {
989 type => 'string',
52389a07 990 description => "the task ID.",
5c752bbf
DM
991 },
992 code => sub {
993 my ($param) = @_;
994
995 my $rpcenv = PVE::RPCEnvironment::get();
996
997 my $authuser = $rpcenv->get_user();
998
52389a07 999 my $target = extract_param($param, 'target');
bb1ac2de 1000
52389a07
DM
1001 my $localnode = PVE::INotify::nodename();
1002 raise_param_exc({ target => "target is local node."}) if $target eq $localnode;
bb1ac2de 1003
52389a07 1004 PVE::Cluster::check_cfs_quorum();
5c752bbf 1005
52389a07 1006 PVE::Cluster::check_node_exists($target);
27916659 1007
52389a07 1008 my $targetip = PVE::Cluster::remote_node_ip($target);
5c752bbf 1009
52389a07 1010 my $vmid = extract_param($param, 'vmid');
5c752bbf 1011
52389a07 1012 # test if VM exists
67afe46e 1013 PVE::LXC::Config->load_config($vmid);
5c752bbf 1014
52389a07
DM
1015 # try to detect errors early
1016 if (PVE::LXC::check_running($vmid)) {
00d8cdc0
DC
1017 die "can't migrate running container without --online or --restart\n"
1018 if !$param->{online} && !$param->{restart};
5c752bbf 1019 }
5c752bbf
DM
1020
1021 if (PVE::HA::Config::vm_is_ha_managed($vmid) && $rpcenv->{type} ne 'ha') {
1022
1023 my $hacmd = sub {
1024 my $upid = shift;
1025
1026 my $service = "ct:$vmid";
1027
52389a07 1028 my $cmd = ['ha-manager', 'migrate', $service, $target];
5c752bbf 1029
545dd4e1 1030 print "Requesting HA migration for CT $vmid to node $target\n";
5c752bbf
DM
1031
1032 PVE::Tools::run_command($cmd);
1033
1034 return;
1035 };
1036
52389a07 1037 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
5c752bbf
DM
1038
1039 } else {
1040
22557519
DM
1041 my $realcmd = sub {
1042 PVE::LXC::Migrate->migrate($target, $targetip, $vmid, $param);
1043 };
56462053 1044
22557519
DM
1045 my $worker = sub {
1046 return PVE::GuestHelpers::guest_migration_lock($vmid, 10, $realcmd);
5c752bbf
DM
1047 };
1048
22557519 1049 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
5c752bbf
DM
1050 }
1051 }});
1052
cc5392c8
WL
1053__PACKAGE__->register_method({
1054 name => 'vm_feature',
1055 path => '{vmid}/feature',
1056 method => 'GET',
1057 proxyto => 'node',
1058 protected => 1,
1059 description => "Check if feature for virtual machine is available.",
1060 permissions => {
1061 check => ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1062 },
1063 parameters => {
1064 additionalProperties => 0,
1065 properties => {
1066 node => get_standard_option('pve-node'),
1067 vmid => get_standard_option('pve-vmid'),
1068 feature => {
1069 description => "Feature to check.",
1070 type => 'string',
d53e5c58 1071 enum => [ 'snapshot', 'clone', 'copy' ],
cc5392c8
WL
1072 },
1073 snapname => get_standard_option('pve-lxc-snapshot-name', {
1074 optional => 1,
1075 }),
1076 },
1077 },
1078 returns => {
1079 type => "object",
1080 properties => {
1081 hasFeature => { type => 'boolean' },
1082 #nodes => {
1083 #type => 'array',
1084 #items => { type => 'string' },
1085 #}
1086 },
1087 },
1088 code => sub {
1089 my ($param) = @_;
1090
1091 my $node = extract_param($param, 'node');
1092
1093 my $vmid = extract_param($param, 'vmid');
1094
1095 my $snapname = extract_param($param, 'snapname');
1096
1097 my $feature = extract_param($param, 'feature');
1098
67afe46e 1099 my $conf = PVE::LXC::Config->load_config($vmid);
cc5392c8
WL
1100
1101 if($snapname){
1102 my $snap = $conf->{snapshots}->{$snapname};
1103 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1104 $conf = $snap;
1105 }
ef241384 1106 my $storage_cfg = PVE::Storage::config();
cc5392c8 1107 #Maybe include later
ef241384 1108 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
4518000b 1109 my $hasFeature = PVE::LXC::Config->has_feature($feature, $conf, $storage_cfg, $snapname);
cc5392c8
WL
1110
1111 return {
1112 hasFeature => $hasFeature,
1113 #nodes => [ keys %$nodelist ],
1114 };
1115 }});
bb1ac2de
DM
1116
1117__PACKAGE__->register_method({
1118 name => 'template',
1119 path => '{vmid}/template',
1120 method => 'POST',
1121 protected => 1,
1122 proxyto => 'node',
1123 description => "Create a Template.",
1124 permissions => {
1125 description => "You need 'VM.Allocate' permissions on /vms/{vmid}",
1126 check => [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1127 },
1128 parameters => {
1129 additionalProperties => 0,
1130 properties => {
1131 node => get_standard_option('pve-node'),
68e8f3c5 1132 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid_stopped }),
bb1ac2de
DM
1133 },
1134 },
1135 returns => { type => 'null'},
1136 code => sub {
1137 my ($param) = @_;
1138
1139 my $rpcenv = PVE::RPCEnvironment::get();
1140
1141 my $authuser = $rpcenv->get_user();
1142
1143 my $node = extract_param($param, 'node');
1144
1145 my $vmid = extract_param($param, 'vmid');
1146
1147 my $updatefn = sub {
1148
67afe46e
FG
1149 my $conf = PVE::LXC::Config->load_config($vmid);
1150 PVE::LXC::Config->check_lock($conf);
bb1ac2de
DM
1151
1152 die "unable to create template, because CT contains snapshots\n"
1153 if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}});
1154
1155 die "you can't convert a template to a template\n"
67afe46e 1156 if PVE::LXC::Config->is_template($conf);
bb1ac2de
DM
1157
1158 die "you can't convert a CT to template if the CT is running\n"
1159 if PVE::LXC::check_running($vmid);
1160
e1313b73
WL
1161 my $scfg = PVE::Storage::config();
1162 PVE::LXC::Config->foreach_mountpoint($conf, sub {
1163 my ($ms, $mp) = @_;
1164
1165 my ($sid) =PVE::Storage::parse_volume_id($mp->{volume}, 0);
812a2bbc 1166 die "Directory storage '$sid' does not support container templates!\n"
e1313b73
WL
1167 if $scfg->{ids}->{$sid}->{path};
1168 });
1169
bb1ac2de
DM
1170 my $realcmd = sub {
1171 PVE::LXC::template_create($vmid, $conf);
bb1ac2de 1172
c2182c49 1173 $conf->{template} = 1;
bb1ac2de 1174
c2182c49
WL
1175 PVE::LXC::Config->write_config($vmid, $conf);
1176 # and remove lxc config
1177 PVE::LXC::update_lxc_config($vmid, $conf);
1178 };
bb1ac2de
DM
1179
1180 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1181 };
1182
67afe46e 1183 PVE::LXC::Config->lock_config($vmid, $updatefn);
bb1ac2de
DM
1184
1185 return undef;
1186 }});
1187
c4a33727
DM
1188__PACKAGE__->register_method({
1189 name => 'clone_vm',
1190 path => '{vmid}/clone',
1191 method => 'POST',
1192 protected => 1,
1193 proxyto => 'node',
1194 description => "Create a container clone/copy",
1195 permissions => {
1196 description => "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1197 "and 'VM.Allocate' permissions " .
1198 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1199 "'Datastore.AllocateSpace' on any used storage.",
1200 check =>
1201 [ 'and',
1202 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1203 [ 'or',
1204 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1205 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param => 'pool'],
1206 ],
1207 ]
1208 },
1209 parameters => {
1210 additionalProperties => 0,
1211 properties => {
1212 node => get_standard_option('pve-node'),
1213 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
1214 newid => get_standard_option('pve-vmid', {
1215 completion => \&PVE::Cluster::complete_next_vmid,
1216 description => 'VMID for the clone.' }),
1217 hostname => {
1218 optional => 1,
1219 type => 'string', format => 'dns-name',
1220 description => "Set a hostname for the new CT.",
1221 },
1222 description => {
1223 optional => 1,
1224 type => 'string',
1225 description => "Description for the new CT.",
1226 },
1227 pool => {
1228 optional => 1,
1229 type => 'string', format => 'pve-poolid',
1230 description => "Add the new CT to the specified pool.",
1231 },
1232 snapname => get_standard_option('pve-lxc-snapshot-name', {
1233 optional => 1,
1234 }),
1235 storage => get_standard_option('pve-storage-id', {
1236 description => "Target storage for full clone.",
c4a33727
DM
1237 optional => 1,
1238 }),
1239 full => {
1240 optional => 1,
1241 type => 'boolean',
c4b4cb83 1242 description => "Create a full copy of all disks. This is always done when " .
c4a33727 1243 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
c4a33727 1244 },
411ae12c
DM
1245 target => get_standard_option('pve-node', {
1246 description => "Target node. Only allowed if the original VM is on shared storage.",
1247 optional => 1,
1248 }),
c4a33727
DM
1249 },
1250 },
1251 returns => {
1252 type => 'string',
1253 },
1254 code => sub {
1255 my ($param) = @_;
1256
1257 my $rpcenv = PVE::RPCEnvironment::get();
1258
1259 my $authuser = $rpcenv->get_user();
1260
1261 my $node = extract_param($param, 'node');
1262
1263 my $vmid = extract_param($param, 'vmid');
1264
1265 my $newid = extract_param($param, 'newid');
1266
1267 my $pool = extract_param($param, 'pool');
1268
1269 if (defined($pool)) {
1270 $rpcenv->check_pool_exist($pool);
1271 }
1272
1273 my $snapname = extract_param($param, 'snapname');
1274
1275 my $storage = extract_param($param, 'storage');
1276
411ae12c
DM
1277 my $target = extract_param($param, 'target');
1278
c4a33727
DM
1279 my $localnode = PVE::INotify::nodename();
1280
411ae12c
DM
1281 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1282
1283 PVE::Cluster::check_node_exists($target) if $target;
1284
c4a33727
DM
1285 my $storecfg = PVE::Storage::config();
1286
1287 if ($storage) {
1288 # check if storage is enabled on local node
1289 PVE::Storage::storage_check_enabled($storecfg, $storage);
411ae12c
DM
1290 if ($target) {
1291 # check if storage is available on target node
1292 PVE::Storage::storage_check_node($storecfg, $storage, $target);
1293 # clone only works if target storage is shared
1294 my $scfg = PVE::Storage::storage_config($storecfg, $storage);
1295 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared};
1296 }
c4a33727
DM
1297 }
1298
1299 PVE::Cluster::check_cfs_quorum();
1300
db01d674
WB
1301 my $conffile;
1302 my $newconf = {};
1303 my $mountpoints = {};
1304 my $fullclone = {};
1305 my $vollist = [];
411ae12c 1306 my $running;
c4a33727 1307
db01d674
WB
1308 PVE::LXC::Config->lock_config($vmid, sub {
1309 my $src_conf = PVE::LXC::Config->set_lock($vmid, 'disk');
e7d553c7 1310
411ae12c
DM
1311 $running = PVE::LXC::check_running($vmid) || 0;
1312
e7d553c7
DM
1313 my $full = extract_param($param, 'full');
1314 if (!defined($full)) {
1315 $full = !PVE::LXC::Config->is_template($src_conf);
1316 }
1317 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1318
db01d674
WB
1319 eval {
1320 die "snapshot '$snapname' does not exist\n"
1321 if $snapname && !defined($src_conf->{snapshots}->{$snapname});
c4a33727 1322
c4a33727 1323
db01d674 1324 my $src_conf = $snapname ? $src_conf->{snapshots}->{$snapname} : $src_conf;
c4a33727 1325
db01d674
WB
1326 $conffile = PVE::LXC::Config->config_file($newid);
1327 die "unable to create CT $newid: config file already exists\n"
1328 if -f $conffile;
c4a33727 1329
24f9d440 1330 my $sharedvm = 1;
db01d674
WB
1331 foreach my $opt (keys %$src_conf) {
1332 next if $opt =~ m/^unused\d+$/;
c4a33727 1333
db01d674 1334 my $value = $src_conf->{$opt};
c4a33727 1335
db01d674
WB
1336 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1337 my $mp = $opt eq 'rootfs' ?
1338 PVE::LXC::Config->parse_ct_rootfs($value) :
1339 PVE::LXC::Config->parse_ct_mountpoint($value);
c4a33727 1340
db01d674
WB
1341 if ($mp->{type} eq 'volume') {
1342 my $volid = $mp->{volume};
09687674
DM
1343
1344 my ($sid, $volname) = PVE::Storage::parse_volume_id($volid);
1345 $sid = $storage if defined($storage);
1346 my $scfg = PVE::Storage::storage_config($storecfg, $sid);
24f9d440
WB
1347 if (!$scfg->{shared}) {
1348 $sharedvm = 0;
1349 warn "found non-shared volume: $volid\n" if $target;
1350 }
09687674
DM
1351
1352 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1353
e7d553c7 1354 if ($full) {
db01d674
WB
1355 die "Cannot do full clones on a running container without snapshots\n"
1356 if $running && !defined($snapname);
1357 $fullclone->{$opt} = 1;
1358 } else {
1359 # not full means clone instead of copy
1360 die "Linked clone feature for '$volid' is not available\n"
1361 if !PVE::Storage::volume_has_feature($storecfg, 'clone', $volid, $snapname, $running);
1362 }
c4a33727 1363
db01d674
WB
1364 $mountpoints->{$opt} = $mp;
1365 push @$vollist, $volid;
c4a33727 1366
c4a33727 1367 } else {
db01d674
WB
1368 # TODO: allow bind mounts?
1369 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
c4a33727 1370 }
63231f52
TL
1371 } elsif ($opt =~ m/^net(\d+)$/) {
1372 # always change MAC! address
1373 my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg');
1374 my $net = PVE::LXC::Config->parse_lxc_network($value);
1375 $net->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix});
1376 $newconf->{$opt} = PVE::LXC::Config->print_lxc_network($net);
c4a33727 1377 } else {
db01d674
WB
1378 # copy everything else
1379 $newconf->{$opt} = $value;
c4a33727 1380 }
db01d674 1381 }
24f9d440
WB
1382 die "can't clone CT to node '$target' (CT uses local storage)\n"
1383 if $target && !$sharedvm;
c4a33727 1384
db01d674
WB
1385 # Replace the 'disk' lock with a 'create' lock.
1386 $newconf->{lock} = 'create';
1387
1388 delete $newconf->{template};
1389 if ($param->{hostname}) {
1390 $newconf->{hostname} = $param->{hostname};
c4a33727 1391 }
c4a33727 1392
db01d674
WB
1393 if ($param->{description}) {
1394 $newconf->{description} = $param->{description};
1395 }
c4a33727 1396
db01d674
WB
1397 # create empty/temp config - this fails if CT already exists on other node
1398 PVE::LXC::Config->write_config($newid, $newconf);
1399 };
1400 if (my $err = $@) {
1401 eval { PVE::LXC::Config->remove_lock($vmid, 'disk') };
1402 warn $@ if $@;
1403 die $err;
c4a33727 1404 }
db01d674 1405 });
c4a33727 1406
db01d674
WB
1407 my $update_conf = sub {
1408 my ($key, $value) = @_;
1409 return PVE::LXC::Config->lock_config($newid, sub {
1410 my $conf = PVE::LXC::Config->load_config($newid);
1411 die "Lost 'create' config lock, aborting.\n"
1412 if !PVE::LXC::Config->has_lock($conf, 'create');
1413 $conf->{$key} = $value;
1414 PVE::LXC::Config->write_config($newid, $conf);
1415 });
1416 };
c4a33727 1417
db01d674
WB
1418 my $realcmd = sub {
1419 my ($upid) = @_;
c4a33727 1420
db01d674 1421 my $newvollist = [];
c4a33727 1422
411ae12c
DM
1423 my $verify_running = PVE::LXC::check_running($vmid) || 0;
1424 die "unexpected state change\n" if $verify_running != $running;
1425
db01d674
WB
1426 eval {
1427 local $SIG{INT} =
1428 local $SIG{TERM} =
1429 local $SIG{QUIT} =
1430 local $SIG{HUP} = sub { die "interrupted by signal\n"; };
c4a33727 1431
db01d674 1432 PVE::Storage::activate_volumes($storecfg, $vollist, $snapname);
c4a33727 1433
db01d674
WB
1434 foreach my $opt (keys %$mountpoints) {
1435 my $mp = $mountpoints->{$opt};
1436 my $volid = $mp->{volume};
c4a33727 1437
db01d674
WB
1438 my $newvolid;
1439 if ($fullclone->{$opt}) {
1440 print "create full clone of mountpoint $opt ($volid)\n";
d7b775db
DM
1441 my $target_storage = $storage // PVE::Storage::parse_volume_id($volid);
1442 $newvolid = PVE::LXC::copy_volume($mp, $newid, $target_storage, $storecfg, $newconf, $snapname);
db01d674
WB
1443 } else {
1444 print "create linked clone of mount point $opt ($volid)\n";
1445 $newvolid = PVE::Storage::vdisk_clone($storecfg, $volid, $newid, $snapname);
c4a33727
DM
1446 }
1447
db01d674
WB
1448 push @$newvollist, $newvolid;
1449 $mp->{volume} = $newvolid;
c4a33727 1450
db01d674 1451 $update_conf->($opt, PVE::LXC::Config->print_ct_mountpoint($mp, $opt eq 'rootfs'));
c4a33727
DM
1452 }
1453
db01d674
WB
1454 PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool;
1455 PVE::LXC::Config->remove_lock($newid, 'create');
411ae12c
DM
1456
1457 if ($target) {
1458 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1459 PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
1460 PVE::Storage::deactivate_volumes($storecfg, $newvollist);
1461
1462 my $newconffile = PVE::LXC::Config->config_file($newid, $target);
1463 die "Failed to move config to node '$target' - rename failed: $!\n"
1464 if !rename($conffile, $newconffile);
1465 }
c4a33727 1466 };
db01d674 1467 my $err = $@;
c4a33727 1468
db01d674
WB
1469 # Unlock the source config in any case:
1470 eval { PVE::LXC::Config->remove_lock($vmid, 'disk') };
1471 warn $@ if $@;
c4a33727 1472
db01d674
WB
1473 if ($err) {
1474 # Now cleanup the config & disks:
1475 unlink $conffile;
c4a33727 1476
db01d674
WB
1477 sleep 1; # some storages like rbd need to wait before release volume - really?
1478
1479 foreach my $volid (@$newvollist) {
1480 eval { PVE::Storage::vdisk_free($storecfg, $volid); };
1481 warn $@ if $@;
1482 }
1483 die "clone failed: $err";
1484 }
1485
1486 return;
c4a33727
DM
1487 };
1488
db01d674
WB
1489 PVE::Firewall::clone_vmfw_conf($vmid, $newid);
1490 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
c4a33727
DM
1491 }});
1492
1493
985b18ed
WL
1494__PACKAGE__->register_method({
1495 name => 'resize_vm',
1496 path => '{vmid}/resize',
1497 method => 'PUT',
1498 protected => 1,
1499 proxyto => 'node',
235dbdf3 1500 description => "Resize a container mount point.",
985b18ed
WL
1501 permissions => {
1502 check => ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any => 1],
1503 },
1504 parameters => {
1505 additionalProperties => 0,
b8c5a95f
WB
1506 properties => {
1507 node => get_standard_option('pve-node'),
1508 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
1509 disk => {
1510 type => 'string',
1511 description => "The disk you want to resize.",
d250604f 1512 enum => [PVE::LXC::Config->mountpoint_names()],
b8c5a95f
WB
1513 },
1514 size => {
1515 type => 'string',
1516 pattern => '\+?\d+(\.\d+)?[KMGT]?',
1517 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.",
1518 },
1519 digest => {
1520 type => 'string',
1521 description => 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1522 maxLength => 40,
1523 optional => 1,
1524 }
1525 },
985b18ed 1526 },
9f3f7963
WL
1527 returns => {
1528 type => 'string',
1529 description => "the task ID.",
1530 },
985b18ed
WL
1531 code => sub {
1532 my ($param) = @_;
1533
1534 my $rpcenv = PVE::RPCEnvironment::get();
1535
1536 my $authuser = $rpcenv->get_user();
1537
1538 my $node = extract_param($param, 'node');
1539
1540 my $vmid = extract_param($param, 'vmid');
1541
1542 my $digest = extract_param($param, 'digest');
1543
1544 my $sizestr = extract_param($param, 'size');
1545 my $ext = ($sizestr =~ s/^\+//);
1546 my $newsize = PVE::JSONSchema::parse_size($sizestr);
1547 die "invalid size string" if !defined($newsize);
1548
1549 die "no options specified\n" if !scalar(keys %$param);
1550
f1ba1a4b 1551 PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, $param, []);
985b18ed
WL
1552
1553 my $storage_cfg = cfs_read_file("storage.cfg");
1554
985b18ed
WL
1555 my $code = sub {
1556
67afe46e
FG
1557 my $conf = PVE::LXC::Config->load_config($vmid);
1558 PVE::LXC::Config->check_lock($conf);
985b18ed
WL
1559
1560 PVE::Tools::assert_if_modified($digest, $conf->{digest});
1561
1562 my $running = PVE::LXC::check_running($vmid);
1563
1564 my $disk = $param->{disk};
1b4cf758
FG
1565 my $mp = $disk eq 'rootfs' ? PVE::LXC::Config->parse_ct_rootfs($conf->{$disk}) :
1566 PVE::LXC::Config->parse_ct_mountpoint($conf->{$disk});
44a9face 1567
985b18ed
WL
1568 my $volid = $mp->{volume};
1569
1570 my (undef, undef, $owner, undef, undef, undef, $format) =
1571 PVE::Storage::parse_volname($storage_cfg, $volid);
1572
235dbdf3 1573 die "can't resize mount point owned by another container ($owner)"
985b18ed
WL
1574 if $vmid != $owner;
1575
1576 die "can't resize volume: $disk if snapshot exists\n"
1577 if %{$conf->{snapshots}} && $format eq 'qcow2';
1578
1579 my ($storeid, $volname) = PVE::Storage::parse_volume_id($volid);
1580
1581 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1582
4e1e1791
WB
1583 PVE::Storage::activate_volumes($storage_cfg, [$volid]);
1584
985b18ed
WL
1585 my $size = PVE::Storage::volume_size_info($storage_cfg, $volid, 5);
1586 $newsize += $size if $ext;
1587 $newsize = int($newsize);
1588
1589 die "unable to shrink disk size\n" if $newsize < $size;
1590
1591 return if $size == $newsize;
1592
1593 PVE::Cluster::log_msg('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
9f3f7963 1594 my $realcmd = sub {
e72c8b0e
DM
1595 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1596 # we pass 0 here (parameter only makes sense for qemu)
9f3f7963
WL
1597 PVE::Storage::volume_resize($storage_cfg, $volid, $newsize, 0);
1598
1599 $mp->{size} = $newsize;
1b4cf758 1600 $conf->{$disk} = PVE::LXC::Config->print_ct_mountpoint($mp, $disk eq 'rootfs');
9f3f7963 1601
67afe46e 1602 PVE::LXC::Config->write_config($vmid, $conf);
9f3f7963
WL
1603
1604 if ($format eq 'raw') {
1605 my $path = PVE::Storage::path($storage_cfg, $volid, undef);
1606 if ($running) {
2a993004
WL
1607
1608 $mp->{mp} = '/';
1609 my $use_loopdev = (PVE::LXC::mountpoint_mount_path($mp, $storage_cfg))[1];
21f292ff 1610 $path = PVE::LXC::query_loopdev($path) if $use_loopdev;
235dbdf3 1611 die "internal error: CT running but mount point not attached to a loop device"
2a993004
WL
1612 if !$path;
1613 PVE::Tools::run_command(['losetup', '--set-capacity', $path]) if $use_loopdev;
9f3f7963
WL
1614
1615 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1616 # to be visible to it in its namespace.
1617 # To not interfere with the rest of the system we unshare the current mount namespace,
1618 # mount over /tmp and then run resize2fs.
1619
1620 # interestingly we don't need to e2fsck on mounted systems...
1621 my $quoted = PVE::Tools::shellquote($path);
1622 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
ce9e6458
WB
1623 eval {
1624 PVE::Tools::run_command(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1625 };
1626 warn "Failed to update the container's filesystem: $@\n" if $@;
9f3f7963 1627 } else {
ce9e6458
WB
1628 eval {
1629 PVE::Tools::run_command(['e2fsck', '-f', '-y', $path]);
1630 PVE::Tools::run_command(['resize2fs', $path]);
1631 };
1632 warn "Failed to update the container's filesystem: $@\n" if $@;
9f3f7963 1633 }
985b18ed 1634 }
9f3f7963 1635 };
985b18ed 1636
9f3f7963
WL
1637 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1638 };
985b18ed 1639
67afe46e 1640 return PVE::LXC::Config->lock_config($vmid, $code);;
985b18ed
WL
1641 }});
1642
3c0f6806
WB
1643__PACKAGE__->register_method({
1644 name => 'move_volume',
1645 path => '{vmid}/move_volume',
1646 method => 'POST',
1647 protected => 1,
1648 proxyto => 'node',
1649 description => "Move a rootfs-/mp-volume to a different storage",
1650 permissions => {
1651 description => "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1652 "and 'Datastore.AllocateSpace' permissions on the storage.",
1653 check =>
1654 [ 'and',
1655 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1656 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1657 ],
1658 },
1659 parameters => {
1660 additionalProperties => 0,
1661 properties => {
1662 node => get_standard_option('pve-node'),
1663 vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid }),
1664 volume => {
1665 type => 'string',
1666 enum => [ PVE::LXC::Config->mountpoint_names() ],
1667 description => "Volume which will be moved.",
1668 },
1669 storage => get_standard_option('pve-storage-id', {
1670 description => "Target Storage.",
1671 completion => \&PVE::Storage::complete_storage_enabled,
1672 }),
1673 delete => {
1674 type => 'boolean',
1675 description => "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1676 optional => 1,
1677 default => 0,
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 },
1686 },
1687 returns => {
1688 type => 'string',
1689 },
1690 code => sub {
1691 my ($param) = @_;
1692
1693 my $rpcenv = PVE::RPCEnvironment::get();
1694
1695 my $authuser = $rpcenv->get_user();
1696
1697 my $vmid = extract_param($param, 'vmid');
1698
1699 my $storage = extract_param($param, 'storage');
1700
1701 my $mpkey = extract_param($param, 'volume');
1702
1703 my $lockname = 'disk';
1704
1705 my ($mpdata, $old_volid);
1706
1707 PVE::LXC::Config->lock_config($vmid, sub {
1708 my $conf = PVE::LXC::Config->load_config($vmid);
1709 PVE::LXC::Config->check_lock($conf);
1710
1711 die "cannot move volumes of a running container\n" if PVE::LXC::check_running($vmid);
1712
1713 if ($mpkey eq 'rootfs') {
1714 $mpdata = PVE::LXC::Config->parse_ct_rootfs($conf->{$mpkey});
1715 } elsif ($mpkey =~ m/mp\d+/) {
1716 $mpdata = PVE::LXC::Config->parse_ct_mountpoint($conf->{$mpkey});
1717 } else {
1718 die "Can't parse $mpkey\n";
1719 }
1720 $old_volid = $mpdata->{volume};
1721
1722 die "you can't move a volume with snapshots and delete the source\n"
1723 if $param->{delete} && PVE::LXC::Config->is_volume_in_use_by_snapshots($conf, $old_volid);
1724
1725 PVE::Tools::assert_if_modified($param->{digest}, $conf->{digest});
1726
1727 PVE::LXC::Config->set_lock($vmid, $lockname);
1728 });
1729
1730 my $realcmd = sub {
1731 eval {
1732 PVE::Cluster::log_msg('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1733
1734 my $conf = PVE::LXC::Config->load_config($vmid);
1735 my $storage_cfg = PVE::Storage::config();
1736
1737 my $new_volid;
1738
1739 eval {
1740 PVE::Storage::activate_volumes($storage_cfg, [ $old_volid ]);
1741 $new_volid = PVE::LXC::copy_volume($mpdata, $vmid, $storage, $storage_cfg, $conf);
1742 $mpdata->{volume} = $new_volid;
1743
1744 PVE::LXC::Config->lock_config($vmid, sub {
1745 my $digest = $conf->{digest};
1746 $conf = PVE::LXC::Config->load_config($vmid);
1747 PVE::Tools::assert_if_modified($digest, $conf->{digest});
1748
1749 $conf->{$mpkey} = PVE::LXC::Config->print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1750
1751 PVE::LXC::Config->add_unused_volume($conf, $old_volid) if !$param->{delete};
1752
1753 PVE::LXC::Config->write_config($vmid, $conf);
1754 });
1755
1756 eval {
1757 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1758 PVE::Storage::deactivate_volumes($storage_cfg, [ $new_volid ])
1759 };
1760 warn $@ if $@;
1761 };
1762 if (my $err = $@) {
1763 eval {
1764 PVE::Storage::vdisk_free($storage_cfg, $new_volid)
1765 if defined($new_volid);
1766 };
1767 warn $@ if $@;
1768 die $err;
1769 }
1770
1771 if ($param->{delete}) {
1772 eval {
1773 PVE::Storage::deactivate_volumes($storage_cfg, [ $old_volid ]);
1774 PVE::Storage::vdisk_free($storage_cfg, $old_volid);
1775 };
1776 warn $@ if $@;
1777 }
1778 };
1779 my $err = $@;
1780 eval { PVE::LXC::Config->remove_lock($vmid, $lockname) };
1781 warn $@ if $@;
1782 die $err if $err;
1783 };
1784 my $task = eval {
1785 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1786 };
1787 if (my $err = $@) {
1788 eval { PVE::LXC::Config->remove_lock($vmid, $lockname) };
1789 warn $@ if $@;
1790 die $err;
1791 }
1792 return $task;
1793 }});
1794
f76a2828 17951;