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);
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);
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.",
644 my $rpcenv = PVE
::RPCEnvironment
::get
();
645 my $authuser = $rpcenv->get_user();
646 my $vmid = $param->{vmid
};
648 # test if container exists
650 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
651 my $early_checks = sub {
653 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
654 PVE
::LXC
::Config-
>check_lock($conf);
656 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("ct:$vmid");
658 if (!$param->{purge
}) {
659 die "unable to remove CT $vmid - used in HA resources and purge parameter not set.\n"
662 # do not allow destroy if there are replication jobs without purge
663 my $repl_conf = PVE
::ReplicationConfig-
>new();
664 $repl_conf->check_for_existing_jobs($vmid);
670 $early_checks->($conf);
672 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
673 die $running_error_msg if !$param->{force
} && PVE
::LXC
::check_running
($vmid); # check early
676 # reload config after lock
677 $conf = PVE
::LXC
::Config-
>load_config($vmid);
678 my $ha_managed = $early_checks->($conf);
680 if (PVE
::LXC
::check_running
($vmid)) {
681 die $running_error_msg if !$param->{force
};
682 warn "forced to stop CT $vmid before destroying!\n";
684 PVE
::LXC
::vm_stop
($vmid, 1);
686 run_command
(['ha-manager', 'crm-command', 'stop', "ct:$vmid", '120']);
690 my $storage_cfg = cfs_read_file
("storage.cfg");
691 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf, { lock => 'destroyed' });
693 PVE
::AccessControl
::remove_vm_access
($vmid);
694 PVE
::Firewall
::remove_vmfw_conf
($vmid);
695 if ($param->{purge
}) {
696 print "purging CT $vmid from related configurations..\n";
697 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
698 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
701 PVE
::HA
::Config
::delete_service_from_config
("ct:$vmid");
702 print "NOTE: removed CT $vmid from HA resource configuration.\n";
706 # only now remove the zombie config, else we can have reuse race
707 PVE
::LXC
::Config-
>destroy_config($vmid);
710 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
712 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
717 __PACKAGE__-
>register_method ({
719 path
=> '{vmid}/vncproxy',
723 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
725 description
=> "Creates a TCP VNC proxy connections.",
727 additionalProperties
=> 0,
729 node
=> get_standard_option
('pve-node'),
730 vmid
=> get_standard_option
('pve-vmid'),
734 description
=> "use websocket instead of standard VNC.",
738 description
=> "sets the width of the console in pixels.",
745 description
=> "sets the height of the console in pixels.",
753 additionalProperties
=> 0,
755 user
=> { type
=> 'string' },
756 ticket
=> { type
=> 'string' },
757 cert
=> { type
=> 'string' },
758 port
=> { type
=> 'integer' },
759 upid
=> { type
=> 'string' },
765 my $rpcenv = PVE
::RPCEnvironment
::get
();
767 my $authuser = $rpcenv->get_user();
769 my $vmid = $param->{vmid
};
770 my $node = $param->{node
};
772 my $authpath = "/vms/$vmid";
774 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
776 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
779 my ($remip, $family);
781 if ($node ne PVE
::INotify
::nodename
()) {
782 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
784 $family = PVE
::Tools
::get_host_address_family
($node);
787 my $port = PVE
::Tools
::next_vnc_port
($family);
789 # NOTE: vncterm VNC traffic is already TLS encrypted,
790 # so we select the fastest chipher here (or 'none'?)
791 my $remcmd = $remip ?
792 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
794 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
795 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
797 my $shcmd = [ '/usr/bin/dtach', '-A',
798 "/var/run/dtach/vzctlconsole$vmid",
799 '-r', 'winch', '-z', @$concmd];
804 syslog
('info', "starting lxc vnc proxy $upid\n");
808 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
809 '-timeout', $timeout, '-authpath', $authpath,
810 '-perm', 'VM.Console'];
812 if ($param->{width
}) {
813 push @$cmd, '-width', $param->{width
};
816 if ($param->{height
}) {
817 push @$cmd, '-height', $param->{height
};
820 if ($param->{websocket
}) {
821 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
822 push @$cmd, '-notls', '-listen', 'localhost';
825 push @$cmd, '-c', @$remcmd, @$shcmd;
827 run_command
($cmd, keeplocale
=> 1);
832 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
834 PVE
::Tools
::wait_for_vnc_port
($port);
845 __PACKAGE__-
>register_method ({
847 path
=> '{vmid}/termproxy',
851 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
853 description
=> "Creates a TCP proxy connection.",
855 additionalProperties
=> 0,
857 node
=> get_standard_option
('pve-node'),
858 vmid
=> get_standard_option
('pve-vmid'),
862 additionalProperties
=> 0,
864 user
=> { type
=> 'string' },
865 ticket
=> { type
=> 'string' },
866 port
=> { type
=> 'integer' },
867 upid
=> { type
=> 'string' },
873 my $rpcenv = PVE
::RPCEnvironment
::get
();
875 my $authuser = $rpcenv->get_user();
877 my $vmid = $param->{vmid
};
878 my $node = $param->{node
};
880 my $authpath = "/vms/$vmid";
882 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
884 my ($remip, $family);
886 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
887 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
889 $family = PVE
::Tools
::get_host_address_family
($node);
892 my $port = PVE
::Tools
::next_vnc_port
($family);
894 my $remcmd = $remip ?
895 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
897 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
898 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
900 my $shcmd = [ '/usr/bin/dtach', '-A',
901 "/var/run/dtach/vzctlconsole$vmid",
902 '-r', 'winch', '-z', @$concmd];
907 syslog
('info', "starting lxc termproxy $upid\n");
909 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
910 '--perm', 'VM.Console', '--'];
911 push @$cmd, @$remcmd, @$shcmd;
913 PVE
::Tools
::run_command
($cmd);
916 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
918 PVE
::Tools
::wait_for_vnc_port
($port);
928 __PACKAGE__-
>register_method({
929 name
=> 'vncwebsocket',
930 path
=> '{vmid}/vncwebsocket',
933 description
=> "You also need to pass a valid ticket (vncticket).",
934 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
936 description
=> "Opens a weksocket for VNC traffic.",
938 additionalProperties
=> 0,
940 node
=> get_standard_option
('pve-node'),
941 vmid
=> get_standard_option
('pve-vmid'),
943 description
=> "Ticket from previous call to vncproxy.",
948 description
=> "Port number returned by previous vncproxy call.",
958 port
=> { type
=> 'string' },
964 my $rpcenv = PVE
::RPCEnvironment
::get
();
966 my $authuser = $rpcenv->get_user();
968 my $authpath = "/vms/$param->{vmid}";
970 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
972 my $port = $param->{port
};
974 return { port
=> $port };
977 __PACKAGE__-
>register_method ({
978 name
=> 'spiceproxy',
979 path
=> '{vmid}/spiceproxy',
984 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
986 description
=> "Returns a SPICE configuration to connect to the CT.",
988 additionalProperties
=> 0,
990 node
=> get_standard_option
('pve-node'),
991 vmid
=> get_standard_option
('pve-vmid'),
992 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
995 returns
=> get_standard_option
('remote-viewer-config'),
999 my $vmid = $param->{vmid
};
1000 my $node = $param->{node
};
1001 my $proxy = $param->{proxy
};
1003 my $authpath = "/vms/$vmid";
1004 my $permissions = 'VM.Console';
1006 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1008 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
1010 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
1012 my $shcmd = ['/usr/bin/dtach', '-A',
1013 "/var/run/dtach/vzctlconsole$vmid",
1014 '-r', 'winch', '-z', @$concmd];
1016 my $title = "CT $vmid";
1018 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1022 __PACKAGE__-
>register_method({
1023 name
=> 'migrate_vm',
1024 path
=> '{vmid}/migrate',
1028 description
=> "Migrate the container to another node. Creates a new migration task.",
1030 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1033 additionalProperties
=> 0,
1035 node
=> get_standard_option
('pve-node'),
1036 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1037 target
=> get_standard_option
('pve-node', {
1038 description
=> "Target node.",
1039 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1043 description
=> "Use online/live migration.",
1048 description
=> "Use restart migration",
1053 description
=> "Timeout in seconds for shutdown for restart migration",
1059 description
=> "Force migration despite local bind / device" .
1060 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1064 description
=> "Override I/O bandwidth limit (in KiB/s).",
1068 default => 'migrate limit from datacenter or storage config',
1074 description
=> "the task ID.",
1079 my $rpcenv = PVE
::RPCEnvironment
::get
();
1081 my $authuser = $rpcenv->get_user();
1083 my $target = extract_param
($param, 'target');
1085 my $localnode = PVE
::INotify
::nodename
();
1086 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1088 PVE
::Cluster
::check_cfs_quorum
();
1090 PVE
::Cluster
::check_node_exists
($target);
1092 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1094 my $vmid = extract_param
($param, 'vmid');
1097 PVE
::LXC
::Config-
>load_config($vmid);
1099 # try to detect errors early
1100 if (PVE
::LXC
::check_running
($vmid)) {
1101 die "can't migrate running container without --online or --restart\n"
1102 if !$param->{online
} && !$param->{restart
};
1105 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1110 my $service = "ct:$vmid";
1112 my $cmd = ['ha-manager', 'migrate', $service, $target];
1114 print "Requesting HA migration for CT $vmid to node $target\n";
1116 PVE
::Tools
::run_command
($cmd);
1121 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1126 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1130 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1133 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1137 __PACKAGE__-
>register_method({
1138 name
=> 'vm_feature',
1139 path
=> '{vmid}/feature',
1143 description
=> "Check if feature for virtual machine is available.",
1145 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1148 additionalProperties
=> 0,
1150 node
=> get_standard_option
('pve-node'),
1151 vmid
=> get_standard_option
('pve-vmid'),
1153 description
=> "Feature to check.",
1155 enum
=> [ 'snapshot', 'clone', 'copy' ],
1157 snapname
=> get_standard_option
('pve-snapshot-name', {
1165 hasFeature
=> { type
=> 'boolean' },
1168 #items => { type => 'string' },
1175 my $node = extract_param
($param, 'node');
1177 my $vmid = extract_param
($param, 'vmid');
1179 my $snapname = extract_param
($param, 'snapname');
1181 my $feature = extract_param
($param, 'feature');
1183 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1186 my $snap = $conf->{snapshots
}->{$snapname};
1187 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1190 my $storage_cfg = PVE
::Storage
::config
();
1191 #Maybe include later
1192 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1193 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1196 hasFeature
=> $hasFeature,
1197 #nodes => [ keys %$nodelist ],
1201 __PACKAGE__-
>register_method({
1203 path
=> '{vmid}/template',
1207 description
=> "Create a Template.",
1209 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1210 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1213 additionalProperties
=> 0,
1215 node
=> get_standard_option
('pve-node'),
1216 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1219 returns
=> { type
=> 'null'},
1223 my $rpcenv = PVE
::RPCEnvironment
::get
();
1225 my $authuser = $rpcenv->get_user();
1227 my $node = extract_param
($param, 'node');
1229 my $vmid = extract_param
($param, 'vmid');
1231 my $updatefn = sub {
1233 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1234 PVE
::LXC
::Config-
>check_lock($conf);
1236 die "unable to create template, because CT contains snapshots\n"
1237 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1239 die "you can't convert a template to a template\n"
1240 if PVE
::LXC
::Config-
>is_template($conf);
1242 die "you can't convert a CT to template if the CT is running\n"
1243 if PVE
::LXC
::check_running
($vmid);
1246 PVE
::LXC
::template_create
($vmid, $conf);
1248 $conf->{template
} = 1;
1250 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1251 # and remove lxc config
1252 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1255 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1258 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1263 __PACKAGE__-
>register_method({
1265 path
=> '{vmid}/clone',
1269 description
=> "Create a container clone/copy",
1271 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1272 "and 'VM.Allocate' permissions " .
1273 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1274 "'Datastore.AllocateSpace' on any used storage.",
1277 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1279 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1280 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1285 additionalProperties
=> 0,
1287 node
=> get_standard_option
('pve-node'),
1288 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1289 newid
=> get_standard_option
('pve-vmid', {
1290 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1291 description
=> 'VMID for the clone.' }),
1294 type
=> 'string', format
=> 'dns-name',
1295 description
=> "Set a hostname for the new CT.",
1300 description
=> "Description for the new CT.",
1304 type
=> 'string', format
=> 'pve-poolid',
1305 description
=> "Add the new CT to the specified pool.",
1307 snapname
=> get_standard_option
('pve-snapshot-name', {
1310 storage
=> get_standard_option
('pve-storage-id', {
1311 description
=> "Target storage for full clone.",
1317 description
=> "Create a full copy of all disks. This is always done when " .
1318 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1320 target
=> get_standard_option
('pve-node', {
1321 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1325 description
=> "Override I/O bandwidth limit (in KiB/s).",
1329 default => 'clone limit from datacenter or storage config',
1339 my $rpcenv = PVE
::RPCEnvironment
::get
();
1341 my $authuser = $rpcenv->get_user();
1343 my $node = extract_param
($param, 'node');
1345 my $vmid = extract_param
($param, 'vmid');
1347 my $newid = extract_param
($param, 'newid');
1349 my $pool = extract_param
($param, 'pool');
1351 if (defined($pool)) {
1352 $rpcenv->check_pool_exist($pool);
1355 my $snapname = extract_param
($param, 'snapname');
1357 my $storage = extract_param
($param, 'storage');
1359 my $target = extract_param
($param, 'target');
1361 my $localnode = PVE
::INotify
::nodename
();
1363 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1365 PVE
::Cluster
::check_node_exists
($target) if $target;
1367 my $storecfg = PVE
::Storage
::config
();
1370 # check if storage is enabled on local node
1371 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1373 # check if storage is available on target node
1374 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1375 # clone only works if target storage is shared
1376 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1377 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1381 PVE
::Cluster
::check_cfs_quorum
();
1385 my $mountpoints = {};
1390 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1391 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1393 $running = PVE
::LXC
::check_running
($vmid) || 0;
1395 my $full = extract_param
($param, 'full');
1396 if (!defined($full)) {
1397 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1399 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1402 die "snapshot '$snapname' does not exist\n"
1403 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1406 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1408 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1409 die "unable to create CT $newid: config file already exists\n"
1413 foreach my $opt (keys %$src_conf) {
1414 next if $opt =~ m/^unused\d+$/;
1416 my $value = $src_conf->{$opt};
1418 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1419 my $mp = PVE
::LXC
::Config-
>parse_volume($opt, $value);
1421 if ($mp->{type
} eq 'volume') {
1422 my $volid = $mp->{volume
};
1424 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1425 $sid = $storage if defined($storage);
1426 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1427 if (!$scfg->{shared
}) {
1429 warn "found non-shared volume: $volid\n" if $target;
1432 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1435 die "Cannot do full clones on a running container without snapshots\n"
1436 if $running && !defined($snapname);
1437 $fullclone->{$opt} = 1;
1439 # not full means clone instead of copy
1440 die "Linked clone feature for '$volid' is not available\n"
1441 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
1444 $mountpoints->{$opt} = $mp;
1445 push @$vollist, $volid;
1448 # TODO: allow bind mounts?
1449 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1451 } elsif ($opt =~ m/^net(\d+)$/) {
1452 # always change MAC! address
1453 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1454 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1455 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1456 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1458 # copy everything else
1459 $newconf->{$opt} = $value;
1462 die "can't clone CT to node '$target' (CT uses local storage)\n"
1463 if $target && !$sharedvm;
1465 # Replace the 'disk' lock with a 'create' lock.
1466 $newconf->{lock} = 'create';
1468 delete $newconf->{pending
};
1469 delete $newconf->{template
};
1470 if ($param->{hostname
}) {
1471 $newconf->{hostname
} = $param->{hostname
};
1474 if ($param->{description
}) {
1475 $newconf->{description
} = $param->{description
};
1478 # create empty/temp config - this fails if CT already exists on other node
1479 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1482 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1488 my $update_conf = sub {
1489 my ($key, $value) = @_;
1490 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1491 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1492 die "Lost 'create' config lock, aborting.\n"
1493 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1494 $conf->{$key} = $value;
1495 PVE
::LXC
::Config-
>write_config($newid, $conf);
1502 my $newvollist = [];
1504 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1505 die "unexpected state change\n" if $verify_running != $running;
1511 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1513 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1514 my $bwlimit = extract_param
($param, 'bwlimit');
1516 foreach my $opt (keys %$mountpoints) {
1517 my $mp = $mountpoints->{$opt};
1518 my $volid = $mp->{volume
};
1521 if ($fullclone->{$opt}) {
1522 print "create full clone of mountpoint $opt ($volid)\n";
1523 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1524 my $target_storage = $storage // $source_storage;
1525 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1526 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1528 print "create linked clone of mount point $opt ($volid)\n";
1529 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1532 push @$newvollist, $newvolid;
1533 $mp->{volume
} = $newvolid;
1535 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1538 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1539 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1542 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1543 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1544 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1546 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1547 die "Failed to move config to node '$target' - rename failed: $!\n"
1548 if !rename($conffile, $newconffile);
1553 # Unlock the source config in any case:
1554 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1558 # Now cleanup the config & disks:
1561 sleep 1; # some storages like rbd need to wait before release volume - really?
1563 foreach my $volid (@$newvollist) {
1564 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1567 die "clone failed: $err";
1573 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1574 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1578 __PACKAGE__-
>register_method({
1579 name
=> 'resize_vm',
1580 path
=> '{vmid}/resize',
1584 description
=> "Resize a container mount point.",
1586 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1589 additionalProperties
=> 0,
1591 node
=> get_standard_option
('pve-node'),
1592 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1595 description
=> "The disk you want to resize.",
1596 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys()],
1600 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1601 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.",
1605 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1613 description
=> "the task ID.",
1618 my $rpcenv = PVE
::RPCEnvironment
::get
();
1620 my $authuser = $rpcenv->get_user();
1622 my $node = extract_param
($param, 'node');
1624 my $vmid = extract_param
($param, 'vmid');
1626 my $digest = extract_param
($param, 'digest');
1628 my $sizestr = extract_param
($param, 'size');
1629 my $ext = ($sizestr =~ s/^\+//);
1630 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1631 die "invalid size string" if !defined($newsize);
1633 die "no options specified\n" if !scalar(keys %$param);
1635 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1637 my $storage_cfg = cfs_read_file
("storage.cfg");
1641 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1642 PVE
::LXC
::Config-
>check_lock($conf);
1644 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1646 my $running = PVE
::LXC
::check_running
($vmid);
1648 my $disk = $param->{disk
};
1649 my $mp = PVE
::LXC
::Config-
>parse_volume($disk, $conf->{$disk});
1651 my $volid = $mp->{volume
};
1653 my (undef, undef, $owner, undef, undef, undef, $format) =
1654 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1656 die "can't resize mount point owned by another container ($owner)"
1659 die "can't resize volume: $disk if snapshot exists\n"
1660 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1662 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1664 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1666 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1668 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1670 die "Could not determine current size of volume '$volid'\n" if !defined($size);
1672 $newsize += $size if $ext;
1673 $newsize = int($newsize);
1675 die "unable to shrink disk size\n" if $newsize < $size;
1677 return if $size == $newsize;
1679 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1681 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1682 # we pass 0 here (parameter only makes sense for qemu)
1683 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1685 $mp->{size
} = $newsize;
1686 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1688 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1690 if ($format eq 'raw') {
1691 # we need to ensure that the volume is mapped, if not needed this is a NOP
1692 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1693 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1697 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1698 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1699 die "internal error: CT running but mount point not attached to a loop device"
1701 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1703 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1704 # to be visible to it in its namespace.
1705 # To not interfere with the rest of the system we unshare the current mount namespace,
1706 # mount over /tmp and then run resize2fs.
1708 # interestingly we don't need to e2fsck on mounted systems...
1709 my $quoted = PVE
::Tools
::shellquote
($path);
1710 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1712 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1714 warn "Failed to update the container's filesystem: $@\n" if $@;
1717 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1718 PVE
::Tools
::run_command
(['resize2fs', $path]);
1720 warn "Failed to update the container's filesystem: $@\n" if $@;
1722 # always un-map if not running, this is a NOP if not needed
1723 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1728 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1731 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1734 __PACKAGE__-
>register_method({
1735 name
=> 'move_volume',
1736 path
=> '{vmid}/move_volume',
1740 description
=> "Move a rootfs-/mp-volume to a different storage",
1742 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1743 "and 'Datastore.AllocateSpace' permissions on the storage.",
1746 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1747 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1751 additionalProperties
=> 0,
1753 node
=> get_standard_option
('pve-node'),
1754 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1757 enum
=> [ PVE
::LXC
::Config-
>valid_volume_keys() ],
1758 description
=> "Volume which will be moved.",
1760 storage
=> get_standard_option
('pve-storage-id', {
1761 description
=> "Target Storage.",
1762 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1766 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1772 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1777 description
=> "Override I/O bandwidth limit (in KiB/s).",
1781 default => 'clone limit from datacenter or storage config',
1791 my $rpcenv = PVE
::RPCEnvironment
::get
();
1793 my $authuser = $rpcenv->get_user();
1795 my $vmid = extract_param
($param, 'vmid');
1797 my $storage = extract_param
($param, 'storage');
1799 my $mpkey = extract_param
($param, 'volume');
1801 my $lockname = 'disk';
1803 my ($mpdata, $old_volid);
1805 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1806 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1807 PVE
::LXC
::Config-
>check_lock($conf);
1809 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1811 $mpdata = PVE
::LXC
::Config-
>parse_volume($mpkey, $conf->{$mpkey});
1812 $old_volid = $mpdata->{volume
};
1814 die "you can't move a volume with snapshots and delete the source\n"
1815 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1817 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1819 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1824 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1826 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1827 my $storage_cfg = PVE
::Storage
::config
();
1832 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1833 my $bwlimit = extract_param
($param, 'bwlimit');
1834 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1835 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$source_storage, $storage], $bwlimit);
1836 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf, undef, $movelimit);
1837 if (PVE
::LXC
::Config-
>is_template($conf)) {
1838 PVE
::Storage
::activate_volumes
($storage_cfg, [ $new_volid ]);
1839 my $template_volid = PVE
::Storage
::vdisk_create_base
($storage_cfg, $new_volid);
1840 $mpdata->{volume
} = $template_volid;
1842 $mpdata->{volume
} = $new_volid;
1845 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1846 my $digest = $conf->{digest
};
1847 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1848 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1850 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1852 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1854 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1858 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1859 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1865 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1866 if defined($new_volid);
1872 if ($param->{delete}) {
1874 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1875 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1879 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1880 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1881 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid);
1882 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1888 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1893 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1896 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1903 __PACKAGE__-
>register_method({
1904 name
=> 'vm_pending',
1905 path
=> '{vmid}/pending',
1908 description
=> 'Get container configuration, including pending changes.',
1910 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1913 additionalProperties
=> 0,
1915 node
=> get_standard_option
('pve-node'),
1916 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1925 description
=> 'Configuration option name.',
1929 description
=> 'Current value.',
1934 description
=> 'Pending value.',
1939 description
=> "Indicates a pending delete request if present and not 0.",
1951 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
1953 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
1955 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);