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
();
647 my $authuser = $rpcenv->get_user();
649 my $vmid = $param->{vmid
};
651 # test if container exists
652 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
654 my $storage_cfg = cfs_read_file
("storage.cfg");
656 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
658 die "unable to remove CT $vmid - used in HA resources\n"
659 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
661 # do not allow destroy if there are replication jobs
662 my $repl_conf = PVE
::ReplicationConfig-
>new();
663 $repl_conf->check_for_existing_jobs($vmid);
665 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
667 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
670 # reload config after lock
671 $conf = PVE
::LXC
::Config-
>load_config($vmid);
672 PVE
::LXC
::Config-
>check_lock($conf);
674 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
676 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
677 PVE
::AccessControl
::remove_vm_access
($vmid);
678 PVE
::Firewall
::remove_vmfw_conf
($vmid);
681 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
683 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
688 __PACKAGE__-
>register_method ({
690 path
=> '{vmid}/vncproxy',
694 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
696 description
=> "Creates a TCP VNC proxy connections.",
698 additionalProperties
=> 0,
700 node
=> get_standard_option
('pve-node'),
701 vmid
=> get_standard_option
('pve-vmid'),
705 description
=> "use websocket instead of standard VNC.",
709 description
=> "sets the width of the console in pixels.",
716 description
=> "sets the height of the console in pixels.",
724 additionalProperties
=> 0,
726 user
=> { type
=> 'string' },
727 ticket
=> { type
=> 'string' },
728 cert
=> { type
=> 'string' },
729 port
=> { type
=> 'integer' },
730 upid
=> { type
=> 'string' },
736 my $rpcenv = PVE
::RPCEnvironment
::get
();
738 my $authuser = $rpcenv->get_user();
740 my $vmid = $param->{vmid
};
741 my $node = $param->{node
};
743 my $authpath = "/vms/$vmid";
745 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
747 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
750 my ($remip, $family);
752 if ($node ne PVE
::INotify
::nodename
()) {
753 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
755 $family = PVE
::Tools
::get_host_address_family
($node);
758 my $port = PVE
::Tools
::next_vnc_port
($family);
760 # NOTE: vncterm VNC traffic is already TLS encrypted,
761 # so we select the fastest chipher here (or 'none'?)
762 my $remcmd = $remip ?
763 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
765 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
766 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
768 my $shcmd = [ '/usr/bin/dtach', '-A',
769 "/var/run/dtach/vzctlconsole$vmid",
770 '-r', 'winch', '-z', @$concmd];
775 syslog
('info', "starting lxc vnc proxy $upid\n");
779 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
780 '-timeout', $timeout, '-authpath', $authpath,
781 '-perm', 'VM.Console'];
783 if ($param->{width
}) {
784 push @$cmd, '-width', $param->{width
};
787 if ($param->{height
}) {
788 push @$cmd, '-height', $param->{height
};
791 if ($param->{websocket
}) {
792 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
793 push @$cmd, '-notls', '-listen', 'localhost';
796 push @$cmd, '-c', @$remcmd, @$shcmd;
798 run_command
($cmd, keeplocale
=> 1);
803 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
805 PVE
::Tools
::wait_for_vnc_port
($port);
816 __PACKAGE__-
>register_method ({
818 path
=> '{vmid}/termproxy',
822 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
824 description
=> "Creates a TCP proxy connection.",
826 additionalProperties
=> 0,
828 node
=> get_standard_option
('pve-node'),
829 vmid
=> get_standard_option
('pve-vmid'),
833 additionalProperties
=> 0,
835 user
=> { type
=> 'string' },
836 ticket
=> { type
=> 'string' },
837 port
=> { type
=> 'integer' },
838 upid
=> { type
=> 'string' },
844 my $rpcenv = PVE
::RPCEnvironment
::get
();
846 my $authuser = $rpcenv->get_user();
848 my $vmid = $param->{vmid
};
849 my $node = $param->{node
};
851 my $authpath = "/vms/$vmid";
853 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
855 my ($remip, $family);
857 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
858 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
860 $family = PVE
::Tools
::get_host_address_family
($node);
863 my $port = PVE
::Tools
::next_vnc_port
($family);
865 my $remcmd = $remip ?
866 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
868 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
869 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
871 my $shcmd = [ '/usr/bin/dtach', '-A',
872 "/var/run/dtach/vzctlconsole$vmid",
873 '-r', 'winch', '-z', @$concmd];
878 syslog
('info', "starting lxc termproxy $upid\n");
880 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
881 '--perm', 'VM.Console', '--'];
882 push @$cmd, @$remcmd, @$shcmd;
884 PVE
::Tools
::run_command
($cmd);
887 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
889 PVE
::Tools
::wait_for_vnc_port
($port);
899 __PACKAGE__-
>register_method({
900 name
=> 'vncwebsocket',
901 path
=> '{vmid}/vncwebsocket',
904 description
=> "You also need to pass a valid ticket (vncticket).",
905 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
907 description
=> "Opens a weksocket for VNC traffic.",
909 additionalProperties
=> 0,
911 node
=> get_standard_option
('pve-node'),
912 vmid
=> get_standard_option
('pve-vmid'),
914 description
=> "Ticket from previous call to vncproxy.",
919 description
=> "Port number returned by previous vncproxy call.",
929 port
=> { type
=> 'string' },
935 my $rpcenv = PVE
::RPCEnvironment
::get
();
937 my $authuser = $rpcenv->get_user();
939 my $authpath = "/vms/$param->{vmid}";
941 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
943 my $port = $param->{port
};
945 return { port
=> $port };
948 __PACKAGE__-
>register_method ({
949 name
=> 'spiceproxy',
950 path
=> '{vmid}/spiceproxy',
955 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
957 description
=> "Returns a SPICE configuration to connect to the CT.",
959 additionalProperties
=> 0,
961 node
=> get_standard_option
('pve-node'),
962 vmid
=> get_standard_option
('pve-vmid'),
963 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
966 returns
=> get_standard_option
('remote-viewer-config'),
970 my $vmid = $param->{vmid
};
971 my $node = $param->{node
};
972 my $proxy = $param->{proxy
};
974 my $authpath = "/vms/$vmid";
975 my $permissions = 'VM.Console';
977 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
979 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
981 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
983 my $shcmd = ['/usr/bin/dtach', '-A',
984 "/var/run/dtach/vzctlconsole$vmid",
985 '-r', 'winch', '-z', @$concmd];
987 my $title = "CT $vmid";
989 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
993 __PACKAGE__-
>register_method({
994 name
=> 'migrate_vm',
995 path
=> '{vmid}/migrate',
999 description
=> "Migrate the container to another node. Creates a new migration task.",
1001 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1004 additionalProperties
=> 0,
1006 node
=> get_standard_option
('pve-node'),
1007 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1008 target
=> get_standard_option
('pve-node', {
1009 description
=> "Target node.",
1010 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1014 description
=> "Use online/live migration.",
1019 description
=> "Use restart migration",
1024 description
=> "Timeout in seconds for shutdown for restart migration",
1030 description
=> "Force migration despite local bind / device" .
1031 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1035 description
=> "Override I/O bandwidth limit (in KiB/s).",
1039 default => 'migrate limit from datacenter or storage config',
1045 description
=> "the task ID.",
1050 my $rpcenv = PVE
::RPCEnvironment
::get
();
1052 my $authuser = $rpcenv->get_user();
1054 my $target = extract_param
($param, 'target');
1056 my $localnode = PVE
::INotify
::nodename
();
1057 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1059 PVE
::Cluster
::check_cfs_quorum
();
1061 PVE
::Cluster
::check_node_exists
($target);
1063 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1065 my $vmid = extract_param
($param, 'vmid');
1068 PVE
::LXC
::Config-
>load_config($vmid);
1070 # try to detect errors early
1071 if (PVE
::LXC
::check_running
($vmid)) {
1072 die "can't migrate running container without --online or --restart\n"
1073 if !$param->{online
} && !$param->{restart
};
1076 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1081 my $service = "ct:$vmid";
1083 my $cmd = ['ha-manager', 'migrate', $service, $target];
1085 print "Requesting HA migration for CT $vmid to node $target\n";
1087 PVE
::Tools
::run_command
($cmd);
1092 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1097 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1101 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1104 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1108 __PACKAGE__-
>register_method({
1109 name
=> 'vm_feature',
1110 path
=> '{vmid}/feature',
1114 description
=> "Check if feature for virtual machine is available.",
1116 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1119 additionalProperties
=> 0,
1121 node
=> get_standard_option
('pve-node'),
1122 vmid
=> get_standard_option
('pve-vmid'),
1124 description
=> "Feature to check.",
1126 enum
=> [ 'snapshot', 'clone', 'copy' ],
1128 snapname
=> get_standard_option
('pve-snapshot-name', {
1136 hasFeature
=> { type
=> 'boolean' },
1139 #items => { type => 'string' },
1146 my $node = extract_param
($param, 'node');
1148 my $vmid = extract_param
($param, 'vmid');
1150 my $snapname = extract_param
($param, 'snapname');
1152 my $feature = extract_param
($param, 'feature');
1154 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1157 my $snap = $conf->{snapshots
}->{$snapname};
1158 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1161 my $storage_cfg = PVE
::Storage
::config
();
1162 #Maybe include later
1163 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1164 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1167 hasFeature
=> $hasFeature,
1168 #nodes => [ keys %$nodelist ],
1172 __PACKAGE__-
>register_method({
1174 path
=> '{vmid}/template',
1178 description
=> "Create a Template.",
1180 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1181 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1184 additionalProperties
=> 0,
1186 node
=> get_standard_option
('pve-node'),
1187 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1190 returns
=> { type
=> 'null'},
1194 my $rpcenv = PVE
::RPCEnvironment
::get
();
1196 my $authuser = $rpcenv->get_user();
1198 my $node = extract_param
($param, 'node');
1200 my $vmid = extract_param
($param, 'vmid');
1202 my $updatefn = sub {
1204 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1205 PVE
::LXC
::Config-
>check_lock($conf);
1207 die "unable to create template, because CT contains snapshots\n"
1208 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1210 die "you can't convert a template to a template\n"
1211 if PVE
::LXC
::Config-
>is_template($conf);
1213 die "you can't convert a CT to template if the CT is running\n"
1214 if PVE
::LXC
::check_running
($vmid);
1216 if (my $err = check_storage_supports_templates
($conf)) {
1221 PVE
::LXC
::template_create
($vmid, $conf);
1223 $conf->{template
} = 1;
1225 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1226 # and remove lxc config
1227 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1230 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1233 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1238 __PACKAGE__-
>register_method({
1240 path
=> '{vmid}/clone',
1244 description
=> "Create a container clone/copy",
1246 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1247 "and 'VM.Allocate' permissions " .
1248 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1249 "'Datastore.AllocateSpace' on any used storage.",
1252 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1254 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1255 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1260 additionalProperties
=> 0,
1262 node
=> get_standard_option
('pve-node'),
1263 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1264 newid
=> get_standard_option
('pve-vmid', {
1265 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1266 description
=> 'VMID for the clone.' }),
1269 type
=> 'string', format
=> 'dns-name',
1270 description
=> "Set a hostname for the new CT.",
1275 description
=> "Description for the new CT.",
1279 type
=> 'string', format
=> 'pve-poolid',
1280 description
=> "Add the new CT to the specified pool.",
1282 snapname
=> get_standard_option
('pve-snapshot-name', {
1285 storage
=> get_standard_option
('pve-storage-id', {
1286 description
=> "Target storage for full clone.",
1292 description
=> "Create a full copy of all disks. This is always done when " .
1293 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1295 target
=> get_standard_option
('pve-node', {
1296 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1300 description
=> "Override I/O bandwidth limit (in KiB/s).",
1304 default => 'clone limit from datacenter or storage config',
1314 my $rpcenv = PVE
::RPCEnvironment
::get
();
1316 my $authuser = $rpcenv->get_user();
1318 my $node = extract_param
($param, 'node');
1320 my $vmid = extract_param
($param, 'vmid');
1322 my $newid = extract_param
($param, 'newid');
1324 my $pool = extract_param
($param, 'pool');
1326 if (defined($pool)) {
1327 $rpcenv->check_pool_exist($pool);
1330 my $snapname = extract_param
($param, 'snapname');
1332 my $storage = extract_param
($param, 'storage');
1334 my $target = extract_param
($param, 'target');
1336 my $localnode = PVE
::INotify
::nodename
();
1338 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1340 PVE
::Cluster
::check_node_exists
($target) if $target;
1342 my $storecfg = PVE
::Storage
::config
();
1345 # check if storage is enabled on local node
1346 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1348 # check if storage is available on target node
1349 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1350 # clone only works if target storage is shared
1351 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1352 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1356 PVE
::Cluster
::check_cfs_quorum
();
1360 my $mountpoints = {};
1365 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1366 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1368 $running = PVE
::LXC
::check_running
($vmid) || 0;
1370 my $full = extract_param
($param, 'full');
1371 if (!defined($full)) {
1372 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1374 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1377 die "snapshot '$snapname' does not exist\n"
1378 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1381 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1383 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1384 die "unable to create CT $newid: config file already exists\n"
1388 foreach my $opt (keys %$src_conf) {
1389 next if $opt =~ m/^unused\d+$/;
1391 my $value = $src_conf->{$opt};
1393 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1394 my $mp = $opt eq 'rootfs' ?
1395 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1396 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1398 if ($mp->{type
} eq 'volume') {
1399 my $volid = $mp->{volume
};
1401 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1402 $sid = $storage if defined($storage);
1403 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1404 if (!$scfg->{shared
}) {
1406 warn "found non-shared volume: $volid\n" if $target;
1409 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1412 die "Cannot do full clones on a running container without snapshots\n"
1413 if $running && !defined($snapname);
1414 $fullclone->{$opt} = 1;
1416 # not full means clone instead of copy
1417 die "Linked clone feature for '$volid' is not available\n"
1418 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1421 $mountpoints->{$opt} = $mp;
1422 push @$vollist, $volid;
1425 # TODO: allow bind mounts?
1426 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1428 } elsif ($opt =~ m/^net(\d+)$/) {
1429 # always change MAC! address
1430 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1431 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1432 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1433 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1435 # copy everything else
1436 $newconf->{$opt} = $value;
1439 die "can't clone CT to node '$target' (CT uses local storage)\n"
1440 if $target && !$sharedvm;
1442 # Replace the 'disk' lock with a 'create' lock.
1443 $newconf->{lock} = 'create';
1445 delete $newconf->{template
};
1446 if ($param->{hostname
}) {
1447 $newconf->{hostname
} = $param->{hostname
};
1450 if ($param->{description
}) {
1451 $newconf->{description
} = $param->{description
};
1454 # create empty/temp config - this fails if CT already exists on other node
1455 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1458 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1464 my $update_conf = sub {
1465 my ($key, $value) = @_;
1466 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1467 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1468 die "Lost 'create' config lock, aborting.\n"
1469 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1470 $conf->{$key} = $value;
1471 PVE
::LXC
::Config-
>write_config($newid, $conf);
1478 my $newvollist = [];
1480 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1481 die "unexpected state change\n" if $verify_running != $running;
1487 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1489 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1490 my $bwlimit = extract_param
($param, 'bwlimit');
1492 foreach my $opt (keys %$mountpoints) {
1493 my $mp = $mountpoints->{$opt};
1494 my $volid = $mp->{volume
};
1497 if ($fullclone->{$opt}) {
1498 print "create full clone of mountpoint $opt ($volid)\n";
1499 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1500 my $target_storage = $storage // $source_storage;
1501 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1502 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1504 print "create linked clone of mount point $opt ($volid)\n";
1505 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1508 push @$newvollist, $newvolid;
1509 $mp->{volume
} = $newvolid;
1511 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1514 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1515 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1518 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1519 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1520 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1522 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1523 die "Failed to move config to node '$target' - rename failed: $!\n"
1524 if !rename($conffile, $newconffile);
1529 # Unlock the source config in any case:
1530 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1534 # Now cleanup the config & disks:
1537 sleep 1; # some storages like rbd need to wait before release volume - really?
1539 foreach my $volid (@$newvollist) {
1540 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1543 die "clone failed: $err";
1549 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1550 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1554 __PACKAGE__-
>register_method({
1555 name
=> 'resize_vm',
1556 path
=> '{vmid}/resize',
1560 description
=> "Resize a container mount point.",
1562 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1565 additionalProperties
=> 0,
1567 node
=> get_standard_option
('pve-node'),
1568 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1571 description
=> "The disk you want to resize.",
1572 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1576 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1577 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.",
1581 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1589 description
=> "the task ID.",
1594 my $rpcenv = PVE
::RPCEnvironment
::get
();
1596 my $authuser = $rpcenv->get_user();
1598 my $node = extract_param
($param, 'node');
1600 my $vmid = extract_param
($param, 'vmid');
1602 my $digest = extract_param
($param, 'digest');
1604 my $sizestr = extract_param
($param, 'size');
1605 my $ext = ($sizestr =~ s/^\+//);
1606 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1607 die "invalid size string" if !defined($newsize);
1609 die "no options specified\n" if !scalar(keys %$param);
1611 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1613 my $storage_cfg = cfs_read_file
("storage.cfg");
1617 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1618 PVE
::LXC
::Config-
>check_lock($conf);
1620 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1622 my $running = PVE
::LXC
::check_running
($vmid);
1624 my $disk = $param->{disk
};
1625 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1626 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1628 my $volid = $mp->{volume
};
1630 my (undef, undef, $owner, undef, undef, undef, $format) =
1631 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1633 die "can't resize mount point owned by another container ($owner)"
1636 die "can't resize volume: $disk if snapshot exists\n"
1637 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1639 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1641 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1643 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1645 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1646 $newsize += $size if $ext;
1647 $newsize = int($newsize);
1649 die "unable to shrink disk size\n" if $newsize < $size;
1651 return if $size == $newsize;
1653 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1655 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1656 # we pass 0 here (parameter only makes sense for qemu)
1657 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1659 $mp->{size
} = $newsize;
1660 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1662 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1664 if ($format eq 'raw') {
1665 # we need to ensure that the volume is mapped, if not needed this is a NOP
1666 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1667 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1671 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1672 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1673 die "internal error: CT running but mount point not attached to a loop device"
1675 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1677 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1678 # to be visible to it in its namespace.
1679 # To not interfere with the rest of the system we unshare the current mount namespace,
1680 # mount over /tmp and then run resize2fs.
1682 # interestingly we don't need to e2fsck on mounted systems...
1683 my $quoted = PVE
::Tools
::shellquote
($path);
1684 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1686 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1688 warn "Failed to update the container's filesystem: $@\n" if $@;
1691 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1692 PVE
::Tools
::run_command
(['resize2fs', $path]);
1694 warn "Failed to update the container's filesystem: $@\n" if $@;
1696 # always un-map if not running, this is a NOP if not needed
1697 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1702 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1705 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1708 __PACKAGE__-
>register_method({
1709 name
=> 'move_volume',
1710 path
=> '{vmid}/move_volume',
1714 description
=> "Move a rootfs-/mp-volume to a different storage",
1716 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1717 "and 'Datastore.AllocateSpace' permissions on the storage.",
1720 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1721 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1725 additionalProperties
=> 0,
1727 node
=> get_standard_option
('pve-node'),
1728 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1731 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1732 description
=> "Volume which will be moved.",
1734 storage
=> get_standard_option
('pve-storage-id', {
1735 description
=> "Target Storage.",
1736 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1740 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1746 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1751 description
=> "Override I/O bandwidth limit (in KiB/s).",
1755 default => 'clone limit from datacenter or storage config',
1765 my $rpcenv = PVE
::RPCEnvironment
::get
();
1767 my $authuser = $rpcenv->get_user();
1769 my $vmid = extract_param
($param, 'vmid');
1771 my $storage = extract_param
($param, 'storage');
1773 my $mpkey = extract_param
($param, 'volume');
1775 my $lockname = 'disk';
1777 my ($mpdata, $old_volid);
1779 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1780 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1781 PVE
::LXC
::Config-
>check_lock($conf);
1783 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1785 if ($mpkey eq 'rootfs') {
1786 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1787 } elsif ($mpkey =~ m/mp\d+/) {
1788 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1790 die "Can't parse $mpkey\n";
1792 $old_volid = $mpdata->{volume
};
1794 die "you can't move a volume with snapshots and delete the source\n"
1795 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1797 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1799 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1804 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1806 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1807 my $storage_cfg = PVE
::Storage
::config
();
1812 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1813 my $bwlimit = extract_param
($param, 'bwlimit');
1814 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1815 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$source_storage, $storage], $bwlimit);
1816 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf, undef, $movelimit);
1817 $mpdata->{volume
} = $new_volid;
1819 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1820 my $digest = $conf->{digest
};
1821 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1822 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1824 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1826 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1828 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1832 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1833 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1839 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1840 if defined($new_volid);
1846 if ($param->{delete}) {
1848 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1849 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1855 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1860 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1863 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };