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.",
764 description
=> "Force migration despite local bind / device" .
765 " mounts. WARNING: identical bind / device mounts need to ".
766 " be available on the target node.",
773 description
=> "the task ID.",
778 my $rpcenv = PVE
::RPCEnvironment
::get
();
780 my $authuser = $rpcenv->get_user();
782 my $target = extract_param
($param, 'target');
784 my $localnode = PVE
::INotify
::nodename
();
785 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
787 PVE
::Cluster
::check_cfs_quorum
();
789 PVE
::Cluster
::check_node_exists
($target);
791 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
793 my $vmid = extract_param
($param, 'vmid');
796 PVE
::LXC
::Config-
>load_config($vmid);
798 # try to detect errors early
799 if (PVE
::LXC
::check_running
($vmid)) {
800 die "can't migrate running container without --online\n"
801 if !$param->{online
};
804 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
809 my $service = "ct:$vmid";
811 my $cmd = ['ha-manager', 'migrate', $service, $target];
813 print "Executing HA migrate for CT $vmid to node $target\n";
815 PVE
::Tools
::run_command
($cmd);
820 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
827 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
832 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
836 __PACKAGE__-
>register_method({
837 name
=> 'vm_feature',
838 path
=> '{vmid}/feature',
842 description
=> "Check if feature for virtual machine is available.",
844 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
847 additionalProperties
=> 0,
849 node
=> get_standard_option
('pve-node'),
850 vmid
=> get_standard_option
('pve-vmid'),
852 description
=> "Feature to check.",
854 enum
=> [ 'snapshot' ],
856 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
864 hasFeature
=> { type
=> 'boolean' },
867 #items => { type => 'string' },
874 my $node = extract_param
($param, 'node');
876 my $vmid = extract_param
($param, 'vmid');
878 my $snapname = extract_param
($param, 'snapname');
880 my $feature = extract_param
($param, 'feature');
882 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
885 my $snap = $conf->{snapshots
}->{$snapname};
886 die "snapshot '$snapname' does not exist\n" if !defined($snap);
889 my $storage_cfg = PVE
::Storage
::config
();
891 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
892 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
895 hasFeature
=> $hasFeature,
896 #nodes => [ keys %$nodelist ],
900 __PACKAGE__-
>register_method({
902 path
=> '{vmid}/template',
906 description
=> "Create a Template.",
908 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
909 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
912 additionalProperties
=> 0,
914 node
=> get_standard_option
('pve-node'),
915 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
918 description
=> "The template feature is experimental, set this " .
919 "flag if you know what you are doing.",
924 returns
=> { type
=> 'null'},
928 my $rpcenv = PVE
::RPCEnvironment
::get
();
930 my $authuser = $rpcenv->get_user();
932 my $node = extract_param
($param, 'node');
934 my $vmid = extract_param
($param, 'vmid');
938 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
939 PVE
::LXC
::Config-
>check_lock($conf);
941 die "unable to create template, because CT contains snapshots\n"
942 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
944 die "you can't convert a template to a template\n"
945 if PVE
::LXC
::Config-
>is_template($conf);
947 die "you can't convert a CT to template if the CT is running\n"
948 if PVE
::LXC
::check_running
($vmid);
951 PVE
::LXC
::template_create
($vmid, $conf);
954 $conf->{template
} = 1;
956 PVE
::LXC
::Config-
>write_config($vmid, $conf);
957 # and remove lxc config
958 PVE
::LXC
::update_lxc_config
(undef, $vmid, $conf);
960 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
963 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
968 __PACKAGE__-
>register_method({
970 path
=> '{vmid}/clone',
974 description
=> "Create a container clone/copy",
976 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
977 "and 'VM.Allocate' permissions " .
978 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
979 "'Datastore.AllocateSpace' on any used storage.",
982 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
984 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
985 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
990 additionalProperties
=> 0,
992 node
=> get_standard_option
('pve-node'),
993 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
994 newid
=> get_standard_option
('pve-vmid', {
995 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
996 description
=> 'VMID for the clone.' }),
999 type
=> 'string', format
=> 'dns-name',
1000 description
=> "Set a hostname for the new CT.",
1005 description
=> "Description for the new CT.",
1009 type
=> 'string', format
=> 'pve-poolid',
1010 description
=> "Add the new CT to the specified pool.",
1012 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1015 storage
=> get_standard_option
('pve-storage-id', {
1016 description
=> "Target storage for full clone.",
1023 description
=> "Create a full copy of all disk. This is always done when " .
1024 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1029 description
=> "The clone feature is experimental, set this " .
1030 "flag if you know what you are doing.",
1033 # target => get_standard_option('pve-node', {
1034 # description => "Target node. Only allowed if the original VM is on shared storage.",
1045 my $rpcenv = PVE
::RPCEnvironment
::get
();
1047 my $authuser = $rpcenv->get_user();
1049 my $node = extract_param
($param, 'node');
1051 my $vmid = extract_param
($param, 'vmid');
1053 my $newid = extract_param
($param, 'newid');
1055 my $pool = extract_param
($param, 'pool');
1057 if (defined($pool)) {
1058 $rpcenv->check_pool_exist($pool);
1061 my $snapname = extract_param
($param, 'snapname');
1063 my $storage = extract_param
($param, 'storage');
1065 my $localnode = PVE
::INotify
::nodename
();
1067 my $storecfg = PVE
::Storage
::config
();
1070 # check if storage is enabled on local node
1071 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1074 PVE
::Cluster
::check_cfs_quorum
();
1076 my $running = PVE
::LXC
::check_running
($vmid) || 0;
1080 # do all tests after lock
1081 # we also try to do all tests before we fork the worker
1082 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1084 PVE
::LXC
::Config-
>check_lock($conf);
1086 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1088 die "unexpected state change\n" if $verify_running != $running;
1090 die "snapshot '$snapname' does not exist\n"
1091 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1093 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1095 my $conffile = PVE
::LXC
::Config-
>config_file($newid);
1096 die "unable to create CT $newid: config file already exists\n"
1099 my $newconf = { lock => 'clone' };
1100 my $mountpoints = {};
1104 foreach my $opt (keys %$oldconf) {
1105 my $value = $oldconf->{$opt};
1107 # no need to copy unused images, because VMID(owner) changes anyways
1108 next if $opt =~ m/^unused\d+$/;
1110 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1111 my $mp = $opt eq 'rootfs' ?
1112 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1113 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1115 if ($mp->{type
} eq 'volume') {
1116 my $volid = $mp->{volume
};
1117 if ($param->{full
}) {
1118 die "fixme: full clone not implemented";
1120 die "Full clone feature for '$volid' is not available\n"
1121 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $volid, $snapname, $running);
1122 $fullclone->{$opt} = 1;
1124 # not full means clone instead of copy
1125 die "Linked clone feature for '$volid' is not available\n"
1126 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1129 $mountpoints->{$opt} = $mp;
1130 push @$vollist, $volid;
1133 # TODO: allow bind mounts?
1134 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1138 # copy everything else
1139 $newconf->{$opt} = $value;
1143 delete $newconf->{template
};
1144 if ($param->{hostname
}) {
1145 $newconf->{hostname
} = $param->{hostname
};
1148 if ($param->{description
}) {
1149 $newconf->{description
} = $param->{description
};
1152 # create empty/temp config - this fails if CT already exists on other node
1153 PVE
::Tools
::file_set_contents
($conffile, "# ctclone temporary file\nlock: clone\n");
1158 my $newvollist = [];
1161 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1163 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1165 foreach my $opt (keys %$mountpoints) {
1166 my $mp = $mountpoints->{$opt};
1167 my $volid = $mp->{volume
};
1169 if ($fullclone->{$opt}) {
1170 die "fixme: full clone not implemented\n";
1172 print "create linked clone of mountpoint $opt ($volid)\n";
1173 my $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1174 push @$newvollist, $newvolid;
1175 $mp->{volume
} = $newvolid;
1177 $newconf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs');
1178 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1182 delete $newconf->{lock};
1183 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1185 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1190 sleep 1; # some storage like rbd need to wait before release volume - really?
1192 foreach my $volid (@$newvollist) {
1193 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1196 die "clone failed: $err";
1202 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1204 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1208 return PVE
::LXC
::Config-
>lock_config($vmid, $clonefn);
1212 __PACKAGE__-
>register_method({
1213 name
=> 'resize_vm',
1214 path
=> '{vmid}/resize',
1218 description
=> "Resize a container mountpoint.",
1220 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1223 additionalProperties
=> 0,
1225 node
=> get_standard_option
('pve-node'),
1226 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1229 description
=> "The disk you want to resize.",
1230 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1234 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1235 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.",
1239 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1247 description
=> "the task ID.",
1252 my $rpcenv = PVE
::RPCEnvironment
::get
();
1254 my $authuser = $rpcenv->get_user();
1256 my $node = extract_param
($param, 'node');
1258 my $vmid = extract_param
($param, 'vmid');
1260 my $digest = extract_param
($param, 'digest');
1262 my $sizestr = extract_param
($param, 'size');
1263 my $ext = ($sizestr =~ s/^\+//);
1264 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1265 die "invalid size string" if !defined($newsize);
1267 die "no options specified\n" if !scalar(keys %$param);
1269 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1271 my $storage_cfg = cfs_read_file
("storage.cfg");
1275 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1276 PVE
::LXC
::Config-
>check_lock($conf);
1278 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1280 my $running = PVE
::LXC
::check_running
($vmid);
1282 my $disk = $param->{disk
};
1283 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1284 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1286 my $volid = $mp->{volume
};
1288 my (undef, undef, $owner, undef, undef, undef, $format) =
1289 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1291 die "can't resize mountpoint owned by another container ($owner)"
1294 die "can't resize volume: $disk if snapshot exists\n"
1295 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1297 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1299 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1301 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1302 $newsize += $size if $ext;
1303 $newsize = int($newsize);
1305 die "unable to shrink disk size\n" if $newsize < $size;
1307 return if $size == $newsize;
1309 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1311 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1312 # we pass 0 here (parameter only makes sense for qemu)
1313 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1315 $mp->{size
} = $newsize;
1316 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1318 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1320 if ($format eq 'raw') {
1321 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1325 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1326 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1327 die "internal error: CT running but mountpoint not attached to a loop device"
1329 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1331 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1332 # to be visible to it in its namespace.
1333 # To not interfere with the rest of the system we unshare the current mount namespace,
1334 # mount over /tmp and then run resize2fs.
1336 # interestingly we don't need to e2fsck on mounted systems...
1337 my $quoted = PVE
::Tools
::shellquote
($path);
1338 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1340 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1342 warn "Failed to update the container's filesystem: $@\n" if $@;
1345 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1346 PVE
::Tools
::run_command
(['resize2fs', $path]);
1348 warn "Failed to update the container's filesystem: $@\n" if $@;
1353 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1356 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;