1 package PVE
::API2
::LXC
;
7 use PVE
::Tools
qw(extract_param run_command);
8 use PVE
::Exception
qw(raise raise_param_exc raise_perm_exc);
10 use PVE
::Cluster
qw(cfs_read_file);
12 use PVE
::DataCenterConfig
;
13 use PVE
::AccessControl
;
17 use PVE
::RPCEnvironment
;
18 use PVE
::ReplicationConfig
;
21 use PVE
::LXC
::Migrate
;
22 use PVE
::GuestHelpers
;
23 use PVE
::VZDump
::Plugin
;
24 use PVE
::API2
::LXC
::Config
;
25 use PVE
::API2
::LXC
::Status
;
26 use PVE
::API2
::LXC
::Snapshot
;
27 use PVE
::JSONSchema
qw(get_standard_option);
28 use base
qw(PVE::RESTHandler);
31 if (!$ENV{PVE_GENERATING_DOCS
}) {
32 require PVE
::HA
::Env
::PVE2
;
33 import PVE
::HA
::Env
::PVE2
;
34 require PVE
::HA
::Config
;
35 import PVE
::HA
::Config
;
39 __PACKAGE__-
>register_method ({
40 subclass
=> "PVE::API2::LXC::Config",
41 path
=> '{vmid}/config',
44 __PACKAGE__-
>register_method ({
45 subclass
=> "PVE::API2::LXC::Status",
46 path
=> '{vmid}/status',
49 __PACKAGE__-
>register_method ({
50 subclass
=> "PVE::API2::LXC::Snapshot",
51 path
=> '{vmid}/snapshot',
54 __PACKAGE__-
>register_method ({
55 subclass
=> "PVE::API2::Firewall::CT",
56 path
=> '{vmid}/firewall',
59 __PACKAGE__-
>register_method({
63 description
=> "LXC container index (per node).",
65 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
69 protected
=> 1, # /proc files are only readable by root
71 additionalProperties
=> 0,
73 node
=> get_standard_option
('pve-node'),
80 properties
=> $PVE::LXC
::vmstatus_return_properties
,
82 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
87 my $rpcenv = PVE
::RPCEnvironment
::get
();
88 my $authuser = $rpcenv->get_user();
90 my $vmstatus = PVE
::LXC
::vmstatus
();
93 foreach my $vmid (keys %$vmstatus) {
94 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
96 my $data = $vmstatus->{$vmid};
104 __PACKAGE__-
>register_method({
108 description
=> "Create or restore a container.",
110 user
=> 'all', # check inside
111 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
112 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
113 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
118 additionalProperties
=> 0,
119 properties
=> PVE
::LXC
::Config-
>json_config_properties({
120 node
=> get_standard_option
('pve-node'),
121 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
123 description
=> "The OS template or backup file.",
126 completion
=> \
&PVE
::LXC
::complete_os_templates
,
131 description
=> "Sets root password inside container.",
134 storage
=> get_standard_option
('pve-storage-id', {
135 description
=> "Default Storage.",
138 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
143 description
=> "Allow to overwrite existing container.",
148 description
=> "Mark this as restore task.",
153 description
=> "Assign a unique random ethernet address.",
154 requires
=> 'restore',
158 type
=> 'string', format
=> 'pve-poolid',
159 description
=> "Add the VM to the specified pool.",
161 'ignore-unpack-errors' => {
164 description
=> "Ignore errors when extracting the template.",
166 'ssh-public-keys' => {
169 description
=> "Setup public SSH keys (one key per line, " .
173 description
=> "Override I/O bandwidth limit (in KiB/s).",
177 default => 'restore limit from datacenter or storage config',
183 description
=> "Start the CT after its creation finished successfully.",
193 PVE
::Cluster
::check_cfs_quorum
();
195 my $rpcenv = PVE
::RPCEnvironment
::get
();
196 my $authuser = $rpcenv->get_user();
198 my $node = extract_param
($param, 'node');
199 my $vmid = extract_param
($param, 'vmid');
200 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
201 my $bwlimit = extract_param
($param, 'bwlimit');
202 my $start_after_create = extract_param
($param, 'start');
204 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
205 my $same_container_exists = -f
$basecfg_fn;
207 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
208 my $unprivileged = extract_param
($param, 'unprivileged');
209 my $restore = extract_param
($param, 'restore');
210 my $unique = extract_param
($param, 'unique');
212 # used to skip firewall config restore if user lacks permission
213 my $skip_fw_config_restore = 0;
216 # fixme: limit allowed parameters
219 my $force = extract_param
($param, 'force');
221 if (!($same_container_exists && $restore && $force)) {
222 PVE
::Cluster
::check_vmid_unused
($vmid);
224 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
225 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
226 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
229 my $password = extract_param
($param, 'password');
230 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
231 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
233 my $pool = extract_param
($param, 'pool');
234 if (defined($pool)) {
235 $rpcenv->check_pool_exist($pool);
236 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
239 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
241 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
243 } elsif ($restore && $force && $same_container_exists &&
244 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
245 # OK: user has VM.Backup permissions, and want to restore an existing VM
247 # we don't want to restore a container-provided FW conf in this case
248 # since the user is lacking permission to configure the container's FW
249 $skip_fw_config_restore = 1;
254 my $ostemplate = extract_param
($param, 'ostemplate');
255 my $storage = extract_param
($param, 'storage') // 'local';
257 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
259 my $storage_cfg = cfs_read_file
("storage.cfg");
262 if ($ostemplate eq '-') {
263 die "pipe requires cli environment\n"
264 if $rpcenv->{type
} ne 'cli';
265 die "pipe can only be used with restore tasks\n"
268 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
270 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
271 $archive = $ostemplate;
275 my $check_and_activate_storage = sub {
278 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
280 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
281 if !$scfg->{content
}->{rootdir
};
283 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
285 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
286 $used_storages{$sid} = 1;
291 my $is_root = $authuser eq 'root@pam';
293 my $no_disk_param = {};
295 my $storage_only_mode = 1;
296 foreach my $opt (keys %$param) {
297 my $value = $param->{$opt};
298 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
299 # allow to use simple numbers (add default storage in that case)
300 if ($value =~ m/^\d+(\.\d+)?$/) {
301 $mp_param->{$opt} = "$storage:$value";
303 $mp_param->{$opt} = $value;
305 $storage_only_mode = 0;
306 } elsif ($opt =~ m/^unused\d+$/) {
307 warn "ignoring '$opt', cannot create/restore with unused volume\n";
308 delete $param->{$opt};
310 $no_disk_param->{$opt} = $value;
314 die "mount points configured, but 'rootfs' not set - aborting\n"
315 if !$storage_only_mode && !defined($mp_param->{rootfs
});
317 # check storage access, activate storage
318 my $delayed_mp_param = {};
319 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
320 my ($ms, $mountpoint) = @_;
322 my $volid = $mountpoint->{volume
};
323 my $mp = $mountpoint->{mp
};
325 if ($mountpoint->{type
} ne 'volume') { # bind or device
326 die "Only root can pass arbitrary filesystem paths.\n"
329 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
330 &$check_and_activate_storage($sid);
334 # check/activate default storage
335 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
337 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
339 $conf->{unprivileged
} = 1 if $unprivileged;
341 my $emsg = $restore ?
"unable to restore CT $vmid -" : "unable to create CT $vmid -";
343 eval { PVE
::LXC
::Config-
>create_and_lock_config($vmid, $force) };
344 die "$emsg $@" if $@;
347 my $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
352 my $orig_mp_param; # only used if $restore
354 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
355 if ($is_root && $archive ne '-') {
357 ($orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
358 $was_template = delete $orig_conf->{template
};
359 # When we're root call 'restore_configuration' with restricted=0,
360 # causing it to restore the raw lxc entries, among which there may be
361 # 'lxc.idmap' entries. We need to make sure that the extracted contents
362 # of the container match up with the restored configuration afterwards:
363 $conf->{lxc
} = $orig_conf->{lxc
};
366 if ($storage_only_mode) {
368 if (!defined($orig_mp_param)) {
369 (undef, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
371 $mp_param = $orig_mp_param;
372 die "rootfs configuration could not be recovered, please check and specify manually!\n"
373 if !defined($mp_param->{rootfs
});
374 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
375 my ($ms, $mountpoint) = @_;
376 my $type = $mountpoint->{type
};
377 if ($type eq 'volume') {
378 die "unable to detect disk size - please specify $ms (size)\n"
379 if !defined($mountpoint->{size
});
380 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
381 delete $mountpoint->{size
};
382 $mountpoint->{volume
} = "$storage:$disksize";
383 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
385 my $type = $mountpoint->{type
};
386 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
387 if ($ms eq 'rootfs');
388 die "restoring '$ms' to $type mount is only possible for root\n"
391 if ($mountpoint->{backup
}) {
392 warn "WARNING - unsupported configuration!\n";
393 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
394 warn "mount point configuration will be restored after archive extraction!\n";
395 warn "contained files will be restored to wrong directory!\n";
397 delete $mp_param->{$ms}; # actually delay bind/dev mps
398 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
402 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
406 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
408 # we always have the 'create' lock so check for more than 1 entry
409 if (scalar(keys %$old_conf) > 1) {
410 # destroy old container volumes
411 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, { lock => 'create' });
415 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
416 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
417 PVE
::LXC
::Create
::restore_archive
($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
420 PVE
::LXC
::Create
::restore_configuration
($vmid, $storage_cfg, $archive, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
421 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
422 $lxc_setup->template_fixup($conf);
424 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
425 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
426 $lxc_setup->post_create_hook($password, $ssh_keys);
430 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
431 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
434 $conf->{hostname
} ||= "CT$vmid";
435 $conf->{memory
} ||= 512;
436 $conf->{swap
} //= 512;
437 foreach my $mp (keys %$delayed_mp_param) {
438 $conf->{$mp} = $delayed_mp_param->{$mp};
440 # If the template flag was set, we try to convert again to template after restore
442 print STDERR
"Convert restored container to template...\n";
443 PVE
::LXC
::template_create
($vmid, $conf);
444 $conf->{template
} = 1;
446 PVE
::LXC
::Config-
>write_config($vmid, $conf);
449 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
450 eval { PVE
::LXC
::Config-
>destroy_config($vmid) };
454 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
456 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
457 if $start_after_create;
460 my $workername = $restore ?
'vzrestore' : 'vzcreate';
461 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
463 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
466 __PACKAGE__-
>register_method({
471 description
=> "Directory index",
476 additionalProperties
=> 0,
478 node
=> get_standard_option
('pve-node'),
479 vmid
=> get_standard_option
('pve-vmid'),
487 subdir
=> { type
=> 'string' },
490 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
496 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
499 { subdir
=> 'config' },
500 { subdir
=> 'pending' },
501 { subdir
=> 'status' },
502 { subdir
=> 'vncproxy' },
503 { subdir
=> 'termproxy' },
504 { subdir
=> 'vncwebsocket' },
505 { subdir
=> 'spiceproxy' },
506 { subdir
=> 'migrate' },
507 { subdir
=> 'clone' },
508 # { subdir => 'initlog' },
510 { subdir
=> 'rrddata' },
511 { subdir
=> 'firewall' },
512 { subdir
=> 'snapshot' },
513 { subdir
=> 'resize' },
520 __PACKAGE__-
>register_method({
522 path
=> '{vmid}/rrd',
524 protected
=> 1, # fixme: can we avoid that?
526 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
528 description
=> "Read VM RRD statistics (returns PNG)",
530 additionalProperties
=> 0,
532 node
=> get_standard_option
('pve-node'),
533 vmid
=> get_standard_option
('pve-vmid'),
535 description
=> "Specify the time frame you are interested in.",
537 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
540 description
=> "The list of datasources you want to display.",
541 type
=> 'string', format
=> 'pve-configid-list',
544 description
=> "The RRD consolidation function",
546 enum
=> [ 'AVERAGE', 'MAX' ],
554 filename
=> { type
=> 'string' },
560 return PVE
::RRD
::create_rrd_graph
(
561 "pve2-vm/$param->{vmid}", $param->{timeframe
},
562 $param->{ds
}, $param->{cf
});
566 __PACKAGE__-
>register_method({
568 path
=> '{vmid}/rrddata',
570 protected
=> 1, # fixme: can we avoid that?
572 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
574 description
=> "Read VM RRD statistics",
576 additionalProperties
=> 0,
578 node
=> get_standard_option
('pve-node'),
579 vmid
=> get_standard_option
('pve-vmid'),
581 description
=> "Specify the time frame you are interested in.",
583 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
586 description
=> "The RRD consolidation function",
588 enum
=> [ 'AVERAGE', 'MAX' ],
603 return PVE
::RRD
::create_rrd_data
(
604 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
607 __PACKAGE__-
>register_method({
608 name
=> 'destroy_vm',
613 description
=> "Destroy the container (also delete all uses files).",
615 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
618 additionalProperties
=> 0,
620 node
=> get_standard_option
('pve-node'),
621 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
624 description
=> "Force destroy, even if running.",
630 description
=> "Remove container from all related configurations."
631 ." For example, backup jobs, replication jobs or HA."
632 ." Related ACLs and Firewall entries will *always* be removed.",
636 'destroy-unreferenced-disks' => {
638 description
=> "If set, destroy additionally all disks with the VMID from all"
639 ." enabled storages which are not referenced in the config.",
650 my $rpcenv = PVE
::RPCEnvironment
::get
();
651 my $authuser = $rpcenv->get_user();
652 my $vmid = $param->{vmid
};
654 # test if container exists
656 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
657 my $early_checks = sub {
659 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
660 PVE
::LXC
::Config-
>check_lock($conf);
662 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("ct:$vmid");
664 if (!$param->{purge
}) {
665 die "unable to remove CT $vmid - used in HA resources and purge parameter not set.\n"
668 # do not allow destroy if there are replication jobs without purge
669 my $repl_conf = PVE
::ReplicationConfig-
>new();
670 $repl_conf->check_for_existing_jobs($vmid);
676 $early_checks->($conf);
678 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
679 die $running_error_msg if !$param->{force
} && PVE
::LXC
::check_running
($vmid); # check early
682 # reload config after lock
683 $conf = PVE
::LXC
::Config-
>load_config($vmid);
684 my $ha_managed = $early_checks->($conf);
686 if (PVE
::LXC
::check_running
($vmid)) {
687 die $running_error_msg if !$param->{force
};
688 warn "forced to stop CT $vmid before destroying!\n";
690 PVE
::LXC
::vm_stop
($vmid, 1);
692 run_command
(['ha-manager', 'crm-command', 'stop', "ct:$vmid", '120']);
696 my $storage_cfg = cfs_read_file
("storage.cfg");
697 PVE
::LXC
::destroy_lxc_container
(
701 { lock => 'destroyed' },
702 $param->{'destroy-unreferenced-disks'},
705 PVE
::AccessControl
::remove_vm_access
($vmid);
706 PVE
::Firewall
::remove_vmfw_conf
($vmid);
707 if ($param->{purge
}) {
708 print "purging CT $vmid from related configurations..\n";
709 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
710 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
713 PVE
::HA
::Config
::delete_service_from_config
("ct:$vmid");
714 print "NOTE: removed CT $vmid from HA resource configuration.\n";
718 # only now remove the zombie config, else we can have reuse race
719 PVE
::LXC
::Config-
>destroy_config($vmid);
722 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
724 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
729 __PACKAGE__-
>register_method ({
731 path
=> '{vmid}/vncproxy',
735 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
737 description
=> "Creates a TCP VNC proxy connections.",
739 additionalProperties
=> 0,
741 node
=> get_standard_option
('pve-node'),
742 vmid
=> get_standard_option
('pve-vmid'),
746 description
=> "use websocket instead of standard VNC.",
750 description
=> "sets the width of the console in pixels.",
757 description
=> "sets the height of the console in pixels.",
765 additionalProperties
=> 0,
767 user
=> { type
=> 'string' },
768 ticket
=> { type
=> 'string' },
769 cert
=> { type
=> 'string' },
770 port
=> { type
=> 'integer' },
771 upid
=> { type
=> 'string' },
777 my $rpcenv = PVE
::RPCEnvironment
::get
();
779 my $authuser = $rpcenv->get_user();
781 my $vmid = $param->{vmid
};
782 my $node = $param->{node
};
784 my $authpath = "/vms/$vmid";
786 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
788 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
791 my ($remip, $family);
793 if ($node ne PVE
::INotify
::nodename
()) {
794 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
796 $family = PVE
::Tools
::get_host_address_family
($node);
799 my $port = PVE
::Tools
::next_vnc_port
($family);
801 # NOTE: vncterm VNC traffic is already TLS encrypted,
802 # so we select the fastest chipher here (or 'none'?)
803 my $remcmd = $remip ?
804 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
806 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
807 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
809 my $shcmd = [ '/usr/bin/dtach', '-A',
810 "/var/run/dtach/vzctlconsole$vmid",
811 '-r', 'winch', '-z', @$concmd];
816 syslog
('info', "starting lxc vnc proxy $upid\n");
820 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
821 '-timeout', $timeout, '-authpath', $authpath,
822 '-perm', 'VM.Console'];
824 if ($param->{width
}) {
825 push @$cmd, '-width', $param->{width
};
828 if ($param->{height
}) {
829 push @$cmd, '-height', $param->{height
};
832 if ($param->{websocket
}) {
833 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
834 push @$cmd, '-notls', '-listen', 'localhost';
837 push @$cmd, '-c', @$remcmd, @$shcmd;
839 run_command
($cmd, keeplocale
=> 1);
844 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
846 PVE
::Tools
::wait_for_vnc_port
($port);
857 __PACKAGE__-
>register_method ({
859 path
=> '{vmid}/termproxy',
863 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
865 description
=> "Creates a TCP proxy connection.",
867 additionalProperties
=> 0,
869 node
=> get_standard_option
('pve-node'),
870 vmid
=> get_standard_option
('pve-vmid'),
874 additionalProperties
=> 0,
876 user
=> { type
=> 'string' },
877 ticket
=> { type
=> 'string' },
878 port
=> { type
=> 'integer' },
879 upid
=> { type
=> 'string' },
885 my $rpcenv = PVE
::RPCEnvironment
::get
();
887 my $authuser = $rpcenv->get_user();
889 my $vmid = $param->{vmid
};
890 my $node = $param->{node
};
892 my $authpath = "/vms/$vmid";
894 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
896 my ($remip, $family);
898 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
899 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
901 $family = PVE
::Tools
::get_host_address_family
($node);
904 my $port = PVE
::Tools
::next_vnc_port
($family);
906 my $remcmd = $remip ?
907 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
909 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
910 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
912 my $shcmd = [ '/usr/bin/dtach', '-A',
913 "/var/run/dtach/vzctlconsole$vmid",
914 '-r', 'winch', '-z', @$concmd];
919 syslog
('info', "starting lxc termproxy $upid\n");
921 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
922 '--perm', 'VM.Console', '--'];
923 push @$cmd, @$remcmd, @$shcmd;
925 PVE
::Tools
::run_command
($cmd);
928 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
930 PVE
::Tools
::wait_for_vnc_port
($port);
940 __PACKAGE__-
>register_method({
941 name
=> 'vncwebsocket',
942 path
=> '{vmid}/vncwebsocket',
945 description
=> "You also need to pass a valid ticket (vncticket).",
946 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
948 description
=> "Opens a weksocket for VNC traffic.",
950 additionalProperties
=> 0,
952 node
=> get_standard_option
('pve-node'),
953 vmid
=> get_standard_option
('pve-vmid'),
955 description
=> "Ticket from previous call to vncproxy.",
960 description
=> "Port number returned by previous vncproxy call.",
970 port
=> { type
=> 'string' },
976 my $rpcenv = PVE
::RPCEnvironment
::get
();
978 my $authuser = $rpcenv->get_user();
980 my $authpath = "/vms/$param->{vmid}";
982 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
984 my $port = $param->{port
};
986 return { port
=> $port };
989 __PACKAGE__-
>register_method ({
990 name
=> 'spiceproxy',
991 path
=> '{vmid}/spiceproxy',
996 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
998 description
=> "Returns a SPICE configuration to connect to the CT.",
1000 additionalProperties
=> 0,
1002 node
=> get_standard_option
('pve-node'),
1003 vmid
=> get_standard_option
('pve-vmid'),
1004 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1007 returns
=> get_standard_option
('remote-viewer-config'),
1011 my $vmid = $param->{vmid
};
1012 my $node = $param->{node
};
1013 my $proxy = $param->{proxy
};
1015 my $authpath = "/vms/$vmid";
1016 my $permissions = 'VM.Console';
1018 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1020 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
1022 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
1024 my $shcmd = ['/usr/bin/dtach', '-A',
1025 "/var/run/dtach/vzctlconsole$vmid",
1026 '-r', 'winch', '-z', @$concmd];
1028 my $title = "CT $vmid";
1030 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1034 __PACKAGE__-
>register_method({
1035 name
=> 'migrate_vm',
1036 path
=> '{vmid}/migrate',
1040 description
=> "Migrate the container to another node. Creates a new migration task.",
1042 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1045 additionalProperties
=> 0,
1047 node
=> get_standard_option
('pve-node'),
1048 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1049 target
=> get_standard_option
('pve-node', {
1050 description
=> "Target node.",
1051 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1055 description
=> "Use online/live migration.",
1060 description
=> "Use restart migration",
1065 description
=> "Timeout in seconds for shutdown for restart migration",
1071 description
=> "Force migration despite local bind / device" .
1072 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1076 description
=> "Override I/O bandwidth limit (in KiB/s).",
1080 default => 'migrate limit from datacenter or storage config',
1086 description
=> "the task ID.",
1091 my $rpcenv = PVE
::RPCEnvironment
::get
();
1093 my $authuser = $rpcenv->get_user();
1095 my $target = extract_param
($param, 'target');
1097 my $localnode = PVE
::INotify
::nodename
();
1098 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1100 PVE
::Cluster
::check_cfs_quorum
();
1102 PVE
::Cluster
::check_node_exists
($target);
1104 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1106 my $vmid = extract_param
($param, 'vmid');
1109 PVE
::LXC
::Config-
>load_config($vmid);
1111 # try to detect errors early
1112 if (PVE
::LXC
::check_running
($vmid)) {
1113 die "can't migrate running container without --online or --restart\n"
1114 if !$param->{online
} && !$param->{restart
};
1117 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1122 my $service = "ct:$vmid";
1124 my $cmd = ['ha-manager', 'migrate', $service, $target];
1126 print "Requesting HA migration for CT $vmid to node $target\n";
1128 PVE
::Tools
::run_command
($cmd);
1133 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1138 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1142 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1145 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1149 __PACKAGE__-
>register_method({
1150 name
=> 'vm_feature',
1151 path
=> '{vmid}/feature',
1155 description
=> "Check if feature for virtual machine is available.",
1157 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1160 additionalProperties
=> 0,
1162 node
=> get_standard_option
('pve-node'),
1163 vmid
=> get_standard_option
('pve-vmid'),
1165 description
=> "Feature to check.",
1167 enum
=> [ 'snapshot', 'clone', 'copy' ],
1169 snapname
=> get_standard_option
('pve-snapshot-name', {
1177 hasFeature
=> { type
=> 'boolean' },
1180 #items => { type => 'string' },
1187 my $node = extract_param
($param, 'node');
1189 my $vmid = extract_param
($param, 'vmid');
1191 my $snapname = extract_param
($param, 'snapname');
1193 my $feature = extract_param
($param, 'feature');
1195 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1198 my $snap = $conf->{snapshots
}->{$snapname};
1199 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1202 my $storage_cfg = PVE
::Storage
::config
();
1203 #Maybe include later
1204 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1205 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1208 hasFeature
=> $hasFeature,
1209 #nodes => [ keys %$nodelist ],
1213 __PACKAGE__-
>register_method({
1215 path
=> '{vmid}/template',
1219 description
=> "Create a Template.",
1221 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1222 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1225 additionalProperties
=> 0,
1227 node
=> get_standard_option
('pve-node'),
1228 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1231 returns
=> { type
=> 'null'},
1235 my $rpcenv = PVE
::RPCEnvironment
::get
();
1237 my $authuser = $rpcenv->get_user();
1239 my $node = extract_param
($param, 'node');
1241 my $vmid = extract_param
($param, 'vmid');
1243 my $updatefn = sub {
1245 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1246 PVE
::LXC
::Config-
>check_lock($conf);
1248 die "unable to create template, because CT contains snapshots\n"
1249 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1251 die "you can't convert a template to a template\n"
1252 if PVE
::LXC
::Config-
>is_template($conf);
1254 die "you can't convert a CT to template if the CT is running\n"
1255 if PVE
::LXC
::check_running
($vmid);
1258 PVE
::LXC
::template_create
($vmid, $conf);
1260 $conf->{template
} = 1;
1262 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1263 # and remove lxc config
1264 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1267 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1270 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1275 __PACKAGE__-
>register_method({
1277 path
=> '{vmid}/clone',
1281 description
=> "Create a container clone/copy",
1283 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1284 "and 'VM.Allocate' permissions " .
1285 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1286 "'Datastore.AllocateSpace' on any used storage.",
1289 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1291 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1292 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1297 additionalProperties
=> 0,
1299 node
=> get_standard_option
('pve-node'),
1300 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1301 newid
=> get_standard_option
('pve-vmid', {
1302 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1303 description
=> 'VMID for the clone.' }),
1306 type
=> 'string', format
=> 'dns-name',
1307 description
=> "Set a hostname for the new CT.",
1312 description
=> "Description for the new CT.",
1316 type
=> 'string', format
=> 'pve-poolid',
1317 description
=> "Add the new CT to the specified pool.",
1319 snapname
=> get_standard_option
('pve-snapshot-name', {
1322 storage
=> get_standard_option
('pve-storage-id', {
1323 description
=> "Target storage for full clone.",
1329 description
=> "Create a full copy of all disks. This is always done when " .
1330 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1332 target
=> get_standard_option
('pve-node', {
1333 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1337 description
=> "Override I/O bandwidth limit (in KiB/s).",
1341 default => 'clone limit from datacenter or storage config',
1351 my $rpcenv = PVE
::RPCEnvironment
::get
();
1353 my $authuser = $rpcenv->get_user();
1355 my $node = extract_param
($param, 'node');
1357 my $vmid = extract_param
($param, 'vmid');
1359 my $newid = extract_param
($param, 'newid');
1361 my $pool = extract_param
($param, 'pool');
1363 if (defined($pool)) {
1364 $rpcenv->check_pool_exist($pool);
1367 my $snapname = extract_param
($param, 'snapname');
1369 my $storage = extract_param
($param, 'storage');
1371 my $target = extract_param
($param, 'target');
1373 my $localnode = PVE
::INotify
::nodename
();
1375 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1377 PVE
::Cluster
::check_node_exists
($target) if $target;
1379 my $storecfg = PVE
::Storage
::config
();
1382 # check if storage is enabled on local node
1383 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1385 # check if storage is available on target node
1386 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1387 # clone only works if target storage is shared
1388 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1389 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1393 PVE
::Cluster
::check_cfs_quorum
();
1397 my $mountpoints = {};
1402 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1403 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1405 $running = PVE
::LXC
::check_running
($vmid) || 0;
1407 my $full = extract_param
($param, 'full');
1408 if (!defined($full)) {
1409 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1411 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1414 die "snapshot '$snapname' does not exist\n"
1415 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1418 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1420 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1421 die "unable to create CT $newid: config file already exists\n"
1425 foreach my $opt (keys %$src_conf) {
1426 next if $opt =~ m/^unused\d+$/;
1428 my $value = $src_conf->{$opt};
1430 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1431 my $mp = PVE
::LXC
::Config-
>parse_volume($opt, $value);
1433 if ($mp->{type
} eq 'volume') {
1434 my $volid = $mp->{volume
};
1436 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1437 $sid = $storage if defined($storage);
1438 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1439 if (!$scfg->{shared
}) {
1441 warn "found non-shared volume: $volid\n" if $target;
1444 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1447 die "Cannot do full clones on a running container without snapshots\n"
1448 if $running && !defined($snapname);
1449 $fullclone->{$opt} = 1;
1451 # not full means clone instead of copy
1452 die "Linked clone feature for '$volid' is not available\n"
1453 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
1456 $mountpoints->{$opt} = $mp;
1457 push @$vollist, $volid;
1460 # TODO: allow bind mounts?
1461 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1463 } elsif ($opt =~ m/^net(\d+)$/) {
1464 # always change MAC! address
1465 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1466 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1467 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1468 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1470 # copy everything else
1471 $newconf->{$opt} = $value;
1474 die "can't clone CT to node '$target' (CT uses local storage)\n"
1475 if $target && !$sharedvm;
1477 # Replace the 'disk' lock with a 'create' lock.
1478 $newconf->{lock} = 'create';
1480 delete $newconf->{snapshots
};
1481 delete $newconf->{pending
};
1482 delete $newconf->{template
};
1483 if ($param->{hostname
}) {
1484 $newconf->{hostname
} = $param->{hostname
};
1487 if ($param->{description
}) {
1488 $newconf->{description
} = $param->{description
};
1491 # create empty/temp config - this fails if CT already exists on other node
1492 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1495 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1501 my $update_conf = sub {
1502 my ($key, $value) = @_;
1503 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1504 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1505 die "Lost 'create' config lock, aborting.\n"
1506 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1507 $conf->{$key} = $value;
1508 PVE
::LXC
::Config-
>write_config($newid, $conf);
1515 my $newvollist = [];
1517 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1518 die "unexpected state change\n" if $verify_running != $running;
1524 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1526 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1527 my $bwlimit = extract_param
($param, 'bwlimit');
1529 foreach my $opt (keys %$mountpoints) {
1530 my $mp = $mountpoints->{$opt};
1531 my $volid = $mp->{volume
};
1534 if ($fullclone->{$opt}) {
1535 print "create full clone of mountpoint $opt ($volid)\n";
1536 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1537 my $target_storage = $storage // $source_storage;
1538 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1539 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1541 print "create linked clone of mount point $opt ($volid)\n";
1542 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1545 push @$newvollist, $newvolid;
1546 $mp->{volume
} = $newvolid;
1548 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1551 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1552 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1555 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1556 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1557 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1559 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1560 die "Failed to move config to node '$target' - rename failed: $!\n"
1561 if !rename($conffile, $newconffile);
1566 # Unlock the source config in any case:
1567 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1571 # Now cleanup the config & disks:
1574 sleep 1; # some storages like rbd need to wait before release volume - really?
1576 foreach my $volid (@$newvollist) {
1577 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1580 die "clone failed: $err";
1586 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1587 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1591 __PACKAGE__-
>register_method({
1592 name
=> 'resize_vm',
1593 path
=> '{vmid}/resize',
1597 description
=> "Resize a container mount point.",
1599 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1602 additionalProperties
=> 0,
1604 node
=> get_standard_option
('pve-node'),
1605 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1608 description
=> "The disk you want to resize.",
1609 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys()],
1613 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1614 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.",
1618 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1626 description
=> "the task ID.",
1631 my $rpcenv = PVE
::RPCEnvironment
::get
();
1633 my $authuser = $rpcenv->get_user();
1635 my $node = extract_param
($param, 'node');
1637 my $vmid = extract_param
($param, 'vmid');
1639 my $digest = extract_param
($param, 'digest');
1641 my $sizestr = extract_param
($param, 'size');
1642 my $ext = ($sizestr =~ s/^\+//);
1643 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1644 die "invalid size string" if !defined($newsize);
1646 die "no options specified\n" if !scalar(keys %$param);
1648 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1650 my $storage_cfg = cfs_read_file
("storage.cfg");
1654 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1655 PVE
::LXC
::Config-
>check_lock($conf);
1657 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1659 my $running = PVE
::LXC
::check_running
($vmid);
1661 my $disk = $param->{disk
};
1662 my $mp = PVE
::LXC
::Config-
>parse_volume($disk, $conf->{$disk});
1664 my $volid = $mp->{volume
};
1666 my (undef, undef, $owner, undef, undef, undef, $format) =
1667 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1669 die "can't resize mount point owned by another container ($owner)"
1672 die "can't resize volume: $disk if snapshot exists\n"
1673 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1675 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1677 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1679 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1681 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1683 die "Could not determine current size of volume '$volid'\n" if !defined($size);
1685 $newsize += $size if $ext;
1686 $newsize = int($newsize);
1688 die "unable to shrink disk size\n" if $newsize < $size;
1690 die "disk is already at specified size\n" if $size == $newsize;
1692 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1694 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1695 # we pass 0 here (parameter only makes sense for qemu)
1696 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1698 $mp->{size
} = $newsize;
1699 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1701 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1703 if ($format eq 'raw') {
1704 # we need to ensure that the volume is mapped, if not needed this is a NOP
1705 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1706 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1710 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1711 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1712 die "internal error: CT running but mount point not attached to a loop device"
1714 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1716 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1717 # to be visible to it in its namespace.
1718 # To not interfere with the rest of the system we unshare the current mount namespace,
1719 # mount over /tmp and then run resize2fs.
1721 # interestingly we don't need to e2fsck on mounted systems...
1722 my $quoted = PVE
::Tools
::shellquote
($path);
1723 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1725 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1727 warn "Failed to update the container's filesystem: $@\n" if $@;
1730 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1731 PVE
::Tools
::run_command
(['resize2fs', $path]);
1733 warn "Failed to update the container's filesystem: $@\n" if $@;
1735 # always un-map if not running, this is a NOP if not needed
1736 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1741 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1744 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1747 __PACKAGE__-
>register_method({
1748 name
=> 'move_volume',
1749 path
=> '{vmid}/move_volume',
1753 description
=> "Move a rootfs-/mp-volume to a different storage",
1755 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1756 "and 'Datastore.AllocateSpace' permissions on the storage.",
1759 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1760 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1764 additionalProperties
=> 0,
1766 node
=> get_standard_option
('pve-node'),
1767 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1770 enum
=> [ PVE
::LXC
::Config-
>valid_volume_keys() ],
1771 description
=> "Volume which will be moved.",
1773 storage
=> get_standard_option
('pve-storage-id', {
1774 description
=> "Target Storage.",
1775 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1779 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1785 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1790 description
=> "Override I/O bandwidth limit (in KiB/s).",
1794 default => 'clone limit from datacenter or storage config',
1804 my $rpcenv = PVE
::RPCEnvironment
::get
();
1806 my $authuser = $rpcenv->get_user();
1808 my $vmid = extract_param
($param, 'vmid');
1810 my $storage = extract_param
($param, 'storage');
1812 my $mpkey = extract_param
($param, 'volume');
1814 my $lockname = 'disk';
1816 my ($mpdata, $old_volid);
1818 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1819 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1820 PVE
::LXC
::Config-
>check_lock($conf);
1822 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1824 $mpdata = PVE
::LXC
::Config-
>parse_volume($mpkey, $conf->{$mpkey});
1825 $old_volid = $mpdata->{volume
};
1827 die "you can't move a volume with snapshots and delete the source\n"
1828 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1830 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1832 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1837 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1839 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1840 my $storage_cfg = PVE
::Storage
::config
();
1845 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1846 my $bwlimit = extract_param
($param, 'bwlimit');
1847 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1848 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$source_storage, $storage], $bwlimit);
1849 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf, undef, $movelimit);
1850 if (PVE
::LXC
::Config-
>is_template($conf)) {
1851 PVE
::Storage
::activate_volumes
($storage_cfg, [ $new_volid ]);
1852 my $template_volid = PVE
::Storage
::vdisk_create_base
($storage_cfg, $new_volid);
1853 $mpdata->{volume
} = $template_volid;
1855 $mpdata->{volume
} = $new_volid;
1858 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1859 my $digest = $conf->{digest
};
1860 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1861 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1863 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1865 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1867 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1871 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1872 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1878 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1879 if defined($new_volid);
1885 if ($param->{delete}) {
1887 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1888 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1892 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1893 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1894 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid);
1895 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1901 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1906 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1909 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1916 __PACKAGE__-
>register_method({
1917 name
=> 'vm_pending',
1918 path
=> '{vmid}/pending',
1921 description
=> 'Get container configuration, including pending changes.',
1923 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1926 additionalProperties
=> 0,
1928 node
=> get_standard_option
('pve-node'),
1929 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1938 description
=> 'Configuration option name.',
1942 description
=> 'Current value.',
1947 description
=> 'Pending value.',
1952 description
=> "Indicates a pending delete request if present and not 0.",
1964 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
1966 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
1968 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);