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.",
159 my $rpcenv = PVE
::RPCEnvironment
::get
();
161 my $authuser = $rpcenv->get_user();
163 my $node = extract_param
($param, 'node');
165 my $vmid = extract_param
($param, 'vmid');
167 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
169 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
171 my $same_container_exists = -f
$basecfg_fn;
173 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
174 my $unprivileged = extract_param
($param, 'unprivileged');
176 my $restore = extract_param
($param, 'restore');
179 # fixme: limit allowed parameters
183 my $force = extract_param
($param, 'force');
185 if (!($same_container_exists && $restore && $force)) {
186 PVE
::Cluster
::check_vmid_unused
($vmid);
188 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
189 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
192 my $password = extract_param
($param, 'password');
194 my $pool = extract_param
($param, 'pool');
196 if (defined($pool)) {
197 $rpcenv->check_pool_exist($pool);
198 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
201 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
203 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
205 } elsif ($restore && $force && $same_container_exists &&
206 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
207 # OK: user has VM.Backup permissions, and want to restore an existing VM
212 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
214 my $storage = extract_param
($param, 'storage') // 'local';
216 my $storage_cfg = cfs_read_file
("storage.cfg");
218 my $ostemplate = extract_param
($param, 'ostemplate');
222 if ($ostemplate eq '-') {
223 die "pipe requires cli environment\n"
224 if $rpcenv->{type
} ne 'cli';
225 die "pipe can only be used with restore tasks\n"
228 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
230 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
231 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
234 my $check_and_activate_storage = sub {
237 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
239 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
240 if !$scfg->{content
}->{rootdir
};
242 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
244 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
249 my $no_disk_param = {};
250 foreach my $opt (keys %$param) {
251 my $value = $param->{$opt};
252 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
253 # allow to use simple numbers (add default storage in that case)
254 $param->{$opt} = "$storage:$value" if $value =~ m/^\d+(\.\d+)?$/;
256 $no_disk_param->{$opt} = $value;
260 # check storage access, activate storage
261 PVE
::LXC
::Config-
>foreach_mountpoint($param, sub {
262 my ($ms, $mountpoint) = @_;
264 my $volid = $mountpoint->{volume
};
265 my $mp = $mountpoint->{mp
};
267 if ($mountpoint->{type
} ne 'volume') { # bind or device
268 die "Only root can pass arbitrary filesystem paths.\n"
269 if $authuser ne 'root@pam';
271 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
272 &$check_and_activate_storage($sid);
276 # check/activate default storage
277 &$check_and_activate_storage($storage) if !defined($param->{rootfs
});
279 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
281 $conf->{unprivileged
} = 1 if $unprivileged;
283 my $check_vmid_usage = sub {
285 die "can't overwrite running container\n"
286 if PVE
::LXC
::check_running
($vmid);
288 PVE
::Cluster
::check_vmid_unused
($vmid);
293 &$check_vmid_usage(); # final check after locking
295 PVE
::Cluster
::check_cfs_quorum
();
299 if (!defined($param->{rootfs
})) {
301 my (undef, $disksize) = PVE
::LXC
::Create
::recover_config
($archive);
302 die "unable to detect disk size - please specify rootfs (size)\n"
304 $disksize /= 1024 * 1024 * 1024; # create_disks expects GB as unit size
305 $param->{rootfs
} = "$storage:$disksize";
307 $param->{rootfs
} = "$storage:4"; # defaults to 4GB
311 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $param, $conf);
313 PVE
::LXC
::Create
::create_rootfs
($storage_cfg, $vmid, $conf, $archive, $password, $restore, $ignore_unpack_errors);
315 $conf->{hostname
} ||= "CT$vmid";
316 $conf->{memory
} ||= 512;
317 $conf->{swap
} //= 512;
318 PVE
::LXC
::Config-
>write_config($vmid, $conf);
321 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
322 PVE
::LXC
::destroy_config
($vmid);
325 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
328 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
330 &$check_vmid_usage(); # first check before locking
332 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
333 $vmid, $authuser, $realcmd);
337 __PACKAGE__-
>register_method({
342 description
=> "Directory index",
347 additionalProperties
=> 0,
349 node
=> get_standard_option
('pve-node'),
350 vmid
=> get_standard_option
('pve-vmid'),
358 subdir
=> { type
=> 'string' },
361 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
367 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
370 { subdir
=> 'config' },
371 { subdir
=> 'status' },
372 { subdir
=> 'vncproxy' },
373 { subdir
=> 'vncwebsocket' },
374 { subdir
=> 'spiceproxy' },
375 { subdir
=> 'migrate' },
376 { subdir
=> 'clone' },
377 # { subdir => 'initlog' },
379 { subdir
=> 'rrddata' },
380 { subdir
=> 'firewall' },
381 { subdir
=> 'snapshot' },
382 { subdir
=> 'resize' },
389 __PACKAGE__-
>register_method({
391 path
=> '{vmid}/rrd',
393 protected
=> 1, # fixme: can we avoid that?
395 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
397 description
=> "Read VM RRD statistics (returns PNG)",
399 additionalProperties
=> 0,
401 node
=> get_standard_option
('pve-node'),
402 vmid
=> get_standard_option
('pve-vmid'),
404 description
=> "Specify the time frame you are interested in.",
406 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
409 description
=> "The list of datasources you want to display.",
410 type
=> 'string', format
=> 'pve-configid-list',
413 description
=> "The RRD consolidation function",
415 enum
=> [ 'AVERAGE', 'MAX' ],
423 filename
=> { type
=> 'string' },
429 return PVE
::Cluster
::create_rrd_graph
(
430 "pve2-vm/$param->{vmid}", $param->{timeframe
},
431 $param->{ds
}, $param->{cf
});
435 __PACKAGE__-
>register_method({
437 path
=> '{vmid}/rrddata',
439 protected
=> 1, # fixme: can we avoid that?
441 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
443 description
=> "Read VM RRD statistics",
445 additionalProperties
=> 0,
447 node
=> get_standard_option
('pve-node'),
448 vmid
=> get_standard_option
('pve-vmid'),
450 description
=> "Specify the time frame you are interested in.",
452 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
455 description
=> "The RRD consolidation function",
457 enum
=> [ 'AVERAGE', 'MAX' ],
472 return PVE
::Cluster
::create_rrd_data
(
473 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
476 __PACKAGE__-
>register_method({
477 name
=> 'destroy_vm',
482 description
=> "Destroy the container (also delete all uses files).",
484 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
487 additionalProperties
=> 0,
489 node
=> get_standard_option
('pve-node'),
490 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
499 my $rpcenv = PVE
::RPCEnvironment
::get
();
501 my $authuser = $rpcenv->get_user();
503 my $vmid = $param->{vmid
};
505 # test if container exists
506 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
508 my $storage_cfg = cfs_read_file
("storage.cfg");
510 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
512 die "unable to remove CT $vmid - used in HA resources\n"
513 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
515 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
517 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
520 # reload config after lock
521 $conf = PVE
::LXC
::Config-
>load_config($vmid);
522 PVE
::LXC
::Config-
>check_lock($conf);
524 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
526 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
527 PVE
::AccessControl
::remove_vm_access
($vmid);
528 PVE
::Firewall
::remove_vmfw_conf
($vmid);
531 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
533 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
538 __PACKAGE__-
>register_method ({
540 path
=> '{vmid}/vncproxy',
544 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
546 description
=> "Creates a TCP VNC proxy connections.",
548 additionalProperties
=> 0,
550 node
=> get_standard_option
('pve-node'),
551 vmid
=> get_standard_option
('pve-vmid'),
555 description
=> "use websocket instead of standard VNC.",
560 additionalProperties
=> 0,
562 user
=> { type
=> 'string' },
563 ticket
=> { type
=> 'string' },
564 cert
=> { type
=> 'string' },
565 port
=> { type
=> 'integer' },
566 upid
=> { type
=> 'string' },
572 my $rpcenv = PVE
::RPCEnvironment
::get
();
574 my $authuser = $rpcenv->get_user();
576 my $vmid = $param->{vmid
};
577 my $node = $param->{node
};
579 my $authpath = "/vms/$vmid";
581 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
583 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
586 my ($remip, $family);
588 if ($node ne PVE
::INotify
::nodename
()) {
589 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
591 $family = PVE
::Tools
::get_host_address_family
($node);
594 my $port = PVE
::Tools
::next_vnc_port
($family);
596 # NOTE: vncterm VNC traffic is already TLS encrypted,
597 # so we select the fastest chipher here (or 'none'?)
598 my $remcmd = $remip ?
599 ['/usr/bin/ssh', '-t', $remip] : [];
601 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
602 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
604 my $shcmd = [ '/usr/bin/dtach', '-A',
605 "/var/run/dtach/vzctlconsole$vmid",
606 '-r', 'winch', '-z', @$concmd];
611 syslog
('info', "starting lxc vnc proxy $upid\n");
615 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
616 '-timeout', $timeout, '-authpath', $authpath,
617 '-perm', 'VM.Console'];
619 if ($param->{websocket
}) {
620 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
621 push @$cmd, '-notls', '-listen', 'localhost';
624 push @$cmd, '-c', @$remcmd, @$shcmd;
631 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
633 PVE
::Tools
::wait_for_vnc_port
($port);
644 __PACKAGE__-
>register_method({
645 name
=> 'vncwebsocket',
646 path
=> '{vmid}/vncwebsocket',
649 description
=> "You also need to pass a valid ticket (vncticket).",
650 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
652 description
=> "Opens a weksocket for VNC traffic.",
654 additionalProperties
=> 0,
656 node
=> get_standard_option
('pve-node'),
657 vmid
=> get_standard_option
('pve-vmid'),
659 description
=> "Ticket from previous call to vncproxy.",
664 description
=> "Port number returned by previous vncproxy call.",
674 port
=> { type
=> 'string' },
680 my $rpcenv = PVE
::RPCEnvironment
::get
();
682 my $authuser = $rpcenv->get_user();
684 my $authpath = "/vms/$param->{vmid}";
686 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
688 my $port = $param->{port
};
690 return { port
=> $port };
693 __PACKAGE__-
>register_method ({
694 name
=> 'spiceproxy',
695 path
=> '{vmid}/spiceproxy',
700 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
702 description
=> "Returns a SPICE configuration to connect to the CT.",
704 additionalProperties
=> 0,
706 node
=> get_standard_option
('pve-node'),
707 vmid
=> get_standard_option
('pve-vmid'),
708 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
711 returns
=> get_standard_option
('remote-viewer-config'),
715 my $vmid = $param->{vmid
};
716 my $node = $param->{node
};
717 my $proxy = $param->{proxy
};
719 my $authpath = "/vms/$vmid";
720 my $permissions = 'VM.Console';
722 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
724 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
726 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
728 my $shcmd = ['/usr/bin/dtach', '-A',
729 "/var/run/dtach/vzctlconsole$vmid",
730 '-r', 'winch', '-z', @$concmd];
732 my $title = "CT $vmid";
734 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
738 __PACKAGE__-
>register_method({
739 name
=> 'migrate_vm',
740 path
=> '{vmid}/migrate',
744 description
=> "Migrate the container to another node. Creates a new migration task.",
746 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
749 additionalProperties
=> 0,
751 node
=> get_standard_option
('pve-node'),
752 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
753 target
=> get_standard_option
('pve-node', {
754 description
=> "Target node.",
755 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
759 description
=> "Use online/live migration.",
766 description
=> "the task ID.",
771 my $rpcenv = PVE
::RPCEnvironment
::get
();
773 my $authuser = $rpcenv->get_user();
775 my $target = extract_param
($param, 'target');
777 my $localnode = PVE
::INotify
::nodename
();
778 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
780 PVE
::Cluster
::check_cfs_quorum
();
782 PVE
::Cluster
::check_node_exists
($target);
784 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
786 my $vmid = extract_param
($param, 'vmid');
789 PVE
::LXC
::Config-
>load_config($vmid);
791 # try to detect errors early
792 if (PVE
::LXC
::check_running
($vmid)) {
793 die "can't migrate running container without --online\n"
794 if !$param->{online
};
797 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
802 my $service = "ct:$vmid";
804 my $cmd = ['ha-manager', 'migrate', $service, $target];
806 print "Executing HA migrate for CT $vmid to node $target\n";
808 PVE
::Tools
::run_command
($cmd);
813 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
820 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
825 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
829 __PACKAGE__-
>register_method({
830 name
=> 'vm_feature',
831 path
=> '{vmid}/feature',
835 description
=> "Check if feature for virtual machine is available.",
837 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
840 additionalProperties
=> 0,
842 node
=> get_standard_option
('pve-node'),
843 vmid
=> get_standard_option
('pve-vmid'),
845 description
=> "Feature to check.",
847 enum
=> [ 'snapshot' ],
849 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
857 hasFeature
=> { type
=> 'boolean' },
860 #items => { type => 'string' },
867 my $node = extract_param
($param, 'node');
869 my $vmid = extract_param
($param, 'vmid');
871 my $snapname = extract_param
($param, 'snapname');
873 my $feature = extract_param
($param, 'feature');
875 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
878 my $snap = $conf->{snapshots
}->{$snapname};
879 die "snapshot '$snapname' does not exist\n" if !defined($snap);
882 my $storage_cfg = PVE
::Storage
::config
();
884 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
885 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
888 hasFeature
=> $hasFeature,
889 #nodes => [ keys %$nodelist ],
893 __PACKAGE__-
>register_method({
895 path
=> '{vmid}/template',
899 description
=> "Create a Template.",
901 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
902 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
905 additionalProperties
=> 0,
907 node
=> get_standard_option
('pve-node'),
908 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
911 description
=> "The template feature is experimental, set this " .
912 "flag if you know what you are doing.",
917 returns
=> { type
=> 'null'},
921 my $rpcenv = PVE
::RPCEnvironment
::get
();
923 my $authuser = $rpcenv->get_user();
925 my $node = extract_param
($param, 'node');
927 my $vmid = extract_param
($param, 'vmid');
931 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
932 PVE
::LXC
::Config-
>check_lock($conf);
934 die "unable to create template, because CT contains snapshots\n"
935 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
937 die "you can't convert a template to a template\n"
938 if PVE
::LXC
::Config-
>is_template($conf);
940 die "you can't convert a CT to template if the CT is running\n"
941 if PVE
::LXC
::check_running
($vmid);
944 PVE
::LXC
::template_create
($vmid, $conf);
947 $conf->{template
} = 1;
949 PVE
::LXC
::Config-
>write_config($vmid, $conf);
950 # and remove lxc config
951 PVE
::LXC
::update_lxc_config
(undef, $vmid, $conf);
953 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
956 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
961 __PACKAGE__-
>register_method({
963 path
=> '{vmid}/clone',
967 description
=> "Create a container clone/copy",
969 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
970 "and 'VM.Allocate' permissions " .
971 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
972 "'Datastore.AllocateSpace' on any used storage.",
975 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
977 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
978 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
983 additionalProperties
=> 0,
985 node
=> get_standard_option
('pve-node'),
986 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
987 newid
=> get_standard_option
('pve-vmid', {
988 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
989 description
=> 'VMID for the clone.' }),
992 type
=> 'string', format
=> 'dns-name',
993 description
=> "Set a hostname for the new CT.",
998 description
=> "Description for the new CT.",
1002 type
=> 'string', format
=> 'pve-poolid',
1003 description
=> "Add the new CT to the specified pool.",
1005 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1008 storage
=> get_standard_option
('pve-storage-id', {
1009 description
=> "Target storage for full clone.",
1016 description
=> "Create a full copy of all disk. This is always done when " .
1017 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1022 description
=> "The clone feature is experimental, set this " .
1023 "flag if you know what you are doing.",
1026 # target => get_standard_option('pve-node', {
1027 # description => "Target node. Only allowed if the original VM is on shared storage.",
1038 my $rpcenv = PVE
::RPCEnvironment
::get
();
1040 my $authuser = $rpcenv->get_user();
1042 my $node = extract_param
($param, 'node');
1044 my $vmid = extract_param
($param, 'vmid');
1046 my $newid = extract_param
($param, 'newid');
1048 my $pool = extract_param
($param, 'pool');
1050 if (defined($pool)) {
1051 $rpcenv->check_pool_exist($pool);
1054 my $snapname = extract_param
($param, 'snapname');
1056 my $storage = extract_param
($param, 'storage');
1058 my $localnode = PVE
::INotify
::nodename
();
1060 my $storecfg = PVE
::Storage
::config
();
1063 # check if storage is enabled on local node
1064 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1067 PVE
::Cluster
::check_cfs_quorum
();
1069 my $running = PVE
::LXC
::check_running
($vmid) || 0;
1073 # do all tests after lock
1074 # we also try to do all tests before we fork the worker
1075 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1077 PVE
::LXC
::Config-
>check_lock($conf);
1079 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1081 die "unexpected state change\n" if $verify_running != $running;
1083 die "snapshot '$snapname' does not exist\n"
1084 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1086 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1088 my $conffile = PVE
::LXC
::Config-
>config_file($newid);
1089 die "unable to create CT $newid: config file already exists\n"
1092 my $newconf = { lock => 'clone' };
1093 my $mountpoints = {};
1097 foreach my $opt (keys %$oldconf) {
1098 my $value = $oldconf->{$opt};
1100 # no need to copy unused images, because VMID(owner) changes anyways
1101 next if $opt =~ m/^unused\d+$/;
1103 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1104 my $mp = $opt eq 'rootfs' ?
1105 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1106 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1108 if ($mp->{type
} eq 'volume') {
1109 my $volid = $mp->{volume
};
1110 if ($param->{full
}) {
1111 die "fixme: full clone not implemented";
1113 die "Full clone feature for '$volid' is not available\n"
1114 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $volid, $snapname, $running);
1115 $fullclone->{$opt} = 1;
1117 # not full means clone instead of copy
1118 die "Linked clone feature for '$volid' is not available\n"
1119 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1122 $mountpoints->{$opt} = $mp;
1123 push @$vollist, $volid;
1126 # TODO: allow bind mounts?
1127 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1131 # copy everything else
1132 $newconf->{$opt} = $value;
1136 delete $newconf->{template
};
1137 if ($param->{hostname
}) {
1138 $newconf->{hostname
} = $param->{hostname
};
1141 if ($param->{description
}) {
1142 $newconf->{description
} = $param->{description
};
1145 # create empty/temp config - this fails if CT already exists on other node
1146 PVE
::Tools
::file_set_contents
($conffile, "# ctclone temporary file\nlock: clone\n");
1151 my $newvollist = [];
1154 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1156 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1158 foreach my $opt (keys %$mountpoints) {
1159 my $mp = $mountpoints->{$opt};
1160 my $volid = $mp->{volume
};
1162 if ($fullclone->{$opt}) {
1163 die "fixme: full clone not implemented\n";
1165 print "create linked clone of mountpoint $opt ($volid)\n";
1166 my $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1167 push @$newvollist, $newvolid;
1168 $mp->{volume
} = $newvolid;
1170 $newconf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs');
1171 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1175 delete $newconf->{lock};
1176 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1178 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1183 sleep 1; # some storage like rbd need to wait before release volume - really?
1185 foreach my $volid (@$newvollist) {
1186 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1189 die "clone failed: $err";
1195 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1197 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1201 return PVE
::LXC
::Config-
>lock_config($vmid, $clonefn);
1205 __PACKAGE__-
>register_method({
1206 name
=> 'resize_vm',
1207 path
=> '{vmid}/resize',
1211 description
=> "Resize a container mountpoint.",
1213 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1216 additionalProperties
=> 0,
1218 node
=> get_standard_option
('pve-node'),
1219 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1222 description
=> "The disk you want to resize.",
1223 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1227 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1228 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.",
1232 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1240 description
=> "the task ID.",
1245 my $rpcenv = PVE
::RPCEnvironment
::get
();
1247 my $authuser = $rpcenv->get_user();
1249 my $node = extract_param
($param, 'node');
1251 my $vmid = extract_param
($param, 'vmid');
1253 my $digest = extract_param
($param, 'digest');
1255 my $sizestr = extract_param
($param, 'size');
1256 my $ext = ($sizestr =~ s/^\+//);
1257 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1258 die "invalid size string" if !defined($newsize);
1260 die "no options specified\n" if !scalar(keys %$param);
1262 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1264 my $storage_cfg = cfs_read_file
("storage.cfg");
1268 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1269 PVE
::LXC
::Config-
>check_lock($conf);
1271 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1273 my $running = PVE
::LXC
::check_running
($vmid);
1275 my $disk = $param->{disk
};
1276 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1277 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1279 my $volid = $mp->{volume
};
1281 my (undef, undef, $owner, undef, undef, undef, $format) =
1282 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1284 die "can't resize mountpoint owned by another container ($owner)"
1287 die "can't resize volume: $disk if snapshot exists\n"
1288 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1290 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1292 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1294 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1295 $newsize += $size if $ext;
1296 $newsize = int($newsize);
1298 die "unable to shrink disk size\n" if $newsize < $size;
1300 return if $size == $newsize;
1302 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1304 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1305 # we pass 0 here (parameter only makes sense for qemu)
1306 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1308 $mp->{size
} = $newsize;
1309 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1311 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1313 if ($format eq 'raw') {
1314 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1318 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1319 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1320 die "internal error: CT running but mountpoint not attached to a loop device"
1322 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1324 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1325 # to be visible to it in its namespace.
1326 # To not interfere with the rest of the system we unshare the current mount namespace,
1327 # mount over /tmp and then run resize2fs.
1329 # interestingly we don't need to e2fsck on mounted systems...
1330 my $quoted = PVE
::Tools
::shellquote
($path);
1331 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1333 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1335 warn "Failed to update the container's filesystem: $@\n" if $@;
1338 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1339 PVE
::Tools
::run_command
(['resize2fs', $path]);
1341 warn "Failed to update the container's filesystem: $@\n" if $@;
1346 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1349 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;