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 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
287 $archive = $ostemplate;
291 my $check_and_activate_storage = sub {
294 my $scfg = PVE
::Storage
::storage_check_enabled
($storage_cfg, $sid, $node);
296 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
297 if !$scfg->{content
}->{rootdir
};
299 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
301 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
302 $used_storages{$sid} = 1;
307 my $is_root = $authuser eq 'root@pam';
309 my $no_disk_param = {};
311 my $storage_only_mode = 1;
312 foreach my $opt (keys %$param) {
313 my $value = $param->{$opt};
314 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
315 # allow to use simple numbers (add default storage in that case)
316 if ($value =~ m/^\d+(\.\d+)?$/) {
317 $mp_param->{$opt} = "$storage:$value";
319 $mp_param->{$opt} = $value;
321 $storage_only_mode = 0;
322 } elsif ($opt =~ m/^unused\d+$/) {
323 warn "ignoring '$opt', cannot create/restore with unused volume\n";
324 delete $param->{$opt};
326 $no_disk_param->{$opt} = $value;
330 die "mount points configured, but 'rootfs' not set - aborting\n"
331 if !$storage_only_mode && !defined($mp_param->{rootfs
});
333 # check storage access, activate storage
334 my $delayed_mp_param = {};
335 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
336 my ($ms, $mountpoint) = @_;
338 my $volid = $mountpoint->{volume
};
339 my $mp = $mountpoint->{mp
};
341 if ($mountpoint->{type
} ne 'volume') { # bind or device
342 die "Only root can pass arbitrary filesystem paths.\n"
345 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
346 &$check_and_activate_storage($sid);
350 # check/activate default storage
351 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
353 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
355 $conf->{unprivileged
} = 1 if $unprivileged;
357 my $emsg = $restore ?
"unable to restore CT $vmid -" : "unable to create CT $vmid -";
359 eval { PVE
::LXC
::Config-
>create_and_lock_config($vmid, $force) };
360 die "$emsg $@" if $@;
365 my $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
369 my $orig_mp_param; # only used if $restore
371 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
372 if ($archive ne '-') {
374 print "recovering backed-up configuration from '$archive'\n";
375 ($orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
377 $was_template = delete $orig_conf->{template
};
379 # When we're root call 'restore_configuration' with restricted=0,
380 # causing it to restore the raw lxc entries, among which there may be
381 # 'lxc.idmap' entries. We need to make sure that the extracted contents
382 # of the container match up with the restored configuration afterwards:
383 $conf->{lxc
} = $orig_conf->{lxc
} if $is_root;
385 $conf->{unprivileged
} = $orig_conf->{unprivileged
}
386 if !defined($unprivileged) && defined($orig_conf->{unprivileged
});
388 # implicit privileged change is checked here
389 if ($old_conf->{unprivileged
} && !$conf->{unprivileged
}) {
390 $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Allocate']);
394 if ($storage_only_mode) {
396 if (!defined($orig_mp_param)) {
397 print "recovering backed-up configuration from '$archive'\n";
398 (undef, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
400 $mp_param = $orig_mp_param;
401 die "rootfs configuration could not be recovered, please check and specify manually!\n"
402 if !defined($mp_param->{rootfs
});
403 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
404 my ($ms, $mountpoint) = @_;
405 my $type = $mountpoint->{type
};
406 if ($type eq 'volume') {
407 die "unable to detect disk size - please specify $ms (size)\n"
408 if !defined($mountpoint->{size
});
409 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
410 delete $mountpoint->{size
};
411 $mountpoint->{volume
} = "$storage:$disksize";
412 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
414 my $type = $mountpoint->{type
};
415 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
416 if ($ms eq 'rootfs');
417 die "restoring '$ms' to $type mount is only possible for root\n"
420 if ($mountpoint->{backup
}) {
421 warn "WARNING - unsupported configuration!\n";
422 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
423 warn "mount point configuration will be restored after archive extraction!\n";
424 warn "contained files will be restored to wrong directory!\n";
426 delete $mp_param->{$ms}; # actually delay bind/dev mps
427 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
431 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
435 die "$emsg $@" if $@;
437 # up until here we did not modify the container, besides the lock
442 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
444 # we always have the 'create' lock so check for more than 1 entry
445 if (scalar(keys %$old_conf) > 1) {
446 # destroy old container volumes
447 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, { lock => 'create' });
451 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
452 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
453 print "restoring '$archive' now..\n"
454 if $restore && $archive ne '-';
455 PVE
::LXC
::Create
::restore_archive
($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
458 print "merging backed-up and given configuration..\n";
459 PVE
::LXC
::Create
::restore_configuration
($vmid, $storage_cfg, $archive, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
460 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
461 $lxc_setup->template_fixup($conf);
463 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
464 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
465 $lxc_setup->post_create_hook($password, $ssh_keys);
469 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
470 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
473 $conf->{hostname
} ||= "CT$vmid";
474 $conf->{memory
} ||= 512;
475 $conf->{swap
} //= 512;
476 foreach my $mp (keys %$delayed_mp_param) {
477 $conf->{$mp} = $delayed_mp_param->{$mp};
479 # If the template flag was set, we try to convert again to template after restore
481 print STDERR
"Convert restored container to template...\n";
482 PVE
::LXC
::template_create
($vmid, $conf);
483 $conf->{template
} = 1;
485 PVE
::LXC
::Config-
>write_config($vmid, $conf);
488 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
489 eval { PVE
::LXC
::Config-
>destroy_config($vmid) };
493 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
495 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
496 if $start_after_create;
499 my $workername = $restore ?
'vzrestore' : 'vzcreate';
502 PVE
::LXC
::Config-
>lock_config($vmid, $code);
505 # if we aborted before changing the container, we must remove the create lock
507 PVE
::LXC
::Config-
>remove_lock($vmid, 'create');
513 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
516 __PACKAGE__-
>register_method({
521 description
=> "Directory index",
526 additionalProperties
=> 0,
528 node
=> get_standard_option
('pve-node'),
529 vmid
=> get_standard_option
('pve-vmid'),
537 subdir
=> { type
=> 'string' },
540 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
546 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
549 { subdir
=> 'config' },
550 { subdir
=> 'pending' },
551 { subdir
=> 'status' },
552 { subdir
=> 'vncproxy' },
553 { subdir
=> 'termproxy' },
554 { subdir
=> 'vncwebsocket' },
555 { subdir
=> 'spiceproxy' },
556 { subdir
=> 'migrate' },
557 { subdir
=> 'clone' },
558 # { subdir => 'initlog' },
560 { subdir
=> 'rrddata' },
561 { subdir
=> 'firewall' },
562 { subdir
=> 'snapshot' },
563 { subdir
=> 'resize' },
570 __PACKAGE__-
>register_method({
572 path
=> '{vmid}/rrd',
574 protected
=> 1, # fixme: can we avoid that?
576 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
578 description
=> "Read VM RRD statistics (returns PNG)",
580 additionalProperties
=> 0,
582 node
=> get_standard_option
('pve-node'),
583 vmid
=> get_standard_option
('pve-vmid'),
585 description
=> "Specify the time frame you are interested in.",
587 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
590 description
=> "The list of datasources you want to display.",
591 type
=> 'string', format
=> 'pve-configid-list',
594 description
=> "The RRD consolidation function",
596 enum
=> [ 'AVERAGE', 'MAX' ],
604 filename
=> { type
=> 'string' },
610 return PVE
::RRD
::create_rrd_graph
(
611 "pve2-vm/$param->{vmid}", $param->{timeframe
},
612 $param->{ds
}, $param->{cf
});
616 __PACKAGE__-
>register_method({
618 path
=> '{vmid}/rrddata',
620 protected
=> 1, # fixme: can we avoid that?
622 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
624 description
=> "Read VM RRD statistics",
626 additionalProperties
=> 0,
628 node
=> get_standard_option
('pve-node'),
629 vmid
=> get_standard_option
('pve-vmid'),
631 description
=> "Specify the time frame you are interested in.",
633 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
636 description
=> "The RRD consolidation function",
638 enum
=> [ 'AVERAGE', 'MAX' ],
653 return PVE
::RRD
::create_rrd_data
(
654 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
657 __PACKAGE__-
>register_method({
658 name
=> 'destroy_vm',
663 description
=> "Destroy the container (also delete all uses files).",
665 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
668 additionalProperties
=> 0,
670 node
=> get_standard_option
('pve-node'),
671 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
674 description
=> "Force destroy, even if running.",
680 description
=> "Remove container from all related configurations."
681 ." For example, backup jobs, replication jobs or HA."
682 ." Related ACLs and Firewall entries will *always* be removed.",
686 'destroy-unreferenced-disks' => {
688 description
=> "If set, destroy additionally all disks with the VMID from all"
689 ." enabled storages which are not referenced in the config.",
700 my $rpcenv = PVE
::RPCEnvironment
::get
();
701 my $authuser = $rpcenv->get_user();
702 my $vmid = $param->{vmid
};
704 # test if container exists
706 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
707 my $early_checks = sub {
709 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
710 PVE
::LXC
::Config-
>check_lock($conf);
712 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("ct:$vmid");
714 if (!$param->{purge
}) {
715 die "unable to remove CT $vmid - used in HA resources and purge parameter not set.\n"
718 # do not allow destroy if there are replication jobs without purge
719 my $repl_conf = PVE
::ReplicationConfig-
>new();
720 $repl_conf->check_for_existing_jobs($vmid);
726 $early_checks->($conf);
728 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
729 die $running_error_msg if !$param->{force
} && PVE
::LXC
::check_running
($vmid); # check early
732 # reload config after lock
733 $conf = PVE
::LXC
::Config-
>load_config($vmid);
734 my $ha_managed = $early_checks->($conf);
736 if (PVE
::LXC
::check_running
($vmid)) {
737 die $running_error_msg if !$param->{force
};
738 warn "forced to stop CT $vmid before destroying!\n";
740 PVE
::LXC
::vm_stop
($vmid, 1);
742 run_command
(['ha-manager', 'crm-command', 'stop', "ct:$vmid", '120']);
746 my $storage_cfg = cfs_read_file
("storage.cfg");
747 PVE
::LXC
::destroy_lxc_container
(
751 { lock => 'destroyed' },
752 $param->{'destroy-unreferenced-disks'},
755 PVE
::AccessControl
::remove_vm_access
($vmid);
756 PVE
::Firewall
::remove_vmfw_conf
($vmid);
757 if ($param->{purge
}) {
758 print "purging CT $vmid from related configurations..\n";
759 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
760 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
763 PVE
::HA
::Config
::delete_service_from_config
("ct:$vmid");
764 print "NOTE: removed CT $vmid from HA resource configuration.\n";
768 # only now remove the zombie config, else we can have reuse race
769 PVE
::LXC
::Config-
>destroy_config($vmid);
772 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
774 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
779 __PACKAGE__-
>register_method ({
781 path
=> '{vmid}/vncproxy',
785 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
787 description
=> "Creates a TCP VNC proxy connections.",
789 additionalProperties
=> 0,
791 node
=> get_standard_option
('pve-node'),
792 vmid
=> get_standard_option
('pve-vmid'),
796 description
=> "use websocket instead of standard VNC.",
800 description
=> "sets the width of the console in pixels.",
807 description
=> "sets the height of the console in pixels.",
815 additionalProperties
=> 0,
817 user
=> { type
=> 'string' },
818 ticket
=> { type
=> 'string' },
819 cert
=> { type
=> 'string' },
820 port
=> { type
=> 'integer' },
821 upid
=> { type
=> 'string' },
827 my $rpcenv = PVE
::RPCEnvironment
::get
();
829 my $authuser = $rpcenv->get_user();
831 my $vmid = $param->{vmid
};
832 my $node = $param->{node
};
834 my $authpath = "/vms/$vmid";
836 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
838 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
841 my ($remip, $family);
843 if ($node ne PVE
::INotify
::nodename
()) {
844 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
846 $family = PVE
::Tools
::get_host_address_family
($node);
849 my $port = PVE
::Tools
::next_vnc_port
($family);
851 # NOTE: vncterm VNC traffic is already TLS encrypted,
852 # so we select the fastest chipher here (or 'none'?)
853 my $remcmd = $remip ?
854 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
856 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
857 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
859 my $shcmd = [ '/usr/bin/dtach', '-A',
860 "/var/run/dtach/vzctlconsole$vmid",
861 '-r', 'winch', '-z', @$concmd];
866 syslog
('info', "starting lxc vnc proxy $upid\n");
870 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
871 '-timeout', $timeout, '-authpath', $authpath,
872 '-perm', 'VM.Console'];
874 if ($param->{width
}) {
875 push @$cmd, '-width', $param->{width
};
878 if ($param->{height
}) {
879 push @$cmd, '-height', $param->{height
};
882 if ($param->{websocket
}) {
883 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
884 push @$cmd, '-notls', '-listen', 'localhost';
887 push @$cmd, '-c', @$remcmd, @$shcmd;
889 run_command
($cmd, keeplocale
=> 1);
894 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
896 PVE
::Tools
::wait_for_vnc_port
($port);
907 __PACKAGE__-
>register_method ({
909 path
=> '{vmid}/termproxy',
913 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
915 description
=> "Creates a TCP proxy connection.",
917 additionalProperties
=> 0,
919 node
=> get_standard_option
('pve-node'),
920 vmid
=> get_standard_option
('pve-vmid'),
924 additionalProperties
=> 0,
926 user
=> { type
=> 'string' },
927 ticket
=> { type
=> 'string' },
928 port
=> { type
=> 'integer' },
929 upid
=> { type
=> 'string' },
935 my $rpcenv = PVE
::RPCEnvironment
::get
();
937 my $authuser = $rpcenv->get_user();
939 my $vmid = $param->{vmid
};
940 my $node = $param->{node
};
942 my $authpath = "/vms/$vmid";
944 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
946 my ($remip, $family);
948 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
949 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
951 $family = PVE
::Tools
::get_host_address_family
($node);
954 my $port = PVE
::Tools
::next_vnc_port
($family);
956 my $remcmd = $remip ?
957 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
959 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
960 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
962 my $shcmd = [ '/usr/bin/dtach', '-A',
963 "/var/run/dtach/vzctlconsole$vmid",
964 '-r', 'winch', '-z', @$concmd];
969 syslog
('info', "starting lxc termproxy $upid\n");
971 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
972 '--perm', 'VM.Console', '--'];
973 push @$cmd, @$remcmd, @$shcmd;
975 PVE
::Tools
::run_command
($cmd);
978 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
980 PVE
::Tools
::wait_for_vnc_port
($port);
990 __PACKAGE__-
>register_method({
991 name
=> 'vncwebsocket',
992 path
=> '{vmid}/vncwebsocket',
995 description
=> "You also need to pass a valid ticket (vncticket).",
996 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
998 description
=> "Opens a weksocket for VNC traffic.",
1000 additionalProperties
=> 0,
1002 node
=> get_standard_option
('pve-node'),
1003 vmid
=> get_standard_option
('pve-vmid'),
1005 description
=> "Ticket from previous call to vncproxy.",
1010 description
=> "Port number returned by previous vncproxy call.",
1020 port
=> { type
=> 'string' },
1026 my $rpcenv = PVE
::RPCEnvironment
::get
();
1028 my $authuser = $rpcenv->get_user();
1030 my $authpath = "/vms/$param->{vmid}";
1032 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
1034 my $port = $param->{port
};
1036 return { port
=> $port };
1039 __PACKAGE__-
>register_method ({
1040 name
=> 'spiceproxy',
1041 path
=> '{vmid}/spiceproxy',
1046 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1048 description
=> "Returns a SPICE configuration to connect to the CT.",
1050 additionalProperties
=> 0,
1052 node
=> get_standard_option
('pve-node'),
1053 vmid
=> get_standard_option
('pve-vmid'),
1054 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1057 returns
=> get_standard_option
('remote-viewer-config'),
1061 my $vmid = $param->{vmid
};
1062 my $node = $param->{node
};
1063 my $proxy = $param->{proxy
};
1065 my $authpath = "/vms/$vmid";
1066 my $permissions = 'VM.Console';
1068 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1070 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
1072 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
1074 my $shcmd = ['/usr/bin/dtach', '-A',
1075 "/var/run/dtach/vzctlconsole$vmid",
1076 '-r', 'winch', '-z', @$concmd];
1078 my $title = "CT $vmid";
1080 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1084 __PACKAGE__-
>register_method({
1085 name
=> 'migrate_vm',
1086 path
=> '{vmid}/migrate',
1090 description
=> "Migrate the container to another node. Creates a new migration task.",
1092 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1095 additionalProperties
=> 0,
1097 node
=> get_standard_option
('pve-node'),
1098 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1099 target
=> get_standard_option
('pve-node', {
1100 description
=> "Target node.",
1101 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1103 'target-storage' => get_standard_option
('pve-targetstorage'),
1106 description
=> "Use online/live migration.",
1111 description
=> "Use restart migration",
1116 description
=> "Timeout in seconds for shutdown for restart migration",
1121 description
=> "Override I/O bandwidth limit (in KiB/s).",
1125 default => 'migrate limit from datacenter or storage config',
1131 description
=> "the task ID.",
1136 my $rpcenv = PVE
::RPCEnvironment
::get
();
1138 my $authuser = $rpcenv->get_user();
1140 my $target = extract_param
($param, 'target');
1142 my $localnode = PVE
::INotify
::nodename
();
1143 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1145 PVE
::Cluster
::check_cfs_quorum
();
1147 PVE
::Cluster
::check_node_exists
($target);
1149 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1151 my $vmid = extract_param
($param, 'vmid');
1154 PVE
::LXC
::Config-
>load_config($vmid);
1156 # try to detect errors early
1157 if (PVE
::LXC
::check_running
($vmid)) {
1158 die "can't migrate running container without --online or --restart\n"
1159 if !$param->{online
} && !$param->{restart
};
1162 if (my $targetstorage = delete $param->{'target-storage'}) {
1163 my $storecfg = PVE
::Storage
::config
();
1164 my $storagemap = eval { PVE
::JSONSchema
::parse_idmap
($targetstorage, 'pve-storage-id') };
1165 raise_param_exc
({ 'target-storage' => "failed to parse storage map: $@" })
1168 $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.Disk'])
1169 if !defined($storagemap->{identity
});
1171 foreach my $target_sid (values %{$storagemap->{entries
}}) {
1172 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $target_sid, $target);
1175 $check_storage_access_migrate->($rpcenv, $authuser, $storecfg, $storagemap->{default}, $target)
1176 if $storagemap->{default};
1178 $param->{storagemap
} = $storagemap;
1181 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1186 my $service = "ct:$vmid";
1188 my $cmd = ['ha-manager', 'migrate', $service, $target];
1190 print "Requesting HA migration for CT $vmid to node $target\n";
1192 PVE
::Tools
::run_command
($cmd);
1197 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1202 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1206 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1209 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1213 __PACKAGE__-
>register_method({
1214 name
=> 'vm_feature',
1215 path
=> '{vmid}/feature',
1219 description
=> "Check if feature for virtual machine is available.",
1221 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1224 additionalProperties
=> 0,
1226 node
=> get_standard_option
('pve-node'),
1227 vmid
=> get_standard_option
('pve-vmid'),
1229 description
=> "Feature to check.",
1231 enum
=> [ 'snapshot', 'clone', 'copy' ],
1233 snapname
=> get_standard_option
('pve-snapshot-name', {
1241 hasFeature
=> { type
=> 'boolean' },
1244 #items => { type => 'string' },
1251 my $node = extract_param
($param, 'node');
1253 my $vmid = extract_param
($param, 'vmid');
1255 my $snapname = extract_param
($param, 'snapname');
1257 my $feature = extract_param
($param, 'feature');
1259 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1262 my $snap = $conf->{snapshots
}->{$snapname};
1263 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1266 my $storage_cfg = PVE
::Storage
::config
();
1267 #Maybe include later
1268 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1269 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1272 hasFeature
=> $hasFeature,
1273 #nodes => [ keys %$nodelist ],
1277 __PACKAGE__-
>register_method({
1279 path
=> '{vmid}/template',
1283 description
=> "Create a Template.",
1285 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1286 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1289 additionalProperties
=> 0,
1291 node
=> get_standard_option
('pve-node'),
1292 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1295 returns
=> { type
=> 'null'},
1299 my $rpcenv = PVE
::RPCEnvironment
::get
();
1301 my $authuser = $rpcenv->get_user();
1303 my $node = extract_param
($param, 'node');
1305 my $vmid = extract_param
($param, 'vmid');
1307 my $updatefn = sub {
1309 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1310 PVE
::LXC
::Config-
>check_lock($conf);
1312 die "unable to create template, because CT contains snapshots\n"
1313 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1315 die "you can't convert a template to a template\n"
1316 if PVE
::LXC
::Config-
>is_template($conf);
1318 die "you can't convert a CT to template if the CT is running\n"
1319 if PVE
::LXC
::check_running
($vmid);
1322 PVE
::LXC
::template_create
($vmid, $conf);
1324 $conf->{template
} = 1;
1326 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1327 # and remove lxc config
1328 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1331 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1334 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1339 __PACKAGE__-
>register_method({
1341 path
=> '{vmid}/clone',
1345 description
=> "Create a container clone/copy",
1347 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1348 "and 'VM.Allocate' permissions " .
1349 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1350 "'Datastore.AllocateSpace' on any used storage.",
1353 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1355 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1356 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1361 additionalProperties
=> 0,
1363 node
=> get_standard_option
('pve-node'),
1364 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1365 newid
=> get_standard_option
('pve-vmid', {
1366 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1367 description
=> 'VMID for the clone.' }),
1370 type
=> 'string', format
=> 'dns-name',
1371 description
=> "Set a hostname for the new CT.",
1376 description
=> "Description for the new CT.",
1380 type
=> 'string', format
=> 'pve-poolid',
1381 description
=> "Add the new CT to the specified pool.",
1383 snapname
=> get_standard_option
('pve-snapshot-name', {
1386 storage
=> get_standard_option
('pve-storage-id', {
1387 description
=> "Target storage for full clone.",
1393 description
=> "Create a full copy of all disks. This is always done when " .
1394 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1396 target
=> get_standard_option
('pve-node', {
1397 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1401 description
=> "Override I/O bandwidth limit (in KiB/s).",
1405 default => 'clone limit from datacenter or storage config',
1415 my $rpcenv = PVE
::RPCEnvironment
::get
();
1416 my $authuser = $rpcenv->get_user();
1418 my $node = extract_param
($param, 'node');
1419 my $vmid = extract_param
($param, 'vmid');
1420 my $newid = extract_param
($param, 'newid');
1421 my $pool = extract_param
($param, 'pool');
1422 if (defined($pool)) {
1423 $rpcenv->check_pool_exist($pool);
1425 my $snapname = extract_param
($param, 'snapname');
1426 my $storage = extract_param
($param, 'storage');
1427 my $target = extract_param
($param, 'target');
1428 my $localnode = PVE
::INotify
::nodename
();
1430 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1432 PVE
::Cluster
::check_node_exists
($target) if $target;
1434 my $storecfg = PVE
::Storage
::config
();
1437 # check if storage is enabled on local node
1438 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1440 # check if storage is available on target node
1441 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
1442 # clone only works if target storage is shared
1443 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1444 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1448 PVE
::Cluster
::check_cfs_quorum
();
1451 my $mountpoints = {};
1456 PVE
::LXC
::Config-
>create_and_lock_config($newid, 0);
1457 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1459 my $lock_and_reload = sub {
1460 my ($vmid, $code) = @_;
1461 return PVE
::LXC
::Config-
>lock_config($vmid, sub {
1462 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1463 die "Lost 'create' config lock, aborting.\n"
1464 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1466 return $code->($conf);
1470 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1472 $running = PVE
::LXC
::check_running
($vmid) || 0;
1474 my $full = extract_param
($param, 'full');
1475 if (!defined($full)) {
1476 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1480 die "parameter 'storage' not allowed for linked clones\n"
1481 if defined($storage) && !$full;
1483 die "snapshot '$snapname' does not exist\n"
1484 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1486 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1489 for my $opt (sort keys %$src_conf) {
1490 next if $opt =~ m/^unused\d+$/;
1492 my $value = $src_conf->{$opt};
1494 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1495 my $mp = PVE
::LXC
::Config-
>parse_volume($opt, $value);
1497 if ($mp->{type
} eq 'volume') {
1498 my $volid = $mp->{volume
};
1500 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1501 $sid = $storage if defined($storage);
1502 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1503 if (!$scfg->{shared
}) {
1505 warn "found non-shared volume: $volid\n" if $target;
1508 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1511 die "Cannot do full clones on a running container without snapshots\n"
1512 if $running && !defined($snapname);
1513 $fullclone->{$opt} = 1;
1515 # not full means clone instead of copy
1516 die "Linked clone feature for '$volid' is not available\n"
1517 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
1520 $mountpoints->{$opt} = $mp;
1521 push @$vollist, $volid;
1524 # TODO: allow bind mounts?
1525 die "unable to clone mountpoint '$opt' (type $mp->{type})\n";
1527 } elsif ($opt =~ m/^net(\d+)$/) {
1528 # always change MAC! address
1529 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1530 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1531 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1532 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1534 # copy everything else
1535 $newconf->{$opt} = $value;
1538 die "can't clone CT to node '$target' (CT uses local storage)\n"
1539 if $target && !$sharedvm;
1541 # Replace the 'disk' lock with a 'create' lock.
1542 $newconf->{lock} = 'create';
1544 # delete all snapshot related config options
1545 delete $newconf->@{qw(snapshots parent snaptime snapstate)};
1547 delete $newconf->{pending
};
1548 delete $newconf->{template
};
1550 $newconf->{hostname
} = $param->{hostname
} if $param->{hostname
};
1551 $newconf->{description
} = $param->{description
} if $param->{description
};
1553 $lock_and_reload->($newid, sub {
1554 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1558 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1559 warn "Failed to remove source CT config lock - $@\n" if $@;
1562 $lock_and_reload->($newid, sub {
1563 PVE
::LXC
::Config-
>destroy_config($newid);
1564 PVE
::Firewall
::remove_vmfw_conf
($newid);
1567 warn "Failed to remove target CT config - $@\n" if $@;
1572 my $update_conf = sub {
1573 my ($key, $value) = @_;
1574 return $lock_and_reload->($newid, sub {
1576 $conf->{$key} = $value;
1577 PVE
::LXC
::Config-
>write_config($newid, $conf);
1584 my $newvollist = [];
1586 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1587 die "unexpected state change\n" if $verify_running != $running;
1593 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1595 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1596 my $bwlimit = extract_param
($param, 'bwlimit');
1598 foreach my $opt (keys %$mountpoints) {
1599 my $mp = $mountpoints->{$opt};
1600 my $volid = $mp->{volume
};
1603 if ($fullclone->{$opt}) {
1604 print "create full clone of mountpoint $opt ($volid)\n";
1605 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1606 my $target_storage = $storage // $source_storage;
1607 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1608 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1610 print "create linked clone of mount point $opt ($volid)\n";
1611 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1614 push @$newvollist, $newvolid;
1615 $mp->{volume
} = $newvolid;
1617 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1620 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1622 $lock_and_reload->($newid, sub {
1624 my $rootdir = PVE
::LXC
::mount_all
($newid, $storecfg, $conf, 1);
1626 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1627 $lxc_setup->post_clone_hook($conf);
1630 eval { PVE
::LXC
::umount_all
($newid, $storecfg, $conf, 1); };
1640 # Unlock the source config in any case:
1641 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1645 # Now cleanup the config & disks:
1646 sleep 1; # some storages like rbd need to wait before release volume - really?
1648 foreach my $volid (@$newvollist) {
1649 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1654 $lock_and_reload->($newid, sub {
1655 PVE
::LXC
::Config-
>destroy_config($newid);
1656 PVE
::Firewall
::remove_vmfw_conf
($newid);
1659 warn "Failed to remove target CT config - $@\n" if $@;
1661 die "clone failed: $err";
1664 $lock_and_reload->($newid, sub {
1665 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1668 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1669 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1670 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1672 PVE
::LXC
::Config-
>move_config_to_node($newid, $target);
1679 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1683 __PACKAGE__-
>register_method({
1684 name
=> 'resize_vm',
1685 path
=> '{vmid}/resize',
1689 description
=> "Resize a container mount point.",
1691 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1694 additionalProperties
=> 0,
1696 node
=> get_standard_option
('pve-node'),
1697 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1700 description
=> "The disk you want to resize.",
1701 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys()],
1705 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1706 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.",
1710 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1718 description
=> "the task ID.",
1723 my $rpcenv = PVE
::RPCEnvironment
::get
();
1725 my $authuser = $rpcenv->get_user();
1727 my $node = extract_param
($param, 'node');
1729 my $vmid = extract_param
($param, 'vmid');
1731 my $digest = extract_param
($param, 'digest');
1733 my $sizestr = extract_param
($param, 'size');
1734 my $ext = ($sizestr =~ s/^\+//);
1735 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1736 die "invalid size string" if !defined($newsize);
1738 die "no options specified\n" if !scalar(keys %$param);
1740 my $storage_cfg = cfs_read_file
("storage.cfg");
1744 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1745 PVE
::LXC
::Config-
>check_lock($conf);
1747 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $conf, $param, [], $conf->{unprivileged
});
1749 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1751 my $running = PVE
::LXC
::check_running
($vmid);
1753 my $disk = $param->{disk
};
1754 my $mp = PVE
::LXC
::Config-
>parse_volume($disk, $conf->{$disk});
1756 my $volid = $mp->{volume
};
1758 my (undef, undef, $owner, undef, undef, undef, $format) =
1759 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1761 die "can't resize mount point owned by another container ($owner)"
1764 die "can't resize volume: $disk if snapshot exists\n"
1765 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1767 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1769 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1771 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1773 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1775 die "Could not determine current size of volume '$volid'\n" if !defined($size);
1777 $newsize += $size if $ext;
1778 $newsize = int($newsize);
1780 die "unable to shrink disk size\n" if $newsize < $size;
1782 die "disk is already at specified size\n" if $size == $newsize;
1784 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1786 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1787 # we pass 0 here (parameter only makes sense for qemu)
1788 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1790 $mp->{size
} = $newsize;
1791 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1793 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1795 if ($format eq 'raw') {
1796 # we need to ensure that the volume is mapped, if not needed this is a NOP
1797 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1798 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1802 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1803 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1804 die "internal error: CT running but mount point not attached to a loop device"
1806 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1808 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1809 # to be visible to it in its namespace.
1810 # To not interfere with the rest of the system we unshare the current mount namespace,
1811 # mount over /tmp and then run resize2fs.
1813 # interestingly we don't need to e2fsck on mounted systems...
1814 my $quoted = PVE
::Tools
::shellquote
($path);
1815 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1817 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1819 warn "Failed to update the container's filesystem: $@\n" if $@;
1822 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1823 PVE
::Tools
::run_command
(['resize2fs', $path]);
1825 warn "Failed to update the container's filesystem: $@\n" if $@;
1827 # always un-map if not running, this is a NOP if not needed
1828 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1833 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1836 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1839 __PACKAGE__-
>register_method({
1840 name
=> 'move_volume',
1841 path
=> '{vmid}/move_volume',
1845 description
=> "Move a rootfs-/mp-volume to a different storage or to a different container.",
1847 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1848 "and 'Datastore.AllocateSpace' permissions on the storage. To move ".
1849 "a volume to another container, you need the permissions on the ".
1850 "target container as well.",
1851 check
=> ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1854 additionalProperties
=> 0,
1856 node
=> get_standard_option
('pve-node'),
1857 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1858 'target-vmid' => get_standard_option
('pve-vmid', {
1859 completion
=> \
&PVE
::LXC
::complete_ctid
,
1864 #TODO: check how to handle unused mount points as the mp parameter is not configured
1865 enum
=> [ PVE
::LXC
::Config-
>valid_volume_keys_with_unused() ],
1866 description
=> "Volume which will be moved.",
1868 storage
=> get_standard_option
('pve-storage-id', {
1869 description
=> "Target Storage.",
1870 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1875 description
=> "Delete the original volume after successful copy. By default the " .
1876 "original is kept as an unused volume entry.",
1882 description
=> 'Prevent changes if current configuration file has different SHA1 " .
1883 "digest. This can be used to prevent concurrent modifications.',
1888 description
=> "Override I/O bandwidth limit (in KiB/s).",
1892 default => 'clone limit from datacenter or storage config',
1894 'target-volume' => {
1896 description
=> "The config key the volume will be moved to. Default is the " .
1897 "source volume key.",
1898 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys_with_unused()],
1901 'target-digest' => {
1903 description
=> 'Prevent changes if current configuration file of the target " .
1904 "container has a different SHA1 digest. This can be used to prevent " .
1905 "concurrent modifications.',
1917 my $rpcenv = PVE
::RPCEnvironment
::get
();
1919 my $authuser = $rpcenv->get_user();
1921 my $vmid = extract_param
($param, 'vmid');
1923 my $target_vmid = extract_param
($param, 'target-vmid');
1925 my $storage = extract_param
($param, 'storage');
1927 my $mpkey = extract_param
($param, 'volume');
1929 my $target_mpkey = extract_param
($param, 'target-volume') // $mpkey;
1931 my $digest = extract_param
($param, 'digest');
1933 my $target_digest = extract_param
($param, 'target-digest');
1935 my $lockname = 'disk';
1937 my ($mpdata, $old_volid);
1939 die "either set storage or target-vmid, but not both\n"
1940 if $storage && $target_vmid;
1942 my $storecfg = PVE
::Storage
::config
();
1944 my $move_to_storage_checks = sub {
1945 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1946 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1947 PVE
::LXC
::Config-
>check_lock($conf);
1949 die "cannot move volumes of a running container\n"
1950 if PVE
::LXC
::check_running
($vmid);
1952 if ($mpkey =~ m/^unused\d+$/) {
1953 die "cannot move volume '$mpkey', only configured volumes can be moved to ".
1954 "another storage\n";
1957 $mpdata = PVE
::LXC
::Config-
>parse_volume($mpkey, $conf->{$mpkey});
1958 $old_volid = $mpdata->{volume
};
1960 die "you can't move a volume with snapshots and delete the source\n"
1961 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1963 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1965 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1969 my $storage_realcmd = sub {
1971 PVE
::Cluster
::log_msg
(
1974 "move volume CT $vmid: move --volume $mpkey --storage $storage"
1977 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1978 my $storage_cfg = PVE
::Storage
::config
();
1983 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1984 my $bwlimit = extract_param
($param, 'bwlimit');
1985 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1986 my $movelimit = PVE
::Storage
::get_bandwidth_limit
(
1988 [$source_storage, $storage],
1991 $new_volid = PVE
::LXC
::copy_volume
(
2000 if (PVE
::LXC
::Config-
>is_template($conf)) {
2001 PVE
::Storage
::activate_volumes
($storage_cfg, [ $new_volid ]);
2002 my $template_volid = PVE
::Storage
::vdisk_create_base
($storage_cfg, $new_volid);
2003 $mpdata->{volume
} = $template_volid;
2005 $mpdata->{volume
} = $new_volid;
2008 PVE
::LXC
::Config-
>lock_config($vmid, sub {
2009 my $digest = $conf->{digest
};
2010 $conf = PVE
::LXC
::Config-
>load_config($vmid);
2011 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
2013 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint(
2018 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
2020 PVE
::LXC
::Config-
>write_config($vmid, $conf);
2024 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
2025 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
2031 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
2032 if defined($new_volid);
2038 if ($param->{delete}) {
2040 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
2041 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
2045 PVE
::LXC
::Config-
>lock_config($vmid, sub {
2046 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
2047 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid);
2048 PVE
::LXC
::Config-
>write_config($vmid, $conf);
2054 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
2059 my $load_and_check_reassign_configs = sub {
2060 my $vmlist = PVE
::Cluster
::get_vmlist
()->{ids
};
2062 die "Cannot move to/from 'rootfs'\n" if $mpkey eq "rootfs" || $target_mpkey eq "rootfs";
2064 if ($mpkey =~ m/^unused\d+$/ && $target_mpkey !~ m/^unused\d+$/) {
2065 die "Moving an unused volume to a used one is not possible\n";
2067 die "could not find CT ${vmid}\n" if !exists($vmlist->{$vmid});
2068 die "could not find CT ${target_vmid}\n" if !exists($vmlist->{$target_vmid});
2070 my $source_node = $vmlist->{$vmid}->{node
};
2071 my $target_node = $vmlist->{$target_vmid}->{node
};
2073 die "Both containers need to be on the same node ($source_node != $target_node)\n"
2074 if $source_node ne $target_node;
2076 my $source_conf = PVE
::LXC
::Config-
>load_config($vmid);
2077 PVE
::LXC
::Config-
>check_lock($source_conf);
2078 my $target_conf = PVE
::LXC
::Config-
>load_config($target_vmid);
2079 PVE
::LXC
::Config-
>check_lock($target_conf);
2081 die "Can't move volumes from or to template CT\n"
2082 if ($source_conf->{template
} || $target_conf->{template
});
2085 eval { PVE
::Tools
::assert_if_modified
($digest, $source_conf->{digest
}) };
2086 die "Container ${vmid}: $@" if $@;
2089 if ($target_digest) {
2090 eval { PVE
::Tools
::assert_if_modified
($target_digest, $target_conf->{digest
}) };
2091 die "Container ${target_vmid}: $@" if $@;
2094 die "volume '${mpkey}' for container '$vmid' does not exist\n"
2095 if !defined($source_conf->{$mpkey});
2097 die "Target volume key '${target_mpkey}' is already in use for container '$target_vmid'\n"
2098 if exists $target_conf->{$target_mpkey};
2100 my $drive = PVE
::LXC
::Config-
>parse_volume(
2102 $source_conf->{$mpkey},
2105 my $source_volid = $drive->{volume
};
2107 die "Volume '${mpkey}' has no associated image\n"
2109 die "Cannot move volume used by a snapshot to another container\n"
2110 if PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($source_conf, $source_volid);
2111 die "Storage does not support moving of this disk to another container\n"
2112 if !PVE
::Storage
::volume_has_feature
($storecfg, 'rename', $source_volid);
2113 die "Cannot move a bindmount or device mount to another container\n"
2114 if $drive->{type
} ne "volume";
2115 die "Cannot move volume to another container while the source is running - detach first\n"
2116 if PVE
::LXC
::check_running
($vmid) && $mpkey !~ m/^unused\d+$/;
2118 my $repl_conf = PVE
::ReplicationConfig-
>new();
2119 if ($repl_conf->check_for_existing_jobs($target_vmid, 1)) {
2120 my ($storeid, undef) = PVE
::Storage
::parse_volume_id
($source_volid);
2121 my $format = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
2123 die "Cannot move volume on storage '$storeid' to a replicated container - missing replication support\n"
2124 if !PVE
::Storage
::storage_can_replicate
($storecfg, $storeid, $format);
2127 return ($source_conf, $target_conf, $drive);
2132 print STDERR
"$msg\n";
2135 my $volume_reassignfn = sub {
2136 return PVE
::LXC
::Config-
>lock_config($vmid, sub {
2137 return PVE
::LXC
::Config-
>lock_config($target_vmid, sub {
2138 my ($source_conf, $target_conf, $drive) = &$load_and_check_reassign_configs();
2139 my $source_volid = $drive->{volume
};
2141 my $target_unused = $target_mpkey =~ m/^unused\d+$/;
2143 print "moving volume '$mpkey' from container '$vmid' to '$target_vmid'\n";
2145 my ($storage, $source_volname) = PVE
::Storage
::parse_volume_id
($source_volid);
2147 my $fmt = (PVE
::Storage
::parse_volname
($storecfg, $source_volid))[6];
2149 my $new_volid = PVE
::Storage
::rename_volume
(
2155 $drive->{volume
} = $new_volid;
2157 delete $source_conf->{$mpkey};
2158 print "removing volume '${mpkey}' from container '${vmid}' config\n";
2159 PVE
::LXC
::Config-
>write_config($vmid, $source_conf);
2163 if ($target_unused) {
2164 $drive_string = $new_volid;
2166 $drive_string = PVE
::LXC
::Config-
>print_volume($target_mpkey, $drive);
2169 if ($target_unused) {
2170 $target_conf->{$target_mpkey} = $drive_string;
2172 my $running = PVE
::LXC
::check_running
($target_vmid);
2173 my $param = { $target_mpkey => $drive_string };
2174 my $errors = PVE
::LXC
::Config-
>update_pct_config(
2181 foreach my $key (keys %$errors) {
2182 $rpcenv->warn($errors->{$key});
2186 PVE
::LXC
::Config-
>write_config($target_vmid, $target_conf);
2187 $target_conf = PVE
::LXC
::Config-
>load_config($target_vmid);
2189 PVE
::LXC
::update_lxc_config
($target_vmid, $target_conf) if !$target_unused;
2190 print "target container '$target_vmid' updated with '$target_mpkey'\n";
2192 # remove possible replication snapshots
2193 if (PVE
::Storage
::volume_has_feature
($storecfg,'replicate', $source_volid)) {
2195 PVE
::Replication
::prepare
(
2205 $rpcenv->warn("Failed to remove replication snapshots on volume ".
2206 "'${target_mpkey}'. Manual cleanup could be necessary. " .
2214 if ($target_vmid && $storage) {
2215 my $msg = "either set 'storage' or 'target-vmid', but not both";
2216 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
2217 } elsif ($target_vmid) {
2218 $rpcenv->check_vm_perm($authuser, $target_vmid, undef, ['VM.Config.Disk'])
2219 if $authuser ne 'root@pam';
2221 if ($vmid eq $target_vmid) {
2222 my $msg = "must be different than source VMID to move disk to another container";
2223 raise_param_exc
({ 'target-vmid' => $msg });
2226 my (undef, undef, $drive) = &$load_and_check_reassign_configs();
2227 my $storeid = PVE
::Storage
::parse_volume_id
($drive->{volume
});
2228 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
2229 return $rpcenv->fork_worker(
2231 "${vmid}-${mpkey}>${target_vmid}-${target_mpkey}",
2235 } elsif ($storage) {
2236 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
2237 &$move_to_storage_checks();
2239 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $storage_realcmd);
2242 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
2248 my $msg = "both 'storage' and 'target-vmid' missing, either needs to be set";
2249 raise_param_exc
({ 'target-vmid' => $msg, 'storage' => $msg });
2253 __PACKAGE__-
>register_method({
2254 name
=> 'vm_pending',
2255 path
=> '{vmid}/pending',
2258 description
=> 'Get container configuration, including pending changes.',
2260 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
2263 additionalProperties
=> 0,
2265 node
=> get_standard_option
('pve-node'),
2266 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
2275 description
=> 'Configuration option name.',
2279 description
=> 'Current value.',
2284 description
=> 'Pending value.',
2289 description
=> "Indicates a pending delete request if present and not 0.",
2301 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
2303 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
2305 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);