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
;
23 use PVE
::JSONSchema
qw(get_standard_option);
24 use base
qw(PVE::RESTHandler);
26 use Data
::Dumper
; # fixme: remove
28 __PACKAGE__-
>register_method ({
29 subclass
=> "PVE::API2::LXC::Config",
30 path
=> '{vmid}/config',
33 __PACKAGE__-
>register_method ({
34 subclass
=> "PVE::API2::LXC::Status",
35 path
=> '{vmid}/status',
38 __PACKAGE__-
>register_method ({
39 subclass
=> "PVE::API2::LXC::Snapshot",
40 path
=> '{vmid}/snapshot',
43 __PACKAGE__-
>register_method ({
44 subclass
=> "PVE::API2::Firewall::CT",
45 path
=> '{vmid}/firewall',
48 __PACKAGE__-
>register_method({
52 description
=> "LXC container index (per node).",
54 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
58 protected
=> 1, # /proc files are only readable by root
60 additionalProperties
=> 0,
62 node
=> get_standard_option
('pve-node'),
71 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
76 my $rpcenv = PVE
::RPCEnvironment
::get
();
77 my $authuser = $rpcenv->get_user();
79 my $vmstatus = PVE
::LXC
::vmstatus
();
82 foreach my $vmid (keys %$vmstatus) {
83 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
85 my $data = $vmstatus->{$vmid};
86 $data->{vmid
} = $vmid;
94 __PACKAGE__-
>register_method({
98 description
=> "Create or restore a container.",
100 user
=> 'all', # check inside
101 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
102 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
103 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
108 additionalProperties
=> 0,
109 properties
=> PVE
::LXC
::json_config_properties
({
110 node
=> get_standard_option
('pve-node'),
111 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
113 description
=> "The OS template or backup file.",
116 completion
=> \
&PVE
::LXC
::complete_os_templates
,
121 description
=> "Sets root password inside container.",
124 storage
=> get_standard_option
('pve-storage-id', {
125 description
=> "Default Storage.",
128 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
133 description
=> "Allow to overwrite existing container.",
138 description
=> "Mark this as restore task.",
142 type
=> 'string', format
=> 'pve-poolid',
143 description
=> "Add the VM to the specified pool.",
145 'ignore-unpack-errors' => {
148 description
=> "Ignore errors when extracting the template.",
158 my $rpcenv = PVE
::RPCEnvironment
::get
();
160 my $authuser = $rpcenv->get_user();
162 my $node = extract_param
($param, 'node');
164 my $vmid = extract_param
($param, 'vmid');
166 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
168 my $basecfg_fn = PVE
::LXC
::config_file
($vmid);
170 my $same_container_exists = -f
$basecfg_fn;
172 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
173 my $unprivileged = extract_param
($param, 'unprivileged');
175 my $restore = extract_param
($param, 'restore');
178 # fixme: limit allowed parameters
182 my $force = extract_param
($param, 'force');
184 if (!($same_container_exists && $restore && $force)) {
185 PVE
::Cluster
::check_vmid_unused
($vmid);
187 my $conf = PVE
::LXC
::load_config
($vmid);
188 PVE
::LXC
::check_protection
($conf, "unable to restore CT $vmid");
191 my $password = extract_param
($param, 'password');
193 my $pool = extract_param
($param, 'pool');
195 if (defined($pool)) {
196 $rpcenv->check_pool_exist($pool);
197 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
200 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
202 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
204 } elsif ($restore && $force && $same_container_exists &&
205 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
206 # OK: user has VM.Backup permissions, and want to restore an existing VM
211 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, [ keys %$param]);
213 my $storage = extract_param
($param, 'storage') // 'local';
215 my $storage_cfg = cfs_read_file
("storage.cfg");
217 my $ostemplate = extract_param
($param, 'ostemplate');
221 if ($ostemplate eq '-') {
222 die "pipe requires cli environment\n"
223 if $rpcenv->{type
} ne 'cli';
224 die "pipe can only be used with restore tasks\n"
227 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
229 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
230 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
233 my $check_and_activate_storage = sub {
236 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
238 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
239 if !$scfg->{content
}->{rootdir
};
241 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
243 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
248 my $no_disk_param = {};
249 foreach my $opt (keys %$param) {
250 my $value = $param->{$opt};
251 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
252 # allow to use simple numbers (add default storage in that case)
253 $param->{$opt} = "$storage:$value" if $value =~ m/^\d+(\.\d+)?$/;
255 $no_disk_param->{$opt} = $value;
259 # check storage access, activate storage
260 PVE
::LXC
::foreach_mountpoint
($param, sub {
261 my ($ms, $mountpoint) = @_;
263 my $volid = $mountpoint->{volume
};
264 my $mp = $mountpoint->{mp
};
266 if ($mountpoint->{type
} ne 'volume') { # bind or device
267 die "Only root can pass arbitrary filesystem paths.\n"
268 if $authuser ne 'root@pam';
270 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
271 &$check_and_activate_storage($sid);
275 # check/activate default storage
276 &$check_and_activate_storage($storage) if !defined($param->{rootfs
});
278 PVE
::LXC
::update_pct_config
($vmid, $conf, 0, $no_disk_param);
280 $conf->{unprivileged
} = 1 if $unprivileged;
282 my $check_vmid_usage = sub {
284 die "can't overwrite running container\n"
285 if PVE
::LXC
::check_running
($vmid);
287 PVE
::Cluster
::check_vmid_unused
($vmid);
292 &$check_vmid_usage(); # final check after locking
294 PVE
::Cluster
::check_cfs_quorum
();
298 if (!defined($param->{rootfs
})) {
300 my (undef, $disksize) = PVE
::LXC
::Create
::recover_config
($archive);
301 die "unable to detect disk size - please specify rootfs (size)\n"
303 $disksize /= 1024 * 1024 * 1024; # create_disks expects GB as unit size
304 $param->{rootfs
} = "$storage:$disksize";
306 $param->{rootfs
} = "$storage:4"; # defaults to 4GB
310 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $param, $conf);
312 PVE
::LXC
::Create
::create_rootfs
($storage_cfg, $vmid, $conf, $archive, $password, $restore, $ignore_unpack_errors);
314 $conf->{hostname
} ||= "CT$vmid";
315 $conf->{memory
} ||= 512;
316 $conf->{swap
} //= 512;
317 PVE
::LXC
::create_config
($vmid, $conf);
320 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
321 PVE
::LXC
::destroy_config
($vmid);
324 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
327 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
329 &$check_vmid_usage(); # first check before locking
331 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
332 $vmid, $authuser, $realcmd);
336 __PACKAGE__-
>register_method({
341 description
=> "Directory index",
346 additionalProperties
=> 0,
348 node
=> get_standard_option
('pve-node'),
349 vmid
=> get_standard_option
('pve-vmid'),
357 subdir
=> { type
=> 'string' },
360 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
366 my $conf = PVE
::LXC
::load_config
($param->{vmid
});
369 { subdir
=> 'config' },
370 { subdir
=> 'status' },
371 { subdir
=> 'vncproxy' },
372 { subdir
=> 'vncwebsocket' },
373 { subdir
=> 'spiceproxy' },
374 { subdir
=> 'migrate' },
375 { subdir
=> 'clone' },
376 # { subdir => 'initlog' },
378 { subdir
=> 'rrddata' },
379 { subdir
=> 'firewall' },
380 { subdir
=> 'snapshot' },
381 { subdir
=> 'resize' },
388 __PACKAGE__-
>register_method({
390 path
=> '{vmid}/rrd',
392 protected
=> 1, # fixme: can we avoid that?
394 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
396 description
=> "Read VM RRD statistics (returns PNG)",
398 additionalProperties
=> 0,
400 node
=> get_standard_option
('pve-node'),
401 vmid
=> get_standard_option
('pve-vmid'),
403 description
=> "Specify the time frame you are interested in.",
405 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
408 description
=> "The list of datasources you want to display.",
409 type
=> 'string', format
=> 'pve-configid-list',
412 description
=> "The RRD consolidation function",
414 enum
=> [ 'AVERAGE', 'MAX' ],
422 filename
=> { type
=> 'string' },
428 return PVE
::Cluster
::create_rrd_graph
(
429 "pve2-vm/$param->{vmid}", $param->{timeframe
},
430 $param->{ds
}, $param->{cf
});
434 __PACKAGE__-
>register_method({
436 path
=> '{vmid}/rrddata',
438 protected
=> 1, # fixme: can we avoid that?
440 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
442 description
=> "Read VM RRD statistics",
444 additionalProperties
=> 0,
446 node
=> get_standard_option
('pve-node'),
447 vmid
=> get_standard_option
('pve-vmid'),
449 description
=> "Specify the time frame you are interested in.",
451 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
454 description
=> "The RRD consolidation function",
456 enum
=> [ 'AVERAGE', 'MAX' ],
471 return PVE
::Cluster
::create_rrd_data
(
472 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
475 __PACKAGE__-
>register_method({
476 name
=> 'destroy_vm',
481 description
=> "Destroy the container (also delete all uses files).",
483 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
486 additionalProperties
=> 0,
488 node
=> get_standard_option
('pve-node'),
489 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
498 my $rpcenv = PVE
::RPCEnvironment
::get
();
500 my $authuser = $rpcenv->get_user();
502 my $vmid = $param->{vmid
};
504 # test if container exists
505 my $conf = PVE
::LXC
::load_config
($vmid);
507 my $storage_cfg = cfs_read_file
("storage.cfg");
509 PVE
::LXC
::check_protection
($conf, "can't remove CT $vmid");
511 die "unable to remove CT $vmid - used in HA resources\n"
512 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
514 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
516 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
519 # reload config after lock
520 $conf = PVE
::LXC
::load_config
($vmid);
521 PVE
::LXC
::check_lock
($conf);
523 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
525 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
526 PVE
::AccessControl
::remove_vm_access
($vmid);
527 PVE
::Firewall
::remove_vmfw_conf
($vmid);
530 my $realcmd = sub { PVE
::LXC
::lock_container
($vmid, 1, $code); };
532 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
537 __PACKAGE__-
>register_method ({
539 path
=> '{vmid}/vncproxy',
543 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
545 description
=> "Creates a TCP VNC proxy connections.",
547 additionalProperties
=> 0,
549 node
=> get_standard_option
('pve-node'),
550 vmid
=> get_standard_option
('pve-vmid'),
554 description
=> "use websocket instead of standard VNC.",
559 additionalProperties
=> 0,
561 user
=> { type
=> 'string' },
562 ticket
=> { type
=> 'string' },
563 cert
=> { type
=> 'string' },
564 port
=> { type
=> 'integer' },
565 upid
=> { type
=> 'string' },
571 my $rpcenv = PVE
::RPCEnvironment
::get
();
573 my $authuser = $rpcenv->get_user();
575 my $vmid = $param->{vmid
};
576 my $node = $param->{node
};
578 my $authpath = "/vms/$vmid";
580 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
582 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
585 my ($remip, $family);
587 if ($node ne PVE
::INotify
::nodename
()) {
588 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
590 $family = PVE
::Tools
::get_host_address_family
($node);
593 my $port = PVE
::Tools
::next_vnc_port
($family);
595 # NOTE: vncterm VNC traffic is already TLS encrypted,
596 # so we select the fastest chipher here (or 'none'?)
597 my $remcmd = $remip ?
598 ['/usr/bin/ssh', '-t', $remip] : [];
600 my $conf = PVE
::LXC
::load_config
($vmid, $node);
601 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
603 my $shcmd = [ '/usr/bin/dtach', '-A',
604 "/var/run/dtach/vzctlconsole$vmid",
605 '-r', 'winch', '-z', @$concmd];
610 syslog
('info', "starting lxc vnc proxy $upid\n");
614 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
615 '-timeout', $timeout, '-authpath', $authpath,
616 '-perm', 'VM.Console'];
618 if ($param->{websocket
}) {
619 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
620 push @$cmd, '-notls', '-listen', 'localhost';
623 push @$cmd, '-c', @$remcmd, @$shcmd;
630 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
632 PVE
::Tools
::wait_for_vnc_port
($port);
643 __PACKAGE__-
>register_method({
644 name
=> 'vncwebsocket',
645 path
=> '{vmid}/vncwebsocket',
648 description
=> "You also need to pass a valid ticket (vncticket).",
649 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
651 description
=> "Opens a weksocket for VNC traffic.",
653 additionalProperties
=> 0,
655 node
=> get_standard_option
('pve-node'),
656 vmid
=> get_standard_option
('pve-vmid'),
658 description
=> "Ticket from previous call to vncproxy.",
663 description
=> "Port number returned by previous vncproxy call.",
673 port
=> { type
=> 'string' },
679 my $rpcenv = PVE
::RPCEnvironment
::get
();
681 my $authuser = $rpcenv->get_user();
683 my $authpath = "/vms/$param->{vmid}";
685 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
687 my $port = $param->{port
};
689 return { port
=> $port };
692 __PACKAGE__-
>register_method ({
693 name
=> 'spiceproxy',
694 path
=> '{vmid}/spiceproxy',
699 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
701 description
=> "Returns a SPICE configuration to connect to the CT.",
703 additionalProperties
=> 0,
705 node
=> get_standard_option
('pve-node'),
706 vmid
=> get_standard_option
('pve-vmid'),
707 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
710 returns
=> get_standard_option
('remote-viewer-config'),
714 my $vmid = $param->{vmid
};
715 my $node = $param->{node
};
716 my $proxy = $param->{proxy
};
718 my $authpath = "/vms/$vmid";
719 my $permissions = 'VM.Console';
721 my $conf = PVE
::LXC
::load_config
($vmid);
723 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
725 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
727 my $shcmd = ['/usr/bin/dtach', '-A',
728 "/var/run/dtach/vzctlconsole$vmid",
729 '-r', 'winch', '-z', @$concmd];
731 my $title = "CT $vmid";
733 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
737 __PACKAGE__-
>register_method({
738 name
=> 'migrate_vm',
739 path
=> '{vmid}/migrate',
743 description
=> "Migrate the container to another node. Creates a new migration task.",
745 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
748 additionalProperties
=> 0,
750 node
=> get_standard_option
('pve-node'),
751 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
752 target
=> get_standard_option
('pve-node', {
753 description
=> "Target node.",
754 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
758 description
=> "Use online/live migration.",
765 description
=> "the task ID.",
770 my $rpcenv = PVE
::RPCEnvironment
::get
();
772 my $authuser = $rpcenv->get_user();
774 my $target = extract_param
($param, 'target');
776 my $localnode = PVE
::INotify
::nodename
();
777 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
779 PVE
::Cluster
::check_cfs_quorum
();
781 PVE
::Cluster
::check_node_exists
($target);
783 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
785 my $vmid = extract_param
($param, 'vmid');
788 PVE
::LXC
::load_config
($vmid);
790 # try to detect errors early
791 if (PVE
::LXC
::check_running
($vmid)) {
792 die "can't migrate running container without --online\n"
793 if !$param->{online
};
796 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
801 my $service = "ct:$vmid";
803 my $cmd = ['ha-manager', 'migrate', $service, $target];
805 print "Executing HA migrate for CT $vmid to node $target\n";
807 PVE
::Tools
::run_command
($cmd);
812 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
819 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
824 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
828 __PACKAGE__-
>register_method({
829 name
=> 'vm_feature',
830 path
=> '{vmid}/feature',
834 description
=> "Check if feature for virtual machine is available.",
836 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
839 additionalProperties
=> 0,
841 node
=> get_standard_option
('pve-node'),
842 vmid
=> get_standard_option
('pve-vmid'),
844 description
=> "Feature to check.",
846 enum
=> [ 'snapshot' ],
848 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
856 hasFeature
=> { type
=> 'boolean' },
859 #items => { type => 'string' },
866 my $node = extract_param
($param, 'node');
868 my $vmid = extract_param
($param, 'vmid');
870 my $snapname = extract_param
($param, 'snapname');
872 my $feature = extract_param
($param, 'feature');
874 my $conf = PVE
::LXC
::load_config
($vmid);
877 my $snap = $conf->{snapshots
}->{$snapname};
878 die "snapshot '$snapname' does not exist\n" if !defined($snap);
881 my $storage_cfg = PVE
::Storage
::config
();
883 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
884 my $hasFeature = PVE
::LXC
::has_feature
($feature, $conf, $storage_cfg, $snapname);
887 hasFeature
=> $hasFeature,
888 #nodes => [ keys %$nodelist ],
892 __PACKAGE__-
>register_method({
894 path
=> '{vmid}/template',
898 description
=> "Create a Template.",
900 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
901 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
904 additionalProperties
=> 0,
906 node
=> get_standard_option
('pve-node'),
907 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
910 returns
=> { type
=> 'null'},
914 my $rpcenv = PVE
::RPCEnvironment
::get
();
916 my $authuser = $rpcenv->get_user();
918 my $node = extract_param
($param, 'node');
920 my $vmid = extract_param
($param, 'vmid');
924 my $conf = PVE
::LXC
::load_config
($vmid);
925 PVE
::LXC
::check_lock
($conf);
927 die "unable to create template, because CT contains snapshots\n"
928 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
930 die "you can't convert a template to a template\n"
931 if PVE
::LXC
::is_template
($conf);
933 die "you can't convert a CT to template if the CT is running\n"
934 if PVE
::LXC
::check_running
($vmid);
937 PVE
::LXC
::template_create
($vmid, $conf);
940 $conf->{template
} = 1;
942 PVE
::LXC
::write_config
($vmid, $conf);
943 # and remove lxc config
944 PVE
::LXC
::update_lxc_config
(undef, $vmid, $conf);
946 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
949 PVE
::LXC
::lock_container
($vmid, undef, $updatefn);
954 __PACKAGE__-
>register_method({
956 path
=> '{vmid}/clone',
960 description
=> "Create a container clone/copy",
962 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
963 "and 'VM.Allocate' permissions " .
964 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
965 "'Datastore.AllocateSpace' on any used storage.",
968 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
970 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
971 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
976 additionalProperties
=> 0,
978 node
=> get_standard_option
('pve-node'),
979 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
980 newid
=> get_standard_option
('pve-vmid', {
981 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
982 description
=> 'VMID for the clone.' }),
985 type
=> 'string', format
=> 'dns-name',
986 description
=> "Set a hostname for the new CT.",
991 description
=> "Description for the new CT.",
995 type
=> 'string', format
=> 'pve-poolid',
996 description
=> "Add the new CT to the specified pool.",
998 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1001 storage
=> get_standard_option
('pve-storage-id', {
1002 description
=> "Target storage for full clone.",
1009 description
=> "Create a full copy of all disk. This is always done when " .
1010 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1013 # target => get_standard_option('pve-node', {
1014 # description => "Target node. Only allowed if the original VM is on shared storage.",
1025 my $rpcenv = PVE
::RPCEnvironment
::get
();
1027 my $authuser = $rpcenv->get_user();
1029 my $node = extract_param
($param, 'node');
1031 my $vmid = extract_param
($param, 'vmid');
1033 my $newid = extract_param
($param, 'newid');
1035 my $pool = extract_param
($param, 'pool');
1037 if (defined($pool)) {
1038 $rpcenv->check_pool_exist($pool);
1041 my $snapname = extract_param
($param, 'snapname');
1043 my $storage = extract_param
($param, 'storage');
1045 my $localnode = PVE
::INotify
::nodename
();
1047 my $storecfg = PVE
::Storage
::config
();
1050 # check if storage is enabled on local node
1051 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1054 PVE
::Cluster
::check_cfs_quorum
();
1056 my $running = PVE
::LXC
::check_running
($vmid) || 0;
1060 # do all tests after lock
1061 # we also try to do all tests before we fork the worker
1062 my $conf = PVE
::LXC
::load_config
($vmid);
1064 PVE
::LXC
::check_lock
($conf);
1066 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1068 die "unexpected state change\n" if $verify_running != $running;
1070 die "snapshot '$snapname' does not exist\n"
1071 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1073 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1075 my $conffile = PVE
::LXC
::config_file
($newid);
1076 die "unable to create CT $newid: config file already exists\n"
1079 my $newconf = { lock => 'clone' };
1080 my $mountpoints = {};
1084 foreach my $opt (keys %$oldconf) {
1085 my $value = $oldconf->{$opt};
1087 # no need to copy unused images, because VMID(owner) changes anyways
1088 next if $opt =~ m/^unused\d+$/;
1090 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1091 my $mp = $opt eq 'rootfs' ?
1092 PVE
::LXC
::parse_ct_rootfs
($value) :
1093 PVE
::LXC
::parse_ct_mountpoint
($value);
1095 if ($mp->{type
} eq 'volume') {
1096 my $volid = $mp->{volume
};
1097 if ($param->{full
}) {
1098 die "fixme: full clone not implemented";
1100 die "Full clone feature for '$volid' is not available\n"
1101 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $volid, $snapname, $running);
1102 $fullclone->{$opt} = 1;
1104 # not full means clone instead of copy
1105 die "Linked clone feature for '$volid' is not available\n"
1106 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1109 $mountpoints->{$opt} = $mp;
1110 push @$vollist, $volid;
1113 # TODO: allow bind mounts?
1114 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1118 # copy everything else
1119 $newconf->{$opt} = $value;
1123 delete $newconf->{template
};
1124 if ($param->{hostname
}) {
1125 $newconf->{hostname
} = $param->{hostname
};
1128 if ($param->{description
}) {
1129 $newconf->{description
} = $param->{description
};
1132 # create empty/temp config - this fails if CT already exists on other node
1133 PVE
::Tools
::file_set_contents
($conffile, "# ctclone temporary file\nlock: clone\n");
1138 my $newvollist = [];
1141 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1143 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1145 foreach my $opt (keys %$mountpoints) {
1146 my $mp = $mountpoints->{$opt};
1147 my $volid = $mp->{volume
};
1149 if ($fullclone->{$opt}) {
1150 die "fixme: full clone not implemented\n";
1152 print "create linked clone of mountpoint $opt ($volid)\n";
1153 my $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1154 push @$newvollist, $newvolid;
1155 $mp->{volume
} = $newvolid;
1157 $newconf->{$opt} = PVE
::LXC
::print_ct_mountpoint
($mp, $opt eq 'rootfs');
1158 PVE
::LXC
::write_config
($newid, $newconf);
1162 delete $newconf->{lock};
1163 PVE
::LXC
::write_config
($newid, $newconf);
1165 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1170 sleep 1; # some storage like rbd need to wait before release volume - really?
1172 foreach my $volid (@$newvollist) {
1173 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1176 die "clone failed: $err";
1182 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1184 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1188 return PVE
::LXC
::lock_container
($vmid, undef, $clonefn);
1192 __PACKAGE__-
>register_method({
1193 name
=> 'resize_vm',
1194 path
=> '{vmid}/resize',
1198 description
=> "Resize a container mountpoint.",
1200 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1203 additionalProperties
=> 0,
1205 node
=> get_standard_option
('pve-node'),
1206 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1209 description
=> "The disk you want to resize.",
1210 enum
=> [PVE
::LXC
::mountpoint_names
()],
1214 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1215 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.",
1219 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1227 description
=> "the task ID.",
1232 my $rpcenv = PVE
::RPCEnvironment
::get
();
1234 my $authuser = $rpcenv->get_user();
1236 my $node = extract_param
($param, 'node');
1238 my $vmid = extract_param
($param, 'vmid');
1240 my $digest = extract_param
($param, 'digest');
1242 my $sizestr = extract_param
($param, 'size');
1243 my $ext = ($sizestr =~ s/^\+//);
1244 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1245 die "invalid size string" if !defined($newsize);
1247 die "no options specified\n" if !scalar(keys %$param);
1249 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, [keys %$param]);
1251 my $storage_cfg = cfs_read_file
("storage.cfg");
1255 my $conf = PVE
::LXC
::load_config
($vmid);
1256 PVE
::LXC
::check_lock
($conf);
1258 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1260 my $running = PVE
::LXC
::check_running
($vmid);
1262 my $disk = $param->{disk
};
1263 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::parse_ct_rootfs
($conf->{$disk}) :
1264 PVE
::LXC
::parse_ct_mountpoint
($conf->{$disk});
1266 my $volid = $mp->{volume
};
1268 my (undef, undef, $owner, undef, undef, undef, $format) =
1269 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1271 die "can't resize mountpoint owned by another container ($owner)"
1274 die "can't resize volume: $disk if snapshot exists\n"
1275 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1277 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1279 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1281 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1282 $newsize += $size if $ext;
1283 $newsize = int($newsize);
1285 die "unable to shrink disk size\n" if $newsize < $size;
1287 return if $size == $newsize;
1289 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1291 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1292 # we pass 0 here (parameter only makes sense for qemu)
1293 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1295 $mp->{size
} = $newsize;
1296 $conf->{$disk} = PVE
::LXC
::print_ct_mountpoint
($mp, $disk eq 'rootfs');
1298 PVE
::LXC
::write_config
($vmid, $conf);
1300 if ($format eq 'raw') {
1301 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1305 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1306 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1307 die "internal error: CT running but mountpoint not attached to a loop device"
1309 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1311 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1312 # to be visible to it in its namespace.
1313 # To not interfere with the rest of the system we unshare the current mount namespace,
1314 # mount over /tmp and then run resize2fs.
1316 # interestingly we don't need to e2fsck on mounted systems...
1317 my $quoted = PVE
::Tools
::shellquote
($path);
1318 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1319 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1321 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1322 PVE
::Tools
::run_command
(['resize2fs', $path]);
1327 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1330 return PVE
::LXC
::lock_container
($vmid, undef, $code);;