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 if (defined($pool)) {
247 $rpcenv->check_pool_exist($pool);
248 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
251 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
253 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
255 } elsif ($restore && $force && $same_container_exists &&
256 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
257 # OK: user has VM.Backup permissions, and want to restore an existing VM
259 # we don't want to restore a container-provided FW conf in this case
260 # since the user is lacking permission to configure the container's FW
261 $skip_fw_config_restore = 1;
263 # error out if a user tries to change from unprivileged to privileged
264 # explicit change is checked here, implicit is checked down below or happening in root-only paths
265 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
266 if ($conf->{unprivileged
} && defined($unprivileged) && !$unprivileged) {
267 raise_perm_exc
("cannot change from unprivileged to privileged without VM.Allocate");
273 my $ostemplate = extract_param
($param, 'ostemplate');
274 my $storage = extract_param
($param, 'storage') // 'local';
276 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, undef, $param, [], $unprivileged);
278 my $storage_cfg = cfs_read_file
("storage.cfg");
281 if ($ostemplate eq '-') {
282 die "pipe requires cli environment\n"
283 if $rpcenv->{type
} ne 'cli';
284 die "pipe can only be used with restore tasks\n"
287 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
289 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
290 $archive = $ostemplate;
294 my $check_and_activate_storage = sub {
297 my $scfg = PVE
::Storage
::storage_check_enabled
($storage_cfg, $sid, $node);
299 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
300 if !$scfg->{content
}->{rootdir
};
302 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
304 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
305 $used_storages{$sid} = 1;
310 my $is_root = $authuser eq 'root@pam';
312 my $no_disk_param = {};
314 my $storage_only_mode = 1;
315 foreach my $opt (keys %$param) {
316 my $value = $param->{$opt};
317 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
318 # allow to use simple numbers (add default storage in that case)
319 if ($value =~ m/^\d+(\.\d+)?$/) {
320 $mp_param->{$opt} = "$storage:$value";
322 $mp_param->{$opt} = $value;
324 $storage_only_mode = 0;
325 } elsif ($opt =~ m/^unused\d+$/) {
326 warn "ignoring '$opt', cannot create/restore with unused volume\n";
327 delete $param->{$opt};
329 $no_disk_param->{$opt} = $value;
333 die "mount points configured, but 'rootfs' not set - aborting\n"
334 if !$storage_only_mode && !defined($mp_param->{rootfs
});
336 # check storage access, activate storage
337 my $delayed_mp_param = {};
338 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
339 my ($ms, $mountpoint) = @_;
341 my $volid = $mountpoint->{volume
};
342 my $mp = $mountpoint->{mp
};
344 if ($mountpoint->{type
} ne 'volume') { # bind or device
345 die "Only root can pass arbitrary filesystem paths.\n"
348 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
349 &$check_and_activate_storage($sid);
353 # check/activate default storage
354 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
356 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
358 $conf->{unprivileged
} = 1 if $unprivileged;
360 my $emsg = $restore ?
"unable to restore CT $vmid -" : "unable to create CT $vmid -";
362 eval { PVE
::LXC
::Config-
>create_and_lock_config($vmid, $force) };
363 die "$emsg $@" if $@;
368 my $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
372 my $orig_mp_param; # only used if $restore
374 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
375 if ($archive ne '-') {
377 print "recovering backed-up configuration from '$archive'\n";
378 ($orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
380 $was_template = delete $orig_conf->{template
};
382 # When we're root call 'restore_configuration' with restricted=0,
383 # causing it to restore the raw lxc entries, among which there may be
384 # 'lxc.idmap' entries. We need to make sure that the extracted contents
385 # of the container match up with the restored configuration afterwards:
386 $conf->{lxc
} = $orig_conf->{lxc
} if $is_root;
388 $conf->{unprivileged
} = $orig_conf->{unprivileged
}
389 if !defined($unprivileged) && defined($orig_conf->{unprivileged
});
391 # implicit privileged change is checked here
392 if ($old_conf->{unprivileged
} && !$conf->{unprivileged
}) {
393 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Allocate']);
397 if ($storage_only_mode) {
399 if (!defined($orig_mp_param)) {
400 print "recovering backed-up configuration from '$archive'\n";
401 (undef, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
403 $mp_param = $orig_mp_param;
404 die "rootfs configuration could not be recovered, please check and specify manually!\n"
405 if !defined($mp_param->{rootfs
});
406 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
407 my ($ms, $mountpoint) = @_;
408 my $type = $mountpoint->{type
};
409 if ($type eq 'volume') {
410 die "unable to detect disk size - please specify $ms (size)\n"
411 if !defined($mountpoint->{size
});
412 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
413 delete $mountpoint->{size
};
414 $mountpoint->{volume
} = "$storage:$disksize";
415 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
417 my $type = $mountpoint->{type
};
418 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
419 if ($ms eq 'rootfs');
420 die "restoring '$ms' to $type mount is only possible for root\n"
423 if ($mountpoint->{backup
}) {
424 warn "WARNING - unsupported configuration!\n";
425 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
426 warn "mount point configuration will be restored after archive extraction!\n";
427 warn "contained files will be restored to wrong directory!\n";
429 delete $mp_param->{$ms}; # actually delay bind/dev mps
430 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
434 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
438 die "$emsg $@" if $@;
440 # up until here we did not modify the container, besides the lock
445 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
447 # we always have the 'create' lock so check for more than 1 entry
448 if (scalar(keys %$old_conf) > 1) {
449 # destroy old container volumes
450 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, { lock => 'create' });
454 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
455 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
456 print "restoring '$archive' now..\n"
457 if $restore && $archive ne '-';
458 PVE
::LXC
::Create
::restore_archive
($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
461 print "merging backed-up and given configuration..\n";
462 PVE
::LXC
::Create
::restore_configuration
($vmid, $storage_cfg, $archive, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
463 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
464 $lxc_setup->template_fixup($conf);
466 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
467 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
468 $lxc_setup->post_create_hook($password, $ssh_keys);
472 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
473 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
476 $conf->{hostname
} ||= "CT$vmid";
477 $conf->{memory
} ||= 512;
478 $conf->{swap
} //= 512;
479 foreach my $mp (keys %$delayed_mp_param) {
480 $conf->{$mp} = $delayed_mp_param->{$mp};
482 # If the template flag was set, we try to convert again to template after restore
484 print STDERR
"Convert restored container to template...\n";
485 PVE
::LXC
::template_create
($vmid, $conf);
486 $conf->{template
} = 1;
488 PVE
::LXC
::Config-
>write_config($vmid, $conf);
491 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
492 eval { PVE
::LXC
::Config-
>destroy_config($vmid) };
496 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
498 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
499 if $start_after_create;
502 my $workername = $restore ?
'vzrestore' : 'vzcreate';
505 PVE
::LXC
::Config-
>lock_config($vmid, $code);
508 # if we aborted before changing the container, we must remove the create lock
510 PVE
::LXC
::Config-
>remove_lock($vmid, 'create');
516 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
519 __PACKAGE__-
>register_method({
524 description
=> "Directory index",
529 additionalProperties
=> 0,
531 node
=> get_standard_option
('pve-node'),
532 vmid
=> get_standard_option
('pve-vmid'),
540 subdir
=> { type
=> 'string' },
543 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
549 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
552 { subdir
=> 'config' },
553 { subdir
=> 'pending' },
554 { subdir
=> 'status' },
555 { subdir
=> 'vncproxy' },
556 { subdir
=> 'termproxy' },
557 { subdir
=> 'vncwebsocket' },
558 { subdir
=> 'spiceproxy' },
559 { subdir
=> 'migrate' },
560 { subdir
=> 'clone' },
561 # { subdir => 'initlog' },
563 { subdir
=> 'rrddata' },
564 { subdir
=> 'firewall' },
565 { subdir
=> 'snapshot' },
566 { subdir
=> 'resize' },
573 __PACKAGE__-
>register_method({
575 path
=> '{vmid}/rrd',
577 protected
=> 1, # fixme: can we avoid that?
579 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
581 description
=> "Read VM RRD statistics (returns PNG)",
583 additionalProperties
=> 0,
585 node
=> get_standard_option
('pve-node'),
586 vmid
=> get_standard_option
('pve-vmid'),
588 description
=> "Specify the time frame you are interested in.",
590 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
593 description
=> "The list of datasources you want to display.",
594 type
=> 'string', format
=> 'pve-configid-list',
597 description
=> "The RRD consolidation function",
599 enum
=> [ 'AVERAGE', 'MAX' ],
607 filename
=> { type
=> 'string' },
613 return PVE
::RRD
::create_rrd_graph
(
614 "pve2-vm/$param->{vmid}", $param->{timeframe
},
615 $param->{ds
}, $param->{cf
});
619 __PACKAGE__-
>register_method({
621 path
=> '{vmid}/rrddata',
623 protected
=> 1, # fixme: can we avoid that?
625 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
627 description
=> "Read VM RRD statistics",
629 additionalProperties
=> 0,
631 node
=> get_standard_option
('pve-node'),
632 vmid
=> get_standard_option
('pve-vmid'),
634 description
=> "Specify the time frame you are interested in.",
636 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
639 description
=> "The RRD consolidation function",
641 enum
=> [ 'AVERAGE', 'MAX' ],
656 return PVE
::RRD
::create_rrd_data
(
657 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
660 __PACKAGE__-
>register_method({
661 name
=> 'destroy_vm',
666 description
=> "Destroy the container (also delete all uses files).",
668 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
671 additionalProperties
=> 0,
673 node
=> get_standard_option
('pve-node'),
674 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
677 description
=> "Force destroy, even if running.",
683 description
=> "Remove container from all related configurations."
684 ." For example, backup jobs, replication jobs or HA."
685 ." Related ACLs and Firewall entries will *always* be removed.",
689 'destroy-unreferenced-disks' => {
691 description
=> "If set, destroy additionally all disks with the VMID from all"
692 ." enabled storages which are not referenced in the config.",
703 my $rpcenv = PVE
::RPCEnvironment
::get
();
704 my $authuser = $rpcenv->get_user();
705 my $vmid = $param->{vmid
};
707 # test if container exists
709 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
710 my $early_checks = sub {
712 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
713 PVE
::LXC
::Config-
>check_lock($conf);
715 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("ct:$vmid");
717 if (!$param->{purge
}) {
718 die "unable to remove CT $vmid - used in HA resources and purge parameter not set.\n"
721 # do not allow destroy if there are replication jobs without purge
722 my $repl_conf = PVE
::ReplicationConfig-
>new();
723 $repl_conf->check_for_existing_jobs($vmid);
729 $early_checks->($conf);
731 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
732 die $running_error_msg if !$param->{force
} && PVE
::LXC
::check_running
($vmid); # check early
735 # reload config after lock
736 $conf = PVE
::LXC
::Config-
>load_config($vmid);
737 my $ha_managed = $early_checks->($conf);
739 if (PVE
::LXC
::check_running
($vmid)) {
740 die $running_error_msg if !$param->{force
};
741 warn "forced to stop CT $vmid before destroying!\n";
743 PVE
::LXC
::vm_stop
($vmid, 1);
745 run_command
(['ha-manager', 'crm-command', 'stop', "ct:$vmid", '120']);
749 my $storage_cfg = cfs_read_file
("storage.cfg");
750 PVE
::LXC
::destroy_lxc_container
(
754 { lock => 'destroyed' },
755 $param->{'destroy-unreferenced-disks'},
758 PVE
::AccessControl
::remove_vm_access
($vmid);
759 PVE
::Firewall
::remove_vmfw_conf
($vmid);
760 if ($param->{purge
}) {
761 print "purging CT $vmid from related configurations..\n";
762 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
763 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
766 PVE
::HA
::Config
::delete_service_from_config
("ct:$vmid");
767 print "NOTE: removed CT $vmid from HA resource configuration.\n";
771 # only now remove the zombie config, else we can have reuse race
772 PVE
::LXC
::Config-
>destroy_config($vmid);
775 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
777 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
782 __PACKAGE__-
>register_method ({
784 path
=> '{vmid}/vncproxy',
788 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
790 description
=> "Creates a TCP VNC proxy connections.",
792 additionalProperties
=> 0,
794 node
=> get_standard_option
('pve-node'),
795 vmid
=> get_standard_option
('pve-vmid'),
799 description
=> "use websocket instead of standard VNC.",
803 description
=> "sets the width of the console in pixels.",
810 description
=> "sets the height of the console in pixels.",
818 additionalProperties
=> 0,
820 user
=> { type
=> 'string' },
821 ticket
=> { type
=> 'string' },
822 cert
=> { type
=> 'string' },
823 port
=> { type
=> 'integer' },
824 upid
=> { type
=> 'string' },
830 my $rpcenv = PVE
::RPCEnvironment
::get
();
832 my $authuser = $rpcenv->get_user();
834 my $vmid = $param->{vmid
};
835 my $node = $param->{node
};
837 my $authpath = "/vms/$vmid";
839 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
841 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
844 my ($remip, $family);
846 if ($node ne PVE
::INotify
::nodename
()) {
847 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
849 $family = PVE
::Tools
::get_host_address_family
($node);
852 my $port = PVE
::Tools
::next_vnc_port
($family);
854 # NOTE: vncterm VNC traffic is already TLS encrypted,
855 # so we select the fastest chipher here (or 'none'?)
856 my $remcmd = $remip ?
857 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
859 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
860 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
862 my $shcmd = [ '/usr/bin/dtach', '-A',
863 "/var/run/dtach/vzctlconsole$vmid",
864 '-r', 'winch', '-z', @$concmd];
869 syslog
('info', "starting lxc vnc proxy $upid\n");
873 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
874 '-timeout', $timeout, '-authpath', $authpath,
875 '-perm', 'VM.Console'];
877 if ($param->{width
}) {
878 push @$cmd, '-width', $param->{width
};
881 if ($param->{height
}) {
882 push @$cmd, '-height', $param->{height
};
885 if ($param->{websocket
}) {
886 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
887 push @$cmd, '-notls', '-listen', 'localhost';
890 push @$cmd, '-c', @$remcmd, @$shcmd;
892 run_command
($cmd, keeplocale
=> 1);
897 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
899 PVE
::Tools
::wait_for_vnc_port
($port);
910 __PACKAGE__-
>register_method ({
912 path
=> '{vmid}/termproxy',
916 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
918 description
=> "Creates a TCP proxy connection.",
920 additionalProperties
=> 0,
922 node
=> get_standard_option
('pve-node'),
923 vmid
=> get_standard_option
('pve-vmid'),
927 additionalProperties
=> 0,
929 user
=> { type
=> 'string' },
930 ticket
=> { type
=> 'string' },
931 port
=> { type
=> 'integer' },
932 upid
=> { type
=> 'string' },
938 my $rpcenv = PVE
::RPCEnvironment
::get
();
940 my $authuser = $rpcenv->get_user();
942 my $vmid = $param->{vmid
};
943 my $node = $param->{node
};
945 my $authpath = "/vms/$vmid";
947 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
949 my ($remip, $family);
951 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
952 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
954 $family = PVE
::Tools
::get_host_address_family
($node);
957 my $port = PVE
::Tools
::next_vnc_port
($family);
959 my $remcmd = $remip ?
960 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
962 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
963 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
965 my $shcmd = [ '/usr/bin/dtach', '-A',
966 "/var/run/dtach/vzctlconsole$vmid",
967 '-r', 'winch', '-z', @$concmd];
972 syslog
('info', "starting lxc termproxy $upid\n");
974 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
975 '--perm', 'VM.Console', '--'];
976 push @$cmd, @$remcmd, @$shcmd;
978 PVE
::Tools
::run_command
($cmd);
981 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
983 PVE
::Tools
::wait_for_vnc_port
($port);
993 __PACKAGE__-
>register_method({
994 name
=> 'vncwebsocket',
995 path
=> '{vmid}/vncwebsocket',
998 description
=> "You also need to pass a valid ticket (vncticket).",
999 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1001 description
=> "Opens a weksocket for VNC traffic.",
1003 additionalProperties
=> 0,
1005 node
=> get_standard_option
('pve-node'),
1006 vmid
=> get_standard_option
('pve-vmid'),
1008 description
=> "Ticket from previous call to vncproxy.",
1013 description
=> "Port number returned by previous vncproxy call.",
1023 port
=> { type
=> 'string' },
1029 my $rpcenv = PVE
::RPCEnvironment
::get
();
1031 my $authuser = $rpcenv->get_user();
1033 my $authpath = "/vms/$param->{vmid}";
1035 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1037 my $port = $param->{port
};
1039 return { port
=> $port };
1042 __PACKAGE__-
>register_method ({
1043 name
=> 'spiceproxy',
1044 path
=> '{vmid}/spiceproxy',
1049 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1051 description
=> "Returns a SPICE configuration to connect to the CT.",
1053 additionalProperties
=> 0,
1055 node
=> get_standard_option
('pve-node'),
1056 vmid
=> get_standard_option
('pve-vmid'),
1057 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1060 returns
=> get_standard_option
('remote-viewer-config'),
1064 my $vmid = $param->{vmid
};
1065 my $node = $param->{node
};
1066 my $proxy = $param->{proxy
};
1068 my $authpath = "/vms/$vmid";
1069 my $permissions = 'VM.Console';
1071 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1073 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
1075 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
1077 my $shcmd = ['/usr/bin/dtach', '-A',
1078 "/var/run/dtach/vzctlconsole$vmid",
1079 '-r', 'winch', '-z', @$concmd];
1081 my $title = "CT $vmid";
1083 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1087 __PACKAGE__-
>register_method({
1088 name
=> 'migrate_vm',
1089 path
=> '{vmid}/migrate',
1093 description
=> "Migrate the container to another node. Creates a new migration task.",
1095 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1098 additionalProperties
=> 0,
1100 node
=> get_standard_option
('pve-node'),
1101 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1102 target
=> get_standard_option
('pve-node', {
1103 description
=> "Target node.",
1104 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1106 'target-storage' => get_standard_option
('pve-targetstorage'),
1109 description
=> "Use online/live migration.",
1114 description
=> "Use restart migration",
1119 description
=> "Timeout in seconds for shutdown for restart migration",
1124 description
=> "Override I/O bandwidth limit (in KiB/s).",
1128 default => 'migrate limit from datacenter or storage config',
1134 description
=> "the task ID.",
1139 my $rpcenv = PVE
::RPCEnvironment
::get
();
1141 my $authuser = $rpcenv->get_user();
1143 my $target = extract_param
($param, 'target');
1145 my $localnode = PVE
::INotify
::nodename
();
1146 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1148 PVE
::Cluster
::check_cfs_quorum
();
1150 PVE
::Cluster
::check_node_exists
($target);
1152 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1154 my $vmid = extract_param
($param, 'vmid');
1157 PVE
::LXC
::Config-
>load_config($vmid);
1159 # try to detect errors early
1160 if (PVE
::LXC
::check_running
($vmid)) {
1161 die "can't migrate running container without --online or --restart\n"
1162 if !$param->{online
} && !$param->{restart
};
1165 if (my $targetstorage = delete $param->{'target-storage'}) {
1166 my $storecfg = PVE
::Storage
::config
();
1167 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
1168 raise_param_exc
({ targetstorage
=> "failed to parse storage map: $@" })
1171 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
1172 if !defined($storagemap->{identity
});
1174 foreach my $target_sid (values %{$storagemap->{entries
}}) {
1175 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
1178 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
1179 if $storagemap->{default};
1181 $param->{storagemap
} = $storagemap;
1184 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1189 my $service = "ct:$vmid";
1191 my $cmd = ['ha-manager', 'migrate', $service, $target];
1193 print "Requesting HA migration for CT $vmid to node $target\n";
1195 PVE
::Tools
::run_command
($cmd);
1200 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1205 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1209 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1212 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1216 __PACKAGE__-
>register_method({
1217 name
=> 'vm_feature',
1218 path
=> '{vmid}/feature',
1222 description
=> "Check if feature for virtual machine is available.",
1224 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1227 additionalProperties
=> 0,
1229 node
=> get_standard_option
('pve-node'),
1230 vmid
=> get_standard_option
('pve-vmid'),
1232 description
=> "Feature to check.",
1234 enum
=> [ 'snapshot', 'clone', 'copy' ],
1236 snapname
=> get_standard_option
('pve-snapshot-name', {
1244 hasFeature
=> { type
=> 'boolean' },
1247 #items => { type => 'string' },
1254 my $node = extract_param
($param, 'node');
1256 my $vmid = extract_param
($param, 'vmid');
1258 my $snapname = extract_param
($param, 'snapname');
1260 my $feature = extract_param
($param, 'feature');
1262 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1265 my $snap = $conf->{snapshots
}->{$snapname};
1266 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1269 my $storage_cfg = PVE
::Storage
::config
();
1270 #Maybe include later
1271 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1272 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1275 hasFeature
=> $hasFeature,
1276 #nodes => [ keys %$nodelist ],
1280 __PACKAGE__-
>register_method({
1282 path
=> '{vmid}/template',
1286 description
=> "Create a Template.",
1288 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1289 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1292 additionalProperties
=> 0,
1294 node
=> get_standard_option
('pve-node'),
1295 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1298 returns
=> { type
=> 'null'},
1302 my $rpcenv = PVE
::RPCEnvironment
::get
();
1304 my $authuser = $rpcenv->get_user();
1306 my $node = extract_param
($param, 'node');
1308 my $vmid = extract_param
($param, 'vmid');
1310 my $updatefn = sub {
1312 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1313 PVE
::LXC
::Config-
>check_lock($conf);
1315 die "unable to create template, because CT contains snapshots\n"
1316 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1318 die "you can't convert a template to a template\n"
1319 if PVE
::LXC
::Config-
>is_template($conf);
1321 die "you can't convert a CT to template if the CT is running\n"
1322 if PVE
::LXC
::check_running
($vmid);
1325 PVE
::LXC
::template_create
($vmid, $conf);
1327 $conf->{template
} = 1;
1329 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1330 # and remove lxc config
1331 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1334 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1337 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1342 __PACKAGE__-
>register_method({
1344 path
=> '{vmid}/clone',
1348 description
=> "Create a container clone/copy",
1350 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1351 "and 'VM.Allocate' permissions " .
1352 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1353 "'Datastore.AllocateSpace' on any used storage.",
1356 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1358 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1359 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1364 additionalProperties
=> 0,
1366 node
=> get_standard_option
('pve-node'),
1367 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1368 newid
=> get_standard_option
('pve-vmid', {
1369 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1370 description
=> 'VMID for the clone.' }),
1373 type
=> 'string', format
=> 'dns-name',
1374 description
=> "Set a hostname for the new CT.",
1379 description
=> "Description for the new CT.",
1383 type
=> 'string', format
=> 'pve-poolid',
1384 description
=> "Add the new CT to the specified pool.",
1386 snapname
=> get_standard_option
('pve-snapshot-name', {
1389 storage
=> get_standard_option
('pve-storage-id', {
1390 description
=> "Target storage for full clone.",
1396 description
=> "Create a full copy of all disks. This is always done when " .
1397 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1399 target
=> get_standard_option
('pve-node', {
1400 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1404 description
=> "Override I/O bandwidth limit (in KiB/s).",
1408 default => 'clone limit from datacenter or storage config',
1418 my $rpcenv = PVE
::RPCEnvironment
::get
();
1419 my $authuser = $rpcenv->get_user();
1421 my $node = extract_param
($param, 'node');
1422 my $vmid = extract_param
($param, 'vmid');
1423 my $newid = extract_param
($param, 'newid');
1424 my $pool = extract_param
($param, 'pool');
1425 if (defined($pool)) {
1426 $rpcenv->check_pool_exist($pool);
1428 my $snapname = extract_param
($param, 'snapname');
1429 my $storage = extract_param
($param, 'storage');
1430 my $target = extract_param
($param, 'target');
1431 my $localnode = PVE
::INotify
::nodename
();
1433 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1435 PVE
::Cluster
::check_node_exists
($target) if $target;
1437 my $storecfg = PVE
::Storage
::config
();
1440 # check if storage is enabled on local node
1441 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1443 # check if storage is available on target node
1444 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
1445 # clone only works if target storage is shared
1446 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1447 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1451 PVE
::Cluster
::check_cfs_quorum
();
1454 my $mountpoints = {};
1459 PVE
::LXC
::Config-
>create_and_lock_config($newid, 0);
1460 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1462 my $lock_and_reload = sub {
1463 my ($vmid, $code) = @_;
1464 return PVE
::LXC
::Config-
>lock_config($vmid, sub {
1465 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1466 die "Lost 'create' config lock, aborting.\n"
1467 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1469 return $code->($conf);
1473 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1475 $running = PVE
::LXC
::check_running
($vmid) || 0;
1477 my $full = extract_param
($param, 'full');
1478 if (!defined($full)) {
1479 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1483 die "parameter 'storage' not allowed for linked clones\n"
1484 if defined($storage) && !$full;
1486 die "snapshot '$snapname' does not exist\n"
1487 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1489 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1492 for my $opt (sort keys %$src_conf) {
1493 next if $opt =~ m/^unused\d+$/;
1495 my $value = $src_conf->{$opt};
1497 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1498 my $mp = PVE
::LXC
::Config-
>parse_volume($opt, $value);
1500 if ($mp->{type
} eq 'volume') {
1501 my $volid = $mp->{volume
};
1503 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1504 $sid = $storage if defined($storage);
1505 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1506 if (!$scfg->{shared
}) {
1508 warn "found non-shared volume: $volid\n" if $target;
1511 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1514 die "Cannot do full clones on a running container without snapshots\n"
1515 if $running && !defined($snapname);
1516 $fullclone->{$opt} = 1;
1518 # not full means clone instead of copy
1519 die "Linked clone feature for '$volid' is not available\n"
1520 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
1523 $mountpoints->{$opt} = $mp;
1524 push @$vollist, $volid;
1527 # TODO: allow bind mounts?
1528 die "unable to clone mountpoint '$opt' (type $mp->{type})\n";
1530 } elsif ($opt =~ m/^net(\d+)$/) {
1531 # always change MAC! address
1532 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1533 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1534 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1535 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1537 # copy everything else
1538 $newconf->{$opt} = $value;
1541 die "can't clone CT to node '$target' (CT uses local storage)\n"
1542 if $target && !$sharedvm;
1544 # Replace the 'disk' lock with a 'create' lock.
1545 $newconf->{lock} = 'create';
1547 # delete all snapshot related config options
1548 delete $newconf->@{qw(snapshots parent snaptime snapstate)};
1550 delete $newconf->{pending
};
1551 delete $newconf->{template
};
1553 $newconf->{hostname
} = $param->{hostname
} if $param->{hostname
};
1554 $newconf->{description
} = $param->{description
} if $param->{description
};
1556 $lock_and_reload->($newid, sub {
1557 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1561 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1562 warn "Failed to remove source CT config lock - $@\n" if $@;
1565 $lock_and_reload->($newid, sub {
1566 PVE
::LXC
::Config-
>destroy_config($newid);
1567 PVE
::Firewall
::remove_vmfw_conf
($newid);
1570 warn "Failed to remove target CT config - $@\n" if $@;
1575 my $update_conf = sub {
1576 my ($key, $value) = @_;
1577 return $lock_and_reload->($newid, sub {
1579 $conf->{$key} = $value;
1580 PVE
::LXC
::Config-
>write_config($newid, $conf);
1587 my $newvollist = [];
1589 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1590 die "unexpected state change\n" if $verify_running != $running;
1596 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1598 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1599 my $bwlimit = extract_param
($param, 'bwlimit');
1601 foreach my $opt (keys %$mountpoints) {
1602 my $mp = $mountpoints->{$opt};
1603 my $volid = $mp->{volume
};
1606 if ($fullclone->{$opt}) {
1607 print "create full clone of mountpoint $opt ($volid)\n";
1608 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1609 my $target_storage = $storage // $source_storage;
1610 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1611 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1613 print "create linked clone of mount point $opt ($volid)\n";
1614 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1617 push @$newvollist, $newvolid;
1618 $mp->{volume
} = $newvolid;
1620 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1623 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1625 $lock_and_reload->($newid, sub {
1627 my $rootdir = PVE
::LXC
::mount_all
($newid, $storecfg, $conf, 1);
1629 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1630 $lxc_setup->post_clone_hook($conf);
1633 eval { PVE
::LXC
::umount_all
($newid, $storecfg, $conf, 1); };
1643 # Unlock the source config in any case:
1644 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1648 # Now cleanup the config & disks:
1649 sleep 1; # some storages like rbd need to wait before release volume - really?
1651 foreach my $volid (@$newvollist) {
1652 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1657 $lock_and_reload->($newid, sub {
1658 PVE
::LXC
::Config-
>destroy_config($newid);
1659 PVE
::Firewall
::remove_vmfw_conf
($newid);
1662 warn "Failed to remove target CT config - $@\n" if $@;
1664 die "clone failed: $err";
1667 $lock_and_reload->($newid, sub {
1668 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1671 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1672 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1673 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1675 PVE
::LXC
::Config-
>move_config_to_node($newid, $target);
1682 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1686 __PACKAGE__-
>register_method({
1687 name
=> 'resize_vm',
1688 path
=> '{vmid}/resize',
1692 description
=> "Resize a container mount point.",
1694 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1697 additionalProperties
=> 0,
1699 node
=> get_standard_option
('pve-node'),
1700 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1703 description
=> "The disk you want to resize.",
1704 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys()],
1708 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1709 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.",
1713 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1721 description
=> "the task ID.",
1726 my $rpcenv = PVE
::RPCEnvironment
::get
();
1728 my $authuser = $rpcenv->get_user();
1730 my $node = extract_param
($param, 'node');
1732 my $vmid = extract_param
($param, 'vmid');
1734 my $digest = extract_param
($param, 'digest');
1736 my $sizestr = extract_param
($param, 'size');
1737 my $ext = ($sizestr =~ s/^\+//);
1738 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1739 die "invalid size string" if !defined($newsize);
1741 die "no options specified\n" if !scalar(keys %$param);
1743 my $storage_cfg = cfs_read_file
("storage.cfg");
1747 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1748 PVE
::LXC
::Config-
>check_lock($conf);
1750 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $conf, $param, [], $conf->{unprivileged
});
1752 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1754 my $running = PVE
::LXC
::check_running
($vmid);
1756 my $disk = $param->{disk
};
1757 my $mp = PVE
::LXC
::Config-
>parse_volume($disk, $conf->{$disk});
1759 my $volid = $mp->{volume
};
1761 my (undef, undef, $owner, undef, undef, undef, $format) =
1762 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1764 die "can't resize mount point owned by another container ($owner)"
1767 die "can't resize volume: $disk if snapshot exists\n"
1768 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1770 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1772 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1774 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1776 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1778 die "Could not determine current size of volume '$volid'\n" if !defined($size);
1780 $newsize += $size if $ext;
1781 $newsize = int($newsize);
1783 die "unable to shrink disk size\n" if $newsize < $size;
1785 die "disk is already at specified size\n" if $size == $newsize;
1787 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1789 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1790 # we pass 0 here (parameter only makes sense for qemu)
1791 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1793 $mp->{size
} = $newsize;
1794 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1796 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1798 if ($format eq 'raw') {
1799 # we need to ensure that the volume is mapped, if not needed this is a NOP
1800 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1801 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1805 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1806 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1807 die "internal error: CT running but mount point not attached to a loop device"
1809 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1811 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1812 # to be visible to it in its namespace.
1813 # To not interfere with the rest of the system we unshare the current mount namespace,
1814 # mount over /tmp and then run resize2fs.
1816 # interestingly we don't need to e2fsck on mounted systems...
1817 my $quoted = PVE
::Tools
::shellquote
($path);
1818 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1820 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1822 warn "Failed to update the container's filesystem: $@\n" if $@;
1825 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1826 PVE
::Tools
::run_command
(['resize2fs', $path]);
1828 warn "Failed to update the container's filesystem: $@\n" if $@;
1830 # always un-map if not running, this is a NOP if not needed
1831 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1836 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1839 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1842 __PACKAGE__-
>register_method({
1843 name
=> 'move_volume',
1844 path
=> '{vmid}/move_volume',
1848 description
=> "Move a rootfs-/mp-volume to a different storage or to a different container.",
1850 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1851 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
1852 "a volume to another container, you need the permissions on the ".
1853 "target container as well.",
1854 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1857 additionalProperties
=> 0,
1859 node
=> get_standard_option
('pve-node'),
1860 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1861 'target-vmid' => get_standard_option
('pve-vmid', {
1862 completion
=> \
&PVE
::LXC
::complete_ctid
,
1867 #TODO: check how to handle unused mount points as the mp parameter is not configured
1868 enum
=> [ PVE
::LXC
::Config-
>valid_volume_keys_with_unused() ],
1869 description
=> "Volume which will be moved.",
1871 storage
=> get_standard_option
('pve-storage-id', {
1872 description
=> "Target Storage.",
1873 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1878 description
=> "Delete the original volume after successful copy. By default the " .
1879 "original is kept as an unused volume entry.",
1885 description
=> 'Prevent changes if current configuration file has different SHA1 " .
1886 "digest. This can be used to prevent concurrent modifications.',
1891 description
=> "Override I/O bandwidth limit (in KiB/s).",
1895 default => 'clone limit from datacenter or storage config',
1897 'target-volume' => {
1899 description
=> "The config key the volume will be moved to. Default is the " .
1900 "source volume key.",
1901 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys_with_unused()],
1904 'target-digest' => {
1906 description
=> 'Prevent changes if current configuration file of the target " .
1907 "container has a different SHA1 digest. This can be used to prevent " .
1908 "concurrent modifications.',
1920 my $rpcenv = PVE
::RPCEnvironment
::get
();
1922 my $authuser = $rpcenv->get_user();
1924 my $vmid = extract_param
($param, 'vmid');
1926 my $target_vmid = extract_param
($param, 'target-vmid');
1928 my $storage = extract_param
($param, 'storage');
1930 my $mpkey = extract_param
($param, 'volume');
1932 my $target_mpkey = extract_param
($param, 'target-volume') // $mpkey;
1934 my $digest = extract_param
($param, 'digest');
1936 my $target_digest = extract_param
($param, 'target-digest');
1938 my $lockname = 'disk';
1940 my ($mpdata, $old_volid);
1942 die "either set storage or target-vmid, but not both\n"
1943 if $storage && $target_vmid;
1945 my $storecfg = PVE
::Storage
::config
();
1947 my $move_to_storage_checks = sub {
1948 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1949 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1950 PVE
::LXC
::Config-
>check_lock($conf);
1952 die "cannot move volumes of a running container\n"
1953 if PVE
::LXC
::check_running
($vmid);
1955 if ($mpkey =~ m/^unused\d+$/) {
1956 die "cannot move volume '$mpkey', only configured volumes can be moved to ".
1957 "another storage\n";
1960 $mpdata = PVE
::LXC
::Config-
>parse_volume($mpkey, $conf->{$mpkey});
1961 $old_volid = $mpdata->{volume
};
1963 die "you can't move a volume with snapshots and delete the source\n"
1964 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1966 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1968 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1972 my $storage_realcmd = sub {
1974 PVE
::Cluster
::log_msg
(
1977 "move volume CT $vmid: move --volume $mpkey --storage $storage"
1980 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1981 my $storage_cfg = PVE
::Storage
::config
();
1986 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1987 my $bwlimit = extract_param
($param, 'bwlimit');
1988 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1989 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
1991 [$source_storage, $storage],
1994 $new_volid = PVE
::LXC
::copy_volume
(
2003 if (PVE
::LXC
::Config-
>is_template($conf)) {
2004 PVE
::Storage
::activate_volumes
($storage_cfg, [ $new_volid ]);
2005 my $template_volid = PVE
::Storage
::vdisk_create_base
($storage_cfg, $new_volid);
2006 $mpdata->{volume
} = $template_volid;
2008 $mpdata->{volume
} = $new_volid;
2011 PVE
::LXC
::Config-
>lock_config($vmid, sub {
2012 my $digest = $conf->{digest
};
2013 $conf = PVE
::LXC
::Config-
>load_config($vmid);
2014 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
2016 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint(
2021 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2023 PVE
::LXC
::Config-
>write_config($vmid, $conf);
2027 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2028 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
2034 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
2035 if defined($new_volid);
2041 if ($param->{delete}) {
2043 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
2044 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
2048 PVE
::LXC
::Config-
>lock_config($vmid, sub {
2049 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
2050 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid);
2051 PVE
::LXC
::Config-
>write_config($vmid, $conf);
2057 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
2062 my $load_and_check_reassign_configs = sub {
2063 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
2065 die "Cannot move to/from 'rootfs'\n" if $mpkey eq "rootfs" || $target_mpkey eq "rootfs";
2067 if ($mpkey =~ m/^unused\d+$/ && $target_mpkey !~ m/^unused\d+$/) {
2068 die "Moving an unused volume to a used one is not possible\n";
2070 die "could not find CT ${vmid}\n" if !exists($vmlist->{$vmid});
2071 die "could not find CT ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
2073 my $source_node = $vmlist->{$vmid}->{node
};
2074 my $target_node = $vmlist->{$target_vmid}->{node
};
2076 die "Both containers need to be on the same node ($source_node != $target_node)\n"
2077 if $source_node ne $target_node;
2079 my $source_conf = PVE
::LXC
::Config-
>load_config($vmid);
2080 PVE
::LXC
::Config-
>check_lock($source_conf);
2081 my $target_conf = PVE
::LXC
::Config-
>load_config($target_vmid);
2082 PVE
::LXC
::Config-
>check_lock($target_conf);
2084 die "Can't move volumes from or to template CT\n"
2085 if ($source_conf->{template
} || $target_conf->{template
});
2088 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
2089 die "Container ${vmid}: $@" if $@;
2092 if ($target_digest) {
2093 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
2094 die "Container ${target_vmid}: $@" if $@;
2097 die "volume '${mpkey}' for container '$vmid' does not exist\n"
2098 if !defined($source_conf->{$mpkey});
2100 die "Target volume key '${target_mpkey}' is already in use for container '$target_vmid'\n"
2101 if exists $target_conf->{$target_mpkey};
2103 my $drive = PVE
::LXC
::Config-
>parse_volume(
2105 $source_conf->{$mpkey},
2108 my $source_volid = $drive->{volume
};
2110 die "Volume '${mpkey}' has no associated image\n"
2112 die "Cannot move volume used by a snapshot to another container\n"
2113 if PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($source_conf, $source_volid);
2114 die "Storage does not support moving of this disk to another container\n"
2115 if !PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid);
2116 die "Cannot move a bindmount or device mount to another container\n"
2117 if $drive->{type
} ne "volume";
2118 die "Cannot move volume to another container while the source is running - detach first\n"
2119 if PVE
::LXC
::check_running
($vmid) && $mpkey !~ m/^unused\d+$/;
2121 my $repl_conf = PVE
::ReplicationConfig-
>new();
2122 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
2123 my ($storeid, undef) = PVE
::Storage
::parse_volume_id
($source_volid);
2124 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
2126 die "Cannot move volume on storage '$storeid' to a replicated container - missing replication support\n"
2127 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
2130 return ($source_conf, $target_conf, $drive);
2135 print STDERR
"$msg\n";
2138 my $volume_reassignfn = sub {
2139 return PVE
::LXC
::Config-
>lock_config($vmid, sub {
2140 return PVE
::LXC
::Config-
>lock_config($target_vmid, sub {
2141 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
2142 my $source_volid = $drive->{volume
};
2144 my $target_unused = $target_mpkey =~ m/^unused\d+$/;
2146 print "moving volume '$mpkey' from container '$vmid' to '$target_vmid'\n";
2148 my ($storage, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
2150 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
2152 my $new_volid = PVE
::Storage
::rename_volume
(
2158 $drive->{volume
} = $new_volid;
2160 delete $source_conf->{$mpkey};
2161 print "removing volume '${mpkey}' from container '${vmid}' config\n";
2162 PVE
::LXC
::Config-
>write_config($vmid, $source_conf);
2166 if ($target_unused) {
2167 $drive_string = $new_volid;
2169 $drive_string = PVE
::LXC
::Config-
>print_volume($target_mpkey, $drive);
2172 if ($target_unused) {
2173 $target_conf->{$target_mpkey} = $drive_string;
2175 my $running = PVE
::LXC
::check_running
($target_vmid);
2176 my $param = { $target_mpkey => $drive_string };
2177 my $errors = PVE
::LXC
::Config-
>update_pct_config(
2184 foreach my $key (keys %$errors) {
2185 $rpcenv->warn($errors->{$key});
2189 PVE
::LXC
::Config-
>write_config($target_vmid, $target_conf);
2190 $target_conf = PVE
::LXC
::Config-
>load_config($target_vmid);
2192 PVE
::LXC
::update_lxc_config
($target_vmid, $target_conf) if !$target_unused;
2193 print "target container '$target_vmid' updated with '$target_mpkey'\n";
2195 # remove possible replication snapshots
2196 if (PVE
::Storage
::volume_has_feature
($storecfg,'replicate', $source_volid)) {
2198 PVE
::Replication
::prepare
(
2208 $rpcenv->warn("Failed to remove replication snapshots on volume ".
2209 "'${target_mpkey}'. Manual cleanup could be necessary. " .
2217 if ($target_vmid && $storage) {
2218 my $msg = "either set 'storage' or 'target-vmid', but not both";
2219 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
2220 } elsif ($target_vmid) {
2221 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
2222 if $authuser ne 'root@pam';
2224 if ($vmid eq $target_vmid) {
2225 my $msg = "must be different than source VMID to move disk to another container";
2226 raise_param_exc
({ 'target-vmid' => $msg });
2229 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
2230 my $storeid = PVE
::Storage
::parse_volume_id
($drive->{volume
});
2231 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2232 return $rpcenv->fork_worker(
2234 "${vmid}-${mpkey}>${target_vmid}-${target_mpkey}",
2238 } elsif ($storage) {
2239 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
2240 &$move_to_storage_checks();
2242 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $storage_realcmd);
2245 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
2251 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
2252 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
2256 __PACKAGE__-
>register_method({
2257 name
=> 'vm_pending',
2258 path
=> '{vmid}/pending',
2261 description
=> 'Get container configuration, including pending changes.',
2263 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2266 additionalProperties
=> 0,
2268 node
=> get_standard_option
('pve-node'),
2269 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
2278 description
=> 'Configuration option name.',
2282 description
=> 'Current value.',
2287 description
=> 'Pending value.',
2292 description
=> "Indicates a pending delete request if present and not 0.",
2304 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
2306 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
2308 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);