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 = PVE
::LXC
::Config-
>parse_volume($opt, $value);
1396 if ($mp->{type
} eq 'volume') {
1397 my $volid = $mp->{volume
};
1399 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1400 $sid = $storage if defined($storage);
1401 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1402 if (!$scfg->{shared
}) {
1404 warn "found non-shared volume: $volid\n" if $target;
1407 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1410 die "Cannot do full clones on a running container without snapshots\n"
1411 if $running && !defined($snapname);
1412 $fullclone->{$opt} = 1;
1414 # not full means clone instead of copy
1415 die "Linked clone feature for '$volid' is not available\n"
1416 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
1419 $mountpoints->{$opt} = $mp;
1420 push @$vollist, $volid;
1423 # TODO: allow bind mounts?
1424 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1426 } elsif ($opt =~ m/^net(\d+)$/) {
1427 # always change MAC! address
1428 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1429 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1430 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1431 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1433 # copy everything else
1434 $newconf->{$opt} = $value;
1437 die "can't clone CT to node '$target' (CT uses local storage)\n"
1438 if $target && !$sharedvm;
1440 # Replace the 'disk' lock with a 'create' lock.
1441 $newconf->{lock} = 'create';
1443 delete $newconf->{pending
};
1444 delete $newconf->{template
};
1445 if ($param->{hostname
}) {
1446 $newconf->{hostname
} = $param->{hostname
};
1449 if ($param->{description
}) {
1450 $newconf->{description
} = $param->{description
};
1453 # create empty/temp config - this fails if CT already exists on other node
1454 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1457 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1463 my $update_conf = sub {
1464 my ($key, $value) = @_;
1465 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1466 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1467 die "Lost 'create' config lock, aborting.\n"
1468 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1469 $conf->{$key} = $value;
1470 PVE
::LXC
::Config-
>write_config($newid, $conf);
1477 my $newvollist = [];
1479 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1480 die "unexpected state change\n" if $verify_running != $running;
1486 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1488 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1489 my $bwlimit = extract_param
($param, 'bwlimit');
1491 foreach my $opt (keys %$mountpoints) {
1492 my $mp = $mountpoints->{$opt};
1493 my $volid = $mp->{volume
};
1496 if ($fullclone->{$opt}) {
1497 print "create full clone of mountpoint $opt ($volid)\n";
1498 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1499 my $target_storage = $storage // $source_storage;
1500 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1501 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1503 print "create linked clone of mount point $opt ($volid)\n";
1504 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1507 push @$newvollist, $newvolid;
1508 $mp->{volume
} = $newvolid;
1510 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1513 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1514 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1517 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1518 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1519 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1521 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1522 die "Failed to move config to node '$target' - rename failed: $!\n"
1523 if !rename($conffile, $newconffile);
1528 # Unlock the source config in any case:
1529 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1533 # Now cleanup the config & disks:
1536 sleep 1; # some storages like rbd need to wait before release volume - really?
1538 foreach my $volid (@$newvollist) {
1539 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1542 die "clone failed: $err";
1548 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1549 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1553 __PACKAGE__-
>register_method({
1554 name
=> 'resize_vm',
1555 path
=> '{vmid}/resize',
1559 description
=> "Resize a container mount point.",
1561 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1564 additionalProperties
=> 0,
1566 node
=> get_standard_option
('pve-node'),
1567 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1570 description
=> "The disk you want to resize.",
1571 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys()],
1575 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1576 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.",
1580 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1588 description
=> "the task ID.",
1593 my $rpcenv = PVE
::RPCEnvironment
::get
();
1595 my $authuser = $rpcenv->get_user();
1597 my $node = extract_param
($param, 'node');
1599 my $vmid = extract_param
($param, 'vmid');
1601 my $digest = extract_param
($param, 'digest');
1603 my $sizestr = extract_param
($param, 'size');
1604 my $ext = ($sizestr =~ s/^\+//);
1605 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1606 die "invalid size string" if !defined($newsize);
1608 die "no options specified\n" if !scalar(keys %$param);
1610 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1612 my $storage_cfg = cfs_read_file
("storage.cfg");
1616 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1617 PVE
::LXC
::Config-
>check_lock($conf);
1619 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1621 my $running = PVE
::LXC
::check_running
($vmid);
1623 my $disk = $param->{disk
};
1624 my $mp = PVE
::LXC
::Config-
>parse_volume($disk, $conf->{$disk});
1626 my $volid = $mp->{volume
};
1628 my (undef, undef, $owner, undef, undef, undef, $format) =
1629 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1631 die "can't resize mount point owned by another container ($owner)"
1634 die "can't resize volume: $disk if snapshot exists\n"
1635 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1637 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1639 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1641 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1643 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1645 die "Could not determine current size of volume '$volid'\n" if !defined($size);
1647 $newsize += $size if $ext;
1648 $newsize = int($newsize);
1650 die "unable to shrink disk size\n" if $newsize < $size;
1652 return if $size == $newsize;
1654 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1656 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1657 # we pass 0 here (parameter only makes sense for qemu)
1658 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1660 $mp->{size
} = $newsize;
1661 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1663 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1665 if ($format eq 'raw') {
1666 # we need to ensure that the volume is mapped, if not needed this is a NOP
1667 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1668 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1672 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1673 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1674 die "internal error: CT running but mount point not attached to a loop device"
1676 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1678 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1679 # to be visible to it in its namespace.
1680 # To not interfere with the rest of the system we unshare the current mount namespace,
1681 # mount over /tmp and then run resize2fs.
1683 # interestingly we don't need to e2fsck on mounted systems...
1684 my $quoted = PVE
::Tools
::shellquote
($path);
1685 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1687 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1689 warn "Failed to update the container's filesystem: $@\n" if $@;
1692 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1693 PVE
::Tools
::run_command
(['resize2fs', $path]);
1695 warn "Failed to update the container's filesystem: $@\n" if $@;
1697 # always un-map if not running, this is a NOP if not needed
1698 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1703 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1706 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1709 __PACKAGE__-
>register_method({
1710 name
=> 'move_volume',
1711 path
=> '{vmid}/move_volume',
1715 description
=> "Move a rootfs-/mp-volume to a different storage",
1717 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1718 "and 'Datastore.AllocateSpace' permissions on the storage.",
1721 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1722 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1726 additionalProperties
=> 0,
1728 node
=> get_standard_option
('pve-node'),
1729 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1732 enum
=> [ PVE
::LXC
::Config-
>valid_volume_keys() ],
1733 description
=> "Volume which will be moved.",
1735 storage
=> get_standard_option
('pve-storage-id', {
1736 description
=> "Target Storage.",
1737 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1741 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1747 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1752 description
=> "Override I/O bandwidth limit (in KiB/s).",
1756 default => 'clone limit from datacenter or storage config',
1766 my $rpcenv = PVE
::RPCEnvironment
::get
();
1768 my $authuser = $rpcenv->get_user();
1770 my $vmid = extract_param
($param, 'vmid');
1772 my $storage = extract_param
($param, 'storage');
1774 my $mpkey = extract_param
($param, 'volume');
1776 my $lockname = 'disk';
1778 my ($mpdata, $old_volid);
1780 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1781 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1782 PVE
::LXC
::Config-
>check_lock($conf);
1784 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1786 PVE
::LXC
::Config-
>parse_volume($mpkey, $conf->{$mpkey});
1787 $old_volid = $mpdata->{volume
};
1789 die "you can't move a volume with snapshots and delete the source\n"
1790 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1792 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1794 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1799 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1801 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1802 my $storage_cfg = PVE
::Storage
::config
();
1807 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1808 my $bwlimit = extract_param
($param, 'bwlimit');
1809 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1810 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$source_storage, $storage], $bwlimit);
1811 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf, undef, $movelimit);
1812 if (PVE
::LXC
::Config-
>is_template($conf)) {
1813 PVE
::Storage
::activate_volumes
($storage_cfg, [ $new_volid ]);
1814 my $template_volid = PVE
::Storage
::vdisk_create_base
($storage_cfg, $new_volid);
1815 $mpdata->{volume
} = $template_volid;
1817 $mpdata->{volume
} = $new_volid;
1820 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1821 my $digest = $conf->{digest
};
1822 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1823 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1825 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1827 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1829 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1833 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1834 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1840 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1841 if defined($new_volid);
1847 if ($param->{delete}) {
1849 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1850 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1854 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1855 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1856 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid);
1857 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1863 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1868 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1871 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1878 __PACKAGE__-
>register_method({
1879 name
=> 'vm_pending',
1880 path
=> '{vmid}/pending',
1883 description
=> 'Get container configuration, including pending changes.',
1885 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1888 additionalProperties
=> 0,
1890 node
=> get_standard_option
('pve-node'),
1891 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1900 description
=> 'Configuration option name.',
1904 description
=> 'Current value.',
1909 description
=> 'Pending value.',
1914 description
=> "Indicates a pending delete request if present and not 0.",
1926 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
1928 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
1930 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);