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);
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+)?$/;
265 $no_disk_param->{$opt} = $value;
269 # check storage access, activate storage
270 PVE
::LXC
::Config-
>foreach_mountpoint($param, sub {
271 my ($ms, $mountpoint) = @_;
273 my $volid = $mountpoint->{volume
};
274 my $mp = $mountpoint->{mp
};
276 if ($mountpoint->{type
} ne 'volume') { # bind or device
277 die "Only root can pass arbitrary filesystem paths.\n"
278 if $authuser ne 'root@pam';
280 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
281 &$check_and_activate_storage($sid);
285 # check/activate default storage
286 &$check_and_activate_storage($storage) if !defined($param->{rootfs
});
288 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
290 $conf->{unprivileged
} = 1 if $unprivileged;
292 my $check_vmid_usage = sub {
294 die "can't overwrite running container\n"
295 if PVE
::LXC
::check_running
($vmid);
297 PVE
::Cluster
::check_vmid_unused
($vmid);
302 &$check_vmid_usage(); # final check after locking
304 PVE
::Cluster
::check_cfs_quorum
();
308 if (!defined($param->{rootfs
})) {
310 my (undef, $disksize) = PVE
::LXC
::Create
::recover_config
($archive);
311 die "unable to detect disk size - please specify rootfs (size)\n"
313 $disksize /= 1024 * 1024 * 1024; # create_disks expects GB as unit size
314 $param->{rootfs
} = "$storage:$disksize";
316 $param->{rootfs
} = "$storage:4"; # defaults to 4GB
320 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $param, $conf);
322 PVE
::LXC
::Create
::create_rootfs
($storage_cfg, $vmid, $conf,
323 $archive, $password, $restore,
324 $ignore_unpack_errors, $ssh_keys);
326 $conf->{hostname
} ||= "CT$vmid";
327 $conf->{memory
} ||= 512;
328 $conf->{swap
} //= 512;
329 PVE
::LXC
::Config-
>write_config($vmid, $conf);
332 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
333 PVE
::LXC
::destroy_config
($vmid);
336 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
339 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
341 &$check_vmid_usage(); # first check before locking
343 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
344 $vmid, $authuser, $realcmd);
348 __PACKAGE__-
>register_method({
353 description
=> "Directory index",
358 additionalProperties
=> 0,
360 node
=> get_standard_option
('pve-node'),
361 vmid
=> get_standard_option
('pve-vmid'),
369 subdir
=> { type
=> 'string' },
372 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
378 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
381 { subdir
=> 'config' },
382 { subdir
=> 'status' },
383 { subdir
=> 'vncproxy' },
384 { subdir
=> 'vncwebsocket' },
385 { subdir
=> 'spiceproxy' },
386 { subdir
=> 'migrate' },
387 { subdir
=> 'clone' },
388 # { subdir => 'initlog' },
390 { subdir
=> 'rrddata' },
391 { subdir
=> 'firewall' },
392 { subdir
=> 'snapshot' },
393 { subdir
=> 'resize' },
400 __PACKAGE__-
>register_method({
402 path
=> '{vmid}/rrd',
404 protected
=> 1, # fixme: can we avoid that?
406 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
408 description
=> "Read VM RRD statistics (returns PNG)",
410 additionalProperties
=> 0,
412 node
=> get_standard_option
('pve-node'),
413 vmid
=> get_standard_option
('pve-vmid'),
415 description
=> "Specify the time frame you are interested in.",
417 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
420 description
=> "The list of datasources you want to display.",
421 type
=> 'string', format
=> 'pve-configid-list',
424 description
=> "The RRD consolidation function",
426 enum
=> [ 'AVERAGE', 'MAX' ],
434 filename
=> { type
=> 'string' },
440 return PVE
::Cluster
::create_rrd_graph
(
441 "pve2-vm/$param->{vmid}", $param->{timeframe
},
442 $param->{ds
}, $param->{cf
});
446 __PACKAGE__-
>register_method({
448 path
=> '{vmid}/rrddata',
450 protected
=> 1, # fixme: can we avoid that?
452 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
454 description
=> "Read VM RRD statistics",
456 additionalProperties
=> 0,
458 node
=> get_standard_option
('pve-node'),
459 vmid
=> get_standard_option
('pve-vmid'),
461 description
=> "Specify the time frame you are interested in.",
463 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
466 description
=> "The RRD consolidation function",
468 enum
=> [ 'AVERAGE', 'MAX' ],
483 return PVE
::Cluster
::create_rrd_data
(
484 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
487 __PACKAGE__-
>register_method({
488 name
=> 'destroy_vm',
493 description
=> "Destroy the container (also delete all uses files).",
495 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
498 additionalProperties
=> 0,
500 node
=> get_standard_option
('pve-node'),
501 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
510 my $rpcenv = PVE
::RPCEnvironment
::get
();
512 my $authuser = $rpcenv->get_user();
514 my $vmid = $param->{vmid
};
516 # test if container exists
517 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
519 my $storage_cfg = cfs_read_file
("storage.cfg");
521 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
523 die "unable to remove CT $vmid - used in HA resources\n"
524 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
526 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
528 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
531 # reload config after lock
532 $conf = PVE
::LXC
::Config-
>load_config($vmid);
533 PVE
::LXC
::Config-
>check_lock($conf);
535 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
537 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
538 PVE
::AccessControl
::remove_vm_access
($vmid);
539 PVE
::Firewall
::remove_vmfw_conf
($vmid);
542 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
544 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
549 __PACKAGE__-
>register_method ({
551 path
=> '{vmid}/vncproxy',
555 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
557 description
=> "Creates a TCP VNC proxy connections.",
559 additionalProperties
=> 0,
561 node
=> get_standard_option
('pve-node'),
562 vmid
=> get_standard_option
('pve-vmid'),
566 description
=> "use websocket instead of standard VNC.",
571 additionalProperties
=> 0,
573 user
=> { type
=> 'string' },
574 ticket
=> { type
=> 'string' },
575 cert
=> { type
=> 'string' },
576 port
=> { type
=> 'integer' },
577 upid
=> { type
=> 'string' },
583 my $rpcenv = PVE
::RPCEnvironment
::get
();
585 my $authuser = $rpcenv->get_user();
587 my $vmid = $param->{vmid
};
588 my $node = $param->{node
};
590 my $authpath = "/vms/$vmid";
592 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
594 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
597 my ($remip, $family);
599 if ($node ne PVE
::INotify
::nodename
()) {
600 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
602 $family = PVE
::Tools
::get_host_address_family
($node);
605 my $port = PVE
::Tools
::next_vnc_port
($family);
607 # NOTE: vncterm VNC traffic is already TLS encrypted,
608 # so we select the fastest chipher here (or 'none'?)
609 my $remcmd = $remip ?
610 ['/usr/bin/ssh', '-t', $remip] : [];
612 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
613 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
615 my $shcmd = [ '/usr/bin/dtach', '-A',
616 "/var/run/dtach/vzctlconsole$vmid",
617 '-r', 'winch', '-z', @$concmd];
622 syslog
('info', "starting lxc vnc proxy $upid\n");
626 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
627 '-timeout', $timeout, '-authpath', $authpath,
628 '-perm', 'VM.Console'];
630 if ($param->{websocket
}) {
631 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
632 push @$cmd, '-notls', '-listen', 'localhost';
635 push @$cmd, '-c', @$remcmd, @$shcmd;
642 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
644 PVE
::Tools
::wait_for_vnc_port
($port);
655 __PACKAGE__-
>register_method({
656 name
=> 'vncwebsocket',
657 path
=> '{vmid}/vncwebsocket',
660 description
=> "You also need to pass a valid ticket (vncticket).",
661 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
663 description
=> "Opens a weksocket for VNC traffic.",
665 additionalProperties
=> 0,
667 node
=> get_standard_option
('pve-node'),
668 vmid
=> get_standard_option
('pve-vmid'),
670 description
=> "Ticket from previous call to vncproxy.",
675 description
=> "Port number returned by previous vncproxy call.",
685 port
=> { type
=> 'string' },
691 my $rpcenv = PVE
::RPCEnvironment
::get
();
693 my $authuser = $rpcenv->get_user();
695 my $authpath = "/vms/$param->{vmid}";
697 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
699 my $port = $param->{port
};
701 return { port
=> $port };
704 __PACKAGE__-
>register_method ({
705 name
=> 'spiceproxy',
706 path
=> '{vmid}/spiceproxy',
711 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
713 description
=> "Returns a SPICE configuration to connect to the CT.",
715 additionalProperties
=> 0,
717 node
=> get_standard_option
('pve-node'),
718 vmid
=> get_standard_option
('pve-vmid'),
719 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
722 returns
=> get_standard_option
('remote-viewer-config'),
726 my $vmid = $param->{vmid
};
727 my $node = $param->{node
};
728 my $proxy = $param->{proxy
};
730 my $authpath = "/vms/$vmid";
731 my $permissions = 'VM.Console';
733 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
735 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
737 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
739 my $shcmd = ['/usr/bin/dtach', '-A',
740 "/var/run/dtach/vzctlconsole$vmid",
741 '-r', 'winch', '-z', @$concmd];
743 my $title = "CT $vmid";
745 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
749 __PACKAGE__-
>register_method({
750 name
=> 'migrate_vm',
751 path
=> '{vmid}/migrate',
755 description
=> "Migrate the container to another node. Creates a new migration task.",
757 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
760 additionalProperties
=> 0,
762 node
=> get_standard_option
('pve-node'),
763 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
764 target
=> get_standard_option
('pve-node', {
765 description
=> "Target node.",
766 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
770 description
=> "Use online/live migration.",
775 description
=> "Force migration despite local bind / device" .
776 " mounts. WARNING: identical bind / device mounts need to ".
777 " be available on the target node.",
784 description
=> "the task ID.",
789 my $rpcenv = PVE
::RPCEnvironment
::get
();
791 my $authuser = $rpcenv->get_user();
793 my $target = extract_param
($param, 'target');
795 my $localnode = PVE
::INotify
::nodename
();
796 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
798 PVE
::Cluster
::check_cfs_quorum
();
800 PVE
::Cluster
::check_node_exists
($target);
802 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
804 my $vmid = extract_param
($param, 'vmid');
807 PVE
::LXC
::Config-
>load_config($vmid);
809 # try to detect errors early
810 if (PVE
::LXC
::check_running
($vmid)) {
811 die "can't migrate running container without --online\n"
812 if !$param->{online
};
815 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
820 my $service = "ct:$vmid";
822 my $cmd = ['ha-manager', 'migrate', $service, $target];
824 print "Executing HA migrate for CT $vmid to node $target\n";
826 PVE
::Tools
::run_command
($cmd);
831 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
838 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
843 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
847 __PACKAGE__-
>register_method({
848 name
=> 'vm_feature',
849 path
=> '{vmid}/feature',
853 description
=> "Check if feature for virtual machine is available.",
855 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
858 additionalProperties
=> 0,
860 node
=> get_standard_option
('pve-node'),
861 vmid
=> get_standard_option
('pve-vmid'),
863 description
=> "Feature to check.",
865 enum
=> [ 'snapshot' ],
867 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
875 hasFeature
=> { type
=> 'boolean' },
878 #items => { type => 'string' },
885 my $node = extract_param
($param, 'node');
887 my $vmid = extract_param
($param, 'vmid');
889 my $snapname = extract_param
($param, 'snapname');
891 my $feature = extract_param
($param, 'feature');
893 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
896 my $snap = $conf->{snapshots
}->{$snapname};
897 die "snapshot '$snapname' does not exist\n" if !defined($snap);
900 my $storage_cfg = PVE
::Storage
::config
();
902 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
903 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
906 hasFeature
=> $hasFeature,
907 #nodes => [ keys %$nodelist ],
911 __PACKAGE__-
>register_method({
913 path
=> '{vmid}/template',
917 description
=> "Create a Template.",
919 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
920 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
923 additionalProperties
=> 0,
925 node
=> get_standard_option
('pve-node'),
926 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
929 description
=> "The template feature is experimental, set this " .
930 "flag if you know what you are doing.",
935 returns
=> { type
=> 'null'},
939 my $rpcenv = PVE
::RPCEnvironment
::get
();
941 my $authuser = $rpcenv->get_user();
943 my $node = extract_param
($param, 'node');
945 my $vmid = extract_param
($param, 'vmid');
949 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
950 PVE
::LXC
::Config-
>check_lock($conf);
952 die "unable to create template, because CT contains snapshots\n"
953 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
955 die "you can't convert a template to a template\n"
956 if PVE
::LXC
::Config-
>is_template($conf);
958 die "you can't convert a CT to template if the CT is running\n"
959 if PVE
::LXC
::check_running
($vmid);
962 PVE
::LXC
::template_create
($vmid, $conf);
965 $conf->{template
} = 1;
967 PVE
::LXC
::Config-
>write_config($vmid, $conf);
968 # and remove lxc config
969 PVE
::LXC
::update_lxc_config
(undef, $vmid, $conf);
971 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
974 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
979 __PACKAGE__-
>register_method({
981 path
=> '{vmid}/clone',
985 description
=> "Create a container clone/copy",
987 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
988 "and 'VM.Allocate' permissions " .
989 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
990 "'Datastore.AllocateSpace' on any used storage.",
993 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
995 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
996 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1001 additionalProperties
=> 0,
1003 node
=> get_standard_option
('pve-node'),
1004 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1005 newid
=> get_standard_option
('pve-vmid', {
1006 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1007 description
=> 'VMID for the clone.' }),
1010 type
=> 'string', format
=> 'dns-name',
1011 description
=> "Set a hostname for the new CT.",
1016 description
=> "Description for the new CT.",
1020 type
=> 'string', format
=> 'pve-poolid',
1021 description
=> "Add the new CT to the specified pool.",
1023 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1026 storage
=> get_standard_option
('pve-storage-id', {
1027 description
=> "Target storage for full clone.",
1034 description
=> "Create a full copy of all disk. This is always done when " .
1035 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1040 description
=> "The clone feature is experimental, set this " .
1041 "flag if you know what you are doing.",
1044 # target => get_standard_option('pve-node', {
1045 # description => "Target node. Only allowed if the original VM is on shared storage.",
1056 my $rpcenv = PVE
::RPCEnvironment
::get
();
1058 my $authuser = $rpcenv->get_user();
1060 my $node = extract_param
($param, 'node');
1062 my $vmid = extract_param
($param, 'vmid');
1064 my $newid = extract_param
($param, 'newid');
1066 my $pool = extract_param
($param, 'pool');
1068 if (defined($pool)) {
1069 $rpcenv->check_pool_exist($pool);
1072 my $snapname = extract_param
($param, 'snapname');
1074 my $storage = extract_param
($param, 'storage');
1076 my $localnode = PVE
::INotify
::nodename
();
1078 my $storecfg = PVE
::Storage
::config
();
1081 # check if storage is enabled on local node
1082 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1085 PVE
::Cluster
::check_cfs_quorum
();
1087 my $running = PVE
::LXC
::check_running
($vmid) || 0;
1091 # do all tests after lock
1092 # we also try to do all tests before we fork the worker
1093 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1095 PVE
::LXC
::Config-
>check_lock($conf);
1097 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1099 die "unexpected state change\n" if $verify_running != $running;
1101 die "snapshot '$snapname' does not exist\n"
1102 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1104 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1106 my $conffile = PVE
::LXC
::Config-
>config_file($newid);
1107 die "unable to create CT $newid: config file already exists\n"
1110 my $newconf = { lock => 'clone' };
1111 my $mountpoints = {};
1115 foreach my $opt (keys %$oldconf) {
1116 my $value = $oldconf->{$opt};
1118 # no need to copy unused images, because VMID(owner) changes anyways
1119 next if $opt =~ m/^unused\d+$/;
1121 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1122 my $mp = $opt eq 'rootfs' ?
1123 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1124 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1126 if ($mp->{type
} eq 'volume') {
1127 my $volid = $mp->{volume
};
1128 if ($param->{full
}) {
1129 die "fixme: full clone not implemented";
1131 die "Full clone feature for '$volid' is not available\n"
1132 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $volid, $snapname, $running);
1133 $fullclone->{$opt} = 1;
1135 # not full means clone instead of copy
1136 die "Linked clone feature for '$volid' is not available\n"
1137 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1140 $mountpoints->{$opt} = $mp;
1141 push @$vollist, $volid;
1144 # TODO: allow bind mounts?
1145 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1149 # copy everything else
1150 $newconf->{$opt} = $value;
1154 delete $newconf->{template
};
1155 if ($param->{hostname
}) {
1156 $newconf->{hostname
} = $param->{hostname
};
1159 if ($param->{description
}) {
1160 $newconf->{description
} = $param->{description
};
1163 # create empty/temp config - this fails if CT already exists on other node
1164 PVE
::Tools
::file_set_contents
($conffile, "# ctclone temporary file\nlock: clone\n");
1169 my $newvollist = [];
1172 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1174 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1176 foreach my $opt (keys %$mountpoints) {
1177 my $mp = $mountpoints->{$opt};
1178 my $volid = $mp->{volume
};
1180 if ($fullclone->{$opt}) {
1181 die "fixme: full clone not implemented\n";
1183 print "create linked clone of mountpoint $opt ($volid)\n";
1184 my $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1185 push @$newvollist, $newvolid;
1186 $mp->{volume
} = $newvolid;
1188 $newconf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs');
1189 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1193 delete $newconf->{lock};
1194 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1196 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1201 sleep 1; # some storage like rbd need to wait before release volume - really?
1203 foreach my $volid (@$newvollist) {
1204 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1207 die "clone failed: $err";
1213 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1215 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1219 return PVE
::LXC
::Config-
>lock_config($vmid, $clonefn);
1223 __PACKAGE__-
>register_method({
1224 name
=> 'resize_vm',
1225 path
=> '{vmid}/resize',
1229 description
=> "Resize a container mountpoint.",
1231 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1234 additionalProperties
=> 0,
1236 node
=> get_standard_option
('pve-node'),
1237 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1240 description
=> "The disk you want to resize.",
1241 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1245 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1246 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.",
1250 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1258 description
=> "the task ID.",
1263 my $rpcenv = PVE
::RPCEnvironment
::get
();
1265 my $authuser = $rpcenv->get_user();
1267 my $node = extract_param
($param, 'node');
1269 my $vmid = extract_param
($param, 'vmid');
1271 my $digest = extract_param
($param, 'digest');
1273 my $sizestr = extract_param
($param, 'size');
1274 my $ext = ($sizestr =~ s/^\+//);
1275 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1276 die "invalid size string" if !defined($newsize);
1278 die "no options specified\n" if !scalar(keys %$param);
1280 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1282 my $storage_cfg = cfs_read_file
("storage.cfg");
1286 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1287 PVE
::LXC
::Config-
>check_lock($conf);
1289 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1291 my $running = PVE
::LXC
::check_running
($vmid);
1293 my $disk = $param->{disk
};
1294 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1295 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1297 my $volid = $mp->{volume
};
1299 my (undef, undef, $owner, undef, undef, undef, $format) =
1300 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1302 die "can't resize mountpoint owned by another container ($owner)"
1305 die "can't resize volume: $disk if snapshot exists\n"
1306 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1308 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1310 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1312 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1314 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1315 $newsize += $size if $ext;
1316 $newsize = int($newsize);
1318 die "unable to shrink disk size\n" if $newsize < $size;
1320 return if $size == $newsize;
1322 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1324 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1325 # we pass 0 here (parameter only makes sense for qemu)
1326 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1328 $mp->{size
} = $newsize;
1329 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1331 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1333 if ($format eq 'raw') {
1334 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1338 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1339 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1340 die "internal error: CT running but mountpoint not attached to a loop device"
1342 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1344 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1345 # to be visible to it in its namespace.
1346 # To not interfere with the rest of the system we unshare the current mount namespace,
1347 # mount over /tmp and then run resize2fs.
1349 # interestingly we don't need to e2fsck on mounted systems...
1350 my $quoted = PVE
::Tools
::shellquote
($path);
1351 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1353 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1355 warn "Failed to update the container's filesystem: $@\n" if $@;
1358 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1359 PVE
::Tools
::run_command
(['resize2fs', $path]);
1361 warn "Failed to update the container's filesystem: $@\n" if $@;
1366 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1369 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;