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
;
23 use PVE
::JSONSchema
qw(get_standard_option);
24 use base
qw(PVE::RESTHandler);
26 use Data
::Dumper
; # fixme: remove
28 __PACKAGE__-
>register_method ({
29 subclass
=> "PVE::API2::LXC::Config",
30 path
=> '{vmid}/config',
33 __PACKAGE__-
>register_method ({
34 subclass
=> "PVE::API2::LXC::Status",
35 path
=> '{vmid}/status',
38 __PACKAGE__-
>register_method ({
39 subclass
=> "PVE::API2::LXC::Snapshot",
40 path
=> '{vmid}/snapshot',
43 __PACKAGE__-
>register_method ({
44 subclass
=> "PVE::API2::Firewall::CT",
45 path
=> '{vmid}/firewall',
48 my $destroy_disks = sub {
49 my ($storecfg, $vollist) = @_;
51 foreach my $volid (@$vollist) {
52 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
60 PVE
::Tools
::run_command
(['mkfs.ext4', '-O', 'mmp', $dev]);
64 my ($storage_cfg, $volid) = @_;
66 if ($volid =~ m!^/dev/.+!) {
71 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
73 die "cannot format volume '$volid' with no storage\n" if !$storage;
75 my $path = PVE
::Storage
::path
($storage_cfg, $volid);
77 my ($vtype, undef, undef, undef, undef, $isBase, $format) =
78 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
80 die "cannot format volume '$volid' (format == $format)\n"
86 my $create_disks = sub {
87 my ($storecfg, $vmid, $settings, $conf) = @_;
92 PVE
::LXC
::foreach_mountpoint
($settings, sub {
93 my ($ms, $mountpoint) = @_;
95 my $volid = $mountpoint->{volume
};
96 my $mp = $mountpoint->{mp
};
98 my ($storage, $volname) = PVE
::Storage
::parse_volume_id
($volid, 1);
102 if ($volid =~ m/^([^:\s]+):(\d+(\.\d+)?)$/) {
103 my ($storeid, $size) = ($1, $2);
105 $size = int($size*1024) * 1024;
107 my $scfg = PVE
::Storage
::storage_config
($storecfg, $storage);
108 # fixme: use better naming ct-$vmid-disk-X.raw?
110 if ($scfg->{type
} eq 'dir' || $scfg->{type
} eq 'nfs') {
112 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw',
114 format_disk
($storecfg, $volid);
116 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
119 } elsif ($scfg->{type
} eq 'zfspool') {
121 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'subvol',
123 } elsif ($scfg->{type
} eq 'drbd') {
125 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size);
126 format_disk
($storecfg, $volid);
128 } elsif ($scfg->{type
} eq 'rbd') {
130 die "krbd option must be enabled on storage type '$scfg->{type}'\n" if !$scfg->{krbd
};
131 $volid = PVE
::Storage
::vdisk_alloc
($storecfg, $storage, $vmid, 'raw', undef, $size);
132 format_disk
($storecfg, $volid);
134 die "unable to create containers on storage type '$scfg->{type}'\n";
136 push @$vollist, $volid;
137 $conf->{$ms} = PVE
::LXC
::print_ct_mountpoint
({volume
=> $volid, size
=> $size, mp
=> $mp });
139 # use specified/existing volid
143 # free allocated images on error
145 syslog
('err', "VM $vmid creating disks failed");
146 &$destroy_disks($storecfg, $vollist);
152 __PACKAGE__-
>register_method({
156 description
=> "LXC container index (per node).",
158 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
162 protected
=> 1, # /proc files are only readable by root
164 additionalProperties
=> 0,
166 node
=> get_standard_option
('pve-node'),
175 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
180 my $rpcenv = PVE
::RPCEnvironment
::get
();
181 my $authuser = $rpcenv->get_user();
183 my $vmstatus = PVE
::LXC
::vmstatus
();
186 foreach my $vmid (keys %$vmstatus) {
187 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
189 my $data = $vmstatus->{$vmid};
190 $data->{vmid
} = $vmid;
198 __PACKAGE__-
>register_method({
202 description
=> "Create or restore a container.",
204 user
=> 'all', # check inside
205 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
206 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
207 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
212 additionalProperties
=> 0,
213 properties
=> PVE
::LXC
::json_config_properties
({
214 node
=> get_standard_option
('pve-node'),
215 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
217 description
=> "The OS template or backup file.",
220 completion
=> \
&PVE
::LXC
::complete_os_templates
,
225 description
=> "Sets root password inside container.",
228 storage
=> get_standard_option
('pve-storage-id', {
229 description
=> "Default Storage.",
236 description
=> "Allow to overwrite existing container.",
241 description
=> "Mark this as restore task.",
245 type
=> 'string', format
=> 'pve-poolid',
246 description
=> "Add the VM to the specified pool.",
256 my $rpcenv = PVE
::RPCEnvironment
::get
();
258 my $authuser = $rpcenv->get_user();
260 my $node = extract_param
($param, 'node');
262 my $vmid = extract_param
($param, 'vmid');
264 my $basecfg_fn = PVE
::LXC
::config_file
($vmid);
266 my $same_container_exists = -f
$basecfg_fn;
268 my $restore = extract_param
($param, 'restore');
271 # fixme: limit allowed parameters
275 my $force = extract_param
($param, 'force');
277 if (!($same_container_exists && $restore && $force)) {
278 PVE
::Cluster
::check_vmid_unused
($vmid);
281 my $password = extract_param
($param, 'password');
283 my $storage = extract_param
($param, 'storage') // 'local';
285 my $storage_cfg = cfs_read_file
("storage.cfg");
287 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $storage, $node);
289 raise_param_exc
({ storage
=> "storage '$storage' does not support container root directories"})
290 if !($scfg->{content
}->{images
} || $scfg->{content
}->{rootdir
});
292 my $pool = extract_param
($param, 'pool');
294 if (defined($pool)) {
295 $rpcenv->check_pool_exist($pool);
296 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
299 $rpcenv->check($authuser, "/storage/$storage", ['Datastore.AllocateSpace']);
301 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
303 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
305 } elsif ($restore && $force && $same_container_exists &&
306 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
307 # OK: user has VM.Backup permissions, and want to restore an existing VM
312 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
314 PVE
::Storage
::activate_storage
($storage_cfg, $storage);
316 my $ostemplate = extract_param
($param, 'ostemplate');
320 if ($ostemplate eq '-') {
321 die "pipe requires cli environment\n"
322 if $rpcenv->{type
} ne 'cli';
323 die "pipe can only be used with restore tasks\n"
326 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
328 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
329 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
334 my $no_disk_param = {};
335 foreach my $opt (keys %$param) {
336 my $value = $param->{$opt};
337 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
338 # allow to use simple numbers (add default storage in that case)
339 $param->{$opt} = "$storage:$value" if $value =~ m/^\d+(\.\d+)?$/;
341 $no_disk_param->{$opt} = $value;
344 PVE
::LXC
::update_pct_config
($vmid, $conf, 0, $no_disk_param);
346 my $check_vmid_usage = sub {
348 die "can't overwrite running container\n"
349 if PVE
::LXC
::check_running
($vmid);
351 PVE
::Cluster
::check_vmid_unused
($vmid);
356 &$check_vmid_usage(); # final check after locking
358 PVE
::Cluster
::check_cfs_quorum
();
362 if (!defined($param->{rootfs
})) {
364 my (undef, $disksize) = PVE
::LXC
::Create
::recover_config
($archive);
365 $disksize /= 1024 * 1024; # create_disks expects GB as unit size
366 die "unable to detect disk size - please specify rootfs (size)\n"
368 $param->{rootfs
} = "$storage:$disksize";
370 $param->{rootfs
} = "$storage:4"; # defaults to 4GB
374 $vollist = &$create_disks($storage_cfg, $vmid, $param, $conf);
376 PVE
::LXC
::Create
::create_rootfs
($storage_cfg, $vmid, $conf, $archive, $password, $restore);
378 $conf->{hostname
} ||= "CT$vmid";
379 $conf->{memory
} ||= 512;
380 $conf->{swap
} //= 512;
381 PVE
::LXC
::create_config
($vmid, $conf);
384 &$destroy_disks($storage_cfg, $vollist);
385 PVE
::LXC
::destroy_config
($vmid);
388 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
391 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
393 &$check_vmid_usage(); # first check before locking
395 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
396 $vmid, $authuser, $realcmd);
400 __PACKAGE__-
>register_method({
405 description
=> "Directory index",
410 additionalProperties
=> 0,
412 node
=> get_standard_option
('pve-node'),
413 vmid
=> get_standard_option
('pve-vmid'),
421 subdir
=> { type
=> 'string' },
424 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
430 my $conf = PVE
::LXC
::load_config
($param->{vmid
});
433 { subdir
=> 'config' },
434 { subdir
=> 'status' },
435 { subdir
=> 'vncproxy' },
436 { subdir
=> 'vncwebsocket' },
437 { subdir
=> 'spiceproxy' },
438 { subdir
=> 'migrate' },
439 # { subdir => 'initlog' },
441 { subdir
=> 'rrddata' },
442 { subdir
=> 'firewall' },
443 { subdir
=> 'snapshot' },
449 __PACKAGE__-
>register_method({
451 path
=> '{vmid}/rrd',
453 protected
=> 1, # fixme: can we avoid that?
455 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
457 description
=> "Read VM RRD statistics (returns PNG)",
459 additionalProperties
=> 0,
461 node
=> get_standard_option
('pve-node'),
462 vmid
=> get_standard_option
('pve-vmid'),
464 description
=> "Specify the time frame you are interested in.",
466 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
469 description
=> "The list of datasources you want to display.",
470 type
=> 'string', format
=> 'pve-configid-list',
473 description
=> "The RRD consolidation function",
475 enum
=> [ 'AVERAGE', 'MAX' ],
483 filename
=> { type
=> 'string' },
489 return PVE
::Cluster
::create_rrd_graph
(
490 "pve2-vm/$param->{vmid}", $param->{timeframe
},
491 $param->{ds
}, $param->{cf
});
495 __PACKAGE__-
>register_method({
497 path
=> '{vmid}/rrddata',
499 protected
=> 1, # fixme: can we avoid that?
501 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
503 description
=> "Read VM RRD statistics",
505 additionalProperties
=> 0,
507 node
=> get_standard_option
('pve-node'),
508 vmid
=> get_standard_option
('pve-vmid'),
510 description
=> "Specify the time frame you are interested in.",
512 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
515 description
=> "The RRD consolidation function",
517 enum
=> [ 'AVERAGE', 'MAX' ],
532 return PVE
::Cluster
::create_rrd_data
(
533 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
536 __PACKAGE__-
>register_method({
537 name
=> 'destroy_vm',
542 description
=> "Destroy the container (also delete all uses files).",
544 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
547 additionalProperties
=> 0,
549 node
=> get_standard_option
('pve-node'),
550 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
559 my $rpcenv = PVE
::RPCEnvironment
::get
();
561 my $authuser = $rpcenv->get_user();
563 my $vmid = $param->{vmid
};
565 # test if container exists
566 my $conf = PVE
::LXC
::load_config
($vmid);
568 my $storage_cfg = cfs_read_file
("storage.cfg");
570 die "unable to remove CT $vmid - used in HA resources\n"
571 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
574 # reload config after lock
575 $conf = PVE
::LXC
::load_config
($vmid);
576 PVE
::LXC
::check_lock
($conf);
578 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
579 PVE
::AccessControl
::remove_vm_access
($vmid);
580 PVE
::Firewall
::remove_vmfw_conf
($vmid);
583 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
585 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
590 __PACKAGE__-
>register_method ({
592 path
=> '{vmid}/vncproxy',
596 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
598 description
=> "Creates a TCP VNC proxy connections.",
600 additionalProperties
=> 0,
602 node
=> get_standard_option
('pve-node'),
603 vmid
=> get_standard_option
('pve-vmid'),
607 description
=> "use websocket instead of standard VNC.",
612 additionalProperties
=> 0,
614 user
=> { type
=> 'string' },
615 ticket
=> { type
=> 'string' },
616 cert
=> { type
=> 'string' },
617 port
=> { type
=> 'integer' },
618 upid
=> { type
=> 'string' },
624 my $rpcenv = PVE
::RPCEnvironment
::get
();
626 my $authuser = $rpcenv->get_user();
628 my $vmid = $param->{vmid
};
629 my $node = $param->{node
};
631 my $authpath = "/vms/$vmid";
633 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
635 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
638 my ($remip, $family);
640 if ($node ne PVE
::INotify
::nodename
()) {
641 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
643 $family = PVE
::Tools
::get_host_address_family
($node);
646 my $port = PVE
::Tools
::next_vnc_port
($family);
648 # NOTE: vncterm VNC traffic is already TLS encrypted,
649 # so we select the fastest chipher here (or 'none'?)
650 my $remcmd = $remip ?
651 ['/usr/bin/ssh', '-t', $remip] : [];
653 my $conf = PVE
::LXC
::load_config
($vmid, $node);
654 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
656 my $shcmd = [ '/usr/bin/dtach', '-A',
657 "/var/run/dtach/vzctlconsole$vmid",
658 '-r', 'winch', '-z', @$concmd];
663 syslog
('info', "starting lxc vnc proxy $upid\n");
667 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
668 '-timeout', $timeout, '-authpath', $authpath,
669 '-perm', 'VM.Console'];
671 if ($param->{websocket
}) {
672 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
673 push @$cmd, '-notls', '-listen', 'localhost';
676 push @$cmd, '-c', @$remcmd, @$shcmd;
683 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
685 PVE
::Tools
::wait_for_vnc_port
($port);
696 __PACKAGE__-
>register_method({
697 name
=> 'vncwebsocket',
698 path
=> '{vmid}/vncwebsocket',
701 description
=> "You also need to pass a valid ticket (vncticket).",
702 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
704 description
=> "Opens a weksocket for VNC traffic.",
706 additionalProperties
=> 0,
708 node
=> get_standard_option
('pve-node'),
709 vmid
=> get_standard_option
('pve-vmid'),
711 description
=> "Ticket from previous call to vncproxy.",
716 description
=> "Port number returned by previous vncproxy call.",
726 port
=> { type
=> 'string' },
732 my $rpcenv = PVE
::RPCEnvironment
::get
();
734 my $authuser = $rpcenv->get_user();
736 my $authpath = "/vms/$param->{vmid}";
738 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
740 my $port = $param->{port
};
742 return { port
=> $port };
745 __PACKAGE__-
>register_method ({
746 name
=> 'spiceproxy',
747 path
=> '{vmid}/spiceproxy',
752 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
754 description
=> "Returns a SPICE configuration to connect to the CT.",
756 additionalProperties
=> 0,
758 node
=> get_standard_option
('pve-node'),
759 vmid
=> get_standard_option
('pve-vmid'),
760 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
763 returns
=> get_standard_option
('remote-viewer-config'),
767 my $vmid = $param->{vmid
};
768 my $node = $param->{node
};
769 my $proxy = $param->{proxy
};
771 my $authpath = "/vms/$vmid";
772 my $permissions = 'VM.Console';
774 my $conf = PVE
::LXC
::load_config
($vmid);
776 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
778 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
780 my $shcmd = ['/usr/bin/dtach', '-A',
781 "/var/run/dtach/vzctlconsole$vmid",
782 '-r', 'winch', '-z', @$concmd];
784 my $title = "CT $vmid";
786 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
790 __PACKAGE__-
>register_method({
791 name
=> 'migrate_vm',
792 path
=> '{vmid}/migrate',
796 description
=> "Migrate the container to another node. Creates a new migration task.",
798 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
801 additionalProperties
=> 0,
803 node
=> get_standard_option
('pve-node'),
804 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
805 target
=> get_standard_option
('pve-node', {
806 description
=> "Target node.",
807 completion
=> \
&PVE
::LXC
::complete_migration_target
,
811 description
=> "Use online/live migration.",
818 description
=> "the task ID.",
823 my $rpcenv = PVE
::RPCEnvironment
::get
();
825 my $authuser = $rpcenv->get_user();
827 my $target = extract_param
($param, 'target');
829 my $localnode = PVE
::INotify
::nodename
();
830 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
832 PVE
::Cluster
::check_cfs_quorum
();
834 PVE
::Cluster
::check_node_exists
($target);
836 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
838 my $vmid = extract_param
($param, 'vmid');
841 PVE
::LXC
::load_config
($vmid);
843 # try to detect errors early
844 if (PVE
::LXC
::check_running
($vmid)) {
845 die "can't migrate running container without --online\n"
846 if !$param->{online
};
849 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
854 my $service = "ct:$vmid";
856 my $cmd = ['ha-manager', 'migrate', $service, $target];
858 print "Executing HA migrate for CT $vmid to node $target\n";
860 PVE
::Tools
::run_command
($cmd);
865 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
872 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
877 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
881 __PACKAGE__-
>register_method({
882 name
=> 'vm_feature',
883 path
=> '{vmid}/feature',
887 description
=> "Check if feature for virtual machine is available.",
889 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
892 additionalProperties
=> 0,
894 node
=> get_standard_option
('pve-node'),
895 vmid
=> get_standard_option
('pve-vmid'),
897 description
=> "Feature to check.",
899 enum
=> [ 'snapshot' ],
901 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
909 hasFeature
=> { type
=> 'boolean' },
912 #items => { type => 'string' },
919 my $node = extract_param
($param, 'node');
921 my $vmid = extract_param
($param, 'vmid');
923 my $snapname = extract_param
($param, 'snapname');
925 my $feature = extract_param
($param, 'feature');
927 my $conf = PVE
::LXC
::load_config
($vmid);
930 my $snap = $conf->{snapshots
}->{$snapname};
931 die "snapshot '$snapname' does not exist\n" if !defined($snap);
934 my $storage_cfg = PVE
::Storage
::config
();
936 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
937 my $hasFeature = PVE
::LXC
::has_feature
($feature, $conf, $storage_cfg, $snapname);
940 hasFeature
=> $hasFeature,
941 #nodes => [ keys %$nodelist ],
945 __PACKAGE__-
>register_method({
947 path
=> '{vmid}/template',
951 description
=> "Create a Template.",
953 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
954 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
957 additionalProperties
=> 0,
959 node
=> get_standard_option
('pve-node'),
960 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
963 returns
=> { type
=> 'null'},
967 my $rpcenv = PVE
::RPCEnvironment
::get
();
969 my $authuser = $rpcenv->get_user();
971 my $node = extract_param
($param, 'node');
973 my $vmid = extract_param
($param, 'vmid');
977 my $conf = PVE
::LXC
::load_config
($vmid);
978 PVE
::LXC
::check_lock
($conf);
980 die "unable to create template, because CT contains snapshots\n"
981 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
983 die "you can't convert a template to a template\n"
984 if PVE
::LXC
::is_template
($conf);
986 die "you can't convert a CT to template if the CT is running\n"
987 if PVE
::LXC
::check_running
($vmid);
990 PVE
::LXC
::template_create
($vmid, $conf);
993 $conf->{template
} = 1;
995 PVE
::LXC
::write_config
($vmid, $conf);
996 # and remove lxc config
997 PVE
::LXC
::update_lxc_config
(undef, $vmid, $conf);
999 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1002 PVE
::LXC
::lock_container
($vmid, undef, $updatefn);