1 package PVE
::API2
::LXC
;
7 use PVE
::Tools
qw(extract_param run_command);
8 use PVE
::Exception
qw(raise raise_param_exc);
10 use PVE
::Cluster
qw(cfs_read_file);
11 use PVE
::AccessControl
;
15 use PVE
::RPCEnvironment
;
16 use PVE
::ReplicationConfig
;
19 use PVE
::LXC
::Migrate
;
20 use PVE
::GuestHelpers
;
21 use PVE
::API2
::LXC
::Config
;
22 use PVE
::API2
::LXC
::Status
;
23 use PVE
::API2
::LXC
::Snapshot
;
24 use PVE
::JSONSchema
qw(get_standard_option);
25 use base
qw(PVE::RESTHandler);
28 if (!$ENV{PVE_GENERATING_DOCS
}) {
29 require PVE
::HA
::Env
::PVE2
;
30 import PVE
::HA
::Env
::PVE2
;
31 require PVE
::HA
::Config
;
32 import PVE
::HA
::Config
;
36 __PACKAGE__-
>register_method ({
37 subclass
=> "PVE::API2::LXC::Config",
38 path
=> '{vmid}/config',
41 __PACKAGE__-
>register_method ({
42 subclass
=> "PVE::API2::LXC::Status",
43 path
=> '{vmid}/status',
46 __PACKAGE__-
>register_method ({
47 subclass
=> "PVE::API2::LXC::Snapshot",
48 path
=> '{vmid}/snapshot',
51 __PACKAGE__-
>register_method ({
52 subclass
=> "PVE::API2::Firewall::CT",
53 path
=> '{vmid}/firewall',
56 __PACKAGE__-
>register_method({
60 description
=> "LXC container index (per node).",
62 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
66 protected
=> 1, # /proc files are only readable by root
68 additionalProperties
=> 0,
70 node
=> get_standard_option
('pve-node'),
79 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
84 my $rpcenv = PVE
::RPCEnvironment
::get
();
85 my $authuser = $rpcenv->get_user();
87 my $vmstatus = PVE
::LXC
::vmstatus
();
90 foreach my $vmid (keys %$vmstatus) {
91 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
93 my $data = $vmstatus->{$vmid};
94 $data->{vmid
} = $vmid;
102 __PACKAGE__-
>register_method({
106 description
=> "Create or restore a container.",
108 user
=> 'all', # check inside
109 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
110 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
111 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
116 additionalProperties
=> 0,
117 properties
=> PVE
::LXC
::Config-
>json_config_properties({
118 node
=> get_standard_option
('pve-node'),
119 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
121 description
=> "The OS template or backup file.",
124 completion
=> \
&PVE
::LXC
::complete_os_templates
,
129 description
=> "Sets root password inside container.",
132 storage
=> get_standard_option
('pve-storage-id', {
133 description
=> "Default Storage.",
136 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
141 description
=> "Allow to overwrite existing container.",
146 description
=> "Mark this as restore task.",
150 type
=> 'string', format
=> 'pve-poolid',
151 description
=> "Add the VM to the specified pool.",
153 'ignore-unpack-errors' => {
156 description
=> "Ignore errors when extracting the template.",
158 'ssh-public-keys' => {
161 description
=> "Setup public SSH keys (one key per line, " .
165 description
=> "Override i/o bandwidth limit (in KiB/s).",
178 my $rpcenv = PVE
::RPCEnvironment
::get
();
180 my $authuser = $rpcenv->get_user();
182 my $node = extract_param
($param, 'node');
184 my $vmid = extract_param
($param, 'vmid');
186 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
188 my $bwlimit = extract_param
($param, 'bwlimit');
190 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
192 my $same_container_exists = -f
$basecfg_fn;
194 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
195 my $unprivileged = extract_param
($param, 'unprivileged');
197 my $restore = extract_param
($param, 'restore');
200 # fixme: limit allowed parameters
204 my $force = extract_param
($param, 'force');
206 if (!($same_container_exists && $restore && $force)) {
207 PVE
::Cluster
::check_vmid_unused
($vmid);
209 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
210 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
213 my $password = extract_param
($param, 'password');
215 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
216 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
218 my $pool = extract_param
($param, 'pool');
220 if (defined($pool)) {
221 $rpcenv->check_pool_exist($pool);
222 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
225 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
227 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
229 } elsif ($restore && $force && $same_container_exists &&
230 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
231 # OK: user has VM.Backup permissions, and want to restore an existing VM
236 my $ostemplate = extract_param
($param, 'ostemplate');
237 my $storage = extract_param
($param, 'storage') // 'local';
239 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
241 my $storage_cfg = cfs_read_file
("storage.cfg");
246 if ($ostemplate eq '-') {
247 die "pipe requires cli environment\n"
248 if $rpcenv->{type
} ne 'cli';
249 die "pipe can only be used with restore tasks\n"
252 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
254 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
255 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
259 my $check_and_activate_storage = sub {
262 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
264 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
265 if !$scfg->{content
}->{rootdir
};
267 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
269 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
271 $used_storages{$sid} = 1;
276 my $no_disk_param = {};
278 my $storage_only_mode = 1;
279 foreach my $opt (keys %$param) {
280 my $value = $param->{$opt};
281 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
282 # allow to use simple numbers (add default storage in that case)
283 if ($value =~ m/^\d+(\.\d+)?$/) {
284 $mp_param->{$opt} = "$storage:$value";
286 $mp_param->{$opt} = $value;
288 $storage_only_mode = 0;
289 } elsif ($opt =~ m/^unused\d+$/) {
290 warn "ignoring '$opt', cannot create/restore with unused volume\n";
291 delete $param->{$opt};
293 $no_disk_param->{$opt} = $value;
297 die "mount points configured, but 'rootfs' not set - aborting\n"
298 if !$storage_only_mode && !defined($mp_param->{rootfs
});
300 # check storage access, activate storage
301 my $delayed_mp_param = {};
302 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
303 my ($ms, $mountpoint) = @_;
305 my $volid = $mountpoint->{volume
};
306 my $mp = $mountpoint->{mp
};
308 if ($mountpoint->{type
} ne 'volume') { # bind or device
309 die "Only root can pass arbitrary filesystem paths.\n"
310 if $authuser ne 'root@pam';
312 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
313 &$check_and_activate_storage($sid);
317 # check/activate default storage
318 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
320 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
322 $conf->{unprivileged
} = 1 if $unprivileged;
324 my $check_vmid_usage = sub {
326 die "can't overwrite running container\n"
327 if PVE
::LXC
::check_running
($vmid);
329 PVE
::Cluster
::check_vmid_unused
($vmid);
334 &$check_vmid_usage(); # final check after locking
337 my $config_fn = PVE
::LXC
::Config-
>config_file($vmid);
339 die "container exists" if !$restore; # just to be sure
340 $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
343 # try to create empty config on local node, we have an flock
344 PVE
::LXC
::Config-
>write_config($vmid, {});
347 # another node was faster, abort
348 die "Could not reserve ID $vmid, already taken\n" if $@;
351 PVE
::Cluster
::check_cfs_quorum
();
355 if ($storage_only_mode) {
357 (undef, $mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
358 die "rootfs configuration could not be recovered, please check and specify manually!\n"
359 if !defined($mp_param->{rootfs
});
360 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
361 my ($ms, $mountpoint) = @_;
362 my $type = $mountpoint->{type
};
363 if ($type eq 'volume') {
364 die "unable to detect disk size - please specify $ms (size)\n"
365 if !defined($mountpoint->{size
});
366 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
367 delete $mountpoint->{size
};
368 $mountpoint->{volume
} = "$storage:$disksize";
369 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
371 my $type = $mountpoint->{type
};
372 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
373 if ($ms eq 'rootfs');
374 die "restoring '$ms' to $type mount is only possible for root\n"
375 if $authuser ne 'root@pam';
377 if ($mountpoint->{backup
}) {
378 warn "WARNING - unsupported configuration!\n";
379 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
380 warn "mount point configuration will be restored after archive extraction!\n";
381 warn "contained files will be restored to wrong directory!\n";
383 delete $mp_param->{$ms}; # actually delay bind/dev mps
384 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
388 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
392 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
394 if (defined($old_conf)) {
395 # destroy old container volumes
396 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, {});
400 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
401 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
402 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
405 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, $authuser ne 'root@pam');
407 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
408 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
409 $lxc_setup->post_create_hook($password, $ssh_keys);
413 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
414 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
417 $conf->{hostname
} ||= "CT$vmid";
418 $conf->{memory
} ||= 512;
419 $conf->{swap
} //= 512;
420 foreach my $mp (keys %$delayed_mp_param) {
421 $conf->{$mp} = $delayed_mp_param->{$mp};
423 PVE
::LXC
::Config-
>write_config($vmid, $conf);
426 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
427 PVE
::LXC
::destroy_config
($vmid);
430 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
433 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
435 &$check_vmid_usage(); # first check before locking
437 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
438 $vmid, $authuser, $realcmd);
442 __PACKAGE__-
>register_method({
447 description
=> "Directory index",
452 additionalProperties
=> 0,
454 node
=> get_standard_option
('pve-node'),
455 vmid
=> get_standard_option
('pve-vmid'),
463 subdir
=> { type
=> 'string' },
466 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
472 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
475 { subdir
=> 'config' },
476 { subdir
=> 'status' },
477 { subdir
=> 'vncproxy' },
478 { subdir
=> 'termproxy' },
479 { subdir
=> 'vncwebsocket' },
480 { subdir
=> 'spiceproxy' },
481 { subdir
=> 'migrate' },
482 { subdir
=> 'clone' },
483 # { subdir => 'initlog' },
485 { subdir
=> 'rrddata' },
486 { subdir
=> 'firewall' },
487 { subdir
=> 'snapshot' },
488 { subdir
=> 'resize' },
495 __PACKAGE__-
>register_method({
497 path
=> '{vmid}/rrd',
499 protected
=> 1, # fixme: can we avoid that?
501 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
503 description
=> "Read VM RRD statistics (returns PNG)",
505 additionalProperties
=> 0,
507 node
=> get_standard_option
('pve-node'),
508 vmid
=> get_standard_option
('pve-vmid'),
510 description
=> "Specify the time frame you are interested in.",
512 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
515 description
=> "The list of datasources you want to display.",
516 type
=> 'string', format
=> 'pve-configid-list',
519 description
=> "The RRD consolidation function",
521 enum
=> [ 'AVERAGE', 'MAX' ],
529 filename
=> { type
=> 'string' },
535 return PVE
::Cluster
::create_rrd_graph
(
536 "pve2-vm/$param->{vmid}", $param->{timeframe
},
537 $param->{ds
}, $param->{cf
});
541 __PACKAGE__-
>register_method({
543 path
=> '{vmid}/rrddata',
545 protected
=> 1, # fixme: can we avoid that?
547 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
549 description
=> "Read VM RRD statistics",
551 additionalProperties
=> 0,
553 node
=> get_standard_option
('pve-node'),
554 vmid
=> get_standard_option
('pve-vmid'),
556 description
=> "Specify the time frame you are interested in.",
558 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
561 description
=> "The RRD consolidation function",
563 enum
=> [ 'AVERAGE', 'MAX' ],
578 return PVE
::Cluster
::create_rrd_data
(
579 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
582 __PACKAGE__-
>register_method({
583 name
=> 'destroy_vm',
588 description
=> "Destroy the container (also delete all uses files).",
590 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
593 additionalProperties
=> 0,
595 node
=> get_standard_option
('pve-node'),
596 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
605 my $rpcenv = PVE
::RPCEnvironment
::get
();
607 my $authuser = $rpcenv->get_user();
609 my $vmid = $param->{vmid
};
611 # test if container exists
612 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
614 my $storage_cfg = cfs_read_file
("storage.cfg");
616 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
618 die "unable to remove CT $vmid - used in HA resources\n"
619 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
621 # do not allow destroy if there are replication jobs
622 my $repl_conf = PVE
::ReplicationConfig-
>new();
623 $repl_conf->check_for_existing_jobs($vmid);
625 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
627 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
630 # reload config after lock
631 $conf = PVE
::LXC
::Config-
>load_config($vmid);
632 PVE
::LXC
::Config-
>check_lock($conf);
634 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
636 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
637 PVE
::AccessControl
::remove_vm_access
($vmid);
638 PVE
::Firewall
::remove_vmfw_conf
($vmid);
641 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
643 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
648 __PACKAGE__-
>register_method ({
650 path
=> '{vmid}/vncproxy',
654 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
656 description
=> "Creates a TCP VNC proxy connections.",
658 additionalProperties
=> 0,
660 node
=> get_standard_option
('pve-node'),
661 vmid
=> get_standard_option
('pve-vmid'),
665 description
=> "use websocket instead of standard VNC.",
669 description
=> "sets the width of the console in pixels.",
676 description
=> "sets the height of the console in pixels.",
684 additionalProperties
=> 0,
686 user
=> { type
=> 'string' },
687 ticket
=> { type
=> 'string' },
688 cert
=> { type
=> 'string' },
689 port
=> { type
=> 'integer' },
690 upid
=> { type
=> 'string' },
696 my $rpcenv = PVE
::RPCEnvironment
::get
();
698 my $authuser = $rpcenv->get_user();
700 my $vmid = $param->{vmid
};
701 my $node = $param->{node
};
703 my $authpath = "/vms/$vmid";
705 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
707 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
710 my ($remip, $family);
712 if ($node ne PVE
::INotify
::nodename
()) {
713 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
715 $family = PVE
::Tools
::get_host_address_family
($node);
718 my $port = PVE
::Tools
::next_vnc_port
($family);
720 # NOTE: vncterm VNC traffic is already TLS encrypted,
721 # so we select the fastest chipher here (or 'none'?)
722 my $remcmd = $remip ?
723 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
725 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
726 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, 1);
728 my $shcmd = [ '/usr/bin/dtach', '-A',
729 "/var/run/dtach/vzctlconsole$vmid",
730 '-r', 'winch', '-z', @$concmd];
735 syslog
('info', "starting lxc vnc proxy $upid\n");
739 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
740 '-timeout', $timeout, '-authpath', $authpath,
741 '-perm', 'VM.Console'];
743 if ($param->{width
}) {
744 push @$cmd, '-width', $param->{width
};
747 if ($param->{height
}) {
748 push @$cmd, '-height', $param->{height
};
751 if ($param->{websocket
}) {
752 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
753 push @$cmd, '-notls', '-listen', 'localhost';
756 push @$cmd, '-c', @$remcmd, @$shcmd;
758 run_command
($cmd, keeplocale
=> 1);
763 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
765 PVE
::Tools
::wait_for_vnc_port
($port);
776 __PACKAGE__-
>register_method ({
778 path
=> '{vmid}/termproxy',
782 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
784 description
=> "Creates a TCP proxy connection.",
786 additionalProperties
=> 0,
788 node
=> get_standard_option
('pve-node'),
789 vmid
=> get_standard_option
('pve-vmid'),
793 additionalProperties
=> 0,
795 user
=> { type
=> 'string' },
796 ticket
=> { type
=> 'string' },
797 port
=> { type
=> 'integer' },
798 upid
=> { type
=> 'string' },
804 my $rpcenv = PVE
::RPCEnvironment
::get
();
806 my $authuser = $rpcenv->get_user();
808 my $vmid = $param->{vmid
};
809 my $node = $param->{node
};
811 my $authpath = "/vms/$vmid";
813 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
815 my ($remip, $family);
817 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
818 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
820 $family = PVE
::Tools
::get_host_address_family
($node);
823 my $port = PVE
::Tools
::next_vnc_port
($family);
825 my $remcmd = $remip ?
826 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
828 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
829 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, 1);
831 my $shcmd = [ '/usr/bin/dtach', '-A',
832 "/var/run/dtach/vzctlconsole$vmid",
833 '-r', 'winch', '-z', @$concmd];
838 syslog
('info', "starting lxc termproxy $upid\n");
840 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
841 '--perm', 'VM.Console', '--'];
842 push @$cmd, @$remcmd, @$shcmd;
844 PVE
::Tools
::run_command
($cmd);
847 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
849 PVE
::Tools
::wait_for_vnc_port
($port);
859 __PACKAGE__-
>register_method({
860 name
=> 'vncwebsocket',
861 path
=> '{vmid}/vncwebsocket',
864 description
=> "You also need to pass a valid ticket (vncticket).",
865 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
867 description
=> "Opens a weksocket for VNC traffic.",
869 additionalProperties
=> 0,
871 node
=> get_standard_option
('pve-node'),
872 vmid
=> get_standard_option
('pve-vmid'),
874 description
=> "Ticket from previous call to vncproxy.",
879 description
=> "Port number returned by previous vncproxy call.",
889 port
=> { type
=> 'string' },
895 my $rpcenv = PVE
::RPCEnvironment
::get
();
897 my $authuser = $rpcenv->get_user();
899 my $authpath = "/vms/$param->{vmid}";
901 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
903 my $port = $param->{port
};
905 return { port
=> $port };
908 __PACKAGE__-
>register_method ({
909 name
=> 'spiceproxy',
910 path
=> '{vmid}/spiceproxy',
915 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
917 description
=> "Returns a SPICE configuration to connect to the CT.",
919 additionalProperties
=> 0,
921 node
=> get_standard_option
('pve-node'),
922 vmid
=> get_standard_option
('pve-vmid'),
923 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
926 returns
=> get_standard_option
('remote-viewer-config'),
930 my $vmid = $param->{vmid
};
931 my $node = $param->{node
};
932 my $proxy = $param->{proxy
};
934 my $authpath = "/vms/$vmid";
935 my $permissions = 'VM.Console';
937 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
939 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
941 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
943 my $shcmd = ['/usr/bin/dtach', '-A',
944 "/var/run/dtach/vzctlconsole$vmid",
945 '-r', 'winch', '-z', @$concmd];
947 my $title = "CT $vmid";
949 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
953 __PACKAGE__-
>register_method({
954 name
=> 'migrate_vm',
955 path
=> '{vmid}/migrate',
959 description
=> "Migrate the container to another node. Creates a new migration task.",
961 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
964 additionalProperties
=> 0,
966 node
=> get_standard_option
('pve-node'),
967 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
968 target
=> get_standard_option
('pve-node', {
969 description
=> "Target node.",
970 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
974 description
=> "Use online/live migration.",
979 description
=> "Use restart migration",
984 description
=> "Timeout in seconds for shutdown for restart migration",
990 description
=> "Force migration despite local bind / device" .
991 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
998 description
=> "the task ID.",
1003 my $rpcenv = PVE
::RPCEnvironment
::get
();
1005 my $authuser = $rpcenv->get_user();
1007 my $target = extract_param
($param, 'target');
1009 my $localnode = PVE
::INotify
::nodename
();
1010 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1012 PVE
::Cluster
::check_cfs_quorum
();
1014 PVE
::Cluster
::check_node_exists
($target);
1016 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1018 my $vmid = extract_param
($param, 'vmid');
1021 PVE
::LXC
::Config-
>load_config($vmid);
1023 # try to detect errors early
1024 if (PVE
::LXC
::check_running
($vmid)) {
1025 die "can't migrate running container without --online or --restart\n"
1026 if !$param->{online
} && !$param->{restart
};
1029 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1034 my $service = "ct:$vmid";
1036 my $cmd = ['ha-manager', 'migrate', $service, $target];
1038 print "Requesting HA migration for CT $vmid to node $target\n";
1040 PVE
::Tools
::run_command
($cmd);
1045 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1050 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1054 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1057 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1061 __PACKAGE__-
>register_method({
1062 name
=> 'vm_feature',
1063 path
=> '{vmid}/feature',
1067 description
=> "Check if feature for virtual machine is available.",
1069 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1072 additionalProperties
=> 0,
1074 node
=> get_standard_option
('pve-node'),
1075 vmid
=> get_standard_option
('pve-vmid'),
1077 description
=> "Feature to check.",
1079 enum
=> [ 'snapshot', 'clone', 'copy' ],
1081 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1089 hasFeature
=> { type
=> 'boolean' },
1092 #items => { type => 'string' },
1099 my $node = extract_param
($param, 'node');
1101 my $vmid = extract_param
($param, 'vmid');
1103 my $snapname = extract_param
($param, 'snapname');
1105 my $feature = extract_param
($param, 'feature');
1107 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1110 my $snap = $conf->{snapshots
}->{$snapname};
1111 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1114 my $storage_cfg = PVE
::Storage
::config
();
1115 #Maybe include later
1116 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1117 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1120 hasFeature
=> $hasFeature,
1121 #nodes => [ keys %$nodelist ],
1125 __PACKAGE__-
>register_method({
1127 path
=> '{vmid}/template',
1131 description
=> "Create a Template.",
1133 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1134 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1137 additionalProperties
=> 0,
1139 node
=> get_standard_option
('pve-node'),
1140 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1143 returns
=> { type
=> 'null'},
1147 my $rpcenv = PVE
::RPCEnvironment
::get
();
1149 my $authuser = $rpcenv->get_user();
1151 my $node = extract_param
($param, 'node');
1153 my $vmid = extract_param
($param, 'vmid');
1155 my $updatefn = sub {
1157 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1158 PVE
::LXC
::Config-
>check_lock($conf);
1160 die "unable to create template, because CT contains snapshots\n"
1161 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1163 die "you can't convert a template to a template\n"
1164 if PVE
::LXC
::Config-
>is_template($conf);
1166 die "you can't convert a CT to template if the CT is running\n"
1167 if PVE
::LXC
::check_running
($vmid);
1170 PVE
::LXC
::template_create
($vmid, $conf);
1172 $conf->{template
} = 1;
1174 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1175 # and remove lxc config
1176 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1179 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1182 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1187 __PACKAGE__-
>register_method({
1189 path
=> '{vmid}/clone',
1193 description
=> "Create a container clone/copy",
1195 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1196 "and 'VM.Allocate' permissions " .
1197 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1198 "'Datastore.AllocateSpace' on any used storage.",
1201 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1203 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1204 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1209 additionalProperties
=> 0,
1211 node
=> get_standard_option
('pve-node'),
1212 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1213 newid
=> get_standard_option
('pve-vmid', {
1214 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1215 description
=> 'VMID for the clone.' }),
1218 type
=> 'string', format
=> 'dns-name',
1219 description
=> "Set a hostname for the new CT.",
1224 description
=> "Description for the new CT.",
1228 type
=> 'string', format
=> 'pve-poolid',
1229 description
=> "Add the new CT to the specified pool.",
1231 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1234 storage
=> get_standard_option
('pve-storage-id', {
1235 description
=> "Target storage for full clone.",
1241 description
=> "Create a full copy of all disks. This is always done when " .
1242 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1244 target
=> get_standard_option
('pve-node', {
1245 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1256 my $rpcenv = PVE
::RPCEnvironment
::get
();
1258 my $authuser = $rpcenv->get_user();
1260 my $node = extract_param
($param, 'node');
1262 my $vmid = extract_param
($param, 'vmid');
1264 my $newid = extract_param
($param, 'newid');
1266 my $pool = extract_param
($param, 'pool');
1268 if (defined($pool)) {
1269 $rpcenv->check_pool_exist($pool);
1272 my $snapname = extract_param
($param, 'snapname');
1274 my $storage = extract_param
($param, 'storage');
1276 my $target = extract_param
($param, 'target');
1278 my $localnode = PVE
::INotify
::nodename
();
1280 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1282 PVE
::Cluster
::check_node_exists
($target) if $target;
1284 my $storecfg = PVE
::Storage
::config
();
1287 # check if storage is enabled on local node
1288 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1290 # check if storage is available on target node
1291 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1292 # clone only works if target storage is shared
1293 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1294 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1298 PVE
::Cluster
::check_cfs_quorum
();
1302 my $mountpoints = {};
1307 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1308 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1310 $running = PVE
::LXC
::check_running
($vmid) || 0;
1312 my $full = extract_param
($param, 'full');
1313 if (!defined($full)) {
1314 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1316 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1319 die "snapshot '$snapname' does not exist\n"
1320 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1323 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1325 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1326 die "unable to create CT $newid: config file already exists\n"
1330 foreach my $opt (keys %$src_conf) {
1331 next if $opt =~ m/^unused\d+$/;
1333 my $value = $src_conf->{$opt};
1335 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1336 my $mp = $opt eq 'rootfs' ?
1337 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1338 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1340 if ($mp->{type
} eq 'volume') {
1341 my $volid = $mp->{volume
};
1343 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1344 $sid = $storage if defined($storage);
1345 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1346 if (!$scfg->{shared
}) {
1348 warn "found non-shared volume: $volid\n" if $target;
1351 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1354 die "Cannot do full clones on a running container without snapshots\n"
1355 if $running && !defined($snapname);
1356 $fullclone->{$opt} = 1;
1358 # not full means clone instead of copy
1359 die "Linked clone feature for '$volid' is not available\n"
1360 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1363 $mountpoints->{$opt} = $mp;
1364 push @$vollist, $volid;
1367 # TODO: allow bind mounts?
1368 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1370 } elsif ($opt =~ m/^net(\d+)$/) {
1371 # always change MAC! address
1372 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1373 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1374 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1375 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1377 # copy everything else
1378 $newconf->{$opt} = $value;
1381 die "can't clone CT to node '$target' (CT uses local storage)\n"
1382 if $target && !$sharedvm;
1384 # Replace the 'disk' lock with a 'create' lock.
1385 $newconf->{lock} = 'create';
1387 delete $newconf->{template
};
1388 if ($param->{hostname
}) {
1389 $newconf->{hostname
} = $param->{hostname
};
1392 if ($param->{description
}) {
1393 $newconf->{description
} = $param->{description
};
1396 # create empty/temp config - this fails if CT already exists on other node
1397 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1400 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1406 my $update_conf = sub {
1407 my ($key, $value) = @_;
1408 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1409 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1410 die "Lost 'create' config lock, aborting.\n"
1411 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1412 $conf->{$key} = $value;
1413 PVE
::LXC
::Config-
>write_config($newid, $conf);
1420 my $newvollist = [];
1422 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1423 die "unexpected state change\n" if $verify_running != $running;
1429 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1431 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1433 foreach my $opt (keys %$mountpoints) {
1434 my $mp = $mountpoints->{$opt};
1435 my $volid = $mp->{volume
};
1438 if ($fullclone->{$opt}) {
1439 print "create full clone of mountpoint $opt ($volid)\n";
1440 my $target_storage = $storage // PVE
::Storage
::parse_volume_id
($volid);
1441 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname);
1443 print "create linked clone of mount point $opt ($volid)\n";
1444 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1447 push @$newvollist, $newvolid;
1448 $mp->{volume
} = $newvolid;
1450 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1453 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1454 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1457 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1458 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1459 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1461 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1462 die "Failed to move config to node '$target' - rename failed: $!\n"
1463 if !rename($conffile, $newconffile);
1468 # Unlock the source config in any case:
1469 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1473 # Now cleanup the config & disks:
1476 sleep 1; # some storages like rbd need to wait before release volume - really?
1478 foreach my $volid (@$newvollist) {
1479 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1482 die "clone failed: $err";
1488 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1489 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1493 __PACKAGE__-
>register_method({
1494 name
=> 'resize_vm',
1495 path
=> '{vmid}/resize',
1499 description
=> "Resize a container mount point.",
1501 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1504 additionalProperties
=> 0,
1506 node
=> get_standard_option
('pve-node'),
1507 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1510 description
=> "The disk you want to resize.",
1511 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1515 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1516 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.",
1520 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1528 description
=> "the task ID.",
1533 my $rpcenv = PVE
::RPCEnvironment
::get
();
1535 my $authuser = $rpcenv->get_user();
1537 my $node = extract_param
($param, 'node');
1539 my $vmid = extract_param
($param, 'vmid');
1541 my $digest = extract_param
($param, 'digest');
1543 my $sizestr = extract_param
($param, 'size');
1544 my $ext = ($sizestr =~ s/^\+//);
1545 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1546 die "invalid size string" if !defined($newsize);
1548 die "no options specified\n" if !scalar(keys %$param);
1550 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1552 my $storage_cfg = cfs_read_file
("storage.cfg");
1556 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1557 PVE
::LXC
::Config-
>check_lock($conf);
1559 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1561 my $running = PVE
::LXC
::check_running
($vmid);
1563 my $disk = $param->{disk
};
1564 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1565 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1567 my $volid = $mp->{volume
};
1569 my (undef, undef, $owner, undef, undef, undef, $format) =
1570 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1572 die "can't resize mount point owned by another container ($owner)"
1575 die "can't resize volume: $disk if snapshot exists\n"
1576 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1578 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1580 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1582 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1584 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1585 $newsize += $size if $ext;
1586 $newsize = int($newsize);
1588 die "unable to shrink disk size\n" if $newsize < $size;
1590 return if $size == $newsize;
1592 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1594 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1595 # we pass 0 here (parameter only makes sense for qemu)
1596 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1598 $mp->{size
} = $newsize;
1599 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1601 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1603 if ($format eq 'raw') {
1604 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1608 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1609 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1610 die "internal error: CT running but mount point not attached to a loop device"
1612 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1614 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1615 # to be visible to it in its namespace.
1616 # To not interfere with the rest of the system we unshare the current mount namespace,
1617 # mount over /tmp and then run resize2fs.
1619 # interestingly we don't need to e2fsck on mounted systems...
1620 my $quoted = PVE
::Tools
::shellquote
($path);
1621 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1623 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1625 warn "Failed to update the container's filesystem: $@\n" if $@;
1628 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1629 PVE
::Tools
::run_command
(['resize2fs', $path]);
1631 warn "Failed to update the container's filesystem: $@\n" if $@;
1636 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1639 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1642 __PACKAGE__-
>register_method({
1643 name
=> 'move_volume',
1644 path
=> '{vmid}/move_volume',
1648 description
=> "Move a rootfs-/mp-volume to a different storage",
1650 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1651 "and 'Datastore.AllocateSpace' permissions on the storage.",
1654 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1655 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1659 additionalProperties
=> 0,
1661 node
=> get_standard_option
('pve-node'),
1662 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1665 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1666 description
=> "Volume which will be moved.",
1668 storage
=> get_standard_option
('pve-storage-id', {
1669 description
=> "Target Storage.",
1670 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1674 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1680 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1692 my $rpcenv = PVE
::RPCEnvironment
::get
();
1694 my $authuser = $rpcenv->get_user();
1696 my $vmid = extract_param
($param, 'vmid');
1698 my $storage = extract_param
($param, 'storage');
1700 my $mpkey = extract_param
($param, 'volume');
1702 my $lockname = 'disk';
1704 my ($mpdata, $old_volid);
1706 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1707 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1708 PVE
::LXC
::Config-
>check_lock($conf);
1710 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1712 if ($mpkey eq 'rootfs') {
1713 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1714 } elsif ($mpkey =~ m/mp\d+/) {
1715 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1717 die "Can't parse $mpkey\n";
1719 $old_volid = $mpdata->{volume
};
1721 die "you can't move a volume with snapshots and delete the source\n"
1722 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1724 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1726 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1731 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1733 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1734 my $storage_cfg = PVE
::Storage
::config
();
1739 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1740 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf);
1741 $mpdata->{volume
} = $new_volid;
1743 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1744 my $digest = $conf->{digest
};
1745 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1746 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1748 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1750 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1752 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1756 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1757 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1763 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1764 if defined($new_volid);
1770 if ($param->{delete}) {
1772 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1773 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1779 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1784 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1787 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };