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_enabled
($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);
360 $was_template = delete $orig_conf->{template
};
362 # When we're root call 'restore_configuration' with restricted=0,
363 # causing it to restore the raw lxc entries, among which there may be
364 # 'lxc.idmap' entries. We need to make sure that the extracted contents
365 # of the container match up with the restored configuration afterwards:
366 $conf->{lxc
} = $orig_conf->{lxc
} if $is_root;
368 $conf->{unprivileged
} = $orig_conf->{unprivileged
}
369 if !defined($unprivileged) && defined($orig_conf->{unprivileged
});
372 if ($storage_only_mode) {
374 if (!defined($orig_mp_param)) {
375 print "recovering backed-up configuration from '$archive'\n";
376 (undef, $orig_mp_param) = PVE
::LXC
::Create
::recover_config
($storage_cfg, $archive, $vmid);
378 $mp_param = $orig_mp_param;
379 die "rootfs configuration could not be recovered, please check and specify manually!\n"
380 if !defined($mp_param->{rootfs
});
381 PVE
::LXC
::Config-
>foreach_volume($mp_param, sub {
382 my ($ms, $mountpoint) = @_;
383 my $type = $mountpoint->{type
};
384 if ($type eq 'volume') {
385 die "unable to detect disk size - please specify $ms (size)\n"
386 if !defined($mountpoint->{size
});
387 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
388 delete $mountpoint->{size
};
389 $mountpoint->{volume
} = "$storage:$disksize";
390 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
392 my $type = $mountpoint->{type
};
393 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
394 if ($ms eq 'rootfs');
395 die "restoring '$ms' to $type mount is only possible for root\n"
398 if ($mountpoint->{backup
}) {
399 warn "WARNING - unsupported configuration!\n";
400 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
401 warn "mount point configuration will be restored after archive extraction!\n";
402 warn "contained files will be restored to wrong directory!\n";
404 delete $mp_param->{$ms}; # actually delay bind/dev mps
405 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
409 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
413 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
415 # we always have the 'create' lock so check for more than 1 entry
416 if (scalar(keys %$old_conf) > 1) {
417 # destroy old container volumes
418 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, { lock => 'create' });
422 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
423 $bwlimit = PVE
::Storage
::get_bandwidth_limit
('restore', [keys %used_storages], $bwlimit);
424 print "restoring '$archive' now..\n"
425 if $restore && $archive ne '-';
426 PVE
::LXC
::Create
::restore_archive
($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit);
429 print "merging backed-up and given configuration..\n";
430 PVE
::LXC
::Create
::restore_configuration
($vmid, $storage_cfg, $archive, $rootdir, $conf, !$is_root, $unique, $skip_fw_config_restore);
431 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
432 $lxc_setup->template_fixup($conf);
434 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
435 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
436 $lxc_setup->post_create_hook($password, $ssh_keys);
440 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
441 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
444 $conf->{hostname
} ||= "CT$vmid";
445 $conf->{memory
} ||= 512;
446 $conf->{swap
} //= 512;
447 foreach my $mp (keys %$delayed_mp_param) {
448 $conf->{$mp} = $delayed_mp_param->{$mp};
450 # If the template flag was set, we try to convert again to template after restore
452 print STDERR
"Convert restored container to template...\n";
453 PVE
::LXC
::template_create
($vmid, $conf);
454 $conf->{template
} = 1;
456 PVE
::LXC
::Config-
>write_config($vmid, $conf);
459 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
460 eval { PVE
::LXC
::Config-
>destroy_config($vmid) };
464 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
466 PVE
::API2
::LXC
::Status-
>vm_start({ vmid
=> $vmid, node
=> $node })
467 if $start_after_create;
470 my $workername = $restore ?
'vzrestore' : 'vzcreate';
471 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
473 return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd);
476 __PACKAGE__-
>register_method({
481 description
=> "Directory index",
486 additionalProperties
=> 0,
488 node
=> get_standard_option
('pve-node'),
489 vmid
=> get_standard_option
('pve-vmid'),
497 subdir
=> { type
=> 'string' },
500 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
506 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
509 { subdir
=> 'config' },
510 { subdir
=> 'pending' },
511 { subdir
=> 'status' },
512 { subdir
=> 'vncproxy' },
513 { subdir
=> 'termproxy' },
514 { subdir
=> 'vncwebsocket' },
515 { subdir
=> 'spiceproxy' },
516 { subdir
=> 'migrate' },
517 { subdir
=> 'clone' },
518 # { subdir => 'initlog' },
520 { subdir
=> 'rrddata' },
521 { subdir
=> 'firewall' },
522 { subdir
=> 'snapshot' },
523 { subdir
=> 'resize' },
530 __PACKAGE__-
>register_method({
532 path
=> '{vmid}/rrd',
534 protected
=> 1, # fixme: can we avoid that?
536 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
538 description
=> "Read VM RRD statistics (returns PNG)",
540 additionalProperties
=> 0,
542 node
=> get_standard_option
('pve-node'),
543 vmid
=> get_standard_option
('pve-vmid'),
545 description
=> "Specify the time frame you are interested in.",
547 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
550 description
=> "The list of datasources you want to display.",
551 type
=> 'string', format
=> 'pve-configid-list',
554 description
=> "The RRD consolidation function",
556 enum
=> [ 'AVERAGE', 'MAX' ],
564 filename
=> { type
=> 'string' },
570 return PVE
::RRD
::create_rrd_graph
(
571 "pve2-vm/$param->{vmid}", $param->{timeframe
},
572 $param->{ds
}, $param->{cf
});
576 __PACKAGE__-
>register_method({
578 path
=> '{vmid}/rrddata',
580 protected
=> 1, # fixme: can we avoid that?
582 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
584 description
=> "Read VM RRD statistics",
586 additionalProperties
=> 0,
588 node
=> get_standard_option
('pve-node'),
589 vmid
=> get_standard_option
('pve-vmid'),
591 description
=> "Specify the time frame you are interested in.",
593 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
596 description
=> "The RRD consolidation function",
598 enum
=> [ 'AVERAGE', 'MAX' ],
613 return PVE
::RRD
::create_rrd_data
(
614 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
617 __PACKAGE__-
>register_method({
618 name
=> 'destroy_vm',
623 description
=> "Destroy the container (also delete all uses files).",
625 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
628 additionalProperties
=> 0,
630 node
=> get_standard_option
('pve-node'),
631 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
634 description
=> "Force destroy, even if running.",
640 description
=> "Remove container from all related configurations."
641 ." For example, backup jobs, replication jobs or HA."
642 ." Related ACLs and Firewall entries will *always* be removed.",
646 'destroy-unreferenced-disks' => {
648 description
=> "If set, destroy additionally all disks with the VMID from all"
649 ." enabled storages which are not referenced in the config.",
660 my $rpcenv = PVE
::RPCEnvironment
::get
();
661 my $authuser = $rpcenv->get_user();
662 my $vmid = $param->{vmid
};
664 # test if container exists
666 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
667 my $early_checks = sub {
669 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
670 PVE
::LXC
::Config-
>check_lock($conf);
672 my $ha_managed = PVE
::HA
::Config
::service_is_configured
("ct:$vmid");
674 if (!$param->{purge
}) {
675 die "unable to remove CT $vmid - used in HA resources and purge parameter not set.\n"
678 # do not allow destroy if there are replication jobs without purge
679 my $repl_conf = PVE
::ReplicationConfig-
>new();
680 $repl_conf->check_for_existing_jobs($vmid);
686 $early_checks->($conf);
688 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
689 die $running_error_msg if !$param->{force
} && PVE
::LXC
::check_running
($vmid); # check early
692 # reload config after lock
693 $conf = PVE
::LXC
::Config-
>load_config($vmid);
694 my $ha_managed = $early_checks->($conf);
696 if (PVE
::LXC
::check_running
($vmid)) {
697 die $running_error_msg if !$param->{force
};
698 warn "forced to stop CT $vmid before destroying!\n";
700 PVE
::LXC
::vm_stop
($vmid, 1);
702 run_command
(['ha-manager', 'crm-command', 'stop', "ct:$vmid", '120']);
706 my $storage_cfg = cfs_read_file
("storage.cfg");
707 PVE
::LXC
::destroy_lxc_container
(
711 { lock => 'destroyed' },
712 $param->{'destroy-unreferenced-disks'},
715 PVE
::AccessControl
::remove_vm_access
($vmid);
716 PVE
::Firewall
::remove_vmfw_conf
($vmid);
717 if ($param->{purge
}) {
718 print "purging CT $vmid from related configurations..\n";
719 PVE
::ReplicationConfig
::remove_vmid_jobs
($vmid);
720 PVE
::VZDump
::Plugin
::remove_vmid_from_backup_jobs
($vmid);
723 PVE
::HA
::Config
::delete_service_from_config
("ct:$vmid");
724 print "NOTE: removed CT $vmid from HA resource configuration.\n";
728 # only now remove the zombie config, else we can have reuse race
729 PVE
::LXC
::Config-
>destroy_config($vmid);
732 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
734 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
739 __PACKAGE__-
>register_method ({
741 path
=> '{vmid}/vncproxy',
745 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
747 description
=> "Creates a TCP VNC proxy connections.",
749 additionalProperties
=> 0,
751 node
=> get_standard_option
('pve-node'),
752 vmid
=> get_standard_option
('pve-vmid'),
756 description
=> "use websocket instead of standard VNC.",
760 description
=> "sets the width of the console in pixels.",
767 description
=> "sets the height of the console in pixels.",
775 additionalProperties
=> 0,
777 user
=> { type
=> 'string' },
778 ticket
=> { type
=> 'string' },
779 cert
=> { type
=> 'string' },
780 port
=> { type
=> 'integer' },
781 upid
=> { type
=> 'string' },
787 my $rpcenv = PVE
::RPCEnvironment
::get
();
789 my $authuser = $rpcenv->get_user();
791 my $vmid = $param->{vmid
};
792 my $node = $param->{node
};
794 my $authpath = "/vms/$vmid";
796 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
798 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
801 my ($remip, $family);
803 if ($node ne PVE
::INotify
::nodename
()) {
804 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
806 $family = PVE
::Tools
::get_host_address_family
($node);
809 my $port = PVE
::Tools
::next_vnc_port
($family);
811 # NOTE: vncterm VNC traffic is already TLS encrypted,
812 # so we select the fastest chipher here (or 'none'?)
813 my $remcmd = $remip ?
814 ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : [];
816 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
817 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
819 my $shcmd = [ '/usr/bin/dtach', '-A',
820 "/var/run/dtach/vzctlconsole$vmid",
821 '-r', 'winch', '-z', @$concmd];
826 syslog
('info', "starting lxc vnc proxy $upid\n");
830 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
831 '-timeout', $timeout, '-authpath', $authpath,
832 '-perm', 'VM.Console'];
834 if ($param->{width
}) {
835 push @$cmd, '-width', $param->{width
};
838 if ($param->{height
}) {
839 push @$cmd, '-height', $param->{height
};
842 if ($param->{websocket
}) {
843 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
844 push @$cmd, '-notls', '-listen', 'localhost';
847 push @$cmd, '-c', @$remcmd, @$shcmd;
849 run_command
($cmd, keeplocale
=> 1);
854 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
856 PVE
::Tools
::wait_for_vnc_port
($port);
867 __PACKAGE__-
>register_method ({
869 path
=> '{vmid}/termproxy',
873 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
875 description
=> "Creates a TCP proxy connection.",
877 additionalProperties
=> 0,
879 node
=> get_standard_option
('pve-node'),
880 vmid
=> get_standard_option
('pve-vmid'),
884 additionalProperties
=> 0,
886 user
=> { type
=> 'string' },
887 ticket
=> { type
=> 'string' },
888 port
=> { type
=> 'integer' },
889 upid
=> { type
=> 'string' },
895 my $rpcenv = PVE
::RPCEnvironment
::get
();
897 my $authuser = $rpcenv->get_user();
899 my $vmid = $param->{vmid
};
900 my $node = $param->{node
};
902 my $authpath = "/vms/$vmid";
904 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
906 my ($remip, $family);
908 if ($node ne 'localhost' && $node ne PVE
::INotify
::nodename
()) {
909 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
911 $family = PVE
::Tools
::get_host_address_family
($node);
914 my $port = PVE
::Tools
::next_vnc_port
($family);
916 my $remcmd = $remip ?
917 ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : [];
919 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
920 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf, -1);
922 my $shcmd = [ '/usr/bin/dtach', '-A',
923 "/var/run/dtach/vzctlconsole$vmid",
924 '-r', 'winch', '-z', @$concmd];
929 syslog
('info', "starting lxc termproxy $upid\n");
931 my $cmd = ['/usr/bin/termproxy', $port, '--path', $authpath,
932 '--perm', 'VM.Console', '--'];
933 push @$cmd, @$remcmd, @$shcmd;
935 PVE
::Tools
::run_command
($cmd);
938 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd, 1);
940 PVE
::Tools
::wait_for_vnc_port
($port);
950 __PACKAGE__-
>register_method({
951 name
=> 'vncwebsocket',
952 path
=> '{vmid}/vncwebsocket',
955 description
=> "You also need to pass a valid ticket (vncticket).",
956 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
958 description
=> "Opens a weksocket for VNC traffic.",
960 additionalProperties
=> 0,
962 node
=> get_standard_option
('pve-node'),
963 vmid
=> get_standard_option
('pve-vmid'),
965 description
=> "Ticket from previous call to vncproxy.",
970 description
=> "Port number returned by previous vncproxy call.",
980 port
=> { type
=> 'string' },
986 my $rpcenv = PVE
::RPCEnvironment
::get
();
988 my $authuser = $rpcenv->get_user();
990 my $authpath = "/vms/$param->{vmid}";
992 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
994 my $port = $param->{port
};
996 return { port
=> $port };
999 __PACKAGE__-
>register_method ({
1000 name
=> 'spiceproxy',
1001 path
=> '{vmid}/spiceproxy',
1006 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
1008 description
=> "Returns a SPICE configuration to connect to the CT.",
1010 additionalProperties
=> 0,
1012 node
=> get_standard_option
('pve-node'),
1013 vmid
=> get_standard_option
('pve-vmid'),
1014 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
1017 returns
=> get_standard_option
('remote-viewer-config'),
1021 my $vmid = $param->{vmid
};
1022 my $node = $param->{node
};
1023 my $proxy = $param->{proxy
};
1025 my $authpath = "/vms/$vmid";
1026 my $permissions = 'VM.Console';
1028 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1030 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
1032 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
1034 my $shcmd = ['/usr/bin/dtach', '-A',
1035 "/var/run/dtach/vzctlconsole$vmid",
1036 '-r', 'winch', '-z', @$concmd];
1038 my $title = "CT $vmid";
1040 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
1044 __PACKAGE__-
>register_method({
1045 name
=> 'migrate_vm',
1046 path
=> '{vmid}/migrate',
1050 description
=> "Migrate the container to another node. Creates a new migration task.",
1052 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
1055 additionalProperties
=> 0,
1057 node
=> get_standard_option
('pve-node'),
1058 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1059 target
=> get_standard_option
('pve-node', {
1060 description
=> "Target node.",
1061 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
1065 description
=> "Use online/live migration.",
1070 description
=> "Use restart migration",
1075 description
=> "Timeout in seconds for shutdown for restart migration",
1080 description
=> "Override I/O bandwidth limit (in KiB/s).",
1084 default => 'migrate limit from datacenter or storage config',
1090 description
=> "the task ID.",
1095 my $rpcenv = PVE
::RPCEnvironment
::get
();
1097 my $authuser = $rpcenv->get_user();
1099 my $target = extract_param
($param, 'target');
1101 my $localnode = PVE
::INotify
::nodename
();
1102 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
1104 PVE
::Cluster
::check_cfs_quorum
();
1106 PVE
::Cluster
::check_node_exists
($target);
1108 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
1110 my $vmid = extract_param
($param, 'vmid');
1113 PVE
::LXC
::Config-
>load_config($vmid);
1115 # try to detect errors early
1116 if (PVE
::LXC
::check_running
($vmid)) {
1117 die "can't migrate running container without --online or --restart\n"
1118 if !$param->{online
} && !$param->{restart
};
1121 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
1126 my $service = "ct:$vmid";
1128 my $cmd = ['ha-manager', 'migrate', $service, $target];
1130 print "Requesting HA migration for CT $vmid to node $target\n";
1132 PVE
::Tools
::run_command
($cmd);
1137 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
1142 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
1146 return PVE
::GuestHelpers
::guest_migration_lock
($vmid, 10, $realcmd);
1149 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $worker);
1153 __PACKAGE__-
>register_method({
1154 name
=> 'vm_feature',
1155 path
=> '{vmid}/feature',
1159 description
=> "Check if feature for virtual machine is available.",
1161 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1164 additionalProperties
=> 0,
1166 node
=> get_standard_option
('pve-node'),
1167 vmid
=> get_standard_option
('pve-vmid'),
1169 description
=> "Feature to check.",
1171 enum
=> [ 'snapshot', 'clone', 'copy' ],
1173 snapname
=> get_standard_option
('pve-snapshot-name', {
1181 hasFeature
=> { type
=> 'boolean' },
1184 #items => { type => 'string' },
1191 my $node = extract_param
($param, 'node');
1193 my $vmid = extract_param
($param, 'vmid');
1195 my $snapname = extract_param
($param, 'snapname');
1197 my $feature = extract_param
($param, 'feature');
1199 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1202 my $snap = $conf->{snapshots
}->{$snapname};
1203 die "snapshot '$snapname' does not exist\n" if !defined($snap);
1206 my $storage_cfg = PVE
::Storage
::config
();
1207 #Maybe include later
1208 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
1209 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1212 hasFeature
=> $hasFeature,
1213 #nodes => [ keys %$nodelist ],
1217 __PACKAGE__-
>register_method({
1219 path
=> '{vmid}/template',
1223 description
=> "Create a Template.",
1225 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1226 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1229 additionalProperties
=> 0,
1231 node
=> get_standard_option
('pve-node'),
1232 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1235 returns
=> { type
=> 'null'},
1239 my $rpcenv = PVE
::RPCEnvironment
::get
();
1241 my $authuser = $rpcenv->get_user();
1243 my $node = extract_param
($param, 'node');
1245 my $vmid = extract_param
($param, 'vmid');
1247 my $updatefn = sub {
1249 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1250 PVE
::LXC
::Config-
>check_lock($conf);
1252 die "unable to create template, because CT contains snapshots\n"
1253 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1255 die "you can't convert a template to a template\n"
1256 if PVE
::LXC
::Config-
>is_template($conf);
1258 die "you can't convert a CT to template if the CT is running\n"
1259 if PVE
::LXC
::check_running
($vmid);
1262 PVE
::LXC
::template_create
($vmid, $conf);
1264 $conf->{template
} = 1;
1266 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1267 # and remove lxc config
1268 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1271 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1274 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1279 __PACKAGE__-
>register_method({
1281 path
=> '{vmid}/clone',
1285 description
=> "Create a container clone/copy",
1287 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1288 "and 'VM.Allocate' permissions " .
1289 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1290 "'Datastore.AllocateSpace' on any used storage.",
1293 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1295 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1296 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1301 additionalProperties
=> 0,
1303 node
=> get_standard_option
('pve-node'),
1304 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1305 newid
=> get_standard_option
('pve-vmid', {
1306 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1307 description
=> 'VMID for the clone.' }),
1310 type
=> 'string', format
=> 'dns-name',
1311 description
=> "Set a hostname for the new CT.",
1316 description
=> "Description for the new CT.",
1320 type
=> 'string', format
=> 'pve-poolid',
1321 description
=> "Add the new CT to the specified pool.",
1323 snapname
=> get_standard_option
('pve-snapshot-name', {
1326 storage
=> get_standard_option
('pve-storage-id', {
1327 description
=> "Target storage for full clone.",
1333 description
=> "Create a full copy of all disks. This is always done when " .
1334 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1336 target
=> get_standard_option
('pve-node', {
1337 description
=> "Target node. Only allowed if the original VM is on shared storage.",
1341 description
=> "Override I/O bandwidth limit (in KiB/s).",
1345 default => 'clone limit from datacenter or storage config',
1355 my $rpcenv = PVE
::RPCEnvironment
::get
();
1356 my $authuser = $rpcenv->get_user();
1358 my $node = extract_param
($param, 'node');
1359 my $vmid = extract_param
($param, 'vmid');
1360 my $newid = extract_param
($param, 'newid');
1361 my $pool = extract_param
($param, 'pool');
1362 if (defined($pool)) {
1363 $rpcenv->check_pool_exist($pool);
1365 my $snapname = extract_param
($param, 'snapname');
1366 my $storage = extract_param
($param, 'storage');
1367 my $target = extract_param
($param, 'target');
1368 my $localnode = PVE
::INotify
::nodename
();
1370 undef $target if $target && ($target eq $localnode || $target eq 'localhost');
1372 PVE
::Cluster
::check_node_exists
($target) if $target;
1374 my $storecfg = PVE
::Storage
::config
();
1377 # check if storage is enabled on local node
1378 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1380 # check if storage is available on target node
1381 PVE
::Storage
::storage_check_enabled
($storecfg, $storage, $target);
1382 # clone only works if target storage is shared
1383 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
1384 die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared
};
1388 PVE
::Cluster
::check_cfs_quorum
();
1391 my $mountpoints = {};
1396 PVE
::LXC
::Config-
>create_and_lock_config($newid, 0);
1397 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1399 my $lock_and_reload = sub {
1400 my ($vmid, $code) = @_;
1401 return PVE
::LXC
::Config-
>lock_config($vmid, sub {
1402 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1403 die "Lost 'create' config lock, aborting.\n"
1404 if !PVE
::LXC
::Config-
>has_lock($conf, 'create');
1406 return $code->($conf);
1410 my $src_conf = PVE
::LXC
::Config-
>set_lock($vmid, 'disk');
1412 $running = PVE
::LXC
::check_running
($vmid) || 0;
1414 my $full = extract_param
($param, 'full');
1415 if (!defined($full)) {
1416 $full = !PVE
::LXC
::Config-
>is_template($src_conf);
1420 die "parameter 'storage' not allowed for linked clones\n"
1421 if defined($storage) && !$full;
1423 die "snapshot '$snapname' does not exist\n"
1424 if $snapname && !defined($src_conf->{snapshots
}->{$snapname});
1426 my $src_conf = $snapname ?
$src_conf->{snapshots
}->{$snapname} : $src_conf;
1429 for my $opt (sort keys %$src_conf) {
1430 next if $opt =~ m/^unused\d+$/;
1432 my $value = $src_conf->{$opt};
1434 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1435 my $mp = PVE
::LXC
::Config-
>parse_volume($opt, $value);
1437 if ($mp->{type
} eq 'volume') {
1438 my $volid = $mp->{volume
};
1440 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1441 $sid = $storage if defined($storage);
1442 my $scfg = PVE
::Storage
::storage_config
($storecfg, $sid);
1443 if (!$scfg->{shared
}) {
1445 warn "found non-shared volume: $volid\n" if $target;
1448 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
1451 die "Cannot do full clones on a running container without snapshots\n"
1452 if $running && !defined($snapname);
1453 $fullclone->{$opt} = 1;
1455 # not full means clone instead of copy
1456 die "Linked clone feature for '$volid' is not available\n"
1457 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running, {'valid_target_formats' => ['raw', 'subvol']});
1460 $mountpoints->{$opt} = $mp;
1461 push @$vollist, $volid;
1464 # TODO: allow bind mounts?
1465 die "unable to clone mountpoint '$opt' (type $mp->{type})\n";
1467 } elsif ($opt =~ m/^net(\d+)$/) {
1468 # always change MAC! address
1469 my $dc = PVE
::Cluster
::cfs_read_file
('datacenter.cfg');
1470 my $net = PVE
::LXC
::Config-
>parse_lxc_network($value);
1471 $net->{hwaddr
} = PVE
::Tools
::random_ether_addr
($dc->{mac_prefix
});
1472 $newconf->{$opt} = PVE
::LXC
::Config-
>print_lxc_network($net);
1474 # copy everything else
1475 $newconf->{$opt} = $value;
1478 die "can't clone CT to node '$target' (CT uses local storage)\n"
1479 if $target && !$sharedvm;
1481 # Replace the 'disk' lock with a 'create' lock.
1482 $newconf->{lock} = 'create';
1484 delete $newconf->{snapshots
};
1485 delete $newconf->{pending
};
1486 delete $newconf->{template
};
1487 if ($param->{hostname
}) {
1488 $newconf->{hostname
} = $param->{hostname
};
1491 if ($param->{description
}) {
1492 $newconf->{description
} = $param->{description
};
1495 $lock_and_reload->($newid, sub {
1496 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1500 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1501 warn "Failed to remove source CT config lock - $@\n" if $@;
1504 $lock_and_reload->($newid, sub {
1505 PVE
::LXC
::Config-
>destroy_config($newid);
1506 PVE
::Firewall
::remove_vmfw_conf
($newid);
1509 warn "Failed to remove target CT config - $@\n" if $@;
1514 my $update_conf = sub {
1515 my ($key, $value) = @_;
1516 return $lock_and_reload->($newid, sub {
1518 $conf->{$key} = $value;
1519 PVE
::LXC
::Config-
>write_config($newid, $conf);
1526 my $newvollist = [];
1528 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1529 die "unexpected state change\n" if $verify_running != $running;
1535 local $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1537 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1538 my $bwlimit = extract_param
($param, 'bwlimit');
1540 foreach my $opt (keys %$mountpoints) {
1541 my $mp = $mountpoints->{$opt};
1542 my $volid = $mp->{volume
};
1545 if ($fullclone->{$opt}) {
1546 print "create full clone of mountpoint $opt ($volid)\n";
1547 my $source_storage = PVE
::Storage
::parse_volume_id
($volid);
1548 my $target_storage = $storage // $source_storage;
1549 my $clonelimit = PVE
::Storage
::get_bandwidth_limit
('clone', [$source_storage, $target_storage], $bwlimit);
1550 $newvolid = PVE
::LXC
::copy_volume
($mp, $newid, $target_storage, $storecfg, $newconf, $snapname, $clonelimit);
1552 print "create linked clone of mount point $opt ($volid)\n";
1553 $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1556 push @$newvollist, $newvolid;
1557 $mp->{volume
} = $newvolid;
1559 $update_conf->($opt, PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs'));
1562 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1564 $lock_and_reload->($newid, sub {
1566 my $rootdir = PVE
::LXC
::mount_all
($newid, $storecfg, $conf, 1);
1568 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir);
1569 $lxc_setup->post_clone_hook($conf);
1572 eval { PVE
::LXC
::umount_all
($newid, $storecfg, $conf, 1); };
1582 # Unlock the source config in any case:
1583 eval { PVE
::LXC
::Config-
>remove_lock($vmid, 'disk') };
1587 # Now cleanup the config & disks:
1588 sleep 1; # some storages like rbd need to wait before release volume - really?
1590 foreach my $volid (@$newvollist) {
1591 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1596 $lock_and_reload->($newid, sub {
1597 PVE
::LXC
::Config-
>destroy_config($newid);
1598 PVE
::Firewall
::remove_vmfw_conf
($newid);
1601 warn "Failed to remove target CT config - $@\n" if $@;
1603 die "clone failed: $err";
1606 $lock_and_reload->($newid, sub {
1607 PVE
::LXC
::Config-
>remove_lock($newid, 'create');
1610 # always deactivate volumes - avoid lvm LVs to be active on several nodes
1611 PVE
::Storage
::deactivate_volumes
($storecfg, $vollist, $snapname) if !$running;
1612 PVE
::Storage
::deactivate_volumes
($storecfg, $newvollist);
1614 PVE
::LXC
::Config-
>move_config_to_node($newid, $target);
1621 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1625 __PACKAGE__-
>register_method({
1626 name
=> 'resize_vm',
1627 path
=> '{vmid}/resize',
1631 description
=> "Resize a container mount point.",
1633 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1636 additionalProperties
=> 0,
1638 node
=> get_standard_option
('pve-node'),
1639 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1642 description
=> "The disk you want to resize.",
1643 enum
=> [PVE
::LXC
::Config-
>valid_volume_keys()],
1647 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1648 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.",
1652 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1660 description
=> "the task ID.",
1665 my $rpcenv = PVE
::RPCEnvironment
::get
();
1667 my $authuser = $rpcenv->get_user();
1669 my $node = extract_param
($param, 'node');
1671 my $vmid = extract_param
($param, 'vmid');
1673 my $digest = extract_param
($param, 'digest');
1675 my $sizestr = extract_param
($param, 'size');
1676 my $ext = ($sizestr =~ s/^\+//);
1677 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1678 die "invalid size string" if !defined($newsize);
1680 die "no options specified\n" if !scalar(keys %$param);
1682 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1684 my $storage_cfg = cfs_read_file
("storage.cfg");
1688 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1689 PVE
::LXC
::Config-
>check_lock($conf);
1691 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1693 my $running = PVE
::LXC
::check_running
($vmid);
1695 my $disk = $param->{disk
};
1696 my $mp = PVE
::LXC
::Config-
>parse_volume($disk, $conf->{$disk});
1698 my $volid = $mp->{volume
};
1700 my (undef, undef, $owner, undef, undef, undef, $format) =
1701 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1703 die "can't resize mount point owned by another container ($owner)"
1706 die "can't resize volume: $disk if snapshot exists\n"
1707 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1709 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1711 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1713 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1715 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1717 die "Could not determine current size of volume '$volid'\n" if !defined($size);
1719 $newsize += $size if $ext;
1720 $newsize = int($newsize);
1722 die "unable to shrink disk size\n" if $newsize < $size;
1724 die "disk is already at specified size\n" if $size == $newsize;
1726 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1728 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1729 # we pass 0 here (parameter only makes sense for qemu)
1730 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1732 $mp->{size
} = $newsize;
1733 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1735 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1737 if ($format eq 'raw') {
1738 # we need to ensure that the volume is mapped, if not needed this is a NOP
1739 my $path = PVE
::Storage
::map_volume
($storage_cfg, $volid);
1740 $path = PVE
::Storage
::path
($storage_cfg, $volid) if !defined($path);
1744 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1745 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1746 die "internal error: CT running but mount point not attached to a loop device"
1748 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1750 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1751 # to be visible to it in its namespace.
1752 # To not interfere with the rest of the system we unshare the current mount namespace,
1753 # mount over /tmp and then run resize2fs.
1755 # interestingly we don't need to e2fsck on mounted systems...
1756 my $quoted = PVE
::Tools
::shellquote
($path);
1757 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1759 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1761 warn "Failed to update the container's filesystem: $@\n" if $@;
1764 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1765 PVE
::Tools
::run_command
(['resize2fs', $path]);
1767 warn "Failed to update the container's filesystem: $@\n" if $@;
1769 # always un-map if not running, this is a NOP if not needed
1770 PVE
::Storage
::unmap_volume
($storage_cfg, $volid);
1775 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1778 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;
1781 __PACKAGE__-
>register_method({
1782 name
=> 'move_volume',
1783 path
=> '{vmid}/move_volume',
1787 description
=> "Move a rootfs-/mp-volume to a different storage",
1789 description
=> "You need 'VM.Config.Disk' permissions on /vms/{vmid}, " .
1790 "and 'Datastore.AllocateSpace' permissions on the storage.",
1793 ['perm', '/vms/{vmid}', [ 'VM.Config.Disk' ]],
1794 ['perm', '/storage/{storage}', [ 'Datastore.AllocateSpace' ]],
1798 additionalProperties
=> 0,
1800 node
=> get_standard_option
('pve-node'),
1801 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1804 enum
=> [ PVE
::LXC
::Config-
>valid_volume_keys() ],
1805 description
=> "Volume which will be moved.",
1807 storage
=> get_standard_option
('pve-storage-id', {
1808 description
=> "Target Storage.",
1809 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
1813 description
=> "Delete the original volume after successful copy. By default the original is kept as an unused volume entry.",
1819 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1824 description
=> "Override I/O bandwidth limit (in KiB/s).",
1828 default => 'clone limit from datacenter or storage config',
1838 my $rpcenv = PVE
::RPCEnvironment
::get
();
1840 my $authuser = $rpcenv->get_user();
1842 my $vmid = extract_param
($param, 'vmid');
1844 my $storage = extract_param
($param, 'storage');
1846 my $mpkey = extract_param
($param, 'volume');
1848 my $lockname = 'disk';
1850 my ($mpdata, $old_volid);
1852 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1853 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1854 PVE
::LXC
::Config-
>check_lock($conf);
1856 die "cannot move volumes of a running container\n" if PVE
::LXC
::check_running
($vmid);
1858 $mpdata = PVE
::LXC
::Config-
>parse_volume($mpkey, $conf->{$mpkey});
1859 $old_volid = $mpdata->{volume
};
1861 die "you can't move a volume with snapshots and delete the source\n"
1862 if $param->{delete} && PVE
::LXC
::Config-
>is_volume_in_use_by_snapshots($conf, $old_volid);
1864 PVE
::Tools
::assert_if_modified
($param->{digest
}, $conf->{digest
});
1866 PVE
::LXC
::Config-
>set_lock($vmid, $lockname);
1871 PVE
::Cluster
::log_msg
('info', $authuser, "move volume CT $vmid: move --volume $mpkey --storage $storage");
1873 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1874 my $storage_cfg = PVE
::Storage
::config
();
1879 PVE
::Storage
::activate_volumes
($storage_cfg, [ $old_volid ]);
1880 my $bwlimit = extract_param
($param, 'bwlimit');
1881 my $source_storage = PVE
::Storage
::parse_volume_id
($old_volid);
1882 my $movelimit = PVE
::Storage
::get_bandwidth_limit
('move', [$source_storage, $storage], $bwlimit);
1883 $new_volid = PVE
::LXC
::copy_volume
($mpdata, $vmid, $storage, $storage_cfg, $conf, undef, $movelimit);
1884 if (PVE
::LXC
::Config-
>is_template($conf)) {
1885 PVE
::Storage
::activate_volumes
($storage_cfg, [ $new_volid ]);
1886 my $template_volid = PVE
::Storage
::vdisk_create_base
($storage_cfg, $new_volid);
1887 $mpdata->{volume
} = $template_volid;
1889 $mpdata->{volume
} = $new_volid;
1892 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1893 my $digest = $conf->{digest
};
1894 $conf = PVE
::LXC
::Config-
>load_config($vmid);
1895 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1897 $conf->{$mpkey} = PVE
::LXC
::Config-
>print_ct_mountpoint($mpdata, $mpkey eq 'rootfs');
1899 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid) if !$param->{delete};
1901 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1905 # try to deactivate volumes - avoid lvm LVs to be active on several nodes
1906 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $new_volid ])
1912 PVE
::Storage
::vdisk_free
($storage_cfg, $new_volid)
1913 if defined($new_volid);
1919 if ($param->{delete}) {
1921 PVE
::Storage
::deactivate_volumes
($storage_cfg, [ $old_volid ]);
1922 PVE
::Storage
::vdisk_free
($storage_cfg, $old_volid);
1926 PVE
::LXC
::Config-
>lock_config($vmid, sub {
1927 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1928 PVE
::LXC
::Config-
>add_unused_volume($conf, $old_volid);
1929 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1935 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1940 $rpcenv->fork_worker('move_volume', $vmid, $authuser, $realcmd);
1943 eval { PVE
::LXC
::Config-
>remove_lock($vmid, $lockname) };
1950 __PACKAGE__-
>register_method({
1951 name
=> 'vm_pending',
1952 path
=> '{vmid}/pending',
1955 description
=> 'Get container configuration, including pending changes.',
1957 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
1960 additionalProperties
=> 0,
1962 node
=> get_standard_option
('pve-node'),
1963 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1972 description
=> 'Configuration option name.',
1976 description
=> 'Current value.',
1981 description
=> 'Pending value.',
1986 description
=> "Indicates a pending delete request if present and not 0.",
1998 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
2000 my $pending_delete_hash = PVE
::LXC
::Config-
>parse_pending_delete($conf->{pending
}->{delete});
2002 return PVE
::GuestHelpers
::config_with_pending_array
($conf, $pending_delete_hash);