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 my $check_storage_access_migrate = sub {
40 my ($rpcenv, $authuser, $storecfg, $storage, $node) = @_;
42 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $node);
44 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
46 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
47 die "storage '$storage' does not support CT rootdirs\n"
48 if !$scfg->{content
}->{rootdir
};
51 __PACKAGE__-
>register_method ({
52 subclass
=> "PVE::API2::LXC::Config",
53 path
=> '{vmid}/config',
56 __PACKAGE__-
>register_method ({
57 subclass
=> "PVE::API2::LXC::Status",
58 path
=> '{vmid}/status',
61 __PACKAGE__-
>register_method ({
62 subclass
=> "PVE::API2::LXC::Snapshot",
63 path
=> '{vmid}/snapshot',
66 __PACKAGE__-
>register_method ({
67 subclass
=> "PVE::API2::Firewall::CT",
68 path
=> '{vmid}/firewall',
71 __PACKAGE__-
>register_method({
75 description
=> "LXC container index (per node).",
77 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
81 protected
=> 1, # /proc files are only readable by root
83 additionalProperties
=> 0,
85 node
=> get_standard_option
('pve-node'),
92 properties
=> $PVE::LXC
::vmstatus_return_properties
,
94 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
99 my $rpcenv = PVE
::RPCEnvironment
::get
();
100 my $authuser = $rpcenv->get_user();
102 my $vmstatus = PVE
::LXC
::vmstatus
();
105 foreach my $vmid (keys %$vmstatus) {
106 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
108 my $data = $vmstatus->{$vmid};
116 __PACKAGE__-
>register_method({
120 description
=> "Create or restore a container.",
122 user
=> 'all', # check inside
123 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
124 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
125 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
130 additionalProperties
=> 0,
131 properties
=> PVE
::LXC
::Config-
>json_config_properties({
132 node
=> get_standard_option
('pve-node'),
133 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
135 description
=> "The OS template or backup file.",
138 completion
=> \
&PVE
::LXC
::complete_os_templates
,
143 description
=> "Sets root password inside container.",
146 storage
=> get_standard_option
('pve-storage-id', {
147 description
=> "Default Storage.",
150 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
155 description
=> "Allow to overwrite existing container.",
160 description
=> "Mark this as restore task.",
165 description
=> "Assign a unique random ethernet address.",
166 requires
=> 'restore',
170 type
=> 'string', format
=> 'pve-poolid',
171 description
=> "Add the VM to the specified pool.",
173 'ignore-unpack-errors' => {
176 description
=> "Ignore errors when extracting the template.",
178 'ssh-public-keys' => {
181 description
=> "Setup public SSH keys (one key per line, " .
185 description
=> "Override I/O bandwidth limit (in KiB/s).",
189 default => 'restore limit from datacenter or storage config',
195 description
=> "Start the CT after its creation finished successfully.",
205 PVE
::Cluster
::check_cfs_quorum
();
207 my $rpcenv = PVE
::RPCEnvironment
::get
();
208 my $authuser = $rpcenv->get_user();
210 my $node = extract_param
($param, 'node');
211 my $vmid = extract_param
($param, 'vmid');
212 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
213 my $bwlimit = extract_param
($param, 'bwlimit');
214 my $start_after_create = extract_param
($param, 'start');
216 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
217 my $same_container_exists = -f
$basecfg_fn;
219 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
220 my $unprivileged = extract_param
($param, 'unprivileged');
221 my $restore = extract_param
($param, 'restore');
222 my $unique = extract_param
($param, 'unique');
224 # used to skip firewall config restore if user lacks permission
225 my $skip_fw_config_restore = 0;
228 # fixme: limit allowed parameters
231 my $force = extract_param
($param, 'force');
233 if (!($same_container_exists && $restore && $force)) {
234 PVE
::Cluster
::check_vmid_unused
($vmid);
236 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
237 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
238 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
241 my $password = extract_param
($param, 'password');
242 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
243 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
245 my $pool = extract_param
($param, 'pool');
246 $rpcenv->check_pool_exist($pool) if defined($pool);
248 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
250 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
252 } elsif ($restore && $force && $same_container_exists &&
253 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
254 # OK: user has VM.Backup permissions, and want to restore an existing VM
256 # we don't want to restore a container-provided FW conf in this case
257 # since the user is lacking permission to configure the container's FW
258 $skip_fw_config_restore = 1;
260 # error out if a user tries to change from unprivileged to privileged
261 # explicit change is checked here, implicit is checked down below or happening in root-only paths
262 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
263 if ($conf->{unprivileged
} && defined($unprivileged) && !$unprivileged) {
264 raise_perm_exc
("cannot change from unprivileged to privileged without VM.Allocate");
270 my $ostemplate = extract_param
($param, 'ostemplate');
271 my $storage = extract_param
($param, 'storage') // 'local';
273 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, undef, $param, [], $unprivileged);
275 my $storage_cfg = cfs_read_file
("storage.cfg");
278 if ($ostemplate eq '-') {
279 die "pipe requires cli environment\n"
280 if $rpcenv->{type
} ne 'cli';
281 die "pipe can only be used with restore tasks\n"
284 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
286 my $content_type = $restore ?
'backup' : 'vztmpl';
287 PVE
::Storage
::check_volume_access
(
295 $archive = $ostemplate;
299 my $check_and_activate_storage = sub {
302 my $scfg = PVE
::Storage
::storage_check_enabled
($storage_cfg, $sid, $node);
304 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
305 if !$scfg->{content
}->{rootdir
};
307 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
309 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
310 $used_storages{$sid} = 1;
315 my $is_root = $authuser eq 'root@pam';
317 my $no_disk_param = {};
319 my $storage_only_mode = 1;
320 foreach my $opt (keys %$param) {
321 my $value = $param->{$opt};
322 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
323 # allow to use simple numbers (add default storage in that case)
324 if ($value =~ m/^\d+(\.\d+)?$/) {
325 $mp_param->{$opt} = "$storage:$value";
327 $mp_param->{$opt} = $value;
329 $storage_only_mode = 0;
330 } elsif ($opt =~ m/^unused\d+$/) {
331 warn "ignoring '$opt', cannot create/restore with unused volume\n";
332 delete $param->{$opt};
334 $no_disk_param->{$opt} = $value;
338 die "mount points configured, but 'rootfs' not set - aborting\n"
339 if !$storage_only_mode && !defined($mp_param->{rootfs
});
341 # check storage access, activate storage
342 my $delayed_mp_param = {};
343 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
344 my ($ms, $mountpoint) = @_;
346 my $volid = $mountpoint->{volume
};
347 my $mp = $mountpoint->{mp
};
349 if ($mountpoint->{type
} ne 'volume') { # bind or device
350 die "Only root can pass arbitrary filesystem paths.\n"
353 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
354 &$check_and_activate_storage($sid);
358 # check/activate default storage
359 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
361 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
363 $conf->{unprivileged
} = 1 if $unprivileged;
365 my $emsg = $restore ?
"unable to restore CT $vmid -" : "unable to create CT $vmid -";
367 eval { PVE
::LXC
::Config-
>create_and_lock_config($vmid, $force) };
368 die "$emsg $@" if $@;
373 my $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
377 my $orig_mp_param; # only used if $restore
379 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
380 if ($archive ne '-') {
382 print "recovering backed-up configuration from '$archive'\n";
383 ($orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
385 $was_template = delete $orig_conf->{template
};
387 # When we're root call 'restore_configuration' with restricted=0,
388 # causing it to restore the raw lxc entries, among which there may be
389 # 'lxc.idmap' entries. We need to make sure that the extracted contents
390 # of the container match up with the restored configuration afterwards:
391 $conf->{lxc
} = $orig_conf->{lxc
} if $is_root;
393 $conf->{unprivileged
} = $orig_conf->{unprivileged
}
394 if !defined($unprivileged) && defined($orig_conf->{unprivileged
});
396 # implicit privileged change is checked here
397 if ($old_conf->{unprivileged
} && !$conf->{unprivileged
}) {
398 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Allocate']);
402 if ($storage_only_mode) {
404 if (!defined($orig_mp_param)) {
405 print "recovering backed-up configuration from '$archive'\n";
406 (undef, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
408 $mp_param = $orig_mp_param;
409 die "rootfs configuration could not be recovered, please check and specify manually!\n"
410 if !defined($mp_param->{rootfs
});
411 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
412 my ($ms, $mountpoint) = @_;
413 my $type = $mountpoint->{type
};
414 if ($type eq 'volume') {
415 die "unable to detect disk size - please specify $ms (size)\n"
416 if !defined($mountpoint->{size
});
417 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
418 delete $mountpoint->{size
};
419 $mountpoint->{volume
} = "$storage:$disksize";
420 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
422 my $type = $mountpoint->{type
};
423 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
424 if ($ms eq 'rootfs');
425 die "restoring '$ms' to $type mount is only possible for root\n"
428 if ($mountpoint->{backup
}) {
429 warn "WARNING - unsupported configuration!\n";
430 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
431 warn "mount point configuration will be restored after archive extraction!\n";
432 warn "contained files will be restored to wrong directory!\n";
434 delete $mp_param->{$ms}; # actually delay bind/dev mps
435 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
439 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
443 die "$emsg $@" if $@;
445 # up until here we did not modify the container, besides the lock
450 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
452 # we always have the 'create' lock so check for more than 1 entry
453 if (scalar(keys %$old_conf) > 1) {
454 # destroy old container volumes
455 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, { lock => 'create' });
459 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
460 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
461 print "restoring '$archive' now..\n"
462 if $restore && $archive ne '-';
463 PVE
::LXC
::Create
::restore_archive
($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
466 print "merging backed-up and given configuration..\n";
467 PVE
::LXC
::Create
::restore_configuration
($vmid, $storage_cfg, $archive, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
468 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
469 $lxc_setup->template_fixup($conf);
471 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
472 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
473 $lxc_setup->post_create_hook($password, $ssh_keys);
477 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
478 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
481 $conf->{hostname
} ||= "CT$vmid";
482 $conf->{memory
} ||= 512;
483 $conf->{swap
} //= 512;
484 foreach my $mp (keys %$delayed_mp_param) {
485 $conf->{$mp} = $delayed_mp_param->{$mp};
487 # If the template flag was set, we try to convert again to template after restore
489 print STDERR
"Convert restored container to template...\n";
490 PVE
::LXC
::template_create
($vmid, $conf);
491 $conf->{template
} = 1;
493 PVE
::LXC
::Config-
>write_config($vmid, $conf);
496 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
497 eval { PVE
::LXC
::Config-
>destroy_config($vmid) };
501 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
503 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
504 if $start_after_create;
507 my $workername = $restore ?
'vzrestore' : 'vzcreate';
510 PVE
::LXC
::Config-
>lock_config($vmid, $code);
513 # if we aborted before changing the container, we must remove the create lock
515 PVE
::LXC
::Config-
>remove_lock($vmid, 'create');
521 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
524 __PACKAGE__-
>register_method({
529 description
=> "Directory index",
534 additionalProperties
=> 0,
536 node
=> get_standard_option
('pve-node'),
537 vmid
=> get_standard_option
('pve-vmid'),
545 subdir
=> { type
=> 'string' },
548 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
554 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
557 { subdir
=> 'config' },
558 { subdir
=> 'pending' },
559 { subdir
=> 'status' },
560 { subdir
=> 'vncproxy' },
561 { subdir
=> 'termproxy' },
562 { subdir
=> 'vncwebsocket' },
563 { subdir
=> 'spiceproxy' },
564 { subdir
=> 'migrate' },
565 { subdir
=> 'clone' },
566 # { subdir => 'initlog' },
568 { subdir
=> 'rrddata' },
569 { subdir
=> 'firewall' },
570 { subdir
=> 'snapshot' },
571 { subdir
=> 'resize' },
578 __PACKAGE__-
>register_method({
580 path
=> '{vmid}/rrd',
582 protected
=> 1, # fixme: can we avoid that?
584 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
586 description
=> "Read VM RRD statistics (returns PNG)",
588 additionalProperties
=> 0,
590 node
=> get_standard_option
('pve-node'),
591 vmid
=> get_standard_option
('pve-vmid'),
593 description
=> "Specify the time frame you are interested in.",
595 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
598 description
=> "The list of datasources you want to display.",
599 type
=> 'string', format
=> 'pve-configid-list',
602 description
=> "The RRD consolidation function",
604 enum
=> [ 'AVERAGE', 'MAX' ],
612 filename
=> { type
=> 'string' },
618 return PVE
::RRD
::create_rrd_graph
(
619 "pve2-vm/$param->{vmid}", $param->{timeframe
},
620 $param->{ds
}, $param->{cf
});
624 __PACKAGE__-
>register_method({
626 path
=> '{vmid}/rrddata',
628 protected
=> 1, # fixme: can we avoid that?
630 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
632 description
=> "Read VM RRD statistics",
634 additionalProperties
=> 0,
636 node
=> get_standard_option
('pve-node'),
637 vmid
=> get_standard_option
('pve-vmid'),
639 description
=> "Specify the time frame you are interested in.",
641 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
644 description
=> "The RRD consolidation function",
646 enum
=> [ 'AVERAGE', 'MAX' ],
661 return PVE
::RRD
::create_rrd_data
(
662 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
665 __PACKAGE__-
>register_method({
666 name
=> 'destroy_vm',
671 description
=> "Destroy the container (also delete all uses files).",
673 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
676 additionalProperties
=> 0,
678 node
=> get_standard_option
('pve-node'),
679 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
682 description
=> "Force destroy, even if running.",
688 description
=> "Remove container from all related configurations."
689 ." For example, backup jobs, replication jobs or HA."
690 ." Related ACLs and Firewall entries will *always* be removed.",
694 'destroy-unreferenced-disks' => {
696 description
=> "If set, destroy additionally all disks with the VMID from all"
697 ." enabled storages which are not referenced in the config.",
708 my $rpcenv = PVE
::RPCEnvironment
::get
();
709 my $authuser = $rpcenv->get_user();
710 my $vmid = $param->{vmid
};
712 # test if container exists
714 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
715 my $early_checks = sub {
717 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
718 PVE
::LXC
::Config-
>check_lock($conf);
720 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("ct:$vmid");
722 if (!$param->{purge
}) {
723 die "unable to remove CT $vmid - used in HA resources and purge parameter not set.\n"
726 # do not allow destroy if there are replication jobs without purge
727 my $repl_conf = PVE
::ReplicationConfig-
>new();
728 $repl_conf->check_for_existing_jobs($vmid);
734 $early_checks->($conf);
736 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
737 die $running_error_msg if !$param->{force
} && PVE
::LXC
::check_running
($vmid); # check early
740 # reload config after lock
741 $conf = PVE
::LXC
::Config-
>load_config($vmid);
742 my $ha_managed = $early_checks->($conf);
744 if (PVE
::LXC
::check_running
($vmid)) {
745 die $running_error_msg if !$param->{force
};
746 warn "forced to stop CT $vmid before destroying!\n";
748 PVE
::LXC
::vm_stop
($vmid, 1);
750 run_command
(['ha-manager', 'crm-command', 'stop', "ct:$vmid", '120']);
754 my $storage_cfg = cfs_read_file
("storage.cfg");
755 PVE
::LXC
::destroy_lxc_container
(
759 { lock => 'destroyed' },
760 $param->{'destroy-unreferenced-disks'},
763 PVE
::AccessControl
::remove_vm_access
($vmid);
764 PVE
::Firewall
::remove_vmfw_conf
($vmid);
765 if ($param->{purge
}) {
766 print "purging CT $vmid from related configurations..\n";
767 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
768 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
771 PVE
::HA
::Config
::delete_service_from_config
("ct:$vmid");
772 print "NOTE: removed CT $vmid from HA resource configuration.\n";
776 # only now remove the zombie config, else we can have reuse race
777 PVE
::LXC
::Config-
>destroy_config($vmid);
780 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
782 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
787 __PACKAGE__-
>register_method ({
789 path
=> '{vmid}/vncproxy',
793 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
795 description
=> "Creates a TCP VNC proxy connections.",
797 additionalProperties
=> 0,
799 node
=> get_standard_option
('pve-node'),
800 vmid
=> get_standard_option
('pve-vmid'),
804 description
=> "use websocket instead of standard VNC.",
808 description
=> "sets the width of the console in pixels.",
815 description
=> "sets the height of the console in pixels.",
823 additionalProperties
=> 0,
825 user
=> { type
=> 'string' },
826 ticket
=> { type
=> 'string' },
827 cert
=> { type
=> 'string' },
828 port
=> { type
=> 'integer' },
829 upid
=> { type
=> 'string' },
835 my $rpcenv = PVE
::RPCEnvironment
::get
();
837 my $authuser = $rpcenv->get_user();
839 my $vmid = $param->{vmid
};
840 my $node = $param->{node
};
842 my $authpath = "/vms/$vmid";
844 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
846 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
849 my ($remip, $family);
851 if ($node ne PVE
::INotify
::nodename
()) {
852 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
854 $family = PVE
::Tools
::get_host_address_family
($node);
857 my $port = PVE
::Tools
::next_vnc_port
($family);
859 # NOTE: vncterm VNC traffic is already TLS encrypted,
860 # so we select the fastest chipher here (or 'none'?)
861 my $remcmd = $remip ?
862 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
864 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
865 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
867 my $shcmd = [ '/usr/bin/dtach', '-A',
868 "/var/run/dtach/vzctlconsole$vmid",
869 '-r', 'winch', '-z', @$concmd];
874 syslog
('info', "starting lxc vnc proxy $upid\n");
878 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
879 '-timeout', $timeout, '-authpath', $authpath,
880 '-perm', 'VM.Console'];
882 if ($param->{width
}) {
883 push @$cmd, '-width', $param->{width
};
886 if ($param->{height
}) {
887 push @$cmd, '-height', $param->{height
};
890 if ($param->{websocket
}) {
891 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
892 push @$cmd, '-notls', '-listen', 'localhost';
895 push @$cmd, '-c', @$remcmd, @$shcmd;
897 run_command
($cmd, keeplocale
=> 1);
902 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
904 PVE
::Tools
::wait_for_vnc_port
($port);
915 __PACKAGE__-
>register_method ({
917 path
=> '{vmid}/termproxy',
921 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
923 description
=> "Creates a TCP proxy connection.",
925 additionalProperties
=> 0,
927 node
=> get_standard_option
('pve-node'),
928 vmid
=> get_standard_option
('pve-vmid'),
932 additionalProperties
=> 0,
934 user
=> { type
=> 'string' },
935 ticket
=> { type
=> 'string' },
936 port
=> { type
=> 'integer' },
937 upid
=> { type
=> 'string' },
943 my $rpcenv = PVE
::RPCEnvironment
::get
();
945 my $authuser = $rpcenv->get_user();
947 my $vmid = $param->{vmid
};
948 my $node = $param->{node
};
950 my $authpath = "/vms/$vmid";
952 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
954 my ($remip, $family);
956 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
957 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
959 $family = PVE
::Tools
::get_host_address_family
($node);
962 my $port = PVE
::Tools
::next_vnc_port
($family);
964 my $remcmd = $remip ?
965 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
967 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
968 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
970 my $shcmd = [ '/usr/bin/dtach', '-A',
971 "/var/run/dtach/vzctlconsole$vmid",
972 '-r', 'winch', '-z', @$concmd];
977 syslog
('info', "starting lxc termproxy $upid\n");
979 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
980 '--perm', 'VM.Console', '--'];
981 push @$cmd, @$remcmd, @$shcmd;
983 PVE
::Tools
::run_command
($cmd);
986 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
988 PVE
::Tools
::wait_for_vnc_port
($port);
998 __PACKAGE__-
>register_method({
999 name
=> 'vncwebsocket',
1000 path
=> '{vmid}/vncwebsocket',
1003 description
=> "You also need to pass a valid ticket (vncticket).",
1004 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1006 description
=> "Opens a weksocket for VNC traffic.",
1008 additionalProperties
=> 0,
1010 node
=> get_standard_option
('pve-node'),
1011 vmid
=> get_standard_option
('pve-vmid'),
1013 description
=> "Ticket from previous call to vncproxy.",
1018 description
=> "Port number returned by previous vncproxy call.",
1028 port
=> { type
=> 'string' },
1034 my $rpcenv = PVE
::RPCEnvironment
::get
();
1036 my $authuser = $rpcenv->get_user();
1038 my $authpath = "/vms/$param->{vmid}";
1040 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1042 my $port = $param->{port
};
1044 return { port
=> $port };
1047 __PACKAGE__-
>register_method ({
1048 name
=> 'spiceproxy',
1049 path
=> '{vmid}/spiceproxy',
1054 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1056 description
=> "Returns a SPICE configuration to connect to the CT.",
1058 additionalProperties
=> 0,
1060 node
=> get_standard_option
('pve-node'),
1061 vmid
=> get_standard_option
('pve-vmid'),
1062 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1065 returns
=> get_standard_option
('remote-viewer-config'),
1069 my $vmid = $param->{vmid
};
1070 my $node = $param->{node
};
1071 my $proxy = $param->{proxy
};
1073 my $authpath = "/vms/$vmid";
1074 my $permissions = 'VM.Console';
1076 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1078 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
1080 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
1082 my $shcmd = ['/usr/bin/dtach', '-A',
1083 "/var/run/dtach/vzctlconsole$vmid",
1084 '-r', 'winch', '-z', @$concmd];
1086 my $title = "CT $vmid";
1088 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1092 __PACKAGE__-
>register_method({
1093 name
=> 'migrate_vm',
1094 path
=> '{vmid}/migrate',
1098 description
=> "Migrate the container to another node. Creates a new migration task.",
1100 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1103 additionalProperties
=> 0,
1105 node
=> get_standard_option
('pve-node'),
1106 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1107 target
=> get_standard_option
('pve-node', {
1108 description
=> "Target node.",
1109 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1111 'target-storage' => get_standard_option
('pve-targetstorage'),
1114 description
=> "Use online/live migration.",
1119 description
=> "Use restart migration",
1124 description
=> "Timeout in seconds for shutdown for restart migration",
1129 description
=> "Override I/O bandwidth limit (in KiB/s).",
1133 default => 'migrate limit from datacenter or storage config',
1139 description
=> "the task ID.",
1144 my $rpcenv = PVE
::RPCEnvironment
::get
();
1146 my $authuser = $rpcenv->get_user();
1148 my $target = extract_param
($param, 'target');
1150 my $localnode = PVE
::INotify
::nodename
();
1151 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1153 PVE
::Cluster
::check_cfs_quorum
();
1155 PVE
::Cluster
::check_node_exists
($target);
1157 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1159 my $vmid = extract_param
($param, 'vmid');
1162 PVE
::LXC
::Config-
>load_config($vmid);
1164 # try to detect errors early
1165 if (PVE
::LXC
::check_running
($vmid)) {
1166 die "can't migrate running container without --online or --restart\n"
1167 if !$param->{online
} && !$param->{restart
};
1170 if (my $targetstorage = delete $param->{'target-storage'}) {
1171 my $storecfg = PVE
::Storage
::config
();
1172 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
1173 raise_param_exc
({ 'target-storage' => "failed to parse storage map: $@" })
1176 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
1177 if !defined($storagemap->{identity
});
1179 foreach my $target_sid (values %{$storagemap->{entries
}}) {
1180 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
1183 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
1184 if $storagemap->{default};
1186 $param->{storagemap
} = $storagemap;
1189 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1194 my $service = "ct:$vmid";
1196 my $cmd = ['ha-manager', 'migrate', $service, $target];
1198 print "Requesting HA migration for CT $vmid to node $target\n";
1200 PVE
::Tools
::run_command
($cmd);
1205 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1210 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1214 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1217 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1221 __PACKAGE__-
>register_method({
1222 name
=> 'vm_feature',
1223 path
=> '{vmid}/feature',
1227 description
=> "Check if feature for virtual machine is available.",
1229 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1232 additionalProperties
=> 0,
1234 node
=> get_standard_option
('pve-node'),
1235 vmid
=> get_standard_option
('pve-vmid'),
1237 description
=> "Feature to check.",
1239 enum
=> [ 'snapshot', 'clone', 'copy' ],
1241 snapname
=> get_standard_option
('pve-snapshot-name', {
1249 hasFeature
=> { type
=> 'boolean' },
1252 #items => { type => 'string' },
1259 my $node = extract_param
($param, 'node');
1261 my $vmid = extract_param
($param, 'vmid');
1263 my $snapname = extract_param
($param, 'snapname');
1265 my $feature = extract_param
($param, 'feature');
1267 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1270 my $snap = $conf->{snapshots
}->{$snapname};
1271 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1274 my $storage_cfg = PVE
::Storage
::config
();
1275 #Maybe include later
1276 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1277 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1280 hasFeature
=> $hasFeature,
1281 #nodes => [ keys %$nodelist ],
1285 __PACKAGE__-
>register_method({
1287 path
=> '{vmid}/template',
1291 description
=> "Create a Template.",
1293 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1294 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1297 additionalProperties
=> 0,
1299 node
=> get_standard_option
('pve-node'),
1300 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1303 returns
=> { type
=> 'null'},
1307 my $rpcenv = PVE
::RPCEnvironment
::get
();
1309 my $authuser = $rpcenv->get_user();
1311 my $node = extract_param
($param, 'node');
1313 my $vmid = extract_param
($param, 'vmid');
1315 my $updatefn = sub {
1317 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1318 PVE
::LXC
::Config-
>check_lock($conf);
1320 die "unable to create template, because CT contains snapshots\n"
1321 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1323 die "you can't convert a template to a template\n"
1324 if PVE
::LXC
::Config-
>is_template($conf);
1326 die "you can't convert a CT to template if the CT is running\n"
1327 if PVE
::LXC
::check_running
($vmid);
1330 PVE
::LXC
::template_create
($vmid, $conf);
1332 $conf->{template
} = 1;
1334 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1335 # and remove lxc config
1336 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1339 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1342 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1347 __PACKAGE__-
>register_method({
1349 path
=> '{vmid}/clone',
1353 description
=> "Create a container clone/copy",
1355 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1356 "and 'VM.Allocate' permissions " .
1357 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1358 "'Datastore.AllocateSpace' on any used storage.",
1361 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1363 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1364 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1369 additionalProperties
=> 0,
1371 node
=> get_standard_option
('pve-node'),
1372 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1373 newid
=> get_standard_option
('pve-vmid', {
1374 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1375 description
=> 'VMID for the clone.' }),
1378 type
=> 'string', format
=> 'dns-name',
1379 description
=> "Set a hostname for the new CT.",
1384 description
=> "Description for the new CT.",
1388 type
=> 'string', format
=> 'pve-poolid',
1389 description
=> "Add the new CT to the specified pool.",
1391 snapname
=> get_standard_option
('pve-snapshot-name', {
1394 storage
=> get_standard_option
('pve-storage-id', {
1395 description
=> "Target storage for full clone.",
1401 description
=> "Create a full copy of all disks. This is always done when " .
1402 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1404 target
=> get_standard_option
('pve-node', {
1405 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1409 description
=> "Override I/O bandwidth limit (in KiB/s).",
1413 default => 'clone limit from datacenter or storage config',
1423 my $rpcenv = PVE
::RPCEnvironment
::get
();
1424 my $authuser = $rpcenv->get_user();
1426 my $node = extract_param
($param, 'node');
1427 my $vmid = extract_param
($param, 'vmid');
1428 my $newid = extract_param
($param, 'newid');
1429 my $pool = extract_param
($param, 'pool');
1430 if (defined($pool)) {
1431 $rpcenv->check_pool_exist($pool);
1433 my $snapname = extract_param
($param, 'snapname');
1434 my $storage = extract_param
($param, 'storage');
1435 my $target = extract_param
($param, 'target');
1436 my $localnode = PVE
::INotify
::nodename
();
1438 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1440 PVE
::Cluster
::check_node_exists
($target) if $target;
1442 my $storecfg = PVE
::Storage
::config
();
1445 # check if storage is enabled on local node
1446 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1448 # check if storage is available on target node
1449 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
1450 # clone only works if target storage is shared
1451 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1452 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1456 PVE
::Cluster
::check_cfs_quorum
();
1459 my $mountpoints = {};
1464 my $lock_and_reload = sub {
1465 my ($vmid, $code) = @_;
1466 return PVE
::LXC
::Config-
>lock_config($vmid, sub {
1467 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1468 die "Lost 'create' config lock, aborting.\n"
1469 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1471 return $code->($conf);
1475 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1478 PVE
::LXC
::Config-
>create_and_lock_config($newid, 0);
1481 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1482 warn "Failed to remove source CT config lock - $@\n" if $@;
1488 $running = PVE
::LXC
::check_running
($vmid) || 0;
1490 my $full = extract_param
($param, 'full');
1491 if (!defined($full)) {
1492 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1495 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1497 die "parameter 'storage' not allowed for linked clones\n"
1498 if defined($storage) && !$full;
1500 die "snapshot '$snapname' does not exist\n"
1501 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1503 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1506 for my $opt (sort keys %$src_conf) {
1507 next if $opt =~ m/^unused\d+$/;
1509 my $value = $src_conf->{$opt};
1511 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1512 my $mp = PVE
::LXC
::Config-
>parse_volume($opt, $value);
1514 if ($mp->{type
} eq 'volume') {
1515 my $volid = $mp->{volume
};
1517 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1518 $sid = $storage if defined($storage);
1519 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1520 if (!$scfg->{shared
}) {
1522 warn "found non-shared volume: $volid\n" if $target;
1525 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1528 die "Cannot do full clones on a running container without snapshots\n"
1529 if $running && !defined($snapname);
1530 $fullclone->{$opt} = 1;
1532 # not full means clone instead of copy
1533 die "Linked clone feature for '$volid' is not available\n"
1534 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
1537 $mountpoints->{$opt} = $mp;
1538 push @$vollist, $volid;
1541 # TODO: allow bind mounts?
1542 die "unable to clone mountpoint '$opt' (type $mp->{type})\n";
1544 } elsif ($opt =~ m/^net(\d+)$/) {
1545 # always change MAC! address
1546 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1547 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1548 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1549 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1551 # copy everything else
1552 $newconf->{$opt} = $value;
1555 die "can't clone CT to node '$target' (CT uses local storage)\n"
1556 if $target && !$sharedvm;
1558 # Replace the 'disk' lock with a 'create' lock.
1559 $newconf->{lock} = 'create';
1561 # delete all snapshot related config options
1562 delete $newconf->@{qw(snapshots parent snaptime snapstate)};
1564 delete $newconf->{pending
};
1565 delete $newconf->{template
};
1567 $newconf->{hostname
} = $param->{hostname
} if $param->{hostname
};
1568 $newconf->{description
} = $param->{description
} if $param->{description
};
1570 $lock_and_reload->($newid, sub {
1571 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1575 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1576 warn "Failed to remove source CT config lock - $@\n" if $@;
1579 $lock_and_reload->($newid, sub {
1580 PVE
::LXC
::Config-
>destroy_config($newid);
1581 PVE
::Firewall
::remove_vmfw_conf
($newid);
1584 warn "Failed to remove target CT config - $@\n" if $@;
1589 my $update_conf = sub {
1590 my ($key, $value) = @_;
1591 return $lock_and_reload->($newid, sub {
1593 $conf->{$key} = $value;
1594 PVE
::LXC
::Config-
>write_config($newid, $conf);
1601 my $newvollist = [];
1603 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1604 die "unexpected state change\n" if $verify_running != $running;
1610 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1612 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1613 my $bwlimit = extract_param
($param, 'bwlimit');
1615 foreach my $opt (keys %$mountpoints) {
1616 my $mp = $mountpoints->{$opt};
1617 my $volid = $mp->{volume
};
1620 if ($fullclone->{$opt}) {
1621 print "create full clone of mountpoint $opt ($volid)\n";
1622 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1623 my $target_storage = $storage // $source_storage;
1624 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1625 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1627 print "create linked clone of mount point $opt ($volid)\n";
1628 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1631 push @$newvollist, $newvolid;
1632 $mp->{volume
} = $newvolid;
1634 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1637 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1639 $lock_and_reload->($newid, sub {
1641 my $rootdir = PVE
::LXC
::mount_all
($newid, $storecfg, $conf, 1);
1643 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1644 $lxc_setup->post_clone_hook($conf);
1647 eval { PVE
::LXC
::umount_all
($newid, $storecfg, $conf, 1); };
1657 # Unlock the source config in any case:
1658 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1662 # Now cleanup the config & disks:
1663 sleep 1; # some storages like rbd need to wait before release volume - really?
1665 foreach my $volid (@$newvollist) {
1666 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1671 $lock_and_reload->($newid, sub {
1672 PVE
::LXC
::Config-
>destroy_config($newid);
1673 PVE
::Firewall
::remove_vmfw_conf
($newid);
1676 warn "Failed to remove target CT config - $@\n" if $@;
1678 die "clone failed: $err";
1681 $lock_and_reload->($newid, sub {
1682 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1685 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1686 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1687 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1689 PVE
::LXC
::Config-
>move_config_to_node($newid, $target);
1696 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1700 __PACKAGE__-
>register_method({
1701 name
=> 'resize_vm',
1702 path
=> '{vmid}/resize',
1706 description
=> "Resize a container mount point.",
1708 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1711 additionalProperties
=> 0,
1713 node
=> get_standard_option
('pve-node'),
1714 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1717 description
=> "The disk you want to resize.",
1718 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys()],
1722 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1723 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.",
1727 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1735 description
=> "the task ID.",
1740 my $rpcenv = PVE
::RPCEnvironment
::get
();
1742 my $authuser = $rpcenv->get_user();
1744 my $node = extract_param
($param, 'node');
1746 my $vmid = extract_param
($param, 'vmid');
1748 my $digest = extract_param
($param, 'digest');
1750 my $sizestr = extract_param
($param, 'size');
1751 my $ext = ($sizestr =~ s/^\+//);
1752 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1753 die "invalid size string" if !defined($newsize);
1755 die "no options specified\n" if !scalar(keys %$param);
1757 my $storage_cfg = cfs_read_file
("storage.cfg");
1761 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1762 PVE
::LXC
::Config-
>check_lock($conf);
1764 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $conf, $param, [], $conf->{unprivileged
});
1766 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1768 my $running = PVE
::LXC
::check_running
($vmid);
1770 my $disk = $param->{disk
};
1771 my $mp = PVE
::LXC
::Config-
>parse_volume($disk, $conf->{$disk});
1773 my $volid = $mp->{volume
};
1775 my (undef, undef, $owner, undef, undef, undef, $format) =
1776 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1778 die "can't resize mount point owned by another container ($owner)"
1781 die "can't resize volume: $disk if snapshot exists\n"
1782 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1784 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1786 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1788 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1790 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1792 die "Could not determine current size of volume '$volid'\n" if !defined($size);
1794 $newsize += $size if $ext;
1795 $newsize = int($newsize);
1797 die "unable to shrink disk size\n" if $newsize < $size;
1799 die "disk is already at specified size\n" if $size == $newsize;
1801 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1803 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1804 # we pass 0 here (parameter only makes sense for qemu)
1805 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1807 $mp->{size
} = $newsize;
1808 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1810 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1812 if ($format eq 'raw') {
1813 # we need to ensure that the volume is mapped, if not needed this is a NOP
1814 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1815 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1819 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1820 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1821 die "internal error: CT running but mount point not attached to a loop device"
1823 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1825 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1826 # to be visible to it in its namespace.
1827 # To not interfere with the rest of the system we unshare the current mount namespace,
1828 # mount over /tmp and then run resize2fs.
1830 # interestingly we don't need to e2fsck on mounted systems...
1831 my $quoted = PVE
::Tools
::shellquote
($path);
1832 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1834 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1836 warn "Failed to update the container's filesystem: $@\n" if $@;
1839 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1840 PVE
::Tools
::run_command
(['resize2fs', $path]);
1842 warn "Failed to update the container's filesystem: $@\n" if $@;
1844 # always un-map if not running, this is a NOP if not needed
1845 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1850 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1853 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1856 __PACKAGE__-
>register_method({
1857 name
=> 'move_volume',
1858 path
=> '{vmid}/move_volume',
1862 description
=> "Move a rootfs-/mp-volume to a different storage or to a different container.",
1864 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1865 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
1866 "a volume to another container, you need the permissions on the ".
1867 "target container as well.",
1868 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1871 additionalProperties
=> 0,
1873 node
=> get_standard_option
('pve-node'),
1874 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1875 'target-vmid' => get_standard_option
('pve-vmid', {
1876 completion
=> \
&PVE
::LXC
::complete_ctid
,
1881 #TODO: check how to handle unused mount points as the mp parameter is not configured
1882 enum
=> [ PVE
::LXC
::Config-
>valid_volume_keys_with_unused() ],
1883 description
=> "Volume which will be moved.",
1885 storage
=> get_standard_option
('pve-storage-id', {
1886 description
=> "Target Storage.",
1887 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1892 description
=> "Delete the original volume after successful copy. By default the " .
1893 "original is kept as an unused volume entry.",
1899 description
=> 'Prevent changes if current configuration file has different SHA1 " .
1900 "digest. This can be used to prevent concurrent modifications.',
1905 description
=> "Override I/O bandwidth limit (in KiB/s).",
1909 default => 'clone limit from datacenter or storage config',
1911 'target-volume' => {
1913 description
=> "The config key the volume will be moved to. Default is the " .
1914 "source volume key.",
1915 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys_with_unused()],
1918 'target-digest' => {
1920 description
=> 'Prevent changes if current configuration file of the target " .
1921 "container has a different SHA1 digest. This can be used to prevent " .
1922 "concurrent modifications.',
1934 my $rpcenv = PVE
::RPCEnvironment
::get
();
1936 my $authuser = $rpcenv->get_user();
1938 my $vmid = extract_param
($param, 'vmid');
1940 my $target_vmid = extract_param
($param, 'target-vmid');
1942 my $storage = extract_param
($param, 'storage');
1944 my $mpkey = extract_param
($param, 'volume');
1946 my $target_mpkey = extract_param
($param, 'target-volume') // $mpkey;
1948 my $digest = extract_param
($param, 'digest');
1950 my $target_digest = extract_param
($param, 'target-digest');
1952 my $lockname = 'disk';
1954 my ($mpdata, $old_volid);
1956 die "either set storage or target-vmid, but not both\n"
1957 if $storage && $target_vmid;
1959 my $storecfg = PVE
::Storage
::config
();
1961 my $move_to_storage_checks = sub {
1962 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1963 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1964 PVE
::LXC
::Config-
>check_lock($conf);
1966 die "cannot move volumes of a running container\n"
1967 if PVE
::LXC
::check_running
($vmid);
1969 if ($mpkey =~ m/^unused\d+$/) {
1970 die "cannot move volume '$mpkey', only configured volumes can be moved to ".
1971 "another storage\n";
1974 $mpdata = PVE
::LXC
::Config-
>parse_volume($mpkey, $conf->{$mpkey});
1975 $old_volid = $mpdata->{volume
};
1977 die "you can't move a volume with snapshots and delete the source\n"
1978 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1980 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1982 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1986 my $storage_realcmd = sub {
1988 PVE
::Cluster
::log_msg
(
1991 "move volume CT $vmid: move --volume $mpkey --storage $storage"
1994 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1995 my $storage_cfg = PVE
::Storage
::config
();
2000 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
2001 my $bwlimit = extract_param
($param, 'bwlimit');
2002 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
2003 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
2005 [$source_storage, $storage],
2008 $new_volid = PVE
::LXC
::copy_volume
(
2017 if (PVE
::LXC
::Config-
>is_template($conf)) {
2018 PVE
::Storage
::activate_volumes
($storage_cfg, [ $new_volid ]);
2019 my $template_volid = PVE
::Storage
::vdisk_create_base
($storage_cfg, $new_volid);
2020 $mpdata->{volume
} = $template_volid;
2022 $mpdata->{volume
} = $new_volid;
2025 PVE
::LXC
::Config-
>lock_config($vmid, sub {
2026 my $digest = $conf->{digest
};
2027 $conf = PVE
::LXC
::Config-
>load_config($vmid);
2028 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
2030 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint(
2035 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2037 PVE
::LXC
::Config-
>write_config($vmid, $conf);
2041 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2042 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
2048 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
2049 if defined($new_volid);
2055 my $deactivated = 0;
2057 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
2062 if ($param->{delete}) {
2066 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
2072 PVE
::LXC
::Config-
>lock_config($vmid, sub {
2073 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
2074 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid);
2075 PVE
::LXC
::Config-
>write_config($vmid, $conf);
2081 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
2086 my $load_and_check_reassign_configs = sub {
2087 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
2089 die "Cannot move to/from 'rootfs'\n" if $mpkey eq "rootfs" || $target_mpkey eq "rootfs";
2091 if ($mpkey =~ m/^unused\d+$/ && $target_mpkey !~ m/^unused\d+$/) {
2092 die "Moving an unused volume to a used one is not possible\n";
2094 die "could not find CT ${vmid}\n" if !exists($vmlist->{$vmid});
2095 die "could not find CT ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
2097 my $source_node = $vmlist->{$vmid}->{node
};
2098 my $target_node = $vmlist->{$target_vmid}->{node
};
2100 die "Both containers need to be on the same node ($source_node != $target_node)\n"
2101 if $source_node ne $target_node;
2103 my $source_conf = PVE
::LXC
::Config-
>load_config($vmid);
2104 PVE
::LXC
::Config-
>check_lock($source_conf);
2106 if ($target_vmid eq $vmid) {
2107 $target_conf = $source_conf;
2109 $target_conf = PVE
::LXC
::Config-
>load_config($target_vmid);
2110 PVE
::LXC
::Config-
>check_lock($target_conf);
2113 die "Can't move volumes from or to template CT\n"
2114 if ($source_conf->{template
} || $target_conf->{template
});
2117 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
2118 die "Container ${vmid}: $@" if $@;
2121 if ($target_digest) {
2122 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
2123 die "Container ${target_vmid}: $@" if $@;
2126 die "volume '${mpkey}' for container '$vmid' does not exist\n"
2127 if !defined($source_conf->{$mpkey});
2129 die "Target volume key '${target_mpkey}' is already in use for container '$target_vmid'\n"
2130 if exists $target_conf->{$target_mpkey};
2132 my $drive = PVE
::LXC
::Config-
>parse_volume($mpkey, $source_conf->{$mpkey});
2133 my $source_volid = $drive->{volume
} or die "Volume '${mpkey}' has no associated image\n";
2134 die "Cannot move volume used by a snapshot to another container\n"
2135 if PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($source_conf, $source_volid);
2136 die "Storage does not support moving of this disk to another container\n"
2137 if !PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid);
2138 die "Cannot move a bindmount or device mount to another container\n"
2139 if $drive->{type
} ne "volume";
2140 die "Cannot move in-use volume while the source CT is running - detach or shutdown first\n"
2141 if PVE
::LXC
::check_running
($vmid) && $mpkey !~ m/^unused\d+$/;
2143 my $repl_conf = PVE
::ReplicationConfig-
>new();
2144 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
2145 my ($storeid, undef) = PVE
::Storage
::parse_volume_id
($source_volid);
2146 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
2148 die "Cannot move volume on storage '$storeid' to a replicated container - missing replication support\n"
2149 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
2152 return ($source_conf, $target_conf, $drive);
2155 my $logfunc = sub { print STDERR
"$_[0]\n"; };
2157 my $volume_reassignfn = sub {
2158 return PVE
::LXC
::Config-
>lock_config($vmid, sub {
2159 return PVE
::LXC
::Config-
>lock_config($target_vmid, sub {
2160 my ($source_conf, $target_conf, $drive) = $load_and_check_reassign_configs->();
2161 my $source_volid = $drive->{volume
};
2163 my $target_unused = $target_mpkey =~ m/^unused\d+$/;
2165 print "moving volume '$mpkey' from container '$vmid' to '$target_vmid'\n";
2167 my ($storage, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
2169 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
2171 my $new_volid = PVE
::Storage
::rename_volume
(
2177 $drive->{volume
} = $new_volid;
2179 delete $source_conf->{$mpkey};
2180 print "removing volume '${mpkey}' from container '${vmid}' config\n";
2181 PVE
::LXC
::Config-
>write_config($vmid, $source_conf);
2184 if ($target_unused) {
2185 $drive_string = $new_volid;
2187 $drive_string = PVE
::LXC
::Config-
>print_volume($target_mpkey, $drive);
2190 if ($target_unused) {
2191 $target_conf->{$target_mpkey} = $drive_string;
2193 my $running = PVE
::LXC
::check_running
($target_vmid);
2194 my $param = { $target_mpkey => $drive_string };
2195 my $errors = PVE
::LXC
::Config-
>update_pct_config(
2201 $rpcenv->warn($errors->{$_}) for keys $errors->%*;
2204 PVE
::LXC
::Config-
>write_config($target_vmid, $target_conf);
2205 $target_conf = PVE
::LXC
::Config-
>load_config($target_vmid);
2207 PVE
::LXC
::update_lxc_config
($target_vmid, $target_conf) if !$target_unused;
2208 print "target container '$target_vmid' updated with '$target_mpkey'\n";
2210 # remove possible replication snapshots
2211 if (PVE
::Storage
::volume_has_feature
($storecfg,'replicate', $source_volid)) {
2213 PVE
::Replication
::prepare
(
2223 $rpcenv->warn("Failed to remove replication snapshots on volume ".
2224 "'${target_mpkey}'. Manual cleanup could be necessary. " .
2232 if ($target_vmid && $storage) {
2233 my $msg = "either set 'storage' or 'target-vmid', but not both";
2234 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
2235 } elsif ($target_vmid) {
2236 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
2237 if $authuser ne 'root@pam';
2239 my (undef, undef, $drive) = $load_and_check_reassign_configs->();
2240 my $storeid = PVE
::Storage
::parse_volume_id
($drive->{volume
});
2241 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2242 return $rpcenv->fork_worker(
2244 "${vmid}-${mpkey}>${target_vmid}-${target_mpkey}",
2248 } elsif ($storage) {
2249 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
2250 &$move_to_storage_checks();
2252 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $storage_realcmd);
2255 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
2261 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
2262 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
2266 __PACKAGE__-
>register_method({
2267 name
=> 'vm_pending',
2268 path
=> '{vmid}/pending',
2271 description
=> 'Get container configuration, including pending changes.',
2273 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2276 additionalProperties
=> 0,
2278 node
=> get_standard_option
('pve-node'),
2279 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
2288 description
=> 'Configuration option name.',
2292 description
=> 'Current value.',
2297 description
=> 'Pending value.',
2302 description
=> "Indicates a pending delete request if present and not 0.",
2314 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
2316 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
2318 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);