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);
419 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
420 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
421 $lxc_setup->post_create_hook($password, $ssh_keys);
425 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
426 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
429 $conf->{hostname
} ||= "CT$vmid";
430 $conf->{memory
} ||= 512;
431 $conf->{swap
} //= 512;
432 foreach my $mp (keys %$delayed_mp_param) {
433 $conf->{$mp} = $delayed_mp_param->{$mp};
435 # If the template flag was set, we try to convert again to template after restore
437 print STDERR
"Convert restored container to template...\n";
438 if (my $err = check_storage_supports_templates
($conf)) {
440 warn "Leave restored backup as container instead of converting to template.\n"
442 PVE
::LXC
::template_create
($vmid, $conf);
443 $conf->{template
} = 1;
446 PVE
::LXC
::Config-
>write_config($vmid, $conf);
449 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
450 eval { PVE
::LXC
::destroy_config
($vmid) };
454 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
456 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
457 if $start_after_create;
460 my $workername = $restore ?
'vzrestore' : 'vzcreate';
461 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
463 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
466 sub check_storage_supports_templates
{
469 my $scfg = PVE
::Storage
::config
();
471 PVE
::LXC
::Config-
>foreach_mountpoint($conf, sub {
474 my ($sid) = PVE
::Storage
::parse_volume_id
($mp->{volume
}, 0);
475 die "Warning: Directory storage '$sid' does not support container templates!\n"
476 if $scfg->{ids
}->{$sid}->{path
};
482 __PACKAGE__-
>register_method({
487 description
=> "Directory index",
492 additionalProperties
=> 0,
494 node
=> get_standard_option
('pve-node'),
495 vmid
=> get_standard_option
('pve-vmid'),
503 subdir
=> { type
=> 'string' },
506 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
512 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
515 { subdir
=> 'config' },
516 { subdir
=> 'status' },
517 { subdir
=> 'vncproxy' },
518 { subdir
=> 'termproxy' },
519 { subdir
=> 'vncwebsocket' },
520 { subdir
=> 'spiceproxy' },
521 { subdir
=> 'migrate' },
522 { subdir
=> 'clone' },
523 # { subdir => 'initlog' },
525 { subdir
=> 'rrddata' },
526 { subdir
=> 'firewall' },
527 { subdir
=> 'snapshot' },
528 { subdir
=> 'resize' },
535 __PACKAGE__-
>register_method({
537 path
=> '{vmid}/rrd',
539 protected
=> 1, # fixme: can we avoid that?
541 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
543 description
=> "Read VM RRD statistics (returns PNG)",
545 additionalProperties
=> 0,
547 node
=> get_standard_option
('pve-node'),
548 vmid
=> get_standard_option
('pve-vmid'),
550 description
=> "Specify the time frame you are interested in.",
552 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
555 description
=> "The list of datasources you want to display.",
556 type
=> 'string', format
=> 'pve-configid-list',
559 description
=> "The RRD consolidation function",
561 enum
=> [ 'AVERAGE', 'MAX' ],
569 filename
=> { type
=> 'string' },
575 return PVE
::Cluster
::create_rrd_graph
(
576 "pve2-vm/$param->{vmid}", $param->{timeframe
},
577 $param->{ds
}, $param->{cf
});
581 __PACKAGE__-
>register_method({
583 path
=> '{vmid}/rrddata',
585 protected
=> 1, # fixme: can we avoid that?
587 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
589 description
=> "Read VM RRD statistics",
591 additionalProperties
=> 0,
593 node
=> get_standard_option
('pve-node'),
594 vmid
=> get_standard_option
('pve-vmid'),
596 description
=> "Specify the time frame you are interested in.",
598 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
601 description
=> "The RRD consolidation function",
603 enum
=> [ 'AVERAGE', 'MAX' ],
618 return PVE
::Cluster
::create_rrd_data
(
619 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
622 __PACKAGE__-
>register_method({
623 name
=> 'destroy_vm',
628 description
=> "Destroy the container (also delete all uses files).",
630 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
633 additionalProperties
=> 0,
635 node
=> get_standard_option
('pve-node'),
636 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
645 my $rpcenv = PVE
::RPCEnvironment
::get
();
646 my $authuser = $rpcenv->get_user();
647 my $vmid = $param->{vmid
};
649 # test if container exists
650 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
651 my $storage_cfg = cfs_read_file
("storage.cfg");
652 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
654 die "unable to remove CT $vmid - used in HA resources\n"
655 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
657 # do not allow destroy if there are replication jobs
658 my $repl_conf = PVE
::ReplicationConfig-
>new();
659 $repl_conf->check_for_existing_jobs($vmid);
661 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
663 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
666 # reload config after lock
667 $conf = PVE
::LXC
::Config-
>load_config($vmid);
668 PVE
::LXC
::Config-
>check_lock($conf);
670 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
672 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
673 PVE
::AccessControl
::remove_vm_access
($vmid);
674 PVE
::Firewall
::remove_vmfw_conf
($vmid);
677 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
679 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
684 __PACKAGE__-
>register_method ({
686 path
=> '{vmid}/vncproxy',
690 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
692 description
=> "Creates a TCP VNC proxy connections.",
694 additionalProperties
=> 0,
696 node
=> get_standard_option
('pve-node'),
697 vmid
=> get_standard_option
('pve-vmid'),
701 description
=> "use websocket instead of standard VNC.",
705 description
=> "sets the width of the console in pixels.",
712 description
=> "sets the height of the console in pixels.",
720 additionalProperties
=> 0,
722 user
=> { type
=> 'string' },
723 ticket
=> { type
=> 'string' },
724 cert
=> { type
=> 'string' },
725 port
=> { type
=> 'integer' },
726 upid
=> { type
=> 'string' },
732 my $rpcenv = PVE
::RPCEnvironment
::get
();
734 my $authuser = $rpcenv->get_user();
736 my $vmid = $param->{vmid
};
737 my $node = $param->{node
};
739 my $authpath = "/vms/$vmid";
741 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
743 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
746 my ($remip, $family);
748 if ($node ne PVE
::INotify
::nodename
()) {
749 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
751 $family = PVE
::Tools
::get_host_address_family
($node);
754 my $port = PVE
::Tools
::next_vnc_port
($family);
756 # NOTE: vncterm VNC traffic is already TLS encrypted,
757 # so we select the fastest chipher here (or 'none'?)
758 my $remcmd = $remip ?
759 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
761 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
762 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
764 my $shcmd = [ '/usr/bin/dtach', '-A',
765 "/var/run/dtach/vzctlconsole$vmid",
766 '-r', 'winch', '-z', @$concmd];
771 syslog
('info', "starting lxc vnc proxy $upid\n");
775 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
776 '-timeout', $timeout, '-authpath', $authpath,
777 '-perm', 'VM.Console'];
779 if ($param->{width
}) {
780 push @$cmd, '-width', $param->{width
};
783 if ($param->{height
}) {
784 push @$cmd, '-height', $param->{height
};
787 if ($param->{websocket
}) {
788 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
789 push @$cmd, '-notls', '-listen', 'localhost';
792 push @$cmd, '-c', @$remcmd, @$shcmd;
794 run_command
($cmd, keeplocale
=> 1);
799 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
801 PVE
::Tools
::wait_for_vnc_port
($port);
812 __PACKAGE__-
>register_method ({
814 path
=> '{vmid}/termproxy',
818 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
820 description
=> "Creates a TCP proxy connection.",
822 additionalProperties
=> 0,
824 node
=> get_standard_option
('pve-node'),
825 vmid
=> get_standard_option
('pve-vmid'),
829 additionalProperties
=> 0,
831 user
=> { type
=> 'string' },
832 ticket
=> { type
=> 'string' },
833 port
=> { type
=> 'integer' },
834 upid
=> { type
=> 'string' },
840 my $rpcenv = PVE
::RPCEnvironment
::get
();
842 my $authuser = $rpcenv->get_user();
844 my $vmid = $param->{vmid
};
845 my $node = $param->{node
};
847 my $authpath = "/vms/$vmid";
849 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
851 my ($remip, $family);
853 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
854 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
856 $family = PVE
::Tools
::get_host_address_family
($node);
859 my $port = PVE
::Tools
::next_vnc_port
($family);
861 my $remcmd = $remip ?
862 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
864 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
865 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
867 my $shcmd = [ '/usr/bin/dtach', '-A',
868 "/var/run/dtach/vzctlconsole$vmid",
869 '-r', 'winch', '-z', @$concmd];
874 syslog
('info', "starting lxc termproxy $upid\n");
876 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
877 '--perm', 'VM.Console', '--'];
878 push @$cmd, @$remcmd, @$shcmd;
880 PVE
::Tools
::run_command
($cmd);
883 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
885 PVE
::Tools
::wait_for_vnc_port
($port);
895 __PACKAGE__-
>register_method({
896 name
=> 'vncwebsocket',
897 path
=> '{vmid}/vncwebsocket',
900 description
=> "You also need to pass a valid ticket (vncticket).",
901 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
903 description
=> "Opens a weksocket for VNC traffic.",
905 additionalProperties
=> 0,
907 node
=> get_standard_option
('pve-node'),
908 vmid
=> get_standard_option
('pve-vmid'),
910 description
=> "Ticket from previous call to vncproxy.",
915 description
=> "Port number returned by previous vncproxy call.",
925 port
=> { type
=> 'string' },
931 my $rpcenv = PVE
::RPCEnvironment
::get
();
933 my $authuser = $rpcenv->get_user();
935 my $authpath = "/vms/$param->{vmid}";
937 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
939 my $port = $param->{port
};
941 return { port
=> $port };
944 __PACKAGE__-
>register_method ({
945 name
=> 'spiceproxy',
946 path
=> '{vmid}/spiceproxy',
951 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
953 description
=> "Returns a SPICE configuration to connect to the CT.",
955 additionalProperties
=> 0,
957 node
=> get_standard_option
('pve-node'),
958 vmid
=> get_standard_option
('pve-vmid'),
959 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
962 returns
=> get_standard_option
('remote-viewer-config'),
966 my $vmid = $param->{vmid
};
967 my $node = $param->{node
};
968 my $proxy = $param->{proxy
};
970 my $authpath = "/vms/$vmid";
971 my $permissions = 'VM.Console';
973 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
975 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
977 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
979 my $shcmd = ['/usr/bin/dtach', '-A',
980 "/var/run/dtach/vzctlconsole$vmid",
981 '-r', 'winch', '-z', @$concmd];
983 my $title = "CT $vmid";
985 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
989 __PACKAGE__-
>register_method({
990 name
=> 'migrate_vm',
991 path
=> '{vmid}/migrate',
995 description
=> "Migrate the container to another node. Creates a new migration task.",
997 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1000 additionalProperties
=> 0,
1002 node
=> get_standard_option
('pve-node'),
1003 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1004 target
=> get_standard_option
('pve-node', {
1005 description
=> "Target node.",
1006 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1010 description
=> "Use online/live migration.",
1015 description
=> "Use restart migration",
1020 description
=> "Timeout in seconds for shutdown for restart migration",
1026 description
=> "Force migration despite local bind / device" .
1027 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1031 description
=> "Override I/O bandwidth limit (in KiB/s).",
1035 default => 'migrate limit from datacenter or storage config',
1041 description
=> "the task ID.",
1046 my $rpcenv = PVE
::RPCEnvironment
::get
();
1048 my $authuser = $rpcenv->get_user();
1050 my $target = extract_param
($param, 'target');
1052 my $localnode = PVE
::INotify
::nodename
();
1053 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1055 PVE
::Cluster
::check_cfs_quorum
();
1057 PVE
::Cluster
::check_node_exists
($target);
1059 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1061 my $vmid = extract_param
($param, 'vmid');
1064 PVE
::LXC
::Config-
>load_config($vmid);
1066 # try to detect errors early
1067 if (PVE
::LXC
::check_running
($vmid)) {
1068 die "can't migrate running container without --online or --restart\n"
1069 if !$param->{online
} && !$param->{restart
};
1072 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1077 my $service = "ct:$vmid";
1079 my $cmd = ['ha-manager', 'migrate', $service, $target];
1081 print "Requesting HA migration for CT $vmid to node $target\n";
1083 PVE
::Tools
::run_command
($cmd);
1088 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1093 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1097 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1100 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1104 __PACKAGE__-
>register_method({
1105 name
=> 'vm_feature',
1106 path
=> '{vmid}/feature',
1110 description
=> "Check if feature for virtual machine is available.",
1112 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1115 additionalProperties
=> 0,
1117 node
=> get_standard_option
('pve-node'),
1118 vmid
=> get_standard_option
('pve-vmid'),
1120 description
=> "Feature to check.",
1122 enum
=> [ 'snapshot', 'clone', 'copy' ],
1124 snapname
=> get_standard_option
('pve-snapshot-name', {
1132 hasFeature
=> { type
=> 'boolean' },
1135 #items => { type => 'string' },
1142 my $node = extract_param
($param, 'node');
1144 my $vmid = extract_param
($param, 'vmid');
1146 my $snapname = extract_param
($param, 'snapname');
1148 my $feature = extract_param
($param, 'feature');
1150 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1153 my $snap = $conf->{snapshots
}->{$snapname};
1154 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1157 my $storage_cfg = PVE
::Storage
::config
();
1158 #Maybe include later
1159 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1160 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1163 hasFeature
=> $hasFeature,
1164 #nodes => [ keys %$nodelist ],
1168 __PACKAGE__-
>register_method({
1170 path
=> '{vmid}/template',
1174 description
=> "Create a Template.",
1176 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1177 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1180 additionalProperties
=> 0,
1182 node
=> get_standard_option
('pve-node'),
1183 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1186 returns
=> { type
=> 'null'},
1190 my $rpcenv = PVE
::RPCEnvironment
::get
();
1192 my $authuser = $rpcenv->get_user();
1194 my $node = extract_param
($param, 'node');
1196 my $vmid = extract_param
($param, 'vmid');
1198 my $updatefn = sub {
1200 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1201 PVE
::LXC
::Config-
>check_lock($conf);
1203 die "unable to create template, because CT contains snapshots\n"
1204 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1206 die "you can't convert a template to a template\n"
1207 if PVE
::LXC
::Config-
>is_template($conf);
1209 die "you can't convert a CT to template if the CT is running\n"
1210 if PVE
::LXC
::check_running
($vmid);
1212 if (my $err = check_storage_supports_templates
($conf)) {
1217 PVE
::LXC
::template_create
($vmid, $conf);
1219 $conf->{template
} = 1;
1221 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1222 # and remove lxc config
1223 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1226 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1229 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1234 __PACKAGE__-
>register_method({
1236 path
=> '{vmid}/clone',
1240 description
=> "Create a container clone/copy",
1242 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1243 "and 'VM.Allocate' permissions " .
1244 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1245 "'Datastore.AllocateSpace' on any used storage.",
1248 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1250 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1251 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1256 additionalProperties
=> 0,
1258 node
=> get_standard_option
('pve-node'),
1259 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1260 newid
=> get_standard_option
('pve-vmid', {
1261 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1262 description
=> 'VMID for the clone.' }),
1265 type
=> 'string', format
=> 'dns-name',
1266 description
=> "Set a hostname for the new CT.",
1271 description
=> "Description for the new CT.",
1275 type
=> 'string', format
=> 'pve-poolid',
1276 description
=> "Add the new CT to the specified pool.",
1278 snapname
=> get_standard_option
('pve-snapshot-name', {
1281 storage
=> get_standard_option
('pve-storage-id', {
1282 description
=> "Target storage for full clone.",
1288 description
=> "Create a full copy of all disks. This is always done when " .
1289 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1291 target
=> get_standard_option
('pve-node', {
1292 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1296 description
=> "Override I/O bandwidth limit (in KiB/s).",
1300 default => 'clone limit from datacenter or storage config',
1310 my $rpcenv = PVE
::RPCEnvironment
::get
();
1312 my $authuser = $rpcenv->get_user();
1314 my $node = extract_param
($param, 'node');
1316 my $vmid = extract_param
($param, 'vmid');
1318 my $newid = extract_param
($param, 'newid');
1320 my $pool = extract_param
($param, 'pool');
1322 if (defined($pool)) {
1323 $rpcenv->check_pool_exist($pool);
1326 my $snapname = extract_param
($param, 'snapname');
1328 my $storage = extract_param
($param, 'storage');
1330 my $target = extract_param
($param, 'target');
1332 my $localnode = PVE
::INotify
::nodename
();
1334 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1336 PVE
::Cluster
::check_node_exists
($target) if $target;
1338 my $storecfg = PVE
::Storage
::config
();
1341 # check if storage is enabled on local node
1342 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1344 # check if storage is available on target node
1345 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1346 # clone only works if target storage is shared
1347 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1348 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1352 PVE
::Cluster
::check_cfs_quorum
();
1356 my $mountpoints = {};
1361 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1362 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1364 $running = PVE
::LXC
::check_running
($vmid) || 0;
1366 my $full = extract_param
($param, 'full');
1367 if (!defined($full)) {
1368 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1370 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1373 die "snapshot '$snapname' does not exist\n"
1374 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1377 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1379 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1380 die "unable to create CT $newid: config file already exists\n"
1384 foreach my $opt (keys %$src_conf) {
1385 next if $opt =~ m/^unused\d+$/;
1387 my $value = $src_conf->{$opt};
1389 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1390 my $mp = $opt eq 'rootfs' ?
1391 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1392 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1394 if ($mp->{type
} eq 'volume') {
1395 my $volid = $mp->{volume
};
1397 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1398 $sid = $storage if defined($storage);
1399 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1400 if (!$scfg->{shared
}) {
1402 warn "found non-shared volume: $volid\n" if $target;
1405 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1408 die "Cannot do full clones on a running container without snapshots\n"
1409 if $running && !defined($snapname);
1410 $fullclone->{$opt} = 1;
1412 # not full means clone instead of copy
1413 die "Linked clone feature for '$volid' is not available\n"
1414 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1417 $mountpoints->{$opt} = $mp;
1418 push @$vollist, $volid;
1421 # TODO: allow bind mounts?
1422 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1424 } elsif ($opt =~ m/^net(\d+)$/) {
1425 # always change MAC! address
1426 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1427 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1428 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1429 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1431 # copy everything else
1432 $newconf->{$opt} = $value;
1435 die "can't clone CT to node '$target' (CT uses local storage)\n"
1436 if $target && !$sharedvm;
1438 # Replace the 'disk' lock with a 'create' lock.
1439 $newconf->{lock} = 'create';
1441 delete $newconf->{template
};
1442 if ($param->{hostname
}) {
1443 $newconf->{hostname
} = $param->{hostname
};
1446 if ($param->{description
}) {
1447 $newconf->{description
} = $param->{description
};
1450 # create empty/temp config - this fails if CT already exists on other node
1451 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1454 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1460 my $update_conf = sub {
1461 my ($key, $value) = @_;
1462 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1463 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1464 die "Lost 'create' config lock, aborting.\n"
1465 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1466 $conf->{$key} = $value;
1467 PVE
::LXC
::Config-
>write_config($newid, $conf);
1474 my $newvollist = [];
1476 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1477 die "unexpected state change\n" if $verify_running != $running;
1483 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1485 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1486 my $bwlimit = extract_param
($param, 'bwlimit');
1488 foreach my $opt (keys %$mountpoints) {
1489 my $mp = $mountpoints->{$opt};
1490 my $volid = $mp->{volume
};
1493 if ($fullclone->{$opt}) {
1494 print "create full clone of mountpoint $opt ($volid)\n";
1495 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1496 my $target_storage = $storage // $source_storage;
1497 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1498 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1500 print "create linked clone of mount point $opt ($volid)\n";
1501 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1504 push @$newvollist, $newvolid;
1505 $mp->{volume
} = $newvolid;
1507 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1510 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1511 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1514 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1515 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1516 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1518 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1519 die "Failed to move config to node '$target' - rename failed: $!\n"
1520 if !rename($conffile, $newconffile);
1525 # Unlock the source config in any case:
1526 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1530 # Now cleanup the config & disks:
1533 sleep 1; # some storages like rbd need to wait before release volume - really?
1535 foreach my $volid (@$newvollist) {
1536 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1539 die "clone failed: $err";
1545 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1546 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1550 __PACKAGE__-
>register_method({
1551 name
=> 'resize_vm',
1552 path
=> '{vmid}/resize',
1556 description
=> "Resize a container mount point.",
1558 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1561 additionalProperties
=> 0,
1563 node
=> get_standard_option
('pve-node'),
1564 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1567 description
=> "The disk you want to resize.",
1568 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1572 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1573 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.",
1577 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1585 description
=> "the task ID.",
1590 my $rpcenv = PVE
::RPCEnvironment
::get
();
1592 my $authuser = $rpcenv->get_user();
1594 my $node = extract_param
($param, 'node');
1596 my $vmid = extract_param
($param, 'vmid');
1598 my $digest = extract_param
($param, 'digest');
1600 my $sizestr = extract_param
($param, 'size');
1601 my $ext = ($sizestr =~ s/^\+//);
1602 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1603 die "invalid size string" if !defined($newsize);
1605 die "no options specified\n" if !scalar(keys %$param);
1607 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1609 my $storage_cfg = cfs_read_file
("storage.cfg");
1613 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1614 PVE
::LXC
::Config-
>check_lock($conf);
1616 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1618 my $running = PVE
::LXC
::check_running
($vmid);
1620 my $disk = $param->{disk
};
1621 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1622 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1624 my $volid = $mp->{volume
};
1626 my (undef, undef, $owner, undef, undef, undef, $format) =
1627 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1629 die "can't resize mount point owned by another container ($owner)"
1632 die "can't resize volume: $disk if snapshot exists\n"
1633 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1635 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1637 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1639 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1641 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1642 $newsize += $size if $ext;
1643 $newsize = int($newsize);
1645 die "unable to shrink disk size\n" if $newsize < $size;
1647 return if $size == $newsize;
1649 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1651 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1652 # we pass 0 here (parameter only makes sense for qemu)
1653 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1655 $mp->{size
} = $newsize;
1656 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1658 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1660 if ($format eq 'raw') {
1661 # we need to ensure that the volume is mapped, if not needed this is a NOP
1662 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1663 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1667 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1668 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1669 die "internal error: CT running but mount point not attached to a loop device"
1671 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1673 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1674 # to be visible to it in its namespace.
1675 # To not interfere with the rest of the system we unshare the current mount namespace,
1676 # mount over /tmp and then run resize2fs.
1678 # interestingly we don't need to e2fsck on mounted systems...
1679 my $quoted = PVE
::Tools
::shellquote
($path);
1680 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1682 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1684 warn "Failed to update the container's filesystem: $@\n" if $@;
1687 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1688 PVE
::Tools
::run_command
(['resize2fs', $path]);
1690 warn "Failed to update the container's filesystem: $@\n" if $@;
1692 # always un-map if not running, this is a NOP if not needed
1693 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1698 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1701 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1704 __PACKAGE__-
>register_method({
1705 name
=> 'move_volume',
1706 path
=> '{vmid}/move_volume',
1710 description
=> "Move a rootfs-/mp-volume to a different storage",
1712 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1713 "and 'Datastore.AllocateSpace' permissions on the storage.",
1716 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1717 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1721 additionalProperties
=> 0,
1723 node
=> get_standard_option
('pve-node'),
1724 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1727 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1728 description
=> "Volume which will be moved.",
1730 storage
=> get_standard_option
('pve-storage-id', {
1731 description
=> "Target Storage.",
1732 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1736 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1742 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1747 description
=> "Override I/O bandwidth limit (in KiB/s).",
1751 default => 'clone limit from datacenter or storage config',
1761 my $rpcenv = PVE
::RPCEnvironment
::get
();
1763 my $authuser = $rpcenv->get_user();
1765 my $vmid = extract_param
($param, 'vmid');
1767 my $storage = extract_param
($param, 'storage');
1769 my $mpkey = extract_param
($param, 'volume');
1771 my $lockname = 'disk';
1773 my ($mpdata, $old_volid);
1775 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1776 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1777 PVE
::LXC
::Config-
>check_lock($conf);
1779 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1781 if ($mpkey eq 'rootfs') {
1782 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1783 } elsif ($mpkey =~ m/mp\d+/) {
1784 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1786 die "Can't parse $mpkey\n";
1788 $old_volid = $mpdata->{volume
};
1790 die "you can't move a volume with snapshots and delete the source\n"
1791 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1793 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1795 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1800 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1802 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1803 my $storage_cfg = PVE
::Storage
::config
();
1808 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1809 my $bwlimit = extract_param
($param, 'bwlimit');
1810 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1811 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$source_storage, $storage], $bwlimit);
1812 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf, undef, $movelimit);
1813 $mpdata->{volume
} = $new_volid;
1815 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1816 my $digest = $conf->{digest
};
1817 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1818 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1820 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1822 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1824 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1828 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1829 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1835 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1836 if defined($new_volid);
1842 if ($param->{delete}) {
1844 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1845 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1851 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1856 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1859 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };