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 ($archive ne '-') {
357 print "recovering backed-up configuration from '$archive'\n";
358 ($orig_conf, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
359 $was_template = delete $orig_conf->{template
};
360 # When we're root call 'restore_configuration' with restricted=0,
361 # causing it to restore the raw lxc entries, among which there may be
362 # 'lxc.idmap' entries. We need to make sure that the extracted contents
363 # of the container match up with the restored configuration afterwards:
364 $conf->{lxc
} = $orig_conf->{lxc
} if $is_root;
366 $conf->{unprivileged
} = $orig_conf->{unprivileged
}
367 if !defined($unprivileged) && defined($orig_conf->{unprivileged
});
370 if ($storage_only_mode) {
372 if (!defined($orig_mp_param)) {
373 print "recovering backed-up configuration from '$archive'\n";
374 (undef, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
376 $mp_param = $orig_mp_param;
377 die "rootfs configuration could not be recovered, please check and specify manually!\n"
378 if !defined($mp_param->{rootfs
});
379 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
380 my ($ms, $mountpoint) = @_;
381 my $type = $mountpoint->{type
};
382 if ($type eq 'volume') {
383 die "unable to detect disk size - please specify $ms (size)\n"
384 if !defined($mountpoint->{size
});
385 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
386 delete $mountpoint->{size
};
387 $mountpoint->{volume
} = "$storage:$disksize";
388 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
390 my $type = $mountpoint->{type
};
391 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
392 if ($ms eq 'rootfs');
393 die "restoring '$ms' to $type mount is only possible for root\n"
396 if ($mountpoint->{backup
}) {
397 warn "WARNING - unsupported configuration!\n";
398 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
399 warn "mount point configuration will be restored after archive extraction!\n";
400 warn "contained files will be restored to wrong directory!\n";
402 delete $mp_param->{$ms}; # actually delay bind/dev mps
403 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
407 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
411 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
413 # we always have the 'create' lock so check for more than 1 entry
414 if (scalar(keys %$old_conf) > 1) {
415 # destroy old container volumes
416 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, { lock => 'create' });
420 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
421 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
422 print "restoring '$archive' now..\n"
423 if $restore && $archive ne '-';
424 PVE
::LXC
::Create
::restore_archive
($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
427 print "merging backed-up and given configuration..\n";
428 PVE
::LXC
::Create
::restore_configuration
($vmid, $storage_cfg, $archive, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
429 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
430 $lxc_setup->template_fixup($conf);
432 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
433 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
434 $lxc_setup->post_create_hook($password, $ssh_keys);
438 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
439 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
442 $conf->{hostname
} ||= "CT$vmid";
443 $conf->{memory
} ||= 512;
444 $conf->{swap
} //= 512;
445 foreach my $mp (keys %$delayed_mp_param) {
446 $conf->{$mp} = $delayed_mp_param->{$mp};
448 # If the template flag was set, we try to convert again to template after restore
450 print STDERR
"Convert restored container to template...\n";
451 PVE
::LXC
::template_create
($vmid, $conf);
452 $conf->{template
} = 1;
454 PVE
::LXC
::Config-
>write_config($vmid, $conf);
457 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
458 eval { PVE
::LXC
::Config-
>destroy_config($vmid) };
462 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
464 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
465 if $start_after_create;
468 my $workername = $restore ?
'vzrestore' : 'vzcreate';
469 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
471 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
474 __PACKAGE__-
>register_method({
479 description
=> "Directory index",
484 additionalProperties
=> 0,
486 node
=> get_standard_option
('pve-node'),
487 vmid
=> get_standard_option
('pve-vmid'),
495 subdir
=> { type
=> 'string' },
498 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
504 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
507 { subdir
=> 'config' },
508 { subdir
=> 'pending' },
509 { subdir
=> 'status' },
510 { subdir
=> 'vncproxy' },
511 { subdir
=> 'termproxy' },
512 { subdir
=> 'vncwebsocket' },
513 { subdir
=> 'spiceproxy' },
514 { subdir
=> 'migrate' },
515 { subdir
=> 'clone' },
516 # { subdir => 'initlog' },
518 { subdir
=> 'rrddata' },
519 { subdir
=> 'firewall' },
520 { subdir
=> 'snapshot' },
521 { subdir
=> 'resize' },
528 __PACKAGE__-
>register_method({
530 path
=> '{vmid}/rrd',
532 protected
=> 1, # fixme: can we avoid that?
534 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
536 description
=> "Read VM RRD statistics (returns PNG)",
538 additionalProperties
=> 0,
540 node
=> get_standard_option
('pve-node'),
541 vmid
=> get_standard_option
('pve-vmid'),
543 description
=> "Specify the time frame you are interested in.",
545 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
548 description
=> "The list of datasources you want to display.",
549 type
=> 'string', format
=> 'pve-configid-list',
552 description
=> "The RRD consolidation function",
554 enum
=> [ 'AVERAGE', 'MAX' ],
562 filename
=> { type
=> 'string' },
568 return PVE
::RRD
::create_rrd_graph
(
569 "pve2-vm/$param->{vmid}", $param->{timeframe
},
570 $param->{ds
}, $param->{cf
});
574 __PACKAGE__-
>register_method({
576 path
=> '{vmid}/rrddata',
578 protected
=> 1, # fixme: can we avoid that?
580 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
582 description
=> "Read VM RRD statistics",
584 additionalProperties
=> 0,
586 node
=> get_standard_option
('pve-node'),
587 vmid
=> get_standard_option
('pve-vmid'),
589 description
=> "Specify the time frame you are interested in.",
591 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
594 description
=> "The RRD consolidation function",
596 enum
=> [ 'AVERAGE', 'MAX' ],
611 return PVE
::RRD
::create_rrd_data
(
612 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
615 __PACKAGE__-
>register_method({
616 name
=> 'destroy_vm',
621 description
=> "Destroy the container (also delete all uses files).",
623 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
626 additionalProperties
=> 0,
628 node
=> get_standard_option
('pve-node'),
629 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
632 description
=> "Force destroy, even if running.",
638 description
=> "Remove container from all related configurations."
639 ." For example, backup jobs, replication jobs or HA."
640 ." Related ACLs and Firewall entries will *always* be removed.",
644 'destroy-unreferenced-disks' => {
646 description
=> "If set, destroy additionally all disks with the VMID from all"
647 ." enabled storages which are not referenced in the config.",
658 my $rpcenv = PVE
::RPCEnvironment
::get
();
659 my $authuser = $rpcenv->get_user();
660 my $vmid = $param->{vmid
};
662 # test if container exists
664 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
665 my $early_checks = sub {
667 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
668 PVE
::LXC
::Config-
>check_lock($conf);
670 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("ct:$vmid");
672 if (!$param->{purge
}) {
673 die "unable to remove CT $vmid - used in HA resources and purge parameter not set.\n"
676 # do not allow destroy if there are replication jobs without purge
677 my $repl_conf = PVE
::ReplicationConfig-
>new();
678 $repl_conf->check_for_existing_jobs($vmid);
684 $early_checks->($conf);
686 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
687 die $running_error_msg if !$param->{force
} && PVE
::LXC
::check_running
($vmid); # check early
690 # reload config after lock
691 $conf = PVE
::LXC
::Config-
>load_config($vmid);
692 my $ha_managed = $early_checks->($conf);
694 if (PVE
::LXC
::check_running
($vmid)) {
695 die $running_error_msg if !$param->{force
};
696 warn "forced to stop CT $vmid before destroying!\n";
698 PVE
::LXC
::vm_stop
($vmid, 1);
700 run_command
(['ha-manager', 'crm-command', 'stop', "ct:$vmid", '120']);
704 my $storage_cfg = cfs_read_file
("storage.cfg");
705 PVE
::LXC
::destroy_lxc_container
(
709 { lock => 'destroyed' },
710 $param->{'destroy-unreferenced-disks'},
713 PVE
::AccessControl
::remove_vm_access
($vmid);
714 PVE
::Firewall
::remove_vmfw_conf
($vmid);
715 if ($param->{purge
}) {
716 print "purging CT $vmid from related configurations..\n";
717 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
718 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
721 PVE
::HA
::Config
::delete_service_from_config
("ct:$vmid");
722 print "NOTE: removed CT $vmid from HA resource configuration.\n";
726 # only now remove the zombie config, else we can have reuse race
727 PVE
::LXC
::Config-
>destroy_config($vmid);
730 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
732 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
737 __PACKAGE__-
>register_method ({
739 path
=> '{vmid}/vncproxy',
743 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
745 description
=> "Creates a TCP VNC proxy connections.",
747 additionalProperties
=> 0,
749 node
=> get_standard_option
('pve-node'),
750 vmid
=> get_standard_option
('pve-vmid'),
754 description
=> "use websocket instead of standard VNC.",
758 description
=> "sets the width of the console in pixels.",
765 description
=> "sets the height of the console in pixels.",
773 additionalProperties
=> 0,
775 user
=> { type
=> 'string' },
776 ticket
=> { type
=> 'string' },
777 cert
=> { type
=> 'string' },
778 port
=> { type
=> 'integer' },
779 upid
=> { type
=> 'string' },
785 my $rpcenv = PVE
::RPCEnvironment
::get
();
787 my $authuser = $rpcenv->get_user();
789 my $vmid = $param->{vmid
};
790 my $node = $param->{node
};
792 my $authpath = "/vms/$vmid";
794 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
796 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
799 my ($remip, $family);
801 if ($node ne PVE
::INotify
::nodename
()) {
802 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
804 $family = PVE
::Tools
::get_host_address_family
($node);
807 my $port = PVE
::Tools
::next_vnc_port
($family);
809 # NOTE: vncterm VNC traffic is already TLS encrypted,
810 # so we select the fastest chipher here (or 'none'?)
811 my $remcmd = $remip ?
812 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
814 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
815 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
817 my $shcmd = [ '/usr/bin/dtach', '-A',
818 "/var/run/dtach/vzctlconsole$vmid",
819 '-r', 'winch', '-z', @$concmd];
824 syslog
('info', "starting lxc vnc proxy $upid\n");
828 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
829 '-timeout', $timeout, '-authpath', $authpath,
830 '-perm', 'VM.Console'];
832 if ($param->{width
}) {
833 push @$cmd, '-width', $param->{width
};
836 if ($param->{height
}) {
837 push @$cmd, '-height', $param->{height
};
840 if ($param->{websocket
}) {
841 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
842 push @$cmd, '-notls', '-listen', 'localhost';
845 push @$cmd, '-c', @$remcmd, @$shcmd;
847 run_command
($cmd, keeplocale
=> 1);
852 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
854 PVE
::Tools
::wait_for_vnc_port
($port);
865 __PACKAGE__-
>register_method ({
867 path
=> '{vmid}/termproxy',
871 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
873 description
=> "Creates a TCP proxy connection.",
875 additionalProperties
=> 0,
877 node
=> get_standard_option
('pve-node'),
878 vmid
=> get_standard_option
('pve-vmid'),
882 additionalProperties
=> 0,
884 user
=> { type
=> 'string' },
885 ticket
=> { type
=> 'string' },
886 port
=> { type
=> 'integer' },
887 upid
=> { type
=> 'string' },
893 my $rpcenv = PVE
::RPCEnvironment
::get
();
895 my $authuser = $rpcenv->get_user();
897 my $vmid = $param->{vmid
};
898 my $node = $param->{node
};
900 my $authpath = "/vms/$vmid";
902 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
904 my ($remip, $family);
906 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
907 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
909 $family = PVE
::Tools
::get_host_address_family
($node);
912 my $port = PVE
::Tools
::next_vnc_port
($family);
914 my $remcmd = $remip ?
915 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
917 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
918 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
920 my $shcmd = [ '/usr/bin/dtach', '-A',
921 "/var/run/dtach/vzctlconsole$vmid",
922 '-r', 'winch', '-z', @$concmd];
927 syslog
('info', "starting lxc termproxy $upid\n");
929 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
930 '--perm', 'VM.Console', '--'];
931 push @$cmd, @$remcmd, @$shcmd;
933 PVE
::Tools
::run_command
($cmd);
936 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
938 PVE
::Tools
::wait_for_vnc_port
($port);
948 __PACKAGE__-
>register_method({
949 name
=> 'vncwebsocket',
950 path
=> '{vmid}/vncwebsocket',
953 description
=> "You also need to pass a valid ticket (vncticket).",
954 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
956 description
=> "Opens a weksocket for VNC traffic.",
958 additionalProperties
=> 0,
960 node
=> get_standard_option
('pve-node'),
961 vmid
=> get_standard_option
('pve-vmid'),
963 description
=> "Ticket from previous call to vncproxy.",
968 description
=> "Port number returned by previous vncproxy call.",
978 port
=> { type
=> 'string' },
984 my $rpcenv = PVE
::RPCEnvironment
::get
();
986 my $authuser = $rpcenv->get_user();
988 my $authpath = "/vms/$param->{vmid}";
990 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
992 my $port = $param->{port
};
994 return { port
=> $port };
997 __PACKAGE__-
>register_method ({
998 name
=> 'spiceproxy',
999 path
=> '{vmid}/spiceproxy',
1004 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1006 description
=> "Returns a SPICE configuration to connect to the CT.",
1008 additionalProperties
=> 0,
1010 node
=> get_standard_option
('pve-node'),
1011 vmid
=> get_standard_option
('pve-vmid'),
1012 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1015 returns
=> get_standard_option
('remote-viewer-config'),
1019 my $vmid = $param->{vmid
};
1020 my $node = $param->{node
};
1021 my $proxy = $param->{proxy
};
1023 my $authpath = "/vms/$vmid";
1024 my $permissions = 'VM.Console';
1026 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1028 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
1030 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
1032 my $shcmd = ['/usr/bin/dtach', '-A',
1033 "/var/run/dtach/vzctlconsole$vmid",
1034 '-r', 'winch', '-z', @$concmd];
1036 my $title = "CT $vmid";
1038 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1042 __PACKAGE__-
>register_method({
1043 name
=> 'migrate_vm',
1044 path
=> '{vmid}/migrate',
1048 description
=> "Migrate the container to another node. Creates a new migration task.",
1050 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1053 additionalProperties
=> 0,
1055 node
=> get_standard_option
('pve-node'),
1056 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1057 target
=> get_standard_option
('pve-node', {
1058 description
=> "Target node.",
1059 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1063 description
=> "Use online/live migration.",
1068 description
=> "Use restart migration",
1073 description
=> "Timeout in seconds for shutdown for restart migration",
1079 description
=> "Force migration despite local bind / device" .
1080 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
1084 description
=> "Override I/O bandwidth limit (in KiB/s).",
1088 default => 'migrate limit from datacenter or storage config',
1094 description
=> "the task ID.",
1099 my $rpcenv = PVE
::RPCEnvironment
::get
();
1101 my $authuser = $rpcenv->get_user();
1103 my $target = extract_param
($param, 'target');
1105 my $localnode = PVE
::INotify
::nodename
();
1106 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1108 PVE
::Cluster
::check_cfs_quorum
();
1110 PVE
::Cluster
::check_node_exists
($target);
1112 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1114 my $vmid = extract_param
($param, 'vmid');
1117 PVE
::LXC
::Config-
>load_config($vmid);
1119 # try to detect errors early
1120 if (PVE
::LXC
::check_running
($vmid)) {
1121 die "can't migrate running container without --online or --restart\n"
1122 if !$param->{online
} && !$param->{restart
};
1125 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1130 my $service = "ct:$vmid";
1132 my $cmd = ['ha-manager', 'migrate', $service, $target];
1134 print "Requesting HA migration for CT $vmid to node $target\n";
1136 PVE
::Tools
::run_command
($cmd);
1141 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1146 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1150 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1153 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1157 __PACKAGE__-
>register_method({
1158 name
=> 'vm_feature',
1159 path
=> '{vmid}/feature',
1163 description
=> "Check if feature for virtual machine is available.",
1165 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1168 additionalProperties
=> 0,
1170 node
=> get_standard_option
('pve-node'),
1171 vmid
=> get_standard_option
('pve-vmid'),
1173 description
=> "Feature to check.",
1175 enum
=> [ 'snapshot', 'clone', 'copy' ],
1177 snapname
=> get_standard_option
('pve-snapshot-name', {
1185 hasFeature
=> { type
=> 'boolean' },
1188 #items => { type => 'string' },
1195 my $node = extract_param
($param, 'node');
1197 my $vmid = extract_param
($param, 'vmid');
1199 my $snapname = extract_param
($param, 'snapname');
1201 my $feature = extract_param
($param, 'feature');
1203 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1206 my $snap = $conf->{snapshots
}->{$snapname};
1207 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1210 my $storage_cfg = PVE
::Storage
::config
();
1211 #Maybe include later
1212 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1213 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1216 hasFeature
=> $hasFeature,
1217 #nodes => [ keys %$nodelist ],
1221 __PACKAGE__-
>register_method({
1223 path
=> '{vmid}/template',
1227 description
=> "Create a Template.",
1229 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1230 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1233 additionalProperties
=> 0,
1235 node
=> get_standard_option
('pve-node'),
1236 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1239 returns
=> { type
=> 'null'},
1243 my $rpcenv = PVE
::RPCEnvironment
::get
();
1245 my $authuser = $rpcenv->get_user();
1247 my $node = extract_param
($param, 'node');
1249 my $vmid = extract_param
($param, 'vmid');
1251 my $updatefn = sub {
1253 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1254 PVE
::LXC
::Config-
>check_lock($conf);
1256 die "unable to create template, because CT contains snapshots\n"
1257 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1259 die "you can't convert a template to a template\n"
1260 if PVE
::LXC
::Config-
>is_template($conf);
1262 die "you can't convert a CT to template if the CT is running\n"
1263 if PVE
::LXC
::check_running
($vmid);
1266 PVE
::LXC
::template_create
($vmid, $conf);
1268 $conf->{template
} = 1;
1270 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1271 # and remove lxc config
1272 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1275 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1278 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1283 __PACKAGE__-
>register_method({
1285 path
=> '{vmid}/clone',
1289 description
=> "Create a container clone/copy",
1291 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1292 "and 'VM.Allocate' permissions " .
1293 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1294 "'Datastore.AllocateSpace' on any used storage.",
1297 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1299 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1300 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1305 additionalProperties
=> 0,
1307 node
=> get_standard_option
('pve-node'),
1308 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1309 newid
=> get_standard_option
('pve-vmid', {
1310 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1311 description
=> 'VMID for the clone.' }),
1314 type
=> 'string', format
=> 'dns-name',
1315 description
=> "Set a hostname for the new CT.",
1320 description
=> "Description for the new CT.",
1324 type
=> 'string', format
=> 'pve-poolid',
1325 description
=> "Add the new CT to the specified pool.",
1327 snapname
=> get_standard_option
('pve-snapshot-name', {
1330 storage
=> get_standard_option
('pve-storage-id', {
1331 description
=> "Target storage for full clone.",
1337 description
=> "Create a full copy of all disks. This is always done when " .
1338 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1340 target
=> get_standard_option
('pve-node', {
1341 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1345 description
=> "Override I/O bandwidth limit (in KiB/s).",
1349 default => 'clone limit from datacenter or storage config',
1359 my $rpcenv = PVE
::RPCEnvironment
::get
();
1361 my $authuser = $rpcenv->get_user();
1363 my $node = extract_param
($param, 'node');
1365 my $vmid = extract_param
($param, 'vmid');
1367 my $newid = extract_param
($param, 'newid');
1369 my $pool = extract_param
($param, 'pool');
1371 if (defined($pool)) {
1372 $rpcenv->check_pool_exist($pool);
1375 my $snapname = extract_param
($param, 'snapname');
1377 my $storage = extract_param
($param, 'storage');
1379 my $target = extract_param
($param, 'target');
1381 my $localnode = PVE
::INotify
::nodename
();
1383 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1385 PVE
::Cluster
::check_node_exists
($target) if $target;
1387 my $storecfg = PVE
::Storage
::config
();
1390 # check if storage is enabled on local node
1391 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1393 # check if storage is available on target node
1394 PVE
::Storage
::storage_check_node
($storecfg, $storage, $target);
1395 # clone only works if target storage is shared
1396 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1397 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1401 PVE
::Cluster
::check_cfs_quorum
();
1405 my $mountpoints = {};
1410 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1411 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1413 $running = PVE
::LXC
::check_running
($vmid) || 0;
1415 my $full = extract_param
($param, 'full');
1416 if (!defined($full)) {
1417 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1419 die "parameter 'storage' not allowed for linked clones\n" if defined($storage) && !$full;
1422 die "snapshot '$snapname' does not exist\n"
1423 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1426 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1428 $conffile = PVE
::LXC
::Config-
>config_file($newid);
1429 die "unable to create CT $newid: config file already exists\n"
1433 foreach my $opt (keys %$src_conf) {
1434 next if $opt =~ m/^unused\d+$/;
1436 my $value = $src_conf->{$opt};
1438 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1439 my $mp = PVE
::LXC
::Config-
>parse_volume($opt, $value);
1441 if ($mp->{type
} eq 'volume') {
1442 my $volid = $mp->{volume
};
1444 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1445 $sid = $storage if defined($storage);
1446 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1447 if (!$scfg->{shared
}) {
1449 warn "found non-shared volume: $volid\n" if $target;
1452 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1455 die "Cannot do full clones on a running container without snapshots\n"
1456 if $running && !defined($snapname);
1457 $fullclone->{$opt} = 1;
1459 # not full means clone instead of copy
1460 die "Linked clone feature for '$volid' is not available\n"
1461 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
1464 $mountpoints->{$opt} = $mp;
1465 push @$vollist, $volid;
1468 # TODO: allow bind mounts?
1469 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1471 } elsif ($opt =~ m/^net(\d+)$/) {
1472 # always change MAC! address
1473 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1474 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1475 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1476 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1478 # copy everything else
1479 $newconf->{$opt} = $value;
1482 die "can't clone CT to node '$target' (CT uses local storage)\n"
1483 if $target && !$sharedvm;
1485 # Replace the 'disk' lock with a 'create' lock.
1486 $newconf->{lock} = 'create';
1488 delete $newconf->{snapshots
};
1489 delete $newconf->{pending
};
1490 delete $newconf->{template
};
1491 if ($param->{hostname
}) {
1492 $newconf->{hostname
} = $param->{hostname
};
1495 if ($param->{description
}) {
1496 $newconf->{description
} = $param->{description
};
1499 # create empty/temp config - this fails if CT already exists on other node
1500 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1503 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1509 my $update_conf = sub {
1510 my ($key, $value) = @_;
1511 return PVE
::LXC
::Config-
>lock_config($newid, sub {
1512 my $conf = PVE
::LXC
::Config-
>load_config($newid);
1513 die "Lost 'create' config lock, aborting.\n"
1514 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1515 $conf->{$key} = $value;
1516 PVE
::LXC
::Config-
>write_config($newid, $conf);
1523 my $newvollist = [];
1525 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1526 die "unexpected state change\n" if $verify_running != $running;
1532 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1534 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1535 my $bwlimit = extract_param
($param, 'bwlimit');
1537 foreach my $opt (keys %$mountpoints) {
1538 my $mp = $mountpoints->{$opt};
1539 my $volid = $mp->{volume
};
1542 if ($fullclone->{$opt}) {
1543 print "create full clone of mountpoint $opt ($volid)\n";
1544 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1545 my $target_storage = $storage // $source_storage;
1546 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1547 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1549 print "create linked clone of mount point $opt ($volid)\n";
1550 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1553 push @$newvollist, $newvolid;
1554 $mp->{volume
} = $newvolid;
1556 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1559 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1560 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1563 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1564 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1565 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1567 my $newconffile = PVE
::LXC
::Config-
>config_file($newid, $target);
1568 die "Failed to move config to node '$target' - rename failed: $!\n"
1569 if !rename($conffile, $newconffile);
1574 # Unlock the source config in any case:
1575 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1579 # Now cleanup the config & disks:
1582 sleep 1; # some storages like rbd need to wait before release volume - really?
1584 foreach my $volid (@$newvollist) {
1585 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1588 die "clone failed: $err";
1594 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1595 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1599 __PACKAGE__-
>register_method({
1600 name
=> 'resize_vm',
1601 path
=> '{vmid}/resize',
1605 description
=> "Resize a container mount point.",
1607 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1610 additionalProperties
=> 0,
1612 node
=> get_standard_option
('pve-node'),
1613 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1616 description
=> "The disk you want to resize.",
1617 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys()],
1621 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1622 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.",
1626 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1634 description
=> "the task ID.",
1639 my $rpcenv = PVE
::RPCEnvironment
::get
();
1641 my $authuser = $rpcenv->get_user();
1643 my $node = extract_param
($param, 'node');
1645 my $vmid = extract_param
($param, 'vmid');
1647 my $digest = extract_param
($param, 'digest');
1649 my $sizestr = extract_param
($param, 'size');
1650 my $ext = ($sizestr =~ s/^\+//);
1651 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1652 die "invalid size string" if !defined($newsize);
1654 die "no options specified\n" if !scalar(keys %$param);
1656 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1658 my $storage_cfg = cfs_read_file
("storage.cfg");
1662 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1663 PVE
::LXC
::Config-
>check_lock($conf);
1665 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1667 my $running = PVE
::LXC
::check_running
($vmid);
1669 my $disk = $param->{disk
};
1670 my $mp = PVE
::LXC
::Config-
>parse_volume($disk, $conf->{$disk});
1672 my $volid = $mp->{volume
};
1674 my (undef, undef, $owner, undef, undef, undef, $format) =
1675 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1677 die "can't resize mount point owned by another container ($owner)"
1680 die "can't resize volume: $disk if snapshot exists\n"
1681 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1683 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1685 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1687 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1689 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1691 die "Could not determine current size of volume '$volid'\n" if !defined($size);
1693 $newsize += $size if $ext;
1694 $newsize = int($newsize);
1696 die "unable to shrink disk size\n" if $newsize < $size;
1698 die "disk is already at specified size\n" if $size == $newsize;
1700 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1702 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1703 # we pass 0 here (parameter only makes sense for qemu)
1704 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1706 $mp->{size
} = $newsize;
1707 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1709 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1711 if ($format eq 'raw') {
1712 # we need to ensure that the volume is mapped, if not needed this is a NOP
1713 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1714 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1718 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1719 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1720 die "internal error: CT running but mount point not attached to a loop device"
1722 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1724 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1725 # to be visible to it in its namespace.
1726 # To not interfere with the rest of the system we unshare the current mount namespace,
1727 # mount over /tmp and then run resize2fs.
1729 # interestingly we don't need to e2fsck on mounted systems...
1730 my $quoted = PVE
::Tools
::shellquote
($path);
1731 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1733 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1735 warn "Failed to update the container's filesystem: $@\n" if $@;
1738 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1739 PVE
::Tools
::run_command
(['resize2fs', $path]);
1741 warn "Failed to update the container's filesystem: $@\n" if $@;
1743 # always un-map if not running, this is a NOP if not needed
1744 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1749 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1752 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1755 __PACKAGE__-
>register_method({
1756 name
=> 'move_volume',
1757 path
=> '{vmid}/move_volume',
1761 description
=> "Move a rootfs-/mp-volume to a different storage",
1763 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1764 "and 'Datastore.AllocateSpace' permissions on the storage.",
1767 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1768 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1772 additionalProperties
=> 0,
1774 node
=> get_standard_option
('pve-node'),
1775 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1778 enum
=> [ PVE
::LXC
::Config-
>valid_volume_keys() ],
1779 description
=> "Volume which will be moved.",
1781 storage
=> get_standard_option
('pve-storage-id', {
1782 description
=> "Target Storage.",
1783 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1787 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1793 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1798 description
=> "Override I/O bandwidth limit (in KiB/s).",
1802 default => 'clone limit from datacenter or storage config',
1812 my $rpcenv = PVE
::RPCEnvironment
::get
();
1814 my $authuser = $rpcenv->get_user();
1816 my $vmid = extract_param
($param, 'vmid');
1818 my $storage = extract_param
($param, 'storage');
1820 my $mpkey = extract_param
($param, 'volume');
1822 my $lockname = 'disk';
1824 my ($mpdata, $old_volid);
1826 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1827 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1828 PVE
::LXC
::Config-
>check_lock($conf);
1830 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1832 $mpdata = PVE
::LXC
::Config-
>parse_volume($mpkey, $conf->{$mpkey});
1833 $old_volid = $mpdata->{volume
};
1835 die "you can't move a volume with snapshots and delete the source\n"
1836 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1838 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1840 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1845 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1847 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1848 my $storage_cfg = PVE
::Storage
::config
();
1853 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1854 my $bwlimit = extract_param
($param, 'bwlimit');
1855 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1856 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$source_storage, $storage], $bwlimit);
1857 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf, undef, $movelimit);
1858 if (PVE
::LXC
::Config-
>is_template($conf)) {
1859 PVE
::Storage
::activate_volumes
($storage_cfg, [ $new_volid ]);
1860 my $template_volid = PVE
::Storage
::vdisk_create_base
($storage_cfg, $new_volid);
1861 $mpdata->{volume
} = $template_volid;
1863 $mpdata->{volume
} = $new_volid;
1866 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1867 my $digest = $conf->{digest
};
1868 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1869 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1871 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1873 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1875 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1879 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1880 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1886 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1887 if defined($new_volid);
1893 if ($param->{delete}) {
1895 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1896 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1900 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1901 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1902 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid);
1903 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1909 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1914 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1917 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1924 __PACKAGE__-
>register_method({
1925 name
=> 'vm_pending',
1926 path
=> '{vmid}/pending',
1929 description
=> 'Get container configuration, including pending changes.',
1931 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1934 additionalProperties
=> 0,
1936 node
=> get_standard_option
('pve-node'),
1937 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1946 description
=> 'Configuration option name.',
1950 description
=> 'Current value.',
1955 description
=> 'Pending value.',
1960 description
=> "Indicates a pending delete request if present and not 0.",
1972 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
1974 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
1976 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);