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.",
150 description
=> "Assign a unique random ethernet address.",
151 requires
=> 'restore',
155 type
=> 'string', format
=> 'pve-poolid',
156 description
=> "Add the VM to the specified pool.",
158 'ignore-unpack-errors' => {
161 description
=> "Ignore errors when extracting the template.",
163 'ssh-public-keys' => {
166 description
=> "Setup public SSH keys (one key per line, " .
170 description
=> "Override I/O bandwidth limit (in KiB/s).",
174 default => 'restore limit from datacenter or storage config',
180 description
=> "Start the CT after its creation finished successfully.",
190 PVE
::Cluster
::check_cfs_quorum
();
192 my $rpcenv = PVE
::RPCEnvironment
::get
();
193 my $authuser = $rpcenv->get_user();
195 my $node = extract_param
($param, 'node');
196 my $vmid = extract_param
($param, 'vmid');
197 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
198 my $bwlimit = extract_param
($param, 'bwlimit');
199 my $start_after_create = extract_param
($param, 'start');
201 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
202 my $same_container_exists = -f
$basecfg_fn;
204 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
205 my $unprivileged = extract_param
($param, 'unprivileged');
206 my $restore = extract_param
($param, 'restore');
207 my $unique = extract_param
($param, 'unique');
209 # used to skip firewall config restore if user lacks permission
210 my $skip_fw_config_restore = 0;
213 # fixme: limit allowed parameters
216 my $force = extract_param
($param, 'force');
218 if (!($same_container_exists && $restore && $force)) {
219 PVE
::Cluster
::check_vmid_unused
($vmid);
221 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
222 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
223 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
226 my $password = extract_param
($param, 'password');
227 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
228 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
230 my $pool = extract_param
($param, 'pool');
231 if (defined($pool)) {
232 $rpcenv->check_pool_exist($pool);
233 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
236 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
238 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
240 } elsif ($restore && $force && $same_container_exists &&
241 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
242 # OK: user has VM.Backup permissions, and want to restore an existing VM
244 # we don't want to restore a container-provided FW conf in this case
245 # since the user is lacking permission to configure the container's FW
246 $skip_fw_config_restore = 1;
251 my $ostemplate = extract_param
($param, 'ostemplate');
252 my $storage = extract_param
($param, 'storage') // 'local';
254 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
256 my $storage_cfg = cfs_read_file
("storage.cfg");
259 if ($ostemplate eq '-') {
260 die "pipe requires cli environment\n"
261 if $rpcenv->{type
} ne 'cli';
262 die "pipe can only be used with restore tasks\n"
265 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
267 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
268 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
272 my $check_and_activate_storage = sub {
275 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
277 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
278 if !$scfg->{content
}->{rootdir
};
280 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
282 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
283 $used_storages{$sid} = 1;
288 my $is_root = $authuser eq 'root@pam';
290 my $no_disk_param = {};
292 my $storage_only_mode = 1;
293 foreach my $opt (keys %$param) {
294 my $value = $param->{$opt};
295 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
296 # allow to use simple numbers (add default storage in that case)
297 if ($value =~ m/^\d+(\.\d+)?$/) {
298 $mp_param->{$opt} = "$storage:$value";
300 $mp_param->{$opt} = $value;
302 $storage_only_mode = 0;
303 } elsif ($opt =~ m/^unused\d+$/) {
304 warn "ignoring '$opt', cannot create/restore with unused volume\n";
305 delete $param->{$opt};
307 $no_disk_param->{$opt} = $value;
311 die "mount points configured, but 'rootfs' not set - aborting\n"
312 if !$storage_only_mode && !defined($mp_param->{rootfs
});
314 # check storage access, activate storage
315 my $delayed_mp_param = {};
316 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
317 my ($ms, $mountpoint) = @_;
319 my $volid = $mountpoint->{volume
};
320 my $mp = $mountpoint->{mp
};
322 if ($mountpoint->{type
} ne 'volume') { # bind or device
323 die "Only root can pass arbitrary filesystem paths.\n"
326 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
327 &$check_and_activate_storage($sid);
331 # check/activate default storage
332 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
334 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
336 $conf->{unprivileged
} = 1 if $unprivileged;
338 my $emsg = $restore ?
"unable to restore CT $vmid -" : "unable to create CT $vmid -";
340 eval { PVE
::LXC
::Config-
>create_and_lock_config($vmid, $force) };
341 die "$emsg $@" if $@;
344 my $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
349 my $orig_mp_param; # only used if $restore
351 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
352 if ($is_root && $archive ne '-') {
354 ($orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
355 $was_template = delete $orig_conf->{template
};
356 # When we're root call 'restore_configuration' with ristricted=0,
357 # causing it to restore the raw lxc entries, among which there may be
358 # 'lxc.idmap' entries. We need to make sure that the extracted contents
359 # of the container match up with the restored configuration afterwards:
360 $conf->{lxc
} = [grep { $_->[0] eq 'lxc.idmap' } @{$orig_conf->{lxc
}}];
363 if ($storage_only_mode) {
365 if (!defined($orig_mp_param)) {
366 (undef, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
368 $mp_param = $orig_mp_param;
369 die "rootfs configuration could not be recovered, please check and specify manually!\n"
370 if !defined($mp_param->{rootfs
});
371 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
372 my ($ms, $mountpoint) = @_;
373 my $type = $mountpoint->{type
};
374 if ($type eq 'volume') {
375 die "unable to detect disk size - please specify $ms (size)\n"
376 if !defined($mountpoint->{size
});
377 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
378 delete $mountpoint->{size
};
379 $mountpoint->{volume
} = "$storage:$disksize";
380 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
382 my $type = $mountpoint->{type
};
383 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
384 if ($ms eq 'rootfs');
385 die "restoring '$ms' to $type mount is only possible for root\n"
388 if ($mountpoint->{backup
}) {
389 warn "WARNING - unsupported configuration!\n";
390 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
391 warn "mount point configuration will be restored after archive extraction!\n";
392 warn "contained files will be restored to wrong directory!\n";
394 delete $mp_param->{$ms}; # actually delay bind/dev mps
395 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
399 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
403 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
405 # we always have the 'create' lock so check for more than 1 entry
406 if (scalar(keys %$old_conf) > 1) {
407 # destroy old container volumes
408 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, { lock => 'create' });
412 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
413 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
414 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
417 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
418 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
419 $lxc_setup->template_fixup($conf);
421 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
422 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
423 $lxc_setup->post_create_hook($password, $ssh_keys);
427 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
428 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
431 $conf->{hostname
} ||= "CT$vmid";
432 $conf->{memory
} ||= 512;
433 $conf->{swap
} //= 512;
434 foreach my $mp (keys %$delayed_mp_param) {
435 $conf->{$mp} = $delayed_mp_param->{$mp};
437 # If the template flag was set, we try to convert again to template after restore
439 print STDERR
"Convert restored container to template...\n";
440 if (my $err = check_storage_supports_templates
($conf)) {
442 warn "Leave restored backup as container instead of converting to template.\n"
444 PVE
::LXC
::template_create
($vmid, $conf);
445 $conf->{template
} = 1;
448 PVE
::LXC
::Config-
>write_config($vmid, $conf);
451 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
452 eval { PVE
::LXC
::destroy_config
($vmid) };
456 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
458 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
459 if $start_after_create;
462 my $workername = $restore ?
'vzrestore' : 'vzcreate';
463 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
465 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
468 sub check_storage_supports_templates
{
471 my $scfg = PVE
::Storage
::config
();
473 PVE
::LXC
::Config-
>foreach_mountpoint($conf, sub {
476 my ($sid) = PVE
::Storage
::parse_volume_id
($mp->{volume
}, 0);
477 die "Warning: Directory storage '$sid' does not support container templates!\n"
478 if $scfg->{ids
}->{$sid}->{path
};
484 __PACKAGE__-
>register_method({
489 description
=> "Directory index",
494 additionalProperties
=> 0,
496 node
=> get_standard_option
('pve-node'),
497 vmid
=> get_standard_option
('pve-vmid'),
505 subdir
=> { type
=> 'string' },
508 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
514 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
517 { subdir
=> 'config' },
518 { subdir
=> 'status' },
519 { subdir
=> 'vncproxy' },
520 { subdir
=> 'termproxy' },
521 { subdir
=> 'vncwebsocket' },
522 { subdir
=> 'spiceproxy' },
523 { subdir
=> 'migrate' },
524 { subdir
=> 'clone' },
525 # { subdir => 'initlog' },
527 { subdir
=> 'rrddata' },
528 { subdir
=> 'firewall' },
529 { subdir
=> 'snapshot' },
530 { subdir
=> 'resize' },
537 __PACKAGE__-
>register_method({
539 path
=> '{vmid}/rrd',
541 protected
=> 1, # fixme: can we avoid that?
543 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
545 description
=> "Read VM RRD statistics (returns PNG)",
547 additionalProperties
=> 0,
549 node
=> get_standard_option
('pve-node'),
550 vmid
=> get_standard_option
('pve-vmid'),
552 description
=> "Specify the time frame you are interested in.",
554 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
557 description
=> "The list of datasources you want to display.",
558 type
=> 'string', format
=> 'pve-configid-list',
561 description
=> "The RRD consolidation function",
563 enum
=> [ 'AVERAGE', 'MAX' ],
571 filename
=> { type
=> 'string' },
577 return PVE
::Cluster
::create_rrd_graph
(
578 "pve2-vm/$param->{vmid}", $param->{timeframe
},
579 $param->{ds
}, $param->{cf
});
583 __PACKAGE__-
>register_method({
585 path
=> '{vmid}/rrddata',
587 protected
=> 1, # fixme: can we avoid that?
589 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
591 description
=> "Read VM RRD statistics",
593 additionalProperties
=> 0,
595 node
=> get_standard_option
('pve-node'),
596 vmid
=> get_standard_option
('pve-vmid'),
598 description
=> "Specify the time frame you are interested in.",
600 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
603 description
=> "The RRD consolidation function",
605 enum
=> [ 'AVERAGE', 'MAX' ],
620 return PVE
::Cluster
::create_rrd_data
(
621 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
624 __PACKAGE__-
>register_method({
625 name
=> 'destroy_vm',
630 description
=> "Destroy the container (also delete all uses files).",
632 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
635 additionalProperties
=> 0,
637 node
=> get_standard_option
('pve-node'),
638 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
647 my $rpcenv = PVE
::RPCEnvironment
::get
();
648 my $authuser = $rpcenv->get_user();
649 my $vmid = $param->{vmid
};
651 # test if container exists
652 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
653 my $storage_cfg = cfs_read_file
("storage.cfg");
654 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
656 die "unable to remove CT $vmid - used in HA resources\n"
657 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
659 # do not allow destroy if there are replication jobs
660 my $repl_conf = PVE
::ReplicationConfig-
>new();
661 $repl_conf->check_for_existing_jobs($vmid);
663 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
665 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
668 # reload config after lock
669 $conf = PVE
::LXC
::Config-
>load_config($vmid);
670 PVE
::LXC
::Config-
>check_lock($conf);
672 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
674 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
675 PVE
::AccessControl
::remove_vm_access
($vmid);
676 PVE
::Firewall
::remove_vmfw_conf
($vmid);
679 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
681 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
686 __PACKAGE__-
>register_method ({
688 path
=> '{vmid}/vncproxy',
692 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
694 description
=> "Creates a TCP VNC proxy connections.",
696 additionalProperties
=> 0,
698 node
=> get_standard_option
('pve-node'),
699 vmid
=> get_standard_option
('pve-vmid'),
703 description
=> "use websocket instead of standard VNC.",
707 description
=> "sets the width of the console in pixels.",
714 description
=> "sets the height of the console in pixels.",
722 additionalProperties
=> 0,
724 user
=> { type
=> 'string' },
725 ticket
=> { type
=> 'string' },
726 cert
=> { type
=> 'string' },
727 port
=> { type
=> 'integer' },
728 upid
=> { type
=> 'string' },
734 my $rpcenv = PVE
::RPCEnvironment
::get
();
736 my $authuser = $rpcenv->get_user();
738 my $vmid = $param->{vmid
};
739 my $node = $param->{node
};
741 my $authpath = "/vms/$vmid";
743 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
745 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
748 my ($remip, $family);
750 if ($node ne PVE
::INotify
::nodename
()) {
751 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
753 $family = PVE
::Tools
::get_host_address_family
($node);
756 my $port = PVE
::Tools
::next_vnc_port
($family);
758 # NOTE: vncterm VNC traffic is already TLS encrypted,
759 # so we select the fastest chipher here (or 'none'?)
760 my $remcmd = $remip ?
761 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
763 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
764 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
766 my $shcmd = [ '/usr/bin/dtach', '-A',
767 "/var/run/dtach/vzctlconsole$vmid",
768 '-r', 'winch', '-z', @$concmd];
773 syslog
('info', "starting lxc vnc proxy $upid\n");
777 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
778 '-timeout', $timeout, '-authpath', $authpath,
779 '-perm', 'VM.Console'];
781 if ($param->{width
}) {
782 push @$cmd, '-width', $param->{width
};
785 if ($param->{height
}) {
786 push @$cmd, '-height', $param->{height
};
789 if ($param->{websocket
}) {
790 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
791 push @$cmd, '-notls', '-listen', 'localhost';
794 push @$cmd, '-c', @$remcmd, @$shcmd;
796 run_command
($cmd, keeplocale
=> 1);
801 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
803 PVE
::Tools
::wait_for_vnc_port
($port);
814 __PACKAGE__-
>register_method ({
816 path
=> '{vmid}/termproxy',
820 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
822 description
=> "Creates a TCP proxy connection.",
824 additionalProperties
=> 0,
826 node
=> get_standard_option
('pve-node'),
827 vmid
=> get_standard_option
('pve-vmid'),
831 additionalProperties
=> 0,
833 user
=> { type
=> 'string' },
834 ticket
=> { type
=> 'string' },
835 port
=> { type
=> 'integer' },
836 upid
=> { type
=> 'string' },
842 my $rpcenv = PVE
::RPCEnvironment
::get
();
844 my $authuser = $rpcenv->get_user();
846 my $vmid = $param->{vmid
};
847 my $node = $param->{node
};
849 my $authpath = "/vms/$vmid";
851 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
853 my ($remip, $family);
855 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
856 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
858 $family = PVE
::Tools
::get_host_address_family
($node);
861 my $port = PVE
::Tools
::next_vnc_port
($family);
863 my $remcmd = $remip ?
864 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
866 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
867 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
869 my $shcmd = [ '/usr/bin/dtach', '-A',
870 "/var/run/dtach/vzctlconsole$vmid",
871 '-r', 'winch', '-z', @$concmd];
876 syslog
('info', "starting lxc termproxy $upid\n");
878 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
879 '--perm', 'VM.Console', '--'];
880 push @$cmd, @$remcmd, @$shcmd;
882 PVE
::Tools
::run_command
($cmd);
885 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
887 PVE
::Tools
::wait_for_vnc_port
($port);
897 __PACKAGE__-
>register_method({
898 name
=> 'vncwebsocket',
899 path
=> '{vmid}/vncwebsocket',
902 description
=> "You also need to pass a valid ticket (vncticket).",
903 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
905 description
=> "Opens a weksocket for VNC traffic.",
907 additionalProperties
=> 0,
909 node
=> get_standard_option
('pve-node'),
910 vmid
=> get_standard_option
('pve-vmid'),
912 description
=> "Ticket from previous call to vncproxy.",
917 description
=> "Port number returned by previous vncproxy call.",
927 port
=> { type
=> 'string' },
933 my $rpcenv = PVE
::RPCEnvironment
::get
();
935 my $authuser = $rpcenv->get_user();
937 my $authpath = "/vms/$param->{vmid}";
939 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
941 my $port = $param->{port
};
943 return { port
=> $port };
946 __PACKAGE__-
>register_method ({
947 name
=> 'spiceproxy',
948 path
=> '{vmid}/spiceproxy',
953 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
955 description
=> "Returns a SPICE configuration to connect to the CT.",
957 additionalProperties
=> 0,
959 node
=> get_standard_option
('pve-node'),
960 vmid
=> get_standard_option
('pve-vmid'),
961 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
964 returns
=> get_standard_option
('remote-viewer-config'),
968 my $vmid = $param->{vmid
};
969 my $node = $param->{node
};
970 my $proxy = $param->{proxy
};
972 my $authpath = "/vms/$vmid";
973 my $permissions = 'VM.Console';
975 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
977 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
979 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
981 my $shcmd = ['/usr/bin/dtach', '-A',
982 "/var/run/dtach/vzctlconsole$vmid",
983 '-r', 'winch', '-z', @$concmd];
985 my $title = "CT $vmid";
987 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
991 __PACKAGE__-
>register_method({
992 name
=> 'migrate_vm',
993 path
=> '{vmid}/migrate',
997 description
=> "Migrate the container to another node. Creates a new migration task.",
999 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1002 additionalProperties
=> 0,
1004 node
=> get_standard_option
('pve-node'),
1005 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1006 target
=> get_standard_option
('pve-node', {
1007 description
=> "Target node.",
1008 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1012 description
=> "Use online/live migration.",
1017 description
=> "Use restart migration",
1022 description
=> "Timeout in seconds for shutdown for restart migration",
1028 description
=> "Force migration despite local bind / device" .
1029 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1033 description
=> "Override I/O bandwidth limit (in KiB/s).",
1037 default => 'migrate limit from datacenter or storage config',
1043 description
=> "the task ID.",
1048 my $rpcenv = PVE
::RPCEnvironment
::get
();
1050 my $authuser = $rpcenv->get_user();
1052 my $target = extract_param
($param, 'target');
1054 my $localnode = PVE
::INotify
::nodename
();
1055 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1057 PVE
::Cluster
::check_cfs_quorum
();
1059 PVE
::Cluster
::check_node_exists
($target);
1061 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1063 my $vmid = extract_param
($param, 'vmid');
1066 PVE
::LXC
::Config-
>load_config($vmid);
1068 # try to detect errors early
1069 if (PVE
::LXC
::check_running
($vmid)) {
1070 die "can't migrate running container without --online or --restart\n"
1071 if !$param->{online
} && !$param->{restart
};
1074 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1079 my $service = "ct:$vmid";
1081 my $cmd = ['ha-manager', 'migrate', $service, $target];
1083 print "Requesting HA migration for CT $vmid to node $target\n";
1085 PVE
::Tools
::run_command
($cmd);
1090 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1095 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1099 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1102 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1106 __PACKAGE__-
>register_method({
1107 name
=> 'vm_feature',
1108 path
=> '{vmid}/feature',
1112 description
=> "Check if feature for virtual machine is available.",
1114 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1117 additionalProperties
=> 0,
1119 node
=> get_standard_option
('pve-node'),
1120 vmid
=> get_standard_option
('pve-vmid'),
1122 description
=> "Feature to check.",
1124 enum
=> [ 'snapshot', 'clone', 'copy' ],
1126 snapname
=> get_standard_option
('pve-snapshot-name', {
1134 hasFeature
=> { type
=> 'boolean' },
1137 #items => { type => 'string' },
1144 my $node = extract_param
($param, 'node');
1146 my $vmid = extract_param
($param, 'vmid');
1148 my $snapname = extract_param
($param, 'snapname');
1150 my $feature = extract_param
($param, 'feature');
1152 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1155 my $snap = $conf->{snapshots
}->{$snapname};
1156 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1159 my $storage_cfg = PVE
::Storage
::config
();
1160 #Maybe include later
1161 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1162 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1165 hasFeature
=> $hasFeature,
1166 #nodes => [ keys %$nodelist ],
1170 __PACKAGE__-
>register_method({
1172 path
=> '{vmid}/template',
1176 description
=> "Create a Template.",
1178 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1179 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1182 additionalProperties
=> 0,
1184 node
=> get_standard_option
('pve-node'),
1185 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1188 returns
=> { type
=> 'null'},
1192 my $rpcenv = PVE
::RPCEnvironment
::get
();
1194 my $authuser = $rpcenv->get_user();
1196 my $node = extract_param
($param, 'node');
1198 my $vmid = extract_param
($param, 'vmid');
1200 my $updatefn = sub {
1202 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1203 PVE
::LXC
::Config-
>check_lock($conf);
1205 die "unable to create template, because CT contains snapshots\n"
1206 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1208 die "you can't convert a template to a template\n"
1209 if PVE
::LXC
::Config-
>is_template($conf);
1211 die "you can't convert a CT to template if the CT is running\n"
1212 if PVE
::LXC
::check_running
($vmid);
1214 if (my $err = check_storage_supports_templates
($conf)) {
1219 PVE
::LXC
::template_create
($vmid, $conf);
1221 $conf->{template
} = 1;
1223 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1224 # and remove lxc config
1225 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1228 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1231 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1236 __PACKAGE__-
>register_method({
1238 path
=> '{vmid}/clone',
1242 description
=> "Create a container clone/copy",
1244 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1245 "and 'VM.Allocate' permissions " .
1246 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1247 "'Datastore.AllocateSpace' on any used storage.",
1250 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1252 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1253 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1258 additionalProperties
=> 0,
1260 node
=> get_standard_option
('pve-node'),
1261 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1262 newid
=> get_standard_option
('pve-vmid', {
1263 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1264 description
=> 'VMID for the clone.' }),
1267 type
=> 'string', format
=> 'dns-name',
1268 description
=> "Set a hostname for the new CT.",
1273 description
=> "Description for the new CT.",
1277 type
=> 'string', format
=> 'pve-poolid',
1278 description
=> "Add the new CT to the specified pool.",
1280 snapname
=> get_standard_option
('pve-snapshot-name', {
1283 storage
=> get_standard_option
('pve-storage-id', {
1284 description
=> "Target storage for full clone.",
1290 description
=> "Create a full copy of all disks. This is always done when " .
1291 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1293 target
=> get_standard_option
('pve-node', {
1294 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1298 description
=> "Override I/O bandwidth limit (in KiB/s).",
1302 default => 'clone limit from datacenter or storage config',
1312 my $rpcenv = PVE
::RPCEnvironment
::get
();
1314 my $authuser = $rpcenv->get_user();
1316 my $node = extract_param
($param, 'node');
1318 my $vmid = extract_param
($param, 'vmid');
1320 my $newid = extract_param
($param, 'newid');
1322 my $pool = extract_param
($param, 'pool');
1324 if (defined($pool)) {
1325 $rpcenv->check_pool_exist($pool);
1328 my $snapname = extract_param
($param, 'snapname');
1330 my $storage = extract_param
($param, 'storage');
1332 my $target = extract_param
($param, 'target');
1334 my $localnode = PVE
::INotify
::nodename
();
1336 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1338 PVE
::Cluster
::check_node_exists
($target) if $target;
1340 my $storecfg = PVE
::Storage
::config
();
1343 # check if storage is enabled on local node
1344 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1346 # check if storage is available on target node
1347 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1348 # clone only works if target storage is shared
1349 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1350 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1354 PVE
::Cluster
::check_cfs_quorum
();
1358 my $mountpoints = {};
1363 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1364 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1366 $running = PVE
::LXC
::check_running
($vmid) || 0;
1368 my $full = extract_param
($param, 'full');
1369 if (!defined($full)) {
1370 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1372 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1375 die "snapshot '$snapname' does not exist\n"
1376 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1379 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1381 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1382 die "unable to create CT $newid: config file already exists\n"
1386 foreach my $opt (keys %$src_conf) {
1387 next if $opt =~ m/^unused\d+$/;
1389 my $value = $src_conf->{$opt};
1391 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1392 my $mp = $opt eq 'rootfs' ?
1393 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1394 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1396 if ($mp->{type
} eq 'volume') {
1397 my $volid = $mp->{volume
};
1399 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1400 $sid = $storage if defined($storage);
1401 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1402 if (!$scfg->{shared
}) {
1404 warn "found non-shared volume: $volid\n" if $target;
1407 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1410 die "Cannot do full clones on a running container without snapshots\n"
1411 if $running && !defined($snapname);
1412 $fullclone->{$opt} = 1;
1414 # not full means clone instead of copy
1415 die "Linked clone feature for '$volid' is not available\n"
1416 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1419 $mountpoints->{$opt} = $mp;
1420 push @$vollist, $volid;
1423 # TODO: allow bind mounts?
1424 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1426 } elsif ($opt =~ m/^net(\d+)$/) {
1427 # always change MAC! address
1428 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1429 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1430 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1431 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1433 # copy everything else
1434 $newconf->{$opt} = $value;
1437 die "can't clone CT to node '$target' (CT uses local storage)\n"
1438 if $target && !$sharedvm;
1440 # Replace the 'disk' lock with a 'create' lock.
1441 $newconf->{lock} = 'create';
1443 delete $newconf->{template
};
1444 if ($param->{hostname
}) {
1445 $newconf->{hostname
} = $param->{hostname
};
1448 if ($param->{description
}) {
1449 $newconf->{description
} = $param->{description
};
1452 # create empty/temp config - this fails if CT already exists on other node
1453 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1456 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1462 my $update_conf = sub {
1463 my ($key, $value) = @_;
1464 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1465 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1466 die "Lost 'create' config lock, aborting.\n"
1467 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1468 $conf->{$key} = $value;
1469 PVE
::LXC
::Config-
>write_config($newid, $conf);
1476 my $newvollist = [];
1478 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1479 die "unexpected state change\n" if $verify_running != $running;
1485 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1487 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1488 my $bwlimit = extract_param
($param, 'bwlimit');
1490 foreach my $opt (keys %$mountpoints) {
1491 my $mp = $mountpoints->{$opt};
1492 my $volid = $mp->{volume
};
1495 if ($fullclone->{$opt}) {
1496 print "create full clone of mountpoint $opt ($volid)\n";
1497 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1498 my $target_storage = $storage // $source_storage;
1499 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1500 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1502 print "create linked clone of mount point $opt ($volid)\n";
1503 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1506 push @$newvollist, $newvolid;
1507 $mp->{volume
} = $newvolid;
1509 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1512 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1513 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1516 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1517 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1518 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1520 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1521 die "Failed to move config to node '$target' - rename failed: $!\n"
1522 if !rename($conffile, $newconffile);
1527 # Unlock the source config in any case:
1528 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1532 # Now cleanup the config & disks:
1535 sleep 1; # some storages like rbd need to wait before release volume - really?
1537 foreach my $volid (@$newvollist) {
1538 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1541 die "clone failed: $err";
1547 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1548 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1552 __PACKAGE__-
>register_method({
1553 name
=> 'resize_vm',
1554 path
=> '{vmid}/resize',
1558 description
=> "Resize a container mount point.",
1560 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1563 additionalProperties
=> 0,
1565 node
=> get_standard_option
('pve-node'),
1566 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1569 description
=> "The disk you want to resize.",
1570 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1574 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1575 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.",
1579 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1587 description
=> "the task ID.",
1592 my $rpcenv = PVE
::RPCEnvironment
::get
();
1594 my $authuser = $rpcenv->get_user();
1596 my $node = extract_param
($param, 'node');
1598 my $vmid = extract_param
($param, 'vmid');
1600 my $digest = extract_param
($param, 'digest');
1602 my $sizestr = extract_param
($param, 'size');
1603 my $ext = ($sizestr =~ s/^\+//);
1604 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1605 die "invalid size string" if !defined($newsize);
1607 die "no options specified\n" if !scalar(keys %$param);
1609 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1611 my $storage_cfg = cfs_read_file
("storage.cfg");
1615 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1616 PVE
::LXC
::Config-
>check_lock($conf);
1618 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1620 my $running = PVE
::LXC
::check_running
($vmid);
1622 my $disk = $param->{disk
};
1623 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1624 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1626 my $volid = $mp->{volume
};
1628 my (undef, undef, $owner, undef, undef, undef, $format) =
1629 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1631 die "can't resize mount point owned by another container ($owner)"
1634 die "can't resize volume: $disk if snapshot exists\n"
1635 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1637 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1639 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1641 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1643 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1644 $newsize += $size if $ext;
1645 $newsize = int($newsize);
1647 die "unable to shrink disk size\n" if $newsize < $size;
1649 return if $size == $newsize;
1651 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1653 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1654 # we pass 0 here (parameter only makes sense for qemu)
1655 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1657 $mp->{size
} = $newsize;
1658 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1660 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1662 if ($format eq 'raw') {
1663 # we need to ensure that the volume is mapped, if not needed this is a NOP
1664 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1665 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1669 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1670 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1671 die "internal error: CT running but mount point not attached to a loop device"
1673 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1675 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1676 # to be visible to it in its namespace.
1677 # To not interfere with the rest of the system we unshare the current mount namespace,
1678 # mount over /tmp and then run resize2fs.
1680 # interestingly we don't need to e2fsck on mounted systems...
1681 my $quoted = PVE
::Tools
::shellquote
($path);
1682 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1684 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1686 warn "Failed to update the container's filesystem: $@\n" if $@;
1689 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1690 PVE
::Tools
::run_command
(['resize2fs', $path]);
1692 warn "Failed to update the container's filesystem: $@\n" if $@;
1694 # always un-map if not running, this is a NOP if not needed
1695 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1700 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1703 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1706 __PACKAGE__-
>register_method({
1707 name
=> 'move_volume',
1708 path
=> '{vmid}/move_volume',
1712 description
=> "Move a rootfs-/mp-volume to a different storage",
1714 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1715 "and 'Datastore.AllocateSpace' permissions on the storage.",
1718 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1719 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1723 additionalProperties
=> 0,
1725 node
=> get_standard_option
('pve-node'),
1726 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1729 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1730 description
=> "Volume which will be moved.",
1732 storage
=> get_standard_option
('pve-storage-id', {
1733 description
=> "Target Storage.",
1734 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1738 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1744 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1749 description
=> "Override I/O bandwidth limit (in KiB/s).",
1753 default => 'clone limit from datacenter or storage config',
1763 my $rpcenv = PVE
::RPCEnvironment
::get
();
1765 my $authuser = $rpcenv->get_user();
1767 my $vmid = extract_param
($param, 'vmid');
1769 my $storage = extract_param
($param, 'storage');
1771 my $mpkey = extract_param
($param, 'volume');
1773 my $lockname = 'disk';
1775 my ($mpdata, $old_volid);
1777 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1778 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1779 PVE
::LXC
::Config-
>check_lock($conf);
1781 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1783 if ($mpkey eq 'rootfs') {
1784 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1785 } elsif ($mpkey =~ m/mp\d+/) {
1786 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1788 die "Can't parse $mpkey\n";
1790 $old_volid = $mpdata->{volume
};
1792 die "you can't move a volume with snapshots and delete the source\n"
1793 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1795 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1797 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1802 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1804 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1805 my $storage_cfg = PVE
::Storage
::config
();
1810 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1811 my $bwlimit = extract_param
($param, 'bwlimit');
1812 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1813 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$source_storage, $storage], $bwlimit);
1814 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf, undef, $movelimit);
1815 $mpdata->{volume
} = $new_volid;
1817 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1818 my $digest = $conf->{digest
};
1819 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1820 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1822 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1824 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1826 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1830 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1831 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1837 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1838 if defined($new_volid);
1844 if ($param->{delete}) {
1846 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1847 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1853 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1858 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1861 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };