1 package PVE
::API2
::LXC
;
7 use PVE
::Tools
qw(extract_param run_command);
8 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
10 use PVE
::Cluster
qw(cfs_read_file);
11 use PVE
::AccessControl
;
15 use PVE
::RPCEnvironment
;
16 use PVE
::ReplicationConfig
;
19 use PVE
::LXC
::Migrate
;
20 use PVE
::GuestHelpers
;
21 use PVE
::API2
::LXC
::Config
;
22 use PVE
::API2
::LXC
::Status
;
23 use PVE
::API2
::LXC
::Snapshot
;
24 use PVE
::JSONSchema
qw(get_standard_option);
25 use base
qw(PVE::RESTHandler);
28 if (!$ENV{PVE_GENERATING_DOCS
}) {
29 require PVE
::HA
::Env
::PVE2
;
30 import PVE
::HA
::Env
::PVE2
;
31 require PVE
::HA
::Config
;
32 import PVE
::HA
::Config
;
36 __PACKAGE__-
>register_method ({
37 subclass
=> "PVE::API2::LXC::Config",
38 path
=> '{vmid}/config',
41 __PACKAGE__-
>register_method ({
42 subclass
=> "PVE::API2::LXC::Status",
43 path
=> '{vmid}/status',
46 __PACKAGE__-
>register_method ({
47 subclass
=> "PVE::API2::LXC::Snapshot",
48 path
=> '{vmid}/snapshot',
51 __PACKAGE__-
>register_method ({
52 subclass
=> "PVE::API2::Firewall::CT",
53 path
=> '{vmid}/firewall',
56 __PACKAGE__-
>register_method({
60 description
=> "LXC container index (per node).",
62 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
66 protected
=> 1, # /proc files are only readable by root
68 additionalProperties
=> 0,
70 node
=> get_standard_option
('pve-node'),
77 properties
=> $PVE::LXC
::vmstatus_return_properties
,
79 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
84 my $rpcenv = PVE
::RPCEnvironment
::get
();
85 my $authuser = $rpcenv->get_user();
87 my $vmstatus = PVE
::LXC
::vmstatus
();
90 foreach my $vmid (keys %$vmstatus) {
91 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
93 my $data = $vmstatus->{$vmid};
101 __PACKAGE__-
>register_method({
105 description
=> "Create or restore a container.",
107 user
=> 'all', # check inside
108 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
109 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
110 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
115 additionalProperties
=> 0,
116 properties
=> PVE
::LXC
::Config-
>json_config_properties({
117 node
=> get_standard_option
('pve-node'),
118 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
120 description
=> "The OS template or backup file.",
123 completion
=> \
&PVE
::LXC
::complete_os_templates
,
128 description
=> "Sets root password inside container.",
131 storage
=> get_standard_option
('pve-storage-id', {
132 description
=> "Default Storage.",
135 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
140 description
=> "Allow to overwrite existing container.",
145 description
=> "Mark this as restore task.",
150 description
=> "Assign a unique random ethernet address.",
151 requires
=> 'restore',
155 type
=> 'string', format
=> 'pve-poolid',
156 description
=> "Add the VM to the specified pool.",
158 'ignore-unpack-errors' => {
161 description
=> "Ignore errors when extracting the template.",
163 'ssh-public-keys' => {
166 description
=> "Setup public SSH keys (one key per line, " .
170 description
=> "Override I/O bandwidth limit (in KiB/s).",
174 default => 'restore limit from datacenter or storage config',
180 description
=> "Start the CT after its creation finished successfully.",
190 PVE
::Cluster
::check_cfs_quorum
();
192 my $rpcenv = PVE
::RPCEnvironment
::get
();
193 my $authuser = $rpcenv->get_user();
195 my $node = extract_param
($param, 'node');
196 my $vmid = extract_param
($param, 'vmid');
197 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
198 my $bwlimit = extract_param
($param, 'bwlimit');
199 my $start_after_create = extract_param
($param, 'start');
201 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
202 my $same_container_exists = -f
$basecfg_fn;
204 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
205 my $unprivileged = extract_param
($param, 'unprivileged');
206 my $restore = extract_param
($param, 'restore');
207 my $unique = extract_param
($param, 'unique');
209 # used to skip firewall config restore if user lacks permission
210 my $skip_fw_config_restore = 0;
213 # fixme: limit allowed parameters
216 my $force = extract_param
($param, 'force');
218 if (!($same_container_exists && $restore && $force)) {
219 PVE
::Cluster
::check_vmid_unused
($vmid);
221 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
222 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
223 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
226 my $password = extract_param
($param, 'password');
227 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
228 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
230 my $pool = extract_param
($param, 'pool');
231 if (defined($pool)) {
232 $rpcenv->check_pool_exist($pool);
233 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
236 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
238 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
240 } elsif ($restore && $force && $same_container_exists &&
241 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
242 # OK: user has VM.Backup permissions, and want to restore an existing VM
244 # we don't want to restore a container-provided FW conf in this case
245 # since the user is lacking permission to configure the container's FW
246 $skip_fw_config_restore = 1;
251 my $ostemplate = extract_param
($param, 'ostemplate');
252 my $storage = extract_param
($param, 'storage') // 'local';
254 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
256 my $storage_cfg = cfs_read_file
("storage.cfg");
259 if ($ostemplate eq '-') {
260 die "pipe requires cli environment\n"
261 if $rpcenv->{type
} ne 'cli';
262 die "pipe can only be used with restore tasks\n"
265 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
267 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
268 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
272 my $check_and_activate_storage = sub {
275 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
277 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
278 if !$scfg->{content
}->{rootdir
};
280 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
282 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
283 $used_storages{$sid} = 1;
288 my $is_root = $authuser eq 'root@pam';
290 my $no_disk_param = {};
292 my $storage_only_mode = 1;
293 foreach my $opt (keys %$param) {
294 my $value = $param->{$opt};
295 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
296 # allow to use simple numbers (add default storage in that case)
297 if ($value =~ m/^\d+(\.\d+)?$/) {
298 $mp_param->{$opt} = "$storage:$value";
300 $mp_param->{$opt} = $value;
302 $storage_only_mode = 0;
303 } elsif ($opt =~ m/^unused\d+$/) {
304 warn "ignoring '$opt', cannot create/restore with unused volume\n";
305 delete $param->{$opt};
307 $no_disk_param->{$opt} = $value;
311 die "mount points configured, but 'rootfs' not set - aborting\n"
312 if !$storage_only_mode && !defined($mp_param->{rootfs
});
314 # check storage access, activate storage
315 my $delayed_mp_param = {};
316 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
317 my ($ms, $mountpoint) = @_;
319 my $volid = $mountpoint->{volume
};
320 my $mp = $mountpoint->{mp
};
322 if ($mountpoint->{type
} ne 'volume') { # bind or device
323 die "Only root can pass arbitrary filesystem paths.\n"
326 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
327 &$check_and_activate_storage($sid);
331 # check/activate default storage
332 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
334 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
336 $conf->{unprivileged
} = 1 if $unprivileged;
338 my $emsg = $restore ?
"unable to restore CT $vmid -" : "unable to create CT $vmid -";
340 eval { PVE
::LXC
::Config-
>create_and_lock_config($vmid, $force) };
341 die "$emsg $@" if $@;
344 my $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
349 my $orig_mp_param; # only used if $restore
351 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
352 if ($is_root && $archive ne '-') {
354 ($orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
355 $was_template = delete $orig_conf->{template
};
356 # When we're root call 'restore_configuration' with restricted=0,
357 # causing it to restore the raw lxc entries, among which there may be
358 # 'lxc.idmap' entries. We need to make sure that the extracted contents
359 # of the container match up with the restored configuration afterwards:
360 $conf->{lxc
} = $orig_conf->{lxc
};
363 if ($storage_only_mode) {
365 if (!defined($orig_mp_param)) {
366 (undef, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
368 $mp_param = $orig_mp_param;
369 die "rootfs configuration could not be recovered, please check and specify manually!\n"
370 if !defined($mp_param->{rootfs
});
371 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
372 my ($ms, $mountpoint) = @_;
373 my $type = $mountpoint->{type
};
374 if ($type eq 'volume') {
375 die "unable to detect disk size - please specify $ms (size)\n"
376 if !defined($mountpoint->{size
});
377 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
378 delete $mountpoint->{size
};
379 $mountpoint->{volume
} = "$storage:$disksize";
380 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
382 my $type = $mountpoint->{type
};
383 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
384 if ($ms eq 'rootfs');
385 die "restoring '$ms' to $type mount is only possible for root\n"
388 if ($mountpoint->{backup
}) {
389 warn "WARNING - unsupported configuration!\n";
390 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
391 warn "mount point configuration will be restored after archive extraction!\n";
392 warn "contained files will be restored to wrong directory!\n";
394 delete $mp_param->{$ms}; # actually delay bind/dev mps
395 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
399 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
403 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
405 # we always have the 'create' lock so check for more than 1 entry
406 if (scalar(keys %$old_conf) > 1) {
407 # destroy old container volumes
408 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, { lock => 'create' });
412 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
413 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
414 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
417 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
418 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
419 $lxc_setup->template_fixup($conf);
421 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
422 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
423 $lxc_setup->post_create_hook($password, $ssh_keys);
427 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
428 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
431 $conf->{hostname
} ||= "CT$vmid";
432 $conf->{memory
} ||= 512;
433 $conf->{swap
} //= 512;
434 foreach my $mp (keys %$delayed_mp_param) {
435 $conf->{$mp} = $delayed_mp_param->{$mp};
437 # If the template flag was set, we try to convert again to template after restore
439 print STDERR
"Convert restored container to template...\n";
440 if (my $err = check_storage_supports_templates
($conf)) {
442 warn "Leave restored backup as container instead of converting to template.\n"
444 PVE
::LXC
::template_create
($vmid, $conf);
445 $conf->{template
} = 1;
448 PVE
::LXC
::Config-
>write_config($vmid, $conf);
451 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
452 eval { PVE
::LXC
::Config-
>destroy_config($vmid) };
456 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
458 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
459 if $start_after_create;
462 my $workername = $restore ?
'vzrestore' : 'vzcreate';
463 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
465 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
468 sub check_storage_supports_templates
{
471 my $scfg = PVE
::Storage
::config
();
473 PVE
::LXC
::Config-
>foreach_mountpoint($conf, sub {
476 my ($sid) = PVE
::Storage
::parse_volume_id
($mp->{volume
}, 0);
477 die "Warning: Directory storage '$sid' does not support container templates!\n"
478 if $scfg->{ids
}->{$sid}->{path
};
484 __PACKAGE__-
>register_method({
489 description
=> "Directory index",
494 additionalProperties
=> 0,
496 node
=> get_standard_option
('pve-node'),
497 vmid
=> get_standard_option
('pve-vmid'),
505 subdir
=> { type
=> 'string' },
508 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
514 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
517 { subdir
=> 'config' },
518 { subdir
=> 'pending' },
519 { subdir
=> 'status' },
520 { subdir
=> 'vncproxy' },
521 { subdir
=> 'termproxy' },
522 { subdir
=> 'vncwebsocket' },
523 { subdir
=> 'spiceproxy' },
524 { subdir
=> 'migrate' },
525 { subdir
=> 'clone' },
526 # { subdir => 'initlog' },
528 { subdir
=> 'rrddata' },
529 { subdir
=> 'firewall' },
530 { subdir
=> 'snapshot' },
531 { subdir
=> 'resize' },
538 __PACKAGE__-
>register_method({
540 path
=> '{vmid}/rrd',
542 protected
=> 1, # fixme: can we avoid that?
544 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
546 description
=> "Read VM RRD statistics (returns PNG)",
548 additionalProperties
=> 0,
550 node
=> get_standard_option
('pve-node'),
551 vmid
=> get_standard_option
('pve-vmid'),
553 description
=> "Specify the time frame you are interested in.",
555 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
558 description
=> "The list of datasources you want to display.",
559 type
=> 'string', format
=> 'pve-configid-list',
562 description
=> "The RRD consolidation function",
564 enum
=> [ 'AVERAGE', 'MAX' ],
572 filename
=> { type
=> 'string' },
578 return PVE
::Cluster
::create_rrd_graph
(
579 "pve2-vm/$param->{vmid}", $param->{timeframe
},
580 $param->{ds
}, $param->{cf
});
584 __PACKAGE__-
>register_method({
586 path
=> '{vmid}/rrddata',
588 protected
=> 1, # fixme: can we avoid that?
590 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
592 description
=> "Read VM RRD statistics",
594 additionalProperties
=> 0,
596 node
=> get_standard_option
('pve-node'),
597 vmid
=> get_standard_option
('pve-vmid'),
599 description
=> "Specify the time frame you are interested in.",
601 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
604 description
=> "The RRD consolidation function",
606 enum
=> [ 'AVERAGE', 'MAX' ],
621 return PVE
::Cluster
::create_rrd_data
(
622 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
625 __PACKAGE__-
>register_method({
626 name
=> 'destroy_vm',
631 description
=> "Destroy the container (also delete all uses files).",
633 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
636 additionalProperties
=> 0,
638 node
=> get_standard_option
('pve-node'),
639 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
648 my $rpcenv = PVE
::RPCEnvironment
::get
();
649 my $authuser = $rpcenv->get_user();
650 my $vmid = $param->{vmid
};
652 # test if container exists
653 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
654 my $storage_cfg = cfs_read_file
("storage.cfg");
655 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
657 die "unable to remove CT $vmid - used in HA resources\n"
658 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
660 # do not allow destroy if there are replication jobs
661 my $repl_conf = PVE
::ReplicationConfig-
>new();
662 $repl_conf->check_for_existing_jobs($vmid);
664 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
666 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
669 # reload config after lock
670 $conf = PVE
::LXC
::Config-
>load_config($vmid);
671 PVE
::LXC
::Config-
>check_lock($conf);
673 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
675 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf, { lock => 'destroyed' });
677 PVE
::AccessControl
::remove_vm_access
($vmid);
678 PVE
::Firewall
::remove_vmfw_conf
($vmid);
680 # only now remove the zombie config, else we can have reuse race
681 PVE
::LXC
::Config-
>destroy_config($vmid);
684 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
686 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
691 __PACKAGE__-
>register_method ({
693 path
=> '{vmid}/vncproxy',
697 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
699 description
=> "Creates a TCP VNC proxy connections.",
701 additionalProperties
=> 0,
703 node
=> get_standard_option
('pve-node'),
704 vmid
=> get_standard_option
('pve-vmid'),
708 description
=> "use websocket instead of standard VNC.",
712 description
=> "sets the width of the console in pixels.",
719 description
=> "sets the height of the console in pixels.",
727 additionalProperties
=> 0,
729 user
=> { type
=> 'string' },
730 ticket
=> { type
=> 'string' },
731 cert
=> { type
=> 'string' },
732 port
=> { type
=> 'integer' },
733 upid
=> { type
=> 'string' },
739 my $rpcenv = PVE
::RPCEnvironment
::get
();
741 my $authuser = $rpcenv->get_user();
743 my $vmid = $param->{vmid
};
744 my $node = $param->{node
};
746 my $authpath = "/vms/$vmid";
748 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
750 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
753 my ($remip, $family);
755 if ($node ne PVE
::INotify
::nodename
()) {
756 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
758 $family = PVE
::Tools
::get_host_address_family
($node);
761 my $port = PVE
::Tools
::next_vnc_port
($family);
763 # NOTE: vncterm VNC traffic is already TLS encrypted,
764 # so we select the fastest chipher here (or 'none'?)
765 my $remcmd = $remip ?
766 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
768 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
769 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
771 my $shcmd = [ '/usr/bin/dtach', '-A',
772 "/var/run/dtach/vzctlconsole$vmid",
773 '-r', 'winch', '-z', @$concmd];
778 syslog
('info', "starting lxc vnc proxy $upid\n");
782 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
783 '-timeout', $timeout, '-authpath', $authpath,
784 '-perm', 'VM.Console'];
786 if ($param->{width
}) {
787 push @$cmd, '-width', $param->{width
};
790 if ($param->{height
}) {
791 push @$cmd, '-height', $param->{height
};
794 if ($param->{websocket
}) {
795 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
796 push @$cmd, '-notls', '-listen', 'localhost';
799 push @$cmd, '-c', @$remcmd, @$shcmd;
801 run_command
($cmd, keeplocale
=> 1);
806 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
808 PVE
::Tools
::wait_for_vnc_port
($port);
819 __PACKAGE__-
>register_method ({
821 path
=> '{vmid}/termproxy',
825 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
827 description
=> "Creates a TCP proxy connection.",
829 additionalProperties
=> 0,
831 node
=> get_standard_option
('pve-node'),
832 vmid
=> get_standard_option
('pve-vmid'),
836 additionalProperties
=> 0,
838 user
=> { type
=> 'string' },
839 ticket
=> { type
=> 'string' },
840 port
=> { type
=> 'integer' },
841 upid
=> { type
=> 'string' },
847 my $rpcenv = PVE
::RPCEnvironment
::get
();
849 my $authuser = $rpcenv->get_user();
851 my $vmid = $param->{vmid
};
852 my $node = $param->{node
};
854 my $authpath = "/vms/$vmid";
856 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
858 my ($remip, $family);
860 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
861 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
863 $family = PVE
::Tools
::get_host_address_family
($node);
866 my $port = PVE
::Tools
::next_vnc_port
($family);
868 my $remcmd = $remip ?
869 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
871 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
872 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
874 my $shcmd = [ '/usr/bin/dtach', '-A',
875 "/var/run/dtach/vzctlconsole$vmid",
876 '-r', 'winch', '-z', @$concmd];
881 syslog
('info', "starting lxc termproxy $upid\n");
883 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
884 '--perm', 'VM.Console', '--'];
885 push @$cmd, @$remcmd, @$shcmd;
887 PVE
::Tools
::run_command
($cmd);
890 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
892 PVE
::Tools
::wait_for_vnc_port
($port);
902 __PACKAGE__-
>register_method({
903 name
=> 'vncwebsocket',
904 path
=> '{vmid}/vncwebsocket',
907 description
=> "You also need to pass a valid ticket (vncticket).",
908 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
910 description
=> "Opens a weksocket for VNC traffic.",
912 additionalProperties
=> 0,
914 node
=> get_standard_option
('pve-node'),
915 vmid
=> get_standard_option
('pve-vmid'),
917 description
=> "Ticket from previous call to vncproxy.",
922 description
=> "Port number returned by previous vncproxy call.",
932 port
=> { type
=> 'string' },
938 my $rpcenv = PVE
::RPCEnvironment
::get
();
940 my $authuser = $rpcenv->get_user();
942 my $authpath = "/vms/$param->{vmid}";
944 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
946 my $port = $param->{port
};
948 return { port
=> $port };
951 __PACKAGE__-
>register_method ({
952 name
=> 'spiceproxy',
953 path
=> '{vmid}/spiceproxy',
958 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
960 description
=> "Returns a SPICE configuration to connect to the CT.",
962 additionalProperties
=> 0,
964 node
=> get_standard_option
('pve-node'),
965 vmid
=> get_standard_option
('pve-vmid'),
966 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
969 returns
=> get_standard_option
('remote-viewer-config'),
973 my $vmid = $param->{vmid
};
974 my $node = $param->{node
};
975 my $proxy = $param->{proxy
};
977 my $authpath = "/vms/$vmid";
978 my $permissions = 'VM.Console';
980 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
982 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
984 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
986 my $shcmd = ['/usr/bin/dtach', '-A',
987 "/var/run/dtach/vzctlconsole$vmid",
988 '-r', 'winch', '-z', @$concmd];
990 my $title = "CT $vmid";
992 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
996 __PACKAGE__-
>register_method({
997 name
=> 'migrate_vm',
998 path
=> '{vmid}/migrate',
1002 description
=> "Migrate the container to another node. Creates a new migration task.",
1004 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1007 additionalProperties
=> 0,
1009 node
=> get_standard_option
('pve-node'),
1010 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1011 target
=> get_standard_option
('pve-node', {
1012 description
=> "Target node.",
1013 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1017 description
=> "Use online/live migration.",
1022 description
=> "Use restart migration",
1027 description
=> "Timeout in seconds for shutdown for restart migration",
1033 description
=> "Force migration despite local bind / device" .
1034 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1038 description
=> "Override I/O bandwidth limit (in KiB/s).",
1042 default => 'migrate limit from datacenter or storage config',
1048 description
=> "the task ID.",
1053 my $rpcenv = PVE
::RPCEnvironment
::get
();
1055 my $authuser = $rpcenv->get_user();
1057 my $target = extract_param
($param, 'target');
1059 my $localnode = PVE
::INotify
::nodename
();
1060 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1062 PVE
::Cluster
::check_cfs_quorum
();
1064 PVE
::Cluster
::check_node_exists
($target);
1066 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1068 my $vmid = extract_param
($param, 'vmid');
1071 PVE
::LXC
::Config-
>load_config($vmid);
1073 # try to detect errors early
1074 if (PVE
::LXC
::check_running
($vmid)) {
1075 die "can't migrate running container without --online or --restart\n"
1076 if !$param->{online
} && !$param->{restart
};
1079 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1084 my $service = "ct:$vmid";
1086 my $cmd = ['ha-manager', 'migrate', $service, $target];
1088 print "Requesting HA migration for CT $vmid to node $target\n";
1090 PVE
::Tools
::run_command
($cmd);
1095 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1100 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1104 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1107 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1111 __PACKAGE__-
>register_method({
1112 name
=> 'vm_feature',
1113 path
=> '{vmid}/feature',
1117 description
=> "Check if feature for virtual machine is available.",
1119 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1122 additionalProperties
=> 0,
1124 node
=> get_standard_option
('pve-node'),
1125 vmid
=> get_standard_option
('pve-vmid'),
1127 description
=> "Feature to check.",
1129 enum
=> [ 'snapshot', 'clone', 'copy' ],
1131 snapname
=> get_standard_option
('pve-snapshot-name', {
1139 hasFeature
=> { type
=> 'boolean' },
1142 #items => { type => 'string' },
1149 my $node = extract_param
($param, 'node');
1151 my $vmid = extract_param
($param, 'vmid');
1153 my $snapname = extract_param
($param, 'snapname');
1155 my $feature = extract_param
($param, 'feature');
1157 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1160 my $snap = $conf->{snapshots
}->{$snapname};
1161 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1164 my $storage_cfg = PVE
::Storage
::config
();
1165 #Maybe include later
1166 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1167 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1170 hasFeature
=> $hasFeature,
1171 #nodes => [ keys %$nodelist ],
1175 __PACKAGE__-
>register_method({
1177 path
=> '{vmid}/template',
1181 description
=> "Create a Template.",
1183 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1184 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1187 additionalProperties
=> 0,
1189 node
=> get_standard_option
('pve-node'),
1190 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1193 returns
=> { type
=> 'null'},
1197 my $rpcenv = PVE
::RPCEnvironment
::get
();
1199 my $authuser = $rpcenv->get_user();
1201 my $node = extract_param
($param, 'node');
1203 my $vmid = extract_param
($param, 'vmid');
1205 my $updatefn = sub {
1207 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1208 PVE
::LXC
::Config-
>check_lock($conf);
1210 die "unable to create template, because CT contains snapshots\n"
1211 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1213 die "you can't convert a template to a template\n"
1214 if PVE
::LXC
::Config-
>is_template($conf);
1216 die "you can't convert a CT to template if the CT is running\n"
1217 if PVE
::LXC
::check_running
($vmid);
1219 if (my $err = check_storage_supports_templates
($conf)) {
1224 PVE
::LXC
::template_create
($vmid, $conf);
1226 $conf->{template
} = 1;
1228 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1229 # and remove lxc config
1230 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1233 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1236 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1241 __PACKAGE__-
>register_method({
1243 path
=> '{vmid}/clone',
1247 description
=> "Create a container clone/copy",
1249 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1250 "and 'VM.Allocate' permissions " .
1251 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1252 "'Datastore.AllocateSpace' on any used storage.",
1255 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1257 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1258 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1263 additionalProperties
=> 0,
1265 node
=> get_standard_option
('pve-node'),
1266 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1267 newid
=> get_standard_option
('pve-vmid', {
1268 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1269 description
=> 'VMID for the clone.' }),
1272 type
=> 'string', format
=> 'dns-name',
1273 description
=> "Set a hostname for the new CT.",
1278 description
=> "Description for the new CT.",
1282 type
=> 'string', format
=> 'pve-poolid',
1283 description
=> "Add the new CT to the specified pool.",
1285 snapname
=> get_standard_option
('pve-snapshot-name', {
1288 storage
=> get_standard_option
('pve-storage-id', {
1289 description
=> "Target storage for full clone.",
1295 description
=> "Create a full copy of all disks. This is always done when " .
1296 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1298 target
=> get_standard_option
('pve-node', {
1299 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1303 description
=> "Override I/O bandwidth limit (in KiB/s).",
1307 default => 'clone limit from datacenter or storage config',
1317 my $rpcenv = PVE
::RPCEnvironment
::get
();
1319 my $authuser = $rpcenv->get_user();
1321 my $node = extract_param
($param, 'node');
1323 my $vmid = extract_param
($param, 'vmid');
1325 my $newid = extract_param
($param, 'newid');
1327 my $pool = extract_param
($param, 'pool');
1329 if (defined($pool)) {
1330 $rpcenv->check_pool_exist($pool);
1333 my $snapname = extract_param
($param, 'snapname');
1335 my $storage = extract_param
($param, 'storage');
1337 my $target = extract_param
($param, 'target');
1339 my $localnode = PVE
::INotify
::nodename
();
1341 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1343 PVE
::Cluster
::check_node_exists
($target) if $target;
1345 my $storecfg = PVE
::Storage
::config
();
1348 # check if storage is enabled on local node
1349 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1351 # check if storage is available on target node
1352 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1353 # clone only works if target storage is shared
1354 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1355 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1359 PVE
::Cluster
::check_cfs_quorum
();
1363 my $mountpoints = {};
1368 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1369 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1371 $running = PVE
::LXC
::check_running
($vmid) || 0;
1373 my $full = extract_param
($param, 'full');
1374 if (!defined($full)) {
1375 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1377 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1380 die "snapshot '$snapname' does not exist\n"
1381 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1384 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1386 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1387 die "unable to create CT $newid: config file already exists\n"
1391 foreach my $opt (keys %$src_conf) {
1392 next if $opt =~ m/^unused\d+$/;
1394 my $value = $src_conf->{$opt};
1396 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1397 my $mp = $opt eq 'rootfs' ?
1398 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1399 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1401 if ($mp->{type
} eq 'volume') {
1402 my $volid = $mp->{volume
};
1404 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1405 $sid = $storage if defined($storage);
1406 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1407 if (!$scfg->{shared
}) {
1409 warn "found non-shared volume: $volid\n" if $target;
1412 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1415 die "Cannot do full clones on a running container without snapshots\n"
1416 if $running && !defined($snapname);
1417 $fullclone->{$opt} = 1;
1419 # not full means clone instead of copy
1420 die "Linked clone feature for '$volid' is not available\n"
1421 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1424 $mountpoints->{$opt} = $mp;
1425 push @$vollist, $volid;
1428 # TODO: allow bind mounts?
1429 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1431 } elsif ($opt =~ m/^net(\d+)$/) {
1432 # always change MAC! address
1433 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1434 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1435 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1436 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1438 # copy everything else
1439 $newconf->{$opt} = $value;
1442 die "can't clone CT to node '$target' (CT uses local storage)\n"
1443 if $target && !$sharedvm;
1445 # Replace the 'disk' lock with a 'create' lock.
1446 $newconf->{lock} = 'create';
1448 delete $newconf->{template
};
1449 if ($param->{hostname
}) {
1450 $newconf->{hostname
} = $param->{hostname
};
1453 if ($param->{description
}) {
1454 $newconf->{description
} = $param->{description
};
1457 # create empty/temp config - this fails if CT already exists on other node
1458 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1461 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1467 my $update_conf = sub {
1468 my ($key, $value) = @_;
1469 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1470 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1471 die "Lost 'create' config lock, aborting.\n"
1472 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1473 $conf->{$key} = $value;
1474 PVE
::LXC
::Config-
>write_config($newid, $conf);
1481 my $newvollist = [];
1483 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1484 die "unexpected state change\n" if $verify_running != $running;
1490 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1492 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1493 my $bwlimit = extract_param
($param, 'bwlimit');
1495 foreach my $opt (keys %$mountpoints) {
1496 my $mp = $mountpoints->{$opt};
1497 my $volid = $mp->{volume
};
1500 if ($fullclone->{$opt}) {
1501 print "create full clone of mountpoint $opt ($volid)\n";
1502 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1503 my $target_storage = $storage // $source_storage;
1504 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1505 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1507 print "create linked clone of mount point $opt ($volid)\n";
1508 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1511 push @$newvollist, $newvolid;
1512 $mp->{volume
} = $newvolid;
1514 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1517 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1518 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1521 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1522 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1523 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1525 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1526 die "Failed to move config to node '$target' - rename failed: $!\n"
1527 if !rename($conffile, $newconffile);
1532 # Unlock the source config in any case:
1533 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1537 # Now cleanup the config & disks:
1540 sleep 1; # some storages like rbd need to wait before release volume - really?
1542 foreach my $volid (@$newvollist) {
1543 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1546 die "clone failed: $err";
1552 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1553 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1557 __PACKAGE__-
>register_method({
1558 name
=> 'resize_vm',
1559 path
=> '{vmid}/resize',
1563 description
=> "Resize a container mount point.",
1565 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1568 additionalProperties
=> 0,
1570 node
=> get_standard_option
('pve-node'),
1571 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1574 description
=> "The disk you want to resize.",
1575 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1579 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1580 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.",
1584 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1592 description
=> "the task ID.",
1597 my $rpcenv = PVE
::RPCEnvironment
::get
();
1599 my $authuser = $rpcenv->get_user();
1601 my $node = extract_param
($param, 'node');
1603 my $vmid = extract_param
($param, 'vmid');
1605 my $digest = extract_param
($param, 'digest');
1607 my $sizestr = extract_param
($param, 'size');
1608 my $ext = ($sizestr =~ s/^\+//);
1609 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1610 die "invalid size string" if !defined($newsize);
1612 die "no options specified\n" if !scalar(keys %$param);
1614 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1616 my $storage_cfg = cfs_read_file
("storage.cfg");
1620 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1621 PVE
::LXC
::Config-
>check_lock($conf);
1623 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1625 my $running = PVE
::LXC
::check_running
($vmid);
1627 my $disk = $param->{disk
};
1628 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1629 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1631 my $volid = $mp->{volume
};
1633 my (undef, undef, $owner, undef, undef, undef, $format) =
1634 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1636 die "can't resize mount point owned by another container ($owner)"
1639 die "can't resize volume: $disk if snapshot exists\n"
1640 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1642 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1644 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1646 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1648 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1649 $newsize += $size if $ext;
1650 $newsize = int($newsize);
1652 die "unable to shrink disk size\n" if $newsize < $size;
1654 return if $size == $newsize;
1656 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1658 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1659 # we pass 0 here (parameter only makes sense for qemu)
1660 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1662 $mp->{size
} = $newsize;
1663 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1665 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1667 if ($format eq 'raw') {
1668 # we need to ensure that the volume is mapped, if not needed this is a NOP
1669 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1670 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1674 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1675 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1676 die "internal error: CT running but mount point not attached to a loop device"
1678 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1680 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1681 # to be visible to it in its namespace.
1682 # To not interfere with the rest of the system we unshare the current mount namespace,
1683 # mount over /tmp and then run resize2fs.
1685 # interestingly we don't need to e2fsck on mounted systems...
1686 my $quoted = PVE
::Tools
::shellquote
($path);
1687 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1689 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1691 warn "Failed to update the container's filesystem: $@\n" if $@;
1694 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1695 PVE
::Tools
::run_command
(['resize2fs', $path]);
1697 warn "Failed to update the container's filesystem: $@\n" if $@;
1699 # always un-map if not running, this is a NOP if not needed
1700 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1705 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1708 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1711 __PACKAGE__-
>register_method({
1712 name
=> 'move_volume',
1713 path
=> '{vmid}/move_volume',
1717 description
=> "Move a rootfs-/mp-volume to a different storage",
1719 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1720 "and 'Datastore.AllocateSpace' permissions on the storage.",
1723 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1724 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1728 additionalProperties
=> 0,
1730 node
=> get_standard_option
('pve-node'),
1731 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1734 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1735 description
=> "Volume which will be moved.",
1737 storage
=> get_standard_option
('pve-storage-id', {
1738 description
=> "Target Storage.",
1739 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1743 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1749 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1754 description
=> "Override I/O bandwidth limit (in KiB/s).",
1758 default => 'clone limit from datacenter or storage config',
1768 my $rpcenv = PVE
::RPCEnvironment
::get
();
1770 my $authuser = $rpcenv->get_user();
1772 my $vmid = extract_param
($param, 'vmid');
1774 my $storage = extract_param
($param, 'storage');
1776 my $mpkey = extract_param
($param, 'volume');
1778 my $lockname = 'disk';
1780 my ($mpdata, $old_volid);
1782 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1783 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1784 PVE
::LXC
::Config-
>check_lock($conf);
1786 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1788 if ($mpkey eq 'rootfs') {
1789 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1790 } elsif ($mpkey =~ m/mp\d+/) {
1791 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1793 die "Can't parse $mpkey\n";
1795 $old_volid = $mpdata->{volume
};
1797 die "you can't move a volume with snapshots and delete the source\n"
1798 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1800 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1802 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1807 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1809 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1810 my $storage_cfg = PVE
::Storage
::config
();
1815 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1816 my $bwlimit = extract_param
($param, 'bwlimit');
1817 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1818 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$source_storage, $storage], $bwlimit);
1819 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf, undef, $movelimit);
1820 $mpdata->{volume
} = $new_volid;
1822 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1823 my $digest = $conf->{digest
};
1824 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1825 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1827 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1829 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1831 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1835 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1836 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1842 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1843 if defined($new_volid);
1849 if ($param->{delete}) {
1851 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1852 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1858 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1863 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1866 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1873 __PACKAGE__-
>register_method({
1874 name
=> 'vm_pending',
1875 path
=> '{vmid}/pending',
1878 description
=> 'Get container configuration, including pending changes.',
1880 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1883 additionalProperties
=> 0,
1885 node
=> get_standard_option
('pve-node'),
1886 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1895 description
=> 'Configuration option name.',
1899 description
=> 'Current value.',
1904 description
=> 'Pending value.',
1909 description
=> "Indicates a pending delete request if present and not 0.",
1921 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
1923 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
1925 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);