1 package PVE
::API2
::LXC
;
6 use Socket
qw(SOCK_STREAM);
9 use PVE
::Tools
qw(extract_param run_command);
10 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
12 use PVE
::Cluster
qw(cfs_read_file);
14 use PVE
::DataCenterConfig
;
15 use PVE
::AccessControl
;
19 use PVE
::RPCEnvironment
;
20 use PVE
::ReplicationConfig
;
23 use PVE
::LXC
::Migrate
;
24 use PVE
::GuestHelpers
;
25 use PVE
::VZDump
::Plugin
;
26 use PVE
::API2
::LXC
::Config
;
27 use PVE
::API2
::LXC
::Status
;
28 use PVE
::API2
::LXC
::Snapshot
;
29 use PVE
::JSONSchema
qw(get_standard_option);
30 use base
qw(PVE::RESTHandler);
33 if (!$ENV{PVE_GENERATING_DOCS
}) {
34 require PVE
::HA
::Env
::PVE2
;
35 import PVE
::HA
::Env
::PVE2
;
36 require PVE
::HA
::Config
;
37 import PVE
::HA
::Config
;
41 my $check_storage_access_migrate = sub {
42 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
44 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $node);
46 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
48 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
49 die "storage '$storage' does not support CT rootdirs\n"
50 if !$scfg->{content
}->{rootdir
};
53 __PACKAGE__-
>register_method ({
54 subclass
=> "PVE::API2::LXC::Config",
55 path
=> '{vmid}/config',
58 __PACKAGE__-
>register_method ({
59 subclass
=> "PVE::API2::LXC::Status",
60 path
=> '{vmid}/status',
63 __PACKAGE__-
>register_method ({
64 subclass
=> "PVE::API2::LXC::Snapshot",
65 path
=> '{vmid}/snapshot',
68 __PACKAGE__-
>register_method ({
69 subclass
=> "PVE::API2::Firewall::CT",
70 path
=> '{vmid}/firewall',
73 __PACKAGE__-
>register_method({
77 description
=> "LXC container index (per node).",
79 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
83 protected
=> 1, # /proc files are only readable by root
85 additionalProperties
=> 0,
87 node
=> get_standard_option
('pve-node'),
94 properties
=> $PVE::LXC
::vmstatus_return_properties
,
96 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
101 my $rpcenv = PVE
::RPCEnvironment
::get
();
102 my $authuser = $rpcenv->get_user();
104 my $vmstatus = PVE
::LXC
::vmstatus
();
107 foreach my $vmid (keys %$vmstatus) {
108 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
110 my $data = $vmstatus->{$vmid};
118 __PACKAGE__-
>register_method({
122 description
=> "Create or restore a container.",
124 user
=> 'all', # check inside
125 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
126 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
127 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
132 additionalProperties
=> 0,
133 properties
=> PVE
::LXC
::Config-
>json_config_properties({
134 node
=> get_standard_option
('pve-node'),
135 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
137 description
=> "The OS template or backup file.",
140 completion
=> \
&PVE
::LXC
::complete_os_templates
,
145 description
=> "Sets root password inside container.",
148 storage
=> get_standard_option
('pve-storage-id', {
149 description
=> "Default Storage.",
152 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
157 description
=> "Allow to overwrite existing container.",
162 description
=> "Mark this as restore task.",
167 description
=> "Assign a unique random ethernet address.",
168 requires
=> 'restore',
172 type
=> 'string', format
=> 'pve-poolid',
173 description
=> "Add the VM to the specified pool.",
175 'ignore-unpack-errors' => {
178 description
=> "Ignore errors when extracting the template.",
180 'ssh-public-keys' => {
183 description
=> "Setup public SSH keys (one key per line, " .
187 description
=> "Override I/O bandwidth limit (in KiB/s).",
191 default => 'restore limit from datacenter or storage config',
197 description
=> "Start the CT after its creation finished successfully.",
207 PVE
::Cluster
::check_cfs_quorum
();
209 my $rpcenv = PVE
::RPCEnvironment
::get
();
210 my $authuser = $rpcenv->get_user();
212 my $node = extract_param
($param, 'node');
213 my $vmid = extract_param
($param, 'vmid');
214 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
215 my $bwlimit = extract_param
($param, 'bwlimit');
216 my $start_after_create = extract_param
($param, 'start');
218 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
219 my $same_container_exists = -f
$basecfg_fn;
221 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
222 my $unprivileged = extract_param
($param, 'unprivileged');
223 my $restore = extract_param
($param, 'restore');
224 my $unique = extract_param
($param, 'unique');
226 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
227 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
229 # used to skip firewall config restore if user lacks permission
230 my $skip_fw_config_restore = 0;
233 # fixme: limit allowed parameters
236 my $force = extract_param
($param, 'force');
238 if (!($same_container_exists && $restore && $force)) {
239 PVE
::Cluster
::check_vmid_unused
($vmid);
241 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
242 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
243 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
246 my $password = extract_param
($param, 'password');
247 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
248 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
250 my $pool = extract_param
($param, 'pool');
251 $rpcenv->check_pool_exist($pool) if defined($pool);
253 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
255 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
257 } elsif ($restore && $force && $same_container_exists &&
258 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
259 # OK: user has VM.Backup permissions, and want to restore an existing VM
261 # we don't want to restore a container-provided FW conf in this case
262 # since the user is lacking permission to configure the container's FW
263 $skip_fw_config_restore = 1;
265 # error out if a user tries to change from unprivileged to privileged
266 # explicit change is checked here, implicit is checked down below or happening in root-only paths
267 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
268 if ($conf->{unprivileged
} && defined($unprivileged) && !$unprivileged) {
269 raise_perm_exc
("cannot change from unprivileged to privileged without VM.Allocate");
275 my $ostemplate = extract_param
($param, 'ostemplate');
276 my $storage = extract_param
($param, 'storage') // 'local';
278 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, undef, $param, [], $unprivileged);
280 my $storage_cfg = cfs_read_file
("storage.cfg");
283 if ($ostemplate eq '-') {
284 die "pipe requires cli environment\n"
285 if $rpcenv->{type
} ne 'cli';
286 die "pipe can only be used with restore tasks\n"
289 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
291 my $content_type = $restore ?
'backup' : 'vztmpl';
292 PVE
::Storage
::check_volume_access
(
300 $archive = $ostemplate;
304 my $check_and_activate_storage = sub {
307 my $scfg = PVE
::Storage
::storage_check_enabled
($storage_cfg, $sid, $node);
309 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
310 if !$scfg->{content
}->{rootdir
};
312 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
314 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
315 $used_storages{$sid} = 1;
320 my $is_root = $authuser eq 'root@pam';
322 my $no_disk_param = {};
324 my $storage_only_mode = 1;
325 foreach my $opt (keys %$param) {
326 my $value = $param->{$opt};
327 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
328 # allow to use simple numbers (add default storage in that case)
329 if ($value =~ m/^\d+(\.\d+)?$/) {
330 $mp_param->{$opt} = "$storage:$value";
332 $mp_param->{$opt} = $value;
334 $storage_only_mode = 0;
335 } elsif ($opt =~ m/^unused\d+$/) {
336 warn "ignoring '$opt', cannot create/restore with unused volume\n";
337 delete $param->{$opt};
339 $no_disk_param->{$opt} = $value;
343 die "mount points configured, but 'rootfs' not set - aborting\n"
344 if !$storage_only_mode && !defined($mp_param->{rootfs
});
346 # check storage access, activate storage
347 my $delayed_mp_param = {};
348 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
349 my ($ms, $mountpoint) = @_;
351 my $volid = $mountpoint->{volume
};
352 my $mp = $mountpoint->{mp
};
354 if ($mountpoint->{type
} ne 'volume') { # bind or device
355 die "Only root can pass arbitrary filesystem paths.\n"
358 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
359 &$check_and_activate_storage($sid);
363 # check/activate default storage
364 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
366 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
368 $conf->{unprivileged
} = 1 if $unprivileged;
370 my $emsg = $restore ?
"unable to restore CT $vmid -" : "unable to create CT $vmid -";
372 eval { PVE
::LXC
::Config-
>create_and_lock_config($vmid, $force) };
373 die "$emsg $@" if $@;
375 my $destroy_config_on_error = !$same_container_exists;
378 my $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
383 my $orig_mp_param; # only used if $restore
385 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
386 if ($archive ne '-') {
388 print "recovering backed-up configuration from '$archive'\n";
389 ($orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
391 for my $opt (keys %$orig_conf) {
392 # early check before disks are created
393 # the "real" check is in later on when actually merging the configs
394 if ($opt =~ /^net\d+$/ && !defined($param->{$opt})) {
395 PVE
::LXC
::check_bridge_access
($rpcenv, $authuser, $orig_conf->{$opt});
399 $was_template = delete $orig_conf->{template
};
401 # When we're root call 'restore_configuration' with restricted=0,
402 # causing it to restore the raw lxc entries, among which there may be
403 # 'lxc.idmap' entries. We need to make sure that the extracted contents
404 # of the container match up with the restored configuration afterwards:
405 $conf->{lxc
} = $orig_conf->{lxc
} if $is_root;
407 $conf->{unprivileged
} = $orig_conf->{unprivileged
}
408 if !defined($unprivileged) && defined($orig_conf->{unprivileged
});
410 # implicit privileged change is checked here
411 if ($old_conf->{unprivileged
} && !$conf->{unprivileged
}) {
412 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Allocate']);
416 if ($storage_only_mode) {
418 if (!defined($orig_mp_param)) {
419 print "recovering backed-up configuration from '$archive'\n";
420 (undef, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
422 $mp_param = $orig_mp_param;
423 die "rootfs configuration could not be recovered, please check and specify manually!\n"
424 if !defined($mp_param->{rootfs
});
425 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
426 my ($ms, $mountpoint) = @_;
427 my $type = $mountpoint->{type
};
428 if ($type eq 'volume') {
429 die "unable to detect disk size - please specify $ms (size)\n"
430 if !defined($mountpoint->{size
});
431 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
432 delete $mountpoint->{size
};
433 $mountpoint->{volume
} = "$storage:$disksize";
434 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
436 my $type = $mountpoint->{type
};
437 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
438 if ($ms eq 'rootfs');
439 die "restoring '$ms' to $type mount is only possible for root\n"
442 if ($mountpoint->{backup
}) {
443 warn "WARNING - unsupported configuration!\n";
444 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
445 warn "mount point configuration will be restored after archive extraction!\n";
446 warn "contained files will be restored to wrong directory!\n";
448 delete $mp_param->{$ms}; # actually delay bind/dev mps
449 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
453 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
457 # up until here we did not modify the container, besides the lock
458 $destroy_config_on_error = 1;
460 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
462 # we always have the 'create' lock so check for more than 1 entry
463 if (scalar(keys %$old_conf) > 1) {
464 # destroy old container volumes
465 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, { lock => 'create' });
469 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
470 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
471 print "restoring '$archive' now..\n"
472 if $restore && $archive ne '-';
473 PVE
::LXC
::Create
::restore_archive
($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
476 print "merging backed-up and given configuration..\n";
477 PVE
::LXC
::Create
::restore_configuration
($vmid, $storage_cfg, $archive, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
478 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
479 $lxc_setup->template_fixup($conf);
481 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
482 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
483 $lxc_setup->post_create_hook($password, $ssh_keys);
487 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
488 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
491 $conf->{hostname
} ||= "CT$vmid";
492 $conf->{memory
} ||= 512;
493 $conf->{swap
} //= 512;
494 foreach my $mp (keys %$delayed_mp_param) {
495 $conf->{$mp} = $delayed_mp_param->{$mp};
497 # If the template flag was set, we try to convert again to template after restore
499 print STDERR
"Convert restored container to template...\n";
500 PVE
::LXC
::template_create
($vmid, $conf);
501 $conf->{template
} = 1;
503 PVE
::LXC
::Config-
>write_config($vmid, $conf);
506 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
507 if ($destroy_config_on_error) {
508 eval { PVE
::LXC
::Config-
>destroy_config($vmid) };
511 if (!$skip_fw_config_restore) { # Only if user has permission to change the fw
512 PVE
::Firewall
::remove_vmfw_conf
($vmid);
518 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
520 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
521 if $start_after_create;
524 my $workername = $restore ?
'vzrestore' : 'vzcreate';
527 PVE
::LXC
::Config-
>lock_config($vmid, $code);
530 # if we aborted before changing the container, we must remove the create lock
531 if (!$destroy_config_on_error) {
532 PVE
::LXC
::Config-
>remove_lock($vmid, 'create');
538 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
541 __PACKAGE__-
>register_method({
546 description
=> "Directory index",
551 additionalProperties
=> 0,
553 node
=> get_standard_option
('pve-node'),
554 vmid
=> get_standard_option
('pve-vmid'),
562 subdir
=> { type
=> 'string' },
565 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
571 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
574 { subdir
=> 'config' },
575 { subdir
=> 'pending' },
576 { subdir
=> 'status' },
577 { subdir
=> 'vncproxy' },
578 { subdir
=> 'termproxy' },
579 { subdir
=> 'vncwebsocket' },
580 { subdir
=> 'spiceproxy' },
581 { subdir
=> 'migrate' },
582 { subdir
=> 'clone' },
583 # { subdir => 'initlog' },
585 { subdir
=> 'rrddata' },
586 { subdir
=> 'firewall' },
587 { subdir
=> 'snapshot' },
588 { subdir
=> 'resize' },
595 __PACKAGE__-
>register_method({
597 path
=> '{vmid}/rrd',
599 protected
=> 1, # fixme: can we avoid that?
601 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
603 description
=> "Read VM RRD statistics (returns PNG)",
605 additionalProperties
=> 0,
607 node
=> get_standard_option
('pve-node'),
608 vmid
=> get_standard_option
('pve-vmid'),
610 description
=> "Specify the time frame you are interested in.",
612 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
615 description
=> "The list of datasources you want to display.",
616 type
=> 'string', format
=> 'pve-configid-list',
619 description
=> "The RRD consolidation function",
621 enum
=> [ 'AVERAGE', 'MAX' ],
629 filename
=> { type
=> 'string' },
635 return PVE
::RRD
::create_rrd_graph
(
636 "pve2-vm/$param->{vmid}", $param->{timeframe
},
637 $param->{ds
}, $param->{cf
});
641 __PACKAGE__-
>register_method({
643 path
=> '{vmid}/rrddata',
645 protected
=> 1, # fixme: can we avoid that?
647 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
649 description
=> "Read VM RRD statistics",
651 additionalProperties
=> 0,
653 node
=> get_standard_option
('pve-node'),
654 vmid
=> get_standard_option
('pve-vmid'),
656 description
=> "Specify the time frame you are interested in.",
658 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
661 description
=> "The RRD consolidation function",
663 enum
=> [ 'AVERAGE', 'MAX' ],
678 return PVE
::RRD
::create_rrd_data
(
679 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
682 __PACKAGE__-
>register_method({
683 name
=> 'destroy_vm',
688 description
=> "Destroy the container (also delete all uses files).",
690 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
693 additionalProperties
=> 0,
695 node
=> get_standard_option
('pve-node'),
696 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
699 description
=> "Force destroy, even if running.",
705 description
=> "Remove container from all related configurations."
706 ." For example, backup jobs, replication jobs or HA."
707 ." Related ACLs and Firewall entries will *always* be removed.",
711 'destroy-unreferenced-disks' => {
713 description
=> "If set, destroy additionally all disks with the VMID from all"
714 ." enabled storages which are not referenced in the config.",
725 my $rpcenv = PVE
::RPCEnvironment
::get
();
726 my $authuser = $rpcenv->get_user();
727 my $vmid = $param->{vmid
};
729 # test if container exists
731 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
732 my $early_checks = sub {
734 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
735 PVE
::LXC
::Config-
>check_lock($conf);
737 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("ct:$vmid");
739 if (!$param->{purge
}) {
740 die "unable to remove CT $vmid - used in HA resources and purge parameter not set.\n"
743 # do not allow destroy if there are replication jobs without purge
744 my $repl_conf = PVE
::ReplicationConfig-
>new();
745 $repl_conf->check_for_existing_jobs($vmid);
751 $early_checks->($conf);
753 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
754 die $running_error_msg if !$param->{force
} && PVE
::LXC
::check_running
($vmid); # check early
757 # reload config after lock
758 $conf = PVE
::LXC
::Config-
>load_config($vmid);
759 my $ha_managed = $early_checks->($conf);
761 if (PVE
::LXC
::check_running
($vmid)) {
762 die $running_error_msg if !$param->{force
};
763 warn "forced to stop CT $vmid before destroying!\n";
765 PVE
::LXC
::vm_stop
($vmid, 1);
767 run_command
(['ha-manager', 'crm-command', 'stop', "ct:$vmid", '120']);
771 my $storage_cfg = cfs_read_file
("storage.cfg");
772 PVE
::LXC
::destroy_lxc_container
(
776 { lock => 'destroyed' },
777 $param->{'destroy-unreferenced-disks'},
780 PVE
::AccessControl
::remove_vm_access
($vmid);
781 PVE
::Firewall
::remove_vmfw_conf
($vmid);
782 if ($param->{purge
}) {
783 print "purging CT $vmid from related configurations..\n";
784 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
785 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
788 PVE
::HA
::Config
::delete_service_from_config
("ct:$vmid");
789 print "NOTE: removed CT $vmid from HA resource configuration.\n";
793 # only now remove the zombie config, else we can have reuse race
794 PVE
::LXC
::Config-
>destroy_config($vmid);
797 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
799 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
804 __PACKAGE__-
>register_method ({
806 path
=> '{vmid}/vncproxy',
810 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
812 description
=> "Creates a TCP VNC proxy connections.",
814 additionalProperties
=> 0,
816 node
=> get_standard_option
('pve-node'),
817 vmid
=> get_standard_option
('pve-vmid'),
821 description
=> "use websocket instead of standard VNC.",
825 description
=> "sets the width of the console in pixels.",
832 description
=> "sets the height of the console in pixels.",
840 additionalProperties
=> 0,
842 user
=> { type
=> 'string' },
843 ticket
=> { type
=> 'string' },
844 cert
=> { type
=> 'string' },
845 port
=> { type
=> 'integer' },
846 upid
=> { type
=> 'string' },
852 my $rpcenv = PVE
::RPCEnvironment
::get
();
854 my $authuser = $rpcenv->get_user();
856 my $vmid = $param->{vmid
};
857 my $node = $param->{node
};
859 my $authpath = "/vms/$vmid";
861 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
863 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
866 my ($remip, $family);
868 if ($node ne PVE
::INotify
::nodename
()) {
869 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
871 $family = PVE
::Tools
::get_host_address_family
($node);
874 my $port = PVE
::Tools
::next_vnc_port
($family);
876 # NOTE: vncterm VNC traffic is already TLS encrypted,
877 # so we select the fastest chipher here (or 'none'?)
878 my $remcmd = $remip ?
879 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
881 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
882 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
884 my $shcmd = [ '/usr/bin/dtach', '-A',
885 "/var/run/dtach/vzctlconsole$vmid",
886 '-r', 'winch', '-z', @$concmd];
891 syslog
('info', "starting lxc vnc proxy $upid\n");
895 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
896 '-timeout', $timeout, '-authpath', $authpath,
897 '-perm', 'VM.Console'];
899 if ($param->{width
}) {
900 push @$cmd, '-width', $param->{width
};
903 if ($param->{height
}) {
904 push @$cmd, '-height', $param->{height
};
907 if ($param->{websocket
}) {
908 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
909 push @$cmd, '-notls', '-listen', 'localhost';
912 push @$cmd, '-c', @$remcmd, @$shcmd;
914 run_command
($cmd, keeplocale
=> 1);
919 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
921 PVE
::Tools
::wait_for_vnc_port
($port);
932 __PACKAGE__-
>register_method ({
934 path
=> '{vmid}/termproxy',
938 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
940 description
=> "Creates a TCP proxy connection.",
942 additionalProperties
=> 0,
944 node
=> get_standard_option
('pve-node'),
945 vmid
=> get_standard_option
('pve-vmid'),
949 additionalProperties
=> 0,
951 user
=> { type
=> 'string' },
952 ticket
=> { type
=> 'string' },
953 port
=> { type
=> 'integer' },
954 upid
=> { type
=> 'string' },
960 my $rpcenv = PVE
::RPCEnvironment
::get
();
962 my $authuser = $rpcenv->get_user();
964 my $vmid = $param->{vmid
};
965 my $node = $param->{node
};
967 my $authpath = "/vms/$vmid";
969 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
971 my ($remip, $family);
973 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
974 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
976 $family = PVE
::Tools
::get_host_address_family
($node);
979 my $port = PVE
::Tools
::next_vnc_port
($family);
981 my $remcmd = $remip ?
982 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
984 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
985 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
987 my $shcmd = [ '/usr/bin/dtach', '-A',
988 "/var/run/dtach/vzctlconsole$vmid",
989 '-r', 'winch', '-z', @$concmd];
994 syslog
('info', "starting lxc termproxy $upid\n");
996 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
997 '--perm', 'VM.Console', '--'];
998 push @$cmd, @$remcmd, @$shcmd;
1000 PVE
::Tools
::run_command
($cmd);
1003 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1005 PVE
::Tools
::wait_for_vnc_port
($port);
1015 __PACKAGE__-
>register_method({
1016 name
=> 'vncwebsocket',
1017 path
=> '{vmid}/vncwebsocket',
1020 description
=> "You also need to pass a valid ticket (vncticket).",
1021 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1023 description
=> "Opens a weksocket for VNC traffic.",
1025 additionalProperties
=> 0,
1027 node
=> get_standard_option
('pve-node'),
1028 vmid
=> get_standard_option
('pve-vmid'),
1030 description
=> "Ticket from previous call to vncproxy.",
1035 description
=> "Port number returned by previous vncproxy call.",
1045 port
=> { type
=> 'string' },
1051 my $rpcenv = PVE
::RPCEnvironment
::get
();
1053 my $authuser = $rpcenv->get_user();
1055 my $authpath = "/vms/$param->{vmid}";
1057 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1059 my $port = $param->{port
};
1061 return { port
=> $port };
1064 __PACKAGE__-
>register_method ({
1065 name
=> 'spiceproxy',
1066 path
=> '{vmid}/spiceproxy',
1071 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1073 description
=> "Returns a SPICE configuration to connect to the CT.",
1075 additionalProperties
=> 0,
1077 node
=> get_standard_option
('pve-node'),
1078 vmid
=> get_standard_option
('pve-vmid'),
1079 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1082 returns
=> get_standard_option
('remote-viewer-config'),
1086 my $vmid = $param->{vmid
};
1087 my $node = $param->{node
};
1088 my $proxy = $param->{proxy
};
1090 my $authpath = "/vms/$vmid";
1091 my $permissions = 'VM.Console';
1093 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1095 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
1097 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
1099 my $shcmd = ['/usr/bin/dtach', '-A',
1100 "/var/run/dtach/vzctlconsole$vmid",
1101 '-r', 'winch', '-z', @$concmd];
1103 my $title = "CT $vmid";
1105 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1109 __PACKAGE__-
>register_method({
1110 name
=> 'remote_migrate_vm',
1111 path
=> '{vmid}/remote_migrate',
1115 description
=> "Migrate the container to another cluster. Creates a new migration task. EXPERIMENTAL feature!",
1117 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1120 additionalProperties
=> 0,
1122 node
=> get_standard_option
('pve-node'),
1123 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1124 'target-vmid' => get_standard_option
('pve-vmid', { optional
=> 1 }),
1125 'target-endpoint' => get_standard_option
('proxmox-remote', {
1126 description
=> "Remote target endpoint",
1130 description
=> "Use online/live migration.",
1135 description
=> "Use restart migration",
1140 description
=> "Timeout in seconds for shutdown for restart migration",
1146 description
=> "Delete the original CT and related data after successful migration. By default the original CT is kept on the source cluster in a stopped state.",
1150 'target-storage' => get_standard_option
('pve-targetstorage', {
1153 'target-bridge' => {
1155 description
=> "Mapping from source to target bridges. Providing only a single bridge ID maps all source bridges to that bridge. Providing the special value '1' will map each source bridge to itself.",
1156 format
=> 'bridge-pair-list',
1159 description
=> "Override I/O bandwidth limit (in KiB/s).",
1163 default => 'migrate limit from datacenter or storage config',
1169 description
=> "the task ID.",
1174 my $rpcenv = PVE
::RPCEnvironment
::get
();
1175 my $authuser = $rpcenv->get_user();
1177 my $source_vmid = extract_param
($param, 'vmid');
1178 my $target_endpoint = extract_param
($param, 'target-endpoint');
1179 my $target_vmid = extract_param
($param, 'target-vmid') // $source_vmid;
1181 my $delete = extract_param
($param, 'delete') // 0;
1183 PVE
::Cluster
::check_cfs_quorum
();
1186 my $conf = PVE
::LXC
::Config-
>load_config($source_vmid);
1187 PVE
::LXC
::Config-
>check_lock($conf);
1189 # try to detect errors early
1190 if (PVE
::LXC
::check_running
($source_vmid)) {
1191 die "can't migrate running container without --online or --restart\n"
1192 if !$param->{online
} && !$param->{restart
};
1195 raise_param_exc
({ vmid
=> "cannot migrate HA-managed CT to remote cluster" })
1196 if PVE
::HA
::Config
::vm_is_ha_managed
($source_vmid);
1198 my $remote = PVE
::JSONSchema
::parse_property_string
('proxmox-remote', $target_endpoint);
1200 # TODO: move this as helper somewhere appropriate?
1202 protocol
=> 'https',
1203 host
=> $remote->{host
},
1204 port
=> $remote->{port
} // 8006,
1205 apitoken
=> $remote->{apitoken
},
1209 if ($fp = $remote->{fingerprint
}) {
1210 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 };
1213 print "Establishing API connection with remote at '$remote->{host}'\n";
1215 my $api_client = PVE
::APIClient
::LWP-
>new(%$conn_args);
1217 if (!defined($fp)) {
1218 my $cert_info = $api_client->get("/nodes/localhost/certificates/info");
1219 foreach my $cert (@$cert_info) {
1220 my $filename = $cert->{filename
};
1221 next if $filename ne 'pveproxy-ssl.pem' && $filename ne 'pve-ssl.pem';
1222 $fp = $cert->{fingerprint
} if !$fp || $filename eq 'pveproxy-ssl.pem';
1224 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 }
1228 my $storecfg = PVE
::Storage
::config
();
1229 my $target_storage = extract_param
($param, 'target-storage');
1230 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($target_storage, 'pve-storage-id') };
1231 raise_param_exc
({ 'target-storage' => "failed to parse storage map: $@" })
1234 my $target_bridge = extract_param
($param, 'target-bridge');
1235 my $bridgemap = eval { PVE
::JSONSchema
::parse_idmap
($target_bridge, 'pve-bridge-id') };
1236 raise_param_exc
({ 'target-bridge' => "failed to parse bridge map: $@" })
1239 die "remote migration requires explicit storage mapping!\n"
1240 if $storagemap->{identity
};
1242 $param->{storagemap
} = $storagemap;
1243 $param->{bridgemap
} = $bridgemap;
1244 $param->{remote
} = {
1245 conn
=> $conn_args, # re-use fingerprint for tunnel
1246 client
=> $api_client,
1247 vmid
=> $target_vmid,
1249 $param->{migration_type
} = 'websocket';
1250 $param->{delete} = $delete if $delete;
1252 my $cluster_status = $api_client->get("/cluster/status");
1254 foreach my $entry (@$cluster_status) {
1255 next if $entry->{type
} ne 'node';
1256 if ($entry->{local}) {
1257 $target_node = $entry->{name
};
1262 die "couldn't determine endpoint's node name\n"
1263 if !defined($target_node);
1266 PVE
::LXC
::Migrate-
>migrate($target_node, $remote->{host
}, $source_vmid, $param);
1270 return PVE
::GuestHelpers
::guest_migration_lock
($source_vmid, 10, $realcmd);
1273 return $rpcenv->fork_worker('vzmigrate', $source_vmid, $authuser, $worker);
1277 __PACKAGE__-
>register_method({
1278 name
=> 'migrate_vm',
1279 path
=> '{vmid}/migrate',
1283 description
=> "Migrate the container to another node. Creates a new migration task.",
1285 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1288 additionalProperties
=> 0,
1290 node
=> get_standard_option
('pve-node'),
1291 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1292 target
=> get_standard_option
('pve-node', {
1293 description
=> "Target node.",
1294 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1296 'target-storage' => get_standard_option
('pve-targetstorage'),
1299 description
=> "Use online/live migration.",
1304 description
=> "Use restart migration",
1309 description
=> "Timeout in seconds for shutdown for restart migration",
1314 description
=> "Override I/O bandwidth limit (in KiB/s).",
1318 default => 'migrate limit from datacenter or storage config',
1324 description
=> "the task ID.",
1329 my $rpcenv = PVE
::RPCEnvironment
::get
();
1331 my $authuser = $rpcenv->get_user();
1333 my $target = extract_param
($param, 'target');
1335 my $localnode = PVE
::INotify
::nodename
();
1336 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1338 PVE
::Cluster
::check_cfs_quorum
();
1340 PVE
::Cluster
::check_node_exists
($target);
1342 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1344 my $vmid = extract_param
($param, 'vmid');
1347 PVE
::LXC
::Config-
>load_config($vmid);
1349 # try to detect errors early
1350 if (PVE
::LXC
::check_running
($vmid)) {
1351 die "can't migrate running container without --online or --restart\n"
1352 if !$param->{online
} && !$param->{restart
};
1355 if (my $targetstorage = delete $param->{'target-storage'}) {
1356 my $storecfg = PVE
::Storage
::config
();
1357 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
1358 raise_param_exc
({ 'target-storage' => "failed to parse storage map: $@" })
1361 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
1362 if !defined($storagemap->{identity
});
1364 foreach my $target_sid (values %{$storagemap->{entries
}}) {
1365 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
1368 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
1369 if $storagemap->{default};
1371 $param->{storagemap
} = $storagemap;
1374 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1379 my $service = "ct:$vmid";
1381 my $cmd = ['ha-manager', 'migrate', $service, $target];
1383 print "Requesting HA migration for CT $vmid to node $target\n";
1385 PVE
::Tools
::run_command
($cmd);
1390 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1395 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1399 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1402 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1406 __PACKAGE__-
>register_method({
1407 name
=> 'vm_feature',
1408 path
=> '{vmid}/feature',
1412 description
=> "Check if feature for virtual machine is available.",
1414 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1417 additionalProperties
=> 0,
1419 node
=> get_standard_option
('pve-node'),
1420 vmid
=> get_standard_option
('pve-vmid'),
1422 description
=> "Feature to check.",
1424 enum
=> [ 'snapshot', 'clone', 'copy' ],
1426 snapname
=> get_standard_option
('pve-snapshot-name', {
1434 hasFeature
=> { type
=> 'boolean' },
1437 #items => { type => 'string' },
1444 my $node = extract_param
($param, 'node');
1446 my $vmid = extract_param
($param, 'vmid');
1448 my $snapname = extract_param
($param, 'snapname');
1450 my $feature = extract_param
($param, 'feature');
1452 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1455 my $snap = $conf->{snapshots
}->{$snapname};
1456 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1459 my $storage_cfg = PVE
::Storage
::config
();
1460 #Maybe include later
1461 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1462 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1465 hasFeature
=> $hasFeature,
1466 #nodes => [ keys %$nodelist ],
1470 __PACKAGE__-
>register_method({
1472 path
=> '{vmid}/template',
1476 description
=> "Create a Template.",
1478 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1479 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1482 additionalProperties
=> 0,
1484 node
=> get_standard_option
('pve-node'),
1485 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1488 returns
=> { type
=> 'null'},
1492 my $rpcenv = PVE
::RPCEnvironment
::get
();
1494 my $authuser = $rpcenv->get_user();
1496 my $node = extract_param
($param, 'node');
1498 my $vmid = extract_param
($param, 'vmid');
1500 my $updatefn = sub {
1502 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1503 PVE
::LXC
::Config-
>check_lock($conf);
1505 die "unable to create template, because CT contains snapshots\n"
1506 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1508 die "you can't convert a template to a template\n"
1509 if PVE
::LXC
::Config-
>is_template($conf);
1511 die "you can't convert a CT to template if the CT is running\n"
1512 if PVE
::LXC
::check_running
($vmid);
1515 PVE
::LXC
::template_create
($vmid, $conf);
1517 $conf->{template
} = 1;
1519 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1520 # and remove lxc config
1521 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1524 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1527 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1532 __PACKAGE__-
>register_method({
1534 path
=> '{vmid}/clone',
1538 description
=> "Create a container clone/copy",
1540 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1541 "and 'VM.Allocate' permissions " .
1542 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1543 "'Datastore.AllocateSpace' on any used storage, and 'SDN.Use' on any bridge.",
1546 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1548 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1549 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1554 additionalProperties
=> 0,
1556 node
=> get_standard_option
('pve-node'),
1557 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1558 newid
=> get_standard_option
('pve-vmid', {
1559 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1560 description
=> 'VMID for the clone.' }),
1563 type
=> 'string', format
=> 'dns-name',
1564 description
=> "Set a hostname for the new CT.",
1569 description
=> "Description for the new CT.",
1573 type
=> 'string', format
=> 'pve-poolid',
1574 description
=> "Add the new CT to the specified pool.",
1576 snapname
=> get_standard_option
('pve-snapshot-name', {
1579 storage
=> get_standard_option
('pve-storage-id', {
1580 description
=> "Target storage for full clone.",
1586 description
=> "Create a full copy of all disks. This is always done when " .
1587 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1589 target
=> get_standard_option
('pve-node', {
1590 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1594 description
=> "Override I/O bandwidth limit (in KiB/s).",
1598 default => 'clone limit from datacenter or storage config',
1608 my $rpcenv = PVE
::RPCEnvironment
::get
();
1609 my $authuser = $rpcenv->get_user();
1611 my $node = extract_param
($param, 'node');
1612 my $vmid = extract_param
($param, 'vmid');
1613 my $newid = extract_param
($param, 'newid');
1614 my $pool = extract_param
($param, 'pool');
1615 if (defined($pool)) {
1616 $rpcenv->check_pool_exist($pool);
1618 my $snapname = extract_param
($param, 'snapname');
1619 my $storage = extract_param
($param, 'storage');
1620 my $target = extract_param
($param, 'target');
1621 my $localnode = PVE
::INotify
::nodename
();
1623 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1625 PVE
::Cluster
::check_node_exists
($target) if $target;
1627 my $storecfg = PVE
::Storage
::config
();
1630 # check if storage is enabled on local node
1631 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1633 # check if storage is available on target node
1634 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
1635 # clone only works if target storage is shared
1636 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1637 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1641 PVE
::Cluster
::check_cfs_quorum
();
1644 my $mountpoints = {};
1649 my $lock_and_reload = sub {
1650 my ($vmid, $code) = @_;
1651 return PVE
::LXC
::Config-
>lock_config($vmid, sub {
1652 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1653 die "Lost 'create' config lock, aborting.\n"
1654 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1656 return $code->($conf);
1660 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1663 PVE
::LXC
::Config-
>create_and_lock_config($newid, 0);
1666 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1667 warn "Failed to remove source CT config lock - $@\n" if $@;
1673 $running = PVE
::LXC
::check_running
($vmid) || 0;
1675 my $full = extract_param
($param, 'full');
1676 if (!defined($full)) {
1677 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1680 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1682 die "parameter 'storage' not allowed for linked clones\n"
1683 if defined($storage) && !$full;
1685 die "snapshot '$snapname' does not exist\n"
1686 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1688 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1691 for my $opt (sort keys %$src_conf) {
1692 next if $opt =~ m/^unused\d+$/;
1694 my $value = $src_conf->{$opt};
1696 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1697 my $mp = PVE
::LXC
::Config-
>parse_volume($opt, $value);
1699 if ($mp->{type
} eq 'volume') {
1700 my $volid = $mp->{volume
};
1702 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1703 $sid = $storage if defined($storage);
1704 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1705 if (!$scfg->{shared
}) {
1707 warn "found non-shared volume: $volid\n" if $target;
1710 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1713 die "Cannot do full clones on a running container without snapshots\n"
1714 if $running && !defined($snapname);
1715 $fullclone->{$opt} = 1;
1717 # not full means clone instead of copy
1718 die "Linked clone feature for '$volid' is not available\n"
1719 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
1722 $mountpoints->{$opt} = $mp;
1723 push @$vollist, $volid;
1726 # TODO: allow bind mounts?
1727 die "unable to clone mountpoint '$opt' (type $mp->{type})\n";
1729 } elsif ($opt =~ m/^net(\d+)$/) {
1730 # always change MAC! address
1731 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1732 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1733 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1734 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1736 PVE
::LXC
::check_bridge_access
($rpcenv, $authuser, $newconf->{$opt});
1738 # copy everything else
1739 $newconf->{$opt} = $value;
1742 die "can't clone CT to node '$target' (CT uses local storage)\n"
1743 if $target && !$sharedvm;
1745 # Replace the 'disk' lock with a 'create' lock.
1746 $newconf->{lock} = 'create';
1748 # delete all snapshot related config options
1749 delete $newconf->@{qw(snapshots parent snaptime snapstate)};
1751 delete $newconf->{pending
};
1752 delete $newconf->{template
};
1754 $newconf->{hostname
} = $param->{hostname
} if $param->{hostname
};
1755 $newconf->{description
} = $param->{description
} if $param->{description
};
1757 $lock_and_reload->($newid, sub {
1758 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1762 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1763 warn "Failed to remove source CT config lock - $@\n" if $@;
1766 $lock_and_reload->($newid, sub {
1767 PVE
::LXC
::Config-
>destroy_config($newid);
1768 PVE
::Firewall
::remove_vmfw_conf
($newid);
1771 warn "Failed to remove target CT config - $@\n" if $@;
1776 my $update_conf = sub {
1777 my ($key, $value) = @_;
1778 return $lock_and_reload->($newid, sub {
1780 $conf->{$key} = $value;
1781 PVE
::LXC
::Config-
>write_config($newid, $conf);
1788 my $newvollist = [];
1790 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1791 die "unexpected state change\n" if $verify_running != $running;
1797 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1799 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1800 my $bwlimit = extract_param
($param, 'bwlimit');
1802 foreach my $opt (keys %$mountpoints) {
1803 my $mp = $mountpoints->{$opt};
1804 my $volid = $mp->{volume
};
1807 if ($fullclone->{$opt}) {
1808 print "create full clone of mountpoint $opt ($volid)\n";
1809 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1810 my $target_storage = $storage // $source_storage;
1811 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1812 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1814 print "create linked clone of mount point $opt ($volid)\n";
1815 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1818 push @$newvollist, $newvolid;
1819 $mp->{volume
} = $newvolid;
1821 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1824 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1826 $lock_and_reload->($newid, sub {
1828 my $rootdir = PVE
::LXC
::mount_all
($newid, $storecfg, $conf, 1);
1830 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1831 $lxc_setup->post_clone_hook($conf);
1834 eval { PVE
::LXC
::umount_all
($newid, $storecfg, $conf, 1); };
1844 # Unlock the source config in any case:
1845 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1849 # Now cleanup the config & disks:
1850 sleep 1; # some storages like rbd need to wait before release volume - really?
1852 foreach my $volid (@$newvollist) {
1853 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1858 $lock_and_reload->($newid, sub {
1859 PVE
::LXC
::Config-
>destroy_config($newid);
1860 PVE
::Firewall
::remove_vmfw_conf
($newid);
1863 warn "Failed to remove target CT config - $@\n" if $@;
1865 die "clone failed: $err";
1868 $lock_and_reload->($newid, sub {
1869 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1872 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1873 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1874 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1876 PVE
::LXC
::Config-
>move_config_to_node($newid, $target);
1883 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1887 __PACKAGE__-
>register_method({
1888 name
=> 'resize_vm',
1889 path
=> '{vmid}/resize',
1893 description
=> "Resize a container mount point.",
1895 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1898 additionalProperties
=> 0,
1900 node
=> get_standard_option
('pve-node'),
1901 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1904 description
=> "The disk you want to resize.",
1905 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys()],
1909 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1910 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.",
1914 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1922 description
=> "the task ID.",
1927 my $rpcenv = PVE
::RPCEnvironment
::get
();
1929 my $authuser = $rpcenv->get_user();
1931 my $node = extract_param
($param, 'node');
1933 my $vmid = extract_param
($param, 'vmid');
1935 my $digest = extract_param
($param, 'digest');
1937 my $sizestr = extract_param
($param, 'size');
1938 my $ext = ($sizestr =~ s/^\+//);
1939 my $request_size = PVE
::JSONSchema
::parse_size
($sizestr);
1940 die "invalid size string" if !defined($request_size);
1942 die "no options specified\n" if !scalar(keys %$param);
1944 my $storage_cfg = cfs_read_file
("storage.cfg");
1946 my $load_and_check = sub {
1947 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1948 PVE
::LXC
::Config-
>check_lock($conf);
1950 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $conf, $param, [], $conf->{unprivileged
});
1952 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1954 my $disk = $param->{disk
};
1955 my $mp = PVE
::LXC
::Config-
>parse_volume($disk, $conf->{$disk});
1957 my $volid = $mp->{volume
};
1959 my (undef, undef, $owner, undef, undef, undef, $format) =
1960 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1962 die "can't resize mount point owned by another container ($owner)"
1965 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1967 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1969 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1971 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1973 die "Could not determine current size of volume '$volid'\n" if !defined($size);
1975 my $newsize = $ext ?
$size + $request_size : $request_size;
1976 $newsize = int($newsize);
1978 die "unable to shrink disk size\n" if $newsize < $size;
1980 die "disk is already at specified size\n" if $size == $newsize;
1982 return ($conf, $disk, $mp, $volid, $format, $newsize);
1986 my ($conf, $disk, $mp, $volid, $format, $newsize) = $load_and_check->();
1988 my $running = PVE
::LXC
::check_running
($vmid);
1990 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1992 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1993 # we pass 0 here (parameter only makes sense for qemu)
1994 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1996 $mp->{size
} = $newsize;
1997 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1999 PVE
::LXC
::Config-
>write_config($vmid, $conf);
2001 if ($format eq 'raw') {
2002 # we need to ensure that the volume is mapped, if not needed this is a NOP
2003 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
2004 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
2008 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
2009 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
2010 die "internal error: CT running but mount point not attached to a loop device"
2012 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
2014 # In order for resize2fs to know that we need online-resizing a mountpoint needs
2015 # to be visible to it in its namespace.
2016 # To not interfere with the rest of the system we unshare the current mount namespace,
2017 # mount over /tmp and then run resize2fs.
2019 # interestingly we don't need to e2fsck on mounted systems...
2020 my $quoted = PVE
::Tools
::shellquote
($path);
2021 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
2023 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
2025 warn "Failed to update the container's filesystem: $@\n" if $@;
2028 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
2029 PVE
::Tools
::run_command
(['resize2fs', $path]);
2031 warn "Failed to update the container's filesystem: $@\n" if $@;
2033 # always un-map if not running, this is a NOP if not needed
2034 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
2040 PVE
::LXC
::Config-
>lock_config($vmid, $code);;
2043 $load_and_check->(); # early checks before forking+locking
2045 return $rpcenv->fork_worker('resize', $vmid, $authuser, $worker);
2048 __PACKAGE__-
>register_method({
2049 name
=> 'move_volume',
2050 path
=> '{vmid}/move_volume',
2054 description
=> "Move a rootfs-/mp-volume to a different storage or to a different container.",
2056 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2057 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
2058 "a volume to another container, you need the permissions on the ".
2059 "target container as well.",
2060 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2063 additionalProperties
=> 0,
2065 node
=> get_standard_option
('pve-node'),
2066 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
2067 'target-vmid' => get_standard_option
('pve-vmid', {
2068 completion
=> \
&PVE
::LXC
::complete_ctid
,
2073 #TODO: check how to handle unused mount points as the mp parameter is not configured
2074 enum
=> [ PVE
::LXC
::Config-
>valid_volume_keys_with_unused() ],
2075 description
=> "Volume which will be moved.",
2077 storage
=> get_standard_option
('pve-storage-id', {
2078 description
=> "Target Storage.",
2079 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2084 description
=> "Delete the original volume after successful copy. By default the " .
2085 "original is kept as an unused volume entry.",
2091 description
=> 'Prevent changes if current configuration file has different SHA1 " .
2092 "digest. This can be used to prevent concurrent modifications.',
2097 description
=> "Override I/O bandwidth limit (in KiB/s).",
2101 default => 'clone limit from datacenter or storage config',
2103 'target-volume' => {
2105 description
=> "The config key the volume will be moved to. Default is the " .
2106 "source volume key.",
2107 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys_with_unused()],
2110 'target-digest' => {
2112 description
=> 'Prevent changes if current configuration file of the target " .
2113 "container has a different SHA1 digest. This can be used to prevent " .
2114 "concurrent modifications.',
2126 my $rpcenv = PVE
::RPCEnvironment
::get
();
2128 my $authuser = $rpcenv->get_user();
2130 my $vmid = extract_param
($param, 'vmid');
2132 my $target_vmid = extract_param
($param, 'target-vmid');
2134 my $storage = extract_param
($param, 'storage');
2136 my $mpkey = extract_param
($param, 'volume');
2138 my $target_mpkey = extract_param
($param, 'target-volume') // $mpkey;
2140 my $digest = extract_param
($param, 'digest');
2142 my $target_digest = extract_param
($param, 'target-digest');
2144 my $lockname = 'disk';
2146 my ($mpdata, $old_volid);
2148 die "either set storage or target-vmid, but not both\n"
2149 if $storage && $target_vmid;
2151 my $storecfg = PVE
::Storage
::config
();
2153 my $move_to_storage_checks = sub {
2154 PVE
::LXC
::Config-
>lock_config($vmid, sub {
2155 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
2156 PVE
::LXC
::Config-
>check_lock($conf);
2158 die "cannot move volumes of a running container\n"
2159 if PVE
::LXC
::check_running
($vmid);
2161 if ($mpkey =~ m/^unused\d+$/) {
2162 die "cannot move volume '$mpkey', only configured volumes can be moved to ".
2163 "another storage\n";
2166 $mpdata = PVE
::LXC
::Config-
>parse_volume($mpkey, $conf->{$mpkey});
2167 $old_volid = $mpdata->{volume
};
2169 die "you can't move a volume with snapshots and delete the source\n"
2170 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
2172 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
2174 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
2178 my $storage_realcmd = sub {
2180 PVE
::Cluster
::log_msg
(
2183 "move volume CT $vmid: move --volume $mpkey --storage $storage"
2186 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
2187 my $storage_cfg = PVE
::Storage
::config
();
2192 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
2193 my $bwlimit = extract_param
($param, 'bwlimit');
2194 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
2195 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
2197 [$source_storage, $storage],
2200 $new_volid = PVE
::LXC
::copy_volume
(
2209 if (PVE
::LXC
::Config-
>is_template($conf)) {
2210 PVE
::Storage
::activate_volumes
($storage_cfg, [ $new_volid ]);
2211 my $template_volid = PVE
::Storage
::vdisk_create_base
($storage_cfg, $new_volid);
2212 $mpdata->{volume
} = $template_volid;
2214 $mpdata->{volume
} = $new_volid;
2217 PVE
::LXC
::Config-
>lock_config($vmid, sub {
2218 my $digest = $conf->{digest
};
2219 $conf = PVE
::LXC
::Config-
>load_config($vmid);
2220 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
2222 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint(
2227 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2229 PVE
::LXC
::Config-
>write_config($vmid, $conf);
2233 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2234 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
2240 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
2241 if defined($new_volid);
2247 my $deactivated = 0;
2249 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
2254 if ($param->{delete}) {
2258 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
2264 PVE
::LXC
::Config-
>lock_config($vmid, sub {
2265 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
2266 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid);
2267 PVE
::LXC
::Config-
>write_config($vmid, $conf);
2273 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
2278 my $load_and_check_reassign_configs = sub {
2279 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
2281 die "Cannot move to/from 'rootfs'\n" if $mpkey eq "rootfs" || $target_mpkey eq "rootfs";
2283 if ($mpkey =~ m/^unused\d+$/ && $target_mpkey !~ m/^unused\d+$/) {
2284 die "Moving an unused volume to a used one is not possible\n";
2286 die "could not find CT ${vmid}\n" if !exists($vmlist->{$vmid});
2287 die "could not find CT ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
2289 my $source_node = $vmlist->{$vmid}->{node
};
2290 my $target_node = $vmlist->{$target_vmid}->{node
};
2292 die "Both containers need to be on the same node ($source_node != $target_node)\n"
2293 if $source_node ne $target_node;
2295 my $source_conf = PVE
::LXC
::Config-
>load_config($vmid);
2296 PVE
::LXC
::Config-
>check_lock($source_conf);
2298 if ($target_vmid eq $vmid) {
2299 $target_conf = $source_conf;
2301 $target_conf = PVE
::LXC
::Config-
>load_config($target_vmid);
2302 PVE
::LXC
::Config-
>check_lock($target_conf);
2305 die "Can't move volumes from or to template CT\n"
2306 if ($source_conf->{template
} || $target_conf->{template
});
2309 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
2310 die "Container ${vmid}: $@" if $@;
2313 if ($target_digest) {
2314 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
2315 die "Container ${target_vmid}: $@" if $@;
2318 die "volume '${mpkey}' for container '$vmid' does not exist\n"
2319 if !defined($source_conf->{$mpkey});
2321 die "Target volume key '${target_mpkey}' is already in use for container '$target_vmid'\n"
2322 if exists $target_conf->{$target_mpkey};
2324 my $drive = PVE
::LXC
::Config-
>parse_volume($mpkey, $source_conf->{$mpkey});
2325 my $source_volid = $drive->{volume
} or die "Volume '${mpkey}' has no associated image\n";
2326 die "Cannot move volume used by a snapshot to another container\n"
2327 if PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($source_conf, $source_volid);
2328 die "Storage does not support moving of this disk to another container\n"
2329 if !PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid);
2330 die "Cannot move a bindmount or device mount to another container\n"
2331 if $drive->{type
} ne "volume";
2332 die "Cannot move in-use volume while the source CT is running - detach or shutdown first\n"
2333 if PVE
::LXC
::check_running
($vmid) && $mpkey !~ m/^unused\d+$/;
2335 my $repl_conf = PVE
::ReplicationConfig-
>new();
2336 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
2337 my ($storeid, undef) = PVE
::Storage
::parse_volume_id
($source_volid);
2338 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
2340 die "Cannot move volume on storage '$storeid' to a replicated container - missing replication support\n"
2341 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
2344 return ($source_conf, $target_conf, $drive);
2347 my $logfunc = sub { print STDERR
"$_[0]\n"; };
2349 my $volume_reassignfn = sub {
2350 return PVE
::LXC
::Config-
>lock_config($vmid, sub {
2351 return PVE
::LXC
::Config-
>lock_config($target_vmid, sub {
2352 my ($source_conf, $target_conf, $drive) = $load_and_check_reassign_configs->();
2353 my $source_volid = $drive->{volume
};
2355 my $target_unused = $target_mpkey =~ m/^unused\d+$/;
2357 print "moving volume '$mpkey' from container '$vmid' to '$target_vmid'\n";
2359 my ($storage, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
2361 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
2363 my $new_volid = PVE
::Storage
::rename_volume
(
2369 $drive->{volume
} = $new_volid;
2371 delete $source_conf->{$mpkey};
2372 print "removing volume '${mpkey}' from container '${vmid}' config\n";
2373 PVE
::LXC
::Config-
>write_config($vmid, $source_conf);
2376 if ($target_unused) {
2377 $drive_string = $new_volid;
2379 $drive_string = PVE
::LXC
::Config-
>print_volume($target_mpkey, $drive);
2382 if ($target_unused) {
2383 $target_conf->{$target_mpkey} = $drive_string;
2385 my $running = PVE
::LXC
::check_running
($target_vmid);
2386 my $param = { $target_mpkey => $drive_string };
2387 my $errors = PVE
::LXC
::Config-
>update_pct_config(
2393 $rpcenv->warn($errors->{$_}) for keys $errors->%*;
2396 PVE
::LXC
::Config-
>write_config($target_vmid, $target_conf);
2397 $target_conf = PVE
::LXC
::Config-
>load_config($target_vmid);
2399 PVE
::LXC
::update_lxc_config
($target_vmid, $target_conf) if !$target_unused;
2400 print "target container '$target_vmid' updated with '$target_mpkey'\n";
2402 # remove possible replication snapshots
2403 if (PVE
::Storage
::volume_has_feature
($storecfg,'replicate', $source_volid)) {
2405 PVE
::Replication
::prepare
(
2415 $rpcenv->warn("Failed to remove replication snapshots on volume ".
2416 "'${target_mpkey}'. Manual cleanup could be necessary. " .
2424 if ($target_vmid && $storage) {
2425 my $msg = "either set 'storage' or 'target-vmid', but not both";
2426 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
2427 } elsif ($target_vmid) {
2428 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
2429 if $authuser ne 'root@pam';
2431 my (undef, undef, $drive) = $load_and_check_reassign_configs->();
2432 my $storeid = PVE
::Storage
::parse_volume_id
($drive->{volume
});
2433 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2434 return $rpcenv->fork_worker(
2436 "${vmid}-${mpkey}>${target_vmid}-${target_mpkey}",
2440 } elsif ($storage) {
2441 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
2442 &$move_to_storage_checks();
2444 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $storage_realcmd);
2447 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
2453 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
2454 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
2458 __PACKAGE__-
>register_method({
2459 name
=> 'vm_pending',
2460 path
=> '{vmid}/pending',
2463 description
=> 'Get container configuration, including pending changes.',
2465 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2468 additionalProperties
=> 0,
2470 node
=> get_standard_option
('pve-node'),
2471 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
2480 description
=> 'Configuration option name.',
2484 description
=> 'Current value.',
2489 description
=> 'Pending value.',
2494 description
=> "Indicates a pending delete request if present and not 0.",
2506 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
2508 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
2510 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
2513 __PACKAGE__-
>register_method({
2515 path
=> '{vmid}/interfaces',
2519 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2521 description
=> 'Get IP addresses of the specified container interface.',
2523 additionalProperties
=> 0,
2525 node
=> get_standard_option
('pve-node'),
2526 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
2536 description
=> 'The name of the interface',
2541 description
=> 'The MAC address of the interface',
2546 description
=> 'The IPv4 address of the interface',
2551 description
=> 'The IPv6 address of the interface',
2560 return PVE
::LXC
::get_interfaces
($param->{vmid
});
2563 __PACKAGE__-
>register_method({
2565 path
=> '{vmid}/mtunnel',
2568 description
=> 'Migration tunnel endpoint - only for internal use by CT migration.',
2572 ['perm', '/vms/{vmid}', [ 'VM.Allocate' ]],
2573 ['perm', '/', [ 'Sys.Incoming' ]],
2575 description
=> "You need 'VM.Allocate' permissions on '/vms/{vmid}' and Sys.Incoming" .
2576 " on '/'. Further permission checks happen during the actual migration.",
2579 additionalProperties
=> 0,
2581 node
=> get_standard_option
('pve-node'),
2582 vmid
=> get_standard_option
('pve-vmid'),
2585 format
=> 'pve-storage-id-list',
2587 description
=> 'List of storages to check permission and availability. Will be checked again for all actually used storages during migration.',
2591 format
=> 'pve-bridge-id-list',
2593 description
=> 'List of network bridges to check availability. Will be checked again for actually used bridges during migration.',
2598 additionalProperties
=> 0,
2600 upid
=> { type
=> 'string' },
2601 ticket
=> { type
=> 'string' },
2602 socket => { type
=> 'string' },
2608 my $rpcenv = PVE
::RPCEnvironment
::get
();
2609 my $authuser = $rpcenv->get_user();
2611 my $node = extract_param
($param, 'node');
2612 my $vmid = extract_param
($param, 'vmid');
2614 my $storages = extract_param
($param, 'storages');
2615 my $bridges = extract_param
($param, 'bridges');
2617 my $nodename = PVE
::INotify
::nodename
();
2619 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
2620 if $node ne 'localhost' && $node ne $nodename;
2624 my $storecfg = PVE
::Storage
::config
();
2625 foreach my $storeid (PVE
::Tools
::split_list
($storages)) {
2626 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storeid, $node);
2629 foreach my $bridge (PVE
::Tools
::split_list
($bridges)) {
2630 PVE
::Network
::read_bridge_mtu
($bridge);
2633 PVE
::Cluster
::check_cfs_quorum
();
2635 my $socket_addr = "/run/pve/ct-$vmid.mtunnel";
2637 my $lock = 'create';
2638 eval { PVE
::LXC
::Config-
>create_and_lock_config($vmid, 0, $lock); };
2640 raise_param_exc
({ vmid
=> "unable to create empty CT config - $@"})
2645 storecfg
=> PVE
::Storage
::config
(),
2650 my $run_locked = sub {
2651 my ($code, $params) = @_;
2652 return PVE
::LXC
::Config-
>lock_config($state->{vmid
}, sub {
2653 my $conf = PVE
::LXC
::Config-
>load_config($state->{vmid
});
2655 $state->{conf
} = $conf;
2657 die "Encountered wrong lock - aborting mtunnel command handling.\n"
2658 if $state->{lock} && !PVE
::LXC
::Config-
>has_lock($conf, $state->{lock});
2660 return $code->($params);
2668 description
=> 'Full CT config, adapted for target cluster/node',
2670 'firewall-config' => {
2672 description
=> 'CT firewall config',
2679 description
=> 'socket path for which the ticket should be valid. must be known to current mtunnel instance.',
2685 description
=> 'remove CT config and volumes, aborting migration',
2689 'disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'disk-import'},
2690 'query-disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'query-disk-import'},
2691 bwlimit
=> $PVE::StorageTunnel
::cmd_schema-
>{bwlimit
},
2694 my $cmd_handlers = {
2696 # compared against other end's version
2697 # bump/reset for breaking changes
2698 # bump/bump for opt-in changes
2700 api
=> $PVE::LXC
::Migrate
::WS_TUNNEL_VERSION
,
2707 # parse and write out VM FW config if given
2708 if (my $fw_conf = $params->{'firewall-config'}) {
2709 my ($path, $fh) = PVE
::Tools
::tempfile_contents
($fw_conf, 700);
2716 ipset_comments
=> {},
2718 my $cluster_fw_conf = PVE
::Firewall
::load_clusterfw_conf
();
2720 # TODO: add flag for strict parsing?
2721 # TODO: add import sub that does all this given raw content?
2722 my $vmfw_conf = PVE
::Firewall
::generic_fw_config_parser
($path, $cluster_fw_conf, $empty_conf, 'vm');
2723 $vmfw_conf->{vmid
} = $state->{vmid
};
2724 PVE
::Firewall
::save_vmfw_conf
($state->{vmid
}, $vmfw_conf);
2726 $state->{cleanup
}->{fw
} = 1;
2729 my $conf_fn = "incoming/lxc/$state->{vmid}.conf";
2730 my $new_conf = PVE
::LXC
::Config
::parse_pct_config
($conf_fn, $params->{conf
}, 1);
2731 delete $new_conf->{lock};
2732 delete $new_conf->{digest
};
2734 my $unprivileged = delete $new_conf->{unprivileged
};
2735 my $arch = delete $new_conf->{arch
};
2737 # TODO handle properly?
2738 delete $new_conf->{snapshots
};
2739 delete $new_conf->{parent
};
2740 delete $new_conf->{pending
};
2741 delete $new_conf->{lxc
};
2743 PVE
::LXC
::Config-
>remove_lock($state->{vmid
}, 'create');
2747 unprivileged
=> $unprivileged,
2750 PVE
::LXC
::check_ct_modify_config_perm
(
2760 my $errors = PVE
::LXC
::Config-
>update_pct_config(
2768 raise_param_exc
($errors) if scalar(keys %$errors);
2769 PVE
::LXC
::Config-
>write_config($state->{vmid
}, $conf);
2770 PVE
::LXC
::update_lxc_config
($vmid, $conf);
2773 # revert to locked previous config
2774 my $conf = PVE
::LXC
::Config-
>load_config($state->{vmid
});
2775 $conf->{lock} = 'create';
2776 PVE
::LXC
::Config-
>write_config($state->{vmid
}, $conf);
2781 my $conf = PVE
::LXC
::Config-
>load_config($state->{vmid
});
2782 $conf->{lock} = 'migrate';
2783 PVE
::LXC
::Config-
>write_config($state->{vmid
}, $conf);
2785 $state->{lock} = 'migrate';
2791 return PVE
::StorageTunnel
::handle_bwlimit
($params);
2793 'disk-import' => sub {
2796 $check_storage_access_migrate->(
2804 $params->{unix
} = "/run/pve/ct-$state->{vmid}.storage";
2806 return PVE
::StorageTunnel
::handle_disk_import
($state, $params);
2808 'query-disk-import' => sub {
2811 return PVE
::StorageTunnel
::handle_query_disk_import
($state, $params);
2814 PVE
::LXC
::Config-
>remove_lock($state->{vmid
}, $state->{lock});
2815 delete $state->{lock};
2828 PVE
::LXC
::vm_stop
($state->{vmid
}, 1, 10, 1);
2834 my $path = $params->{path
};
2836 die "Not allowed to generate ticket for unknown socket '$path'\n"
2837 if !defined($state->{sockets
}->{$path});
2839 return { ticket
=> PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$path") };
2844 if ($params->{cleanup
}) {
2845 if ($state->{cleanup
}->{fw
}) {
2846 PVE
::Firewall
::remove_vmfw_conf
($state->{vmid
});
2849 for my $volid (keys $state->{cleanup
}->{volumes
}->%*) {
2850 print "freeing volume '$volid' as part of cleanup\n";
2851 eval { PVE
::Storage
::vdisk_free
($state->{storecfg
}, $volid) };
2855 PVE
::LXC
::destroy_lxc_container
(
2864 print "switching to exit-mode, waiting for client to disconnect\n";
2871 my $socket_addr = "/run/pve/ct-$state->{vmid}.mtunnel";
2872 unlink $socket_addr;
2874 $state->{socket} = IO
::Socket
::UNIX-
>new(
2875 Type
=> SOCK_STREAM
(),
2876 Local
=> $socket_addr,
2880 $state->{socket_uid
} = getpwnam('www-data')
2881 or die "Failed to resolve user 'www-data' to numeric UID\n";
2882 chown $state->{socket_uid
}, -1, $socket_addr;
2885 print "mtunnel started\n";
2887 my $conn = eval { PVE
::Tools
::run_with_timeout
(300, sub { $state->{socket}->accept() }) };
2889 warn "Failed to accept tunnel connection - $@\n";
2891 warn "Removing tunnel socket..\n";
2892 unlink $state->{socket};
2894 warn "Removing temporary VM config..\n";
2896 PVE
::LXC
::destroy_config
($state->{vmid
});
2899 die "Exiting mtunnel\n";
2902 $state->{conn
} = $conn;
2904 my $reply_err = sub {
2907 my $reply = JSON
::encode_json
({
2908 success
=> JSON
::false
,
2911 $conn->print("$reply\n");
2915 my $reply_ok = sub {
2918 $res->{success
} = JSON
::true
;
2919 my $reply = JSON
::encode_json
($res);
2920 $conn->print("$reply\n");
2924 while (my $line = <$conn>) {
2927 # untaint, we validate below if needed
2928 ($line) = $line =~ /^(.*)$/;
2929 my $parsed = eval { JSON
::decode_json
($line) };
2931 $reply_err->("failed to parse command - $@");
2935 my $cmd = delete $parsed->{cmd
};
2936 if (!defined($cmd)) {
2937 $reply_err->("'cmd' missing");
2938 } elsif ($state->{exit}) {
2939 $reply_err->("tunnel is in exit-mode, processing '$cmd' cmd not possible");
2941 } elsif (my $handler = $cmd_handlers->{$cmd}) {
2942 print "received command '$cmd'\n";
2944 if ($cmd_desc->{$cmd}) {
2945 PVE
::JSONSchema
::validate
($parsed, $cmd_desc->{$cmd});
2949 my $res = $run_locked->($handler, $parsed);
2952 $reply_err->("failed to handle '$cmd' command - $@")
2955 $reply_err->("unknown command '$cmd' given");
2959 if ($state->{exit}) {
2960 print "mtunnel exited\n";
2962 die "mtunnel exited unexpectedly\n";
2966 my $ticket = PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$socket_addr");
2967 my $upid = $rpcenv->fork_worker('vzmtunnel', $vmid, $authuser, $realcmd);
2972 socket => $socket_addr,
2976 __PACKAGE__-
>register_method({
2977 name
=> 'mtunnelwebsocket',
2978 path
=> '{vmid}/mtunnelwebsocket',
2981 description
=> "You need to pass a ticket valid for the selected socket. Tickets can be created via the mtunnel API call, which will check permissions accordingly.",
2982 user
=> 'all', # check inside
2984 description
=> 'Migration tunnel endpoint for websocket upgrade - only for internal use by VM migration.',
2986 additionalProperties
=> 0,
2988 node
=> get_standard_option
('pve-node'),
2989 vmid
=> get_standard_option
('pve-vmid'),
2992 description
=> "unix socket to forward to",
2996 description
=> "ticket return by initial 'mtunnel' API call, or retrieved via 'ticket' tunnel command",
3003 port
=> { type
=> 'string', optional
=> 1 },
3004 socket => { type
=> 'string', optional
=> 1 },
3010 my $rpcenv = PVE
::RPCEnvironment
::get
();
3011 my $authuser = $rpcenv->get_user();
3013 my $nodename = PVE
::INotify
::nodename
();
3014 my $node = extract_param
($param, 'node');
3016 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
3017 if $node ne 'localhost' && $node ne $nodename;
3019 my $vmid = $param->{vmid
};
3021 PVE
::LXC
::Config-
>load_config($vmid);
3023 my $socket = $param->{socket};
3024 PVE
::AccessControl
::verify_tunnel_ticket
($param->{ticket
}, $authuser, "/socket/$socket");
3026 return { socket => $socket };