1 package PVE
::API2
::LXC
;
7 use PVE
::Tools
qw(extract_param run_command);
8 use PVE
::Exception
qw(raise raise_param_exc);
10 use PVE
::Cluster
qw(cfs_read_file);
11 use PVE
::AccessControl
;
15 use PVE
::RPCEnvironment
;
18 use PVE
::LXC
::Migrate
;
19 use PVE
::API2
::LXC
::Config
;
20 use PVE
::API2
::LXC
::Status
;
21 use PVE
::API2
::LXC
::Snapshot
;
22 use PVE
::HA
::Env
::PVE2
;
24 use PVE
::JSONSchema
qw(get_standard_option);
25 use base
qw(PVE::RESTHandler);
27 use Data
::Dumper
; # fixme: remove
29 __PACKAGE__-
>register_method ({
30 subclass
=> "PVE::API2::LXC::Config",
31 path
=> '{vmid}/config',
34 __PACKAGE__-
>register_method ({
35 subclass
=> "PVE::API2::LXC::Status",
36 path
=> '{vmid}/status',
39 __PACKAGE__-
>register_method ({
40 subclass
=> "PVE::API2::LXC::Snapshot",
41 path
=> '{vmid}/snapshot',
44 __PACKAGE__-
>register_method ({
45 subclass
=> "PVE::API2::Firewall::CT",
46 path
=> '{vmid}/firewall',
49 __PACKAGE__-
>register_method({
53 description
=> "LXC container index (per node).",
55 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
59 protected
=> 1, # /proc files are only readable by root
61 additionalProperties
=> 0,
63 node
=> get_standard_option
('pve-node'),
72 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
77 my $rpcenv = PVE
::RPCEnvironment
::get
();
78 my $authuser = $rpcenv->get_user();
80 my $vmstatus = PVE
::LXC
::vmstatus
();
83 foreach my $vmid (keys %$vmstatus) {
84 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
86 my $data = $vmstatus->{$vmid};
87 $data->{vmid
} = $vmid;
95 __PACKAGE__-
>register_method({
99 description
=> "Create or restore a container.",
101 user
=> 'all', # check inside
102 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
103 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
104 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
109 additionalProperties
=> 0,
110 properties
=> PVE
::LXC
::Config-
>json_config_properties({
111 node
=> get_standard_option
('pve-node'),
112 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
114 description
=> "The OS template or backup file.",
117 completion
=> \
&PVE
::LXC
::complete_os_templates
,
122 description
=> "Sets root password inside container.",
125 storage
=> get_standard_option
('pve-storage-id', {
126 description
=> "Default Storage.",
129 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
134 description
=> "Allow to overwrite existing container.",
139 description
=> "Mark this as restore task.",
143 type
=> 'string', format
=> 'pve-poolid',
144 description
=> "Add the VM to the specified pool.",
146 'ignore-unpack-errors' => {
149 description
=> "Ignore errors when extracting the template.",
151 'ssh-public-keys' => {
154 description
=> "Setup public SSH keys (one key per line, " .
165 my $rpcenv = PVE
::RPCEnvironment
::get
();
167 my $authuser = $rpcenv->get_user();
169 my $node = extract_param
($param, 'node');
171 my $vmid = extract_param
($param, 'vmid');
173 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
175 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
177 my $same_container_exists = -f
$basecfg_fn;
179 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
180 my $unprivileged = extract_param
($param, 'unprivileged');
182 my $restore = extract_param
($param, 'restore');
185 # fixme: limit allowed parameters
189 my $force = extract_param
($param, 'force');
191 if (!($same_container_exists && $restore && $force)) {
192 PVE
::Cluster
::check_vmid_unused
($vmid);
194 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
195 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
198 my $password = extract_param
($param, 'password');
200 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
201 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
203 my $pool = extract_param
($param, 'pool');
205 if (defined($pool)) {
206 $rpcenv->check_pool_exist($pool);
207 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
210 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
212 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
214 } elsif ($restore && $force && $same_container_exists &&
215 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
216 # OK: user has VM.Backup permissions, and want to restore an existing VM
221 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
223 my $storage = extract_param
($param, 'storage') // 'local';
225 my $storage_cfg = cfs_read_file
("storage.cfg");
227 my $ostemplate = extract_param
($param, 'ostemplate');
231 if ($ostemplate eq '-') {
232 die "pipe requires cli environment\n"
233 if $rpcenv->{type
} ne 'cli';
234 die "pipe can only be used with restore tasks\n"
237 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
239 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
240 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
243 my $check_and_activate_storage = sub {
246 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
248 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
249 if !$scfg->{content
}->{rootdir
};
251 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
253 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
258 my $no_disk_param = {};
260 my $storage_only_mode = 1;
261 foreach my $opt (keys %$param) {
262 my $value = $param->{$opt};
263 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
264 # allow to use simple numbers (add default storage in that case)
265 if ($value =~ m/^\d+(\.\d+)?$/) {
266 $mp_param->{$opt} = "$storage:$value";
268 $mp_param->{$opt} = $value;
270 $storage_only_mode = 0;
271 } elsif ($opt =~ m/^unused\d+$/) {
272 warn "ignoring '$opt', cannot create/restore with unused volume\n";
273 delete $param->{$opt};
275 $no_disk_param->{$opt} = $value;
279 die "mountpoints configured, but 'rootfs' not set - aborting\n"
280 if !$storage_only_mode && !defined($mp_param->{rootfs
});
282 # check storage access, activate storage
283 my $delayed_mp_param = {};
284 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
285 my ($ms, $mountpoint) = @_;
287 my $volid = $mountpoint->{volume
};
288 my $mp = $mountpoint->{mp
};
290 if ($mountpoint->{type
} ne 'volume') { # bind or device
291 die "Only root can pass arbitrary filesystem paths.\n"
292 if $authuser ne 'root@pam';
294 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
295 &$check_and_activate_storage($sid);
299 # check/activate default storage
300 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
302 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
304 $conf->{unprivileged
} = 1 if $unprivileged;
306 my $check_vmid_usage = sub {
308 die "can't overwrite running container\n"
309 if PVE
::LXC
::check_running
($vmid);
311 PVE
::Cluster
::check_vmid_unused
($vmid);
316 &$check_vmid_usage(); # final check after locking
319 my $config_fn = PVE
::LXC
::Config-
>config_file($vmid);
321 die "container exists" if !$restore; # just to be sure
322 $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
325 # try to create empty config on local node, we have an flock
326 PVE
::LXC
::Config-
>write_config($vmid, {});
329 # another node was faster, abort
330 die "Could not reserve ID $vmid, already taken\n" if $@;
333 PVE
::Cluster
::check_cfs_quorum
();
337 if ($storage_only_mode) {
339 (undef, $mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
340 die "rootfs configuration could not be recovered, please check and specify manually!\n"
341 if !defined($mp_param->{rootfs
});
342 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
343 my ($ms, $mountpoint) = @_;
344 my $type = $mountpoint->{type
};
345 if ($type eq 'volume') {
346 die "unable to detect disk size - please specify $ms (size)\n"
347 if !defined($mountpoint->{size
});
348 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
349 delete $mountpoint->{size
};
350 $mountpoint->{volume
} = "$storage:$disksize";
351 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
353 my $type = $mountpoint->{type
};
354 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
355 if ($ms eq 'rootfs');
356 die "restoring '$ms' to $type mount is only possible for root\n"
357 if $authuser ne 'root@pam';
359 if ($mountpoint->{backup
}) {
360 warn "WARNING - unsupported configuration!\n";
361 warn "backup was enabled for $type mountpoint $ms ('$mountpoint->{mp}')\n";
362 warn "mountpoint configuration will be restored after archive extraction!\n";
363 warn "contained files will be restored to wrong directory!\n";
365 delete $mp_param->{$ms}; # actually delay bind/dev mps
366 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
370 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
374 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
376 if (defined($old_conf)) {
377 # destroy old container volumes
378 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, {});
382 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
383 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors);
386 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, $authuser ne 'root@pam');
388 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
389 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
390 $lxc_setup->post_create_hook($password, $ssh_keys);
394 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
395 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
398 $conf->{hostname
} ||= "CT$vmid";
399 $conf->{memory
} ||= 512;
400 $conf->{swap
} //= 512;
401 foreach my $mp (keys %$delayed_mp_param) {
402 $conf->{$mp} = $delayed_mp_param->{$mp};
404 PVE
::LXC
::Config-
>write_config($vmid, $conf);
407 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
408 PVE
::LXC
::destroy_config
($vmid);
411 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
414 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
416 &$check_vmid_usage(); # first check before locking
418 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
419 $vmid, $authuser, $realcmd);
423 __PACKAGE__-
>register_method({
428 description
=> "Directory index",
433 additionalProperties
=> 0,
435 node
=> get_standard_option
('pve-node'),
436 vmid
=> get_standard_option
('pve-vmid'),
444 subdir
=> { type
=> 'string' },
447 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
453 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
456 { subdir
=> 'config' },
457 { subdir
=> 'status' },
458 { subdir
=> 'vncproxy' },
459 { subdir
=> 'vncwebsocket' },
460 { subdir
=> 'spiceproxy' },
461 { subdir
=> 'migrate' },
462 { subdir
=> 'clone' },
463 # { subdir => 'initlog' },
465 { subdir
=> 'rrddata' },
466 { subdir
=> 'firewall' },
467 { subdir
=> 'snapshot' },
468 { subdir
=> 'resize' },
475 __PACKAGE__-
>register_method({
477 path
=> '{vmid}/rrd',
479 protected
=> 1, # fixme: can we avoid that?
481 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
483 description
=> "Read VM RRD statistics (returns PNG)",
485 additionalProperties
=> 0,
487 node
=> get_standard_option
('pve-node'),
488 vmid
=> get_standard_option
('pve-vmid'),
490 description
=> "Specify the time frame you are interested in.",
492 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
495 description
=> "The list of datasources you want to display.",
496 type
=> 'string', format
=> 'pve-configid-list',
499 description
=> "The RRD consolidation function",
501 enum
=> [ 'AVERAGE', 'MAX' ],
509 filename
=> { type
=> 'string' },
515 return PVE
::Cluster
::create_rrd_graph
(
516 "pve2-vm/$param->{vmid}", $param->{timeframe
},
517 $param->{ds
}, $param->{cf
});
521 __PACKAGE__-
>register_method({
523 path
=> '{vmid}/rrddata',
525 protected
=> 1, # fixme: can we avoid that?
527 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
529 description
=> "Read VM RRD statistics",
531 additionalProperties
=> 0,
533 node
=> get_standard_option
('pve-node'),
534 vmid
=> get_standard_option
('pve-vmid'),
536 description
=> "Specify the time frame you are interested in.",
538 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
541 description
=> "The RRD consolidation function",
543 enum
=> [ 'AVERAGE', 'MAX' ],
558 return PVE
::Cluster
::create_rrd_data
(
559 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
562 __PACKAGE__-
>register_method({
563 name
=> 'destroy_vm',
568 description
=> "Destroy the container (also delete all uses files).",
570 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
573 additionalProperties
=> 0,
575 node
=> get_standard_option
('pve-node'),
576 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
585 my $rpcenv = PVE
::RPCEnvironment
::get
();
587 my $authuser = $rpcenv->get_user();
589 my $vmid = $param->{vmid
};
591 # test if container exists
592 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
594 my $storage_cfg = cfs_read_file
("storage.cfg");
596 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
598 die "unable to remove CT $vmid - used in HA resources\n"
599 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
601 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
603 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
606 # reload config after lock
607 $conf = PVE
::LXC
::Config-
>load_config($vmid);
608 PVE
::LXC
::Config-
>check_lock($conf);
610 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
612 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
613 PVE
::AccessControl
::remove_vm_access
($vmid);
614 PVE
::Firewall
::remove_vmfw_conf
($vmid);
617 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
619 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
624 __PACKAGE__-
>register_method ({
626 path
=> '{vmid}/vncproxy',
630 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
632 description
=> "Creates a TCP VNC proxy connections.",
634 additionalProperties
=> 0,
636 node
=> get_standard_option
('pve-node'),
637 vmid
=> get_standard_option
('pve-vmid'),
641 description
=> "use websocket instead of standard VNC.",
646 additionalProperties
=> 0,
648 user
=> { type
=> 'string' },
649 ticket
=> { type
=> 'string' },
650 cert
=> { type
=> 'string' },
651 port
=> { type
=> 'integer' },
652 upid
=> { type
=> 'string' },
658 my $rpcenv = PVE
::RPCEnvironment
::get
();
660 my $authuser = $rpcenv->get_user();
662 my $vmid = $param->{vmid
};
663 my $node = $param->{node
};
665 my $authpath = "/vms/$vmid";
667 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
669 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
672 my ($remip, $family);
674 if ($node ne PVE
::INotify
::nodename
()) {
675 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
677 $family = PVE
::Tools
::get_host_address_family
($node);
680 my $port = PVE
::Tools
::next_vnc_port
($family);
682 # NOTE: vncterm VNC traffic is already TLS encrypted,
683 # so we select the fastest chipher here (or 'none'?)
684 my $remcmd = $remip ?
685 ['/usr/bin/ssh', '-t', $remip] : [];
687 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
688 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
690 my $shcmd = [ '/usr/bin/dtach', '-A',
691 "/var/run/dtach/vzctlconsole$vmid",
692 '-r', 'winch', '-z', @$concmd];
697 syslog
('info', "starting lxc vnc proxy $upid\n");
701 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
702 '-timeout', $timeout, '-authpath', $authpath,
703 '-perm', 'VM.Console'];
705 if ($param->{websocket
}) {
706 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
707 push @$cmd, '-notls', '-listen', 'localhost';
710 push @$cmd, '-c', @$remcmd, @$shcmd;
717 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
719 PVE
::Tools
::wait_for_vnc_port
($port);
730 __PACKAGE__-
>register_method({
731 name
=> 'vncwebsocket',
732 path
=> '{vmid}/vncwebsocket',
735 description
=> "You also need to pass a valid ticket (vncticket).",
736 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
738 description
=> "Opens a weksocket for VNC traffic.",
740 additionalProperties
=> 0,
742 node
=> get_standard_option
('pve-node'),
743 vmid
=> get_standard_option
('pve-vmid'),
745 description
=> "Ticket from previous call to vncproxy.",
750 description
=> "Port number returned by previous vncproxy call.",
760 port
=> { type
=> 'string' },
766 my $rpcenv = PVE
::RPCEnvironment
::get
();
768 my $authuser = $rpcenv->get_user();
770 my $authpath = "/vms/$param->{vmid}";
772 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
774 my $port = $param->{port
};
776 return { port
=> $port };
779 __PACKAGE__-
>register_method ({
780 name
=> 'spiceproxy',
781 path
=> '{vmid}/spiceproxy',
786 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
788 description
=> "Returns a SPICE configuration to connect to the CT.",
790 additionalProperties
=> 0,
792 node
=> get_standard_option
('pve-node'),
793 vmid
=> get_standard_option
('pve-vmid'),
794 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
797 returns
=> get_standard_option
('remote-viewer-config'),
801 my $vmid = $param->{vmid
};
802 my $node = $param->{node
};
803 my $proxy = $param->{proxy
};
805 my $authpath = "/vms/$vmid";
806 my $permissions = 'VM.Console';
808 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
810 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
812 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
814 my $shcmd = ['/usr/bin/dtach', '-A',
815 "/var/run/dtach/vzctlconsole$vmid",
816 '-r', 'winch', '-z', @$concmd];
818 my $title = "CT $vmid";
820 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
824 __PACKAGE__-
>register_method({
825 name
=> 'migrate_vm',
826 path
=> '{vmid}/migrate',
830 description
=> "Migrate the container to another node. Creates a new migration task.",
832 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
835 additionalProperties
=> 0,
837 node
=> get_standard_option
('pve-node'),
838 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
839 target
=> get_standard_option
('pve-node', {
840 description
=> "Target node.",
841 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
845 description
=> "Use online/live migration.",
850 description
=> "Force migration despite local bind / device" .
851 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
858 description
=> "the task ID.",
863 my $rpcenv = PVE
::RPCEnvironment
::get
();
865 my $authuser = $rpcenv->get_user();
867 my $target = extract_param
($param, 'target');
869 my $localnode = PVE
::INotify
::nodename
();
870 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
872 PVE
::Cluster
::check_cfs_quorum
();
874 PVE
::Cluster
::check_node_exists
($target);
876 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
878 my $vmid = extract_param
($param, 'vmid');
881 PVE
::LXC
::Config-
>load_config($vmid);
883 # try to detect errors early
884 if (PVE
::LXC
::check_running
($vmid)) {
885 die "can't migrate running container without --online\n"
886 if !$param->{online
};
889 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
894 my $service = "ct:$vmid";
896 my $cmd = ['ha-manager', 'migrate', $service, $target];
898 print "Executing HA migrate for CT $vmid to node $target\n";
900 PVE
::Tools
::run_command
($cmd);
905 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
912 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
917 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
921 __PACKAGE__-
>register_method({
922 name
=> 'vm_feature',
923 path
=> '{vmid}/feature',
927 description
=> "Check if feature for virtual machine is available.",
929 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
932 additionalProperties
=> 0,
934 node
=> get_standard_option
('pve-node'),
935 vmid
=> get_standard_option
('pve-vmid'),
937 description
=> "Feature to check.",
939 enum
=> [ 'snapshot' ],
941 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
949 hasFeature
=> { type
=> 'boolean' },
952 #items => { type => 'string' },
959 my $node = extract_param
($param, 'node');
961 my $vmid = extract_param
($param, 'vmid');
963 my $snapname = extract_param
($param, 'snapname');
965 my $feature = extract_param
($param, 'feature');
967 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
970 my $snap = $conf->{snapshots
}->{$snapname};
971 die "snapshot '$snapname' does not exist\n" if !defined($snap);
974 my $storage_cfg = PVE
::Storage
::config
();
976 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
977 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
980 hasFeature
=> $hasFeature,
981 #nodes => [ keys %$nodelist ],
985 __PACKAGE__-
>register_method({
987 path
=> '{vmid}/template',
991 description
=> "Create a Template.",
993 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
994 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
997 additionalProperties
=> 0,
999 node
=> get_standard_option
('pve-node'),
1000 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1003 description
=> "The template feature is experimental, set this " .
1004 "flag if you know what you are doing.",
1009 returns
=> { type
=> 'null'},
1013 my $rpcenv = PVE
::RPCEnvironment
::get
();
1015 my $authuser = $rpcenv->get_user();
1017 my $node = extract_param
($param, 'node');
1019 my $vmid = extract_param
($param, 'vmid');
1021 my $updatefn = sub {
1023 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1024 PVE
::LXC
::Config-
>check_lock($conf);
1026 die "unable to create template, because CT contains snapshots\n"
1027 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1029 die "you can't convert a template to a template\n"
1030 if PVE
::LXC
::Config-
>is_template($conf);
1032 die "you can't convert a CT to template if the CT is running\n"
1033 if PVE
::LXC
::check_running
($vmid);
1036 PVE
::LXC
::template_create
($vmid, $conf);
1039 $conf->{template
} = 1;
1041 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1042 # and remove lxc config
1043 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1045 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1048 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1053 __PACKAGE__-
>register_method({
1055 path
=> '{vmid}/clone',
1059 description
=> "Create a container clone/copy",
1061 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1062 "and 'VM.Allocate' permissions " .
1063 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1064 "'Datastore.AllocateSpace' on any used storage.",
1067 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1069 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1070 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1075 additionalProperties
=> 0,
1077 node
=> get_standard_option
('pve-node'),
1078 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1079 newid
=> get_standard_option
('pve-vmid', {
1080 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1081 description
=> 'VMID for the clone.' }),
1084 type
=> 'string', format
=> 'dns-name',
1085 description
=> "Set a hostname for the new CT.",
1090 description
=> "Description for the new CT.",
1094 type
=> 'string', format
=> 'pve-poolid',
1095 description
=> "Add the new CT to the specified pool.",
1097 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1100 storage
=> get_standard_option
('pve-storage-id', {
1101 description
=> "Target storage for full clone.",
1108 description
=> "Create a full copy of all disk. This is always done when " .
1109 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1114 description
=> "The clone feature is experimental, set this " .
1115 "flag if you know what you are doing.",
1118 # target => get_standard_option('pve-node', {
1119 # description => "Target node. Only allowed if the original VM is on shared storage.",
1130 my $rpcenv = PVE
::RPCEnvironment
::get
();
1132 my $authuser = $rpcenv->get_user();
1134 my $node = extract_param
($param, 'node');
1136 my $vmid = extract_param
($param, 'vmid');
1138 my $newid = extract_param
($param, 'newid');
1140 my $pool = extract_param
($param, 'pool');
1142 if (defined($pool)) {
1143 $rpcenv->check_pool_exist($pool);
1146 my $snapname = extract_param
($param, 'snapname');
1148 my $storage = extract_param
($param, 'storage');
1150 my $localnode = PVE
::INotify
::nodename
();
1152 my $storecfg = PVE
::Storage
::config
();
1155 # check if storage is enabled on local node
1156 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1159 PVE
::Cluster
::check_cfs_quorum
();
1161 my $running = PVE
::LXC
::check_running
($vmid) || 0;
1165 # do all tests after lock
1166 # we also try to do all tests before we fork the worker
1167 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1169 PVE
::LXC
::Config-
>check_lock($conf);
1171 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1173 die "unexpected state change\n" if $verify_running != $running;
1175 die "snapshot '$snapname' does not exist\n"
1176 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1178 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1180 my $conffile = PVE
::LXC
::Config-
>config_file($newid);
1181 die "unable to create CT $newid: config file already exists\n"
1184 my $newconf = { lock => 'clone' };
1185 my $mountpoints = {};
1189 foreach my $opt (keys %$oldconf) {
1190 my $value = $oldconf->{$opt};
1192 # no need to copy unused images, because VMID(owner) changes anyways
1193 next if $opt =~ m/^unused\d+$/;
1195 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1196 my $mp = $opt eq 'rootfs' ?
1197 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1198 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1200 if ($mp->{type
} eq 'volume') {
1201 my $volid = $mp->{volume
};
1202 if ($param->{full
}) {
1203 die "fixme: full clone not implemented";
1205 die "Full clone feature for '$volid' is not available\n"
1206 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $volid, $snapname, $running);
1207 $fullclone->{$opt} = 1;
1209 # not full means clone instead of copy
1210 die "Linked clone feature for '$volid' is not available\n"
1211 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1214 $mountpoints->{$opt} = $mp;
1215 push @$vollist, $volid;
1218 # TODO: allow bind mounts?
1219 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1223 # copy everything else
1224 $newconf->{$opt} = $value;
1228 delete $newconf->{template
};
1229 if ($param->{hostname
}) {
1230 $newconf->{hostname
} = $param->{hostname
};
1233 if ($param->{description
}) {
1234 $newconf->{description
} = $param->{description
};
1237 # create empty/temp config - this fails if CT already exists on other node
1238 PVE
::Tools
::file_set_contents
($conffile, "# ctclone temporary file\nlock: clone\n");
1243 my $newvollist = [];
1246 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1248 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1250 foreach my $opt (keys %$mountpoints) {
1251 my $mp = $mountpoints->{$opt};
1252 my $volid = $mp->{volume
};
1254 if ($fullclone->{$opt}) {
1255 die "fixme: full clone not implemented\n";
1257 print "create linked clone of mountpoint $opt ($volid)\n";
1258 my $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1259 push @$newvollist, $newvolid;
1260 $mp->{volume
} = $newvolid;
1262 $newconf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs');
1263 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1267 delete $newconf->{lock};
1268 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1270 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1275 sleep 1; # some storage like rbd need to wait before release volume - really?
1277 foreach my $volid (@$newvollist) {
1278 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1281 die "clone failed: $err";
1287 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1289 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1293 return PVE
::LXC
::Config-
>lock_config($vmid, $clonefn);
1297 __PACKAGE__-
>register_method({
1298 name
=> 'resize_vm',
1299 path
=> '{vmid}/resize',
1303 description
=> "Resize a container mountpoint.",
1305 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1308 additionalProperties
=> 0,
1310 node
=> get_standard_option
('pve-node'),
1311 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1314 description
=> "The disk you want to resize.",
1315 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1319 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1320 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.",
1324 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1332 description
=> "the task ID.",
1337 my $rpcenv = PVE
::RPCEnvironment
::get
();
1339 my $authuser = $rpcenv->get_user();
1341 my $node = extract_param
($param, 'node');
1343 my $vmid = extract_param
($param, 'vmid');
1345 my $digest = extract_param
($param, 'digest');
1347 my $sizestr = extract_param
($param, 'size');
1348 my $ext = ($sizestr =~ s/^\+//);
1349 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1350 die "invalid size string" if !defined($newsize);
1352 die "no options specified\n" if !scalar(keys %$param);
1354 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1356 my $storage_cfg = cfs_read_file
("storage.cfg");
1360 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1361 PVE
::LXC
::Config-
>check_lock($conf);
1363 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1365 my $running = PVE
::LXC
::check_running
($vmid);
1367 my $disk = $param->{disk
};
1368 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1369 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1371 my $volid = $mp->{volume
};
1373 my (undef, undef, $owner, undef, undef, undef, $format) =
1374 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1376 die "can't resize mountpoint owned by another container ($owner)"
1379 die "can't resize volume: $disk if snapshot exists\n"
1380 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1382 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1384 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1386 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1388 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1389 $newsize += $size if $ext;
1390 $newsize = int($newsize);
1392 die "unable to shrink disk size\n" if $newsize < $size;
1394 return if $size == $newsize;
1396 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1398 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1399 # we pass 0 here (parameter only makes sense for qemu)
1400 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1402 $mp->{size
} = $newsize;
1403 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1405 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1407 if ($format eq 'raw') {
1408 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1412 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1413 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1414 die "internal error: CT running but mountpoint not attached to a loop device"
1416 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1418 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1419 # to be visible to it in its namespace.
1420 # To not interfere with the rest of the system we unshare the current mount namespace,
1421 # mount over /tmp and then run resize2fs.
1423 # interestingly we don't need to e2fsck on mounted systems...
1424 my $quoted = PVE
::Tools
::shellquote
($path);
1425 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1427 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1429 warn "Failed to update the container's filesystem: $@\n" if $@;
1432 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1433 PVE
::Tools
::run_command
(['resize2fs', $path]);
1435 warn "Failed to update the container's filesystem: $@\n" if $@;
1440 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1443 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;