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 = {};
259 foreach my $opt (keys %$param) {
260 my $value = $param->{$opt};
261 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
262 # allow to use simple numbers (add default storage in that case)
263 $param->{$opt} = "$storage:$value" if $value =~ m/^\d+(\.\d+)?$/;
264 } elsif ($opt =~ m/^unused\d+$/) {
265 warn "ignoring '$opt', cannot create/restore with unused volume\n";
266 delete $param->{$opt};
268 $no_disk_param->{$opt} = $value;
272 # check storage access, activate storage
273 PVE
::LXC
::Config-
>foreach_mountpoint($param, sub {
274 my ($ms, $mountpoint) = @_;
276 my $volid = $mountpoint->{volume
};
277 my $mp = $mountpoint->{mp
};
279 if ($mountpoint->{type
} ne 'volume') { # bind or device
280 die "Only root can pass arbitrary filesystem paths.\n"
281 if $authuser ne 'root@pam';
283 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
284 &$check_and_activate_storage($sid);
288 # check/activate default storage
289 &$check_and_activate_storage($storage) if !defined($param->{rootfs
});
291 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
293 $conf->{unprivileged
} = 1 if $unprivileged;
295 my $check_vmid_usage = sub {
297 die "can't overwrite running container\n"
298 if PVE
::LXC
::check_running
($vmid);
300 PVE
::Cluster
::check_vmid_unused
($vmid);
305 &$check_vmid_usage(); # final check after locking
307 PVE
::Cluster
::check_cfs_quorum
();
311 if (!defined($param->{rootfs
})) {
313 my (undef, $rootfsinfo) = PVE
::LXC
::Create
::recover_config
($archive);
314 die "unable to detect disk size - please specify rootfs (size)\n"
315 if !defined($rootfsinfo->{size
});
316 my $disksize = $rootfsinfo->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
317 delete $rootfsinfo->{size
};
318 delete $rootfsinfo->{ro
} if defined($rootfsinfo->{ro
});
319 $rootfsinfo->{volume
} = "$storage:$disksize";
320 $param->{rootfs
} = PVE
::LXC
::Config-
>print_ct_mountpoint($rootfsinfo, 1);
322 $param->{rootfs
} = "$storage:4"; # defaults to 4GB
326 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $param, $conf);
328 PVE
::LXC
::Create
::create_rootfs
($storage_cfg, $vmid, $conf,
329 $archive, $password, $restore,
330 $ignore_unpack_errors, $ssh_keys);
332 $conf->{hostname
} ||= "CT$vmid";
333 $conf->{memory
} ||= 512;
334 $conf->{swap
} //= 512;
335 PVE
::LXC
::Config-
>write_config($vmid, $conf);
338 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
339 PVE
::LXC
::destroy_config
($vmid);
342 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
345 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
347 &$check_vmid_usage(); # first check before locking
349 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
350 $vmid, $authuser, $realcmd);
354 __PACKAGE__-
>register_method({
359 description
=> "Directory index",
364 additionalProperties
=> 0,
366 node
=> get_standard_option
('pve-node'),
367 vmid
=> get_standard_option
('pve-vmid'),
375 subdir
=> { type
=> 'string' },
378 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
384 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
387 { subdir
=> 'config' },
388 { subdir
=> 'status' },
389 { subdir
=> 'vncproxy' },
390 { subdir
=> 'vncwebsocket' },
391 { subdir
=> 'spiceproxy' },
392 { subdir
=> 'migrate' },
393 { subdir
=> 'clone' },
394 # { subdir => 'initlog' },
396 { subdir
=> 'rrddata' },
397 { subdir
=> 'firewall' },
398 { subdir
=> 'snapshot' },
399 { subdir
=> 'resize' },
406 __PACKAGE__-
>register_method({
408 path
=> '{vmid}/rrd',
410 protected
=> 1, # fixme: can we avoid that?
412 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
414 description
=> "Read VM RRD statistics (returns PNG)",
416 additionalProperties
=> 0,
418 node
=> get_standard_option
('pve-node'),
419 vmid
=> get_standard_option
('pve-vmid'),
421 description
=> "Specify the time frame you are interested in.",
423 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
426 description
=> "The list of datasources you want to display.",
427 type
=> 'string', format
=> 'pve-configid-list',
430 description
=> "The RRD consolidation function",
432 enum
=> [ 'AVERAGE', 'MAX' ],
440 filename
=> { type
=> 'string' },
446 return PVE
::Cluster
::create_rrd_graph
(
447 "pve2-vm/$param->{vmid}", $param->{timeframe
},
448 $param->{ds
}, $param->{cf
});
452 __PACKAGE__-
>register_method({
454 path
=> '{vmid}/rrddata',
456 protected
=> 1, # fixme: can we avoid that?
458 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
460 description
=> "Read VM RRD statistics",
462 additionalProperties
=> 0,
464 node
=> get_standard_option
('pve-node'),
465 vmid
=> get_standard_option
('pve-vmid'),
467 description
=> "Specify the time frame you are interested in.",
469 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
472 description
=> "The RRD consolidation function",
474 enum
=> [ 'AVERAGE', 'MAX' ],
489 return PVE
::Cluster
::create_rrd_data
(
490 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
493 __PACKAGE__-
>register_method({
494 name
=> 'destroy_vm',
499 description
=> "Destroy the container (also delete all uses files).",
501 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
504 additionalProperties
=> 0,
506 node
=> get_standard_option
('pve-node'),
507 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
516 my $rpcenv = PVE
::RPCEnvironment
::get
();
518 my $authuser = $rpcenv->get_user();
520 my $vmid = $param->{vmid
};
522 # test if container exists
523 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
525 my $storage_cfg = cfs_read_file
("storage.cfg");
527 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
529 die "unable to remove CT $vmid - used in HA resources\n"
530 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
532 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
534 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
537 # reload config after lock
538 $conf = PVE
::LXC
::Config-
>load_config($vmid);
539 PVE
::LXC
::Config-
>check_lock($conf);
541 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
543 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
544 PVE
::AccessControl
::remove_vm_access
($vmid);
545 PVE
::Firewall
::remove_vmfw_conf
($vmid);
548 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
550 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
555 __PACKAGE__-
>register_method ({
557 path
=> '{vmid}/vncproxy',
561 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
563 description
=> "Creates a TCP VNC proxy connections.",
565 additionalProperties
=> 0,
567 node
=> get_standard_option
('pve-node'),
568 vmid
=> get_standard_option
('pve-vmid'),
572 description
=> "use websocket instead of standard VNC.",
577 additionalProperties
=> 0,
579 user
=> { type
=> 'string' },
580 ticket
=> { type
=> 'string' },
581 cert
=> { type
=> 'string' },
582 port
=> { type
=> 'integer' },
583 upid
=> { type
=> 'string' },
589 my $rpcenv = PVE
::RPCEnvironment
::get
();
591 my $authuser = $rpcenv->get_user();
593 my $vmid = $param->{vmid
};
594 my $node = $param->{node
};
596 my $authpath = "/vms/$vmid";
598 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
600 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
603 my ($remip, $family);
605 if ($node ne PVE
::INotify
::nodename
()) {
606 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
608 $family = PVE
::Tools
::get_host_address_family
($node);
611 my $port = PVE
::Tools
::next_vnc_port
($family);
613 # NOTE: vncterm VNC traffic is already TLS encrypted,
614 # so we select the fastest chipher here (or 'none'?)
615 my $remcmd = $remip ?
616 ['/usr/bin/ssh', '-t', $remip] : [];
618 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
619 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
621 my $shcmd = [ '/usr/bin/dtach', '-A',
622 "/var/run/dtach/vzctlconsole$vmid",
623 '-r', 'winch', '-z', @$concmd];
628 syslog
('info', "starting lxc vnc proxy $upid\n");
632 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
633 '-timeout', $timeout, '-authpath', $authpath,
634 '-perm', 'VM.Console'];
636 if ($param->{websocket
}) {
637 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
638 push @$cmd, '-notls', '-listen', 'localhost';
641 push @$cmd, '-c', @$remcmd, @$shcmd;
648 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
650 PVE
::Tools
::wait_for_vnc_port
($port);
661 __PACKAGE__-
>register_method({
662 name
=> 'vncwebsocket',
663 path
=> '{vmid}/vncwebsocket',
666 description
=> "You also need to pass a valid ticket (vncticket).",
667 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
669 description
=> "Opens a weksocket for VNC traffic.",
671 additionalProperties
=> 0,
673 node
=> get_standard_option
('pve-node'),
674 vmid
=> get_standard_option
('pve-vmid'),
676 description
=> "Ticket from previous call to vncproxy.",
681 description
=> "Port number returned by previous vncproxy call.",
691 port
=> { type
=> 'string' },
697 my $rpcenv = PVE
::RPCEnvironment
::get
();
699 my $authuser = $rpcenv->get_user();
701 my $authpath = "/vms/$param->{vmid}";
703 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
705 my $port = $param->{port
};
707 return { port
=> $port };
710 __PACKAGE__-
>register_method ({
711 name
=> 'spiceproxy',
712 path
=> '{vmid}/spiceproxy',
717 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
719 description
=> "Returns a SPICE configuration to connect to the CT.",
721 additionalProperties
=> 0,
723 node
=> get_standard_option
('pve-node'),
724 vmid
=> get_standard_option
('pve-vmid'),
725 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
728 returns
=> get_standard_option
('remote-viewer-config'),
732 my $vmid = $param->{vmid
};
733 my $node = $param->{node
};
734 my $proxy = $param->{proxy
};
736 my $authpath = "/vms/$vmid";
737 my $permissions = 'VM.Console';
739 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
741 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
743 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
745 my $shcmd = ['/usr/bin/dtach', '-A',
746 "/var/run/dtach/vzctlconsole$vmid",
747 '-r', 'winch', '-z', @$concmd];
749 my $title = "CT $vmid";
751 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
755 __PACKAGE__-
>register_method({
756 name
=> 'migrate_vm',
757 path
=> '{vmid}/migrate',
761 description
=> "Migrate the container to another node. Creates a new migration task.",
763 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
766 additionalProperties
=> 0,
768 node
=> get_standard_option
('pve-node'),
769 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
770 target
=> get_standard_option
('pve-node', {
771 description
=> "Target node.",
772 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
776 description
=> "Use online/live migration.",
781 description
=> "Force migration despite local bind / device" .
782 " mounts. WARNING: identical bind / device mounts need to ".
783 " be available on the target node.",
790 description
=> "the task ID.",
795 my $rpcenv = PVE
::RPCEnvironment
::get
();
797 my $authuser = $rpcenv->get_user();
799 my $target = extract_param
($param, 'target');
801 my $localnode = PVE
::INotify
::nodename
();
802 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
804 PVE
::Cluster
::check_cfs_quorum
();
806 PVE
::Cluster
::check_node_exists
($target);
808 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
810 my $vmid = extract_param
($param, 'vmid');
813 PVE
::LXC
::Config-
>load_config($vmid);
815 # try to detect errors early
816 if (PVE
::LXC
::check_running
($vmid)) {
817 die "can't migrate running container without --online\n"
818 if !$param->{online
};
821 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
826 my $service = "ct:$vmid";
828 my $cmd = ['ha-manager', 'migrate', $service, $target];
830 print "Executing HA migrate for CT $vmid to node $target\n";
832 PVE
::Tools
::run_command
($cmd);
837 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
844 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
849 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
853 __PACKAGE__-
>register_method({
854 name
=> 'vm_feature',
855 path
=> '{vmid}/feature',
859 description
=> "Check if feature for virtual machine is available.",
861 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
864 additionalProperties
=> 0,
866 node
=> get_standard_option
('pve-node'),
867 vmid
=> get_standard_option
('pve-vmid'),
869 description
=> "Feature to check.",
871 enum
=> [ 'snapshot' ],
873 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
881 hasFeature
=> { type
=> 'boolean' },
884 #items => { type => 'string' },
891 my $node = extract_param
($param, 'node');
893 my $vmid = extract_param
($param, 'vmid');
895 my $snapname = extract_param
($param, 'snapname');
897 my $feature = extract_param
($param, 'feature');
899 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
902 my $snap = $conf->{snapshots
}->{$snapname};
903 die "snapshot '$snapname' does not exist\n" if !defined($snap);
906 my $storage_cfg = PVE
::Storage
::config
();
908 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
909 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
912 hasFeature
=> $hasFeature,
913 #nodes => [ keys %$nodelist ],
917 __PACKAGE__-
>register_method({
919 path
=> '{vmid}/template',
923 description
=> "Create a Template.",
925 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
926 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
929 additionalProperties
=> 0,
931 node
=> get_standard_option
('pve-node'),
932 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
935 description
=> "The template feature is experimental, set this " .
936 "flag if you know what you are doing.",
941 returns
=> { type
=> 'null'},
945 my $rpcenv = PVE
::RPCEnvironment
::get
();
947 my $authuser = $rpcenv->get_user();
949 my $node = extract_param
($param, 'node');
951 my $vmid = extract_param
($param, 'vmid');
955 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
956 PVE
::LXC
::Config-
>check_lock($conf);
958 die "unable to create template, because CT contains snapshots\n"
959 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
961 die "you can't convert a template to a template\n"
962 if PVE
::LXC
::Config-
>is_template($conf);
964 die "you can't convert a CT to template if the CT is running\n"
965 if PVE
::LXC
::check_running
($vmid);
968 PVE
::LXC
::template_create
($vmid, $conf);
971 $conf->{template
} = 1;
973 PVE
::LXC
::Config-
>write_config($vmid, $conf);
974 # and remove lxc config
975 PVE
::LXC
::update_lxc_config
(undef, $vmid, $conf);
977 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
980 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
985 __PACKAGE__-
>register_method({
987 path
=> '{vmid}/clone',
991 description
=> "Create a container clone/copy",
993 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
994 "and 'VM.Allocate' permissions " .
995 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
996 "'Datastore.AllocateSpace' on any used storage.",
999 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1001 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1002 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1007 additionalProperties
=> 0,
1009 node
=> get_standard_option
('pve-node'),
1010 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1011 newid
=> get_standard_option
('pve-vmid', {
1012 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1013 description
=> 'VMID for the clone.' }),
1016 type
=> 'string', format
=> 'dns-name',
1017 description
=> "Set a hostname for the new CT.",
1022 description
=> "Description for the new CT.",
1026 type
=> 'string', format
=> 'pve-poolid',
1027 description
=> "Add the new CT to the specified pool.",
1029 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1032 storage
=> get_standard_option
('pve-storage-id', {
1033 description
=> "Target storage for full clone.",
1040 description
=> "Create a full copy of all disk. This is always done when " .
1041 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1046 description
=> "The clone feature is experimental, set this " .
1047 "flag if you know what you are doing.",
1050 # target => get_standard_option('pve-node', {
1051 # description => "Target node. Only allowed if the original VM is on shared storage.",
1062 my $rpcenv = PVE
::RPCEnvironment
::get
();
1064 my $authuser = $rpcenv->get_user();
1066 my $node = extract_param
($param, 'node');
1068 my $vmid = extract_param
($param, 'vmid');
1070 my $newid = extract_param
($param, 'newid');
1072 my $pool = extract_param
($param, 'pool');
1074 if (defined($pool)) {
1075 $rpcenv->check_pool_exist($pool);
1078 my $snapname = extract_param
($param, 'snapname');
1080 my $storage = extract_param
($param, 'storage');
1082 my $localnode = PVE
::INotify
::nodename
();
1084 my $storecfg = PVE
::Storage
::config
();
1087 # check if storage is enabled on local node
1088 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1091 PVE
::Cluster
::check_cfs_quorum
();
1093 my $running = PVE
::LXC
::check_running
($vmid) || 0;
1097 # do all tests after lock
1098 # we also try to do all tests before we fork the worker
1099 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1101 PVE
::LXC
::Config-
>check_lock($conf);
1103 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1105 die "unexpected state change\n" if $verify_running != $running;
1107 die "snapshot '$snapname' does not exist\n"
1108 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1110 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1112 my $conffile = PVE
::LXC
::Config-
>config_file($newid);
1113 die "unable to create CT $newid: config file already exists\n"
1116 my $newconf = { lock => 'clone' };
1117 my $mountpoints = {};
1121 foreach my $opt (keys %$oldconf) {
1122 my $value = $oldconf->{$opt};
1124 # no need to copy unused images, because VMID(owner) changes anyways
1125 next if $opt =~ m/^unused\d+$/;
1127 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1128 my $mp = $opt eq 'rootfs' ?
1129 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1130 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1132 if ($mp->{type
} eq 'volume') {
1133 my $volid = $mp->{volume
};
1134 if ($param->{full
}) {
1135 die "fixme: full clone not implemented";
1137 die "Full clone feature for '$volid' is not available\n"
1138 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $volid, $snapname, $running);
1139 $fullclone->{$opt} = 1;
1141 # not full means clone instead of copy
1142 die "Linked clone feature for '$volid' is not available\n"
1143 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1146 $mountpoints->{$opt} = $mp;
1147 push @$vollist, $volid;
1150 # TODO: allow bind mounts?
1151 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1155 # copy everything else
1156 $newconf->{$opt} = $value;
1160 delete $newconf->{template
};
1161 if ($param->{hostname
}) {
1162 $newconf->{hostname
} = $param->{hostname
};
1165 if ($param->{description
}) {
1166 $newconf->{description
} = $param->{description
};
1169 # create empty/temp config - this fails if CT already exists on other node
1170 PVE
::Tools
::file_set_contents
($conffile, "# ctclone temporary file\nlock: clone\n");
1175 my $newvollist = [];
1178 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1180 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1182 foreach my $opt (keys %$mountpoints) {
1183 my $mp = $mountpoints->{$opt};
1184 my $volid = $mp->{volume
};
1186 if ($fullclone->{$opt}) {
1187 die "fixme: full clone not implemented\n";
1189 print "create linked clone of mountpoint $opt ($volid)\n";
1190 my $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1191 push @$newvollist, $newvolid;
1192 $mp->{volume
} = $newvolid;
1194 $newconf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs');
1195 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1199 delete $newconf->{lock};
1200 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1202 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1207 sleep 1; # some storage like rbd need to wait before release volume - really?
1209 foreach my $volid (@$newvollist) {
1210 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1213 die "clone failed: $err";
1219 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1221 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1225 return PVE
::LXC
::Config-
>lock_config($vmid, $clonefn);
1229 __PACKAGE__-
>register_method({
1230 name
=> 'resize_vm',
1231 path
=> '{vmid}/resize',
1235 description
=> "Resize a container mountpoint.",
1237 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1240 additionalProperties
=> 0,
1242 node
=> get_standard_option
('pve-node'),
1243 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1246 description
=> "The disk you want to resize.",
1247 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1251 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1252 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.",
1256 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1264 description
=> "the task ID.",
1269 my $rpcenv = PVE
::RPCEnvironment
::get
();
1271 my $authuser = $rpcenv->get_user();
1273 my $node = extract_param
($param, 'node');
1275 my $vmid = extract_param
($param, 'vmid');
1277 my $digest = extract_param
($param, 'digest');
1279 my $sizestr = extract_param
($param, 'size');
1280 my $ext = ($sizestr =~ s/^\+//);
1281 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1282 die "invalid size string" if !defined($newsize);
1284 die "no options specified\n" if !scalar(keys %$param);
1286 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1288 my $storage_cfg = cfs_read_file
("storage.cfg");
1292 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1293 PVE
::LXC
::Config-
>check_lock($conf);
1295 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1297 my $running = PVE
::LXC
::check_running
($vmid);
1299 my $disk = $param->{disk
};
1300 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1301 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1303 my $volid = $mp->{volume
};
1305 my (undef, undef, $owner, undef, undef, undef, $format) =
1306 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1308 die "can't resize mountpoint owned by another container ($owner)"
1311 die "can't resize volume: $disk if snapshot exists\n"
1312 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1314 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1316 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1318 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1320 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1321 $newsize += $size if $ext;
1322 $newsize = int($newsize);
1324 die "unable to shrink disk size\n" if $newsize < $size;
1326 return if $size == $newsize;
1328 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1330 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1331 # we pass 0 here (parameter only makes sense for qemu)
1332 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1334 $mp->{size
} = $newsize;
1335 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1337 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1339 if ($format eq 'raw') {
1340 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1344 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1345 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1346 die "internal error: CT running but mountpoint not attached to a loop device"
1348 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1350 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1351 # to be visible to it in its namespace.
1352 # To not interfere with the rest of the system we unshare the current mount namespace,
1353 # mount over /tmp and then run resize2fs.
1355 # interestingly we don't need to e2fsck on mounted systems...
1356 my $quoted = PVE
::Tools
::shellquote
($path);
1357 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1359 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1361 warn "Failed to update the container's filesystem: $@\n" if $@;
1364 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1365 PVE
::Tools
::run_command
(['resize2fs', $path]);
1367 warn "Failed to update the container's filesystem: $@\n" if $@;
1372 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1375 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;