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;
251 # error out if a user tries to change from unprivileged to privileged
252 # explicit change is checked here, implicit is checked down below or happening in root-only paths
253 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
254 if ($conf->{unprivileged
} && defined($unprivileged) && !$unprivileged) {
255 raise_perm_exc
("cannot change from unprivileged to privileged without VM.Allocate");
261 my $ostemplate = extract_param
($param, 'ostemplate');
262 my $storage = extract_param
($param, 'storage') // 'local';
264 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, undef, $param, [], $unprivileged);
266 my $storage_cfg = cfs_read_file
("storage.cfg");
269 if ($ostemplate eq '-') {
270 die "pipe requires cli environment\n"
271 if $rpcenv->{type
} ne 'cli';
272 die "pipe can only be used with restore tasks\n"
275 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
277 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
278 $archive = $ostemplate;
282 my $check_and_activate_storage = sub {
285 my $scfg = PVE
::Storage
::storage_check_enabled
($storage_cfg, $sid, $node);
287 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
288 if !$scfg->{content
}->{rootdir
};
290 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
292 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
293 $used_storages{$sid} = 1;
298 my $is_root = $authuser eq 'root@pam';
300 my $no_disk_param = {};
302 my $storage_only_mode = 1;
303 foreach my $opt (keys %$param) {
304 my $value = $param->{$opt};
305 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
306 # allow to use simple numbers (add default storage in that case)
307 if ($value =~ m/^\d+(\.\d+)?$/) {
308 $mp_param->{$opt} = "$storage:$value";
310 $mp_param->{$opt} = $value;
312 $storage_only_mode = 0;
313 } elsif ($opt =~ m/^unused\d+$/) {
314 warn "ignoring '$opt', cannot create/restore with unused volume\n";
315 delete $param->{$opt};
317 $no_disk_param->{$opt} = $value;
321 die "mount points configured, but 'rootfs' not set - aborting\n"
322 if !$storage_only_mode && !defined($mp_param->{rootfs
});
324 # check storage access, activate storage
325 my $delayed_mp_param = {};
326 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
327 my ($ms, $mountpoint) = @_;
329 my $volid = $mountpoint->{volume
};
330 my $mp = $mountpoint->{mp
};
332 if ($mountpoint->{type
} ne 'volume') { # bind or device
333 die "Only root can pass arbitrary filesystem paths.\n"
336 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
337 &$check_and_activate_storage($sid);
341 # check/activate default storage
342 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
344 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
346 $conf->{unprivileged
} = 1 if $unprivileged;
348 my $emsg = $restore ?
"unable to restore CT $vmid -" : "unable to create CT $vmid -";
350 eval { PVE
::LXC
::Config-
>create_and_lock_config($vmid, $force) };
351 die "$emsg $@" if $@;
356 my $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
360 my $orig_mp_param; # only used if $restore
362 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
363 if ($archive ne '-') {
365 print "recovering backed-up configuration from '$archive'\n";
366 ($orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
368 $was_template = delete $orig_conf->{template
};
370 # When we're root call 'restore_configuration' with restricted=0,
371 # causing it to restore the raw lxc entries, among which there may be
372 # 'lxc.idmap' entries. We need to make sure that the extracted contents
373 # of the container match up with the restored configuration afterwards:
374 $conf->{lxc
} = $orig_conf->{lxc
} if $is_root;
376 $conf->{unprivileged
} = $orig_conf->{unprivileged
}
377 if !defined($unprivileged) && defined($orig_conf->{unprivileged
});
379 # implicit privileged change is checked here
380 if ($old_conf->{unprivileged
} && !$conf->{unprivileged
}) {
381 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Allocate']);
385 if ($storage_only_mode) {
387 if (!defined($orig_mp_param)) {
388 print "recovering backed-up configuration from '$archive'\n";
389 (undef, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
391 $mp_param = $orig_mp_param;
392 die "rootfs configuration could not be recovered, please check and specify manually!\n"
393 if !defined($mp_param->{rootfs
});
394 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
395 my ($ms, $mountpoint) = @_;
396 my $type = $mountpoint->{type
};
397 if ($type eq 'volume') {
398 die "unable to detect disk size - please specify $ms (size)\n"
399 if !defined($mountpoint->{size
});
400 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
401 delete $mountpoint->{size
};
402 $mountpoint->{volume
} = "$storage:$disksize";
403 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
405 my $type = $mountpoint->{type
};
406 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
407 if ($ms eq 'rootfs');
408 die "restoring '$ms' to $type mount is only possible for root\n"
411 if ($mountpoint->{backup
}) {
412 warn "WARNING - unsupported configuration!\n";
413 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
414 warn "mount point configuration will be restored after archive extraction!\n";
415 warn "contained files will be restored to wrong directory!\n";
417 delete $mp_param->{$ms}; # actually delay bind/dev mps
418 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
422 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
426 die "$emsg $@" if $@;
428 # up until here we did not modify the container, besides the lock
433 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
435 # we always have the 'create' lock so check for more than 1 entry
436 if (scalar(keys %$old_conf) > 1) {
437 # destroy old container volumes
438 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, { lock => 'create' });
442 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
443 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
444 print "restoring '$archive' now..\n"
445 if $restore && $archive ne '-';
446 PVE
::LXC
::Create
::restore_archive
($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
449 print "merging backed-up and given configuration..\n";
450 PVE
::LXC
::Create
::restore_configuration
($vmid, $storage_cfg, $archive, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
451 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
452 $lxc_setup->template_fixup($conf);
454 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
455 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
456 $lxc_setup->post_create_hook($password, $ssh_keys);
460 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
461 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
464 $conf->{hostname
} ||= "CT$vmid";
465 $conf->{memory
} ||= 512;
466 $conf->{swap
} //= 512;
467 foreach my $mp (keys %$delayed_mp_param) {
468 $conf->{$mp} = $delayed_mp_param->{$mp};
470 # If the template flag was set, we try to convert again to template after restore
472 print STDERR
"Convert restored container to template...\n";
473 PVE
::LXC
::template_create
($vmid, $conf);
474 $conf->{template
} = 1;
476 PVE
::LXC
::Config-
>write_config($vmid, $conf);
479 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
480 eval { PVE
::LXC
::Config-
>destroy_config($vmid) };
484 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
486 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
487 if $start_after_create;
490 my $workername = $restore ?
'vzrestore' : 'vzcreate';
493 PVE
::LXC
::Config-
>lock_config($vmid, $code);
496 # if we aborted before changing the container, we must remove the create lock
498 PVE
::LXC
::Config-
>remove_lock($vmid, 'create');
504 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
507 __PACKAGE__-
>register_method({
512 description
=> "Directory index",
517 additionalProperties
=> 0,
519 node
=> get_standard_option
('pve-node'),
520 vmid
=> get_standard_option
('pve-vmid'),
528 subdir
=> { type
=> 'string' },
531 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
537 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
540 { subdir
=> 'config' },
541 { subdir
=> 'pending' },
542 { subdir
=> 'status' },
543 { subdir
=> 'vncproxy' },
544 { subdir
=> 'termproxy' },
545 { subdir
=> 'vncwebsocket' },
546 { subdir
=> 'spiceproxy' },
547 { subdir
=> 'migrate' },
548 { subdir
=> 'clone' },
549 # { subdir => 'initlog' },
551 { subdir
=> 'rrddata' },
552 { subdir
=> 'firewall' },
553 { subdir
=> 'snapshot' },
554 { subdir
=> 'resize' },
561 __PACKAGE__-
>register_method({
563 path
=> '{vmid}/rrd',
565 protected
=> 1, # fixme: can we avoid that?
567 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
569 description
=> "Read VM RRD statistics (returns PNG)",
571 additionalProperties
=> 0,
573 node
=> get_standard_option
('pve-node'),
574 vmid
=> get_standard_option
('pve-vmid'),
576 description
=> "Specify the time frame you are interested in.",
578 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
581 description
=> "The list of datasources you want to display.",
582 type
=> 'string', format
=> 'pve-configid-list',
585 description
=> "The RRD consolidation function",
587 enum
=> [ 'AVERAGE', 'MAX' ],
595 filename
=> { type
=> 'string' },
601 return PVE
::RRD
::create_rrd_graph
(
602 "pve2-vm/$param->{vmid}", $param->{timeframe
},
603 $param->{ds
}, $param->{cf
});
607 __PACKAGE__-
>register_method({
609 path
=> '{vmid}/rrddata',
611 protected
=> 1, # fixme: can we avoid that?
613 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
615 description
=> "Read VM RRD statistics",
617 additionalProperties
=> 0,
619 node
=> get_standard_option
('pve-node'),
620 vmid
=> get_standard_option
('pve-vmid'),
622 description
=> "Specify the time frame you are interested in.",
624 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
627 description
=> "The RRD consolidation function",
629 enum
=> [ 'AVERAGE', 'MAX' ],
644 return PVE
::RRD
::create_rrd_data
(
645 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
648 __PACKAGE__-
>register_method({
649 name
=> 'destroy_vm',
654 description
=> "Destroy the container (also delete all uses files).",
656 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
659 additionalProperties
=> 0,
661 node
=> get_standard_option
('pve-node'),
662 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
665 description
=> "Force destroy, even if running.",
671 description
=> "Remove container from all related configurations."
672 ." For example, backup jobs, replication jobs or HA."
673 ." Related ACLs and Firewall entries will *always* be removed.",
677 'destroy-unreferenced-disks' => {
679 description
=> "If set, destroy additionally all disks with the VMID from all"
680 ." enabled storages which are not referenced in the config.",
691 my $rpcenv = PVE
::RPCEnvironment
::get
();
692 my $authuser = $rpcenv->get_user();
693 my $vmid = $param->{vmid
};
695 # test if container exists
697 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
698 my $early_checks = sub {
700 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
701 PVE
::LXC
::Config-
>check_lock($conf);
703 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("ct:$vmid");
705 if (!$param->{purge
}) {
706 die "unable to remove CT $vmid - used in HA resources and purge parameter not set.\n"
709 # do not allow destroy if there are replication jobs without purge
710 my $repl_conf = PVE
::ReplicationConfig-
>new();
711 $repl_conf->check_for_existing_jobs($vmid);
717 $early_checks->($conf);
719 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
720 die $running_error_msg if !$param->{force
} && PVE
::LXC
::check_running
($vmid); # check early
723 # reload config after lock
724 $conf = PVE
::LXC
::Config-
>load_config($vmid);
725 my $ha_managed = $early_checks->($conf);
727 if (PVE
::LXC
::check_running
($vmid)) {
728 die $running_error_msg if !$param->{force
};
729 warn "forced to stop CT $vmid before destroying!\n";
731 PVE
::LXC
::vm_stop
($vmid, 1);
733 run_command
(['ha-manager', 'crm-command', 'stop', "ct:$vmid", '120']);
737 my $storage_cfg = cfs_read_file
("storage.cfg");
738 PVE
::LXC
::destroy_lxc_container
(
742 { lock => 'destroyed' },
743 $param->{'destroy-unreferenced-disks'},
746 PVE
::AccessControl
::remove_vm_access
($vmid);
747 PVE
::Firewall
::remove_vmfw_conf
($vmid);
748 if ($param->{purge
}) {
749 print "purging CT $vmid from related configurations..\n";
750 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
751 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
754 PVE
::HA
::Config
::delete_service_from_config
("ct:$vmid");
755 print "NOTE: removed CT $vmid from HA resource configuration.\n";
759 # only now remove the zombie config, else we can have reuse race
760 PVE
::LXC
::Config-
>destroy_config($vmid);
763 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
765 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
770 __PACKAGE__-
>register_method ({
772 path
=> '{vmid}/vncproxy',
776 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
778 description
=> "Creates a TCP VNC proxy connections.",
780 additionalProperties
=> 0,
782 node
=> get_standard_option
('pve-node'),
783 vmid
=> get_standard_option
('pve-vmid'),
787 description
=> "use websocket instead of standard VNC.",
791 description
=> "sets the width of the console in pixels.",
798 description
=> "sets the height of the console in pixels.",
806 additionalProperties
=> 0,
808 user
=> { type
=> 'string' },
809 ticket
=> { type
=> 'string' },
810 cert
=> { type
=> 'string' },
811 port
=> { type
=> 'integer' },
812 upid
=> { type
=> 'string' },
818 my $rpcenv = PVE
::RPCEnvironment
::get
();
820 my $authuser = $rpcenv->get_user();
822 my $vmid = $param->{vmid
};
823 my $node = $param->{node
};
825 my $authpath = "/vms/$vmid";
827 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
829 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
832 my ($remip, $family);
834 if ($node ne PVE
::INotify
::nodename
()) {
835 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
837 $family = PVE
::Tools
::get_host_address_family
($node);
840 my $port = PVE
::Tools
::next_vnc_port
($family);
842 # NOTE: vncterm VNC traffic is already TLS encrypted,
843 # so we select the fastest chipher here (or 'none'?)
844 my $remcmd = $remip ?
845 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
847 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
848 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
850 my $shcmd = [ '/usr/bin/dtach', '-A',
851 "/var/run/dtach/vzctlconsole$vmid",
852 '-r', 'winch', '-z', @$concmd];
857 syslog
('info', "starting lxc vnc proxy $upid\n");
861 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
862 '-timeout', $timeout, '-authpath', $authpath,
863 '-perm', 'VM.Console'];
865 if ($param->{width
}) {
866 push @$cmd, '-width', $param->{width
};
869 if ($param->{height
}) {
870 push @$cmd, '-height', $param->{height
};
873 if ($param->{websocket
}) {
874 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
875 push @$cmd, '-notls', '-listen', 'localhost';
878 push @$cmd, '-c', @$remcmd, @$shcmd;
880 run_command
($cmd, keeplocale
=> 1);
885 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
887 PVE
::Tools
::wait_for_vnc_port
($port);
898 __PACKAGE__-
>register_method ({
900 path
=> '{vmid}/termproxy',
904 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
906 description
=> "Creates a TCP proxy connection.",
908 additionalProperties
=> 0,
910 node
=> get_standard_option
('pve-node'),
911 vmid
=> get_standard_option
('pve-vmid'),
915 additionalProperties
=> 0,
917 user
=> { type
=> 'string' },
918 ticket
=> { type
=> 'string' },
919 port
=> { type
=> 'integer' },
920 upid
=> { type
=> 'string' },
926 my $rpcenv = PVE
::RPCEnvironment
::get
();
928 my $authuser = $rpcenv->get_user();
930 my $vmid = $param->{vmid
};
931 my $node = $param->{node
};
933 my $authpath = "/vms/$vmid";
935 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
937 my ($remip, $family);
939 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
940 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
942 $family = PVE
::Tools
::get_host_address_family
($node);
945 my $port = PVE
::Tools
::next_vnc_port
($family);
947 my $remcmd = $remip ?
948 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
950 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
951 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
953 my $shcmd = [ '/usr/bin/dtach', '-A',
954 "/var/run/dtach/vzctlconsole$vmid",
955 '-r', 'winch', '-z', @$concmd];
960 syslog
('info', "starting lxc termproxy $upid\n");
962 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
963 '--perm', 'VM.Console', '--'];
964 push @$cmd, @$remcmd, @$shcmd;
966 PVE
::Tools
::run_command
($cmd);
969 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
971 PVE
::Tools
::wait_for_vnc_port
($port);
981 __PACKAGE__-
>register_method({
982 name
=> 'vncwebsocket',
983 path
=> '{vmid}/vncwebsocket',
986 description
=> "You also need to pass a valid ticket (vncticket).",
987 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
989 description
=> "Opens a weksocket for VNC traffic.",
991 additionalProperties
=> 0,
993 node
=> get_standard_option
('pve-node'),
994 vmid
=> get_standard_option
('pve-vmid'),
996 description
=> "Ticket from previous call to vncproxy.",
1001 description
=> "Port number returned by previous vncproxy call.",
1011 port
=> { type
=> 'string' },
1017 my $rpcenv = PVE
::RPCEnvironment
::get
();
1019 my $authuser = $rpcenv->get_user();
1021 my $authpath = "/vms/$param->{vmid}";
1023 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1025 my $port = $param->{port
};
1027 return { port
=> $port };
1030 __PACKAGE__-
>register_method ({
1031 name
=> 'spiceproxy',
1032 path
=> '{vmid}/spiceproxy',
1037 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1039 description
=> "Returns a SPICE configuration to connect to the CT.",
1041 additionalProperties
=> 0,
1043 node
=> get_standard_option
('pve-node'),
1044 vmid
=> get_standard_option
('pve-vmid'),
1045 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1048 returns
=> get_standard_option
('remote-viewer-config'),
1052 my $vmid = $param->{vmid
};
1053 my $node = $param->{node
};
1054 my $proxy = $param->{proxy
};
1056 my $authpath = "/vms/$vmid";
1057 my $permissions = 'VM.Console';
1059 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1061 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
1063 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
1065 my $shcmd = ['/usr/bin/dtach', '-A',
1066 "/var/run/dtach/vzctlconsole$vmid",
1067 '-r', 'winch', '-z', @$concmd];
1069 my $title = "CT $vmid";
1071 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1075 __PACKAGE__-
>register_method({
1076 name
=> 'migrate_vm',
1077 path
=> '{vmid}/migrate',
1081 description
=> "Migrate the container to another node. Creates a new migration task.",
1083 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1086 additionalProperties
=> 0,
1088 node
=> get_standard_option
('pve-node'),
1089 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1090 target
=> get_standard_option
('pve-node', {
1091 description
=> "Target node.",
1092 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1096 description
=> "Use online/live migration.",
1101 description
=> "Use restart migration",
1106 description
=> "Timeout in seconds for shutdown for restart migration",
1111 description
=> "Override I/O bandwidth limit (in KiB/s).",
1115 default => 'migrate limit from datacenter or storage config',
1121 description
=> "the task ID.",
1126 my $rpcenv = PVE
::RPCEnvironment
::get
();
1128 my $authuser = $rpcenv->get_user();
1130 my $target = extract_param
($param, 'target');
1132 my $localnode = PVE
::INotify
::nodename
();
1133 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1135 PVE
::Cluster
::check_cfs_quorum
();
1137 PVE
::Cluster
::check_node_exists
($target);
1139 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1141 my $vmid = extract_param
($param, 'vmid');
1144 PVE
::LXC
::Config-
>load_config($vmid);
1146 # try to detect errors early
1147 if (PVE
::LXC
::check_running
($vmid)) {
1148 die "can't migrate running container without --online or --restart\n"
1149 if !$param->{online
} && !$param->{restart
};
1152 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1157 my $service = "ct:$vmid";
1159 my $cmd = ['ha-manager', 'migrate', $service, $target];
1161 print "Requesting HA migration for CT $vmid to node $target\n";
1163 PVE
::Tools
::run_command
($cmd);
1168 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1173 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1177 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1180 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1184 __PACKAGE__-
>register_method({
1185 name
=> 'vm_feature',
1186 path
=> '{vmid}/feature',
1190 description
=> "Check if feature for virtual machine is available.",
1192 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1195 additionalProperties
=> 0,
1197 node
=> get_standard_option
('pve-node'),
1198 vmid
=> get_standard_option
('pve-vmid'),
1200 description
=> "Feature to check.",
1202 enum
=> [ 'snapshot', 'clone', 'copy' ],
1204 snapname
=> get_standard_option
('pve-snapshot-name', {
1212 hasFeature
=> { type
=> 'boolean' },
1215 #items => { type => 'string' },
1222 my $node = extract_param
($param, 'node');
1224 my $vmid = extract_param
($param, 'vmid');
1226 my $snapname = extract_param
($param, 'snapname');
1228 my $feature = extract_param
($param, 'feature');
1230 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1233 my $snap = $conf->{snapshots
}->{$snapname};
1234 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1237 my $storage_cfg = PVE
::Storage
::config
();
1238 #Maybe include later
1239 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1240 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1243 hasFeature
=> $hasFeature,
1244 #nodes => [ keys %$nodelist ],
1248 __PACKAGE__-
>register_method({
1250 path
=> '{vmid}/template',
1254 description
=> "Create a Template.",
1256 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1257 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1260 additionalProperties
=> 0,
1262 node
=> get_standard_option
('pve-node'),
1263 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1266 returns
=> { type
=> 'null'},
1270 my $rpcenv = PVE
::RPCEnvironment
::get
();
1272 my $authuser = $rpcenv->get_user();
1274 my $node = extract_param
($param, 'node');
1276 my $vmid = extract_param
($param, 'vmid');
1278 my $updatefn = sub {
1280 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1281 PVE
::LXC
::Config-
>check_lock($conf);
1283 die "unable to create template, because CT contains snapshots\n"
1284 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1286 die "you can't convert a template to a template\n"
1287 if PVE
::LXC
::Config-
>is_template($conf);
1289 die "you can't convert a CT to template if the CT is running\n"
1290 if PVE
::LXC
::check_running
($vmid);
1293 PVE
::LXC
::template_create
($vmid, $conf);
1295 $conf->{template
} = 1;
1297 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1298 # and remove lxc config
1299 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1302 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1305 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1310 __PACKAGE__-
>register_method({
1312 path
=> '{vmid}/clone',
1316 description
=> "Create a container clone/copy",
1318 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1319 "and 'VM.Allocate' permissions " .
1320 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1321 "'Datastore.AllocateSpace' on any used storage.",
1324 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1326 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1327 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1332 additionalProperties
=> 0,
1334 node
=> get_standard_option
('pve-node'),
1335 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1336 newid
=> get_standard_option
('pve-vmid', {
1337 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1338 description
=> 'VMID for the clone.' }),
1341 type
=> 'string', format
=> 'dns-name',
1342 description
=> "Set a hostname for the new CT.",
1347 description
=> "Description for the new CT.",
1351 type
=> 'string', format
=> 'pve-poolid',
1352 description
=> "Add the new CT to the specified pool.",
1354 snapname
=> get_standard_option
('pve-snapshot-name', {
1357 storage
=> get_standard_option
('pve-storage-id', {
1358 description
=> "Target storage for full clone.",
1364 description
=> "Create a full copy of all disks. This is always done when " .
1365 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1367 target
=> get_standard_option
('pve-node', {
1368 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1372 description
=> "Override I/O bandwidth limit (in KiB/s).",
1376 default => 'clone limit from datacenter or storage config',
1386 my $rpcenv = PVE
::RPCEnvironment
::get
();
1387 my $authuser = $rpcenv->get_user();
1389 my $node = extract_param
($param, 'node');
1390 my $vmid = extract_param
($param, 'vmid');
1391 my $newid = extract_param
($param, 'newid');
1392 my $pool = extract_param
($param, 'pool');
1393 if (defined($pool)) {
1394 $rpcenv->check_pool_exist($pool);
1396 my $snapname = extract_param
($param, 'snapname');
1397 my $storage = extract_param
($param, 'storage');
1398 my $target = extract_param
($param, 'target');
1399 my $localnode = PVE
::INotify
::nodename
();
1401 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1403 PVE
::Cluster
::check_node_exists
($target) if $target;
1405 my $storecfg = PVE
::Storage
::config
();
1408 # check if storage is enabled on local node
1409 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1411 # check if storage is available on target node
1412 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
1413 # clone only works if target storage is shared
1414 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1415 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1419 PVE
::Cluster
::check_cfs_quorum
();
1422 my $mountpoints = {};
1427 PVE
::LXC
::Config-
>create_and_lock_config($newid, 0);
1428 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1430 my $lock_and_reload = sub {
1431 my ($vmid, $code) = @_;
1432 return PVE
::LXC
::Config-
>lock_config($vmid, sub {
1433 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1434 die "Lost 'create' config lock, aborting.\n"
1435 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1437 return $code->($conf);
1441 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1443 $running = PVE
::LXC
::check_running
($vmid) || 0;
1445 my $full = extract_param
($param, 'full');
1446 if (!defined($full)) {
1447 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1451 die "parameter 'storage' not allowed for linked clones\n"
1452 if defined($storage) && !$full;
1454 die "snapshot '$snapname' does not exist\n"
1455 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1457 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1460 for my $opt (sort keys %$src_conf) {
1461 next if $opt =~ m/^unused\d+$/;
1463 my $value = $src_conf->{$opt};
1465 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1466 my $mp = PVE
::LXC
::Config-
>parse_volume($opt, $value);
1468 if ($mp->{type
} eq 'volume') {
1469 my $volid = $mp->{volume
};
1471 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1472 $sid = $storage if defined($storage);
1473 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1474 if (!$scfg->{shared
}) {
1476 warn "found non-shared volume: $volid\n" if $target;
1479 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1482 die "Cannot do full clones on a running container without snapshots\n"
1483 if $running && !defined($snapname);
1484 $fullclone->{$opt} = 1;
1486 # not full means clone instead of copy
1487 die "Linked clone feature for '$volid' is not available\n"
1488 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
1491 $mountpoints->{$opt} = $mp;
1492 push @$vollist, $volid;
1495 # TODO: allow bind mounts?
1496 die "unable to clone mountpoint '$opt' (type $mp->{type})\n";
1498 } elsif ($opt =~ m/^net(\d+)$/) {
1499 # always change MAC! address
1500 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1501 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1502 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1503 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1505 # copy everything else
1506 $newconf->{$opt} = $value;
1509 die "can't clone CT to node '$target' (CT uses local storage)\n"
1510 if $target && !$sharedvm;
1512 # Replace the 'disk' lock with a 'create' lock.
1513 $newconf->{lock} = 'create';
1515 # delete all snapshot related config options
1516 delete $newconf->@{qw(snapshots parent snaptime snapstate)};
1518 delete $newconf->{pending
};
1519 delete $newconf->{template
};
1521 $newconf->{hostname
} = $param->{hostname
} if $param->{hostname
};
1522 $newconf->{description
} = $param->{description
} if $param->{description
};
1524 $lock_and_reload->($newid, sub {
1525 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1529 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1530 warn "Failed to remove source CT config lock - $@\n" if $@;
1533 $lock_and_reload->($newid, sub {
1534 PVE
::LXC
::Config-
>destroy_config($newid);
1535 PVE
::Firewall
::remove_vmfw_conf
($newid);
1538 warn "Failed to remove target CT config - $@\n" if $@;
1543 my $update_conf = sub {
1544 my ($key, $value) = @_;
1545 return $lock_and_reload->($newid, sub {
1547 $conf->{$key} = $value;
1548 PVE
::LXC
::Config-
>write_config($newid, $conf);
1555 my $newvollist = [];
1557 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1558 die "unexpected state change\n" if $verify_running != $running;
1564 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1566 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1567 my $bwlimit = extract_param
($param, 'bwlimit');
1569 foreach my $opt (keys %$mountpoints) {
1570 my $mp = $mountpoints->{$opt};
1571 my $volid = $mp->{volume
};
1574 if ($fullclone->{$opt}) {
1575 print "create full clone of mountpoint $opt ($volid)\n";
1576 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1577 my $target_storage = $storage // $source_storage;
1578 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1579 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1581 print "create linked clone of mount point $opt ($volid)\n";
1582 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1585 push @$newvollist, $newvolid;
1586 $mp->{volume
} = $newvolid;
1588 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1591 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1593 $lock_and_reload->($newid, sub {
1595 my $rootdir = PVE
::LXC
::mount_all
($newid, $storecfg, $conf, 1);
1597 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1598 $lxc_setup->post_clone_hook($conf);
1601 eval { PVE
::LXC
::umount_all
($newid, $storecfg, $conf, 1); };
1611 # Unlock the source config in any case:
1612 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1616 # Now cleanup the config & disks:
1617 sleep 1; # some storages like rbd need to wait before release volume - really?
1619 foreach my $volid (@$newvollist) {
1620 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1625 $lock_and_reload->($newid, sub {
1626 PVE
::LXC
::Config-
>destroy_config($newid);
1627 PVE
::Firewall
::remove_vmfw_conf
($newid);
1630 warn "Failed to remove target CT config - $@\n" if $@;
1632 die "clone failed: $err";
1635 $lock_and_reload->($newid, sub {
1636 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1639 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1640 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1641 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1643 PVE
::LXC
::Config-
>move_config_to_node($newid, $target);
1650 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1654 __PACKAGE__-
>register_method({
1655 name
=> 'resize_vm',
1656 path
=> '{vmid}/resize',
1660 description
=> "Resize a container mount point.",
1662 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1665 additionalProperties
=> 0,
1667 node
=> get_standard_option
('pve-node'),
1668 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1671 description
=> "The disk you want to resize.",
1672 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys()],
1676 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1677 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.",
1681 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1689 description
=> "the task ID.",
1694 my $rpcenv = PVE
::RPCEnvironment
::get
();
1696 my $authuser = $rpcenv->get_user();
1698 my $node = extract_param
($param, 'node');
1700 my $vmid = extract_param
($param, 'vmid');
1702 my $digest = extract_param
($param, 'digest');
1704 my $sizestr = extract_param
($param, 'size');
1705 my $ext = ($sizestr =~ s/^\+//);
1706 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1707 die "invalid size string" if !defined($newsize);
1709 die "no options specified\n" if !scalar(keys %$param);
1711 my $storage_cfg = cfs_read_file
("storage.cfg");
1715 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1716 PVE
::LXC
::Config-
>check_lock($conf);
1718 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $conf, $param, [], $conf->{unprivileged
});
1720 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1722 my $running = PVE
::LXC
::check_running
($vmid);
1724 my $disk = $param->{disk
};
1725 my $mp = PVE
::LXC
::Config-
>parse_volume($disk, $conf->{$disk});
1727 my $volid = $mp->{volume
};
1729 my (undef, undef, $owner, undef, undef, undef, $format) =
1730 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1732 die "can't resize mount point owned by another container ($owner)"
1735 die "can't resize volume: $disk if snapshot exists\n"
1736 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1738 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1740 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1742 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1744 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1746 die "Could not determine current size of volume '$volid'\n" if !defined($size);
1748 $newsize += $size if $ext;
1749 $newsize = int($newsize);
1751 die "unable to shrink disk size\n" if $newsize < $size;
1753 die "disk is already at specified size\n" if $size == $newsize;
1755 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1757 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1758 # we pass 0 here (parameter only makes sense for qemu)
1759 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1761 $mp->{size
} = $newsize;
1762 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1764 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1766 if ($format eq 'raw') {
1767 # we need to ensure that the volume is mapped, if not needed this is a NOP
1768 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1769 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1773 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1774 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1775 die "internal error: CT running but mount point not attached to a loop device"
1777 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1779 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1780 # to be visible to it in its namespace.
1781 # To not interfere with the rest of the system we unshare the current mount namespace,
1782 # mount over /tmp and then run resize2fs.
1784 # interestingly we don't need to e2fsck on mounted systems...
1785 my $quoted = PVE
::Tools
::shellquote
($path);
1786 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1788 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1790 warn "Failed to update the container's filesystem: $@\n" if $@;
1793 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1794 PVE
::Tools
::run_command
(['resize2fs', $path]);
1796 warn "Failed to update the container's filesystem: $@\n" if $@;
1798 # always un-map if not running, this is a NOP if not needed
1799 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1804 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1807 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1810 __PACKAGE__-
>register_method({
1811 name
=> 'move_volume',
1812 path
=> '{vmid}/move_volume',
1816 description
=> "Move a rootfs-/mp-volume to a different storage or to a different container.",
1818 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1819 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
1820 "a volume to another container, you need the permissions on the ".
1821 "target container as well.",
1824 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1825 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1829 additionalProperties
=> 0,
1831 node
=> get_standard_option
('pve-node'),
1832 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1833 'target-vmid' => get_standard_option
('pve-vmid', {
1834 completion
=> \
&PVE
::LXC
::complete_ctid
,
1839 #TODO: check how to handle unused mount points as the mp parameter is not configured
1840 enum
=> [ PVE
::LXC
::Config-
>valid_volume_keys_with_unused() ],
1841 description
=> "Volume which will be moved.",
1843 storage
=> get_standard_option
('pve-storage-id', {
1844 description
=> "Target Storage.",
1845 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1850 description
=> "Delete the original volume after successful copy. By default the " .
1851 "original is kept as an unused volume entry.",
1857 description
=> 'Prevent changes if current configuration file has different SHA1 " .
1858 "digest. This can be used to prevent concurrent modifications.',
1863 description
=> "Override I/O bandwidth limit (in KiB/s).",
1867 default => 'clone limit from datacenter or storage config',
1869 'target-volume' => {
1871 description
=> "The config key the volume will be moved to. Default is the " .
1872 "source volume key.",
1873 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys_with_unused()],
1876 'target-digest' => {
1878 description
=> 'Prevent changes if current configuration file of the target " .
1879 "container has a different SHA1 digest. This can be used to prevent " .
1880 "concurrent modifications.',
1892 my $rpcenv = PVE
::RPCEnvironment
::get
();
1894 my $authuser = $rpcenv->get_user();
1896 my $vmid = extract_param
($param, 'vmid');
1898 my $target_vmid = extract_param
($param, 'target-vmid');
1900 my $storage = extract_param
($param, 'storage');
1902 my $mpkey = extract_param
($param, 'volume');
1904 my $target_mpkey = extract_param
($param, 'target-volume') // $mpkey;
1906 my $digest = extract_param
($param, 'digest');
1908 my $target_digest = extract_param
($param, 'target-digest');
1910 my $lockname = 'disk';
1912 my ($mpdata, $old_volid);
1914 die "either set storage or target-vmid, but not both\n"
1915 if $storage && $target_vmid;
1917 my $storecfg = PVE
::Storage
::config
();
1920 my $move_to_storage_checks = sub {
1921 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1922 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1923 PVE
::LXC
::Config-
>check_lock($conf);
1925 die "cannot move volumes of a running container\n"
1926 if PVE
::LXC
::check_running
($vmid);
1928 if ($mpkey =~ m/^unused\d+$/) {
1929 die "cannot move volume '$mpkey', only configured volumes can be moved to ".
1930 "another storage\n";
1933 $mpdata = PVE
::LXC
::Config-
>parse_volume($mpkey, $conf->{$mpkey});
1934 $old_volid = $mpdata->{volume
};
1936 die "you can't move a volume with snapshots and delete the source\n"
1937 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1939 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1941 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1945 my $storage_realcmd = sub {
1947 PVE
::Cluster
::log_msg
(
1950 "move volume CT $vmid: move --volume $mpkey --storage $storage"
1953 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1954 my $storage_cfg = PVE
::Storage
::config
();
1959 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1960 my $bwlimit = extract_param
($param, 'bwlimit');
1961 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1962 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
1964 [$source_storage, $storage],
1967 $new_volid = PVE
::LXC
::copy_volume
(
1976 if (PVE
::LXC
::Config-
>is_template($conf)) {
1977 PVE
::Storage
::activate_volumes
($storage_cfg, [ $new_volid ]);
1978 my $template_volid = PVE
::Storage
::vdisk_create_base
($storage_cfg, $new_volid);
1979 $mpdata->{volume
} = $template_volid;
1981 $mpdata->{volume
} = $new_volid;
1984 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1985 my $digest = $conf->{digest
};
1986 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1987 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1989 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint(
1994 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1996 PVE
::LXC
::Config-
>write_config($vmid, $conf);
2000 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2001 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
2007 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
2008 if defined($new_volid);
2014 if ($param->{delete}) {
2016 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
2017 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
2021 PVE
::LXC
::Config-
>lock_config($vmid, sub {
2022 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
2023 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid);
2024 PVE
::LXC
::Config-
>write_config($vmid, $conf);
2030 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
2035 my $load_and_check_reassign_configs = sub {
2036 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
2038 die "Cannot move to/from 'rootfs'\n" if $mpkey eq "rootfs" || $target_mpkey eq "rootfs";
2040 if ($mpkey =~ m/^unused\d+$/ && $target_mpkey !~ m/^unused\d+$/) {
2041 die "Moving an unused volume to a used one is not possible\n";
2043 die "could not find CT ${vmid}\n" if !exists($vmlist->{$vmid});
2045 die "Both containers need to be on the same node ($vmlist->{$vmid}->{node}) ".
2046 "but target continer is on $vmlist->{$target_vmid}->{node}.\n"
2047 if $vmlist->{$vmid}->{node
} ne $vmlist->{$target_vmid}->{node
};
2049 my $source_conf = PVE
::LXC
::Config-
>load_config($vmid);
2050 PVE
::LXC
::Config-
>check_lock($source_conf);
2051 my $target_conf = PVE
::LXC
::Config-
>load_config($target_vmid);
2052 PVE
::LXC
::Config-
>check_lock($target_conf);
2054 die "Can't move volumes from or to template VMs\n"
2055 if ($source_conf->{template
} || $target_conf->{template
});
2058 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
2059 die "Container ${vmid}: $@" if $@;
2062 if ($target_digest) {
2063 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
2064 die "Container ${target_vmid}: $@" if $@;
2067 die "volume '${mpkey}' for container '$vmid' does not exist\n"
2068 if !defined($source_conf->{$mpkey});
2070 die "Target volume key '${target_mpkey}' is already in use for container '$target_vmid'\n"
2071 if exists $target_conf->{$target_mpkey};
2073 my $drive = PVE
::LXC
::Config-
>parse_volume(
2075 $source_conf->{$mpkey},
2078 $source_volid = $drive->{volume
};
2080 die "Volume '${mpkey}' has no associated image\n"
2082 die "Cannot move volume used by a snapshot to another container\n"
2083 if PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($source_conf, $source_volid);
2084 die "Storage does not support moving of this disk to another container\n"
2085 if !PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid);
2086 die "Cannot move a bindmound or device mount to another container\n"
2087 if $drive->{type
} ne "volume";
2088 die "Cannot move volume to another container while the source container is running\n"
2089 if PVE
::LXC
::check_running
($vmid) && $mpkey !~ m/^unused\d+$/;
2091 my $repl_conf = PVE
::ReplicationConfig-
>new();
2092 my $is_replicated = $repl_conf->check_for_existing_jobs($target_vmid, 1);
2093 my ($storeid, undef) = PVE
::Storage
::parse_volume_id
($source_volid);
2094 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
2097 && !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format)
2099 die "Cannot move volume to a replicated container. Storage " .
2100 "does not support replication!\n";
2102 return ($source_conf, $target_conf);
2107 print STDERR
"$msg\n";
2110 my $volume_reassignfn = sub {
2111 return PVE
::LXC
::Config-
>lock_config($vmid, sub {
2112 return PVE
::LXC
::Config-
>lock_config($target_vmid, sub {
2113 my ($source_conf, $target_conf) = &$load_and_check_reassign_configs();
2115 my $target_unused = $target_mpkey =~ m/^unused\d+$/;
2117 my $drive_param = PVE
::LXC
::Config-
>parse_volume(
2119 $source_conf->{$mpkey},
2122 print "moving volume '$mpkey' from container '$vmid' to '$target_vmid'\n";
2123 my ($storage, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
2125 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
2127 my $new_volid = PVE
::Storage
::rename_volume
(
2133 $drive_param->{volume
} = $new_volid;
2135 delete $source_conf->{$mpkey};
2136 print "removing volume '${mpkey}' from container '${vmid}' config\n";
2137 PVE
::LXC
::Config-
>write_config($vmid, $source_conf);
2141 if ($target_unused) {
2142 $drive_string = $new_volid;
2144 $drive_string = PVE
::LXC
::Config-
>print_volume($target_mpkey, $drive_param);
2147 if ($target_unused) {
2148 $target_conf->{$target_mpkey} = $drive_string;
2150 my $running = PVE
::LXC
::check_running
($target_vmid);
2151 my $param = { $target_mpkey => $drive_string };
2152 my $errors = PVE
::LXC
::Config-
>update_pct_config(
2159 foreach my $key (keys %$errors) {
2160 $rpcenv->warn($errors->{$key});
2164 PVE
::LXC
::Config-
>write_config($target_vmid, $target_conf);
2165 $target_conf = PVE
::LXC
::Config-
>load_config($target_vmid);
2167 PVE
::LXC
::update_lxc_config
($target_vmid, $target_conf) if !$target_unused;
2168 print "target container '$target_vmid' updated with '$target_mpkey'\n";
2170 # remove possible replication snapshots
2171 if (PVE
::Storage
::volume_has_feature
($storecfg,'replicate', $source_volid)) {
2173 PVE
::Replication
::prepare
(
2183 $rpcenv->warn("Failed to remove replication snapshots on volume ".
2184 "'${target_mpkey}'. Manual cleanup could be necessary. " .
2193 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
2194 if $authuser ne 'root@pam';
2196 die "Moving a volume to the same container is not possible. Did you ".
2197 "mean to move the volume to a different storage?\n"
2198 if $vmid eq $target_vmid;
2200 &$load_and_check_reassign_configs();
2201 return $rpcenv->fork_worker(
2203 "${vmid}-${mpkey}>${target_vmid}-${target_mpkey}",
2207 } elsif ($storage) {
2208 &$move_to_storage_checks();
2210 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $storage_realcmd);
2213 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
2219 die "Provide either a 'storage' to move the mount point to a ".
2220 "different storage or 'target-vmid' and 'target-mp' to move ".
2221 "the mount point to another container\n";
2225 __PACKAGE__-
>register_method({
2226 name
=> 'vm_pending',
2227 path
=> '{vmid}/pending',
2230 description
=> 'Get container configuration, including pending changes.',
2232 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2235 additionalProperties
=> 0,
2237 node
=> get_standard_option
('pve-node'),
2238 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
2247 description
=> 'Configuration option name.',
2251 description
=> 'Current value.',
2256 description
=> 'Pending value.',
2261 description
=> "Indicates a pending delete request if present and not 0.",
2273 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
2275 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
2277 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);