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');
357 if ($mountpoint->{backup
}) {
358 warn "WARNING - unsupported configuration!\n";
359 warn "backup was enabled for $type mountpoint $ms ('$mountpoint->{mp}')\n";
360 warn "mountpoint configuration will be restored after archive extraction!\n";
361 warn "contained files will be restored to wrong directory!\n";
363 delete $mp_param->{$ms}; # actually delay bind/dev mps
364 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
368 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
372 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
374 if (defined($old_conf)) {
375 # destroy old container volumes
376 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, {});
380 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
381 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors);
384 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf);
386 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
387 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
388 $lxc_setup->post_create_hook($password, $ssh_keys);
392 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
393 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
396 $conf->{hostname
} ||= "CT$vmid";
397 $conf->{memory
} ||= 512;
398 $conf->{swap
} //= 512;
399 foreach my $mp (keys %$delayed_mp_param) {
400 $conf->{$mp} = $delayed_mp_param->{$mp};
402 PVE
::LXC
::Config-
>write_config($vmid, $conf);
405 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
406 PVE
::LXC
::destroy_config
($vmid);
409 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
412 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
414 &$check_vmid_usage(); # first check before locking
416 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
417 $vmid, $authuser, $realcmd);
421 __PACKAGE__-
>register_method({
426 description
=> "Directory index",
431 additionalProperties
=> 0,
433 node
=> get_standard_option
('pve-node'),
434 vmid
=> get_standard_option
('pve-vmid'),
442 subdir
=> { type
=> 'string' },
445 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
451 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
454 { subdir
=> 'config' },
455 { subdir
=> 'status' },
456 { subdir
=> 'vncproxy' },
457 { subdir
=> 'vncwebsocket' },
458 { subdir
=> 'spiceproxy' },
459 { subdir
=> 'migrate' },
460 { subdir
=> 'clone' },
461 # { subdir => 'initlog' },
463 { subdir
=> 'rrddata' },
464 { subdir
=> 'firewall' },
465 { subdir
=> 'snapshot' },
466 { subdir
=> 'resize' },
473 __PACKAGE__-
>register_method({
475 path
=> '{vmid}/rrd',
477 protected
=> 1, # fixme: can we avoid that?
479 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
481 description
=> "Read VM RRD statistics (returns PNG)",
483 additionalProperties
=> 0,
485 node
=> get_standard_option
('pve-node'),
486 vmid
=> get_standard_option
('pve-vmid'),
488 description
=> "Specify the time frame you are interested in.",
490 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
493 description
=> "The list of datasources you want to display.",
494 type
=> 'string', format
=> 'pve-configid-list',
497 description
=> "The RRD consolidation function",
499 enum
=> [ 'AVERAGE', 'MAX' ],
507 filename
=> { type
=> 'string' },
513 return PVE
::Cluster
::create_rrd_graph
(
514 "pve2-vm/$param->{vmid}", $param->{timeframe
},
515 $param->{ds
}, $param->{cf
});
519 __PACKAGE__-
>register_method({
521 path
=> '{vmid}/rrddata',
523 protected
=> 1, # fixme: can we avoid that?
525 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
527 description
=> "Read VM RRD statistics",
529 additionalProperties
=> 0,
531 node
=> get_standard_option
('pve-node'),
532 vmid
=> get_standard_option
('pve-vmid'),
534 description
=> "Specify the time frame you are interested in.",
536 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
539 description
=> "The RRD consolidation function",
541 enum
=> [ 'AVERAGE', 'MAX' ],
556 return PVE
::Cluster
::create_rrd_data
(
557 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
560 __PACKAGE__-
>register_method({
561 name
=> 'destroy_vm',
566 description
=> "Destroy the container (also delete all uses files).",
568 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
571 additionalProperties
=> 0,
573 node
=> get_standard_option
('pve-node'),
574 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
583 my $rpcenv = PVE
::RPCEnvironment
::get
();
585 my $authuser = $rpcenv->get_user();
587 my $vmid = $param->{vmid
};
589 # test if container exists
590 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
592 my $storage_cfg = cfs_read_file
("storage.cfg");
594 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
596 die "unable to remove CT $vmid - used in HA resources\n"
597 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
599 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
601 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
604 # reload config after lock
605 $conf = PVE
::LXC
::Config-
>load_config($vmid);
606 PVE
::LXC
::Config-
>check_lock($conf);
608 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
610 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
611 PVE
::AccessControl
::remove_vm_access
($vmid);
612 PVE
::Firewall
::remove_vmfw_conf
($vmid);
615 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
617 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
622 __PACKAGE__-
>register_method ({
624 path
=> '{vmid}/vncproxy',
628 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
630 description
=> "Creates a TCP VNC proxy connections.",
632 additionalProperties
=> 0,
634 node
=> get_standard_option
('pve-node'),
635 vmid
=> get_standard_option
('pve-vmid'),
639 description
=> "use websocket instead of standard VNC.",
644 additionalProperties
=> 0,
646 user
=> { type
=> 'string' },
647 ticket
=> { type
=> 'string' },
648 cert
=> { type
=> 'string' },
649 port
=> { type
=> 'integer' },
650 upid
=> { type
=> 'string' },
656 my $rpcenv = PVE
::RPCEnvironment
::get
();
658 my $authuser = $rpcenv->get_user();
660 my $vmid = $param->{vmid
};
661 my $node = $param->{node
};
663 my $authpath = "/vms/$vmid";
665 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
667 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
670 my ($remip, $family);
672 if ($node ne PVE
::INotify
::nodename
()) {
673 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
675 $family = PVE
::Tools
::get_host_address_family
($node);
678 my $port = PVE
::Tools
::next_vnc_port
($family);
680 # NOTE: vncterm VNC traffic is already TLS encrypted,
681 # so we select the fastest chipher here (or 'none'?)
682 my $remcmd = $remip ?
683 ['/usr/bin/ssh', '-t', $remip] : [];
685 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
686 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
688 my $shcmd = [ '/usr/bin/dtach', '-A',
689 "/var/run/dtach/vzctlconsole$vmid",
690 '-r', 'winch', '-z', @$concmd];
695 syslog
('info', "starting lxc vnc proxy $upid\n");
699 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
700 '-timeout', $timeout, '-authpath', $authpath,
701 '-perm', 'VM.Console'];
703 if ($param->{websocket
}) {
704 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
705 push @$cmd, '-notls', '-listen', 'localhost';
708 push @$cmd, '-c', @$remcmd, @$shcmd;
715 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
717 PVE
::Tools
::wait_for_vnc_port
($port);
728 __PACKAGE__-
>register_method({
729 name
=> 'vncwebsocket',
730 path
=> '{vmid}/vncwebsocket',
733 description
=> "You also need to pass a valid ticket (vncticket).",
734 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
736 description
=> "Opens a weksocket for VNC traffic.",
738 additionalProperties
=> 0,
740 node
=> get_standard_option
('pve-node'),
741 vmid
=> get_standard_option
('pve-vmid'),
743 description
=> "Ticket from previous call to vncproxy.",
748 description
=> "Port number returned by previous vncproxy call.",
758 port
=> { type
=> 'string' },
764 my $rpcenv = PVE
::RPCEnvironment
::get
();
766 my $authuser = $rpcenv->get_user();
768 my $authpath = "/vms/$param->{vmid}";
770 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
772 my $port = $param->{port
};
774 return { port
=> $port };
777 __PACKAGE__-
>register_method ({
778 name
=> 'spiceproxy',
779 path
=> '{vmid}/spiceproxy',
784 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
786 description
=> "Returns a SPICE configuration to connect to the CT.",
788 additionalProperties
=> 0,
790 node
=> get_standard_option
('pve-node'),
791 vmid
=> get_standard_option
('pve-vmid'),
792 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
795 returns
=> get_standard_option
('remote-viewer-config'),
799 my $vmid = $param->{vmid
};
800 my $node = $param->{node
};
801 my $proxy = $param->{proxy
};
803 my $authpath = "/vms/$vmid";
804 my $permissions = 'VM.Console';
806 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
808 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
810 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
812 my $shcmd = ['/usr/bin/dtach', '-A',
813 "/var/run/dtach/vzctlconsole$vmid",
814 '-r', 'winch', '-z', @$concmd];
816 my $title = "CT $vmid";
818 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
822 __PACKAGE__-
>register_method({
823 name
=> 'migrate_vm',
824 path
=> '{vmid}/migrate',
828 description
=> "Migrate the container to another node. Creates a new migration task.",
830 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
833 additionalProperties
=> 0,
835 node
=> get_standard_option
('pve-node'),
836 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
837 target
=> get_standard_option
('pve-node', {
838 description
=> "Target node.",
839 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
843 description
=> "Use online/live migration.",
848 description
=> "Force migration despite local bind / device" .
849 " mounts. WARNING: identical bind / device mounts need to ".
850 " be available on the target node.",
857 description
=> "the task ID.",
862 my $rpcenv = PVE
::RPCEnvironment
::get
();
864 my $authuser = $rpcenv->get_user();
866 my $target = extract_param
($param, 'target');
868 my $localnode = PVE
::INotify
::nodename
();
869 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
871 PVE
::Cluster
::check_cfs_quorum
();
873 PVE
::Cluster
::check_node_exists
($target);
875 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
877 my $vmid = extract_param
($param, 'vmid');
880 PVE
::LXC
::Config-
>load_config($vmid);
882 # try to detect errors early
883 if (PVE
::LXC
::check_running
($vmid)) {
884 die "can't migrate running container without --online\n"
885 if !$param->{online
};
888 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
893 my $service = "ct:$vmid";
895 my $cmd = ['ha-manager', 'migrate', $service, $target];
897 print "Executing HA migrate for CT $vmid to node $target\n";
899 PVE
::Tools
::run_command
($cmd);
904 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
911 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
916 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
920 __PACKAGE__-
>register_method({
921 name
=> 'vm_feature',
922 path
=> '{vmid}/feature',
926 description
=> "Check if feature for virtual machine is available.",
928 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
931 additionalProperties
=> 0,
933 node
=> get_standard_option
('pve-node'),
934 vmid
=> get_standard_option
('pve-vmid'),
936 description
=> "Feature to check.",
938 enum
=> [ 'snapshot' ],
940 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
948 hasFeature
=> { type
=> 'boolean' },
951 #items => { type => 'string' },
958 my $node = extract_param
($param, 'node');
960 my $vmid = extract_param
($param, 'vmid');
962 my $snapname = extract_param
($param, 'snapname');
964 my $feature = extract_param
($param, 'feature');
966 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
969 my $snap = $conf->{snapshots
}->{$snapname};
970 die "snapshot '$snapname' does not exist\n" if !defined($snap);
973 my $storage_cfg = PVE
::Storage
::config
();
975 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
976 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
979 hasFeature
=> $hasFeature,
980 #nodes => [ keys %$nodelist ],
984 __PACKAGE__-
>register_method({
986 path
=> '{vmid}/template',
990 description
=> "Create a Template.",
992 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
993 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
996 additionalProperties
=> 0,
998 node
=> get_standard_option
('pve-node'),
999 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1002 description
=> "The template feature is experimental, set this " .
1003 "flag if you know what you are doing.",
1008 returns
=> { type
=> 'null'},
1012 my $rpcenv = PVE
::RPCEnvironment
::get
();
1014 my $authuser = $rpcenv->get_user();
1016 my $node = extract_param
($param, 'node');
1018 my $vmid = extract_param
($param, 'vmid');
1020 my $updatefn = sub {
1022 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1023 PVE
::LXC
::Config-
>check_lock($conf);
1025 die "unable to create template, because CT contains snapshots\n"
1026 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1028 die "you can't convert a template to a template\n"
1029 if PVE
::LXC
::Config-
>is_template($conf);
1031 die "you can't convert a CT to template if the CT is running\n"
1032 if PVE
::LXC
::check_running
($vmid);
1035 PVE
::LXC
::template_create
($vmid, $conf);
1038 $conf->{template
} = 1;
1040 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1041 # and remove lxc config
1042 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1044 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1047 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1052 __PACKAGE__-
>register_method({
1054 path
=> '{vmid}/clone',
1058 description
=> "Create a container clone/copy",
1060 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1061 "and 'VM.Allocate' permissions " .
1062 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1063 "'Datastore.AllocateSpace' on any used storage.",
1066 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1068 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1069 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1074 additionalProperties
=> 0,
1076 node
=> get_standard_option
('pve-node'),
1077 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1078 newid
=> get_standard_option
('pve-vmid', {
1079 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1080 description
=> 'VMID for the clone.' }),
1083 type
=> 'string', format
=> 'dns-name',
1084 description
=> "Set a hostname for the new CT.",
1089 description
=> "Description for the new CT.",
1093 type
=> 'string', format
=> 'pve-poolid',
1094 description
=> "Add the new CT to the specified pool.",
1096 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1099 storage
=> get_standard_option
('pve-storage-id', {
1100 description
=> "Target storage for full clone.",
1107 description
=> "Create a full copy of all disk. This is always done when " .
1108 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1113 description
=> "The clone feature is experimental, set this " .
1114 "flag if you know what you are doing.",
1117 # target => get_standard_option('pve-node', {
1118 # description => "Target node. Only allowed if the original VM is on shared storage.",
1129 my $rpcenv = PVE
::RPCEnvironment
::get
();
1131 my $authuser = $rpcenv->get_user();
1133 my $node = extract_param
($param, 'node');
1135 my $vmid = extract_param
($param, 'vmid');
1137 my $newid = extract_param
($param, 'newid');
1139 my $pool = extract_param
($param, 'pool');
1141 if (defined($pool)) {
1142 $rpcenv->check_pool_exist($pool);
1145 my $snapname = extract_param
($param, 'snapname');
1147 my $storage = extract_param
($param, 'storage');
1149 my $localnode = PVE
::INotify
::nodename
();
1151 my $storecfg = PVE
::Storage
::config
();
1154 # check if storage is enabled on local node
1155 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1158 PVE
::Cluster
::check_cfs_quorum
();
1160 my $running = PVE
::LXC
::check_running
($vmid) || 0;
1164 # do all tests after lock
1165 # we also try to do all tests before we fork the worker
1166 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1168 PVE
::LXC
::Config-
>check_lock($conf);
1170 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1172 die "unexpected state change\n" if $verify_running != $running;
1174 die "snapshot '$snapname' does not exist\n"
1175 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1177 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1179 my $conffile = PVE
::LXC
::Config-
>config_file($newid);
1180 die "unable to create CT $newid: config file already exists\n"
1183 my $newconf = { lock => 'clone' };
1184 my $mountpoints = {};
1188 foreach my $opt (keys %$oldconf) {
1189 my $value = $oldconf->{$opt};
1191 # no need to copy unused images, because VMID(owner) changes anyways
1192 next if $opt =~ m/^unused\d+$/;
1194 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1195 my $mp = $opt eq 'rootfs' ?
1196 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1197 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1199 if ($mp->{type
} eq 'volume') {
1200 my $volid = $mp->{volume
};
1201 if ($param->{full
}) {
1202 die "fixme: full clone not implemented";
1204 die "Full clone feature for '$volid' is not available\n"
1205 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $volid, $snapname, $running);
1206 $fullclone->{$opt} = 1;
1208 # not full means clone instead of copy
1209 die "Linked clone feature for '$volid' is not available\n"
1210 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1213 $mountpoints->{$opt} = $mp;
1214 push @$vollist, $volid;
1217 # TODO: allow bind mounts?
1218 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1222 # copy everything else
1223 $newconf->{$opt} = $value;
1227 delete $newconf->{template
};
1228 if ($param->{hostname
}) {
1229 $newconf->{hostname
} = $param->{hostname
};
1232 if ($param->{description
}) {
1233 $newconf->{description
} = $param->{description
};
1236 # create empty/temp config - this fails if CT already exists on other node
1237 PVE
::Tools
::file_set_contents
($conffile, "# ctclone temporary file\nlock: clone\n");
1242 my $newvollist = [];
1245 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1247 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1249 foreach my $opt (keys %$mountpoints) {
1250 my $mp = $mountpoints->{$opt};
1251 my $volid = $mp->{volume
};
1253 if ($fullclone->{$opt}) {
1254 die "fixme: full clone not implemented\n";
1256 print "create linked clone of mountpoint $opt ($volid)\n";
1257 my $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1258 push @$newvollist, $newvolid;
1259 $mp->{volume
} = $newvolid;
1261 $newconf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs');
1262 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1266 delete $newconf->{lock};
1267 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1269 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1274 sleep 1; # some storage like rbd need to wait before release volume - really?
1276 foreach my $volid (@$newvollist) {
1277 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1280 die "clone failed: $err";
1286 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1288 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1292 return PVE
::LXC
::Config-
>lock_config($vmid, $clonefn);
1296 __PACKAGE__-
>register_method({
1297 name
=> 'resize_vm',
1298 path
=> '{vmid}/resize',
1302 description
=> "Resize a container mountpoint.",
1304 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1307 additionalProperties
=> 0,
1309 node
=> get_standard_option
('pve-node'),
1310 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1313 description
=> "The disk you want to resize.",
1314 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1318 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1319 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.",
1323 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1331 description
=> "the task ID.",
1336 my $rpcenv = PVE
::RPCEnvironment
::get
();
1338 my $authuser = $rpcenv->get_user();
1340 my $node = extract_param
($param, 'node');
1342 my $vmid = extract_param
($param, 'vmid');
1344 my $digest = extract_param
($param, 'digest');
1346 my $sizestr = extract_param
($param, 'size');
1347 my $ext = ($sizestr =~ s/^\+//);
1348 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1349 die "invalid size string" if !defined($newsize);
1351 die "no options specified\n" if !scalar(keys %$param);
1353 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1355 my $storage_cfg = cfs_read_file
("storage.cfg");
1359 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1360 PVE
::LXC
::Config-
>check_lock($conf);
1362 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1364 my $running = PVE
::LXC
::check_running
($vmid);
1366 my $disk = $param->{disk
};
1367 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1368 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1370 my $volid = $mp->{volume
};
1372 my (undef, undef, $owner, undef, undef, undef, $format) =
1373 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1375 die "can't resize mountpoint owned by another container ($owner)"
1378 die "can't resize volume: $disk if snapshot exists\n"
1379 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1381 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1383 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1385 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1387 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1388 $newsize += $size if $ext;
1389 $newsize = int($newsize);
1391 die "unable to shrink disk size\n" if $newsize < $size;
1393 return if $size == $newsize;
1395 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1397 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1398 # we pass 0 here (parameter only makes sense for qemu)
1399 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1401 $mp->{size
} = $newsize;
1402 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1404 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1406 if ($format eq 'raw') {
1407 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1411 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1412 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1413 die "internal error: CT running but mountpoint not attached to a loop device"
1415 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1417 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1418 # to be visible to it in its namespace.
1419 # To not interfere with the rest of the system we unshare the current mount namespace,
1420 # mount over /tmp and then run resize2fs.
1422 # interestingly we don't need to e2fsck on mounted systems...
1423 my $quoted = PVE
::Tools
::shellquote
($path);
1424 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1426 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1428 warn "Failed to update the container's filesystem: $@\n" if $@;
1431 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1432 PVE
::Tools
::run_command
(['resize2fs', $path]);
1434 warn "Failed to update the container's filesystem: $@\n" if $@;
1439 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1442 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;