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);
12 use PVE
::DataCenterConfig
;
13 use PVE
::AccessControl
;
17 use PVE
::RPCEnvironment
;
18 use PVE
::ReplicationConfig
;
21 use PVE
::LXC
::Migrate
;
22 use PVE
::GuestHelpers
;
23 use PVE
::VZDump
::Plugin
;
24 use PVE
::API2
::LXC
::Config
;
25 use PVE
::API2
::LXC
::Status
;
26 use PVE
::API2
::LXC
::Snapshot
;
27 use PVE
::JSONSchema
qw(get_standard_option);
28 use base
qw(PVE::RESTHandler);
31 if (!$ENV{PVE_GENERATING_DOCS
}) {
32 require PVE
::HA
::Env
::PVE2
;
33 import PVE
::HA
::Env
::PVE2
;
34 require PVE
::HA
::Config
;
35 import PVE
::HA
::Config
;
39 __PACKAGE__-
>register_method ({
40 subclass
=> "PVE::API2::LXC::Config",
41 path
=> '{vmid}/config',
44 __PACKAGE__-
>register_method ({
45 subclass
=> "PVE::API2::LXC::Status",
46 path
=> '{vmid}/status',
49 __PACKAGE__-
>register_method ({
50 subclass
=> "PVE::API2::LXC::Snapshot",
51 path
=> '{vmid}/snapshot',
54 __PACKAGE__-
>register_method ({
55 subclass
=> "PVE::API2::Firewall::CT",
56 path
=> '{vmid}/firewall',
59 __PACKAGE__-
>register_method({
63 description
=> "LXC container index (per node).",
65 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
69 protected
=> 1, # /proc files are only readable by root
71 additionalProperties
=> 0,
73 node
=> get_standard_option
('pve-node'),
80 properties
=> $PVE::LXC
::vmstatus_return_properties
,
82 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
87 my $rpcenv = PVE
::RPCEnvironment
::get
();
88 my $authuser = $rpcenv->get_user();
90 my $vmstatus = PVE
::LXC
::vmstatus
();
93 foreach my $vmid (keys %$vmstatus) {
94 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
96 my $data = $vmstatus->{$vmid};
104 __PACKAGE__-
>register_method({
108 description
=> "Create or restore a container.",
110 user
=> 'all', # check inside
111 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
112 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
113 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
118 additionalProperties
=> 0,
119 properties
=> PVE
::LXC
::Config-
>json_config_properties({
120 node
=> get_standard_option
('pve-node'),
121 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
123 description
=> "The OS template or backup file.",
126 completion
=> \
&PVE
::LXC
::complete_os_templates
,
131 description
=> "Sets root password inside container.",
134 storage
=> get_standard_option
('pve-storage-id', {
135 description
=> "Default Storage.",
138 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
143 description
=> "Allow to overwrite existing container.",
148 description
=> "Mark this as restore task.",
153 description
=> "Assign a unique random ethernet address.",
154 requires
=> 'restore',
158 type
=> 'string', format
=> 'pve-poolid',
159 description
=> "Add the VM to the specified pool.",
161 'ignore-unpack-errors' => {
164 description
=> "Ignore errors when extracting the template.",
166 'ssh-public-keys' => {
169 description
=> "Setup public SSH keys (one key per line, " .
173 description
=> "Override I/O bandwidth limit (in KiB/s).",
177 default => 'restore limit from datacenter or storage config',
183 description
=> "Start the CT after its creation finished successfully.",
193 PVE
::Cluster
::check_cfs_quorum
();
195 my $rpcenv = PVE
::RPCEnvironment
::get
();
196 my $authuser = $rpcenv->get_user();
198 my $node = extract_param
($param, 'node');
199 my $vmid = extract_param
($param, 'vmid');
200 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
201 my $bwlimit = extract_param
($param, 'bwlimit');
202 my $start_after_create = extract_param
($param, 'start');
204 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
205 my $same_container_exists = -f
$basecfg_fn;
207 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
208 my $unprivileged = extract_param
($param, 'unprivileged');
209 my $restore = extract_param
($param, 'restore');
210 my $unique = extract_param
($param, 'unique');
212 # used to skip firewall config restore if user lacks permission
213 my $skip_fw_config_restore = 0;
216 # fixme: limit allowed parameters
219 my $force = extract_param
($param, 'force');
221 if (!($same_container_exists && $restore && $force)) {
222 PVE
::Cluster
::check_vmid_unused
($vmid);
224 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
225 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
226 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
229 my $password = extract_param
($param, 'password');
230 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
231 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
233 my $pool = extract_param
($param, 'pool');
234 if (defined($pool)) {
235 $rpcenv->check_pool_exist($pool);
236 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
239 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
241 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
243 } elsif ($restore && $force && $same_container_exists &&
244 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
245 # OK: user has VM.Backup permissions, and want to restore an existing VM
247 # we don't want to restore a container-provided FW conf in this case
248 # since the user is lacking permission to configure the container's FW
249 $skip_fw_config_restore = 1;
254 my $ostemplate = extract_param
($param, 'ostemplate');
255 my $storage = extract_param
($param, 'storage') // 'local';
257 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
259 my $storage_cfg = cfs_read_file
("storage.cfg");
262 if ($ostemplate eq '-') {
263 die "pipe requires cli environment\n"
264 if $rpcenv->{type
} ne 'cli';
265 die "pipe can only be used with restore tasks\n"
268 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
270 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
271 $archive = $ostemplate;
275 my $check_and_activate_storage = sub {
278 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
280 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
281 if !$scfg->{content
}->{rootdir
};
283 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
285 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
286 $used_storages{$sid} = 1;
291 my $is_root = $authuser eq 'root@pam';
293 my $no_disk_param = {};
295 my $storage_only_mode = 1;
296 foreach my $opt (keys %$param) {
297 my $value = $param->{$opt};
298 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
299 # allow to use simple numbers (add default storage in that case)
300 if ($value =~ m/^\d+(\.\d+)?$/) {
301 $mp_param->{$opt} = "$storage:$value";
303 $mp_param->{$opt} = $value;
305 $storage_only_mode = 0;
306 } elsif ($opt =~ m/^unused\d+$/) {
307 warn "ignoring '$opt', cannot create/restore with unused volume\n";
308 delete $param->{$opt};
310 $no_disk_param->{$opt} = $value;
314 die "mount points configured, but 'rootfs' not set - aborting\n"
315 if !$storage_only_mode && !defined($mp_param->{rootfs
});
317 # check storage access, activate storage
318 my $delayed_mp_param = {};
319 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
320 my ($ms, $mountpoint) = @_;
322 my $volid = $mountpoint->{volume
};
323 my $mp = $mountpoint->{mp
};
325 if ($mountpoint->{type
} ne 'volume') { # bind or device
326 die "Only root can pass arbitrary filesystem paths.\n"
329 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
330 &$check_and_activate_storage($sid);
334 # check/activate default storage
335 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
337 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
339 $conf->{unprivileged
} = 1 if $unprivileged;
341 my $emsg = $restore ?
"unable to restore CT $vmid -" : "unable to create CT $vmid -";
343 eval { PVE
::LXC
::Config-
>create_and_lock_config($vmid, $force) };
344 die "$emsg $@" if $@;
347 my $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
352 my $orig_mp_param; # only used if $restore
354 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
355 if ($is_root && $archive ne '-') {
357 ($orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive);
358 $was_template = delete $orig_conf->{template
};
359 # When we're root call 'restore_configuration' with restricted=0,
360 # causing it to restore the raw lxc entries, among which there may be
361 # 'lxc.idmap' entries. We need to make sure that the extracted contents
362 # of the container match up with the restored configuration afterwards:
363 $conf->{lxc
} = $orig_conf->{lxc
};
366 if ($storage_only_mode) {
368 if (!defined($orig_mp_param)) {
369 (undef, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive);
371 $mp_param = $orig_mp_param;
372 die "rootfs configuration could not be recovered, please check and specify manually!\n"
373 if !defined($mp_param->{rootfs
});
374 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
375 my ($ms, $mountpoint) = @_;
376 my $type = $mountpoint->{type
};
377 if ($type eq 'volume') {
378 die "unable to detect disk size - please specify $ms (size)\n"
379 if !defined($mountpoint->{size
});
380 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
381 delete $mountpoint->{size
};
382 $mountpoint->{volume
} = "$storage:$disksize";
383 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
385 my $type = $mountpoint->{type
};
386 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
387 if ($ms eq 'rootfs');
388 die "restoring '$ms' to $type mount is only possible for root\n"
391 if ($mountpoint->{backup
}) {
392 warn "WARNING - unsupported configuration!\n";
393 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
394 warn "mount point configuration will be restored after archive extraction!\n";
395 warn "contained files will be restored to wrong directory!\n";
397 delete $mp_param->{$ms}; # actually delay bind/dev mps
398 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
402 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
406 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
408 # we always have the 'create' lock so check for more than 1 entry
409 if (scalar(keys %$old_conf) > 1) {
410 # destroy old container volumes
411 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, { lock => 'create' });
415 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
416 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
417 PVE
::LXC
::Create
::restore_archive
($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
420 PVE
::LXC
::Create
::restore_configuration
($vmid, $storage_cfg, $archive, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
421 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
422 $lxc_setup->template_fixup($conf);
424 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
425 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
426 $lxc_setup->post_create_hook($password, $ssh_keys);
430 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
431 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
434 $conf->{hostname
} ||= "CT$vmid";
435 $conf->{memory
} ||= 512;
436 $conf->{swap
} //= 512;
437 foreach my $mp (keys %$delayed_mp_param) {
438 $conf->{$mp} = $delayed_mp_param->{$mp};
440 # If the template flag was set, we try to convert again to template after restore
442 print STDERR
"Convert restored container to template...\n";
443 if (my $err = check_storage_supports_templates
($conf)) {
445 warn "Leave restored backup as container instead of converting to template.\n"
447 PVE
::LXC
::template_create
($vmid, $conf);
448 $conf->{template
} = 1;
451 PVE
::LXC
::Config-
>write_config($vmid, $conf);
454 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
455 eval { PVE
::LXC
::Config-
>destroy_config($vmid) };
459 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
461 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
462 if $start_after_create;
465 my $workername = $restore ?
'vzrestore' : 'vzcreate';
466 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
468 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
471 sub check_storage_supports_templates
{
474 my $scfg = PVE
::Storage
::config
();
476 PVE
::LXC
::Config-
>foreach_mountpoint($conf, sub {
479 my ($sid) = PVE
::Storage
::parse_volume_id
($mp->{volume
}, 0);
480 die "Warning: Directory storage '$sid' does not support container templates!\n"
481 if $scfg->{ids
}->{$sid}->{path
};
487 __PACKAGE__-
>register_method({
492 description
=> "Directory index",
497 additionalProperties
=> 0,
499 node
=> get_standard_option
('pve-node'),
500 vmid
=> get_standard_option
('pve-vmid'),
508 subdir
=> { type
=> 'string' },
511 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
517 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
520 { subdir
=> 'config' },
521 { subdir
=> 'pending' },
522 { subdir
=> 'status' },
523 { subdir
=> 'vncproxy' },
524 { subdir
=> 'termproxy' },
525 { subdir
=> 'vncwebsocket' },
526 { subdir
=> 'spiceproxy' },
527 { subdir
=> 'migrate' },
528 { subdir
=> 'clone' },
529 # { subdir => 'initlog' },
531 { subdir
=> 'rrddata' },
532 { subdir
=> 'firewall' },
533 { subdir
=> 'snapshot' },
534 { subdir
=> 'resize' },
541 __PACKAGE__-
>register_method({
543 path
=> '{vmid}/rrd',
545 protected
=> 1, # fixme: can we avoid that?
547 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
549 description
=> "Read VM RRD statistics (returns PNG)",
551 additionalProperties
=> 0,
553 node
=> get_standard_option
('pve-node'),
554 vmid
=> get_standard_option
('pve-vmid'),
556 description
=> "Specify the time frame you are interested in.",
558 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
561 description
=> "The list of datasources you want to display.",
562 type
=> 'string', format
=> 'pve-configid-list',
565 description
=> "The RRD consolidation function",
567 enum
=> [ 'AVERAGE', 'MAX' ],
575 filename
=> { type
=> 'string' },
581 return PVE
::RRD
::create_rrd_graph
(
582 "pve2-vm/$param->{vmid}", $param->{timeframe
},
583 $param->{ds
}, $param->{cf
});
587 __PACKAGE__-
>register_method({
589 path
=> '{vmid}/rrddata',
591 protected
=> 1, # fixme: can we avoid that?
593 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
595 description
=> "Read VM RRD statistics",
597 additionalProperties
=> 0,
599 node
=> get_standard_option
('pve-node'),
600 vmid
=> get_standard_option
('pve-vmid'),
602 description
=> "Specify the time frame you are interested in.",
604 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
607 description
=> "The RRD consolidation function",
609 enum
=> [ 'AVERAGE', 'MAX' ],
624 return PVE
::RRD
::create_rrd_data
(
625 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
628 __PACKAGE__-
>register_method({
629 name
=> 'destroy_vm',
634 description
=> "Destroy the container (also delete all uses files).",
636 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
639 additionalProperties
=> 0,
641 node
=> get_standard_option
('pve-node'),
642 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
645 description
=> "Remove vmid from backup cron jobs.",
656 my $rpcenv = PVE
::RPCEnvironment
::get
();
657 my $authuser = $rpcenv->get_user();
658 my $vmid = $param->{vmid
};
660 # test if container exists
661 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
662 my $storage_cfg = cfs_read_file
("storage.cfg");
663 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
665 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("ct:$vmid");
667 if (!$param->{purge
}) {
668 die "unable to remove CT $vmid - used in HA resources and purge parameter not set.\n"
671 # do not allow destroy if there are replication jobs without purge
672 my $repl_conf = PVE
::ReplicationConfig-
>new();
673 $repl_conf->check_for_existing_jobs($vmid);
676 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
678 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
681 # reload config after lock
682 $conf = PVE
::LXC
::Config-
>load_config($vmid);
683 PVE
::LXC
::Config-
>check_lock($conf);
685 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
687 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf, { lock => 'destroyed' });
689 PVE
::AccessControl
::remove_vm_access
($vmid);
690 PVE
::Firewall
::remove_vmfw_conf
($vmid);
691 if ($param->{purge
}) {
692 print "purging CT $vmid from related configurations..\n";
693 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
694 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
697 PVE
::HA
::Config
::delete_service_from_config
("ct:$vmid");
698 print "NOTE: removed CT $vmid from HA resource configuration.\n";
702 # only now remove the zombie config, else we can have reuse race
703 PVE
::LXC
::Config-
>destroy_config($vmid);
706 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
708 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
713 __PACKAGE__-
>register_method ({
715 path
=> '{vmid}/vncproxy',
719 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
721 description
=> "Creates a TCP VNC proxy connections.",
723 additionalProperties
=> 0,
725 node
=> get_standard_option
('pve-node'),
726 vmid
=> get_standard_option
('pve-vmid'),
730 description
=> "use websocket instead of standard VNC.",
734 description
=> "sets the width of the console in pixels.",
741 description
=> "sets the height of the console in pixels.",
749 additionalProperties
=> 0,
751 user
=> { type
=> 'string' },
752 ticket
=> { type
=> 'string' },
753 cert
=> { type
=> 'string' },
754 port
=> { type
=> 'integer' },
755 upid
=> { type
=> 'string' },
761 my $rpcenv = PVE
::RPCEnvironment
::get
();
763 my $authuser = $rpcenv->get_user();
765 my $vmid = $param->{vmid
};
766 my $node = $param->{node
};
768 my $authpath = "/vms/$vmid";
770 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
772 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
775 my ($remip, $family);
777 if ($node ne PVE
::INotify
::nodename
()) {
778 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
780 $family = PVE
::Tools
::get_host_address_family
($node);
783 my $port = PVE
::Tools
::next_vnc_port
($family);
785 # NOTE: vncterm VNC traffic is already TLS encrypted,
786 # so we select the fastest chipher here (or 'none'?)
787 my $remcmd = $remip ?
788 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
790 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
791 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
793 my $shcmd = [ '/usr/bin/dtach', '-A',
794 "/var/run/dtach/vzctlconsole$vmid",
795 '-r', 'winch', '-z', @$concmd];
800 syslog
('info', "starting lxc vnc proxy $upid\n");
804 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
805 '-timeout', $timeout, '-authpath', $authpath,
806 '-perm', 'VM.Console'];
808 if ($param->{width
}) {
809 push @$cmd, '-width', $param->{width
};
812 if ($param->{height
}) {
813 push @$cmd, '-height', $param->{height
};
816 if ($param->{websocket
}) {
817 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
818 push @$cmd, '-notls', '-listen', 'localhost';
821 push @$cmd, '-c', @$remcmd, @$shcmd;
823 run_command
($cmd, keeplocale
=> 1);
828 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
830 PVE
::Tools
::wait_for_vnc_port
($port);
841 __PACKAGE__-
>register_method ({
843 path
=> '{vmid}/termproxy',
847 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
849 description
=> "Creates a TCP proxy connection.",
851 additionalProperties
=> 0,
853 node
=> get_standard_option
('pve-node'),
854 vmid
=> get_standard_option
('pve-vmid'),
858 additionalProperties
=> 0,
860 user
=> { type
=> 'string' },
861 ticket
=> { type
=> 'string' },
862 port
=> { type
=> 'integer' },
863 upid
=> { type
=> 'string' },
869 my $rpcenv = PVE
::RPCEnvironment
::get
();
871 my $authuser = $rpcenv->get_user();
873 my $vmid = $param->{vmid
};
874 my $node = $param->{node
};
876 my $authpath = "/vms/$vmid";
878 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
880 my ($remip, $family);
882 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
883 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
885 $family = PVE
::Tools
::get_host_address_family
($node);
888 my $port = PVE
::Tools
::next_vnc_port
($family);
890 my $remcmd = $remip ?
891 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
893 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
894 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
896 my $shcmd = [ '/usr/bin/dtach', '-A',
897 "/var/run/dtach/vzctlconsole$vmid",
898 '-r', 'winch', '-z', @$concmd];
903 syslog
('info', "starting lxc termproxy $upid\n");
905 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
906 '--perm', 'VM.Console', '--'];
907 push @$cmd, @$remcmd, @$shcmd;
909 PVE
::Tools
::run_command
($cmd);
912 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
914 PVE
::Tools
::wait_for_vnc_port
($port);
924 __PACKAGE__-
>register_method({
925 name
=> 'vncwebsocket',
926 path
=> '{vmid}/vncwebsocket',
929 description
=> "You also need to pass a valid ticket (vncticket).",
930 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
932 description
=> "Opens a weksocket for VNC traffic.",
934 additionalProperties
=> 0,
936 node
=> get_standard_option
('pve-node'),
937 vmid
=> get_standard_option
('pve-vmid'),
939 description
=> "Ticket from previous call to vncproxy.",
944 description
=> "Port number returned by previous vncproxy call.",
954 port
=> { type
=> 'string' },
960 my $rpcenv = PVE
::RPCEnvironment
::get
();
962 my $authuser = $rpcenv->get_user();
964 my $authpath = "/vms/$param->{vmid}";
966 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
968 my $port = $param->{port
};
970 return { port
=> $port };
973 __PACKAGE__-
>register_method ({
974 name
=> 'spiceproxy',
975 path
=> '{vmid}/spiceproxy',
980 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
982 description
=> "Returns a SPICE configuration to connect to the CT.",
984 additionalProperties
=> 0,
986 node
=> get_standard_option
('pve-node'),
987 vmid
=> get_standard_option
('pve-vmid'),
988 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
991 returns
=> get_standard_option
('remote-viewer-config'),
995 my $vmid = $param->{vmid
};
996 my $node = $param->{node
};
997 my $proxy = $param->{proxy
};
999 my $authpath = "/vms/$vmid";
1000 my $permissions = 'VM.Console';
1002 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1004 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
1006 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
1008 my $shcmd = ['/usr/bin/dtach', '-A',
1009 "/var/run/dtach/vzctlconsole$vmid",
1010 '-r', 'winch', '-z', @$concmd];
1012 my $title = "CT $vmid";
1014 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1018 __PACKAGE__-
>register_method({
1019 name
=> 'migrate_vm',
1020 path
=> '{vmid}/migrate',
1024 description
=> "Migrate the container to another node. Creates a new migration task.",
1026 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1029 additionalProperties
=> 0,
1031 node
=> get_standard_option
('pve-node'),
1032 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1033 target
=> get_standard_option
('pve-node', {
1034 description
=> "Target node.",
1035 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1039 description
=> "Use online/live migration.",
1044 description
=> "Use restart migration",
1049 description
=> "Timeout in seconds for shutdown for restart migration",
1055 description
=> "Force migration despite local bind / device" .
1056 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1060 description
=> "Override I/O bandwidth limit (in KiB/s).",
1064 default => 'migrate limit from datacenter or storage config',
1070 description
=> "the task ID.",
1075 my $rpcenv = PVE
::RPCEnvironment
::get
();
1077 my $authuser = $rpcenv->get_user();
1079 my $target = extract_param
($param, 'target');
1081 my $localnode = PVE
::INotify
::nodename
();
1082 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1084 PVE
::Cluster
::check_cfs_quorum
();
1086 PVE
::Cluster
::check_node_exists
($target);
1088 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1090 my $vmid = extract_param
($param, 'vmid');
1093 PVE
::LXC
::Config-
>load_config($vmid);
1095 # try to detect errors early
1096 if (PVE
::LXC
::check_running
($vmid)) {
1097 die "can't migrate running container without --online or --restart\n"
1098 if !$param->{online
} && !$param->{restart
};
1101 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1106 my $service = "ct:$vmid";
1108 my $cmd = ['ha-manager', 'migrate', $service, $target];
1110 print "Requesting HA migration for CT $vmid to node $target\n";
1112 PVE
::Tools
::run_command
($cmd);
1117 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1122 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1126 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1129 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1133 __PACKAGE__-
>register_method({
1134 name
=> 'vm_feature',
1135 path
=> '{vmid}/feature',
1139 description
=> "Check if feature for virtual machine is available.",
1141 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1144 additionalProperties
=> 0,
1146 node
=> get_standard_option
('pve-node'),
1147 vmid
=> get_standard_option
('pve-vmid'),
1149 description
=> "Feature to check.",
1151 enum
=> [ 'snapshot', 'clone', 'copy' ],
1153 snapname
=> get_standard_option
('pve-snapshot-name', {
1161 hasFeature
=> { type
=> 'boolean' },
1164 #items => { type => 'string' },
1171 my $node = extract_param
($param, 'node');
1173 my $vmid = extract_param
($param, 'vmid');
1175 my $snapname = extract_param
($param, 'snapname');
1177 my $feature = extract_param
($param, 'feature');
1179 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1182 my $snap = $conf->{snapshots
}->{$snapname};
1183 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1186 my $storage_cfg = PVE
::Storage
::config
();
1187 #Maybe include later
1188 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1189 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1192 hasFeature
=> $hasFeature,
1193 #nodes => [ keys %$nodelist ],
1197 __PACKAGE__-
>register_method({
1199 path
=> '{vmid}/template',
1203 description
=> "Create a Template.",
1205 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1206 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1209 additionalProperties
=> 0,
1211 node
=> get_standard_option
('pve-node'),
1212 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1215 returns
=> { type
=> 'null'},
1219 my $rpcenv = PVE
::RPCEnvironment
::get
();
1221 my $authuser = $rpcenv->get_user();
1223 my $node = extract_param
($param, 'node');
1225 my $vmid = extract_param
($param, 'vmid');
1227 my $updatefn = sub {
1229 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1230 PVE
::LXC
::Config-
>check_lock($conf);
1232 die "unable to create template, because CT contains snapshots\n"
1233 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1235 die "you can't convert a template to a template\n"
1236 if PVE
::LXC
::Config-
>is_template($conf);
1238 die "you can't convert a CT to template if the CT is running\n"
1239 if PVE
::LXC
::check_running
($vmid);
1241 if (my $err = check_storage_supports_templates
($conf)) {
1246 PVE
::LXC
::template_create
($vmid, $conf);
1248 $conf->{template
} = 1;
1250 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1251 # and remove lxc config
1252 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1255 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1258 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1263 __PACKAGE__-
>register_method({
1265 path
=> '{vmid}/clone',
1269 description
=> "Create a container clone/copy",
1271 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1272 "and 'VM.Allocate' permissions " .
1273 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1274 "'Datastore.AllocateSpace' on any used storage.",
1277 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1279 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1280 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1285 additionalProperties
=> 0,
1287 node
=> get_standard_option
('pve-node'),
1288 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1289 newid
=> get_standard_option
('pve-vmid', {
1290 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1291 description
=> 'VMID for the clone.' }),
1294 type
=> 'string', format
=> 'dns-name',
1295 description
=> "Set a hostname for the new CT.",
1300 description
=> "Description for the new CT.",
1304 type
=> 'string', format
=> 'pve-poolid',
1305 description
=> "Add the new CT to the specified pool.",
1307 snapname
=> get_standard_option
('pve-snapshot-name', {
1310 storage
=> get_standard_option
('pve-storage-id', {
1311 description
=> "Target storage for full clone.",
1317 description
=> "Create a full copy of all disks. This is always done when " .
1318 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1320 target
=> get_standard_option
('pve-node', {
1321 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1325 description
=> "Override I/O bandwidth limit (in KiB/s).",
1329 default => 'clone limit from datacenter or storage config',
1339 my $rpcenv = PVE
::RPCEnvironment
::get
();
1341 my $authuser = $rpcenv->get_user();
1343 my $node = extract_param
($param, 'node');
1345 my $vmid = extract_param
($param, 'vmid');
1347 my $newid = extract_param
($param, 'newid');
1349 my $pool = extract_param
($param, 'pool');
1351 if (defined($pool)) {
1352 $rpcenv->check_pool_exist($pool);
1355 my $snapname = extract_param
($param, 'snapname');
1357 my $storage = extract_param
($param, 'storage');
1359 my $target = extract_param
($param, 'target');
1361 my $localnode = PVE
::INotify
::nodename
();
1363 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1365 PVE
::Cluster
::check_node_exists
($target) if $target;
1367 my $storecfg = PVE
::Storage
::config
();
1370 # check if storage is enabled on local node
1371 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1373 # check if storage is available on target node
1374 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1375 # clone only works if target storage is shared
1376 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1377 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1381 PVE
::Cluster
::check_cfs_quorum
();
1385 my $mountpoints = {};
1390 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1391 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1393 $running = PVE
::LXC
::check_running
($vmid) || 0;
1395 my $full = extract_param
($param, 'full');
1396 if (!defined($full)) {
1397 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1399 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1402 die "snapshot '$snapname' does not exist\n"
1403 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1406 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1408 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1409 die "unable to create CT $newid: config file already exists\n"
1413 foreach my $opt (keys %$src_conf) {
1414 next if $opt =~ m/^unused\d+$/;
1416 my $value = $src_conf->{$opt};
1418 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1419 my $mp = $opt eq 'rootfs' ?
1420 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1421 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1423 if ($mp->{type
} eq 'volume') {
1424 my $volid = $mp->{volume
};
1426 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1427 $sid = $storage if defined($storage);
1428 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1429 if (!$scfg->{shared
}) {
1431 warn "found non-shared volume: $volid\n" if $target;
1434 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1437 die "Cannot do full clones on a running container without snapshots\n"
1438 if $running && !defined($snapname);
1439 $fullclone->{$opt} = 1;
1441 # not full means clone instead of copy
1442 die "Linked clone feature for '$volid' is not available\n"
1443 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
1446 $mountpoints->{$opt} = $mp;
1447 push @$vollist, $volid;
1450 # TODO: allow bind mounts?
1451 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1453 } elsif ($opt =~ m/^net(\d+)$/) {
1454 # always change MAC! address
1455 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1456 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1457 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1458 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1460 # copy everything else
1461 $newconf->{$opt} = $value;
1464 die "can't clone CT to node '$target' (CT uses local storage)\n"
1465 if $target && !$sharedvm;
1467 # Replace the 'disk' lock with a 'create' lock.
1468 $newconf->{lock} = 'create';
1470 delete $newconf->{pending
};
1471 delete $newconf->{template
};
1472 if ($param->{hostname
}) {
1473 $newconf->{hostname
} = $param->{hostname
};
1476 if ($param->{description
}) {
1477 $newconf->{description
} = $param->{description
};
1480 # create empty/temp config - this fails if CT already exists on other node
1481 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1484 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1490 my $update_conf = sub {
1491 my ($key, $value) = @_;
1492 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1493 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1494 die "Lost 'create' config lock, aborting.\n"
1495 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1496 $conf->{$key} = $value;
1497 PVE
::LXC
::Config-
>write_config($newid, $conf);
1504 my $newvollist = [];
1506 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1507 die "unexpected state change\n" if $verify_running != $running;
1513 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1515 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1516 my $bwlimit = extract_param
($param, 'bwlimit');
1518 foreach my $opt (keys %$mountpoints) {
1519 my $mp = $mountpoints->{$opt};
1520 my $volid = $mp->{volume
};
1523 if ($fullclone->{$opt}) {
1524 print "create full clone of mountpoint $opt ($volid)\n";
1525 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1526 my $target_storage = $storage // $source_storage;
1527 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1528 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1530 print "create linked clone of mount point $opt ($volid)\n";
1531 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1534 push @$newvollist, $newvolid;
1535 $mp->{volume
} = $newvolid;
1537 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1540 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1541 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1544 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1545 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1546 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1548 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1549 die "Failed to move config to node '$target' - rename failed: $!\n"
1550 if !rename($conffile, $newconffile);
1555 # Unlock the source config in any case:
1556 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1560 # Now cleanup the config & disks:
1563 sleep 1; # some storages like rbd need to wait before release volume - really?
1565 foreach my $volid (@$newvollist) {
1566 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1569 die "clone failed: $err";
1575 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1576 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1580 __PACKAGE__-
>register_method({
1581 name
=> 'resize_vm',
1582 path
=> '{vmid}/resize',
1586 description
=> "Resize a container mount point.",
1588 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1591 additionalProperties
=> 0,
1593 node
=> get_standard_option
('pve-node'),
1594 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1597 description
=> "The disk you want to resize.",
1598 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1602 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1603 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.",
1607 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1615 description
=> "the task ID.",
1620 my $rpcenv = PVE
::RPCEnvironment
::get
();
1622 my $authuser = $rpcenv->get_user();
1624 my $node = extract_param
($param, 'node');
1626 my $vmid = extract_param
($param, 'vmid');
1628 my $digest = extract_param
($param, 'digest');
1630 my $sizestr = extract_param
($param, 'size');
1631 my $ext = ($sizestr =~ s/^\+//);
1632 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1633 die "invalid size string" if !defined($newsize);
1635 die "no options specified\n" if !scalar(keys %$param);
1637 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1639 my $storage_cfg = cfs_read_file
("storage.cfg");
1643 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1644 PVE
::LXC
::Config-
>check_lock($conf);
1646 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1648 my $running = PVE
::LXC
::check_running
($vmid);
1650 my $disk = $param->{disk
};
1651 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1652 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1654 my $volid = $mp->{volume
};
1656 my (undef, undef, $owner, undef, undef, undef, $format) =
1657 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1659 die "can't resize mount point owned by another container ($owner)"
1662 die "can't resize volume: $disk if snapshot exists\n"
1663 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1665 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1667 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1669 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1671 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1673 die "Could not determine current size of volume '$volid'\n" if !defined($size);
1675 $newsize += $size if $ext;
1676 $newsize = int($newsize);
1678 die "unable to shrink disk size\n" if $newsize < $size;
1680 return if $size == $newsize;
1682 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1684 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1685 # we pass 0 here (parameter only makes sense for qemu)
1686 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1688 $mp->{size
} = $newsize;
1689 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1691 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1693 if ($format eq 'raw') {
1694 # we need to ensure that the volume is mapped, if not needed this is a NOP
1695 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1696 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1700 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1701 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1702 die "internal error: CT running but mount point not attached to a loop device"
1704 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1706 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1707 # to be visible to it in its namespace.
1708 # To not interfere with the rest of the system we unshare the current mount namespace,
1709 # mount over /tmp and then run resize2fs.
1711 # interestingly we don't need to e2fsck on mounted systems...
1712 my $quoted = PVE
::Tools
::shellquote
($path);
1713 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1715 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1717 warn "Failed to update the container's filesystem: $@\n" if $@;
1720 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1721 PVE
::Tools
::run_command
(['resize2fs', $path]);
1723 warn "Failed to update the container's filesystem: $@\n" if $@;
1725 # always un-map if not running, this is a NOP if not needed
1726 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1731 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1734 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1737 __PACKAGE__-
>register_method({
1738 name
=> 'move_volume',
1739 path
=> '{vmid}/move_volume',
1743 description
=> "Move a rootfs-/mp-volume to a different storage",
1745 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1746 "and 'Datastore.AllocateSpace' permissions on the storage.",
1749 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1750 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1754 additionalProperties
=> 0,
1756 node
=> get_standard_option
('pve-node'),
1757 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1760 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1761 description
=> "Volume which will be moved.",
1763 storage
=> get_standard_option
('pve-storage-id', {
1764 description
=> "Target Storage.",
1765 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1769 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1775 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1780 description
=> "Override I/O bandwidth limit (in KiB/s).",
1784 default => 'clone limit from datacenter or storage config',
1794 my $rpcenv = PVE
::RPCEnvironment
::get
();
1796 my $authuser = $rpcenv->get_user();
1798 my $vmid = extract_param
($param, 'vmid');
1800 my $storage = extract_param
($param, 'storage');
1802 my $mpkey = extract_param
($param, 'volume');
1804 my $lockname = 'disk';
1806 my ($mpdata, $old_volid);
1808 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1809 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1810 PVE
::LXC
::Config-
>check_lock($conf);
1812 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1814 if ($mpkey eq 'rootfs') {
1815 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1816 } elsif ($mpkey =~ m/mp\d+/) {
1817 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1819 die "Can't parse $mpkey\n";
1821 $old_volid = $mpdata->{volume
};
1823 die "you can't move a volume with snapshots and delete the source\n"
1824 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1826 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1828 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1833 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1835 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1836 my $storage_cfg = PVE
::Storage
::config
();
1841 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1842 my $bwlimit = extract_param
($param, 'bwlimit');
1843 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1844 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$source_storage, $storage], $bwlimit);
1845 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf, undef, $movelimit);
1846 $mpdata->{volume
} = $new_volid;
1848 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1849 my $digest = $conf->{digest
};
1850 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1851 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1853 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1855 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1857 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1861 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1862 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1868 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1869 if defined($new_volid);
1875 if ($param->{delete}) {
1877 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1878 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1884 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1889 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1892 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1899 __PACKAGE__-
>register_method({
1900 name
=> 'vm_pending',
1901 path
=> '{vmid}/pending',
1904 description
=> 'Get container configuration, including pending changes.',
1906 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1909 additionalProperties
=> 0,
1911 node
=> get_standard_option
('pve-node'),
1912 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1921 description
=> 'Configuration option name.',
1925 description
=> 'Current value.',
1930 description
=> 'Pending value.',
1935 description
=> "Indicates a pending delete request if present and not 0.",
1947 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
1949 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
1951 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);