1 package PVE
::API2
::LXC
;
7 use PVE
::Tools
qw(extract_param run_command);
8 use PVE
::Exception
qw(raise raise_param_exc);
10 use PVE
::Cluster
qw(cfs_read_file);
11 use PVE
::AccessControl
;
15 use PVE
::RPCEnvironment
;
18 use PVE
::LXC
::Migrate
;
19 use PVE
::API2
::LXC
::Config
;
20 use PVE
::API2
::LXC
::Status
;
21 use PVE
::API2
::LXC
::Snapshot
;
22 use PVE
::HA
::Env
::PVE2
;
24 use PVE
::JSONSchema
qw(get_standard_option);
25 use base
qw(PVE::RESTHandler);
27 use Data
::Dumper
; # fixme: remove
29 __PACKAGE__-
>register_method ({
30 subclass
=> "PVE::API2::LXC::Config",
31 path
=> '{vmid}/config',
34 __PACKAGE__-
>register_method ({
35 subclass
=> "PVE::API2::LXC::Status",
36 path
=> '{vmid}/status',
39 __PACKAGE__-
>register_method ({
40 subclass
=> "PVE::API2::LXC::Snapshot",
41 path
=> '{vmid}/snapshot',
44 __PACKAGE__-
>register_method ({
45 subclass
=> "PVE::API2::Firewall::CT",
46 path
=> '{vmid}/firewall',
49 __PACKAGE__-
>register_method({
53 description
=> "LXC container index (per node).",
55 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
59 protected
=> 1, # /proc files are only readable by root
61 additionalProperties
=> 0,
63 node
=> get_standard_option
('pve-node'),
72 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
77 my $rpcenv = PVE
::RPCEnvironment
::get
();
78 my $authuser = $rpcenv->get_user();
80 my $vmstatus = PVE
::LXC
::vmstatus
();
83 foreach my $vmid (keys %$vmstatus) {
84 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
86 my $data = $vmstatus->{$vmid};
87 $data->{vmid
} = $vmid;
95 __PACKAGE__-
>register_method({
99 description
=> "Create or restore a container.",
101 user
=> 'all', # check inside
102 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
103 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
104 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
109 additionalProperties
=> 0,
110 properties
=> PVE
::LXC
::Config-
>json_config_properties({
111 node
=> get_standard_option
('pve-node'),
112 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
114 description
=> "The OS template or backup file.",
117 completion
=> \
&PVE
::LXC
::complete_os_templates
,
122 description
=> "Sets root password inside container.",
125 storage
=> get_standard_option
('pve-storage-id', {
126 description
=> "Default Storage.",
129 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
134 description
=> "Allow to overwrite existing container.",
139 description
=> "Mark this as restore task.",
143 type
=> 'string', format
=> 'pve-poolid',
144 description
=> "Add the VM to the specified pool.",
146 'ignore-unpack-errors' => {
149 description
=> "Ignore errors when extracting the template.",
151 'ssh-public-keys' => {
154 description
=> "Setup public SSH keys (one key per line, " .
165 my $rpcenv = PVE
::RPCEnvironment
::get
();
167 my $authuser = $rpcenv->get_user();
169 my $node = extract_param
($param, 'node');
171 my $vmid = extract_param
($param, 'vmid');
173 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
175 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
177 my $same_container_exists = -f
$basecfg_fn;
179 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
180 my $unprivileged = extract_param
($param, 'unprivileged');
182 my $restore = extract_param
($param, 'restore');
185 # fixme: limit allowed parameters
189 my $force = extract_param
($param, 'force');
191 if (!($same_container_exists && $restore && $force)) {
192 PVE
::Cluster
::check_vmid_unused
($vmid);
194 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
195 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
198 my $password = extract_param
($param, 'password');
200 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
201 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
203 my $pool = extract_param
($param, 'pool');
205 if (defined($pool)) {
206 $rpcenv->check_pool_exist($pool);
207 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
210 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
212 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
214 } elsif ($restore && $force && $same_container_exists &&
215 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
216 # OK: user has VM.Backup permissions, and want to restore an existing VM
221 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
223 my $storage = extract_param
($param, 'storage') // 'local';
225 my $storage_cfg = cfs_read_file
("storage.cfg");
227 my $ostemplate = extract_param
($param, 'ostemplate');
231 if ($ostemplate eq '-') {
232 die "pipe requires cli environment\n"
233 if $rpcenv->{type
} ne 'cli';
234 die "pipe can only be used with restore tasks\n"
237 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
239 $rpcenv->check_volume_access($authuser, $storage_cfg, $vmid, $ostemplate);
240 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
243 my $check_and_activate_storage = sub {
246 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
248 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
249 if !$scfg->{content
}->{rootdir
};
251 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
253 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
258 my $no_disk_param = {};
260 my $storage_only_mode = 1;
261 foreach my $opt (keys %$param) {
262 my $value = $param->{$opt};
263 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
264 # allow to use simple numbers (add default storage in that case)
265 if ($value =~ m/^\d+(\.\d+)?$/) {
266 $mp_param->{$opt} = "$storage:$value";
268 $mp_param->{$opt} = $value;
270 $storage_only_mode = 0;
271 } elsif ($opt =~ m/^unused\d+$/) {
272 warn "ignoring '$opt', cannot create/restore with unused volume\n";
273 delete $param->{$opt};
275 $no_disk_param->{$opt} = $value;
279 die "mountpoints configured, but 'rootfs' not set - aborting\n"
280 if !$storage_only_mode && !defined($mp_param->{rootfs
});
282 # check storage access, activate storage
283 my $delayed_mp_param = {};
284 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
285 my ($ms, $mountpoint) = @_;
287 my $volid = $mountpoint->{volume
};
288 my $mp = $mountpoint->{mp
};
290 if ($mountpoint->{type
} ne 'volume') { # bind or device
291 die "Only root can pass arbitrary filesystem paths.\n"
292 if $authuser ne 'root@pam';
294 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
295 &$check_and_activate_storage($sid);
299 # check/activate default storage
300 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
302 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
304 $conf->{unprivileged
} = 1 if $unprivileged;
306 my $check_vmid_usage = sub {
308 die "can't overwrite running container\n"
309 if PVE
::LXC
::check_running
($vmid);
311 PVE
::Cluster
::check_vmid_unused
($vmid);
316 &$check_vmid_usage(); # final check after locking
319 my $config_fn = PVE
::LXC
::Config-
>config_file($vmid);
321 die "container exists" if !$restore; # just to be sure
322 $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
325 # try to create empty config on local node, we have an flock
326 PVE
::LXC
::Config-
>write_config($vmid, {});
329 # another node was faster, abort
330 die "Could not reserve ID $vmid, already taken\n" if $@;
333 PVE
::Cluster
::check_cfs_quorum
();
337 if ($storage_only_mode) {
339 (undef, $mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
340 die "rootfs configuration could not be recovered, please check and specify manually!\n"
341 if !defined($mp_param->{rootfs
});
342 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
343 my ($ms, $mountpoint) = @_;
344 my $type = $mountpoint->{type
};
345 if ($type eq 'volume') {
346 die "unable to detect disk size - please specify $ms (size)\n"
347 if !defined($mountpoint->{size
});
348 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
349 delete $mountpoint->{size
};
350 $mountpoint->{volume
} = "$storage:$disksize";
351 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
353 my $type = $mountpoint->{type
};
354 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
355 if ($ms eq 'rootfs');
357 if ($mountpoint->{backup
}) {
358 warn "WARNING - unsupported configuration!\n";
359 warn "backup was enabled for $type mountpoint $ms ('$mountpoint->{mp}')\n";
360 warn "mountpoint configuration will be restored after archive extraction!\n";
361 warn "contained files will be restored to wrong directory!\n";
363 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
367 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
371 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
373 if (defined($old_conf)) {
374 # destroy old container volumes
375 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, {});
379 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
380 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors);
383 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf);
385 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
386 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
387 $lxc_setup->post_create_hook($password, $ssh_keys);
391 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
392 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
395 $conf->{hostname
} ||= "CT$vmid";
396 $conf->{memory
} ||= 512;
397 $conf->{swap
} //= 512;
398 foreach my $mp (keys %$delayed_mp_param) {
399 $conf->{$mp} = $delayed_mp_param->{$mp};
401 PVE
::LXC
::Config-
>write_config($vmid, $conf);
404 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
405 PVE
::LXC
::destroy_config
($vmid);
408 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
411 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
413 &$check_vmid_usage(); # first check before locking
415 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
416 $vmid, $authuser, $realcmd);
420 __PACKAGE__-
>register_method({
425 description
=> "Directory index",
430 additionalProperties
=> 0,
432 node
=> get_standard_option
('pve-node'),
433 vmid
=> get_standard_option
('pve-vmid'),
441 subdir
=> { type
=> 'string' },
444 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
450 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
453 { subdir
=> 'config' },
454 { subdir
=> 'status' },
455 { subdir
=> 'vncproxy' },
456 { subdir
=> 'vncwebsocket' },
457 { subdir
=> 'spiceproxy' },
458 { subdir
=> 'migrate' },
459 { subdir
=> 'clone' },
460 # { subdir => 'initlog' },
462 { subdir
=> 'rrddata' },
463 { subdir
=> 'firewall' },
464 { subdir
=> 'snapshot' },
465 { subdir
=> 'resize' },
472 __PACKAGE__-
>register_method({
474 path
=> '{vmid}/rrd',
476 protected
=> 1, # fixme: can we avoid that?
478 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
480 description
=> "Read VM RRD statistics (returns PNG)",
482 additionalProperties
=> 0,
484 node
=> get_standard_option
('pve-node'),
485 vmid
=> get_standard_option
('pve-vmid'),
487 description
=> "Specify the time frame you are interested in.",
489 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
492 description
=> "The list of datasources you want to display.",
493 type
=> 'string', format
=> 'pve-configid-list',
496 description
=> "The RRD consolidation function",
498 enum
=> [ 'AVERAGE', 'MAX' ],
506 filename
=> { type
=> 'string' },
512 return PVE
::Cluster
::create_rrd_graph
(
513 "pve2-vm/$param->{vmid}", $param->{timeframe
},
514 $param->{ds
}, $param->{cf
});
518 __PACKAGE__-
>register_method({
520 path
=> '{vmid}/rrddata',
522 protected
=> 1, # fixme: can we avoid that?
524 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
526 description
=> "Read VM RRD statistics",
528 additionalProperties
=> 0,
530 node
=> get_standard_option
('pve-node'),
531 vmid
=> get_standard_option
('pve-vmid'),
533 description
=> "Specify the time frame you are interested in.",
535 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
538 description
=> "The RRD consolidation function",
540 enum
=> [ 'AVERAGE', 'MAX' ],
555 return PVE
::Cluster
::create_rrd_data
(
556 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
559 __PACKAGE__-
>register_method({
560 name
=> 'destroy_vm',
565 description
=> "Destroy the container (also delete all uses files).",
567 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
570 additionalProperties
=> 0,
572 node
=> get_standard_option
('pve-node'),
573 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
582 my $rpcenv = PVE
::RPCEnvironment
::get
();
584 my $authuser = $rpcenv->get_user();
586 my $vmid = $param->{vmid
};
588 # test if container exists
589 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
591 my $storage_cfg = cfs_read_file
("storage.cfg");
593 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
595 die "unable to remove CT $vmid - used in HA resources\n"
596 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
598 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
600 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
603 # reload config after lock
604 $conf = PVE
::LXC
::Config-
>load_config($vmid);
605 PVE
::LXC
::Config-
>check_lock($conf);
607 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
609 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
610 PVE
::AccessControl
::remove_vm_access
($vmid);
611 PVE
::Firewall
::remove_vmfw_conf
($vmid);
614 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
616 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
621 __PACKAGE__-
>register_method ({
623 path
=> '{vmid}/vncproxy',
627 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
629 description
=> "Creates a TCP VNC proxy connections.",
631 additionalProperties
=> 0,
633 node
=> get_standard_option
('pve-node'),
634 vmid
=> get_standard_option
('pve-vmid'),
638 description
=> "use websocket instead of standard VNC.",
643 additionalProperties
=> 0,
645 user
=> { type
=> 'string' },
646 ticket
=> { type
=> 'string' },
647 cert
=> { type
=> 'string' },
648 port
=> { type
=> 'integer' },
649 upid
=> { type
=> 'string' },
655 my $rpcenv = PVE
::RPCEnvironment
::get
();
657 my $authuser = $rpcenv->get_user();
659 my $vmid = $param->{vmid
};
660 my $node = $param->{node
};
662 my $authpath = "/vms/$vmid";
664 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
666 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
669 my ($remip, $family);
671 if ($node ne PVE
::INotify
::nodename
()) {
672 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
674 $family = PVE
::Tools
::get_host_address_family
($node);
677 my $port = PVE
::Tools
::next_vnc_port
($family);
679 # NOTE: vncterm VNC traffic is already TLS encrypted,
680 # so we select the fastest chipher here (or 'none'?)
681 my $remcmd = $remip ?
682 ['/usr/bin/ssh', '-t', $remip] : [];
684 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
685 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
687 my $shcmd = [ '/usr/bin/dtach', '-A',
688 "/var/run/dtach/vzctlconsole$vmid",
689 '-r', 'winch', '-z', @$concmd];
694 syslog
('info', "starting lxc vnc proxy $upid\n");
698 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
699 '-timeout', $timeout, '-authpath', $authpath,
700 '-perm', 'VM.Console'];
702 if ($param->{websocket
}) {
703 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
704 push @$cmd, '-notls', '-listen', 'localhost';
707 push @$cmd, '-c', @$remcmd, @$shcmd;
714 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
716 PVE
::Tools
::wait_for_vnc_port
($port);
727 __PACKAGE__-
>register_method({
728 name
=> 'vncwebsocket',
729 path
=> '{vmid}/vncwebsocket',
732 description
=> "You also need to pass a valid ticket (vncticket).",
733 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
735 description
=> "Opens a weksocket for VNC traffic.",
737 additionalProperties
=> 0,
739 node
=> get_standard_option
('pve-node'),
740 vmid
=> get_standard_option
('pve-vmid'),
742 description
=> "Ticket from previous call to vncproxy.",
747 description
=> "Port number returned by previous vncproxy call.",
757 port
=> { type
=> 'string' },
763 my $rpcenv = PVE
::RPCEnvironment
::get
();
765 my $authuser = $rpcenv->get_user();
767 my $authpath = "/vms/$param->{vmid}";
769 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
771 my $port = $param->{port
};
773 return { port
=> $port };
776 __PACKAGE__-
>register_method ({
777 name
=> 'spiceproxy',
778 path
=> '{vmid}/spiceproxy',
783 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
785 description
=> "Returns a SPICE configuration to connect to the CT.",
787 additionalProperties
=> 0,
789 node
=> get_standard_option
('pve-node'),
790 vmid
=> get_standard_option
('pve-vmid'),
791 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
794 returns
=> get_standard_option
('remote-viewer-config'),
798 my $vmid = $param->{vmid
};
799 my $node = $param->{node
};
800 my $proxy = $param->{proxy
};
802 my $authpath = "/vms/$vmid";
803 my $permissions = 'VM.Console';
805 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
807 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
809 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
811 my $shcmd = ['/usr/bin/dtach', '-A',
812 "/var/run/dtach/vzctlconsole$vmid",
813 '-r', 'winch', '-z', @$concmd];
815 my $title = "CT $vmid";
817 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
821 __PACKAGE__-
>register_method({
822 name
=> 'migrate_vm',
823 path
=> '{vmid}/migrate',
827 description
=> "Migrate the container to another node. Creates a new migration task.",
829 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
832 additionalProperties
=> 0,
834 node
=> get_standard_option
('pve-node'),
835 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
836 target
=> get_standard_option
('pve-node', {
837 description
=> "Target node.",
838 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
842 description
=> "Use online/live migration.",
847 description
=> "Force migration despite local bind / device" .
848 " mounts. WARNING: identical bind / device mounts need to ".
849 " be available on the target node.",
856 description
=> "the task ID.",
861 my $rpcenv = PVE
::RPCEnvironment
::get
();
863 my $authuser = $rpcenv->get_user();
865 my $target = extract_param
($param, 'target');
867 my $localnode = PVE
::INotify
::nodename
();
868 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
870 PVE
::Cluster
::check_cfs_quorum
();
872 PVE
::Cluster
::check_node_exists
($target);
874 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
876 my $vmid = extract_param
($param, 'vmid');
879 PVE
::LXC
::Config-
>load_config($vmid);
881 # try to detect errors early
882 if (PVE
::LXC
::check_running
($vmid)) {
883 die "can't migrate running container without --online\n"
884 if !$param->{online
};
887 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
892 my $service = "ct:$vmid";
894 my $cmd = ['ha-manager', 'migrate', $service, $target];
896 print "Executing HA migrate for CT $vmid to node $target\n";
898 PVE
::Tools
::run_command
($cmd);
903 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
910 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
915 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
919 __PACKAGE__-
>register_method({
920 name
=> 'vm_feature',
921 path
=> '{vmid}/feature',
925 description
=> "Check if feature for virtual machine is available.",
927 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
930 additionalProperties
=> 0,
932 node
=> get_standard_option
('pve-node'),
933 vmid
=> get_standard_option
('pve-vmid'),
935 description
=> "Feature to check.",
937 enum
=> [ 'snapshot' ],
939 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
947 hasFeature
=> { type
=> 'boolean' },
950 #items => { type => 'string' },
957 my $node = extract_param
($param, 'node');
959 my $vmid = extract_param
($param, 'vmid');
961 my $snapname = extract_param
($param, 'snapname');
963 my $feature = extract_param
($param, 'feature');
965 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
968 my $snap = $conf->{snapshots
}->{$snapname};
969 die "snapshot '$snapname' does not exist\n" if !defined($snap);
972 my $storage_cfg = PVE
::Storage
::config
();
974 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
975 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
978 hasFeature
=> $hasFeature,
979 #nodes => [ keys %$nodelist ],
983 __PACKAGE__-
>register_method({
985 path
=> '{vmid}/template',
989 description
=> "Create a Template.",
991 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
992 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
995 additionalProperties
=> 0,
997 node
=> get_standard_option
('pve-node'),
998 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1001 description
=> "The template feature is experimental, set this " .
1002 "flag if you know what you are doing.",
1007 returns
=> { type
=> 'null'},
1011 my $rpcenv = PVE
::RPCEnvironment
::get
();
1013 my $authuser = $rpcenv->get_user();
1015 my $node = extract_param
($param, 'node');
1017 my $vmid = extract_param
($param, 'vmid');
1019 my $updatefn = sub {
1021 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1022 PVE
::LXC
::Config-
>check_lock($conf);
1024 die "unable to create template, because CT contains snapshots\n"
1025 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1027 die "you can't convert a template to a template\n"
1028 if PVE
::LXC
::Config-
>is_template($conf);
1030 die "you can't convert a CT to template if the CT is running\n"
1031 if PVE
::LXC
::check_running
($vmid);
1034 PVE
::LXC
::template_create
($vmid, $conf);
1037 $conf->{template
} = 1;
1039 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1040 # and remove lxc config
1041 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1043 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1046 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1051 __PACKAGE__-
>register_method({
1053 path
=> '{vmid}/clone',
1057 description
=> "Create a container clone/copy",
1059 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1060 "and 'VM.Allocate' permissions " .
1061 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1062 "'Datastore.AllocateSpace' on any used storage.",
1065 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1067 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1068 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1073 additionalProperties
=> 0,
1075 node
=> get_standard_option
('pve-node'),
1076 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1077 newid
=> get_standard_option
('pve-vmid', {
1078 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1079 description
=> 'VMID for the clone.' }),
1082 type
=> 'string', format
=> 'dns-name',
1083 description
=> "Set a hostname for the new CT.",
1088 description
=> "Description for the new CT.",
1092 type
=> 'string', format
=> 'pve-poolid',
1093 description
=> "Add the new CT to the specified pool.",
1095 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1098 storage
=> get_standard_option
('pve-storage-id', {
1099 description
=> "Target storage for full clone.",
1106 description
=> "Create a full copy of all disk. This is always done when " .
1107 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1112 description
=> "The clone feature is experimental, set this " .
1113 "flag if you know what you are doing.",
1116 # target => get_standard_option('pve-node', {
1117 # description => "Target node. Only allowed if the original VM is on shared storage.",
1128 my $rpcenv = PVE
::RPCEnvironment
::get
();
1130 my $authuser = $rpcenv->get_user();
1132 my $node = extract_param
($param, 'node');
1134 my $vmid = extract_param
($param, 'vmid');
1136 my $newid = extract_param
($param, 'newid');
1138 my $pool = extract_param
($param, 'pool');
1140 if (defined($pool)) {
1141 $rpcenv->check_pool_exist($pool);
1144 my $snapname = extract_param
($param, 'snapname');
1146 my $storage = extract_param
($param, 'storage');
1148 my $localnode = PVE
::INotify
::nodename
();
1150 my $storecfg = PVE
::Storage
::config
();
1153 # check if storage is enabled on local node
1154 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1157 PVE
::Cluster
::check_cfs_quorum
();
1159 my $running = PVE
::LXC
::check_running
($vmid) || 0;
1163 # do all tests after lock
1164 # we also try to do all tests before we fork the worker
1165 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1167 PVE
::LXC
::Config-
>check_lock($conf);
1169 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1171 die "unexpected state change\n" if $verify_running != $running;
1173 die "snapshot '$snapname' does not exist\n"
1174 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1176 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1178 my $conffile = PVE
::LXC
::Config-
>config_file($newid);
1179 die "unable to create CT $newid: config file already exists\n"
1182 my $newconf = { lock => 'clone' };
1183 my $mountpoints = {};
1187 foreach my $opt (keys %$oldconf) {
1188 my $value = $oldconf->{$opt};
1190 # no need to copy unused images, because VMID(owner) changes anyways
1191 next if $opt =~ m/^unused\d+$/;
1193 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1194 my $mp = $opt eq 'rootfs' ?
1195 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1196 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1198 if ($mp->{type
} eq 'volume') {
1199 my $volid = $mp->{volume
};
1200 if ($param->{full
}) {
1201 die "fixme: full clone not implemented";
1203 die "Full clone feature for '$volid' is not available\n"
1204 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $volid, $snapname, $running);
1205 $fullclone->{$opt} = 1;
1207 # not full means clone instead of copy
1208 die "Linked clone feature for '$volid' is not available\n"
1209 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1212 $mountpoints->{$opt} = $mp;
1213 push @$vollist, $volid;
1216 # TODO: allow bind mounts?
1217 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1221 # copy everything else
1222 $newconf->{$opt} = $value;
1226 delete $newconf->{template
};
1227 if ($param->{hostname
}) {
1228 $newconf->{hostname
} = $param->{hostname
};
1231 if ($param->{description
}) {
1232 $newconf->{description
} = $param->{description
};
1235 # create empty/temp config - this fails if CT already exists on other node
1236 PVE
::Tools
::file_set_contents
($conffile, "# ctclone temporary file\nlock: clone\n");
1241 my $newvollist = [];
1244 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1246 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1248 foreach my $opt (keys %$mountpoints) {
1249 my $mp = $mountpoints->{$opt};
1250 my $volid = $mp->{volume
};
1252 if ($fullclone->{$opt}) {
1253 die "fixme: full clone not implemented\n";
1255 print "create linked clone of mountpoint $opt ($volid)\n";
1256 my $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1257 push @$newvollist, $newvolid;
1258 $mp->{volume
} = $newvolid;
1260 $newconf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs');
1261 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1265 delete $newconf->{lock};
1266 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1268 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1273 sleep 1; # some storage like rbd need to wait before release volume - really?
1275 foreach my $volid (@$newvollist) {
1276 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1279 die "clone failed: $err";
1285 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1287 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1291 return PVE
::LXC
::Config-
>lock_config($vmid, $clonefn);
1295 __PACKAGE__-
>register_method({
1296 name
=> 'resize_vm',
1297 path
=> '{vmid}/resize',
1301 description
=> "Resize a container mountpoint.",
1303 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1306 additionalProperties
=> 0,
1308 node
=> get_standard_option
('pve-node'),
1309 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1312 description
=> "The disk you want to resize.",
1313 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1317 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1318 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.",
1322 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1330 description
=> "the task ID.",
1335 my $rpcenv = PVE
::RPCEnvironment
::get
();
1337 my $authuser = $rpcenv->get_user();
1339 my $node = extract_param
($param, 'node');
1341 my $vmid = extract_param
($param, 'vmid');
1343 my $digest = extract_param
($param, 'digest');
1345 my $sizestr = extract_param
($param, 'size');
1346 my $ext = ($sizestr =~ s/^\+//);
1347 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1348 die "invalid size string" if !defined($newsize);
1350 die "no options specified\n" if !scalar(keys %$param);
1352 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1354 my $storage_cfg = cfs_read_file
("storage.cfg");
1358 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1359 PVE
::LXC
::Config-
>check_lock($conf);
1361 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1363 my $running = PVE
::LXC
::check_running
($vmid);
1365 my $disk = $param->{disk
};
1366 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1367 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1369 my $volid = $mp->{volume
};
1371 my (undef, undef, $owner, undef, undef, undef, $format) =
1372 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1374 die "can't resize mountpoint owned by another container ($owner)"
1377 die "can't resize volume: $disk if snapshot exists\n"
1378 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1380 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1382 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1384 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1386 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1387 $newsize += $size if $ext;
1388 $newsize = int($newsize);
1390 die "unable to shrink disk size\n" if $newsize < $size;
1392 return if $size == $newsize;
1394 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1396 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1397 # we pass 0 here (parameter only makes sense for qemu)
1398 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1400 $mp->{size
} = $newsize;
1401 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1403 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1405 if ($format eq 'raw') {
1406 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1410 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1411 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1412 die "internal error: CT running but mountpoint not attached to a loop device"
1414 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1416 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1417 # to be visible to it in its namespace.
1418 # To not interfere with the rest of the system we unshare the current mount namespace,
1419 # mount over /tmp and then run resize2fs.
1421 # interestingly we don't need to e2fsck on mounted systems...
1422 my $quoted = PVE
::Tools
::shellquote
($path);
1423 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1425 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1427 warn "Failed to update the container's filesystem: $@\n" if $@;
1430 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1431 PVE
::Tools
::run_command
(['resize2fs', $path]);
1433 warn "Failed to update the container's filesystem: $@\n" if $@;
1438 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1441 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;