1 package PVE
::API2
::LXC
;
7 use Socket
qw(SOCK_STREAM);
10 use PVE
::Tools
qw(extract_param run_command);
11 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
13 use PVE
::Cluster
qw(cfs_read_file);
15 use PVE
::DataCenterConfig
;
16 use PVE
::AccessControl
;
20 use PVE
::RPCEnvironment
;
21 use PVE
::ReplicationConfig
;
24 use PVE
::LXC
::Migrate
;
25 use PVE
::GuestHelpers
;
26 use PVE
::VZDump
::Plugin
;
27 use PVE
::API2
::LXC
::Config
;
28 use PVE
::API2
::LXC
::Status
;
29 use PVE
::API2
::LXC
::Snapshot
;
30 use PVE
::JSONSchema
qw(get_standard_option);
31 use base
qw(PVE::RESTHandler);
34 if (!$ENV{PVE_GENERATING_DOCS
}) {
35 require PVE
::HA
::Env
::PVE2
;
36 import PVE
::HA
::Env
::PVE2
;
37 require PVE
::HA
::Config
;
38 import PVE
::HA
::Config
;
42 my $check_storage_access_migrate = sub {
43 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
45 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $node);
47 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
49 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
50 die "storage '$storage' does not support CT rootdirs\n"
51 if !$scfg->{content
}->{rootdir
};
54 __PACKAGE__-
>register_method ({
55 subclass
=> "PVE::API2::LXC::Config",
56 path
=> '{vmid}/config',
59 __PACKAGE__-
>register_method ({
60 subclass
=> "PVE::API2::LXC::Status",
61 path
=> '{vmid}/status',
64 __PACKAGE__-
>register_method ({
65 subclass
=> "PVE::API2::LXC::Snapshot",
66 path
=> '{vmid}/snapshot',
69 __PACKAGE__-
>register_method ({
70 subclass
=> "PVE::API2::Firewall::CT",
71 path
=> '{vmid}/firewall',
74 __PACKAGE__-
>register_method({
78 description
=> "LXC container index (per node).",
80 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
84 protected
=> 1, # /proc files are only readable by root
86 additionalProperties
=> 0,
88 node
=> get_standard_option
('pve-node'),
95 properties
=> $PVE::LXC
::vmstatus_return_properties
,
97 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
102 my $rpcenv = PVE
::RPCEnvironment
::get
();
103 my $authuser = $rpcenv->get_user();
105 my $vmstatus = PVE
::LXC
::vmstatus
();
108 foreach my $vmid (keys %$vmstatus) {
109 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
111 my $data = $vmstatus->{$vmid};
119 __PACKAGE__-
>register_method({
123 description
=> "Create or restore a container.",
125 user
=> 'all', # check inside
126 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
127 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
128 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
133 additionalProperties
=> 0,
134 properties
=> PVE
::LXC
::Config-
>json_config_properties({
135 node
=> get_standard_option
('pve-node'),
136 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
138 description
=> "The OS template or backup file.",
141 completion
=> \
&PVE
::LXC
::complete_os_templates
,
146 description
=> "Sets root password inside container.",
149 storage
=> get_standard_option
('pve-storage-id', {
150 description
=> "Default Storage.",
153 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
158 description
=> "Allow to overwrite existing container.",
163 description
=> "Mark this as restore task.",
168 description
=> "Assign a unique random ethernet address.",
169 requires
=> 'restore',
173 type
=> 'string', format
=> 'pve-poolid',
174 description
=> "Add the VM to the specified pool.",
176 'ignore-unpack-errors' => {
179 description
=> "Ignore errors when extracting the template.",
181 'ssh-public-keys' => {
184 description
=> "Setup public SSH keys (one key per line, " .
188 description
=> "Override I/O bandwidth limit (in KiB/s).",
192 default => 'restore limit from datacenter or storage config',
198 description
=> "Start the CT after its creation finished successfully.",
208 PVE
::Cluster
::check_cfs_quorum
();
210 my $rpcenv = PVE
::RPCEnvironment
::get
();
211 my $authuser = $rpcenv->get_user();
213 my $node = extract_param
($param, 'node');
214 my $vmid = extract_param
($param, 'vmid');
215 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
216 my $bwlimit = extract_param
($param, 'bwlimit');
217 my $start_after_create = extract_param
($param, 'start');
219 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
220 my $same_container_exists = -f
$basecfg_fn;
222 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
223 my $unprivileged = extract_param
($param, 'unprivileged');
224 my $restore = extract_param
($param, 'restore');
225 my $unique = extract_param
($param, 'unique');
227 $param->{cpuunits
} = PVE
::CGroup
::clamp_cpu_shares
($param->{cpuunits
})
228 if defined($param->{cpuunits
}); # clamp value depending on cgroup version
230 # used to skip firewall config restore if user lacks permission
231 my $skip_fw_config_restore = 0;
234 # fixme: limit allowed parameters
237 my $force = extract_param
($param, 'force');
239 if (!($same_container_exists && $restore && $force)) {
240 PVE
::Cluster
::check_vmid_unused
($vmid);
242 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
243 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
244 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
247 my $password = extract_param
($param, 'password');
248 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
249 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
251 my $pool = extract_param
($param, 'pool');
252 $rpcenv->check_pool_exist($pool) if defined($pool);
254 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
256 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
258 } elsif ($restore && $force && $same_container_exists &&
259 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
260 # OK: user has VM.Backup permissions, and want to restore an existing VM
262 # we don't want to restore a container-provided FW conf in this case
263 # since the user is lacking permission to configure the container's FW
264 $skip_fw_config_restore = 1;
266 # error out if a user tries to change from unprivileged to privileged
267 # explicit change is checked here, implicit is checked down below or happening in root-only paths
268 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
269 if ($conf->{unprivileged
} && defined($unprivileged) && !$unprivileged) {
270 raise_perm_exc
("cannot change from unprivileged to privileged without VM.Allocate");
276 my $ostemplate = extract_param
($param, 'ostemplate');
277 my $storage = extract_param
($param, 'storage') // 'local';
279 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, undef, $param, [], $unprivileged);
281 my $storage_cfg = cfs_read_file
("storage.cfg");
284 if ($ostemplate eq '-') {
285 die "pipe requires cli environment\n"
286 if $rpcenv->{type
} ne 'cli';
287 die "pipe can only be used with restore tasks\n"
290 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
292 my $content_type = $restore ?
'backup' : 'vztmpl';
293 PVE
::Storage
::check_volume_access
(
301 $archive = $ostemplate;
305 my $check_and_activate_storage = sub {
308 my $scfg = PVE
::Storage
::storage_check_enabled
($storage_cfg, $sid, $node);
310 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
311 if !$scfg->{content
}->{rootdir
};
313 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
315 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
316 $used_storages{$sid} = 1;
321 my $is_root = $authuser eq 'root@pam';
323 my $no_disk_param = {};
325 my $storage_only_mode = 1;
326 foreach my $opt (keys %$param) {
327 my $value = $param->{$opt};
328 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
329 # allow to use simple numbers (add default storage in that case)
330 if ($value =~ m/^\d+(\.\d+)?$/) {
331 $mp_param->{$opt} = "$storage:$value";
333 $mp_param->{$opt} = $value;
335 $storage_only_mode = 0;
336 } elsif ($opt =~ m/^unused\d+$/) {
337 warn "ignoring '$opt', cannot create/restore with unused volume\n";
338 delete $param->{$opt};
340 $no_disk_param->{$opt} = $value;
344 die "mount points configured, but 'rootfs' not set - aborting\n"
345 if !$storage_only_mode && !defined($mp_param->{rootfs
});
347 # check storage access, activate storage
348 my $delayed_mp_param = {};
349 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
350 my ($ms, $mountpoint) = @_;
352 my $volid = $mountpoint->{volume
};
353 my $mp = $mountpoint->{mp
};
355 if ($mountpoint->{type
} ne 'volume') { # bind or device
356 die "Only root can pass arbitrary filesystem paths.\n"
359 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
360 &$check_and_activate_storage($sid);
364 # check/activate default storage
365 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
367 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
369 $conf->{unprivileged
} = 1 if $unprivileged;
371 my $emsg = $restore ?
"unable to restore CT $vmid -" : "unable to create CT $vmid -";
373 eval { PVE
::LXC
::Config-
>create_and_lock_config($vmid, $force) };
374 die "$emsg $@" if $@;
376 my $destroy_config_on_error = !$same_container_exists;
379 my $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
384 my $orig_mp_param; # only used if $restore
386 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
387 if ($archive ne '-') {
389 print "recovering backed-up configuration from '$archive'\n";
390 ($orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
392 for my $opt (keys %$orig_conf) {
393 # early check before disks are created
394 # the "real" check is in later on when actually merging the configs
395 if ($opt =~ /^net\d+$/ && !defined($param->{$opt})) {
396 PVE
::LXC
::check_bridge_access
($rpcenv, $authuser, $orig_conf->{$opt});
400 $was_template = delete $orig_conf->{template
};
402 # When we're root call 'restore_configuration' with restricted=0,
403 # causing it to restore the raw lxc entries, among which there may be
404 # 'lxc.idmap' entries. We need to make sure that the extracted contents
405 # of the container match up with the restored configuration afterwards:
406 $conf->{lxc
} = $orig_conf->{lxc
} if $is_root;
408 $conf->{unprivileged
} = $orig_conf->{unprivileged
}
409 if !defined($unprivileged) && defined($orig_conf->{unprivileged
});
411 # implicit privileged change is checked here
412 if ($old_conf->{unprivileged
} && !$conf->{unprivileged
}) {
413 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Allocate']);
417 if ($storage_only_mode) {
419 if (!defined($orig_mp_param)) {
420 print "recovering backed-up configuration from '$archive'\n";
421 (undef, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
423 $mp_param = $orig_mp_param;
424 die "rootfs configuration could not be recovered, please check and specify manually!\n"
425 if !defined($mp_param->{rootfs
});
426 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
427 my ($ms, $mountpoint) = @_;
428 my $type = $mountpoint->{type
};
429 if ($type eq 'volume') {
430 die "unable to detect disk size - please specify $ms (size)\n"
431 if !defined($mountpoint->{size
});
432 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
433 delete $mountpoint->{size
};
434 $mountpoint->{volume
} = "$storage:$disksize";
435 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
437 my $type = $mountpoint->{type
};
438 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
439 if ($ms eq 'rootfs');
440 die "restoring '$ms' to $type mount is only possible for root\n"
443 if ($mountpoint->{backup
}) {
444 warn "WARNING - unsupported configuration!\n";
445 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
446 warn "mount point configuration will be restored after archive extraction!\n";
447 warn "contained files will be restored to wrong directory!\n";
449 delete $mp_param->{$ms}; # actually delay bind/dev mps
450 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
454 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
458 # up until here we did not modify the container, besides the lock
459 $destroy_config_on_error = 1;
461 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
463 # we always have the 'create' lock so check for more than 1 entry
464 if (scalar(keys %$old_conf) > 1) {
465 # destroy old container volumes
466 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, { lock => 'create' });
470 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
471 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
472 print "restoring '$archive' now..\n"
473 if $restore && $archive ne '-';
474 PVE
::LXC
::Create
::restore_archive
($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
477 print "merging backed-up and given configuration..\n";
478 PVE
::LXC
::Create
::restore_configuration
($vmid, $storage_cfg, $archive, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
479 PVE
::LXC
::create_ifaces_ipams_ips
($conf, $vmid) if $unique;
480 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
481 $lxc_setup->template_fixup($conf);
483 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
484 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
485 $lxc_setup->post_create_hook($password, $ssh_keys);
489 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
490 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
493 $conf->{hostname
} ||= "CT$vmid";
494 $conf->{memory
} ||= 512;
495 $conf->{swap
} //= 512;
496 foreach my $mp (keys %$delayed_mp_param) {
497 $conf->{$mp} = $delayed_mp_param->{$mp};
499 # If the template flag was set, we try to convert again to template after restore
501 print STDERR
"Convert restored container to template...\n";
502 PVE
::LXC
::template_create
($vmid, $conf);
503 $conf->{template
} = 1;
505 PVE
::LXC
::Config-
>write_config($vmid, $conf);
508 eval { PVE
::LXC
::delete_ifaces_ipams_ips
($conf, $vmid) };
510 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
511 if ($destroy_config_on_error) {
512 eval { PVE
::LXC
::Config-
>destroy_config($vmid) };
515 if (!$skip_fw_config_restore) { # Only if user has permission to change the fw
516 PVE
::Firewall
::remove_vmfw_conf
($vmid);
522 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
525 my $workername = $restore ?
'vzrestore' : 'vzcreate';
528 PVE
::LXC
::Config-
>lock_config($vmid, $code);
531 # if we aborted before changing the container, we must remove the create lock
532 if (!$destroy_config_on_error) {
533 PVE
::LXC
::Config-
>remove_lock($vmid, 'create');
536 } elsif ($start_after_create) {
537 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node });
541 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
544 __PACKAGE__-
>register_method({
549 description
=> "Directory index",
554 additionalProperties
=> 0,
556 node
=> get_standard_option
('pve-node'),
557 vmid
=> get_standard_option
('pve-vmid'),
565 subdir
=> { type
=> 'string' },
568 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
574 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
577 { subdir
=> 'config' },
578 { subdir
=> 'pending' },
579 { subdir
=> 'status' },
580 { subdir
=> 'vncproxy' },
581 { subdir
=> 'termproxy' },
582 { subdir
=> 'vncwebsocket' },
583 { subdir
=> 'spiceproxy' },
584 { subdir
=> 'migrate' },
585 { subdir
=> 'clone' },
586 # { subdir => 'initlog' },
588 { subdir
=> 'rrddata' },
589 { subdir
=> 'firewall' },
590 { subdir
=> 'snapshot' },
591 { subdir
=> 'resize' },
598 __PACKAGE__-
>register_method({
600 path
=> '{vmid}/rrd',
602 protected
=> 1, # fixme: can we avoid that?
604 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
606 description
=> "Read VM RRD statistics (returns PNG)",
608 additionalProperties
=> 0,
610 node
=> get_standard_option
('pve-node'),
611 vmid
=> get_standard_option
('pve-vmid'),
613 description
=> "Specify the time frame you are interested in.",
615 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
618 description
=> "The list of datasources you want to display.",
619 type
=> 'string', format
=> 'pve-configid-list',
622 description
=> "The RRD consolidation function",
624 enum
=> [ 'AVERAGE', 'MAX' ],
632 filename
=> { type
=> 'string' },
638 return PVE
::RRD
::create_rrd_graph
(
639 "pve2-vm/$param->{vmid}", $param->{timeframe
},
640 $param->{ds
}, $param->{cf
});
644 __PACKAGE__-
>register_method({
646 path
=> '{vmid}/rrddata',
648 protected
=> 1, # fixme: can we avoid that?
650 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
652 description
=> "Read VM RRD statistics",
654 additionalProperties
=> 0,
656 node
=> get_standard_option
('pve-node'),
657 vmid
=> get_standard_option
('pve-vmid'),
659 description
=> "Specify the time frame you are interested in.",
661 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
664 description
=> "The RRD consolidation function",
666 enum
=> [ 'AVERAGE', 'MAX' ],
681 return PVE
::RRD
::create_rrd_data
(
682 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
685 __PACKAGE__-
>register_method({
686 name
=> 'destroy_vm',
691 description
=> "Destroy the container (also delete all uses files).",
693 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
696 additionalProperties
=> 0,
698 node
=> get_standard_option
('pve-node'),
699 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
702 description
=> "Force destroy, even if running.",
708 description
=> "Remove container from all related configurations."
709 ." For example, backup jobs, replication jobs or HA."
710 ." Related ACLs and Firewall entries will *always* be removed.",
714 'destroy-unreferenced-disks' => {
716 description
=> "If set, destroy additionally all disks with the VMID from all"
717 ." enabled storages which are not referenced in the config.",
728 my $rpcenv = PVE
::RPCEnvironment
::get
();
729 my $authuser = $rpcenv->get_user();
730 my $vmid = $param->{vmid
};
732 # test if container exists
734 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
735 my $early_checks = sub {
737 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
738 PVE
::LXC
::Config-
>check_lock($conf);
740 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("ct:$vmid");
742 if (!$param->{purge
}) {
743 die "unable to remove CT $vmid - used in HA resources and purge parameter not set.\n"
746 # do not allow destroy if there are replication jobs without purge
747 my $repl_conf = PVE
::ReplicationConfig-
>new();
748 $repl_conf->check_for_existing_jobs($vmid);
754 $early_checks->($conf);
756 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
757 die $running_error_msg if !$param->{force
} && PVE
::LXC
::check_running
($vmid); # check early
760 # reload config after lock
761 $conf = PVE
::LXC
::Config-
>load_config($vmid);
762 my $ha_managed = $early_checks->($conf);
764 if (PVE
::LXC
::check_running
($vmid)) {
765 die $running_error_msg if !$param->{force
};
766 warn "forced to stop CT $vmid before destroying!\n";
768 PVE
::LXC
::vm_stop
($vmid, 1);
770 run_command
(['ha-manager', 'crm-command', 'stop', "ct:$vmid", '120']);
774 my $storage_cfg = cfs_read_file
("storage.cfg");
775 PVE
::LXC
::destroy_lxc_container
(
779 { lock => 'destroyed' },
780 $param->{'destroy-unreferenced-disks'},
783 PVE
::AccessControl
::remove_vm_access
($vmid);
784 PVE
::Firewall
::remove_vmfw_conf
($vmid);
785 if ($param->{purge
}) {
786 print "purging CT $vmid from related configurations..\n";
787 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
788 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
791 PVE
::HA
::Config
::delete_service_from_config
("ct:$vmid");
792 print "NOTE: removed CT $vmid from HA resource configuration.\n";
796 # only now remove the zombie config, else we can have reuse race
797 PVE
::LXC
::Config-
>destroy_config($vmid);
800 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
802 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
807 __PACKAGE__-
>register_method ({
809 path
=> '{vmid}/vncproxy',
813 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
815 description
=> "Creates a TCP VNC proxy connections.",
817 additionalProperties
=> 0,
819 node
=> get_standard_option
('pve-node'),
820 vmid
=> get_standard_option
('pve-vmid'),
824 description
=> "use websocket instead of standard VNC.",
828 description
=> "sets the width of the console in pixels.",
835 description
=> "sets the height of the console in pixels.",
843 additionalProperties
=> 0,
845 user
=> { type
=> 'string' },
846 ticket
=> { type
=> 'string' },
847 cert
=> { type
=> 'string' },
848 port
=> { type
=> 'integer' },
849 upid
=> { type
=> 'string' },
855 my $rpcenv = PVE
::RPCEnvironment
::get
();
857 my $authuser = $rpcenv->get_user();
859 my $vmid = $param->{vmid
};
860 my $node = $param->{node
};
862 my $authpath = "/vms/$vmid";
864 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
866 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
869 my ($remip, $family);
871 if ($node ne PVE
::INotify
::nodename
()) {
872 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
874 $family = PVE
::Tools
::get_host_address_family
($node);
877 my $port = PVE
::Tools
::next_vnc_port
($family);
879 # NOTE: vncterm VNC traffic is already TLS encrypted,
880 # so we select the fastest chipher here (or 'none'?)
881 my $remcmd = $remip ?
882 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
884 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
885 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
887 my $shcmd = [ '/usr/bin/dtach', '-A',
888 "/var/run/dtach/vzctlconsole$vmid",
889 '-r', 'winch', '-z', @$concmd];
894 syslog
('info', "starting lxc vnc proxy $upid\n");
898 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
899 '-timeout', $timeout, '-authpath', $authpath,
900 '-perm', 'VM.Console'];
902 if ($param->{width
}) {
903 push @$cmd, '-width', $param->{width
};
906 if ($param->{height
}) {
907 push @$cmd, '-height', $param->{height
};
910 if ($param->{websocket
}) {
911 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
912 push @$cmd, '-notls', '-listen', 'localhost';
915 push @$cmd, '-c', @$remcmd, @$shcmd;
917 run_command
($cmd, keeplocale
=> 1);
922 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
924 PVE
::Tools
::wait_for_vnc_port
($port);
935 __PACKAGE__-
>register_method ({
937 path
=> '{vmid}/termproxy',
941 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
943 description
=> "Creates a TCP proxy connection.",
945 additionalProperties
=> 0,
947 node
=> get_standard_option
('pve-node'),
948 vmid
=> get_standard_option
('pve-vmid'),
952 additionalProperties
=> 0,
954 user
=> { type
=> 'string' },
955 ticket
=> { type
=> 'string' },
956 port
=> { type
=> 'integer' },
957 upid
=> { type
=> 'string' },
963 my $rpcenv = PVE
::RPCEnvironment
::get
();
965 my $authuser = $rpcenv->get_user();
967 my $vmid = $param->{vmid
};
968 my $node = $param->{node
};
970 my $authpath = "/vms/$vmid";
972 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
974 my ($remip, $family);
976 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
977 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
979 $family = PVE
::Tools
::get_host_address_family
($node);
982 my $port = PVE
::Tools
::next_vnc_port
($family);
984 my $remcmd = $remip ?
985 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
987 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
988 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
990 my $shcmd = [ '/usr/bin/dtach', '-A',
991 "/var/run/dtach/vzctlconsole$vmid",
992 '-r', 'winch', '-z', @$concmd];
997 syslog
('info', "starting lxc termproxy $upid\n");
999 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
1000 '--perm', 'VM.Console', '--'];
1001 push @$cmd, @$remcmd, @$shcmd;
1003 PVE
::Tools
::run_command
($cmd);
1006 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
1008 PVE
::Tools
::wait_for_vnc_port
($port);
1018 __PACKAGE__-
>register_method({
1019 name
=> 'vncwebsocket',
1020 path
=> '{vmid}/vncwebsocket',
1023 description
=> "You also need to pass a valid ticket (vncticket).",
1024 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1026 description
=> "Opens a weksocket for VNC traffic.",
1028 additionalProperties
=> 0,
1030 node
=> get_standard_option
('pve-node'),
1031 vmid
=> get_standard_option
('pve-vmid'),
1033 description
=> "Ticket from previous call to vncproxy.",
1038 description
=> "Port number returned by previous vncproxy call.",
1048 port
=> { type
=> 'string' },
1054 my $rpcenv = PVE
::RPCEnvironment
::get
();
1056 my $authuser = $rpcenv->get_user();
1058 my $authpath = "/vms/$param->{vmid}";
1060 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1062 my $port = $param->{port
};
1064 return { port
=> $port };
1067 __PACKAGE__-
>register_method ({
1068 name
=> 'spiceproxy',
1069 path
=> '{vmid}/spiceproxy',
1074 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1076 description
=> "Returns a SPICE configuration to connect to the CT.",
1078 additionalProperties
=> 0,
1080 node
=> get_standard_option
('pve-node'),
1081 vmid
=> get_standard_option
('pve-vmid'),
1082 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1085 returns
=> get_standard_option
('remote-viewer-config'),
1089 my $vmid = $param->{vmid
};
1090 my $node = $param->{node
};
1091 my $proxy = $param->{proxy
};
1093 my $authpath = "/vms/$vmid";
1094 my $permissions = 'VM.Console';
1096 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1098 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
1100 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
1102 my $shcmd = ['/usr/bin/dtach', '-A',
1103 "/var/run/dtach/vzctlconsole$vmid",
1104 '-r', 'winch', '-z', @$concmd];
1106 my $title = "CT $vmid";
1108 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1112 __PACKAGE__-
>register_method({
1113 name
=> 'remote_migrate_vm',
1114 path
=> '{vmid}/remote_migrate',
1118 description
=> "Migrate the container to another cluster. Creates a new migration task. EXPERIMENTAL feature!",
1120 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1123 additionalProperties
=> 0,
1125 node
=> get_standard_option
('pve-node'),
1126 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1127 'target-vmid' => get_standard_option
('pve-vmid', { optional
=> 1 }),
1128 'target-endpoint' => get_standard_option
('proxmox-remote', {
1129 description
=> "Remote target endpoint",
1133 description
=> "Use online/live migration.",
1138 description
=> "Use restart migration",
1143 description
=> "Timeout in seconds for shutdown for restart migration",
1149 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.",
1153 'target-storage' => get_standard_option
('pve-targetstorage', {
1156 'target-bridge' => {
1158 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.",
1159 format
=> 'bridge-pair-list',
1162 description
=> "Override I/O bandwidth limit (in KiB/s).",
1166 default => 'migrate limit from datacenter or storage config',
1172 description
=> "the task ID.",
1177 my $rpcenv = PVE
::RPCEnvironment
::get
();
1178 my $authuser = $rpcenv->get_user();
1180 my $source_vmid = extract_param
($param, 'vmid');
1181 my $target_endpoint = extract_param
($param, 'target-endpoint');
1182 my $target_vmid = extract_param
($param, 'target-vmid') // $source_vmid;
1184 my $delete = extract_param
($param, 'delete') // 0;
1186 PVE
::Cluster
::check_cfs_quorum
();
1189 my $conf = PVE
::LXC
::Config-
>load_config($source_vmid);
1190 PVE
::LXC
::Config-
>check_lock($conf);
1192 # try to detect errors early
1193 if (PVE
::LXC
::check_running
($source_vmid)) {
1194 die "can't migrate running container without --online or --restart\n"
1195 if !$param->{online
} && !$param->{restart
};
1198 raise_param_exc
({ vmid
=> "cannot migrate HA-managed CT to remote cluster" })
1199 if PVE
::HA
::Config
::vm_is_ha_managed
($source_vmid);
1201 my $remote = PVE
::JSONSchema
::parse_property_string
('proxmox-remote', $target_endpoint);
1203 # TODO: move this as helper somewhere appropriate?
1205 protocol
=> 'https',
1206 host
=> $remote->{host
},
1207 port
=> $remote->{port
} // 8006,
1208 apitoken
=> $remote->{apitoken
},
1212 if ($fp = $remote->{fingerprint
}) {
1213 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 };
1216 print "Establishing API connection with remote at '$remote->{host}'\n";
1218 my $api_client = PVE
::APIClient
::LWP-
>new(%$conn_args);
1220 if (!defined($fp)) {
1221 my $cert_info = $api_client->get("/nodes/localhost/certificates/info");
1222 foreach my $cert (@$cert_info) {
1223 my $filename = $cert->{filename
};
1224 next if $filename ne 'pveproxy-ssl.pem' && $filename ne 'pve-ssl.pem';
1225 $fp = $cert->{fingerprint
} if !$fp || $filename eq 'pveproxy-ssl.pem';
1227 $conn_args->{cached_fingerprints
} = { uc($fp) => 1 }
1231 my $storecfg = PVE
::Storage
::config
();
1232 my $target_storage = extract_param
($param, 'target-storage');
1233 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($target_storage, 'pve-storage-id') };
1234 raise_param_exc
({ 'target-storage' => "failed to parse storage map: $@" })
1237 my $target_bridge = extract_param
($param, 'target-bridge');
1238 my $bridgemap = eval { PVE
::JSONSchema
::parse_idmap
($target_bridge, 'pve-bridge-id') };
1239 raise_param_exc
({ 'target-bridge' => "failed to parse bridge map: $@" })
1242 die "remote migration requires explicit storage mapping!\n"
1243 if $storagemap->{identity
};
1245 $param->{storagemap
} = $storagemap;
1246 $param->{bridgemap
} = $bridgemap;
1247 $param->{remote
} = {
1248 conn
=> $conn_args, # re-use fingerprint for tunnel
1249 client
=> $api_client,
1250 vmid
=> $target_vmid,
1252 $param->{migration_type
} = 'websocket';
1253 $param->{delete} = $delete if $delete;
1255 my $cluster_status = $api_client->get("/cluster/status");
1257 foreach my $entry (@$cluster_status) {
1258 next if $entry->{type
} ne 'node';
1259 if ($entry->{local}) {
1260 $target_node = $entry->{name
};
1265 die "couldn't determine endpoint's node name\n"
1266 if !defined($target_node);
1269 PVE
::LXC
::Migrate-
>migrate($target_node, $remote->{host
}, $source_vmid, $param);
1273 return PVE
::GuestHelpers
::guest_migration_lock
($source_vmid, 10, $realcmd);
1276 return $rpcenv->fork_worker('vzmigrate', $source_vmid, $authuser, $worker);
1280 __PACKAGE__-
>register_method({
1281 name
=> 'migrate_vm',
1282 path
=> '{vmid}/migrate',
1286 description
=> "Migrate the container to another node. Creates a new migration task.",
1288 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1291 additionalProperties
=> 0,
1293 node
=> get_standard_option
('pve-node'),
1294 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1295 target
=> get_standard_option
('pve-node', {
1296 description
=> "Target node.",
1297 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1299 'target-storage' => get_standard_option
('pve-targetstorage'),
1302 description
=> "Use online/live migration.",
1307 description
=> "Use restart migration",
1312 description
=> "Timeout in seconds for shutdown for restart migration",
1317 description
=> "Override I/O bandwidth limit (in KiB/s).",
1321 default => 'migrate limit from datacenter or storage config',
1327 description
=> "the task ID.",
1332 my $rpcenv = PVE
::RPCEnvironment
::get
();
1334 my $authuser = $rpcenv->get_user();
1336 my $target = extract_param
($param, 'target');
1338 my $localnode = PVE
::INotify
::nodename
();
1339 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1341 PVE
::Cluster
::check_cfs_quorum
();
1343 PVE
::Cluster
::check_node_exists
($target);
1345 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1347 my $vmid = extract_param
($param, 'vmid');
1350 PVE
::LXC
::Config-
>load_config($vmid);
1352 # try to detect errors early
1353 if (PVE
::LXC
::check_running
($vmid)) {
1354 die "can't migrate running container without --online or --restart\n"
1355 if !$param->{online
} && !$param->{restart
};
1358 if (my $targetstorage = delete $param->{'target-storage'}) {
1359 my $storecfg = PVE
::Storage
::config
();
1360 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
1361 raise_param_exc
({ 'target-storage' => "failed to parse storage map: $@" })
1364 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
1365 if !defined($storagemap->{identity
});
1367 foreach my $target_sid (values %{$storagemap->{entries
}}) {
1368 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
1371 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
1372 if $storagemap->{default};
1374 $param->{storagemap
} = $storagemap;
1377 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1382 my $service = "ct:$vmid";
1384 my $cmd = ['ha-manager', 'migrate', $service, $target];
1386 print "Requesting HA migration for CT $vmid to node $target\n";
1388 PVE
::Tools
::run_command
($cmd);
1393 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1398 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1402 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1405 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1409 __PACKAGE__-
>register_method({
1410 name
=> 'vm_feature',
1411 path
=> '{vmid}/feature',
1415 description
=> "Check if feature for virtual machine is available.",
1417 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1420 additionalProperties
=> 0,
1422 node
=> get_standard_option
('pve-node'),
1423 vmid
=> get_standard_option
('pve-vmid'),
1425 description
=> "Feature to check.",
1427 enum
=> [ 'snapshot', 'clone', 'copy' ],
1429 snapname
=> get_standard_option
('pve-snapshot-name', {
1437 hasFeature
=> { type
=> 'boolean' },
1440 #items => { type => 'string' },
1447 my $node = extract_param
($param, 'node');
1449 my $vmid = extract_param
($param, 'vmid');
1451 my $snapname = extract_param
($param, 'snapname');
1453 my $feature = extract_param
($param, 'feature');
1455 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1458 my $snap = $conf->{snapshots
}->{$snapname};
1459 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1462 my $storage_cfg = PVE
::Storage
::config
();
1463 #Maybe include later
1464 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1465 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1468 hasFeature
=> $hasFeature,
1469 #nodes => [ keys %$nodelist ],
1473 __PACKAGE__-
>register_method({
1475 path
=> '{vmid}/template',
1479 description
=> "Create a Template.",
1481 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1482 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1485 additionalProperties
=> 0,
1487 node
=> get_standard_option
('pve-node'),
1488 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1491 returns
=> { type
=> 'null'},
1495 my $rpcenv = PVE
::RPCEnvironment
::get
();
1497 my $authuser = $rpcenv->get_user();
1499 my $node = extract_param
($param, 'node');
1501 my $vmid = extract_param
($param, 'vmid');
1503 my $updatefn = sub {
1505 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1506 PVE
::LXC
::Config-
>check_lock($conf);
1508 die "unable to create template, because CT contains snapshots\n"
1509 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1511 die "you can't convert a template to a template\n"
1512 if PVE
::LXC
::Config-
>is_template($conf);
1514 die "you can't convert a CT to template if the CT is running\n"
1515 if PVE
::LXC
::check_running
($vmid);
1518 PVE
::LXC
::template_create
($vmid, $conf);
1520 $conf->{template
} = 1;
1522 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1523 # and remove lxc config
1524 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1527 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1530 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1535 __PACKAGE__-
>register_method({
1537 path
=> '{vmid}/clone',
1541 description
=> "Create a container clone/copy",
1543 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1544 "and 'VM.Allocate' permissions " .
1545 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1546 "'Datastore.AllocateSpace' on any used storage, and 'SDN.Use' on any bridge.",
1549 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1551 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1552 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1557 additionalProperties
=> 0,
1559 node
=> get_standard_option
('pve-node'),
1560 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1561 newid
=> get_standard_option
('pve-vmid', {
1562 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1563 description
=> 'VMID for the clone.' }),
1566 type
=> 'string', format
=> 'dns-name',
1567 description
=> "Set a hostname for the new CT.",
1572 description
=> "Description for the new CT.",
1576 type
=> 'string', format
=> 'pve-poolid',
1577 description
=> "Add the new CT to the specified pool.",
1579 snapname
=> get_standard_option
('pve-snapshot-name', {
1582 storage
=> get_standard_option
('pve-storage-id', {
1583 description
=> "Target storage for full clone.",
1589 description
=> "Create a full copy of all disks. This is always done when " .
1590 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1592 target
=> get_standard_option
('pve-node', {
1593 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1597 description
=> "Override I/O bandwidth limit (in KiB/s).",
1601 default => 'clone limit from datacenter or storage config',
1611 my $rpcenv = PVE
::RPCEnvironment
::get
();
1612 my $authuser = $rpcenv->get_user();
1614 my $node = extract_param
($param, 'node');
1615 my $vmid = extract_param
($param, 'vmid');
1616 my $newid = extract_param
($param, 'newid');
1617 my $pool = extract_param
($param, 'pool');
1618 if (defined($pool)) {
1619 $rpcenv->check_pool_exist($pool);
1621 my $snapname = extract_param
($param, 'snapname');
1622 my $storage = extract_param
($param, 'storage');
1623 my $target = extract_param
($param, 'target');
1624 my $localnode = PVE
::INotify
::nodename
();
1626 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1628 PVE
::Cluster
::check_node_exists
($target) if $target;
1630 my $storecfg = PVE
::Storage
::config
();
1633 # check if storage is enabled on local node
1634 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1636 # check if storage is available on target node
1637 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
1638 # clone only works if target storage is shared
1639 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1640 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1644 PVE
::Cluster
::check_cfs_quorum
();
1647 my $mountpoints = {};
1652 my $lock_and_reload = sub {
1653 my ($vmid, $code) = @_;
1654 return PVE
::LXC
::Config-
>lock_config($vmid, sub {
1655 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1656 die "Lost 'create' config lock, aborting.\n"
1657 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1659 return $code->($conf);
1663 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1666 PVE
::LXC
::Config-
>create_and_lock_config($newid, 0);
1669 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1670 warn "Failed to remove source CT config lock - $@\n" if $@;
1676 $running = PVE
::LXC
::check_running
($vmid) || 0;
1678 my $full = extract_param
($param, 'full');
1679 if (!defined($full)) {
1680 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1683 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1685 die "parameter 'storage' not allowed for linked clones\n"
1686 if defined($storage) && !$full;
1688 die "snapshot '$snapname' does not exist\n"
1689 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1691 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1694 for my $opt (sort keys %$src_conf) {
1695 next if $opt =~ m/^unused\d+$/;
1697 my $value = $src_conf->{$opt};
1699 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1700 my $mp = PVE
::LXC
::Config-
>parse_volume($opt, $value);
1702 if ($mp->{type
} eq 'volume') {
1703 my $volid = $mp->{volume
};
1705 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1706 $sid = $storage if defined($storage);
1707 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1708 if (!$scfg->{shared
}) {
1710 warn "found non-shared volume: $volid\n" if $target;
1713 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1716 die "Cannot do full clones on a running container without snapshots\n"
1717 if $running && !defined($snapname);
1718 $fullclone->{$opt} = 1;
1720 # not full means clone instead of copy
1721 die "Linked clone feature for '$volid' is not available\n"
1722 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
1725 $mountpoints->{$opt} = $mp;
1726 push @$vollist, $volid;
1729 # TODO: allow bind mounts?
1730 die "unable to clone mountpoint '$opt' (type $mp->{type})\n";
1732 } elsif ($opt =~ m/^net(\d+)$/) {
1733 # always change MAC! address
1734 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1735 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1736 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1737 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1739 PVE
::LXC
::check_bridge_access
($rpcenv, $authuser, $newconf->{$opt});
1741 # copy everything else
1742 $newconf->{$opt} = $value;
1745 die "can't clone CT to node '$target' (CT uses local storage)\n"
1746 if $target && !$sharedvm;
1748 # Replace the 'disk' lock with a 'create' lock.
1749 $newconf->{lock} = 'create';
1751 # delete all snapshot related config options
1752 delete $newconf->@{qw(snapshots parent snaptime snapstate)};
1754 delete $newconf->{pending
};
1755 delete $newconf->{template
};
1757 $newconf->{hostname
} = $param->{hostname
} if $param->{hostname
};
1758 $newconf->{description
} = $param->{description
} if $param->{description
};
1760 $lock_and_reload->($newid, sub {
1761 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1765 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1766 warn "Failed to remove source CT config lock - $@\n" if $@;
1769 $lock_and_reload->($newid, sub {
1770 PVE
::LXC
::Config-
>destroy_config($newid);
1771 PVE
::Firewall
::remove_vmfw_conf
($newid);
1774 warn "Failed to remove target CT config - $@\n" if $@;
1779 my $update_conf = sub {
1780 my ($key, $value) = @_;
1781 return $lock_and_reload->($newid, sub {
1783 $conf->{$key} = $value;
1784 PVE
::LXC
::Config-
>write_config($newid, $conf);
1791 my $newvollist = [];
1793 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1794 die "unexpected state change\n" if $verify_running != $running;
1800 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1802 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1803 my $bwlimit = extract_param
($param, 'bwlimit');
1805 foreach my $opt (keys %$mountpoints) {
1806 my $mp = $mountpoints->{$opt};
1807 my $volid = $mp->{volume
};
1810 if ($fullclone->{$opt}) {
1811 print "create full clone of mountpoint $opt ($volid)\n";
1812 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1813 my $target_storage = $storage // $source_storage;
1814 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1815 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1817 print "create linked clone of mount point $opt ($volid)\n";
1818 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1821 push @$newvollist, $newvolid;
1822 $mp->{volume
} = $newvolid;
1824 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1827 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1829 $lock_and_reload->($newid, sub {
1831 my $rootdir = PVE
::LXC
::mount_all
($newid, $storecfg, $conf, 1);
1834 PVE
::LXC
::create_ifaces_ipams_ips
($conf, $vmid);
1835 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1836 $lxc_setup->post_clone_hook($conf);
1839 eval { PVE
::LXC
::umount_all
($newid, $storecfg, $conf, 1); };
1849 # Unlock the source config in any case:
1850 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1854 # Now cleanup the config & disks & ipam:
1855 sleep 1; # some storages like rbd need to wait before release volume - really?
1857 foreach my $volid (@$newvollist) {
1858 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1863 $lock_and_reload->($newid, sub {
1865 PVE
::LXC
::delete_ifaces_ipams_ips
($conf, $newid);
1866 PVE
::LXC
::Config-
>destroy_config($newid);
1867 PVE
::Firewall
::remove_vmfw_conf
($newid);
1870 warn "Failed to remove target CT config - $@\n" if $@;
1872 die "clone failed: $err";
1875 $lock_and_reload->($newid, sub {
1876 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1879 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1880 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1881 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1883 PVE
::LXC
::Config-
>move_config_to_node($newid, $target);
1890 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1894 __PACKAGE__-
>register_method({
1895 name
=> 'resize_vm',
1896 path
=> '{vmid}/resize',
1900 description
=> "Resize a container mount point.",
1902 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1905 additionalProperties
=> 0,
1907 node
=> get_standard_option
('pve-node'),
1908 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1911 description
=> "The disk you want to resize.",
1912 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys()],
1916 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1917 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.",
1921 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1929 description
=> "the task ID.",
1934 my $rpcenv = PVE
::RPCEnvironment
::get
();
1936 my $authuser = $rpcenv->get_user();
1938 my $node = extract_param
($param, 'node');
1940 my $vmid = extract_param
($param, 'vmid');
1942 my $digest = extract_param
($param, 'digest');
1944 my $sizestr = extract_param
($param, 'size');
1945 my $ext = ($sizestr =~ s/^\+//);
1946 my $request_size = PVE
::JSONSchema
::parse_size
($sizestr);
1947 die "invalid size string" if !defined($request_size);
1949 die "no options specified\n" if !scalar(keys %$param);
1951 my $storage_cfg = cfs_read_file
("storage.cfg");
1953 my $load_and_check = sub {
1954 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1955 PVE
::LXC
::Config-
>check_lock($conf);
1957 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $conf, $param, [], $conf->{unprivileged
});
1959 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1961 my $disk = $param->{disk
};
1962 my $mp = PVE
::LXC
::Config-
>parse_volume($disk, $conf->{$disk});
1964 my $volid = $mp->{volume
};
1966 my (undef, undef, $owner, undef, undef, undef, $format) =
1967 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1969 die "can't resize mount point owned by another container ($owner)"
1972 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1974 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1976 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1978 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1980 die "Could not determine current size of volume '$volid'\n" if !defined($size);
1982 my $newsize = $ext ?
$size + $request_size : $request_size;
1983 $newsize = int($newsize);
1985 die "unable to shrink disk size\n" if $newsize < $size;
1987 die "disk is already at specified size\n" if $size == $newsize;
1989 return ($conf, $disk, $mp, $volid, $format, $newsize);
1993 my ($conf, $disk, $mp, $volid, $format, $newsize) = $load_and_check->();
1995 my $running = PVE
::LXC
::check_running
($vmid);
1997 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1999 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
2000 # we pass 0 here (parameter only makes sense for qemu)
2001 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
2003 $mp->{size
} = $newsize;
2004 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
2006 PVE
::LXC
::Config-
>write_config($vmid, $conf);
2008 if ($format eq 'raw') {
2009 # we need to ensure that the volume is mapped, if not needed this is a NOP
2010 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
2011 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
2015 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
2016 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
2017 die "internal error: CT running but mount point not attached to a loop device"
2019 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
2021 # In order for resize2fs to know that we need online-resizing a mountpoint needs
2022 # to be visible to it in its namespace.
2023 # To not interfere with the rest of the system we unshare the current mount namespace,
2024 # mount over /tmp and then run resize2fs.
2026 # interestingly we don't need to e2fsck on mounted systems...
2027 my $quoted = PVE
::Tools
::shellquote
($path);
2028 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
2030 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
2032 warn "Failed to update the container's filesystem: $@\n" if $@;
2035 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
2036 PVE
::Tools
::run_command
(['resize2fs', $path]);
2038 warn "Failed to update the container's filesystem: $@\n" if $@;
2040 # always un-map if not running, this is a NOP if not needed
2041 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
2047 PVE
::LXC
::Config-
>lock_config($vmid, $code);;
2050 $load_and_check->(); # early checks before forking+locking
2052 return $rpcenv->fork_worker('resize', $vmid, $authuser, $worker);
2055 __PACKAGE__-
>register_method({
2056 name
=> 'move_volume',
2057 path
=> '{vmid}/move_volume',
2061 description
=> "Move a rootfs-/mp-volume to a different storage or to a different container.",
2063 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
2064 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
2065 "a volume to another container, you need the permissions on the ".
2066 "target container as well.",
2067 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
2070 additionalProperties
=> 0,
2072 node
=> get_standard_option
('pve-node'),
2073 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
2074 'target-vmid' => get_standard_option
('pve-vmid', {
2075 completion
=> \
&PVE
::LXC
::complete_ctid
,
2080 #TODO: check how to handle unused mount points as the mp parameter is not configured
2081 enum
=> [ PVE
::LXC
::Config-
>valid_volume_keys_with_unused() ],
2082 description
=> "Volume which will be moved.",
2084 storage
=> get_standard_option
('pve-storage-id', {
2085 description
=> "Target Storage.",
2086 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
2091 description
=> "Delete the original volume after successful copy. By default the " .
2092 "original is kept as an unused volume entry.",
2098 description
=> 'Prevent changes if current configuration file has different SHA1 " .
2099 "digest. This can be used to prevent concurrent modifications.',
2104 description
=> "Override I/O bandwidth limit (in KiB/s).",
2108 default => 'clone limit from datacenter or storage config',
2110 'target-volume' => {
2112 description
=> "The config key the volume will be moved to. Default is the " .
2113 "source volume key.",
2114 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys_with_unused()],
2117 'target-digest' => {
2119 description
=> 'Prevent changes if current configuration file of the target " .
2120 "container has a different SHA1 digest. This can be used to prevent " .
2121 "concurrent modifications.',
2133 my $rpcenv = PVE
::RPCEnvironment
::get
();
2135 my $authuser = $rpcenv->get_user();
2137 my $vmid = extract_param
($param, 'vmid');
2139 my $target_vmid = extract_param
($param, 'target-vmid');
2141 my $storage = extract_param
($param, 'storage');
2143 my $mpkey = extract_param
($param, 'volume');
2145 my $target_mpkey = extract_param
($param, 'target-volume') // $mpkey;
2147 my $digest = extract_param
($param, 'digest');
2149 my $target_digest = extract_param
($param, 'target-digest');
2151 my $lockname = 'disk';
2153 my ($mpdata, $old_volid);
2155 die "either set storage or target-vmid, but not both\n"
2156 if $storage && $target_vmid;
2158 my $storecfg = PVE
::Storage
::config
();
2160 my $move_to_storage_checks = sub {
2161 PVE
::LXC
::Config-
>lock_config($vmid, sub {
2162 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
2163 PVE
::LXC
::Config-
>check_lock($conf);
2165 die "cannot move volumes of a running container\n"
2166 if PVE
::LXC
::check_running
($vmid);
2168 if ($mpkey =~ m/^unused\d+$/) {
2169 die "cannot move volume '$mpkey', only configured volumes can be moved to ".
2170 "another storage\n";
2173 $mpdata = PVE
::LXC
::Config-
>parse_volume($mpkey, $conf->{$mpkey});
2174 $old_volid = $mpdata->{volume
};
2176 die "you can't move a volume with snapshots and delete the source\n"
2177 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
2179 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
2181 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
2185 my $storage_realcmd = sub {
2187 PVE
::Cluster
::log_msg
(
2190 "move volume CT $vmid: move --volume $mpkey --storage $storage"
2193 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
2194 my $storage_cfg = PVE
::Storage
::config
();
2199 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
2200 my $bwlimit = extract_param
($param, 'bwlimit');
2201 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
2202 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
2204 [$source_storage, $storage],
2207 $new_volid = PVE
::LXC
::copy_volume
(
2216 if (PVE
::LXC
::Config-
>is_template($conf)) {
2217 PVE
::Storage
::activate_volumes
($storage_cfg, [ $new_volid ]);
2218 my $template_volid = PVE
::Storage
::vdisk_create_base
($storage_cfg, $new_volid);
2219 $mpdata->{volume
} = $template_volid;
2221 $mpdata->{volume
} = $new_volid;
2224 PVE
::LXC
::Config-
>lock_config($vmid, sub {
2225 my $digest = $conf->{digest
};
2226 $conf = PVE
::LXC
::Config-
>load_config($vmid);
2227 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
2229 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint(
2234 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2236 PVE
::LXC
::Config-
>write_config($vmid, $conf);
2240 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2241 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
2247 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
2248 if defined($new_volid);
2254 my $deactivated = 0;
2256 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
2261 if ($param->{delete}) {
2265 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
2271 PVE
::LXC
::Config-
>lock_config($vmid, sub {
2272 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
2273 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid);
2274 PVE
::LXC
::Config-
>write_config($vmid, $conf);
2280 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
2285 my $load_and_check_reassign_configs = sub {
2286 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
2288 die "Cannot move to/from 'rootfs'\n" if $mpkey eq "rootfs" || $target_mpkey eq "rootfs";
2290 if ($mpkey =~ m/^unused\d+$/ && $target_mpkey !~ m/^unused\d+$/) {
2291 die "Moving an unused volume to a used one is not possible\n";
2293 die "could not find CT ${vmid}\n" if !exists($vmlist->{$vmid});
2294 die "could not find CT ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
2296 my $source_node = $vmlist->{$vmid}->{node
};
2297 my $target_node = $vmlist->{$target_vmid}->{node
};
2299 die "Both containers need to be on the same node ($source_node != $target_node)\n"
2300 if $source_node ne $target_node;
2302 my $source_conf = PVE
::LXC
::Config-
>load_config($vmid);
2303 PVE
::LXC
::Config-
>check_lock($source_conf);
2305 if ($target_vmid eq $vmid) {
2306 $target_conf = $source_conf;
2308 $target_conf = PVE
::LXC
::Config-
>load_config($target_vmid);
2309 PVE
::LXC
::Config-
>check_lock($target_conf);
2312 die "Can't move volumes from or to template CT\n"
2313 if ($source_conf->{template
} || $target_conf->{template
});
2316 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
2317 die "Container ${vmid}: $@" if $@;
2320 if ($target_digest) {
2321 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
2322 die "Container ${target_vmid}: $@" if $@;
2325 die "volume '${mpkey}' for container '$vmid' does not exist\n"
2326 if !defined($source_conf->{$mpkey});
2328 die "Target volume key '${target_mpkey}' is already in use for container '$target_vmid'\n"
2329 if exists $target_conf->{$target_mpkey};
2331 my $drive = PVE
::LXC
::Config-
>parse_volume($mpkey, $source_conf->{$mpkey});
2332 my $source_volid = $drive->{volume
} or die "Volume '${mpkey}' has no associated image\n";
2333 die "Cannot move volume used by a snapshot to another container\n"
2334 if PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($source_conf, $source_volid);
2335 die "Storage does not support moving of this disk to another container\n"
2336 if !PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid);
2337 die "Cannot move a bindmount or device mount to another container\n"
2338 if $drive->{type
} ne "volume";
2339 die "Cannot move in-use volume while the source CT is running - detach or shutdown first\n"
2340 if PVE
::LXC
::check_running
($vmid) && $mpkey !~ m/^unused\d+$/;
2342 my $repl_conf = PVE
::ReplicationConfig-
>new();
2343 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
2344 my ($storeid, undef) = PVE
::Storage
::parse_volume_id
($source_volid);
2345 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
2347 die "Cannot move volume on storage '$storeid' to a replicated container - missing replication support\n"
2348 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
2351 return ($source_conf, $target_conf, $drive);
2354 my $logfunc = sub { print STDERR
"$_[0]\n"; };
2356 my $volume_reassignfn = sub {
2357 return PVE
::LXC
::Config-
>lock_config($vmid, sub {
2358 return PVE
::LXC
::Config-
>lock_config($target_vmid, sub {
2359 my ($source_conf, $target_conf, $drive) = $load_and_check_reassign_configs->();
2360 my $source_volid = $drive->{volume
};
2362 my $target_unused = $target_mpkey =~ m/^unused\d+$/;
2364 print "moving volume '$mpkey' from container '$vmid' to '$target_vmid'\n";
2366 my ($storage, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
2368 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
2370 my $new_volid = PVE
::Storage
::rename_volume
(
2376 $drive->{volume
} = $new_volid;
2378 delete $source_conf->{$mpkey};
2379 print "removing volume '${mpkey}' from container '${vmid}' config\n";
2380 PVE
::LXC
::Config-
>write_config($vmid, $source_conf);
2383 if ($target_unused) {
2384 $drive_string = $new_volid;
2386 $drive_string = PVE
::LXC
::Config-
>print_volume($target_mpkey, $drive);
2389 if ($target_unused) {
2390 $target_conf->{$target_mpkey} = $drive_string;
2392 my $running = PVE
::LXC
::check_running
($target_vmid);
2393 my $param = { $target_mpkey => $drive_string };
2394 my $errors = PVE
::LXC
::Config-
>update_pct_config(
2400 $rpcenv->warn($errors->{$_}) for keys $errors->%*;
2403 PVE
::LXC
::Config-
>write_config($target_vmid, $target_conf);
2404 $target_conf = PVE
::LXC
::Config-
>load_config($target_vmid);
2406 PVE
::LXC
::update_lxc_config
($target_vmid, $target_conf) if !$target_unused;
2407 print "target container '$target_vmid' updated with '$target_mpkey'\n";
2409 # remove possible replication snapshots
2410 if (PVE
::Storage
::volume_has_feature
($storecfg,'replicate', $source_volid)) {
2412 PVE
::Replication
::prepare
(
2422 $rpcenv->warn("Failed to remove replication snapshots on volume ".
2423 "'${target_mpkey}'. Manual cleanup could be necessary. " .
2431 if ($target_vmid && $storage) {
2432 my $msg = "either set 'storage' or 'target-vmid', but not both";
2433 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
2434 } elsif ($target_vmid) {
2435 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
2436 if $authuser ne 'root@pam';
2438 my (undef, undef, $drive) = $load_and_check_reassign_configs->();
2439 my $storeid = PVE
::Storage
::parse_volume_id
($drive->{volume
});
2440 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2441 return $rpcenv->fork_worker(
2443 "${vmid}-${mpkey}>${target_vmid}-${target_mpkey}",
2447 } elsif ($storage) {
2448 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
2449 &$move_to_storage_checks();
2451 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $storage_realcmd);
2454 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
2460 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
2461 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
2465 __PACKAGE__-
>register_method({
2466 name
=> 'vm_pending',
2467 path
=> '{vmid}/pending',
2470 description
=> 'Get container configuration, including pending changes.',
2472 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2475 additionalProperties
=> 0,
2477 node
=> get_standard_option
('pve-node'),
2478 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
2487 description
=> 'Configuration option name.',
2491 description
=> 'Current value.',
2496 description
=> 'Pending value.',
2501 description
=> "Indicates a pending delete request if present and not 0.",
2513 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
2515 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
2517 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);
2520 __PACKAGE__-
>register_method({
2522 path
=> '{vmid}/interfaces',
2526 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2528 description
=> 'Get IP addresses of the specified container interface.',
2530 additionalProperties
=> 0,
2532 node
=> get_standard_option
('pve-node'),
2533 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
2543 description
=> 'The name of the interface',
2548 description
=> 'The MAC address of the interface',
2553 description
=> 'The IPv4 address of the interface',
2558 description
=> 'The IPv6 address of the interface',
2567 return PVE
::LXC
::get_interfaces
($param->{vmid
});
2570 __PACKAGE__-
>register_method({
2572 path
=> '{vmid}/mtunnel',
2575 description
=> 'Migration tunnel endpoint - only for internal use by CT migration.',
2579 ['perm', '/vms/{vmid}', [ 'VM.Allocate' ]],
2580 ['perm', '/', [ 'Sys.Incoming' ]],
2582 description
=> "You need 'VM.Allocate' permissions on '/vms/{vmid}' and Sys.Incoming" .
2583 " on '/'. Further permission checks happen during the actual migration.",
2586 additionalProperties
=> 0,
2588 node
=> get_standard_option
('pve-node'),
2589 vmid
=> get_standard_option
('pve-vmid'),
2592 format
=> 'pve-storage-id-list',
2594 description
=> 'List of storages to check permission and availability. Will be checked again for all actually used storages during migration.',
2598 format
=> 'pve-bridge-id-list',
2600 description
=> 'List of network bridges to check availability. Will be checked again for actually used bridges during migration.',
2605 additionalProperties
=> 0,
2607 upid
=> { type
=> 'string' },
2608 ticket
=> { type
=> 'string' },
2609 socket => { type
=> 'string' },
2615 my $rpcenv = PVE
::RPCEnvironment
::get
();
2616 my $authuser = $rpcenv->get_user();
2618 my $node = extract_param
($param, 'node');
2619 my $vmid = extract_param
($param, 'vmid');
2621 my $storages = extract_param
($param, 'storages');
2622 my $bridges = extract_param
($param, 'bridges');
2624 my $nodename = PVE
::INotify
::nodename
();
2626 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
2627 if $node ne 'localhost' && $node ne $nodename;
2631 my $storecfg = PVE
::Storage
::config
();
2632 foreach my $storeid (PVE
::Tools
::split_list
($storages)) {
2633 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storeid, $node);
2636 foreach my $bridge (PVE
::Tools
::split_list
($bridges)) {
2637 PVE
::Network
::read_bridge_mtu
($bridge);
2640 PVE
::Cluster
::check_cfs_quorum
();
2642 my $socket_addr = "/run/pve/ct-$vmid.mtunnel";
2644 my $lock = 'create';
2645 eval { PVE
::LXC
::Config-
>create_and_lock_config($vmid, 0, $lock); };
2647 raise_param_exc
({ vmid
=> "unable to create empty CT config - $@"})
2652 storecfg
=> PVE
::Storage
::config
(),
2657 my $run_locked = sub {
2658 my ($code, $params) = @_;
2659 return PVE
::LXC
::Config-
>lock_config($state->{vmid
}, sub {
2660 my $conf = PVE
::LXC
::Config-
>load_config($state->{vmid
});
2662 $state->{conf
} = $conf;
2664 die "Encountered wrong lock - aborting mtunnel command handling.\n"
2665 if $state->{lock} && !PVE
::LXC
::Config-
>has_lock($conf, $state->{lock});
2667 return $code->($params);
2675 description
=> 'Full CT config, adapted for target cluster/node',
2677 'firewall-config' => {
2679 description
=> 'CT firewall config',
2686 description
=> 'socket path for which the ticket should be valid. must be known to current mtunnel instance.',
2692 description
=> 'remove CT config and volumes, aborting migration',
2696 'disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'disk-import'},
2697 'query-disk-import' => $PVE::StorageTunnel
::cmd_schema-
>{'query-disk-import'},
2698 bwlimit
=> $PVE::StorageTunnel
::cmd_schema-
>{bwlimit
},
2701 my $cmd_handlers = {
2703 # compared against other end's version
2704 # bump/reset for breaking changes
2705 # bump/bump for opt-in changes
2707 api
=> $PVE::LXC
::Migrate
::WS_TUNNEL_VERSION
,
2714 # parse and write out VM FW config if given
2715 if (my $fw_conf = $params->{'firewall-config'}) {
2716 my ($path, $fh) = PVE
::Tools
::tempfile_contents
($fw_conf, 700);
2723 ipset_comments
=> {},
2725 my $cluster_fw_conf = PVE
::Firewall
::load_clusterfw_conf
();
2727 # TODO: add flag for strict parsing?
2728 # TODO: add import sub that does all this given raw content?
2729 my $vmfw_conf = PVE
::Firewall
::generic_fw_config_parser
($path, $cluster_fw_conf, $empty_conf, 'vm');
2730 $vmfw_conf->{vmid
} = $state->{vmid
};
2731 PVE
::Firewall
::save_vmfw_conf
($state->{vmid
}, $vmfw_conf);
2733 $state->{cleanup
}->{fw
} = 1;
2736 my $conf_fn = "incoming/lxc/$state->{vmid}.conf";
2737 my $new_conf = PVE
::LXC
::Config
::parse_pct_config
($conf_fn, $params->{conf
}, 1);
2738 delete $new_conf->{lock};
2739 delete $new_conf->{digest
};
2741 my $unprivileged = delete $new_conf->{unprivileged
};
2742 my $arch = delete $new_conf->{arch
};
2744 # TODO handle properly?
2745 delete $new_conf->{snapshots
};
2746 delete $new_conf->{parent
};
2747 delete $new_conf->{pending
};
2748 delete $new_conf->{lxc
};
2750 PVE
::LXC
::Config-
>remove_lock($state->{vmid
}, 'create');
2754 unprivileged
=> $unprivileged,
2757 PVE
::LXC
::check_ct_modify_config_perm
(
2767 my $errors = PVE
::LXC
::Config-
>update_pct_config(
2775 raise_param_exc
($errors) if scalar(keys %$errors);
2776 PVE
::LXC
::Config-
>write_config($state->{vmid
}, $conf);
2777 PVE
::LXC
::update_lxc_config
($vmid, $conf);
2780 # revert to locked previous config
2781 my $conf = PVE
::LXC
::Config-
>load_config($state->{vmid
});
2782 $conf->{lock} = 'create';
2783 PVE
::LXC
::Config-
>write_config($state->{vmid
}, $conf);
2788 my $conf = PVE
::LXC
::Config-
>load_config($state->{vmid
});
2789 $conf->{lock} = 'migrate';
2790 PVE
::LXC
::Config-
>write_config($state->{vmid
}, $conf);
2792 $state->{lock} = 'migrate';
2798 return PVE
::StorageTunnel
::handle_bwlimit
($params);
2800 'disk-import' => sub {
2803 $check_storage_access_migrate->(
2811 $params->{unix
} = "/run/pve/ct-$state->{vmid}.storage";
2813 return PVE
::StorageTunnel
::handle_disk_import
($state, $params);
2815 'query-disk-import' => sub {
2818 return PVE
::StorageTunnel
::handle_query_disk_import
($state, $params);
2821 PVE
::LXC
::Config-
>remove_lock($state->{vmid
}, $state->{lock});
2822 delete $state->{lock};
2835 PVE
::LXC
::vm_stop
($state->{vmid
}, 1, 10, 1);
2841 my $path = $params->{path
};
2843 die "Not allowed to generate ticket for unknown socket '$path'\n"
2844 if !defined($state->{sockets
}->{$path});
2846 return { ticket
=> PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$path") };
2851 if ($params->{cleanup
}) {
2852 if ($state->{cleanup
}->{fw
}) {
2853 PVE
::Firewall
::remove_vmfw_conf
($state->{vmid
});
2856 for my $volid (keys $state->{cleanup
}->{volumes
}->%*) {
2857 print "freeing volume '$volid' as part of cleanup\n";
2858 eval { PVE
::Storage
::vdisk_free
($state->{storecfg
}, $volid) };
2862 PVE
::LXC
::destroy_lxc_container
(
2871 print "switching to exit-mode, waiting for client to disconnect\n";
2878 my $socket_addr = "/run/pve/ct-$state->{vmid}.mtunnel";
2879 unlink $socket_addr;
2881 $state->{socket} = IO
::Socket
::UNIX-
>new(
2882 Type
=> SOCK_STREAM
(),
2883 Local
=> $socket_addr,
2887 $state->{socket_uid
} = getpwnam('www-data')
2888 or die "Failed to resolve user 'www-data' to numeric UID\n";
2889 chown $state->{socket_uid
}, -1, $socket_addr;
2892 print "mtunnel started\n";
2894 my $conn = eval { PVE
::Tools
::run_with_timeout
(300, sub { $state->{socket}->accept() }) };
2896 warn "Failed to accept tunnel connection - $@\n";
2898 warn "Removing tunnel socket..\n";
2899 unlink $state->{socket};
2901 warn "Removing temporary VM config..\n";
2903 PVE
::LXC
::destroy_config
($state->{vmid
});
2906 die "Exiting mtunnel\n";
2909 $state->{conn
} = $conn;
2911 my $reply_err = sub {
2914 my $reply = JSON
::encode_json
({
2915 success
=> JSON
::false
,
2918 $conn->print("$reply\n");
2922 my $reply_ok = sub {
2925 $res->{success
} = JSON
::true
;
2926 my $reply = JSON
::encode_json
($res);
2927 $conn->print("$reply\n");
2931 while (my $line = <$conn>) {
2934 # untaint, we validate below if needed
2935 ($line) = $line =~ /^(.*)$/;
2936 my $parsed = eval { JSON
::decode_json
($line) };
2938 $reply_err->("failed to parse command - $@");
2942 my $cmd = delete $parsed->{cmd
};
2943 if (!defined($cmd)) {
2944 $reply_err->("'cmd' missing");
2945 } elsif ($state->{exit}) {
2946 $reply_err->("tunnel is in exit-mode, processing '$cmd' cmd not possible");
2948 } elsif (my $handler = $cmd_handlers->{$cmd}) {
2949 print "received command '$cmd'\n";
2951 if ($cmd_desc->{$cmd}) {
2952 PVE
::JSONSchema
::validate
($parsed, $cmd_desc->{$cmd});
2956 my $res = $run_locked->($handler, $parsed);
2959 $reply_err->("failed to handle '$cmd' command - $@")
2962 $reply_err->("unknown command '$cmd' given");
2966 if ($state->{exit}) {
2967 print "mtunnel exited\n";
2969 die "mtunnel exited unexpectedly\n";
2973 my $ticket = PVE
::AccessControl
::assemble_tunnel_ticket
($authuser, "/socket/$socket_addr");
2974 my $upid = $rpcenv->fork_worker('vzmtunnel', $vmid, $authuser, $realcmd);
2979 socket => $socket_addr,
2983 __PACKAGE__-
>register_method({
2984 name
=> 'mtunnelwebsocket',
2985 path
=> '{vmid}/mtunnelwebsocket',
2988 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.",
2989 user
=> 'all', # check inside
2991 description
=> 'Migration tunnel endpoint for websocket upgrade - only for internal use by VM migration.',
2993 additionalProperties
=> 0,
2995 node
=> get_standard_option
('pve-node'),
2996 vmid
=> get_standard_option
('pve-vmid'),
2999 description
=> "unix socket to forward to",
3003 description
=> "ticket return by initial 'mtunnel' API call, or retrieved via 'ticket' tunnel command",
3010 port
=> { type
=> 'string', optional
=> 1 },
3011 socket => { type
=> 'string', optional
=> 1 },
3017 my $rpcenv = PVE
::RPCEnvironment
::get
();
3018 my $authuser = $rpcenv->get_user();
3020 my $nodename = PVE
::INotify
::nodename
();
3021 my $node = extract_param
($param, 'node');
3023 raise_param_exc
({ node
=> "node needs to be 'localhost' or local hostname '$nodename'" })
3024 if $node ne 'localhost' && $node ne $nodename;
3026 my $vmid = $param->{vmid
};
3028 PVE
::LXC
::Config-
>load_config($vmid);
3030 my $socket = $param->{socket};
3031 PVE
::AccessControl
::verify_tunnel_ticket
($param->{ticket
}, $authuser, "/socket/$socket");
3033 return { socket => $socket };