1 package PVE
::API2
::LXC
;
7 use PVE
::Tools
qw(extract_param run_command);
8 use PVE
::Exception
qw(raise raise_param_exc raise_perm_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'),
77 properties
=> $PVE::LXC
::vmstatus_return_properties
,
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};
101 __PACKAGE__-
>register_method({
105 description
=> "Create or restore a container.",
107 user
=> 'all', # check inside
108 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
109 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
110 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
115 additionalProperties
=> 0,
116 properties
=> PVE
::LXC
::Config-
>json_config_properties({
117 node
=> get_standard_option
('pve-node'),
118 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
120 description
=> "The OS template or backup file.",
123 completion
=> \
&PVE
::LXC
::complete_os_templates
,
128 description
=> "Sets root password inside container.",
131 storage
=> get_standard_option
('pve-storage-id', {
132 description
=> "Default Storage.",
135 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
140 description
=> "Allow to overwrite existing container.",
145 description
=> "Mark this as restore task.",
149 type
=> 'string', format
=> 'pve-poolid',
150 description
=> "Add the VM to the specified pool.",
152 'ignore-unpack-errors' => {
155 description
=> "Ignore errors when extracting the template.",
157 'ssh-public-keys' => {
160 description
=> "Setup public SSH keys (one key per line, " .
164 description
=> "Override i/o bandwidth limit (in KiB/s).",
173 description
=> "Start the CT after its creation finished successfully.",
183 my $rpcenv = PVE
::RPCEnvironment
::get
();
184 my $authuser = $rpcenv->get_user();
186 my $node = extract_param
($param, 'node');
187 my $vmid = extract_param
($param, 'vmid');
188 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
189 my $bwlimit = extract_param
($param, 'bwlimit');
190 my $start_after_create = extract_param
($param, 'start');
192 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
193 my $same_container_exists = -f
$basecfg_fn;
195 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
196 my $unprivileged = extract_param
($param, 'unprivileged');
197 my $restore = extract_param
($param, 'restore');
200 # fixme: limit allowed parameters
203 my $force = extract_param
($param, 'force');
205 if (!($same_container_exists && $restore && $force)) {
206 PVE
::Cluster
::check_vmid_unused
($vmid);
208 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
209 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
212 my $password = extract_param
($param, 'password');
213 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
214 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
216 my $pool = extract_param
($param, 'pool');
217 if (defined($pool)) {
218 $rpcenv->check_pool_exist($pool);
219 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
222 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
224 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
226 } elsif ($restore && $force && $same_container_exists &&
227 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
228 # OK: user has VM.Backup permissions, and want to restore an existing VM
233 my $ostemplate = extract_param
($param, 'ostemplate');
234 my $storage = extract_param
($param, 'storage') // 'local';
236 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
238 my $storage_cfg = cfs_read_file
("storage.cfg");
241 if ($ostemplate eq '-') {
242 die "pipe requires cli environment\n"
243 if $rpcenv->{type
} ne 'cli';
244 die "pipe can only be used with restore tasks\n"
247 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
249 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
250 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
254 my $check_and_activate_storage = sub {
257 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
259 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
260 if !$scfg->{content
}->{rootdir
};
262 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
264 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
265 $used_storages{$sid} = 1;
270 my $is_root = $authuser eq 'root@pam';
272 my $no_disk_param = {};
274 my $storage_only_mode = 1;
275 foreach my $opt (keys %$param) {
276 my $value = $param->{$opt};
277 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
278 # allow to use simple numbers (add default storage in that case)
279 if ($value =~ m/^\d+(\.\d+)?$/) {
280 $mp_param->{$opt} = "$storage:$value";
282 $mp_param->{$opt} = $value;
284 $storage_only_mode = 0;
285 } elsif ($opt =~ m/^unused\d+$/) {
286 warn "ignoring '$opt', cannot create/restore with unused volume\n";
287 delete $param->{$opt};
289 $no_disk_param->{$opt} = $value;
293 die "mount points configured, but 'rootfs' not set - aborting\n"
294 if !$storage_only_mode && !defined($mp_param->{rootfs
});
296 # check storage access, activate storage
297 my $delayed_mp_param = {};
298 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
299 my ($ms, $mountpoint) = @_;
301 my $volid = $mountpoint->{volume
};
302 my $mp = $mountpoint->{mp
};
304 if ($mountpoint->{type
} ne 'volume') { # bind or device
305 die "Only root can pass arbitrary filesystem paths.\n"
308 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
309 &$check_and_activate_storage($sid);
313 # check/activate default storage
314 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
316 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
318 $conf->{unprivileged
} = 1 if $unprivileged;
320 my $check_vmid_usage = sub {
322 die "can't overwrite running container\n"
323 if PVE
::LXC
::check_running
($vmid);
325 PVE
::Cluster
::check_vmid_unused
($vmid);
330 &$check_vmid_usage(); # final check after locking
333 my $config_fn = PVE
::LXC
::Config-
>config_file($vmid);
335 die "container exists" if !$restore; # just to be sure
336 $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
339 # try to create empty config on local node, we have an flock
340 PVE
::LXC
::Config-
>write_config($vmid, {});
343 # another node was faster, abort
344 die "Could not reserve ID $vmid, already taken\n" if $@;
347 PVE
::Cluster
::check_cfs_quorum
();
350 my $orig_mp_param; # only used if $restore
352 (my $orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
354 # When we're root call 'restore_configuration' with ristricted=0,
355 # causing it to restore the raw lxc entries, among which there may be
356 # 'lxc.idmap' entries. We need to make sure that the extracted contents
357 # of the container match up with the restored configuration afterwards:
358 $conf->{lxc
} = [grep { $_->[0] eq 'lxc.idmap' } @{$orig_conf->{lxc
}}];
361 if ($storage_only_mode) {
363 $mp_param = $orig_mp_param;
364 die "rootfs configuration could not be recovered, please check and specify manually!\n"
365 if !defined($mp_param->{rootfs
});
366 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
367 my ($ms, $mountpoint) = @_;
368 my $type = $mountpoint->{type
};
369 if ($type eq 'volume') {
370 die "unable to detect disk size - please specify $ms (size)\n"
371 if !defined($mountpoint->{size
});
372 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
373 delete $mountpoint->{size
};
374 $mountpoint->{volume
} = "$storage:$disksize";
375 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
377 my $type = $mountpoint->{type
};
378 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
379 if ($ms eq 'rootfs');
380 die "restoring '$ms' to $type mount is only possible for root\n"
383 if ($mountpoint->{backup
}) {
384 warn "WARNING - unsupported configuration!\n";
385 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
386 warn "mount point configuration will be restored after archive extraction!\n";
387 warn "contained files will be restored to wrong directory!\n";
389 delete $mp_param->{$ms}; # actually delay bind/dev mps
390 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
394 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
398 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
400 if (defined($old_conf)) {
401 # destroy old container volumes
402 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, {});
406 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
407 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
408 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
411 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, !$is_root);
413 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
414 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
415 $lxc_setup->post_create_hook($password, $ssh_keys);
419 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
420 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
423 $conf->{hostname
} ||= "CT$vmid";
424 $conf->{memory
} ||= 512;
425 $conf->{swap
} //= 512;
426 foreach my $mp (keys %$delayed_mp_param) {
427 $conf->{$mp} = $delayed_mp_param->{$mp};
429 PVE
::LXC
::Config-
>write_config($vmid, $conf);
432 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
433 PVE
::LXC
::destroy_config
($vmid);
436 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
438 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
439 if $start_after_create;
442 my $workername = $restore ?
'vzrestore' : 'vzcreate';
443 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
445 &$check_vmid_usage(); # first check before locking
447 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
450 __PACKAGE__-
>register_method({
455 description
=> "Directory index",
460 additionalProperties
=> 0,
462 node
=> get_standard_option
('pve-node'),
463 vmid
=> get_standard_option
('pve-vmid'),
471 subdir
=> { type
=> 'string' },
474 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
480 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
483 { subdir
=> 'config' },
484 { subdir
=> 'status' },
485 { subdir
=> 'vncproxy' },
486 { subdir
=> 'termproxy' },
487 { subdir
=> 'vncwebsocket' },
488 { subdir
=> 'spiceproxy' },
489 { subdir
=> 'migrate' },
490 { subdir
=> 'clone' },
491 # { subdir => 'initlog' },
493 { subdir
=> 'rrddata' },
494 { subdir
=> 'firewall' },
495 { subdir
=> 'snapshot' },
496 { subdir
=> 'resize' },
503 __PACKAGE__-
>register_method({
505 path
=> '{vmid}/rrd',
507 protected
=> 1, # fixme: can we avoid that?
509 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
511 description
=> "Read VM RRD statistics (returns PNG)",
513 additionalProperties
=> 0,
515 node
=> get_standard_option
('pve-node'),
516 vmid
=> get_standard_option
('pve-vmid'),
518 description
=> "Specify the time frame you are interested in.",
520 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
523 description
=> "The list of datasources you want to display.",
524 type
=> 'string', format
=> 'pve-configid-list',
527 description
=> "The RRD consolidation function",
529 enum
=> [ 'AVERAGE', 'MAX' ],
537 filename
=> { type
=> 'string' },
543 return PVE
::Cluster
::create_rrd_graph
(
544 "pve2-vm/$param->{vmid}", $param->{timeframe
},
545 $param->{ds
}, $param->{cf
});
549 __PACKAGE__-
>register_method({
551 path
=> '{vmid}/rrddata',
553 protected
=> 1, # fixme: can we avoid that?
555 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
557 description
=> "Read VM RRD statistics",
559 additionalProperties
=> 0,
561 node
=> get_standard_option
('pve-node'),
562 vmid
=> get_standard_option
('pve-vmid'),
564 description
=> "Specify the time frame you are interested in.",
566 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
569 description
=> "The RRD consolidation function",
571 enum
=> [ 'AVERAGE', 'MAX' ],
586 return PVE
::Cluster
::create_rrd_data
(
587 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
590 __PACKAGE__-
>register_method({
591 name
=> 'destroy_vm',
596 description
=> "Destroy the container (also delete all uses files).",
598 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
601 additionalProperties
=> 0,
603 node
=> get_standard_option
('pve-node'),
604 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
613 my $rpcenv = PVE
::RPCEnvironment
::get
();
615 my $authuser = $rpcenv->get_user();
617 my $vmid = $param->{vmid
};
619 # test if container exists
620 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
622 my $storage_cfg = cfs_read_file
("storage.cfg");
624 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
626 die "unable to remove CT $vmid - used in HA resources\n"
627 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
629 # do not allow destroy if there are replication jobs
630 my $repl_conf = PVE
::ReplicationConfig-
>new();
631 $repl_conf->check_for_existing_jobs($vmid);
633 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
635 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
638 # reload config after lock
639 $conf = PVE
::LXC
::Config-
>load_config($vmid);
640 PVE
::LXC
::Config-
>check_lock($conf);
642 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
644 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
645 PVE
::AccessControl
::remove_vm_access
($vmid);
646 PVE
::Firewall
::remove_vmfw_conf
($vmid);
649 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
651 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
656 __PACKAGE__-
>register_method ({
658 path
=> '{vmid}/vncproxy',
662 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
664 description
=> "Creates a TCP VNC proxy connections.",
666 additionalProperties
=> 0,
668 node
=> get_standard_option
('pve-node'),
669 vmid
=> get_standard_option
('pve-vmid'),
673 description
=> "use websocket instead of standard VNC.",
677 description
=> "sets the width of the console in pixels.",
684 description
=> "sets the height of the console in pixels.",
692 additionalProperties
=> 0,
694 user
=> { type
=> 'string' },
695 ticket
=> { type
=> 'string' },
696 cert
=> { type
=> 'string' },
697 port
=> { type
=> 'integer' },
698 upid
=> { type
=> 'string' },
704 my $rpcenv = PVE
::RPCEnvironment
::get
();
706 my $authuser = $rpcenv->get_user();
708 my $vmid = $param->{vmid
};
709 my $node = $param->{node
};
711 my $authpath = "/vms/$vmid";
713 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
715 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
718 my ($remip, $family);
720 if ($node ne PVE
::INotify
::nodename
()) {
721 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
723 $family = PVE
::Tools
::get_host_address_family
($node);
726 my $port = PVE
::Tools
::next_vnc_port
($family);
728 # NOTE: vncterm VNC traffic is already TLS encrypted,
729 # so we select the fastest chipher here (or 'none'?)
730 my $remcmd = $remip ?
731 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
733 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
734 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
736 my $shcmd = [ '/usr/bin/dtach', '-A',
737 "/var/run/dtach/vzctlconsole$vmid",
738 '-r', 'winch', '-z', @$concmd];
743 syslog
('info', "starting lxc vnc proxy $upid\n");
747 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
748 '-timeout', $timeout, '-authpath', $authpath,
749 '-perm', 'VM.Console'];
751 if ($param->{width
}) {
752 push @$cmd, '-width', $param->{width
};
755 if ($param->{height
}) {
756 push @$cmd, '-height', $param->{height
};
759 if ($param->{websocket
}) {
760 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
761 push @$cmd, '-notls', '-listen', 'localhost';
764 push @$cmd, '-c', @$remcmd, @$shcmd;
766 run_command
($cmd, keeplocale
=> 1);
771 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
773 PVE
::Tools
::wait_for_vnc_port
($port);
784 __PACKAGE__-
>register_method ({
786 path
=> '{vmid}/termproxy',
790 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
792 description
=> "Creates a TCP proxy connection.",
794 additionalProperties
=> 0,
796 node
=> get_standard_option
('pve-node'),
797 vmid
=> get_standard_option
('pve-vmid'),
801 additionalProperties
=> 0,
803 user
=> { type
=> 'string' },
804 ticket
=> { type
=> 'string' },
805 port
=> { type
=> 'integer' },
806 upid
=> { type
=> 'string' },
812 my $rpcenv = PVE
::RPCEnvironment
::get
();
814 my $authuser = $rpcenv->get_user();
816 my $vmid = $param->{vmid
};
817 my $node = $param->{node
};
819 my $authpath = "/vms/$vmid";
821 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
823 my ($remip, $family);
825 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
826 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
828 $family = PVE
::Tools
::get_host_address_family
($node);
831 my $port = PVE
::Tools
::next_vnc_port
($family);
833 my $remcmd = $remip ?
834 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
836 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
837 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
839 my $shcmd = [ '/usr/bin/dtach', '-A',
840 "/var/run/dtach/vzctlconsole$vmid",
841 '-r', 'winch', '-z', @$concmd];
846 syslog
('info', "starting lxc termproxy $upid\n");
848 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
849 '--perm', 'VM.Console', '--'];
850 push @$cmd, @$remcmd, @$shcmd;
852 PVE
::Tools
::run_command
($cmd);
855 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
857 PVE
::Tools
::wait_for_vnc_port
($port);
867 __PACKAGE__-
>register_method({
868 name
=> 'vncwebsocket',
869 path
=> '{vmid}/vncwebsocket',
872 description
=> "You also need to pass a valid ticket (vncticket).",
873 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
875 description
=> "Opens a weksocket for VNC traffic.",
877 additionalProperties
=> 0,
879 node
=> get_standard_option
('pve-node'),
880 vmid
=> get_standard_option
('pve-vmid'),
882 description
=> "Ticket from previous call to vncproxy.",
887 description
=> "Port number returned by previous vncproxy call.",
897 port
=> { type
=> 'string' },
903 my $rpcenv = PVE
::RPCEnvironment
::get
();
905 my $authuser = $rpcenv->get_user();
907 my $authpath = "/vms/$param->{vmid}";
909 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
911 my $port = $param->{port
};
913 return { port
=> $port };
916 __PACKAGE__-
>register_method ({
917 name
=> 'spiceproxy',
918 path
=> '{vmid}/spiceproxy',
923 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
925 description
=> "Returns a SPICE configuration to connect to the CT.",
927 additionalProperties
=> 0,
929 node
=> get_standard_option
('pve-node'),
930 vmid
=> get_standard_option
('pve-vmid'),
931 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
934 returns
=> get_standard_option
('remote-viewer-config'),
938 my $vmid = $param->{vmid
};
939 my $node = $param->{node
};
940 my $proxy = $param->{proxy
};
942 my $authpath = "/vms/$vmid";
943 my $permissions = 'VM.Console';
945 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
947 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
949 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
951 my $shcmd = ['/usr/bin/dtach', '-A',
952 "/var/run/dtach/vzctlconsole$vmid",
953 '-r', 'winch', '-z', @$concmd];
955 my $title = "CT $vmid";
957 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
961 __PACKAGE__-
>register_method({
962 name
=> 'migrate_vm',
963 path
=> '{vmid}/migrate',
967 description
=> "Migrate the container to another node. Creates a new migration task.",
969 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
972 additionalProperties
=> 0,
974 node
=> get_standard_option
('pve-node'),
975 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
976 target
=> get_standard_option
('pve-node', {
977 description
=> "Target node.",
978 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
982 description
=> "Use online/live migration.",
987 description
=> "Use restart migration",
992 description
=> "Timeout in seconds for shutdown for restart migration",
998 description
=> "Force migration despite local bind / device" .
999 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1006 description
=> "the task ID.",
1011 my $rpcenv = PVE
::RPCEnvironment
::get
();
1013 my $authuser = $rpcenv->get_user();
1015 my $target = extract_param
($param, 'target');
1017 my $localnode = PVE
::INotify
::nodename
();
1018 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1020 PVE
::Cluster
::check_cfs_quorum
();
1022 PVE
::Cluster
::check_node_exists
($target);
1024 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1026 my $vmid = extract_param
($param, 'vmid');
1029 PVE
::LXC
::Config-
>load_config($vmid);
1031 # try to detect errors early
1032 if (PVE
::LXC
::check_running
($vmid)) {
1033 die "can't migrate running container without --online or --restart\n"
1034 if !$param->{online
} && !$param->{restart
};
1037 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1042 my $service = "ct:$vmid";
1044 my $cmd = ['ha-manager', 'migrate', $service, $target];
1046 print "Requesting HA migration for CT $vmid to node $target\n";
1048 PVE
::Tools
::run_command
($cmd);
1053 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1058 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1062 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1065 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1069 __PACKAGE__-
>register_method({
1070 name
=> 'vm_feature',
1071 path
=> '{vmid}/feature',
1075 description
=> "Check if feature for virtual machine is available.",
1077 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1080 additionalProperties
=> 0,
1082 node
=> get_standard_option
('pve-node'),
1083 vmid
=> get_standard_option
('pve-vmid'),
1085 description
=> "Feature to check.",
1087 enum
=> [ 'snapshot', 'clone', 'copy' ],
1089 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1097 hasFeature
=> { type
=> 'boolean' },
1100 #items => { type => 'string' },
1107 my $node = extract_param
($param, 'node');
1109 my $vmid = extract_param
($param, 'vmid');
1111 my $snapname = extract_param
($param, 'snapname');
1113 my $feature = extract_param
($param, 'feature');
1115 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1118 my $snap = $conf->{snapshots
}->{$snapname};
1119 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1122 my $storage_cfg = PVE
::Storage
::config
();
1123 #Maybe include later
1124 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1125 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1128 hasFeature
=> $hasFeature,
1129 #nodes => [ keys %$nodelist ],
1133 __PACKAGE__-
>register_method({
1135 path
=> '{vmid}/template',
1139 description
=> "Create a Template.",
1141 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1142 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1145 additionalProperties
=> 0,
1147 node
=> get_standard_option
('pve-node'),
1148 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1151 returns
=> { type
=> 'null'},
1155 my $rpcenv = PVE
::RPCEnvironment
::get
();
1157 my $authuser = $rpcenv->get_user();
1159 my $node = extract_param
($param, 'node');
1161 my $vmid = extract_param
($param, 'vmid');
1163 my $updatefn = sub {
1165 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1166 PVE
::LXC
::Config-
>check_lock($conf);
1168 die "unable to create template, because CT contains snapshots\n"
1169 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1171 die "you can't convert a template to a template\n"
1172 if PVE
::LXC
::Config-
>is_template($conf);
1174 die "you can't convert a CT to template if the CT is running\n"
1175 if PVE
::LXC
::check_running
($vmid);
1177 my $scfg = PVE
::Storage
::config
();
1178 PVE
::LXC
::Config-
>foreach_mountpoint($conf, sub {
1181 my ($sid) =PVE
::Storage
::parse_volume_id
($mp->{volume
}, 0);
1182 die "Directory storage '$sid' does not support container templates!\n"
1183 if $scfg->{ids
}->{$sid}->{path
};
1187 PVE
::LXC
::template_create
($vmid, $conf);
1189 $conf->{template
} = 1;
1191 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1192 # and remove lxc config
1193 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1196 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1199 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1204 __PACKAGE__-
>register_method({
1206 path
=> '{vmid}/clone',
1210 description
=> "Create a container clone/copy",
1212 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1213 "and 'VM.Allocate' permissions " .
1214 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1215 "'Datastore.AllocateSpace' on any used storage.",
1218 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1220 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1221 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1226 additionalProperties
=> 0,
1228 node
=> get_standard_option
('pve-node'),
1229 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1230 newid
=> get_standard_option
('pve-vmid', {
1231 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1232 description
=> 'VMID for the clone.' }),
1235 type
=> 'string', format
=> 'dns-name',
1236 description
=> "Set a hostname for the new CT.",
1241 description
=> "Description for the new CT.",
1245 type
=> 'string', format
=> 'pve-poolid',
1246 description
=> "Add the new CT to the specified pool.",
1248 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1251 storage
=> get_standard_option
('pve-storage-id', {
1252 description
=> "Target storage for full clone.",
1258 description
=> "Create a full copy of all disks. This is always done when " .
1259 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1261 target
=> get_standard_option
('pve-node', {
1262 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1273 my $rpcenv = PVE
::RPCEnvironment
::get
();
1275 my $authuser = $rpcenv->get_user();
1277 my $node = extract_param
($param, 'node');
1279 my $vmid = extract_param
($param, 'vmid');
1281 my $newid = extract_param
($param, 'newid');
1283 my $pool = extract_param
($param, 'pool');
1285 if (defined($pool)) {
1286 $rpcenv->check_pool_exist($pool);
1289 my $snapname = extract_param
($param, 'snapname');
1291 my $storage = extract_param
($param, 'storage');
1293 my $target = extract_param
($param, 'target');
1295 my $localnode = PVE
::INotify
::nodename
();
1297 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1299 PVE
::Cluster
::check_node_exists
($target) if $target;
1301 my $storecfg = PVE
::Storage
::config
();
1304 # check if storage is enabled on local node
1305 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1307 # check if storage is available on target node
1308 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1309 # clone only works if target storage is shared
1310 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1311 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1315 PVE
::Cluster
::check_cfs_quorum
();
1319 my $mountpoints = {};
1324 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1325 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1327 $running = PVE
::LXC
::check_running
($vmid) || 0;
1329 my $full = extract_param
($param, 'full');
1330 if (!defined($full)) {
1331 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1333 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1336 die "snapshot '$snapname' does not exist\n"
1337 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1340 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1342 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1343 die "unable to create CT $newid: config file already exists\n"
1347 foreach my $opt (keys %$src_conf) {
1348 next if $opt =~ m/^unused\d+$/;
1350 my $value = $src_conf->{$opt};
1352 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1353 my $mp = $opt eq 'rootfs' ?
1354 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1355 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1357 if ($mp->{type
} eq 'volume') {
1358 my $volid = $mp->{volume
};
1360 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1361 $sid = $storage if defined($storage);
1362 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1363 if (!$scfg->{shared
}) {
1365 warn "found non-shared volume: $volid\n" if $target;
1368 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1371 die "Cannot do full clones on a running container without snapshots\n"
1372 if $running && !defined($snapname);
1373 $fullclone->{$opt} = 1;
1375 # not full means clone instead of copy
1376 die "Linked clone feature for '$volid' is not available\n"
1377 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1380 $mountpoints->{$opt} = $mp;
1381 push @$vollist, $volid;
1384 # TODO: allow bind mounts?
1385 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1387 } elsif ($opt =~ m/^net(\d+)$/) {
1388 # always change MAC! address
1389 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1390 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1391 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1392 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1394 # copy everything else
1395 $newconf->{$opt} = $value;
1398 die "can't clone CT to node '$target' (CT uses local storage)\n"
1399 if $target && !$sharedvm;
1401 # Replace the 'disk' lock with a 'create' lock.
1402 $newconf->{lock} = 'create';
1404 delete $newconf->{template
};
1405 if ($param->{hostname
}) {
1406 $newconf->{hostname
} = $param->{hostname
};
1409 if ($param->{description
}) {
1410 $newconf->{description
} = $param->{description
};
1413 # create empty/temp config - this fails if CT already exists on other node
1414 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1417 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1423 my $update_conf = sub {
1424 my ($key, $value) = @_;
1425 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1426 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1427 die "Lost 'create' config lock, aborting.\n"
1428 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1429 $conf->{$key} = $value;
1430 PVE
::LXC
::Config-
>write_config($newid, $conf);
1437 my $newvollist = [];
1439 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1440 die "unexpected state change\n" if $verify_running != $running;
1446 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1448 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1450 foreach my $opt (keys %$mountpoints) {
1451 my $mp = $mountpoints->{$opt};
1452 my $volid = $mp->{volume
};
1455 if ($fullclone->{$opt}) {
1456 print "create full clone of mountpoint $opt ($volid)\n";
1457 my $target_storage = $storage // PVE
::Storage
::parse_volume_id
($volid);
1458 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname);
1460 print "create linked clone of mount point $opt ($volid)\n";
1461 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1464 push @$newvollist, $newvolid;
1465 $mp->{volume
} = $newvolid;
1467 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1470 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1471 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1474 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1475 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1476 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1478 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1479 die "Failed to move config to node '$target' - rename failed: $!\n"
1480 if !rename($conffile, $newconffile);
1485 # Unlock the source config in any case:
1486 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1490 # Now cleanup the config & disks:
1493 sleep 1; # some storages like rbd need to wait before release volume - really?
1495 foreach my $volid (@$newvollist) {
1496 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1499 die "clone failed: $err";
1505 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1506 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1510 __PACKAGE__-
>register_method({
1511 name
=> 'resize_vm',
1512 path
=> '{vmid}/resize',
1516 description
=> "Resize a container mount point.",
1518 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1521 additionalProperties
=> 0,
1523 node
=> get_standard_option
('pve-node'),
1524 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1527 description
=> "The disk you want to resize.",
1528 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1532 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1533 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.",
1537 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1545 description
=> "the task ID.",
1550 my $rpcenv = PVE
::RPCEnvironment
::get
();
1552 my $authuser = $rpcenv->get_user();
1554 my $node = extract_param
($param, 'node');
1556 my $vmid = extract_param
($param, 'vmid');
1558 my $digest = extract_param
($param, 'digest');
1560 my $sizestr = extract_param
($param, 'size');
1561 my $ext = ($sizestr =~ s/^\+//);
1562 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1563 die "invalid size string" if !defined($newsize);
1565 die "no options specified\n" if !scalar(keys %$param);
1567 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1569 my $storage_cfg = cfs_read_file
("storage.cfg");
1573 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1574 PVE
::LXC
::Config-
>check_lock($conf);
1576 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1578 my $running = PVE
::LXC
::check_running
($vmid);
1580 my $disk = $param->{disk
};
1581 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1582 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1584 my $volid = $mp->{volume
};
1586 my (undef, undef, $owner, undef, undef, undef, $format) =
1587 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1589 die "can't resize mount point owned by another container ($owner)"
1592 die "can't resize volume: $disk if snapshot exists\n"
1593 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1595 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1597 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1599 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1601 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1602 $newsize += $size if $ext;
1603 $newsize = int($newsize);
1605 die "unable to shrink disk size\n" if $newsize < $size;
1607 return if $size == $newsize;
1609 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1611 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1612 # we pass 0 here (parameter only makes sense for qemu)
1613 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1615 $mp->{size
} = $newsize;
1616 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1618 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1620 if ($format eq 'raw') {
1621 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1625 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1626 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1627 die "internal error: CT running but mount point not attached to a loop device"
1629 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1631 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1632 # to be visible to it in its namespace.
1633 # To not interfere with the rest of the system we unshare the current mount namespace,
1634 # mount over /tmp and then run resize2fs.
1636 # interestingly we don't need to e2fsck on mounted systems...
1637 my $quoted = PVE
::Tools
::shellquote
($path);
1638 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1640 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1642 warn "Failed to update the container's filesystem: $@\n" if $@;
1645 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1646 PVE
::Tools
::run_command
(['resize2fs', $path]);
1648 warn "Failed to update the container's filesystem: $@\n" if $@;
1653 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1656 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1659 __PACKAGE__-
>register_method({
1660 name
=> 'move_volume',
1661 path
=> '{vmid}/move_volume',
1665 description
=> "Move a rootfs-/mp-volume to a different storage",
1667 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1668 "and 'Datastore.AllocateSpace' permissions on the storage.",
1671 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1672 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1676 additionalProperties
=> 0,
1678 node
=> get_standard_option
('pve-node'),
1679 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1682 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1683 description
=> "Volume which will be moved.",
1685 storage
=> get_standard_option
('pve-storage-id', {
1686 description
=> "Target Storage.",
1687 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1691 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1697 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1709 my $rpcenv = PVE
::RPCEnvironment
::get
();
1711 my $authuser = $rpcenv->get_user();
1713 my $vmid = extract_param
($param, 'vmid');
1715 my $storage = extract_param
($param, 'storage');
1717 my $mpkey = extract_param
($param, 'volume');
1719 my $lockname = 'disk';
1721 my ($mpdata, $old_volid);
1723 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1724 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1725 PVE
::LXC
::Config-
>check_lock($conf);
1727 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1729 if ($mpkey eq 'rootfs') {
1730 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1731 } elsif ($mpkey =~ m/mp\d+/) {
1732 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1734 die "Can't parse $mpkey\n";
1736 $old_volid = $mpdata->{volume
};
1738 die "you can't move a volume with snapshots and delete the source\n"
1739 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1741 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1743 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1748 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1750 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1751 my $storage_cfg = PVE
::Storage
::config
();
1756 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1757 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf);
1758 $mpdata->{volume
} = $new_volid;
1760 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1761 my $digest = $conf->{digest
};
1762 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1763 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1765 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1767 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1769 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1773 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1774 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1780 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1781 if defined($new_volid);
1787 if ($param->{delete}) {
1789 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1790 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1796 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1801 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1804 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };