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. WARNING: identical bind / device mounts need to ".
852 " be available on the target node.",
859 description
=> "the task ID.",
864 my $rpcenv = PVE
::RPCEnvironment
::get
();
866 my $authuser = $rpcenv->get_user();
868 my $target = extract_param
($param, 'target');
870 my $localnode = PVE
::INotify
::nodename
();
871 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
873 PVE
::Cluster
::check_cfs_quorum
();
875 PVE
::Cluster
::check_node_exists
($target);
877 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
879 my $vmid = extract_param
($param, 'vmid');
882 PVE
::LXC
::Config-
>load_config($vmid);
884 # try to detect errors early
885 if (PVE
::LXC
::check_running
($vmid)) {
886 die "can't migrate running container without --online\n"
887 if !$param->{online
};
890 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
895 my $service = "ct:$vmid";
897 my $cmd = ['ha-manager', 'migrate', $service, $target];
899 print "Executing HA migrate for CT $vmid to node $target\n";
901 PVE
::Tools
::run_command
($cmd);
906 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
913 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
918 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
922 __PACKAGE__-
>register_method({
923 name
=> 'vm_feature',
924 path
=> '{vmid}/feature',
928 description
=> "Check if feature for virtual machine is available.",
930 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
933 additionalProperties
=> 0,
935 node
=> get_standard_option
('pve-node'),
936 vmid
=> get_standard_option
('pve-vmid'),
938 description
=> "Feature to check.",
940 enum
=> [ 'snapshot' ],
942 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
950 hasFeature
=> { type
=> 'boolean' },
953 #items => { type => 'string' },
960 my $node = extract_param
($param, 'node');
962 my $vmid = extract_param
($param, 'vmid');
964 my $snapname = extract_param
($param, 'snapname');
966 my $feature = extract_param
($param, 'feature');
968 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
971 my $snap = $conf->{snapshots
}->{$snapname};
972 die "snapshot '$snapname' does not exist\n" if !defined($snap);
975 my $storage_cfg = PVE
::Storage
::config
();
977 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
978 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
981 hasFeature
=> $hasFeature,
982 #nodes => [ keys %$nodelist ],
986 __PACKAGE__-
>register_method({
988 path
=> '{vmid}/template',
992 description
=> "Create a Template.",
994 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
995 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
998 additionalProperties
=> 0,
1000 node
=> get_standard_option
('pve-node'),
1001 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1004 description
=> "The template feature is experimental, set this " .
1005 "flag if you know what you are doing.",
1010 returns
=> { type
=> 'null'},
1014 my $rpcenv = PVE
::RPCEnvironment
::get
();
1016 my $authuser = $rpcenv->get_user();
1018 my $node = extract_param
($param, 'node');
1020 my $vmid = extract_param
($param, 'vmid');
1022 my $updatefn = sub {
1024 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1025 PVE
::LXC
::Config-
>check_lock($conf);
1027 die "unable to create template, because CT contains snapshots\n"
1028 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1030 die "you can't convert a template to a template\n"
1031 if PVE
::LXC
::Config-
>is_template($conf);
1033 die "you can't convert a CT to template if the CT is running\n"
1034 if PVE
::LXC
::check_running
($vmid);
1037 PVE
::LXC
::template_create
($vmid, $conf);
1040 $conf->{template
} = 1;
1042 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1043 # and remove lxc config
1044 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1046 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1049 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1054 __PACKAGE__-
>register_method({
1056 path
=> '{vmid}/clone',
1060 description
=> "Create a container clone/copy",
1062 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1063 "and 'VM.Allocate' permissions " .
1064 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1065 "'Datastore.AllocateSpace' on any used storage.",
1068 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1070 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1071 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1076 additionalProperties
=> 0,
1078 node
=> get_standard_option
('pve-node'),
1079 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1080 newid
=> get_standard_option
('pve-vmid', {
1081 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1082 description
=> 'VMID for the clone.' }),
1085 type
=> 'string', format
=> 'dns-name',
1086 description
=> "Set a hostname for the new CT.",
1091 description
=> "Description for the new CT.",
1095 type
=> 'string', format
=> 'pve-poolid',
1096 description
=> "Add the new CT to the specified pool.",
1098 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1101 storage
=> get_standard_option
('pve-storage-id', {
1102 description
=> "Target storage for full clone.",
1109 description
=> "Create a full copy of all disk. This is always done when " .
1110 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1115 description
=> "The clone feature is experimental, set this " .
1116 "flag if you know what you are doing.",
1119 # target => get_standard_option('pve-node', {
1120 # description => "Target node. Only allowed if the original VM is on shared storage.",
1131 my $rpcenv = PVE
::RPCEnvironment
::get
();
1133 my $authuser = $rpcenv->get_user();
1135 my $node = extract_param
($param, 'node');
1137 my $vmid = extract_param
($param, 'vmid');
1139 my $newid = extract_param
($param, 'newid');
1141 my $pool = extract_param
($param, 'pool');
1143 if (defined($pool)) {
1144 $rpcenv->check_pool_exist($pool);
1147 my $snapname = extract_param
($param, 'snapname');
1149 my $storage = extract_param
($param, 'storage');
1151 my $localnode = PVE
::INotify
::nodename
();
1153 my $storecfg = PVE
::Storage
::config
();
1156 # check if storage is enabled on local node
1157 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1160 PVE
::Cluster
::check_cfs_quorum
();
1162 my $running = PVE
::LXC
::check_running
($vmid) || 0;
1166 # do all tests after lock
1167 # we also try to do all tests before we fork the worker
1168 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1170 PVE
::LXC
::Config-
>check_lock($conf);
1172 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1174 die "unexpected state change\n" if $verify_running != $running;
1176 die "snapshot '$snapname' does not exist\n"
1177 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1179 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1181 my $conffile = PVE
::LXC
::Config-
>config_file($newid);
1182 die "unable to create CT $newid: config file already exists\n"
1185 my $newconf = { lock => 'clone' };
1186 my $mountpoints = {};
1190 foreach my $opt (keys %$oldconf) {
1191 my $value = $oldconf->{$opt};
1193 # no need to copy unused images, because VMID(owner) changes anyways
1194 next if $opt =~ m/^unused\d+$/;
1196 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1197 my $mp = $opt eq 'rootfs' ?
1198 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1199 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1201 if ($mp->{type
} eq 'volume') {
1202 my $volid = $mp->{volume
};
1203 if ($param->{full
}) {
1204 die "fixme: full clone not implemented";
1206 die "Full clone feature for '$volid' is not available\n"
1207 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $volid, $snapname, $running);
1208 $fullclone->{$opt} = 1;
1210 # not full means clone instead of copy
1211 die "Linked clone feature for '$volid' is not available\n"
1212 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1215 $mountpoints->{$opt} = $mp;
1216 push @$vollist, $volid;
1219 # TODO: allow bind mounts?
1220 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1224 # copy everything else
1225 $newconf->{$opt} = $value;
1229 delete $newconf->{template
};
1230 if ($param->{hostname
}) {
1231 $newconf->{hostname
} = $param->{hostname
};
1234 if ($param->{description
}) {
1235 $newconf->{description
} = $param->{description
};
1238 # create empty/temp config - this fails if CT already exists on other node
1239 PVE
::Tools
::file_set_contents
($conffile, "# ctclone temporary file\nlock: clone\n");
1244 my $newvollist = [];
1247 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1249 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1251 foreach my $opt (keys %$mountpoints) {
1252 my $mp = $mountpoints->{$opt};
1253 my $volid = $mp->{volume
};
1255 if ($fullclone->{$opt}) {
1256 die "fixme: full clone not implemented\n";
1258 print "create linked clone of mountpoint $opt ($volid)\n";
1259 my $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1260 push @$newvollist, $newvolid;
1261 $mp->{volume
} = $newvolid;
1263 $newconf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs');
1264 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1268 delete $newconf->{lock};
1269 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1271 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1276 sleep 1; # some storage like rbd need to wait before release volume - really?
1278 foreach my $volid (@$newvollist) {
1279 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1282 die "clone failed: $err";
1288 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1290 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1294 return PVE
::LXC
::Config-
>lock_config($vmid, $clonefn);
1298 __PACKAGE__-
>register_method({
1299 name
=> 'resize_vm',
1300 path
=> '{vmid}/resize',
1304 description
=> "Resize a container mountpoint.",
1306 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1309 additionalProperties
=> 0,
1311 node
=> get_standard_option
('pve-node'),
1312 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1315 description
=> "The disk you want to resize.",
1316 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1320 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1321 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.",
1325 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1333 description
=> "the task ID.",
1338 my $rpcenv = PVE
::RPCEnvironment
::get
();
1340 my $authuser = $rpcenv->get_user();
1342 my $node = extract_param
($param, 'node');
1344 my $vmid = extract_param
($param, 'vmid');
1346 my $digest = extract_param
($param, 'digest');
1348 my $sizestr = extract_param
($param, 'size');
1349 my $ext = ($sizestr =~ s/^\+//);
1350 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1351 die "invalid size string" if !defined($newsize);
1353 die "no options specified\n" if !scalar(keys %$param);
1355 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1357 my $storage_cfg = cfs_read_file
("storage.cfg");
1361 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1362 PVE
::LXC
::Config-
>check_lock($conf);
1364 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1366 my $running = PVE
::LXC
::check_running
($vmid);
1368 my $disk = $param->{disk
};
1369 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1370 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1372 my $volid = $mp->{volume
};
1374 my (undef, undef, $owner, undef, undef, undef, $format) =
1375 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1377 die "can't resize mountpoint owned by another container ($owner)"
1380 die "can't resize volume: $disk if snapshot exists\n"
1381 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1383 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1385 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1387 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1389 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1390 $newsize += $size if $ext;
1391 $newsize = int($newsize);
1393 die "unable to shrink disk size\n" if $newsize < $size;
1395 return if $size == $newsize;
1397 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1399 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1400 # we pass 0 here (parameter only makes sense for qemu)
1401 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1403 $mp->{size
} = $newsize;
1404 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1406 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1408 if ($format eq 'raw') {
1409 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1413 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1414 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1415 die "internal error: CT running but mountpoint not attached to a loop device"
1417 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1419 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1420 # to be visible to it in its namespace.
1421 # To not interfere with the rest of the system we unshare the current mount namespace,
1422 # mount over /tmp and then run resize2fs.
1424 # interestingly we don't need to e2fsck on mounted systems...
1425 my $quoted = PVE
::Tools
::shellquote
($path);
1426 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1428 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1430 warn "Failed to update the container's filesystem: $@\n" if $@;
1433 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1434 PVE
::Tools
::run_command
(['resize2fs', $path]);
1436 warn "Failed to update the container's filesystem: $@\n" if $@;
1441 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1444 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;