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 = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $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
($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
($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
($archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
420 PVE
::LXC
::Create
::restore_configuration
($vmid, $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 die "unable to remove CT $vmid - used in HA resources\n"
666 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
668 if (!$param->{purge
}) {
669 # do not allow destroy if there are replication jobs without purge
670 my $repl_conf = PVE
::ReplicationConfig-
>new();
671 $repl_conf->check_for_existing_jobs($vmid);
674 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
676 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
679 # reload config after lock
680 $conf = PVE
::LXC
::Config-
>load_config($vmid);
681 PVE
::LXC
::Config-
>check_lock($conf);
683 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
685 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf, { lock => 'destroyed' });
687 PVE
::AccessControl
::remove_vm_access
($vmid);
688 PVE
::Firewall
::remove_vmfw_conf
($vmid);
689 if ($param->{purge
}) {
690 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
691 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
694 # only now remove the zombie config, else we can have reuse race
695 PVE
::LXC
::Config-
>destroy_config($vmid);
698 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
700 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
705 __PACKAGE__-
>register_method ({
707 path
=> '{vmid}/vncproxy',
711 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
713 description
=> "Creates a TCP VNC proxy connections.",
715 additionalProperties
=> 0,
717 node
=> get_standard_option
('pve-node'),
718 vmid
=> get_standard_option
('pve-vmid'),
722 description
=> "use websocket instead of standard VNC.",
726 description
=> "sets the width of the console in pixels.",
733 description
=> "sets the height of the console in pixels.",
741 additionalProperties
=> 0,
743 user
=> { type
=> 'string' },
744 ticket
=> { type
=> 'string' },
745 cert
=> { type
=> 'string' },
746 port
=> { type
=> 'integer' },
747 upid
=> { type
=> 'string' },
753 my $rpcenv = PVE
::RPCEnvironment
::get
();
755 my $authuser = $rpcenv->get_user();
757 my $vmid = $param->{vmid
};
758 my $node = $param->{node
};
760 my $authpath = "/vms/$vmid";
762 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
764 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
767 my ($remip, $family);
769 if ($node ne PVE
::INotify
::nodename
()) {
770 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
772 $family = PVE
::Tools
::get_host_address_family
($node);
775 my $port = PVE
::Tools
::next_vnc_port
($family);
777 # NOTE: vncterm VNC traffic is already TLS encrypted,
778 # so we select the fastest chipher here (or 'none'?)
779 my $remcmd = $remip ?
780 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
782 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
783 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
785 my $shcmd = [ '/usr/bin/dtach', '-A',
786 "/var/run/dtach/vzctlconsole$vmid",
787 '-r', 'winch', '-z', @$concmd];
792 syslog
('info', "starting lxc vnc proxy $upid\n");
796 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
797 '-timeout', $timeout, '-authpath', $authpath,
798 '-perm', 'VM.Console'];
800 if ($param->{width
}) {
801 push @$cmd, '-width', $param->{width
};
804 if ($param->{height
}) {
805 push @$cmd, '-height', $param->{height
};
808 if ($param->{websocket
}) {
809 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
810 push @$cmd, '-notls', '-listen', 'localhost';
813 push @$cmd, '-c', @$remcmd, @$shcmd;
815 run_command
($cmd, keeplocale
=> 1);
820 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
822 PVE
::Tools
::wait_for_vnc_port
($port);
833 __PACKAGE__-
>register_method ({
835 path
=> '{vmid}/termproxy',
839 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
841 description
=> "Creates a TCP proxy connection.",
843 additionalProperties
=> 0,
845 node
=> get_standard_option
('pve-node'),
846 vmid
=> get_standard_option
('pve-vmid'),
850 additionalProperties
=> 0,
852 user
=> { type
=> 'string' },
853 ticket
=> { type
=> 'string' },
854 port
=> { type
=> 'integer' },
855 upid
=> { type
=> 'string' },
861 my $rpcenv = PVE
::RPCEnvironment
::get
();
863 my $authuser = $rpcenv->get_user();
865 my $vmid = $param->{vmid
};
866 my $node = $param->{node
};
868 my $authpath = "/vms/$vmid";
870 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
872 my ($remip, $family);
874 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
875 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
877 $family = PVE
::Tools
::get_host_address_family
($node);
880 my $port = PVE
::Tools
::next_vnc_port
($family);
882 my $remcmd = $remip ?
883 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
885 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
886 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
888 my $shcmd = [ '/usr/bin/dtach', '-A',
889 "/var/run/dtach/vzctlconsole$vmid",
890 '-r', 'winch', '-z', @$concmd];
895 syslog
('info', "starting lxc termproxy $upid\n");
897 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
898 '--perm', 'VM.Console', '--'];
899 push @$cmd, @$remcmd, @$shcmd;
901 PVE
::Tools
::run_command
($cmd);
904 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
906 PVE
::Tools
::wait_for_vnc_port
($port);
916 __PACKAGE__-
>register_method({
917 name
=> 'vncwebsocket',
918 path
=> '{vmid}/vncwebsocket',
921 description
=> "You also need to pass a valid ticket (vncticket).",
922 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
924 description
=> "Opens a weksocket for VNC traffic.",
926 additionalProperties
=> 0,
928 node
=> get_standard_option
('pve-node'),
929 vmid
=> get_standard_option
('pve-vmid'),
931 description
=> "Ticket from previous call to vncproxy.",
936 description
=> "Port number returned by previous vncproxy call.",
946 port
=> { type
=> 'string' },
952 my $rpcenv = PVE
::RPCEnvironment
::get
();
954 my $authuser = $rpcenv->get_user();
956 my $authpath = "/vms/$param->{vmid}";
958 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
960 my $port = $param->{port
};
962 return { port
=> $port };
965 __PACKAGE__-
>register_method ({
966 name
=> 'spiceproxy',
967 path
=> '{vmid}/spiceproxy',
972 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
974 description
=> "Returns a SPICE configuration to connect to the CT.",
976 additionalProperties
=> 0,
978 node
=> get_standard_option
('pve-node'),
979 vmid
=> get_standard_option
('pve-vmid'),
980 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
983 returns
=> get_standard_option
('remote-viewer-config'),
987 my $vmid = $param->{vmid
};
988 my $node = $param->{node
};
989 my $proxy = $param->{proxy
};
991 my $authpath = "/vms/$vmid";
992 my $permissions = 'VM.Console';
994 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
996 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
998 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
1000 my $shcmd = ['/usr/bin/dtach', '-A',
1001 "/var/run/dtach/vzctlconsole$vmid",
1002 '-r', 'winch', '-z', @$concmd];
1004 my $title = "CT $vmid";
1006 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1010 __PACKAGE__-
>register_method({
1011 name
=> 'migrate_vm',
1012 path
=> '{vmid}/migrate',
1016 description
=> "Migrate the container to another node. Creates a new migration task.",
1018 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1021 additionalProperties
=> 0,
1023 node
=> get_standard_option
('pve-node'),
1024 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1025 target
=> get_standard_option
('pve-node', {
1026 description
=> "Target node.",
1027 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1031 description
=> "Use online/live migration.",
1036 description
=> "Use restart migration",
1041 description
=> "Timeout in seconds for shutdown for restart migration",
1047 description
=> "Force migration despite local bind / device" .
1048 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1052 description
=> "Override I/O bandwidth limit (in KiB/s).",
1056 default => 'migrate limit from datacenter or storage config',
1062 description
=> "the task ID.",
1067 my $rpcenv = PVE
::RPCEnvironment
::get
();
1069 my $authuser = $rpcenv->get_user();
1071 my $target = extract_param
($param, 'target');
1073 my $localnode = PVE
::INotify
::nodename
();
1074 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1076 PVE
::Cluster
::check_cfs_quorum
();
1078 PVE
::Cluster
::check_node_exists
($target);
1080 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1082 my $vmid = extract_param
($param, 'vmid');
1085 PVE
::LXC
::Config-
>load_config($vmid);
1087 # try to detect errors early
1088 if (PVE
::LXC
::check_running
($vmid)) {
1089 die "can't migrate running container without --online or --restart\n"
1090 if !$param->{online
} && !$param->{restart
};
1093 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1098 my $service = "ct:$vmid";
1100 my $cmd = ['ha-manager', 'migrate', $service, $target];
1102 print "Requesting HA migration for CT $vmid to node $target\n";
1104 PVE
::Tools
::run_command
($cmd);
1109 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1114 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1118 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1121 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1125 __PACKAGE__-
>register_method({
1126 name
=> 'vm_feature',
1127 path
=> '{vmid}/feature',
1131 description
=> "Check if feature for virtual machine is available.",
1133 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1136 additionalProperties
=> 0,
1138 node
=> get_standard_option
('pve-node'),
1139 vmid
=> get_standard_option
('pve-vmid'),
1141 description
=> "Feature to check.",
1143 enum
=> [ 'snapshot', 'clone', 'copy' ],
1145 snapname
=> get_standard_option
('pve-snapshot-name', {
1153 hasFeature
=> { type
=> 'boolean' },
1156 #items => { type => 'string' },
1163 my $node = extract_param
($param, 'node');
1165 my $vmid = extract_param
($param, 'vmid');
1167 my $snapname = extract_param
($param, 'snapname');
1169 my $feature = extract_param
($param, 'feature');
1171 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1174 my $snap = $conf->{snapshots
}->{$snapname};
1175 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1178 my $storage_cfg = PVE
::Storage
::config
();
1179 #Maybe include later
1180 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1181 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1184 hasFeature
=> $hasFeature,
1185 #nodes => [ keys %$nodelist ],
1189 __PACKAGE__-
>register_method({
1191 path
=> '{vmid}/template',
1195 description
=> "Create a Template.",
1197 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1198 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1201 additionalProperties
=> 0,
1203 node
=> get_standard_option
('pve-node'),
1204 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1207 returns
=> { type
=> 'null'},
1211 my $rpcenv = PVE
::RPCEnvironment
::get
();
1213 my $authuser = $rpcenv->get_user();
1215 my $node = extract_param
($param, 'node');
1217 my $vmid = extract_param
($param, 'vmid');
1219 my $updatefn = sub {
1221 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1222 PVE
::LXC
::Config-
>check_lock($conf);
1224 die "unable to create template, because CT contains snapshots\n"
1225 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1227 die "you can't convert a template to a template\n"
1228 if PVE
::LXC
::Config-
>is_template($conf);
1230 die "you can't convert a CT to template if the CT is running\n"
1231 if PVE
::LXC
::check_running
($vmid);
1233 if (my $err = check_storage_supports_templates
($conf)) {
1238 PVE
::LXC
::template_create
($vmid, $conf);
1240 $conf->{template
} = 1;
1242 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1243 # and remove lxc config
1244 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1247 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1250 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1255 __PACKAGE__-
>register_method({
1257 path
=> '{vmid}/clone',
1261 description
=> "Create a container clone/copy",
1263 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1264 "and 'VM.Allocate' permissions " .
1265 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1266 "'Datastore.AllocateSpace' on any used storage.",
1269 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1271 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1272 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1277 additionalProperties
=> 0,
1279 node
=> get_standard_option
('pve-node'),
1280 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1281 newid
=> get_standard_option
('pve-vmid', {
1282 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1283 description
=> 'VMID for the clone.' }),
1286 type
=> 'string', format
=> 'dns-name',
1287 description
=> "Set a hostname for the new CT.",
1292 description
=> "Description for the new CT.",
1296 type
=> 'string', format
=> 'pve-poolid',
1297 description
=> "Add the new CT to the specified pool.",
1299 snapname
=> get_standard_option
('pve-snapshot-name', {
1302 storage
=> get_standard_option
('pve-storage-id', {
1303 description
=> "Target storage for full clone.",
1309 description
=> "Create a full copy of all disks. This is always done when " .
1310 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1312 target
=> get_standard_option
('pve-node', {
1313 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1317 description
=> "Override I/O bandwidth limit (in KiB/s).",
1321 default => 'clone limit from datacenter or storage config',
1331 my $rpcenv = PVE
::RPCEnvironment
::get
();
1333 my $authuser = $rpcenv->get_user();
1335 my $node = extract_param
($param, 'node');
1337 my $vmid = extract_param
($param, 'vmid');
1339 my $newid = extract_param
($param, 'newid');
1341 my $pool = extract_param
($param, 'pool');
1343 if (defined($pool)) {
1344 $rpcenv->check_pool_exist($pool);
1347 my $snapname = extract_param
($param, 'snapname');
1349 my $storage = extract_param
($param, 'storage');
1351 my $target = extract_param
($param, 'target');
1353 my $localnode = PVE
::INotify
::nodename
();
1355 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1357 PVE
::Cluster
::check_node_exists
($target) if $target;
1359 my $storecfg = PVE
::Storage
::config
();
1362 # check if storage is enabled on local node
1363 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1365 # check if storage is available on target node
1366 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1367 # clone only works if target storage is shared
1368 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1369 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1373 PVE
::Cluster
::check_cfs_quorum
();
1377 my $mountpoints = {};
1382 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1383 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1385 $running = PVE
::LXC
::check_running
($vmid) || 0;
1387 my $full = extract_param
($param, 'full');
1388 if (!defined($full)) {
1389 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1391 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1394 die "snapshot '$snapname' does not exist\n"
1395 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1398 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1400 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1401 die "unable to create CT $newid: config file already exists\n"
1405 foreach my $opt (keys %$src_conf) {
1406 next if $opt =~ m/^unused\d+$/;
1408 my $value = $src_conf->{$opt};
1410 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1411 my $mp = $opt eq 'rootfs' ?
1412 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1413 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1415 if ($mp->{type
} eq 'volume') {
1416 my $volid = $mp->{volume
};
1418 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1419 $sid = $storage if defined($storage);
1420 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1421 if (!$scfg->{shared
}) {
1423 warn "found non-shared volume: $volid\n" if $target;
1426 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1429 die "Cannot do full clones on a running container without snapshots\n"
1430 if $running && !defined($snapname);
1431 $fullclone->{$opt} = 1;
1433 # not full means clone instead of copy
1434 die "Linked clone feature for '$volid' is not available\n"
1435 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1438 $mountpoints->{$opt} = $mp;
1439 push @$vollist, $volid;
1442 # TODO: allow bind mounts?
1443 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1445 } elsif ($opt =~ m/^net(\d+)$/) {
1446 # always change MAC! address
1447 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1448 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1449 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1450 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1452 # copy everything else
1453 $newconf->{$opt} = $value;
1456 die "can't clone CT to node '$target' (CT uses local storage)\n"
1457 if $target && !$sharedvm;
1459 # Replace the 'disk' lock with a 'create' lock.
1460 $newconf->{lock} = 'create';
1462 delete $newconf->{pending
};
1463 delete $newconf->{template
};
1464 if ($param->{hostname
}) {
1465 $newconf->{hostname
} = $param->{hostname
};
1468 if ($param->{description
}) {
1469 $newconf->{description
} = $param->{description
};
1472 # create empty/temp config - this fails if CT already exists on other node
1473 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1476 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1482 my $update_conf = sub {
1483 my ($key, $value) = @_;
1484 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1485 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1486 die "Lost 'create' config lock, aborting.\n"
1487 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1488 $conf->{$key} = $value;
1489 PVE
::LXC
::Config-
>write_config($newid, $conf);
1496 my $newvollist = [];
1498 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1499 die "unexpected state change\n" if $verify_running != $running;
1505 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1507 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1508 my $bwlimit = extract_param
($param, 'bwlimit');
1510 foreach my $opt (keys %$mountpoints) {
1511 my $mp = $mountpoints->{$opt};
1512 my $volid = $mp->{volume
};
1515 if ($fullclone->{$opt}) {
1516 print "create full clone of mountpoint $opt ($volid)\n";
1517 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1518 my $target_storage = $storage // $source_storage;
1519 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1520 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1522 print "create linked clone of mount point $opt ($volid)\n";
1523 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1526 push @$newvollist, $newvolid;
1527 $mp->{volume
} = $newvolid;
1529 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1532 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1533 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1536 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1537 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1538 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1540 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1541 die "Failed to move config to node '$target' - rename failed: $!\n"
1542 if !rename($conffile, $newconffile);
1547 # Unlock the source config in any case:
1548 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1552 # Now cleanup the config & disks:
1555 sleep 1; # some storages like rbd need to wait before release volume - really?
1557 foreach my $volid (@$newvollist) {
1558 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1561 die "clone failed: $err";
1567 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1568 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1572 __PACKAGE__-
>register_method({
1573 name
=> 'resize_vm',
1574 path
=> '{vmid}/resize',
1578 description
=> "Resize a container mount point.",
1580 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1583 additionalProperties
=> 0,
1585 node
=> get_standard_option
('pve-node'),
1586 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1589 description
=> "The disk you want to resize.",
1590 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1594 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1595 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.",
1599 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1607 description
=> "the task ID.",
1612 my $rpcenv = PVE
::RPCEnvironment
::get
();
1614 my $authuser = $rpcenv->get_user();
1616 my $node = extract_param
($param, 'node');
1618 my $vmid = extract_param
($param, 'vmid');
1620 my $digest = extract_param
($param, 'digest');
1622 my $sizestr = extract_param
($param, 'size');
1623 my $ext = ($sizestr =~ s/^\+//);
1624 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1625 die "invalid size string" if !defined($newsize);
1627 die "no options specified\n" if !scalar(keys %$param);
1629 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1631 my $storage_cfg = cfs_read_file
("storage.cfg");
1635 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1636 PVE
::LXC
::Config-
>check_lock($conf);
1638 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1640 my $running = PVE
::LXC
::check_running
($vmid);
1642 my $disk = $param->{disk
};
1643 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1644 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1646 my $volid = $mp->{volume
};
1648 my (undef, undef, $owner, undef, undef, undef, $format) =
1649 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1651 die "can't resize mount point owned by another container ($owner)"
1654 die "can't resize volume: $disk if snapshot exists\n"
1655 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1657 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1659 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1661 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1663 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1665 die "Could not determine current size of volume '$volid'\n" if !defined($size);
1667 $newsize += $size if $ext;
1668 $newsize = int($newsize);
1670 die "unable to shrink disk size\n" if $newsize < $size;
1672 return if $size == $newsize;
1674 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1676 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1677 # we pass 0 here (parameter only makes sense for qemu)
1678 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1680 $mp->{size
} = $newsize;
1681 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1683 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1685 if ($format eq 'raw') {
1686 # we need to ensure that the volume is mapped, if not needed this is a NOP
1687 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1688 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1692 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1693 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1694 die "internal error: CT running but mount point not attached to a loop device"
1696 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1698 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1699 # to be visible to it in its namespace.
1700 # To not interfere with the rest of the system we unshare the current mount namespace,
1701 # mount over /tmp and then run resize2fs.
1703 # interestingly we don't need to e2fsck on mounted systems...
1704 my $quoted = PVE
::Tools
::shellquote
($path);
1705 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1707 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1709 warn "Failed to update the container's filesystem: $@\n" if $@;
1712 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1713 PVE
::Tools
::run_command
(['resize2fs', $path]);
1715 warn "Failed to update the container's filesystem: $@\n" if $@;
1717 # always un-map if not running, this is a NOP if not needed
1718 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1723 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1726 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1729 __PACKAGE__-
>register_method({
1730 name
=> 'move_volume',
1731 path
=> '{vmid}/move_volume',
1735 description
=> "Move a rootfs-/mp-volume to a different storage",
1737 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1738 "and 'Datastore.AllocateSpace' permissions on the storage.",
1741 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1742 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1746 additionalProperties
=> 0,
1748 node
=> get_standard_option
('pve-node'),
1749 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1752 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1753 description
=> "Volume which will be moved.",
1755 storage
=> get_standard_option
('pve-storage-id', {
1756 description
=> "Target Storage.",
1757 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1761 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1767 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1772 description
=> "Override I/O bandwidth limit (in KiB/s).",
1776 default => 'clone limit from datacenter or storage config',
1786 my $rpcenv = PVE
::RPCEnvironment
::get
();
1788 my $authuser = $rpcenv->get_user();
1790 my $vmid = extract_param
($param, 'vmid');
1792 my $storage = extract_param
($param, 'storage');
1794 my $mpkey = extract_param
($param, 'volume');
1796 my $lockname = 'disk';
1798 my ($mpdata, $old_volid);
1800 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1801 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1802 PVE
::LXC
::Config-
>check_lock($conf);
1804 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1806 if ($mpkey eq 'rootfs') {
1807 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1808 } elsif ($mpkey =~ m/mp\d+/) {
1809 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1811 die "Can't parse $mpkey\n";
1813 $old_volid = $mpdata->{volume
};
1815 die "you can't move a volume with snapshots and delete the source\n"
1816 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1818 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1820 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1825 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1827 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1828 my $storage_cfg = PVE
::Storage
::config
();
1833 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1834 my $bwlimit = extract_param
($param, 'bwlimit');
1835 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1836 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$source_storage, $storage], $bwlimit);
1837 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf, undef, $movelimit);
1838 $mpdata->{volume
} = $new_volid;
1840 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1841 my $digest = $conf->{digest
};
1842 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1843 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1845 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1847 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1849 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1853 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1854 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1860 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1861 if defined($new_volid);
1867 if ($param->{delete}) {
1869 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1870 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1876 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1881 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1884 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1891 __PACKAGE__-
>register_method({
1892 name
=> 'vm_pending',
1893 path
=> '{vmid}/pending',
1896 description
=> 'Get container configuration, including pending changes.',
1898 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1901 additionalProperties
=> 0,
1903 node
=> get_standard_option
('pve-node'),
1904 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1913 description
=> 'Configuration option name.',
1917 description
=> 'Current value.',
1922 description
=> 'Pending value.',
1927 description
=> "Indicates a pending delete request if present and not 0.",
1939 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
1941 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
1943 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);