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
::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_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
::load_config
($vmid);
189 PVE
::LXC
::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, [ keys %$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
::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
::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
::create_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
::lock_container
($vmid, 1, $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
::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
::load_config
($vmid);
508 my $storage_cfg = cfs_read_file
("storage.cfg");
510 PVE
::LXC
::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
::load_config
($vmid);
522 PVE
::LXC
::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
::lock_container
($vmid, 1, $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
::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
::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
::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
::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
::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 returns
=> { type
=> 'null'},
915 my $rpcenv = PVE
::RPCEnvironment
::get
();
917 my $authuser = $rpcenv->get_user();
919 my $node = extract_param
($param, 'node');
921 my $vmid = extract_param
($param, 'vmid');
925 my $conf = PVE
::LXC
::load_config
($vmid);
926 PVE
::LXC
::check_lock
($conf);
928 die "unable to create template, because CT contains snapshots\n"
929 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
931 die "you can't convert a template to a template\n"
932 if PVE
::LXC
::is_template
($conf);
934 die "you can't convert a CT to template if the CT is running\n"
935 if PVE
::LXC
::check_running
($vmid);
938 PVE
::LXC
::template_create
($vmid, $conf);
941 $conf->{template
} = 1;
943 PVE
::LXC
::write_config
($vmid, $conf);
944 # and remove lxc config
945 PVE
::LXC
::update_lxc_config
(undef, $vmid, $conf);
947 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
950 PVE
::LXC
::lock_container
($vmid, undef, $updatefn);
955 __PACKAGE__-
>register_method({
957 path
=> '{vmid}/clone',
961 description
=> "Create a container clone/copy",
963 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
964 "and 'VM.Allocate' permissions " .
965 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
966 "'Datastore.AllocateSpace' on any used storage.",
969 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
971 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
972 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
977 additionalProperties
=> 0,
979 node
=> get_standard_option
('pve-node'),
980 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
981 newid
=> get_standard_option
('pve-vmid', {
982 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
983 description
=> 'VMID for the clone.' }),
986 type
=> 'string', format
=> 'dns-name',
987 description
=> "Set a hostname for the new CT.",
992 description
=> "Description for the new CT.",
996 type
=> 'string', format
=> 'pve-poolid',
997 description
=> "Add the new CT to the specified pool.",
999 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1002 storage
=> get_standard_option
('pve-storage-id', {
1003 description
=> "Target storage for full clone.",
1010 description
=> "Create a full copy of all disk. This is always done when " .
1011 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1014 # target => get_standard_option('pve-node', {
1015 # description => "Target node. Only allowed if the original VM is on shared storage.",
1026 my $rpcenv = PVE
::RPCEnvironment
::get
();
1028 my $authuser = $rpcenv->get_user();
1030 my $node = extract_param
($param, 'node');
1032 my $vmid = extract_param
($param, 'vmid');
1034 my $newid = extract_param
($param, 'newid');
1036 my $pool = extract_param
($param, 'pool');
1038 if (defined($pool)) {
1039 $rpcenv->check_pool_exist($pool);
1042 my $snapname = extract_param
($param, 'snapname');
1044 my $storage = extract_param
($param, 'storage');
1046 my $localnode = PVE
::INotify
::nodename
();
1048 my $storecfg = PVE
::Storage
::config
();
1051 # check if storage is enabled on local node
1052 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1055 PVE
::Cluster
::check_cfs_quorum
();
1057 my $running = PVE
::LXC
::check_running
($vmid) || 0;
1061 # do all tests after lock
1062 # we also try to do all tests before we fork the worker
1063 my $conf = PVE
::LXC
::load_config
($vmid);
1065 PVE
::LXC
::check_lock
($conf);
1067 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1069 die "unexpected state change\n" if $verify_running != $running;
1071 die "snapshot '$snapname' does not exist\n"
1072 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1074 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1076 my $conffile = PVE
::LXC
::config_file
($newid);
1077 die "unable to create CT $newid: config file already exists\n"
1080 my $newconf = { lock => 'clone' };
1081 my $mountpoints = {};
1085 foreach my $opt (keys %$oldconf) {
1086 my $value = $oldconf->{$opt};
1088 # no need to copy unused images, because VMID(owner) changes anyways
1089 next if $opt =~ m/^unused\d+$/;
1091 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1092 my $mp = $opt eq 'rootfs' ?
1093 PVE
::LXC
::parse_ct_rootfs
($value) :
1094 PVE
::LXC
::parse_ct_mountpoint
($value);
1096 if ($mp->{type
} eq 'volume') {
1097 my $volid = $mp->{volume
};
1098 if ($param->{full
}) {
1099 die "fixme: full clone not implemented";
1101 die "Full clone feature for '$volid' is not available\n"
1102 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $volid, $snapname, $running);
1103 $fullclone->{$opt} = 1;
1105 # not full means clone instead of copy
1106 die "Linked clone feature for '$volid' is not available\n"
1107 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1110 $mountpoints->{$opt} = $mp;
1111 push @$vollist, $volid;
1114 # TODO: allow bind mounts?
1115 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1119 # copy everything else
1120 $newconf->{$opt} = $value;
1124 delete $newconf->{template
};
1125 if ($param->{hostname
}) {
1126 $newconf->{hostname
} = $param->{hostname
};
1129 if ($param->{description
}) {
1130 $newconf->{description
} = $param->{description
};
1133 # create empty/temp config - this fails if CT already exists on other node
1134 PVE
::Tools
::file_set_contents
($conffile, "# ctclone temporary file\nlock: clone\n");
1139 my $newvollist = [];
1142 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1144 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1146 foreach my $opt (keys %$mountpoints) {
1147 my $mp = $mountpoints->{$opt};
1148 my $volid = $mp->{volume
};
1150 if ($fullclone->{$opt}) {
1151 die "fixme: full clone not implemented\n";
1153 print "create linked clone of mountpoint $opt ($volid)\n";
1154 my $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1155 push @$newvollist, $newvolid;
1156 $mp->{volume
} = $newvolid;
1158 $newconf->{$opt} = PVE
::LXC
::print_ct_mountpoint
($mp, $opt eq 'rootfs');
1159 PVE
::LXC
::write_config
($newid, $newconf);
1163 delete $newconf->{lock};
1164 PVE
::LXC
::write_config
($newid, $newconf);
1166 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1171 sleep 1; # some storage like rbd need to wait before release volume - really?
1173 foreach my $volid (@$newvollist) {
1174 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1177 die "clone failed: $err";
1183 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1185 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1189 return PVE
::LXC
::lock_container
($vmid, undef, $clonefn);
1193 __PACKAGE__-
>register_method({
1194 name
=> 'resize_vm',
1195 path
=> '{vmid}/resize',
1199 description
=> "Resize a container mountpoint.",
1201 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1204 additionalProperties
=> 0,
1206 node
=> get_standard_option
('pve-node'),
1207 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1210 description
=> "The disk you want to resize.",
1211 enum
=> [PVE
::LXC
::mountpoint_names
()],
1215 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1216 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.",
1220 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1228 description
=> "the task ID.",
1233 my $rpcenv = PVE
::RPCEnvironment
::get
();
1235 my $authuser = $rpcenv->get_user();
1237 my $node = extract_param
($param, 'node');
1239 my $vmid = extract_param
($param, 'vmid');
1241 my $digest = extract_param
($param, 'digest');
1243 my $sizestr = extract_param
($param, 'size');
1244 my $ext = ($sizestr =~ s/^\+//);
1245 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1246 die "invalid size string" if !defined($newsize);
1248 die "no options specified\n" if !scalar(keys %$param);
1250 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1252 my $storage_cfg = cfs_read_file
("storage.cfg");
1256 my $conf = PVE
::LXC
::load_config
($vmid);
1257 PVE
::LXC
::check_lock
($conf);
1259 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1261 my $running = PVE
::LXC
::check_running
($vmid);
1263 my $disk = $param->{disk
};
1264 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::parse_ct_rootfs
($conf->{$disk}) :
1265 PVE
::LXC
::parse_ct_mountpoint
($conf->{$disk});
1267 my $volid = $mp->{volume
};
1269 my (undef, undef, $owner, undef, undef, undef, $format) =
1270 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1272 die "can't resize mountpoint owned by another container ($owner)"
1275 die "can't resize volume: $disk if snapshot exists\n"
1276 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1278 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1280 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1282 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1283 $newsize += $size if $ext;
1284 $newsize = int($newsize);
1286 die "unable to shrink disk size\n" if $newsize < $size;
1288 return if $size == $newsize;
1290 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1292 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1293 # we pass 0 here (parameter only makes sense for qemu)
1294 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1296 $mp->{size
} = $newsize;
1297 $conf->{$disk} = PVE
::LXC
::print_ct_mountpoint
($mp, $disk eq 'rootfs');
1299 PVE
::LXC
::write_config
($vmid, $conf);
1301 if ($format eq 'raw') {
1302 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1306 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1307 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1308 die "internal error: CT running but mountpoint not attached to a loop device"
1310 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1312 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1313 # to be visible to it in its namespace.
1314 # To not interfere with the rest of the system we unshare the current mount namespace,
1315 # mount over /tmp and then run resize2fs.
1317 # interestingly we don't need to e2fsck on mounted systems...
1318 my $quoted = PVE
::Tools
::shellquote
($path);
1319 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1321 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1323 warn "Failed to update the container's filesystem: $@\n" if $@;
1326 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1327 PVE
::Tools
::run_command
(['resize2fs', $path]);
1329 warn "Failed to update the container's filesystem: $@\n" if $@;
1334 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1337 return PVE
::LXC
::lock_container
($vmid, undef, $code);;