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
=> "Remove vmid from backup cron jobs.",
635 my $rpcenv = PVE
::RPCEnvironment
::get
();
636 my $authuser = $rpcenv->get_user();
637 my $vmid = $param->{vmid
};
639 # test if container exists
640 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
641 my $storage_cfg = cfs_read_file
("storage.cfg");
642 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
644 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("ct:$vmid");
646 if (!$param->{purge
}) {
647 die "unable to remove CT $vmid - used in HA resources and purge parameter not set.\n"
650 # do not allow destroy if there are replication jobs without purge
651 my $repl_conf = PVE
::ReplicationConfig-
>new();
652 $repl_conf->check_for_existing_jobs($vmid);
655 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
657 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
660 # reload config after lock
661 $conf = PVE
::LXC
::Config-
>load_config($vmid);
662 PVE
::LXC
::Config-
>check_lock($conf);
664 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
666 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf, { lock => 'destroyed' });
668 PVE
::AccessControl
::remove_vm_access
($vmid);
669 PVE
::Firewall
::remove_vmfw_conf
($vmid);
670 if ($param->{purge
}) {
671 print "purging CT $vmid from related configurations..\n";
672 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
673 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
676 PVE
::HA
::Config
::delete_service_from_config
("ct:$vmid");
677 print "NOTE: removed CT $vmid from HA resource configuration.\n";
681 # only now remove the zombie config, else we can have reuse race
682 PVE
::LXC
::Config-
>destroy_config($vmid);
685 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
687 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
692 __PACKAGE__-
>register_method ({
694 path
=> '{vmid}/vncproxy',
698 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
700 description
=> "Creates a TCP VNC proxy connections.",
702 additionalProperties
=> 0,
704 node
=> get_standard_option
('pve-node'),
705 vmid
=> get_standard_option
('pve-vmid'),
709 description
=> "use websocket instead of standard VNC.",
713 description
=> "sets the width of the console in pixels.",
720 description
=> "sets the height of the console in pixels.",
728 additionalProperties
=> 0,
730 user
=> { type
=> 'string' },
731 ticket
=> { type
=> 'string' },
732 cert
=> { type
=> 'string' },
733 port
=> { type
=> 'integer' },
734 upid
=> { type
=> 'string' },
740 my $rpcenv = PVE
::RPCEnvironment
::get
();
742 my $authuser = $rpcenv->get_user();
744 my $vmid = $param->{vmid
};
745 my $node = $param->{node
};
747 my $authpath = "/vms/$vmid";
749 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
751 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
754 my ($remip, $family);
756 if ($node ne PVE
::INotify
::nodename
()) {
757 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
759 $family = PVE
::Tools
::get_host_address_family
($node);
762 my $port = PVE
::Tools
::next_vnc_port
($family);
764 # NOTE: vncterm VNC traffic is already TLS encrypted,
765 # so we select the fastest chipher here (or 'none'?)
766 my $remcmd = $remip ?
767 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
769 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
770 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
772 my $shcmd = [ '/usr/bin/dtach', '-A',
773 "/var/run/dtach/vzctlconsole$vmid",
774 '-r', 'winch', '-z', @$concmd];
779 syslog
('info', "starting lxc vnc proxy $upid\n");
783 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
784 '-timeout', $timeout, '-authpath', $authpath,
785 '-perm', 'VM.Console'];
787 if ($param->{width
}) {
788 push @$cmd, '-width', $param->{width
};
791 if ($param->{height
}) {
792 push @$cmd, '-height', $param->{height
};
795 if ($param->{websocket
}) {
796 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
797 push @$cmd, '-notls', '-listen', 'localhost';
800 push @$cmd, '-c', @$remcmd, @$shcmd;
802 run_command
($cmd, keeplocale
=> 1);
807 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
809 PVE
::Tools
::wait_for_vnc_port
($port);
820 __PACKAGE__-
>register_method ({
822 path
=> '{vmid}/termproxy',
826 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
828 description
=> "Creates a TCP proxy connection.",
830 additionalProperties
=> 0,
832 node
=> get_standard_option
('pve-node'),
833 vmid
=> get_standard_option
('pve-vmid'),
837 additionalProperties
=> 0,
839 user
=> { type
=> 'string' },
840 ticket
=> { type
=> 'string' },
841 port
=> { type
=> 'integer' },
842 upid
=> { type
=> 'string' },
848 my $rpcenv = PVE
::RPCEnvironment
::get
();
850 my $authuser = $rpcenv->get_user();
852 my $vmid = $param->{vmid
};
853 my $node = $param->{node
};
855 my $authpath = "/vms/$vmid";
857 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
859 my ($remip, $family);
861 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
862 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
864 $family = PVE
::Tools
::get_host_address_family
($node);
867 my $port = PVE
::Tools
::next_vnc_port
($family);
869 my $remcmd = $remip ?
870 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
872 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
873 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
875 my $shcmd = [ '/usr/bin/dtach', '-A',
876 "/var/run/dtach/vzctlconsole$vmid",
877 '-r', 'winch', '-z', @$concmd];
882 syslog
('info', "starting lxc termproxy $upid\n");
884 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
885 '--perm', 'VM.Console', '--'];
886 push @$cmd, @$remcmd, @$shcmd;
888 PVE
::Tools
::run_command
($cmd);
891 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
893 PVE
::Tools
::wait_for_vnc_port
($port);
903 __PACKAGE__-
>register_method({
904 name
=> 'vncwebsocket',
905 path
=> '{vmid}/vncwebsocket',
908 description
=> "You also need to pass a valid ticket (vncticket).",
909 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
911 description
=> "Opens a weksocket for VNC traffic.",
913 additionalProperties
=> 0,
915 node
=> get_standard_option
('pve-node'),
916 vmid
=> get_standard_option
('pve-vmid'),
918 description
=> "Ticket from previous call to vncproxy.",
923 description
=> "Port number returned by previous vncproxy call.",
933 port
=> { type
=> 'string' },
939 my $rpcenv = PVE
::RPCEnvironment
::get
();
941 my $authuser = $rpcenv->get_user();
943 my $authpath = "/vms/$param->{vmid}";
945 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
947 my $port = $param->{port
};
949 return { port
=> $port };
952 __PACKAGE__-
>register_method ({
953 name
=> 'spiceproxy',
954 path
=> '{vmid}/spiceproxy',
959 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
961 description
=> "Returns a SPICE configuration to connect to the CT.",
963 additionalProperties
=> 0,
965 node
=> get_standard_option
('pve-node'),
966 vmid
=> get_standard_option
('pve-vmid'),
967 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
970 returns
=> get_standard_option
('remote-viewer-config'),
974 my $vmid = $param->{vmid
};
975 my $node = $param->{node
};
976 my $proxy = $param->{proxy
};
978 my $authpath = "/vms/$vmid";
979 my $permissions = 'VM.Console';
981 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
983 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
985 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
987 my $shcmd = ['/usr/bin/dtach', '-A',
988 "/var/run/dtach/vzctlconsole$vmid",
989 '-r', 'winch', '-z', @$concmd];
991 my $title = "CT $vmid";
993 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
997 __PACKAGE__-
>register_method({
998 name
=> 'migrate_vm',
999 path
=> '{vmid}/migrate',
1003 description
=> "Migrate the container to another node. Creates a new migration task.",
1005 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1008 additionalProperties
=> 0,
1010 node
=> get_standard_option
('pve-node'),
1011 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1012 target
=> get_standard_option
('pve-node', {
1013 description
=> "Target node.",
1014 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1018 description
=> "Use online/live migration.",
1023 description
=> "Use restart migration",
1028 description
=> "Timeout in seconds for shutdown for restart migration",
1034 description
=> "Force migration despite local bind / device" .
1035 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1039 description
=> "Override I/O bandwidth limit (in KiB/s).",
1043 default => 'migrate limit from datacenter or storage config',
1049 description
=> "the task ID.",
1054 my $rpcenv = PVE
::RPCEnvironment
::get
();
1056 my $authuser = $rpcenv->get_user();
1058 my $target = extract_param
($param, 'target');
1060 my $localnode = PVE
::INotify
::nodename
();
1061 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1063 PVE
::Cluster
::check_cfs_quorum
();
1065 PVE
::Cluster
::check_node_exists
($target);
1067 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1069 my $vmid = extract_param
($param, 'vmid');
1072 PVE
::LXC
::Config-
>load_config($vmid);
1074 # try to detect errors early
1075 if (PVE
::LXC
::check_running
($vmid)) {
1076 die "can't migrate running container without --online or --restart\n"
1077 if !$param->{online
} && !$param->{restart
};
1080 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1085 my $service = "ct:$vmid";
1087 my $cmd = ['ha-manager', 'migrate', $service, $target];
1089 print "Requesting HA migration for CT $vmid to node $target\n";
1091 PVE
::Tools
::run_command
($cmd);
1096 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1101 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1105 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1108 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1112 __PACKAGE__-
>register_method({
1113 name
=> 'vm_feature',
1114 path
=> '{vmid}/feature',
1118 description
=> "Check if feature for virtual machine is available.",
1120 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1123 additionalProperties
=> 0,
1125 node
=> get_standard_option
('pve-node'),
1126 vmid
=> get_standard_option
('pve-vmid'),
1128 description
=> "Feature to check.",
1130 enum
=> [ 'snapshot', 'clone', 'copy' ],
1132 snapname
=> get_standard_option
('pve-snapshot-name', {
1140 hasFeature
=> { type
=> 'boolean' },
1143 #items => { type => 'string' },
1150 my $node = extract_param
($param, 'node');
1152 my $vmid = extract_param
($param, 'vmid');
1154 my $snapname = extract_param
($param, 'snapname');
1156 my $feature = extract_param
($param, 'feature');
1158 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1161 my $snap = $conf->{snapshots
}->{$snapname};
1162 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1165 my $storage_cfg = PVE
::Storage
::config
();
1166 #Maybe include later
1167 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1168 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1171 hasFeature
=> $hasFeature,
1172 #nodes => [ keys %$nodelist ],
1176 __PACKAGE__-
>register_method({
1178 path
=> '{vmid}/template',
1182 description
=> "Create a Template.",
1184 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1185 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1188 additionalProperties
=> 0,
1190 node
=> get_standard_option
('pve-node'),
1191 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1194 returns
=> { type
=> 'null'},
1198 my $rpcenv = PVE
::RPCEnvironment
::get
();
1200 my $authuser = $rpcenv->get_user();
1202 my $node = extract_param
($param, 'node');
1204 my $vmid = extract_param
($param, 'vmid');
1206 my $updatefn = sub {
1208 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1209 PVE
::LXC
::Config-
>check_lock($conf);
1211 die "unable to create template, because CT contains snapshots\n"
1212 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1214 die "you can't convert a template to a template\n"
1215 if PVE
::LXC
::Config-
>is_template($conf);
1217 die "you can't convert a CT to template if the CT is running\n"
1218 if PVE
::LXC
::check_running
($vmid);
1221 PVE
::LXC
::template_create
($vmid, $conf);
1223 $conf->{template
} = 1;
1225 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1226 # and remove lxc config
1227 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1230 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1233 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1238 __PACKAGE__-
>register_method({
1240 path
=> '{vmid}/clone',
1244 description
=> "Create a container clone/copy",
1246 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1247 "and 'VM.Allocate' permissions " .
1248 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1249 "'Datastore.AllocateSpace' on any used storage.",
1252 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1254 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1255 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1260 additionalProperties
=> 0,
1262 node
=> get_standard_option
('pve-node'),
1263 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1264 newid
=> get_standard_option
('pve-vmid', {
1265 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1266 description
=> 'VMID for the clone.' }),
1269 type
=> 'string', format
=> 'dns-name',
1270 description
=> "Set a hostname for the new CT.",
1275 description
=> "Description for the new CT.",
1279 type
=> 'string', format
=> 'pve-poolid',
1280 description
=> "Add the new CT to the specified pool.",
1282 snapname
=> get_standard_option
('pve-snapshot-name', {
1285 storage
=> get_standard_option
('pve-storage-id', {
1286 description
=> "Target storage for full clone.",
1292 description
=> "Create a full copy of all disks. This is always done when " .
1293 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1295 target
=> get_standard_option
('pve-node', {
1296 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1300 description
=> "Override I/O bandwidth limit (in KiB/s).",
1304 default => 'clone limit from datacenter or storage config',
1314 my $rpcenv = PVE
::RPCEnvironment
::get
();
1316 my $authuser = $rpcenv->get_user();
1318 my $node = extract_param
($param, 'node');
1320 my $vmid = extract_param
($param, 'vmid');
1322 my $newid = extract_param
($param, 'newid');
1324 my $pool = extract_param
($param, 'pool');
1326 if (defined($pool)) {
1327 $rpcenv->check_pool_exist($pool);
1330 my $snapname = extract_param
($param, 'snapname');
1332 my $storage = extract_param
($param, 'storage');
1334 my $target = extract_param
($param, 'target');
1336 my $localnode = PVE
::INotify
::nodename
();
1338 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1340 PVE
::Cluster
::check_node_exists
($target) if $target;
1342 my $storecfg = PVE
::Storage
::config
();
1345 # check if storage is enabled on local node
1346 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1348 # check if storage is available on target node
1349 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1350 # clone only works if target storage is shared
1351 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1352 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1356 PVE
::Cluster
::check_cfs_quorum
();
1360 my $mountpoints = {};
1365 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1366 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1368 $running = PVE
::LXC
::check_running
($vmid) || 0;
1370 my $full = extract_param
($param, 'full');
1371 if (!defined($full)) {
1372 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1374 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1377 die "snapshot '$snapname' does not exist\n"
1378 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1381 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1383 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1384 die "unable to create CT $newid: config file already exists\n"
1388 foreach my $opt (keys %$src_conf) {
1389 next if $opt =~ m/^unused\d+$/;
1391 my $value = $src_conf->{$opt};
1393 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1394 my $mp = $opt eq 'rootfs' ?
1395 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1396 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1398 if ($mp->{type
} eq 'volume') {
1399 my $volid = $mp->{volume
};
1401 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1402 $sid = $storage if defined($storage);
1403 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1404 if (!$scfg->{shared
}) {
1406 warn "found non-shared volume: $volid\n" if $target;
1409 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1412 die "Cannot do full clones on a running container without snapshots\n"
1413 if $running && !defined($snapname);
1414 $fullclone->{$opt} = 1;
1416 # not full means clone instead of copy
1417 die "Linked clone feature for '$volid' is not available\n"
1418 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
1421 $mountpoints->{$opt} = $mp;
1422 push @$vollist, $volid;
1425 # TODO: allow bind mounts?
1426 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1428 } elsif ($opt =~ m/^net(\d+)$/) {
1429 # always change MAC! address
1430 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1431 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1432 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1433 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1435 # copy everything else
1436 $newconf->{$opt} = $value;
1439 die "can't clone CT to node '$target' (CT uses local storage)\n"
1440 if $target && !$sharedvm;
1442 # Replace the 'disk' lock with a 'create' lock.
1443 $newconf->{lock} = 'create';
1445 delete $newconf->{pending
};
1446 delete $newconf->{template
};
1447 if ($param->{hostname
}) {
1448 $newconf->{hostname
} = $param->{hostname
};
1451 if ($param->{description
}) {
1452 $newconf->{description
} = $param->{description
};
1455 # create empty/temp config - this fails if CT already exists on other node
1456 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1459 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1465 my $update_conf = sub {
1466 my ($key, $value) = @_;
1467 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1468 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1469 die "Lost 'create' config lock, aborting.\n"
1470 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1471 $conf->{$key} = $value;
1472 PVE
::LXC
::Config-
>write_config($newid, $conf);
1479 my $newvollist = [];
1481 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1482 die "unexpected state change\n" if $verify_running != $running;
1488 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1490 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1491 my $bwlimit = extract_param
($param, 'bwlimit');
1493 foreach my $opt (keys %$mountpoints) {
1494 my $mp = $mountpoints->{$opt};
1495 my $volid = $mp->{volume
};
1498 if ($fullclone->{$opt}) {
1499 print "create full clone of mountpoint $opt ($volid)\n";
1500 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1501 my $target_storage = $storage // $source_storage;
1502 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1503 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1505 print "create linked clone of mount point $opt ($volid)\n";
1506 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1509 push @$newvollist, $newvolid;
1510 $mp->{volume
} = $newvolid;
1512 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1515 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1516 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1519 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1520 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1521 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1523 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1524 die "Failed to move config to node '$target' - rename failed: $!\n"
1525 if !rename($conffile, $newconffile);
1530 # Unlock the source config in any case:
1531 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1535 # Now cleanup the config & disks:
1538 sleep 1; # some storages like rbd need to wait before release volume - really?
1540 foreach my $volid (@$newvollist) {
1541 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1544 die "clone failed: $err";
1550 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1551 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1555 __PACKAGE__-
>register_method({
1556 name
=> 'resize_vm',
1557 path
=> '{vmid}/resize',
1561 description
=> "Resize a container mount point.",
1563 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1566 additionalProperties
=> 0,
1568 node
=> get_standard_option
('pve-node'),
1569 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1572 description
=> "The disk you want to resize.",
1573 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys()],
1577 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1578 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.",
1582 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1590 description
=> "the task ID.",
1595 my $rpcenv = PVE
::RPCEnvironment
::get
();
1597 my $authuser = $rpcenv->get_user();
1599 my $node = extract_param
($param, 'node');
1601 my $vmid = extract_param
($param, 'vmid');
1603 my $digest = extract_param
($param, 'digest');
1605 my $sizestr = extract_param
($param, 'size');
1606 my $ext = ($sizestr =~ s/^\+//);
1607 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1608 die "invalid size string" if !defined($newsize);
1610 die "no options specified\n" if !scalar(keys %$param);
1612 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1614 my $storage_cfg = cfs_read_file
("storage.cfg");
1618 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1619 PVE
::LXC
::Config-
>check_lock($conf);
1621 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1623 my $running = PVE
::LXC
::check_running
($vmid);
1625 my $disk = $param->{disk
};
1626 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1627 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1629 my $volid = $mp->{volume
};
1631 my (undef, undef, $owner, undef, undef, undef, $format) =
1632 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1634 die "can't resize mount point owned by another container ($owner)"
1637 die "can't resize volume: $disk if snapshot exists\n"
1638 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1640 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1642 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1644 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1646 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1648 die "Could not determine current size of volume '$volid'\n" if !defined($size);
1650 $newsize += $size if $ext;
1651 $newsize = int($newsize);
1653 die "unable to shrink disk size\n" if $newsize < $size;
1655 return if $size == $newsize;
1657 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1659 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1660 # we pass 0 here (parameter only makes sense for qemu)
1661 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1663 $mp->{size
} = $newsize;
1664 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1666 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1668 if ($format eq 'raw') {
1669 # we need to ensure that the volume is mapped, if not needed this is a NOP
1670 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1671 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1675 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1676 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1677 die "internal error: CT running but mount point not attached to a loop device"
1679 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1681 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1682 # to be visible to it in its namespace.
1683 # To not interfere with the rest of the system we unshare the current mount namespace,
1684 # mount over /tmp and then run resize2fs.
1686 # interestingly we don't need to e2fsck on mounted systems...
1687 my $quoted = PVE
::Tools
::shellquote
($path);
1688 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1690 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1692 warn "Failed to update the container's filesystem: $@\n" if $@;
1695 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1696 PVE
::Tools
::run_command
(['resize2fs', $path]);
1698 warn "Failed to update the container's filesystem: $@\n" if $@;
1700 # always un-map if not running, this is a NOP if not needed
1701 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1706 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1709 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1712 __PACKAGE__-
>register_method({
1713 name
=> 'move_volume',
1714 path
=> '{vmid}/move_volume',
1718 description
=> "Move a rootfs-/mp-volume to a different storage",
1720 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1721 "and 'Datastore.AllocateSpace' permissions on the storage.",
1724 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1725 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1729 additionalProperties
=> 0,
1731 node
=> get_standard_option
('pve-node'),
1732 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1735 enum
=> [ PVE
::LXC
::Config-
>valid_volume_keys() ],
1736 description
=> "Volume which will be moved.",
1738 storage
=> get_standard_option
('pve-storage-id', {
1739 description
=> "Target Storage.",
1740 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1744 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1750 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1755 description
=> "Override I/O bandwidth limit (in KiB/s).",
1759 default => 'clone limit from datacenter or storage config',
1769 my $rpcenv = PVE
::RPCEnvironment
::get
();
1771 my $authuser = $rpcenv->get_user();
1773 my $vmid = extract_param
($param, 'vmid');
1775 my $storage = extract_param
($param, 'storage');
1777 my $mpkey = extract_param
($param, 'volume');
1779 my $lockname = 'disk';
1781 my ($mpdata, $old_volid);
1783 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1784 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1785 PVE
::LXC
::Config-
>check_lock($conf);
1787 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1789 if ($mpkey eq 'rootfs') {
1790 $mpdata = PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$mpkey});
1791 } elsif ($mpkey =~ m/mp\d+/) {
1792 $mpdata = PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$mpkey});
1794 die "Can't parse $mpkey\n";
1796 $old_volid = $mpdata->{volume
};
1798 die "you can't move a volume with snapshots and delete the source\n"
1799 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1801 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1803 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1808 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1810 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1811 my $storage_cfg = PVE
::Storage
::config
();
1816 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1817 my $bwlimit = extract_param
($param, 'bwlimit');
1818 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1819 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$source_storage, $storage], $bwlimit);
1820 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf, undef, $movelimit);
1821 if (PVE
::LXC
::Config-
>is_template($conf)) {
1822 PVE
::Storage
::activate_volumes
($storage_cfg, [ $new_volid ]);
1823 my $template_volid = PVE
::Storage
::vdisk_create_base
($storage_cfg, $new_volid);
1824 $mpdata->{volume
} = $template_volid;
1826 $mpdata->{volume
} = $new_volid;
1829 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1830 my $digest = $conf->{digest
};
1831 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1832 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1834 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1836 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1838 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1842 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1843 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1849 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1850 if defined($new_volid);
1856 if ($param->{delete}) {
1858 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1859 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1863 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1864 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1865 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid);
1866 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1872 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1877 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1880 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1887 __PACKAGE__-
>register_method({
1888 name
=> 'vm_pending',
1889 path
=> '{vmid}/pending',
1892 description
=> 'Get container configuration, including pending changes.',
1894 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1897 additionalProperties
=> 0,
1899 node
=> get_standard_option
('pve-node'),
1900 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1909 description
=> 'Configuration option name.',
1913 description
=> 'Current value.',
1918 description
=> 'Pending value.',
1923 description
=> "Indicates a pending delete request if present and not 0.",
1935 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
1937 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
1939 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);