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 restricted=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
} = $orig_conf->{lxc
};
363 if ($storage_only_mode) {
365 if (!defined($orig_mp_param)) {
366 (undef, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
368 $mp_param = $orig_mp_param;
369 die "rootfs configuration could not be recovered, please check and specify manually!\n"
370 if !defined($mp_param->{rootfs
});
371 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
372 my ($ms, $mountpoint) = @_;
373 my $type = $mountpoint->{type
};
374 if ($type eq 'volume') {
375 die "unable to detect disk size - please specify $ms (size)\n"
376 if !defined($mountpoint->{size
});
377 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
378 delete $mountpoint->{size
};
379 $mountpoint->{volume
} = "$storage:$disksize";
380 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
382 my $type = $mountpoint->{type
};
383 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
384 if ($ms eq 'rootfs');
385 die "restoring '$ms' to $type mount is only possible for root\n"
388 if ($mountpoint->{backup
}) {
389 warn "WARNING - unsupported configuration!\n";
390 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
391 warn "mount point configuration will be restored after archive extraction!\n";
392 warn "contained files will be restored to wrong directory!\n";
394 delete $mp_param->{$ms}; # actually delay bind/dev mps
395 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
399 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
403 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
405 # we always have the 'create' lock so check for more than 1 entry
406 if (scalar(keys %$old_conf) > 1) {
407 # destroy old container volumes
408 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, { lock => 'create' });
412 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
413 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
414 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
417 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
418 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
419 $lxc_setup->template_fixup($conf);
421 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
422 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
423 $lxc_setup->post_create_hook($password, $ssh_keys);
427 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
428 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
431 $conf->{hostname
} ||= "CT$vmid";
432 $conf->{memory
} ||= 512;
433 $conf->{swap
} //= 512;
434 foreach my $mp (keys %$delayed_mp_param) {
435 $conf->{$mp} = $delayed_mp_param->{$mp};
437 # If the template flag was set, we try to convert again to template after restore
439 print STDERR
"Convert restored container to template...\n";
440 if (my $err = check_storage_supports_templates
($conf)) {
442 warn "Leave restored backup as container instead of converting to template.\n"
444 PVE
::LXC
::template_create
($vmid, $conf);
445 $conf->{template
} = 1;
448 PVE
::LXC
::Config-
>write_config($vmid, $conf);
451 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
452 eval { PVE
::LXC
::Config-
>destroy_config($vmid) };
456 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
458 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
459 if $start_after_create;
462 my $workername = $restore ?
'vzrestore' : 'vzcreate';
463 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
465 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
468 sub check_storage_supports_templates
{
471 my $scfg = PVE
::Storage
::config
();
473 PVE
::LXC
::Config-
>foreach_mountpoint($conf, sub {
476 my ($sid) = PVE
::Storage
::parse_volume_id
($mp->{volume
}, 0);
477 die "Warning: Directory storage '$sid' does not support container templates!\n"
478 if $scfg->{ids
}->{$sid}->{path
};
484 __PACKAGE__-
>register_method({
489 description
=> "Directory index",
494 additionalProperties
=> 0,
496 node
=> get_standard_option
('pve-node'),
497 vmid
=> get_standard_option
('pve-vmid'),
505 subdir
=> { type
=> 'string' },
508 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
514 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
517 { subdir
=> 'config' },
518 { subdir
=> 'pending' },
519 { subdir
=> 'status' },
520 { subdir
=> 'vncproxy' },
521 { subdir
=> 'termproxy' },
522 { subdir
=> 'vncwebsocket' },
523 { subdir
=> 'spiceproxy' },
524 { subdir
=> 'migrate' },
525 { subdir
=> 'clone' },
526 # { subdir => 'initlog' },
528 { subdir
=> 'rrddata' },
529 { subdir
=> 'firewall' },
530 { subdir
=> 'snapshot' },
531 { subdir
=> 'resize' },
538 __PACKAGE__-
>register_method({
540 path
=> '{vmid}/rrd',
542 protected
=> 1, # fixme: can we avoid that?
544 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
546 description
=> "Read VM RRD statistics (returns PNG)",
548 additionalProperties
=> 0,
550 node
=> get_standard_option
('pve-node'),
551 vmid
=> get_standard_option
('pve-vmid'),
553 description
=> "Specify the time frame you are interested in.",
555 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
558 description
=> "The list of datasources you want to display.",
559 type
=> 'string', format
=> 'pve-configid-list',
562 description
=> "The RRD consolidation function",
564 enum
=> [ 'AVERAGE', 'MAX' ],
572 filename
=> { type
=> 'string' },
578 return PVE
::Cluster
::create_rrd_graph
(
579 "pve2-vm/$param->{vmid}", $param->{timeframe
},
580 $param->{ds
}, $param->{cf
});
584 __PACKAGE__-
>register_method({
586 path
=> '{vmid}/rrddata',
588 protected
=> 1, # fixme: can we avoid that?
590 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
592 description
=> "Read VM RRD statistics",
594 additionalProperties
=> 0,
596 node
=> get_standard_option
('pve-node'),
597 vmid
=> get_standard_option
('pve-vmid'),
599 description
=> "Specify the time frame you are interested in.",
601 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
604 description
=> "The RRD consolidation function",
606 enum
=> [ 'AVERAGE', 'MAX' ],
621 return PVE
::Cluster
::create_rrd_data
(
622 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
625 __PACKAGE__-
>register_method({
626 name
=> 'destroy_vm',
631 description
=> "Destroy the container (also delete all uses files).",
633 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
636 additionalProperties
=> 0,
638 node
=> get_standard_option
('pve-node'),
639 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
648 my $rpcenv = PVE
::RPCEnvironment
::get
();
649 my $authuser = $rpcenv->get_user();
650 my $vmid = $param->{vmid
};
652 # test if container exists
653 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
654 my $storage_cfg = cfs_read_file
("storage.cfg");
655 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
657 die "unable to remove CT $vmid - used in HA resources\n"
658 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
660 # do not allow destroy if there are replication jobs
661 my $repl_conf = PVE
::ReplicationConfig-
>new();
662 $repl_conf->check_for_existing_jobs($vmid);
664 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
666 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
669 # reload config after lock
670 $conf = PVE
::LXC
::Config-
>load_config($vmid);
671 PVE
::LXC
::Config-
>check_lock($conf);
673 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
675 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf, { lock => 'destroyed' });
677 PVE
::AccessControl
::remove_vm_access
($vmid);
678 PVE
::Firewall
::remove_vmfw_conf
($vmid);
680 # only now remove the zombie config, else we can have reuse race
681 PVE
::LXC
::Config-
>destroy_config($vmid);
684 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
686 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
691 __PACKAGE__-
>register_method ({
693 path
=> '{vmid}/vncproxy',
697 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
699 description
=> "Creates a TCP VNC proxy connections.",
701 additionalProperties
=> 0,
703 node
=> get_standard_option
('pve-node'),
704 vmid
=> get_standard_option
('pve-vmid'),
708 description
=> "use websocket instead of standard VNC.",
712 description
=> "sets the width of the console in pixels.",
719 description
=> "sets the height of the console in pixels.",
727 additionalProperties
=> 0,
729 user
=> { type
=> 'string' },
730 ticket
=> { type
=> 'string' },
731 cert
=> { type
=> 'string' },
732 port
=> { type
=> 'integer' },
733 upid
=> { type
=> 'string' },
739 my $rpcenv = PVE
::RPCEnvironment
::get
();
741 my $authuser = $rpcenv->get_user();
743 my $vmid = $param->{vmid
};
744 my $node = $param->{node
};
746 my $authpath = "/vms/$vmid";
748 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
750 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
753 my ($remip, $family);
755 if ($node ne PVE
::INotify
::nodename
()) {
756 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
758 $family = PVE
::Tools
::get_host_address_family
($node);
761 my $port = PVE
::Tools
::next_vnc_port
($family);
763 # NOTE: vncterm VNC traffic is already TLS encrypted,
764 # so we select the fastest chipher here (or 'none'?)
765 my $remcmd = $remip ?
766 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
768 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
769 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
771 my $shcmd = [ '/usr/bin/dtach', '-A',
772 "/var/run/dtach/vzctlconsole$vmid",
773 '-r', 'winch', '-z', @$concmd];
778 syslog
('info', "starting lxc vnc proxy $upid\n");
782 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
783 '-timeout', $timeout, '-authpath', $authpath,
784 '-perm', 'VM.Console'];
786 if ($param->{width
}) {
787 push @$cmd, '-width', $param->{width
};
790 if ($param->{height
}) {
791 push @$cmd, '-height', $param->{height
};
794 if ($param->{websocket
}) {
795 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
796 push @$cmd, '-notls', '-listen', 'localhost';
799 push @$cmd, '-c', @$remcmd, @$shcmd;
801 run_command
($cmd, keeplocale
=> 1);
806 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
808 PVE
::Tools
::wait_for_vnc_port
($port);
819 __PACKAGE__-
>register_method ({
821 path
=> '{vmid}/termproxy',
825 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
827 description
=> "Creates a TCP proxy connection.",
829 additionalProperties
=> 0,
831 node
=> get_standard_option
('pve-node'),
832 vmid
=> get_standard_option
('pve-vmid'),
836 additionalProperties
=> 0,
838 user
=> { type
=> 'string' },
839 ticket
=> { type
=> 'string' },
840 port
=> { type
=> 'integer' },
841 upid
=> { type
=> 'string' },
847 my $rpcenv = PVE
::RPCEnvironment
::get
();
849 my $authuser = $rpcenv->get_user();
851 my $vmid = $param->{vmid
};
852 my $node = $param->{node
};
854 my $authpath = "/vms/$vmid";
856 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
858 my ($remip, $family);
860 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
861 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
863 $family = PVE
::Tools
::get_host_address_family
($node);
866 my $port = PVE
::Tools
::next_vnc_port
($family);
868 my $remcmd = $remip ?
869 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
871 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
872 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
874 my $shcmd = [ '/usr/bin/dtach', '-A',
875 "/var/run/dtach/vzctlconsole$vmid",
876 '-r', 'winch', '-z', @$concmd];
881 syslog
('info', "starting lxc termproxy $upid\n");
883 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
884 '--perm', 'VM.Console', '--'];
885 push @$cmd, @$remcmd, @$shcmd;
887 PVE
::Tools
::run_command
($cmd);
890 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
892 PVE
::Tools
::wait_for_vnc_port
($port);
902 __PACKAGE__-
>register_method({
903 name
=> 'vncwebsocket',
904 path
=> '{vmid}/vncwebsocket',
907 description
=> "You also need to pass a valid ticket (vncticket).",
908 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
910 description
=> "Opens a weksocket for VNC traffic.",
912 additionalProperties
=> 0,
914 node
=> get_standard_option
('pve-node'),
915 vmid
=> get_standard_option
('pve-vmid'),
917 description
=> "Ticket from previous call to vncproxy.",
922 description
=> "Port number returned by previous vncproxy call.",
932 port
=> { type
=> 'string' },
938 my $rpcenv = PVE
::RPCEnvironment
::get
();
940 my $authuser = $rpcenv->get_user();
942 my $authpath = "/vms/$param->{vmid}";
944 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
946 my $port = $param->{port
};
948 return { port
=> $port };
951 __PACKAGE__-
>register_method ({
952 name
=> 'spiceproxy',
953 path
=> '{vmid}/spiceproxy',
958 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
960 description
=> "Returns a SPICE configuration to connect to the CT.",
962 additionalProperties
=> 0,
964 node
=> get_standard_option
('pve-node'),
965 vmid
=> get_standard_option
('pve-vmid'),
966 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
969 returns
=> get_standard_option
('remote-viewer-config'),
973 my $vmid = $param->{vmid
};
974 my $node = $param->{node
};
975 my $proxy = $param->{proxy
};
977 my $authpath = "/vms/$vmid";
978 my $permissions = 'VM.Console';
980 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
982 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
984 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
986 my $shcmd = ['/usr/bin/dtach', '-A',
987 "/var/run/dtach/vzctlconsole$vmid",
988 '-r', 'winch', '-z', @$concmd];
990 my $title = "CT $vmid";
992 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
996 __PACKAGE__-
>register_method({
997 name
=> 'migrate_vm',
998 path
=> '{vmid}/migrate',
1002 description
=> "Migrate the container to another node. Creates a new migration task.",
1004 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1007 additionalProperties
=> 0,
1009 node
=> get_standard_option
('pve-node'),
1010 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1011 target
=> get_standard_option
('pve-node', {
1012 description
=> "Target node.",
1013 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1017 description
=> "Use online/live migration.",
1022 description
=> "Use restart migration",
1027 description
=> "Timeout in seconds for shutdown for restart migration",
1033 description
=> "Force migration despite local bind / device" .
1034 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1038 description
=> "Override I/O bandwidth limit (in KiB/s).",
1042 default => 'migrate limit from datacenter or storage config',
1048 description
=> "the task ID.",
1053 my $rpcenv = PVE
::RPCEnvironment
::get
();
1055 my $authuser = $rpcenv->get_user();
1057 my $target = extract_param
($param, 'target');
1059 my $localnode = PVE
::INotify
::nodename
();
1060 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1062 PVE
::Cluster
::check_cfs_quorum
();
1064 PVE
::Cluster
::check_node_exists
($target);
1066 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1068 my $vmid = extract_param
($param, 'vmid');
1071 PVE
::LXC
::Config-
>load_config($vmid);
1073 # try to detect errors early
1074 if (PVE
::LXC
::check_running
($vmid)) {
1075 die "can't migrate running container without --online or --restart\n"
1076 if !$param->{online
} && !$param->{restart
};
1079 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1084 my $service = "ct:$vmid";
1086 my $cmd = ['ha-manager', 'migrate', $service, $target];
1088 print "Requesting HA migration for CT $vmid to node $target\n";
1090 PVE
::Tools
::run_command
($cmd);
1095 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1100 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1104 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1107 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1111 __PACKAGE__-
>register_method({
1112 name
=> 'vm_feature',
1113 path
=> '{vmid}/feature',
1117 description
=> "Check if feature for virtual machine is available.",
1119 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1122 additionalProperties
=> 0,
1124 node
=> get_standard_option
('pve-node'),
1125 vmid
=> get_standard_option
('pve-vmid'),
1127 description
=> "Feature to check.",
1129 enum
=> [ 'snapshot', 'clone', 'copy' ],
1131 snapname
=> get_standard_option
('pve-snapshot-name', {
1139 hasFeature
=> { type
=> 'boolean' },
1142 #items => { type => 'string' },
1149 my $node = extract_param
($param, 'node');
1151 my $vmid = extract_param
($param, 'vmid');
1153 my $snapname = extract_param
($param, 'snapname');
1155 my $feature = extract_param
($param, 'feature');
1157 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1160 my $snap = $conf->{snapshots
}->{$snapname};
1161 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1164 my $storage_cfg = PVE
::Storage
::config
();
1165 #Maybe include later
1166 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1167 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1170 hasFeature
=> $hasFeature,
1171 #nodes => [ keys %$nodelist ],
1175 __PACKAGE__-
>register_method({
1177 path
=> '{vmid}/template',
1181 description
=> "Create a Template.",
1183 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1184 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1187 additionalProperties
=> 0,
1189 node
=> get_standard_option
('pve-node'),
1190 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1193 returns
=> { type
=> 'null'},
1197 my $rpcenv = PVE
::RPCEnvironment
::get
();
1199 my $authuser = $rpcenv->get_user();
1201 my $node = extract_param
($param, 'node');
1203 my $vmid = extract_param
($param, 'vmid');
1205 my $updatefn = sub {
1207 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1208 PVE
::LXC
::Config-
>check_lock($conf);
1210 die "unable to create template, because CT contains snapshots\n"
1211 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1213 die "you can't convert a template to a template\n"
1214 if PVE
::LXC
::Config-
>is_template($conf);
1216 die "you can't convert a CT to template if the CT is running\n"
1217 if PVE
::LXC
::check_running
($vmid);
1219 if (my $err = check_storage_supports_templates
($conf)) {
1224 PVE
::LXC
::template_create
($vmid, $conf);
1226 $conf->{template
} = 1;
1228 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1229 # and remove lxc config
1230 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1233 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1236 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1241 __PACKAGE__-
>register_method({
1243 path
=> '{vmid}/clone',
1247 description
=> "Create a container clone/copy",
1249 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1250 "and 'VM.Allocate' permissions " .
1251 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1252 "'Datastore.AllocateSpace' on any used storage.",
1255 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1257 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1258 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1263 additionalProperties
=> 0,
1265 node
=> get_standard_option
('pve-node'),
1266 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1267 newid
=> get_standard_option
('pve-vmid', {
1268 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1269 description
=> 'VMID for the clone.' }),
1272 type
=> 'string', format
=> 'dns-name',
1273 description
=> "Set a hostname for the new CT.",
1278 description
=> "Description for the new CT.",
1282 type
=> 'string', format
=> 'pve-poolid',
1283 description
=> "Add the new CT to the specified pool.",
1285 snapname
=> get_standard_option
('pve-snapshot-name', {
1288 storage
=> get_standard_option
('pve-storage-id', {
1289 description
=> "Target storage for full clone.",
1295 description
=> "Create a full copy of all disks. This is always done when " .
1296 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1298 target
=> get_standard_option
('pve-node', {
1299 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1303 description
=> "Override I/O bandwidth limit (in KiB/s).",
1307 default => 'clone limit from datacenter or storage config',
1317 my $rpcenv = PVE
::RPCEnvironment
::get
();
1319 my $authuser = $rpcenv->get_user();
1321 my $node = extract_param
($param, 'node');
1323 my $vmid = extract_param
($param, 'vmid');
1325 my $newid = extract_param
($param, 'newid');
1327 my $pool = extract_param
($param, 'pool');
1329 if (defined($pool)) {
1330 $rpcenv->check_pool_exist($pool);
1333 my $snapname = extract_param
($param, 'snapname');
1335 my $storage = extract_param
($param, 'storage');
1337 my $target = extract_param
($param, 'target');
1339 my $localnode = PVE
::INotify
::nodename
();
1341 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1343 PVE
::Cluster
::check_node_exists
($target) if $target;
1345 my $storecfg = PVE
::Storage
::config
();
1348 # check if storage is enabled on local node
1349 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1351 # check if storage is available on target node
1352 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1353 # clone only works if target storage is shared
1354 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1355 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1359 PVE
::Cluster
::check_cfs_quorum
();
1363 my $mountpoints = {};
1368 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1369 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1371 $running = PVE
::LXC
::check_running
($vmid) || 0;
1373 my $full = extract_param
($param, 'full');
1374 if (!defined($full)) {
1375 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1377 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1380 die "snapshot '$snapname' does not exist\n"
1381 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1384 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1386 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1387 die "unable to create CT $newid: config file already exists\n"
1391 foreach my $opt (keys %$src_conf) {
1392 next if $opt =~ m/^unused\d+$/;
1394 my $value = $src_conf->{$opt};
1396 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1397 my $mp = $opt eq 'rootfs' ?
1398 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1399 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1401 if ($mp->{type
} eq 'volume') {
1402 my $volid = $mp->{volume
};
1404 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1405 $sid = $storage if defined($storage);
1406 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1407 if (!$scfg->{shared
}) {
1409 warn "found non-shared volume: $volid\n" if $target;
1412 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1415 die "Cannot do full clones on a running container without snapshots\n"
1416 if $running && !defined($snapname);
1417 $fullclone->{$opt} = 1;
1419 # not full means clone instead of copy
1420 die "Linked clone feature for '$volid' is not available\n"
1421 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1424 $mountpoints->{$opt} = $mp;
1425 push @$vollist, $volid;
1428 # TODO: allow bind mounts?
1429 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1431 } elsif ($opt =~ m/^net(\d+)$/) {
1432 # always change MAC! address
1433 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1434 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1435 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1436 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1438 # copy everything else
1439 $newconf->{$opt} = $value;
1442 die "can't clone CT to node '$target' (CT uses local storage)\n"
1443 if $target && !$sharedvm;
1445 # Replace the 'disk' lock with a 'create' lock.
1446 $newconf->{lock} = 'create';
1448 delete $newconf->{pending
};
1449 delete $newconf->{template
};
1450 if ($param->{hostname
}) {
1451 $newconf->{hostname
} = $param->{hostname
};
1454 if ($param->{description
}) {
1455 $newconf->{description
} = $param->{description
};
1458 # create empty/temp config - this fails if CT already exists on other node
1459 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1462 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1468 my $update_conf = sub {
1469 my ($key, $value) = @_;
1470 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1471 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1472 die "Lost 'create' config lock, aborting.\n"
1473 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1474 $conf->{$key} = $value;
1475 PVE
::LXC
::Config-
>write_config($newid, $conf);
1482 my $newvollist = [];
1484 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1485 die "unexpected state change\n" if $verify_running != $running;
1491 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1493 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1494 my $bwlimit = extract_param
($param, 'bwlimit');
1496 foreach my $opt (keys %$mountpoints) {
1497 my $mp = $mountpoints->{$opt};
1498 my $volid = $mp->{volume
};
1501 if ($fullclone->{$opt}) {
1502 print "create full clone of mountpoint $opt ($volid)\n";
1503 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1504 my $target_storage = $storage // $source_storage;
1505 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1506 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1508 print "create linked clone of mount point $opt ($volid)\n";
1509 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1512 push @$newvollist, $newvolid;
1513 $mp->{volume
} = $newvolid;
1515 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1518 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1519 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1522 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1523 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1524 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1526 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1527 die "Failed to move config to node '$target' - rename failed: $!\n"
1528 if !rename($conffile, $newconffile);
1533 # Unlock the source config in any case:
1534 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1538 # Now cleanup the config & disks:
1541 sleep 1; # some storages like rbd need to wait before release volume - really?
1543 foreach my $volid (@$newvollist) {
1544 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1547 die "clone failed: $err";
1553 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1554 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1558 __PACKAGE__-
>register_method({
1559 name
=> 'resize_vm',
1560 path
=> '{vmid}/resize',
1564 description
=> "Resize a container mount point.",
1566 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1569 additionalProperties
=> 0,
1571 node
=> get_standard_option
('pve-node'),
1572 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1575 description
=> "The disk you want to resize.",
1576 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1580 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1581 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.",
1585 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1593 description
=> "the task ID.",
1598 my $rpcenv = PVE
::RPCEnvironment
::get
();
1600 my $authuser = $rpcenv->get_user();
1602 my $node = extract_param
($param, 'node');
1604 my $vmid = extract_param
($param, 'vmid');
1606 my $digest = extract_param
($param, 'digest');
1608 my $sizestr = extract_param
($param, 'size');
1609 my $ext = ($sizestr =~ s/^\+//);
1610 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1611 die "invalid size string" if !defined($newsize);
1613 die "no options specified\n" if !scalar(keys %$param);
1615 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1617 my $storage_cfg = cfs_read_file
("storage.cfg");
1621 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1622 PVE
::LXC
::Config-
>check_lock($conf);
1624 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1626 my $running = PVE
::LXC
::check_running
($vmid);
1628 my $disk = $param->{disk
};
1629 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1630 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1632 my $volid = $mp->{volume
};
1634 my (undef, undef, $owner, undef, undef, undef, $format) =
1635 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1637 die "can't resize mount point owned by another container ($owner)"
1640 die "can't resize volume: $disk if snapshot exists\n"
1641 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1643 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1645 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1647 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1649 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1650 $newsize += $size if $ext;
1651 $newsize = int($newsize);
1653 die "unable to shrink disk size\n" if $newsize < $size;
1655 return if $size == $newsize;
1657 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1659 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1660 # we pass 0 here (parameter only makes sense for qemu)
1661 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1663 $mp->{size
} = $newsize;
1664 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1666 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1668 if ($format eq 'raw') {
1669 # we need to ensure that the volume is mapped, if not needed this is a NOP
1670 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1671 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1675 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1676 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1677 die "internal error: CT running but mount point not attached to a loop device"
1679 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1681 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1682 # to be visible to it in its namespace.
1683 # To not interfere with the rest of the system we unshare the current mount namespace,
1684 # mount over /tmp and then run resize2fs.
1686 # interestingly we don't need to e2fsck on mounted systems...
1687 my $quoted = PVE
::Tools
::shellquote
($path);
1688 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1690 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1692 warn "Failed to update the container's filesystem: $@\n" if $@;
1695 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1696 PVE
::Tools
::run_command
(['resize2fs', $path]);
1698 warn "Failed to update the container's filesystem: $@\n" if $@;
1700 # always un-map if not running, this is a NOP if not needed
1701 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1706 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1709 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1712 __PACKAGE__-
>register_method({
1713 name
=> 'move_volume',
1714 path
=> '{vmid}/move_volume',
1718 description
=> "Move a rootfs-/mp-volume to a different storage",
1720 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1721 "and 'Datastore.AllocateSpace' permissions on the storage.",
1724 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1725 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1729 additionalProperties
=> 0,
1731 node
=> get_standard_option
('pve-node'),
1732 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1735 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1736 description
=> "Volume which will be moved.",
1738 storage
=> get_standard_option
('pve-storage-id', {
1739 description
=> "Target Storage.",
1740 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1744 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1750 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1755 description
=> "Override I/O bandwidth limit (in KiB/s).",
1759 default => 'clone limit from datacenter or storage config',
1769 my $rpcenv = PVE
::RPCEnvironment
::get
();
1771 my $authuser = $rpcenv->get_user();
1773 my $vmid = extract_param
($param, 'vmid');
1775 my $storage = extract_param
($param, 'storage');
1777 my $mpkey = extract_param
($param, 'volume');
1779 my $lockname = 'disk';
1781 my ($mpdata, $old_volid);
1783 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1784 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1785 PVE
::LXC
::Config-
>check_lock($conf);
1787 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1789 if ($mpkey eq 'rootfs') {
1790 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1791 } elsif ($mpkey =~ m/mp\d+/) {
1792 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1794 die "Can't parse $mpkey\n";
1796 $old_volid = $mpdata->{volume
};
1798 die "you can't move a volume with snapshots and delete the source\n"
1799 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1801 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1803 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1808 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1810 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1811 my $storage_cfg = PVE
::Storage
::config
();
1816 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1817 my $bwlimit = extract_param
($param, 'bwlimit');
1818 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1819 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$source_storage, $storage], $bwlimit);
1820 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf, undef, $movelimit);
1821 $mpdata->{volume
} = $new_volid;
1823 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1824 my $digest = $conf->{digest
};
1825 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1826 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1828 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1830 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1832 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1836 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1837 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1843 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1844 if defined($new_volid);
1850 if ($param->{delete}) {
1852 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1853 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1859 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1864 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1867 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1874 __PACKAGE__-
>register_method({
1875 name
=> 'vm_pending',
1876 path
=> '{vmid}/pending',
1879 description
=> 'Get container configuration, including pending changes.',
1881 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1884 additionalProperties
=> 0,
1886 node
=> get_standard_option
('pve-node'),
1887 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1896 description
=> 'Configuration option name.',
1900 description
=> 'Current value.',
1905 description
=> 'Pending value.',
1910 description
=> "Indicates a pending delete request if present and not 0.",
1922 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
1924 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
1926 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);