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);
11 use PVE
::AccessControl
;
15 use PVE
::RPCEnvironment
;
16 use PVE
::ReplicationConfig
;
19 use PVE
::LXC
::Migrate
;
20 use PVE
::GuestHelpers
;
21 use PVE
::VZDump
::Plugin
;
22 use PVE
::API2
::LXC
::Config
;
23 use PVE
::API2
::LXC
::Status
;
24 use PVE
::API2
::LXC
::Snapshot
;
25 use PVE
::JSONSchema
qw(get_standard_option);
26 use base
qw(PVE::RESTHandler);
29 if (!$ENV{PVE_GENERATING_DOCS
}) {
30 require PVE
::HA
::Env
::PVE2
;
31 import PVE
::HA
::Env
::PVE2
;
32 require PVE
::HA
::Config
;
33 import PVE
::HA
::Config
;
37 __PACKAGE__-
>register_method ({
38 subclass
=> "PVE::API2::LXC::Config",
39 path
=> '{vmid}/config',
42 __PACKAGE__-
>register_method ({
43 subclass
=> "PVE::API2::LXC::Status",
44 path
=> '{vmid}/status',
47 __PACKAGE__-
>register_method ({
48 subclass
=> "PVE::API2::LXC::Snapshot",
49 path
=> '{vmid}/snapshot',
52 __PACKAGE__-
>register_method ({
53 subclass
=> "PVE::API2::Firewall::CT",
54 path
=> '{vmid}/firewall',
57 __PACKAGE__-
>register_method({
61 description
=> "LXC container index (per node).",
63 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
67 protected
=> 1, # /proc files are only readable by root
69 additionalProperties
=> 0,
71 node
=> get_standard_option
('pve-node'),
78 properties
=> $PVE::LXC
::vmstatus_return_properties
,
80 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
85 my $rpcenv = PVE
::RPCEnvironment
::get
();
86 my $authuser = $rpcenv->get_user();
88 my $vmstatus = PVE
::LXC
::vmstatus
();
91 foreach my $vmid (keys %$vmstatus) {
92 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
94 my $data = $vmstatus->{$vmid};
102 __PACKAGE__-
>register_method({
106 description
=> "Create or restore a container.",
108 user
=> 'all', # check inside
109 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
110 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
111 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
116 additionalProperties
=> 0,
117 properties
=> PVE
::LXC
::Config-
>json_config_properties({
118 node
=> get_standard_option
('pve-node'),
119 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
121 description
=> "The OS template or backup file.",
124 completion
=> \
&PVE
::LXC
::complete_os_templates
,
129 description
=> "Sets root password inside container.",
132 storage
=> get_standard_option
('pve-storage-id', {
133 description
=> "Default Storage.",
136 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
141 description
=> "Allow to overwrite existing container.",
146 description
=> "Mark this as restore task.",
151 description
=> "Assign a unique random ethernet address.",
152 requires
=> 'restore',
156 type
=> 'string', format
=> 'pve-poolid',
157 description
=> "Add the VM to the specified pool.",
159 'ignore-unpack-errors' => {
162 description
=> "Ignore errors when extracting the template.",
164 'ssh-public-keys' => {
167 description
=> "Setup public SSH keys (one key per line, " .
171 description
=> "Override I/O bandwidth limit (in KiB/s).",
175 default => 'restore limit from datacenter or storage config',
181 description
=> "Start the CT after its creation finished successfully.",
191 PVE
::Cluster
::check_cfs_quorum
();
193 my $rpcenv = PVE
::RPCEnvironment
::get
();
194 my $authuser = $rpcenv->get_user();
196 my $node = extract_param
($param, 'node');
197 my $vmid = extract_param
($param, 'vmid');
198 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
199 my $bwlimit = extract_param
($param, 'bwlimit');
200 my $start_after_create = extract_param
($param, 'start');
202 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
203 my $same_container_exists = -f
$basecfg_fn;
205 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
206 my $unprivileged = extract_param
($param, 'unprivileged');
207 my $restore = extract_param
($param, 'restore');
208 my $unique = extract_param
($param, 'unique');
210 # used to skip firewall config restore if user lacks permission
211 my $skip_fw_config_restore = 0;
214 # fixme: limit allowed parameters
217 my $force = extract_param
($param, 'force');
219 if (!($same_container_exists && $restore && $force)) {
220 PVE
::Cluster
::check_vmid_unused
($vmid);
222 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
223 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
224 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
227 my $password = extract_param
($param, 'password');
228 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
229 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
231 my $pool = extract_param
($param, 'pool');
232 if (defined($pool)) {
233 $rpcenv->check_pool_exist($pool);
234 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
237 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
239 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
241 } elsif ($restore && $force && $same_container_exists &&
242 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
243 # OK: user has VM.Backup permissions, and want to restore an existing VM
245 # we don't want to restore a container-provided FW conf in this case
246 # since the user is lacking permission to configure the container's FW
247 $skip_fw_config_restore = 1;
252 my $ostemplate = extract_param
($param, 'ostemplate');
253 my $storage = extract_param
($param, 'storage') // 'local';
255 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
257 my $storage_cfg = cfs_read_file
("storage.cfg");
260 if ($ostemplate eq '-') {
261 die "pipe requires cli environment\n"
262 if $rpcenv->{type
} ne 'cli';
263 die "pipe can only be used with restore tasks\n"
266 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
268 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
269 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
273 my $check_and_activate_storage = sub {
276 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
278 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
279 if !$scfg->{content
}->{rootdir
};
281 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
283 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
284 $used_storages{$sid} = 1;
289 my $is_root = $authuser eq 'root@pam';
291 my $no_disk_param = {};
293 my $storage_only_mode = 1;
294 foreach my $opt (keys %$param) {
295 my $value = $param->{$opt};
296 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
297 # allow to use simple numbers (add default storage in that case)
298 if ($value =~ m/^\d+(\.\d+)?$/) {
299 $mp_param->{$opt} = "$storage:$value";
301 $mp_param->{$opt} = $value;
303 $storage_only_mode = 0;
304 } elsif ($opt =~ m/^unused\d+$/) {
305 warn "ignoring '$opt', cannot create/restore with unused volume\n";
306 delete $param->{$opt};
308 $no_disk_param->{$opt} = $value;
312 die "mount points configured, but 'rootfs' not set - aborting\n"
313 if !$storage_only_mode && !defined($mp_param->{rootfs
});
315 # check storage access, activate storage
316 my $delayed_mp_param = {};
317 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
318 my ($ms, $mountpoint) = @_;
320 my $volid = $mountpoint->{volume
};
321 my $mp = $mountpoint->{mp
};
323 if ($mountpoint->{type
} ne 'volume') { # bind or device
324 die "Only root can pass arbitrary filesystem paths.\n"
327 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
328 &$check_and_activate_storage($sid);
332 # check/activate default storage
333 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
335 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
337 $conf->{unprivileged
} = 1 if $unprivileged;
339 my $emsg = $restore ?
"unable to restore CT $vmid -" : "unable to create CT $vmid -";
341 eval { PVE
::LXC
::Config-
>create_and_lock_config($vmid, $force) };
342 die "$emsg $@" if $@;
345 my $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
350 my $orig_mp_param; # only used if $restore
352 die "can't overwrite running container\n" if PVE
::LXC
::check_running
($vmid);
353 if ($is_root && $archive ne '-') {
355 ($orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
356 $was_template = delete $orig_conf->{template
};
357 # When we're root call 'restore_configuration' with restricted=0,
358 # causing it to restore the raw lxc entries, among which there may be
359 # 'lxc.idmap' entries. We need to make sure that the extracted contents
360 # of the container match up with the restored configuration afterwards:
361 $conf->{lxc
} = $orig_conf->{lxc
};
364 if ($storage_only_mode) {
366 if (!defined($orig_mp_param)) {
367 (undef, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
369 $mp_param = $orig_mp_param;
370 die "rootfs configuration could not be recovered, please check and specify manually!\n"
371 if !defined($mp_param->{rootfs
});
372 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
373 my ($ms, $mountpoint) = @_;
374 my $type = $mountpoint->{type
};
375 if ($type eq 'volume') {
376 die "unable to detect disk size - please specify $ms (size)\n"
377 if !defined($mountpoint->{size
});
378 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
379 delete $mountpoint->{size
};
380 $mountpoint->{volume
} = "$storage:$disksize";
381 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
383 my $type = $mountpoint->{type
};
384 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
385 if ($ms eq 'rootfs');
386 die "restoring '$ms' to $type mount is only possible for root\n"
389 if ($mountpoint->{backup
}) {
390 warn "WARNING - unsupported configuration!\n";
391 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
392 warn "mount point configuration will be restored after archive extraction!\n";
393 warn "contained files will be restored to wrong directory!\n";
395 delete $mp_param->{$ms}; # actually delay bind/dev mps
396 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
400 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
404 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
406 # we always have the 'create' lock so check for more than 1 entry
407 if (scalar(keys %$old_conf) > 1) {
408 # destroy old container volumes
409 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, { lock => 'create' });
413 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
414 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
415 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
418 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
419 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
420 $lxc_setup->template_fixup($conf);
422 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
423 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
424 $lxc_setup->post_create_hook($password, $ssh_keys);
428 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
429 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
432 $conf->{hostname
} ||= "CT$vmid";
433 $conf->{memory
} ||= 512;
434 $conf->{swap
} //= 512;
435 foreach my $mp (keys %$delayed_mp_param) {
436 $conf->{$mp} = $delayed_mp_param->{$mp};
438 # If the template flag was set, we try to convert again to template after restore
440 print STDERR
"Convert restored container to template...\n";
441 if (my $err = check_storage_supports_templates
($conf)) {
443 warn "Leave restored backup as container instead of converting to template.\n"
445 PVE
::LXC
::template_create
($vmid, $conf);
446 $conf->{template
} = 1;
449 PVE
::LXC
::Config-
>write_config($vmid, $conf);
452 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
453 eval { PVE
::LXC
::Config-
>destroy_config($vmid) };
457 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
459 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
460 if $start_after_create;
463 my $workername = $restore ?
'vzrestore' : 'vzcreate';
464 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
466 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
469 sub check_storage_supports_templates
{
472 my $scfg = PVE
::Storage
::config
();
474 PVE
::LXC
::Config-
>foreach_mountpoint($conf, sub {
477 my ($sid) = PVE
::Storage
::parse_volume_id
($mp->{volume
}, 0);
478 die "Warning: Directory storage '$sid' does not support container templates!\n"
479 if $scfg->{ids
}->{$sid}->{path
};
485 __PACKAGE__-
>register_method({
490 description
=> "Directory index",
495 additionalProperties
=> 0,
497 node
=> get_standard_option
('pve-node'),
498 vmid
=> get_standard_option
('pve-vmid'),
506 subdir
=> { type
=> 'string' },
509 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
515 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
518 { subdir
=> 'config' },
519 { subdir
=> 'pending' },
520 { subdir
=> 'status' },
521 { subdir
=> 'vncproxy' },
522 { subdir
=> 'termproxy' },
523 { subdir
=> 'vncwebsocket' },
524 { subdir
=> 'spiceproxy' },
525 { subdir
=> 'migrate' },
526 { subdir
=> 'clone' },
527 # { subdir => 'initlog' },
529 { subdir
=> 'rrddata' },
530 { subdir
=> 'firewall' },
531 { subdir
=> 'snapshot' },
532 { subdir
=> 'resize' },
539 __PACKAGE__-
>register_method({
541 path
=> '{vmid}/rrd',
543 protected
=> 1, # fixme: can we avoid that?
545 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
547 description
=> "Read VM RRD statistics (returns PNG)",
549 additionalProperties
=> 0,
551 node
=> get_standard_option
('pve-node'),
552 vmid
=> get_standard_option
('pve-vmid'),
554 description
=> "Specify the time frame you are interested in.",
556 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
559 description
=> "The list of datasources you want to display.",
560 type
=> 'string', format
=> 'pve-configid-list',
563 description
=> "The RRD consolidation function",
565 enum
=> [ 'AVERAGE', 'MAX' ],
573 filename
=> { type
=> 'string' },
579 return PVE
::Cluster
::create_rrd_graph
(
580 "pve2-vm/$param->{vmid}", $param->{timeframe
},
581 $param->{ds
}, $param->{cf
});
585 __PACKAGE__-
>register_method({
587 path
=> '{vmid}/rrddata',
589 protected
=> 1, # fixme: can we avoid that?
591 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
593 description
=> "Read VM RRD statistics",
595 additionalProperties
=> 0,
597 node
=> get_standard_option
('pve-node'),
598 vmid
=> get_standard_option
('pve-vmid'),
600 description
=> "Specify the time frame you are interested in.",
602 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
605 description
=> "The RRD consolidation function",
607 enum
=> [ 'AVERAGE', 'MAX' ],
622 return PVE
::Cluster
::create_rrd_data
(
623 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
626 __PACKAGE__-
>register_method({
627 name
=> 'destroy_vm',
632 description
=> "Destroy the container (also delete all uses files).",
634 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
637 additionalProperties
=> 0,
639 node
=> get_standard_option
('pve-node'),
640 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
643 description
=> "Remove vmid from backup cron jobs.",
654 my $rpcenv = PVE
::RPCEnvironment
::get
();
655 my $authuser = $rpcenv->get_user();
656 my $vmid = $param->{vmid
};
658 # test if container exists
659 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
660 my $storage_cfg = cfs_read_file
("storage.cfg");
661 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
663 die "unable to remove CT $vmid - used in HA resources\n"
664 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
666 if (!$param->{purge
}) {
667 # do not allow destroy if there are replication jobs without purge
668 my $repl_conf = PVE
::ReplicationConfig-
>new();
669 $repl_conf->check_for_existing_jobs($vmid);
672 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
674 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
677 # reload config after lock
678 $conf = PVE
::LXC
::Config-
>load_config($vmid);
679 PVE
::LXC
::Config-
>check_lock($conf);
681 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
683 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf, { lock => 'destroyed' });
685 PVE
::AccessControl
::remove_vm_access
($vmid);
686 PVE
::Firewall
::remove_vmfw_conf
($vmid);
687 if ($param->{purge
}) {
688 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
689 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
692 # only now remove the zombie config, else we can have reuse race
693 PVE
::LXC
::Config-
>destroy_config($vmid);
696 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
698 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
703 __PACKAGE__-
>register_method ({
705 path
=> '{vmid}/vncproxy',
709 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
711 description
=> "Creates a TCP VNC proxy connections.",
713 additionalProperties
=> 0,
715 node
=> get_standard_option
('pve-node'),
716 vmid
=> get_standard_option
('pve-vmid'),
720 description
=> "use websocket instead of standard VNC.",
724 description
=> "sets the width of the console in pixels.",
731 description
=> "sets the height of the console in pixels.",
739 additionalProperties
=> 0,
741 user
=> { type
=> 'string' },
742 ticket
=> { type
=> 'string' },
743 cert
=> { type
=> 'string' },
744 port
=> { type
=> 'integer' },
745 upid
=> { type
=> 'string' },
751 my $rpcenv = PVE
::RPCEnvironment
::get
();
753 my $authuser = $rpcenv->get_user();
755 my $vmid = $param->{vmid
};
756 my $node = $param->{node
};
758 my $authpath = "/vms/$vmid";
760 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
762 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
765 my ($remip, $family);
767 if ($node ne PVE
::INotify
::nodename
()) {
768 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
770 $family = PVE
::Tools
::get_host_address_family
($node);
773 my $port = PVE
::Tools
::next_vnc_port
($family);
775 # NOTE: vncterm VNC traffic is already TLS encrypted,
776 # so we select the fastest chipher here (or 'none'?)
777 my $remcmd = $remip ?
778 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
780 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
781 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
783 my $shcmd = [ '/usr/bin/dtach', '-A',
784 "/var/run/dtach/vzctlconsole$vmid",
785 '-r', 'winch', '-z', @$concmd];
790 syslog
('info', "starting lxc vnc proxy $upid\n");
794 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
795 '-timeout', $timeout, '-authpath', $authpath,
796 '-perm', 'VM.Console'];
798 if ($param->{width
}) {
799 push @$cmd, '-width', $param->{width
};
802 if ($param->{height
}) {
803 push @$cmd, '-height', $param->{height
};
806 if ($param->{websocket
}) {
807 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
808 push @$cmd, '-notls', '-listen', 'localhost';
811 push @$cmd, '-c', @$remcmd, @$shcmd;
813 run_command
($cmd, keeplocale
=> 1);
818 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
820 PVE
::Tools
::wait_for_vnc_port
($port);
831 __PACKAGE__-
>register_method ({
833 path
=> '{vmid}/termproxy',
837 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
839 description
=> "Creates a TCP proxy connection.",
841 additionalProperties
=> 0,
843 node
=> get_standard_option
('pve-node'),
844 vmid
=> get_standard_option
('pve-vmid'),
848 additionalProperties
=> 0,
850 user
=> { type
=> 'string' },
851 ticket
=> { type
=> 'string' },
852 port
=> { type
=> 'integer' },
853 upid
=> { type
=> 'string' },
859 my $rpcenv = PVE
::RPCEnvironment
::get
();
861 my $authuser = $rpcenv->get_user();
863 my $vmid = $param->{vmid
};
864 my $node = $param->{node
};
866 my $authpath = "/vms/$vmid";
868 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
870 my ($remip, $family);
872 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
873 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
875 $family = PVE
::Tools
::get_host_address_family
($node);
878 my $port = PVE
::Tools
::next_vnc_port
($family);
880 my $remcmd = $remip ?
881 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
883 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
884 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
886 my $shcmd = [ '/usr/bin/dtach', '-A',
887 "/var/run/dtach/vzctlconsole$vmid",
888 '-r', 'winch', '-z', @$concmd];
893 syslog
('info', "starting lxc termproxy $upid\n");
895 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
896 '--perm', 'VM.Console', '--'];
897 push @$cmd, @$remcmd, @$shcmd;
899 PVE
::Tools
::run_command
($cmd);
902 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
904 PVE
::Tools
::wait_for_vnc_port
($port);
914 __PACKAGE__-
>register_method({
915 name
=> 'vncwebsocket',
916 path
=> '{vmid}/vncwebsocket',
919 description
=> "You also need to pass a valid ticket (vncticket).",
920 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
922 description
=> "Opens a weksocket for VNC traffic.",
924 additionalProperties
=> 0,
926 node
=> get_standard_option
('pve-node'),
927 vmid
=> get_standard_option
('pve-vmid'),
929 description
=> "Ticket from previous call to vncproxy.",
934 description
=> "Port number returned by previous vncproxy call.",
944 port
=> { type
=> 'string' },
950 my $rpcenv = PVE
::RPCEnvironment
::get
();
952 my $authuser = $rpcenv->get_user();
954 my $authpath = "/vms/$param->{vmid}";
956 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
958 my $port = $param->{port
};
960 return { port
=> $port };
963 __PACKAGE__-
>register_method ({
964 name
=> 'spiceproxy',
965 path
=> '{vmid}/spiceproxy',
970 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
972 description
=> "Returns a SPICE configuration to connect to the CT.",
974 additionalProperties
=> 0,
976 node
=> get_standard_option
('pve-node'),
977 vmid
=> get_standard_option
('pve-vmid'),
978 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
981 returns
=> get_standard_option
('remote-viewer-config'),
985 my $vmid = $param->{vmid
};
986 my $node = $param->{node
};
987 my $proxy = $param->{proxy
};
989 my $authpath = "/vms/$vmid";
990 my $permissions = 'VM.Console';
992 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
994 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
996 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
998 my $shcmd = ['/usr/bin/dtach', '-A',
999 "/var/run/dtach/vzctlconsole$vmid",
1000 '-r', 'winch', '-z', @$concmd];
1002 my $title = "CT $vmid";
1004 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1008 __PACKAGE__-
>register_method({
1009 name
=> 'migrate_vm',
1010 path
=> '{vmid}/migrate',
1014 description
=> "Migrate the container to another node. Creates a new migration task.",
1016 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1019 additionalProperties
=> 0,
1021 node
=> get_standard_option
('pve-node'),
1022 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1023 target
=> get_standard_option
('pve-node', {
1024 description
=> "Target node.",
1025 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1029 description
=> "Use online/live migration.",
1034 description
=> "Use restart migration",
1039 description
=> "Timeout in seconds for shutdown for restart migration",
1045 description
=> "Force migration despite local bind / device" .
1046 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1050 description
=> "Override I/O bandwidth limit (in KiB/s).",
1054 default => 'migrate limit from datacenter or storage config',
1060 description
=> "the task ID.",
1065 my $rpcenv = PVE
::RPCEnvironment
::get
();
1067 my $authuser = $rpcenv->get_user();
1069 my $target = extract_param
($param, 'target');
1071 my $localnode = PVE
::INotify
::nodename
();
1072 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1074 PVE
::Cluster
::check_cfs_quorum
();
1076 PVE
::Cluster
::check_node_exists
($target);
1078 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1080 my $vmid = extract_param
($param, 'vmid');
1083 PVE
::LXC
::Config-
>load_config($vmid);
1085 # try to detect errors early
1086 if (PVE
::LXC
::check_running
($vmid)) {
1087 die "can't migrate running container without --online or --restart\n"
1088 if !$param->{online
} && !$param->{restart
};
1091 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1096 my $service = "ct:$vmid";
1098 my $cmd = ['ha-manager', 'migrate', $service, $target];
1100 print "Requesting HA migration for CT $vmid to node $target\n";
1102 PVE
::Tools
::run_command
($cmd);
1107 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1112 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1116 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1119 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1123 __PACKAGE__-
>register_method({
1124 name
=> 'vm_feature',
1125 path
=> '{vmid}/feature',
1129 description
=> "Check if feature for virtual machine is available.",
1131 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1134 additionalProperties
=> 0,
1136 node
=> get_standard_option
('pve-node'),
1137 vmid
=> get_standard_option
('pve-vmid'),
1139 description
=> "Feature to check.",
1141 enum
=> [ 'snapshot', 'clone', 'copy' ],
1143 snapname
=> get_standard_option
('pve-snapshot-name', {
1151 hasFeature
=> { type
=> 'boolean' },
1154 #items => { type => 'string' },
1161 my $node = extract_param
($param, 'node');
1163 my $vmid = extract_param
($param, 'vmid');
1165 my $snapname = extract_param
($param, 'snapname');
1167 my $feature = extract_param
($param, 'feature');
1169 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1172 my $snap = $conf->{snapshots
}->{$snapname};
1173 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1176 my $storage_cfg = PVE
::Storage
::config
();
1177 #Maybe include later
1178 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1179 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1182 hasFeature
=> $hasFeature,
1183 #nodes => [ keys %$nodelist ],
1187 __PACKAGE__-
>register_method({
1189 path
=> '{vmid}/template',
1193 description
=> "Create a Template.",
1195 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1196 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1199 additionalProperties
=> 0,
1201 node
=> get_standard_option
('pve-node'),
1202 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1205 returns
=> { type
=> 'null'},
1209 my $rpcenv = PVE
::RPCEnvironment
::get
();
1211 my $authuser = $rpcenv->get_user();
1213 my $node = extract_param
($param, 'node');
1215 my $vmid = extract_param
($param, 'vmid');
1217 my $updatefn = sub {
1219 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1220 PVE
::LXC
::Config-
>check_lock($conf);
1222 die "unable to create template, because CT contains snapshots\n"
1223 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1225 die "you can't convert a template to a template\n"
1226 if PVE
::LXC
::Config-
>is_template($conf);
1228 die "you can't convert a CT to template if the CT is running\n"
1229 if PVE
::LXC
::check_running
($vmid);
1231 if (my $err = check_storage_supports_templates
($conf)) {
1236 PVE
::LXC
::template_create
($vmid, $conf);
1238 $conf->{template
} = 1;
1240 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1241 # and remove lxc config
1242 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1245 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1248 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1253 __PACKAGE__-
>register_method({
1255 path
=> '{vmid}/clone',
1259 description
=> "Create a container clone/copy",
1261 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1262 "and 'VM.Allocate' permissions " .
1263 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1264 "'Datastore.AllocateSpace' on any used storage.",
1267 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1269 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1270 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1275 additionalProperties
=> 0,
1277 node
=> get_standard_option
('pve-node'),
1278 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1279 newid
=> get_standard_option
('pve-vmid', {
1280 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1281 description
=> 'VMID for the clone.' }),
1284 type
=> 'string', format
=> 'dns-name',
1285 description
=> "Set a hostname for the new CT.",
1290 description
=> "Description for the new CT.",
1294 type
=> 'string', format
=> 'pve-poolid',
1295 description
=> "Add the new CT to the specified pool.",
1297 snapname
=> get_standard_option
('pve-snapshot-name', {
1300 storage
=> get_standard_option
('pve-storage-id', {
1301 description
=> "Target storage for full clone.",
1307 description
=> "Create a full copy of all disks. This is always done when " .
1308 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1310 target
=> get_standard_option
('pve-node', {
1311 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1315 description
=> "Override I/O bandwidth limit (in KiB/s).",
1319 default => 'clone limit from datacenter or storage config',
1329 my $rpcenv = PVE
::RPCEnvironment
::get
();
1331 my $authuser = $rpcenv->get_user();
1333 my $node = extract_param
($param, 'node');
1335 my $vmid = extract_param
($param, 'vmid');
1337 my $newid = extract_param
($param, 'newid');
1339 my $pool = extract_param
($param, 'pool');
1341 if (defined($pool)) {
1342 $rpcenv->check_pool_exist($pool);
1345 my $snapname = extract_param
($param, 'snapname');
1347 my $storage = extract_param
($param, 'storage');
1349 my $target = extract_param
($param, 'target');
1351 my $localnode = PVE
::INotify
::nodename
();
1353 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1355 PVE
::Cluster
::check_node_exists
($target) if $target;
1357 my $storecfg = PVE
::Storage
::config
();
1360 # check if storage is enabled on local node
1361 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1363 # check if storage is available on target node
1364 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1365 # clone only works if target storage is shared
1366 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1367 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1371 PVE
::Cluster
::check_cfs_quorum
();
1375 my $mountpoints = {};
1380 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1381 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1383 $running = PVE
::LXC
::check_running
($vmid) || 0;
1385 my $full = extract_param
($param, 'full');
1386 if (!defined($full)) {
1387 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1389 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1392 die "snapshot '$snapname' does not exist\n"
1393 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1396 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1398 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1399 die "unable to create CT $newid: config file already exists\n"
1403 foreach my $opt (keys %$src_conf) {
1404 next if $opt =~ m/^unused\d+$/;
1406 my $value = $src_conf->{$opt};
1408 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1409 my $mp = $opt eq 'rootfs' ?
1410 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1411 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1413 if ($mp->{type
} eq 'volume') {
1414 my $volid = $mp->{volume
};
1416 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1417 $sid = $storage if defined($storage);
1418 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1419 if (!$scfg->{shared
}) {
1421 warn "found non-shared volume: $volid\n" if $target;
1424 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1427 die "Cannot do full clones on a running container without snapshots\n"
1428 if $running && !defined($snapname);
1429 $fullclone->{$opt} = 1;
1431 # not full means clone instead of copy
1432 die "Linked clone feature for '$volid' is not available\n"
1433 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1436 $mountpoints->{$opt} = $mp;
1437 push @$vollist, $volid;
1440 # TODO: allow bind mounts?
1441 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1443 } elsif ($opt =~ m/^net(\d+)$/) {
1444 # always change MAC! address
1445 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1446 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1447 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1448 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1450 # copy everything else
1451 $newconf->{$opt} = $value;
1454 die "can't clone CT to node '$target' (CT uses local storage)\n"
1455 if $target && !$sharedvm;
1457 # Replace the 'disk' lock with a 'create' lock.
1458 $newconf->{lock} = 'create';
1460 delete $newconf->{pending
};
1461 delete $newconf->{template
};
1462 if ($param->{hostname
}) {
1463 $newconf->{hostname
} = $param->{hostname
};
1466 if ($param->{description
}) {
1467 $newconf->{description
} = $param->{description
};
1470 # create empty/temp config - this fails if CT already exists on other node
1471 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1474 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1480 my $update_conf = sub {
1481 my ($key, $value) = @_;
1482 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1483 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1484 die "Lost 'create' config lock, aborting.\n"
1485 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1486 $conf->{$key} = $value;
1487 PVE
::LXC
::Config-
>write_config($newid, $conf);
1494 my $newvollist = [];
1496 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1497 die "unexpected state change\n" if $verify_running != $running;
1503 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1505 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1506 my $bwlimit = extract_param
($param, 'bwlimit');
1508 foreach my $opt (keys %$mountpoints) {
1509 my $mp = $mountpoints->{$opt};
1510 my $volid = $mp->{volume
};
1513 if ($fullclone->{$opt}) {
1514 print "create full clone of mountpoint $opt ($volid)\n";
1515 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1516 my $target_storage = $storage // $source_storage;
1517 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1518 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1520 print "create linked clone of mount point $opt ($volid)\n";
1521 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1524 push @$newvollist, $newvolid;
1525 $mp->{volume
} = $newvolid;
1527 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1530 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1531 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1534 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1535 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1536 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1538 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1539 die "Failed to move config to node '$target' - rename failed: $!\n"
1540 if !rename($conffile, $newconffile);
1545 # Unlock the source config in any case:
1546 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1550 # Now cleanup the config & disks:
1553 sleep 1; # some storages like rbd need to wait before release volume - really?
1555 foreach my $volid (@$newvollist) {
1556 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1559 die "clone failed: $err";
1565 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1566 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1570 __PACKAGE__-
>register_method({
1571 name
=> 'resize_vm',
1572 path
=> '{vmid}/resize',
1576 description
=> "Resize a container mount point.",
1578 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1581 additionalProperties
=> 0,
1583 node
=> get_standard_option
('pve-node'),
1584 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1587 description
=> "The disk you want to resize.",
1588 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1592 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1593 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.",
1597 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1605 description
=> "the task ID.",
1610 my $rpcenv = PVE
::RPCEnvironment
::get
();
1612 my $authuser = $rpcenv->get_user();
1614 my $node = extract_param
($param, 'node');
1616 my $vmid = extract_param
($param, 'vmid');
1618 my $digest = extract_param
($param, 'digest');
1620 my $sizestr = extract_param
($param, 'size');
1621 my $ext = ($sizestr =~ s/^\+//);
1622 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1623 die "invalid size string" if !defined($newsize);
1625 die "no options specified\n" if !scalar(keys %$param);
1627 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1629 my $storage_cfg = cfs_read_file
("storage.cfg");
1633 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1634 PVE
::LXC
::Config-
>check_lock($conf);
1636 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1638 my $running = PVE
::LXC
::check_running
($vmid);
1640 my $disk = $param->{disk
};
1641 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1642 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1644 my $volid = $mp->{volume
};
1646 my (undef, undef, $owner, undef, undef, undef, $format) =
1647 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1649 die "can't resize mount point owned by another container ($owner)"
1652 die "can't resize volume: $disk if snapshot exists\n"
1653 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1655 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1657 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1659 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1661 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1663 die "Size of volume $volid couldn't be determined\n" if (!defined($size));
1665 $newsize += $size if $ext;
1666 $newsize = int($newsize);
1668 die "unable to shrink disk size\n" if $newsize < $size;
1670 return if $size == $newsize;
1672 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1674 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1675 # we pass 0 here (parameter only makes sense for qemu)
1676 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1678 $mp->{size
} = $newsize;
1679 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1681 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1683 if ($format eq 'raw') {
1684 # we need to ensure that the volume is mapped, if not needed this is a NOP
1685 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1686 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1690 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1691 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1692 die "internal error: CT running but mount point not attached to a loop device"
1694 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1696 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1697 # to be visible to it in its namespace.
1698 # To not interfere with the rest of the system we unshare the current mount namespace,
1699 # mount over /tmp and then run resize2fs.
1701 # interestingly we don't need to e2fsck on mounted systems...
1702 my $quoted = PVE
::Tools
::shellquote
($path);
1703 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1705 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1707 warn "Failed to update the container's filesystem: $@\n" if $@;
1710 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1711 PVE
::Tools
::run_command
(['resize2fs', $path]);
1713 warn "Failed to update the container's filesystem: $@\n" if $@;
1715 # always un-map if not running, this is a NOP if not needed
1716 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1721 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1724 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1727 __PACKAGE__-
>register_method({
1728 name
=> 'move_volume',
1729 path
=> '{vmid}/move_volume',
1733 description
=> "Move a rootfs-/mp-volume to a different storage",
1735 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1736 "and 'Datastore.AllocateSpace' permissions on the storage.",
1739 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1740 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1744 additionalProperties
=> 0,
1746 node
=> get_standard_option
('pve-node'),
1747 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1750 enum
=> [ PVE
::LXC
::Config-
>mountpoint_names() ],
1751 description
=> "Volume which will be moved.",
1753 storage
=> get_standard_option
('pve-storage-id', {
1754 description
=> "Target Storage.",
1755 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1759 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1765 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1770 description
=> "Override I/O bandwidth limit (in KiB/s).",
1774 default => 'clone limit from datacenter or storage config',
1784 my $rpcenv = PVE
::RPCEnvironment
::get
();
1786 my $authuser = $rpcenv->get_user();
1788 my $vmid = extract_param
($param, 'vmid');
1790 my $storage = extract_param
($param, 'storage');
1792 my $mpkey = extract_param
($param, 'volume');
1794 my $lockname = 'disk';
1796 my ($mpdata, $old_volid);
1798 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1799 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1800 PVE
::LXC
::Config-
>check_lock($conf);
1802 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1804 if ($mpkey eq 'rootfs') {
1805 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1806 } elsif ($mpkey =~ m/mp\d+/) {
1807 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1809 die "Can't parse $mpkey\n";
1811 $old_volid = $mpdata->{volume
};
1813 die "you can't move a volume with snapshots and delete the source\n"
1814 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1816 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1818 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1823 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1825 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1826 my $storage_cfg = PVE
::Storage
::config
();
1831 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1832 my $bwlimit = extract_param
($param, 'bwlimit');
1833 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1834 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$source_storage, $storage], $bwlimit);
1835 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf, undef, $movelimit);
1836 $mpdata->{volume
} = $new_volid;
1838 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1839 my $digest = $conf->{digest
};
1840 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1841 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1843 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1845 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1847 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1851 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1852 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1858 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1859 if defined($new_volid);
1865 if ($param->{delete}) {
1867 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1868 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1874 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1879 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1882 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1889 __PACKAGE__-
>register_method({
1890 name
=> 'vm_pending',
1891 path
=> '{vmid}/pending',
1894 description
=> 'Get container configuration, including pending changes.',
1896 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1899 additionalProperties
=> 0,
1901 node
=> get_standard_option
('pve-node'),
1902 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1911 description
=> 'Configuration option name.',
1915 description
=> 'Current value.',
1920 description
=> 'Pending value.',
1925 description
=> "Indicates a pending delete request if present and not 0.",
1937 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
1939 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
1941 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);