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, " .
172 my $rpcenv = PVE
::RPCEnvironment
::get
();
174 my $authuser = $rpcenv->get_user();
176 my $node = extract_param
($param, 'node');
178 my $vmid = extract_param
($param, 'vmid');
180 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
182 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
184 my $same_container_exists = -f
$basecfg_fn;
186 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
187 my $unprivileged = extract_param
($param, 'unprivileged');
189 my $restore = extract_param
($param, 'restore');
192 # fixme: limit allowed parameters
196 my $force = extract_param
($param, 'force');
198 if (!($same_container_exists && $restore && $force)) {
199 PVE
::Cluster
::check_vmid_unused
($vmid);
201 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
202 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
205 my $password = extract_param
($param, 'password');
207 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
208 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
210 my $pool = extract_param
($param, 'pool');
212 if (defined($pool)) {
213 $rpcenv->check_pool_exist($pool);
214 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
217 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
219 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
221 } elsif ($restore && $force && $same_container_exists &&
222 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
223 # OK: user has VM.Backup permissions, and want to restore an existing VM
228 my $ostemplate = extract_param
($param, 'ostemplate');
229 my $storage = extract_param
($param, 'storage') // 'local';
231 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
233 my $storage_cfg = cfs_read_file
("storage.cfg");
238 if ($ostemplate eq '-') {
239 die "pipe requires cli environment\n"
240 if $rpcenv->{type
} ne 'cli';
241 die "pipe can only be used with restore tasks\n"
244 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
246 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
247 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
250 my $check_and_activate_storage = sub {
253 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
255 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
256 if !$scfg->{content
}->{rootdir
};
258 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
260 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
265 my $no_disk_param = {};
267 my $storage_only_mode = 1;
268 foreach my $opt (keys %$param) {
269 my $value = $param->{$opt};
270 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
271 # allow to use simple numbers (add default storage in that case)
272 if ($value =~ m/^\d+(\.\d+)?$/) {
273 $mp_param->{$opt} = "$storage:$value";
275 $mp_param->{$opt} = $value;
277 $storage_only_mode = 0;
278 } elsif ($opt =~ m/^unused\d+$/) {
279 warn "ignoring '$opt', cannot create/restore with unused volume\n";
280 delete $param->{$opt};
282 $no_disk_param->{$opt} = $value;
286 die "mount points configured, but 'rootfs' not set - aborting\n"
287 if !$storage_only_mode && !defined($mp_param->{rootfs
});
289 # check storage access, activate storage
290 my $delayed_mp_param = {};
291 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
292 my ($ms, $mountpoint) = @_;
294 my $volid = $mountpoint->{volume
};
295 my $mp = $mountpoint->{mp
};
297 if ($mountpoint->{type
} ne 'volume') { # bind or device
298 die "Only root can pass arbitrary filesystem paths.\n"
299 if $authuser ne 'root@pam';
301 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
302 &$check_and_activate_storage($sid);
306 # check/activate default storage
307 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
309 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
311 $conf->{unprivileged
} = 1 if $unprivileged;
313 my $check_vmid_usage = sub {
315 die "can't overwrite running container\n"
316 if PVE
::LXC
::check_running
($vmid);
318 PVE
::Cluster
::check_vmid_unused
($vmid);
323 &$check_vmid_usage(); # final check after locking
326 my $config_fn = PVE
::LXC
::Config-
>config_file($vmid);
328 die "container exists" if !$restore; # just to be sure
329 $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
332 # try to create empty config on local node, we have an flock
333 PVE
::LXC
::Config-
>write_config($vmid, {});
336 # another node was faster, abort
337 die "Could not reserve ID $vmid, already taken\n" if $@;
340 PVE
::Cluster
::check_cfs_quorum
();
344 if ($storage_only_mode) {
346 (undef, $mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
347 die "rootfs configuration could not be recovered, please check and specify manually!\n"
348 if !defined($mp_param->{rootfs
});
349 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
350 my ($ms, $mountpoint) = @_;
351 my $type = $mountpoint->{type
};
352 if ($type eq 'volume') {
353 die "unable to detect disk size - please specify $ms (size)\n"
354 if !defined($mountpoint->{size
});
355 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
356 delete $mountpoint->{size
};
357 $mountpoint->{volume
} = "$storage:$disksize";
358 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
360 my $type = $mountpoint->{type
};
361 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
362 if ($ms eq 'rootfs');
363 die "restoring '$ms' to $type mount is only possible for root\n"
364 if $authuser ne 'root@pam';
366 if ($mountpoint->{backup
}) {
367 warn "WARNING - unsupported configuration!\n";
368 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
369 warn "mount point configuration will be restored after archive extraction!\n";
370 warn "contained files will be restored to wrong directory!\n";
372 delete $mp_param->{$ms}; # actually delay bind/dev mps
373 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
377 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
381 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
383 if (defined($old_conf)) {
384 # destroy old container volumes
385 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, {});
389 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
390 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors);
393 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, $authuser ne 'root@pam');
395 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
396 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
397 $lxc_setup->post_create_hook($password, $ssh_keys);
401 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
402 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
405 $conf->{hostname
} ||= "CT$vmid";
406 $conf->{memory
} ||= 512;
407 $conf->{swap
} //= 512;
408 foreach my $mp (keys %$delayed_mp_param) {
409 $conf->{$mp} = $delayed_mp_param->{$mp};
411 PVE
::LXC
::Config-
>write_config($vmid, $conf);
414 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
415 PVE
::LXC
::destroy_config
($vmid);
418 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
421 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
423 &$check_vmid_usage(); # first check before locking
425 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
426 $vmid, $authuser, $realcmd);
430 __PACKAGE__-
>register_method({
435 description
=> "Directory index",
440 additionalProperties
=> 0,
442 node
=> get_standard_option
('pve-node'),
443 vmid
=> get_standard_option
('pve-vmid'),
451 subdir
=> { type
=> 'string' },
454 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
460 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
463 { subdir
=> 'config' },
464 { subdir
=> 'status' },
465 { subdir
=> 'vncproxy' },
466 { subdir
=> 'termproxy' },
467 { subdir
=> 'vncwebsocket' },
468 { subdir
=> 'spiceproxy' },
469 { subdir
=> 'migrate' },
470 { subdir
=> 'clone' },
471 # { subdir => 'initlog' },
473 { subdir
=> 'rrddata' },
474 { subdir
=> 'firewall' },
475 { subdir
=> 'snapshot' },
476 { subdir
=> 'resize' },
483 __PACKAGE__-
>register_method({
485 path
=> '{vmid}/rrd',
487 protected
=> 1, # fixme: can we avoid that?
489 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
491 description
=> "Read VM RRD statistics (returns PNG)",
493 additionalProperties
=> 0,
495 node
=> get_standard_option
('pve-node'),
496 vmid
=> get_standard_option
('pve-vmid'),
498 description
=> "Specify the time frame you are interested in.",
500 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
503 description
=> "The list of datasources you want to display.",
504 type
=> 'string', format
=> 'pve-configid-list',
507 description
=> "The RRD consolidation function",
509 enum
=> [ 'AVERAGE', 'MAX' ],
517 filename
=> { type
=> 'string' },
523 return PVE
::Cluster
::create_rrd_graph
(
524 "pve2-vm/$param->{vmid}", $param->{timeframe
},
525 $param->{ds
}, $param->{cf
});
529 __PACKAGE__-
>register_method({
531 path
=> '{vmid}/rrddata',
533 protected
=> 1, # fixme: can we avoid that?
535 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
537 description
=> "Read VM RRD statistics",
539 additionalProperties
=> 0,
541 node
=> get_standard_option
('pve-node'),
542 vmid
=> get_standard_option
('pve-vmid'),
544 description
=> "Specify the time frame you are interested in.",
546 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
549 description
=> "The RRD consolidation function",
551 enum
=> [ 'AVERAGE', 'MAX' ],
566 return PVE
::Cluster
::create_rrd_data
(
567 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
570 __PACKAGE__-
>register_method({
571 name
=> 'destroy_vm',
576 description
=> "Destroy the container (also delete all uses files).",
578 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
581 additionalProperties
=> 0,
583 node
=> get_standard_option
('pve-node'),
584 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
593 my $rpcenv = PVE
::RPCEnvironment
::get
();
595 my $authuser = $rpcenv->get_user();
597 my $vmid = $param->{vmid
};
599 # test if container exists
600 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
602 my $storage_cfg = cfs_read_file
("storage.cfg");
604 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
606 die "unable to remove CT $vmid - used in HA resources\n"
607 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
609 # do not allow destroy if there are replication jobs
610 my $repl_conf = PVE
::ReplicationConfig-
>new();
611 $repl_conf->check_for_existing_jobs($vmid);
613 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
615 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
618 # reload config after lock
619 $conf = PVE
::LXC
::Config-
>load_config($vmid);
620 PVE
::LXC
::Config-
>check_lock($conf);
622 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
624 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
625 PVE
::AccessControl
::remove_vm_access
($vmid);
626 PVE
::Firewall
::remove_vmfw_conf
($vmid);
629 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
631 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
636 __PACKAGE__-
>register_method ({
638 path
=> '{vmid}/vncproxy',
642 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
644 description
=> "Creates a TCP VNC proxy connections.",
646 additionalProperties
=> 0,
648 node
=> get_standard_option
('pve-node'),
649 vmid
=> get_standard_option
('pve-vmid'),
653 description
=> "use websocket instead of standard VNC.",
657 description
=> "sets the width of the console in pixels.",
664 description
=> "sets the height of the console in pixels.",
672 additionalProperties
=> 0,
674 user
=> { type
=> 'string' },
675 ticket
=> { type
=> 'string' },
676 cert
=> { type
=> 'string' },
677 port
=> { type
=> 'integer' },
678 upid
=> { type
=> 'string' },
684 my $rpcenv = PVE
::RPCEnvironment
::get
();
686 my $authuser = $rpcenv->get_user();
688 my $vmid = $param->{vmid
};
689 my $node = $param->{node
};
691 my $authpath = "/vms/$vmid";
693 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
695 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
698 my ($remip, $family);
700 if ($node ne PVE
::INotify
::nodename
()) {
701 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
703 $family = PVE
::Tools
::get_host_address_family
($node);
706 my $port = PVE
::Tools
::next_vnc_port
($family);
708 # NOTE: vncterm VNC traffic is already TLS encrypted,
709 # so we select the fastest chipher here (or 'none'?)
710 my $remcmd = $remip ?
711 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
713 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
714 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, 1);
716 my $shcmd = [ '/usr/bin/dtach', '-A',
717 "/var/run/dtach/vzctlconsole$vmid",
718 '-r', 'winch', '-z', @$concmd];
723 syslog
('info', "starting lxc vnc proxy $upid\n");
727 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
728 '-timeout', $timeout, '-authpath', $authpath,
729 '-perm', 'VM.Console'];
731 if ($param->{width
}) {
732 push @$cmd, '-width', $param->{width
};
735 if ($param->{height
}) {
736 push @$cmd, '-height', $param->{height
};
739 if ($param->{websocket
}) {
740 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
741 push @$cmd, '-notls', '-listen', 'localhost';
744 push @$cmd, '-c', @$remcmd, @$shcmd;
746 run_command
($cmd, keeplocale
=> 1);
751 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
753 PVE
::Tools
::wait_for_vnc_port
($port);
764 __PACKAGE__-
>register_method ({
766 path
=> '{vmid}/termproxy',
770 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
772 description
=> "Creates a TCP proxy connection.",
774 additionalProperties
=> 0,
776 node
=> get_standard_option
('pve-node'),
777 vmid
=> get_standard_option
('pve-vmid'),
781 additionalProperties
=> 0,
783 user
=> { type
=> 'string' },
784 ticket
=> { type
=> 'string' },
785 port
=> { type
=> 'integer' },
786 upid
=> { type
=> 'string' },
792 my $rpcenv = PVE
::RPCEnvironment
::get
();
794 my $authuser = $rpcenv->get_user();
796 my $vmid = $param->{vmid
};
797 my $node = $param->{node
};
799 my $authpath = "/vms/$vmid";
801 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
803 my ($remip, $family);
805 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
806 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
808 $family = PVE
::Tools
::get_host_address_family
($node);
811 my $port = PVE
::Tools
::next_vnc_port
($family);
813 my $remcmd = $remip ?
814 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
816 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
817 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, 1);
819 my $shcmd = [ '/usr/bin/dtach', '-A',
820 "/var/run/dtach/vzctlconsole$vmid",
821 '-r', 'winch', '-z', @$concmd];
826 syslog
('info', "starting lxc termproxy $upid\n");
828 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
829 '--perm', 'VM.Console', '--'];
830 push @$cmd, @$remcmd, @$shcmd;
832 PVE
::Tools
::run_command
($cmd);
835 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
837 PVE
::Tools
::wait_for_vnc_port
($port);
847 __PACKAGE__-
>register_method({
848 name
=> 'vncwebsocket',
849 path
=> '{vmid}/vncwebsocket',
852 description
=> "You also need to pass a valid ticket (vncticket).",
853 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
855 description
=> "Opens a weksocket for VNC traffic.",
857 additionalProperties
=> 0,
859 node
=> get_standard_option
('pve-node'),
860 vmid
=> get_standard_option
('pve-vmid'),
862 description
=> "Ticket from previous call to vncproxy.",
867 description
=> "Port number returned by previous vncproxy call.",
877 port
=> { type
=> 'string' },
883 my $rpcenv = PVE
::RPCEnvironment
::get
();
885 my $authuser = $rpcenv->get_user();
887 my $authpath = "/vms/$param->{vmid}";
889 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
891 my $port = $param->{port
};
893 return { port
=> $port };
896 __PACKAGE__-
>register_method ({
897 name
=> 'spiceproxy',
898 path
=> '{vmid}/spiceproxy',
903 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
905 description
=> "Returns a SPICE configuration to connect to the CT.",
907 additionalProperties
=> 0,
909 node
=> get_standard_option
('pve-node'),
910 vmid
=> get_standard_option
('pve-vmid'),
911 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
914 returns
=> get_standard_option
('remote-viewer-config'),
918 my $vmid = $param->{vmid
};
919 my $node = $param->{node
};
920 my $proxy = $param->{proxy
};
922 my $authpath = "/vms/$vmid";
923 my $permissions = 'VM.Console';
925 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
927 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
929 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
931 my $shcmd = ['/usr/bin/dtach', '-A',
932 "/var/run/dtach/vzctlconsole$vmid",
933 '-r', 'winch', '-z', @$concmd];
935 my $title = "CT $vmid";
937 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
941 __PACKAGE__-
>register_method({
942 name
=> 'migrate_vm',
943 path
=> '{vmid}/migrate',
947 description
=> "Migrate the container to another node. Creates a new migration task.",
949 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
952 additionalProperties
=> 0,
954 node
=> get_standard_option
('pve-node'),
955 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
956 target
=> get_standard_option
('pve-node', {
957 description
=> "Target node.",
958 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
962 description
=> "Use online/live migration.",
967 description
=> "Use restart migration",
972 description
=> "Timeout in seconds for shutdown for restart migration",
978 description
=> "Force migration despite local bind / device" .
979 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
986 description
=> "the task ID.",
991 my $rpcenv = PVE
::RPCEnvironment
::get
();
993 my $authuser = $rpcenv->get_user();
995 my $target = extract_param
($param, 'target');
997 my $localnode = PVE
::INotify
::nodename
();
998 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1000 PVE
::Cluster
::check_cfs_quorum
();
1002 PVE
::Cluster
::check_node_exists
($target);
1004 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1006 my $vmid = extract_param
($param, 'vmid');
1009 PVE
::LXC
::Config-
>load_config($vmid);
1011 # try to detect errors early
1012 if (PVE
::LXC
::check_running
($vmid)) {
1013 die "can't migrate running container without --online or --restart\n"
1014 if !$param->{online
} && !$param->{restart
};
1017 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1022 my $service = "ct:$vmid";
1024 my $cmd = ['ha-manager', 'migrate', $service, $target];
1026 print "Requesting HA migration for CT $vmid to node $target\n";
1028 PVE
::Tools
::run_command
($cmd);
1033 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1038 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1042 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1045 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1049 __PACKAGE__-
>register_method({
1050 name
=> 'vm_feature',
1051 path
=> '{vmid}/feature',
1055 description
=> "Check if feature for virtual machine is available.",
1057 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1060 additionalProperties
=> 0,
1062 node
=> get_standard_option
('pve-node'),
1063 vmid
=> get_standard_option
('pve-vmid'),
1065 description
=> "Feature to check.",
1067 enum
=> [ 'snapshot', 'clone', 'copy' ],
1069 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1077 hasFeature
=> { type
=> 'boolean' },
1080 #items => { type => 'string' },
1087 my $node = extract_param
($param, 'node');
1089 my $vmid = extract_param
($param, 'vmid');
1091 my $snapname = extract_param
($param, 'snapname');
1093 my $feature = extract_param
($param, 'feature');
1095 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1098 my $snap = $conf->{snapshots
}->{$snapname};
1099 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1102 my $storage_cfg = PVE
::Storage
::config
();
1103 #Maybe include later
1104 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1105 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1108 hasFeature
=> $hasFeature,
1109 #nodes => [ keys %$nodelist ],
1113 __PACKAGE__-
>register_method({
1115 path
=> '{vmid}/template',
1119 description
=> "Create a Template.",
1121 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1122 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1125 additionalProperties
=> 0,
1127 node
=> get_standard_option
('pve-node'),
1128 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1131 returns
=> { type
=> 'null'},
1135 my $rpcenv = PVE
::RPCEnvironment
::get
();
1137 my $authuser = $rpcenv->get_user();
1139 my $node = extract_param
($param, 'node');
1141 my $vmid = extract_param
($param, 'vmid');
1143 my $updatefn = sub {
1145 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1146 PVE
::LXC
::Config-
>check_lock($conf);
1148 die "unable to create template, because CT contains snapshots\n"
1149 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1151 die "you can't convert a template to a template\n"
1152 if PVE
::LXC
::Config-
>is_template($conf);
1154 die "you can't convert a CT to template if the CT is running\n"
1155 if PVE
::LXC
::check_running
($vmid);
1158 PVE
::LXC
::template_create
($vmid, $conf);
1161 $conf->{template
} = 1;
1163 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1164 # and remove lxc config
1165 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1167 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1170 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1175 __PACKAGE__-
>register_method({
1177 path
=> '{vmid}/clone',
1181 description
=> "Create a container clone/copy",
1183 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1184 "and 'VM.Allocate' permissions " .
1185 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1186 "'Datastore.AllocateSpace' on any used storage.",
1189 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1191 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1192 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1197 additionalProperties
=> 0,
1199 node
=> get_standard_option
('pve-node'),
1200 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1201 newid
=> get_standard_option
('pve-vmid', {
1202 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1203 description
=> 'VMID for the clone.' }),
1206 type
=> 'string', format
=> 'dns-name',
1207 description
=> "Set a hostname for the new CT.",
1212 description
=> "Description for the new CT.",
1216 type
=> 'string', format
=> 'pve-poolid',
1217 description
=> "Add the new CT to the specified pool.",
1219 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1222 storage
=> get_standard_option
('pve-storage-id', {
1223 description
=> "Target storage for full clone.",
1229 description
=> "Create a full copy of all disks. This is always done when " .
1230 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1232 target
=> get_standard_option
('pve-node', {
1233 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1244 my $rpcenv = PVE
::RPCEnvironment
::get
();
1246 my $authuser = $rpcenv->get_user();
1248 my $node = extract_param
($param, 'node');
1250 my $vmid = extract_param
($param, 'vmid');
1252 my $newid = extract_param
($param, 'newid');
1254 my $pool = extract_param
($param, 'pool');
1256 if (defined($pool)) {
1257 $rpcenv->check_pool_exist($pool);
1260 my $snapname = extract_param
($param, 'snapname');
1262 my $storage = extract_param
($param, 'storage');
1264 my $target = extract_param
($param, 'target');
1266 my $localnode = PVE
::INotify
::nodename
();
1268 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1270 PVE
::Cluster
::check_node_exists
($target) if $target;
1272 my $storecfg = PVE
::Storage
::config
();
1275 # check if storage is enabled on local node
1276 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1278 # check if storage is available on target node
1279 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1280 # clone only works if target storage is shared
1281 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1282 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1286 PVE
::Cluster
::check_cfs_quorum
();
1290 my $mountpoints = {};
1295 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1296 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1298 $running = PVE
::LXC
::check_running
($vmid) || 0;
1300 my $full = extract_param
($param, 'full');
1301 if (!defined($full)) {
1302 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1304 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1307 die "snapshot '$snapname' does not exist\n"
1308 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1311 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1313 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1314 die "unable to create CT $newid: config file already exists\n"
1317 foreach my $opt (keys %$src_conf) {
1318 next if $opt =~ m/^unused\d+$/;
1320 my $value = $src_conf->{$opt};
1322 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1323 my $mp = $opt eq 'rootfs' ?
1324 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1325 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1327 if ($mp->{type
} eq 'volume') {
1328 my $volid = $mp->{volume
};
1330 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1331 $sid = $storage if defined($storage);
1332 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1334 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1337 die "Cannot do full clones on a running container without snapshots\n"
1338 if $running && !defined($snapname);
1339 $fullclone->{$opt} = 1;
1341 # not full means clone instead of copy
1342 die "Linked clone feature for '$volid' is not available\n"
1343 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1346 $mountpoints->{$opt} = $mp;
1347 push @$vollist, $volid;
1350 # TODO: allow bind mounts?
1351 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1353 } elsif ($opt =~ m/^net(\d+)$/) {
1354 # always change MAC! address
1355 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1356 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1357 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1358 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1360 # copy everything else
1361 $newconf->{$opt} = $value;
1365 # Replace the 'disk' lock with a 'create' lock.
1366 $newconf->{lock} = 'create';
1368 delete $newconf->{template
};
1369 if ($param->{hostname
}) {
1370 $newconf->{hostname
} = $param->{hostname
};
1373 if ($param->{description
}) {
1374 $newconf->{description
} = $param->{description
};
1377 # create empty/temp config - this fails if CT already exists on other node
1378 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1381 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1387 my $update_conf = sub {
1388 my ($key, $value) = @_;
1389 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1390 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1391 die "Lost 'create' config lock, aborting.\n"
1392 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1393 $conf->{$key} = $value;
1394 PVE
::LXC
::Config-
>write_config($newid, $conf);
1401 my $newvollist = [];
1403 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1404 die "unexpected state change\n" if $verify_running != $running;
1410 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1412 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1414 foreach my $opt (keys %$mountpoints) {
1415 my $mp = $mountpoints->{$opt};
1416 my $volid = $mp->{volume
};
1419 if ($fullclone->{$opt}) {
1420 print "create full clone of mountpoint $opt ($volid)\n";
1421 my $target_storage = $storage // PVE
::Storage
::parse_volume_id
($volid);
1422 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname);
1424 print "create linked clone of mount point $opt ($volid)\n";
1425 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1428 push @$newvollist, $newvolid;
1429 $mp->{volume
} = $newvolid;
1431 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1434 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1435 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1438 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1439 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1440 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1442 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1443 die "Failed to move config to node '$target' - rename failed: $!\n"
1444 if !rename($conffile, $newconffile);
1449 # Unlock the source config in any case:
1450 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1454 # Now cleanup the config & disks:
1457 sleep 1; # some storages like rbd need to wait before release volume - really?
1459 foreach my $volid (@$newvollist) {
1460 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1463 die "clone failed: $err";
1469 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1470 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1474 __PACKAGE__-
>register_method({
1475 name
=> 'resize_vm',
1476 path
=> '{vmid}/resize',
1480 description
=> "Resize a container mount point.",
1482 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1485 additionalProperties
=> 0,
1487 node
=> get_standard_option
('pve-node'),
1488 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1491 description
=> "The disk you want to resize.",
1492 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1496 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1497 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.",
1501 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1509 description
=> "the task ID.",
1514 my $rpcenv = PVE
::RPCEnvironment
::get
();
1516 my $authuser = $rpcenv->get_user();
1518 my $node = extract_param
($param, 'node');
1520 my $vmid = extract_param
($param, 'vmid');
1522 my $digest = extract_param
($param, 'digest');
1524 my $sizestr = extract_param
($param, 'size');
1525 my $ext = ($sizestr =~ s/^\+//);
1526 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1527 die "invalid size string" if !defined($newsize);
1529 die "no options specified\n" if !scalar(keys %$param);
1531 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1533 my $storage_cfg = cfs_read_file
("storage.cfg");
1537 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1538 PVE
::LXC
::Config-
>check_lock($conf);
1540 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1542 my $running = PVE
::LXC
::check_running
($vmid);
1544 my $disk = $param->{disk
};
1545 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1546 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1548 my $volid = $mp->{volume
};
1550 my (undef, undef, $owner, undef, undef, undef, $format) =
1551 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1553 die "can't resize mount point owned by another container ($owner)"
1556 die "can't resize volume: $disk if snapshot exists\n"
1557 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1559 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1561 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1563 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1565 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1566 $newsize += $size if $ext;
1567 $newsize = int($newsize);
1569 die "unable to shrink disk size\n" if $newsize < $size;
1571 return if $size == $newsize;
1573 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1575 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1576 # we pass 0 here (parameter only makes sense for qemu)
1577 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1579 $mp->{size
} = $newsize;
1580 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1582 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1584 if ($format eq 'raw') {
1585 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1589 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1590 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1591 die "internal error: CT running but mount point not attached to a loop device"
1593 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1595 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1596 # to be visible to it in its namespace.
1597 # To not interfere with the rest of the system we unshare the current mount namespace,
1598 # mount over /tmp and then run resize2fs.
1600 # interestingly we don't need to e2fsck on mounted systems...
1601 my $quoted = PVE
::Tools
::shellquote
($path);
1602 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1604 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1606 warn "Failed to update the container's filesystem: $@\n" if $@;
1609 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1610 PVE
::Tools
::run_command
(['resize2fs', $path]);
1612 warn "Failed to update the container's filesystem: $@\n" if $@;
1617 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1620 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1623 __PACKAGE__-
>register_method({
1624 name
=> 'move_volume',
1625 path
=> '{vmid}/move_volume',
1629 description
=> "Move a rootfs-/mp-volume to a different storage",
1631 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1632 "and 'Datastore.AllocateSpace' permissions on the storage.",
1635 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1636 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1640 additionalProperties
=> 0,
1642 node
=> get_standard_option
('pve-node'),
1643 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1646 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1647 description
=> "Volume which will be moved.",
1649 storage
=> get_standard_option
('pve-storage-id', {
1650 description
=> "Target Storage.",
1651 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1655 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1661 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1673 my $rpcenv = PVE
::RPCEnvironment
::get
();
1675 my $authuser = $rpcenv->get_user();
1677 my $vmid = extract_param
($param, 'vmid');
1679 my $storage = extract_param
($param, 'storage');
1681 my $mpkey = extract_param
($param, 'volume');
1683 my $lockname = 'disk';
1685 my ($mpdata, $old_volid);
1687 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1688 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1689 PVE
::LXC
::Config-
>check_lock($conf);
1691 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1693 if ($mpkey eq 'rootfs') {
1694 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1695 } elsif ($mpkey =~ m/mp\d+/) {
1696 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1698 die "Can't parse $mpkey\n";
1700 $old_volid = $mpdata->{volume
};
1702 die "you can't move a volume with snapshots and delete the source\n"
1703 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1705 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1707 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1712 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1714 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1715 my $storage_cfg = PVE
::Storage
::config
();
1720 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1721 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf);
1722 $mpdata->{volume
} = $new_volid;
1724 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1725 my $digest = $conf->{digest
};
1726 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1727 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1729 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1731 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1733 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1737 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1738 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1744 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1745 if defined($new_volid);
1751 if ($param->{delete}) {
1753 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1754 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1760 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1765 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1768 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };