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 "mount points 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');
356 die "restoring '$ms' to $type mount is only possible for root\n"
357 if $authuser ne 'root@pam';
359 if ($mountpoint->{backup
}) {
360 warn "WARNING - unsupported configuration!\n";
361 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
362 warn "mount point configuration will be restored after archive extraction!\n";
363 warn "contained files will be restored to wrong directory!\n";
365 delete $mp_param->{$ms}; # actually delay bind/dev mps
366 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
370 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
374 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
376 if (defined($old_conf)) {
377 # destroy old container volumes
378 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, {});
382 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
383 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors);
386 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, $authuser ne 'root@pam');
388 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
389 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
390 $lxc_setup->post_create_hook($password, $ssh_keys);
394 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
395 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
398 $conf->{hostname
} ||= "CT$vmid";
399 $conf->{memory
} ||= 512;
400 $conf->{swap
} //= 512;
401 foreach my $mp (keys %$delayed_mp_param) {
402 $conf->{$mp} = $delayed_mp_param->{$mp};
404 PVE
::LXC
::Config-
>write_config($vmid, $conf);
407 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
408 PVE
::LXC
::destroy_config
($vmid);
411 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
414 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
416 &$check_vmid_usage(); # first check before locking
418 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
419 $vmid, $authuser, $realcmd);
423 __PACKAGE__-
>register_method({
428 description
=> "Directory index",
433 additionalProperties
=> 0,
435 node
=> get_standard_option
('pve-node'),
436 vmid
=> get_standard_option
('pve-vmid'),
444 subdir
=> { type
=> 'string' },
447 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
453 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
456 { subdir
=> 'config' },
457 { subdir
=> 'status' },
458 { subdir
=> 'vncproxy' },
459 { subdir
=> 'vncwebsocket' },
460 { subdir
=> 'spiceproxy' },
461 { subdir
=> 'migrate' },
462 { subdir
=> 'clone' },
463 # { subdir => 'initlog' },
465 { subdir
=> 'rrddata' },
466 { subdir
=> 'firewall' },
467 { subdir
=> 'snapshot' },
468 { subdir
=> 'resize' },
475 __PACKAGE__-
>register_method({
477 path
=> '{vmid}/rrd',
479 protected
=> 1, # fixme: can we avoid that?
481 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
483 description
=> "Read VM RRD statistics (returns PNG)",
485 additionalProperties
=> 0,
487 node
=> get_standard_option
('pve-node'),
488 vmid
=> get_standard_option
('pve-vmid'),
490 description
=> "Specify the time frame you are interested in.",
492 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
495 description
=> "The list of datasources you want to display.",
496 type
=> 'string', format
=> 'pve-configid-list',
499 description
=> "The RRD consolidation function",
501 enum
=> [ 'AVERAGE', 'MAX' ],
509 filename
=> { type
=> 'string' },
515 return PVE
::Cluster
::create_rrd_graph
(
516 "pve2-vm/$param->{vmid}", $param->{timeframe
},
517 $param->{ds
}, $param->{cf
});
521 __PACKAGE__-
>register_method({
523 path
=> '{vmid}/rrddata',
525 protected
=> 1, # fixme: can we avoid that?
527 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
529 description
=> "Read VM RRD statistics",
531 additionalProperties
=> 0,
533 node
=> get_standard_option
('pve-node'),
534 vmid
=> get_standard_option
('pve-vmid'),
536 description
=> "Specify the time frame you are interested in.",
538 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
541 description
=> "The RRD consolidation function",
543 enum
=> [ 'AVERAGE', 'MAX' ],
558 return PVE
::Cluster
::create_rrd_data
(
559 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
562 __PACKAGE__-
>register_method({
563 name
=> 'destroy_vm',
568 description
=> "Destroy the container (also delete all uses files).",
570 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
573 additionalProperties
=> 0,
575 node
=> get_standard_option
('pve-node'),
576 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
585 my $rpcenv = PVE
::RPCEnvironment
::get
();
587 my $authuser = $rpcenv->get_user();
589 my $vmid = $param->{vmid
};
591 # test if container exists
592 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
594 my $storage_cfg = cfs_read_file
("storage.cfg");
596 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
598 die "unable to remove CT $vmid - used in HA resources\n"
599 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
601 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
603 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
606 # reload config after lock
607 $conf = PVE
::LXC
::Config-
>load_config($vmid);
608 PVE
::LXC
::Config-
>check_lock($conf);
610 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
612 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
613 PVE
::AccessControl
::remove_vm_access
($vmid);
614 PVE
::Firewall
::remove_vmfw_conf
($vmid);
617 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
619 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
624 __PACKAGE__-
>register_method ({
626 path
=> '{vmid}/vncproxy',
630 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
632 description
=> "Creates a TCP VNC proxy connections.",
634 additionalProperties
=> 0,
636 node
=> get_standard_option
('pve-node'),
637 vmid
=> get_standard_option
('pve-vmid'),
641 description
=> "use websocket instead of standard VNC.",
646 additionalProperties
=> 0,
648 user
=> { type
=> 'string' },
649 ticket
=> { type
=> 'string' },
650 cert
=> { type
=> 'string' },
651 port
=> { type
=> 'integer' },
652 upid
=> { type
=> 'string' },
658 my $rpcenv = PVE
::RPCEnvironment
::get
();
660 my $authuser = $rpcenv->get_user();
662 my $vmid = $param->{vmid
};
663 my $node = $param->{node
};
665 my $authpath = "/vms/$vmid";
667 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
669 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
672 my ($remip, $family);
674 if ($node ne PVE
::INotify
::nodename
()) {
675 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
677 $family = PVE
::Tools
::get_host_address_family
($node);
680 my $port = PVE
::Tools
::next_vnc_port
($family);
682 # NOTE: vncterm VNC traffic is already TLS encrypted,
683 # so we select the fastest chipher here (or 'none'?)
684 my $remcmd = $remip ?
685 ['/usr/bin/ssh', '-t', $remip] : [];
687 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
688 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
690 my $shcmd = [ '/usr/bin/dtach', '-A',
691 "/var/run/dtach/vzctlconsole$vmid",
692 '-r', 'winch', '-z', @$concmd];
697 syslog
('info', "starting lxc vnc proxy $upid\n");
701 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
702 '-timeout', $timeout, '-authpath', $authpath,
703 '-perm', 'VM.Console'];
705 if ($param->{websocket
}) {
706 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
707 push @$cmd, '-notls', '-listen', 'localhost';
710 push @$cmd, '-c', @$remcmd, @$shcmd;
717 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
719 PVE
::Tools
::wait_for_vnc_port
($port);
730 __PACKAGE__-
>register_method({
731 name
=> 'vncwebsocket',
732 path
=> '{vmid}/vncwebsocket',
735 description
=> "You also need to pass a valid ticket (vncticket).",
736 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
738 description
=> "Opens a weksocket for VNC traffic.",
740 additionalProperties
=> 0,
742 node
=> get_standard_option
('pve-node'),
743 vmid
=> get_standard_option
('pve-vmid'),
745 description
=> "Ticket from previous call to vncproxy.",
750 description
=> "Port number returned by previous vncproxy call.",
760 port
=> { type
=> 'string' },
766 my $rpcenv = PVE
::RPCEnvironment
::get
();
768 my $authuser = $rpcenv->get_user();
770 my $authpath = "/vms/$param->{vmid}";
772 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
774 my $port = $param->{port
};
776 return { port
=> $port };
779 __PACKAGE__-
>register_method ({
780 name
=> 'spiceproxy',
781 path
=> '{vmid}/spiceproxy',
786 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
788 description
=> "Returns a SPICE configuration to connect to the CT.",
790 additionalProperties
=> 0,
792 node
=> get_standard_option
('pve-node'),
793 vmid
=> get_standard_option
('pve-vmid'),
794 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
797 returns
=> get_standard_option
('remote-viewer-config'),
801 my $vmid = $param->{vmid
};
802 my $node = $param->{node
};
803 my $proxy = $param->{proxy
};
805 my $authpath = "/vms/$vmid";
806 my $permissions = 'VM.Console';
808 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
810 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
812 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
814 my $shcmd = ['/usr/bin/dtach', '-A',
815 "/var/run/dtach/vzctlconsole$vmid",
816 '-r', 'winch', '-z', @$concmd];
818 my $title = "CT $vmid";
820 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
824 __PACKAGE__-
>register_method({
825 name
=> 'migrate_vm',
826 path
=> '{vmid}/migrate',
830 description
=> "Migrate the container to another node. Creates a new migration task.",
832 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
835 additionalProperties
=> 0,
837 node
=> get_standard_option
('pve-node'),
838 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
839 target
=> get_standard_option
('pve-node', {
840 description
=> "Target node.",
841 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
845 description
=> "Use online/live migration.",
850 description
=> "Use restart migration",
855 description
=> "Timeout in seconds for shutdown for restart migration",
861 description
=> "Force migration despite local bind / device" .
862 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
869 description
=> "the task ID.",
874 my $rpcenv = PVE
::RPCEnvironment
::get
();
876 my $authuser = $rpcenv->get_user();
878 my $target = extract_param
($param, 'target');
880 my $localnode = PVE
::INotify
::nodename
();
881 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
883 PVE
::Cluster
::check_cfs_quorum
();
885 PVE
::Cluster
::check_node_exists
($target);
887 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
889 my $vmid = extract_param
($param, 'vmid');
892 PVE
::LXC
::Config-
>load_config($vmid);
894 # try to detect errors early
895 if (PVE
::LXC
::check_running
($vmid)) {
896 die "can't migrate running container without --online or --restart\n"
897 if !$param->{online
} && !$param->{restart
};
900 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
905 my $service = "ct:$vmid";
907 my $cmd = ['ha-manager', 'migrate', $service, $target];
909 print "Executing HA migrate for CT $vmid to node $target\n";
911 PVE
::Tools
::run_command
($cmd);
916 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
923 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
928 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
932 __PACKAGE__-
>register_method({
933 name
=> 'vm_feature',
934 path
=> '{vmid}/feature',
938 description
=> "Check if feature for virtual machine is available.",
940 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
943 additionalProperties
=> 0,
945 node
=> get_standard_option
('pve-node'),
946 vmid
=> get_standard_option
('pve-vmid'),
948 description
=> "Feature to check.",
950 enum
=> [ 'snapshot' ],
952 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
960 hasFeature
=> { type
=> 'boolean' },
963 #items => { type => 'string' },
970 my $node = extract_param
($param, 'node');
972 my $vmid = extract_param
($param, 'vmid');
974 my $snapname = extract_param
($param, 'snapname');
976 my $feature = extract_param
($param, 'feature');
978 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
981 my $snap = $conf->{snapshots
}->{$snapname};
982 die "snapshot '$snapname' does not exist\n" if !defined($snap);
985 my $storage_cfg = PVE
::Storage
::config
();
987 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
988 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
991 hasFeature
=> $hasFeature,
992 #nodes => [ keys %$nodelist ],
996 __PACKAGE__-
>register_method({
998 path
=> '{vmid}/template',
1002 description
=> "Create a Template.",
1004 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1005 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1008 additionalProperties
=> 0,
1010 node
=> get_standard_option
('pve-node'),
1011 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1014 description
=> "The template feature is experimental, set this " .
1015 "flag if you know what you are doing.",
1020 returns
=> { type
=> 'null'},
1024 my $rpcenv = PVE
::RPCEnvironment
::get
();
1026 my $authuser = $rpcenv->get_user();
1028 my $node = extract_param
($param, 'node');
1030 my $vmid = extract_param
($param, 'vmid');
1032 my $updatefn = sub {
1034 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1035 PVE
::LXC
::Config-
>check_lock($conf);
1037 die "unable to create template, because CT contains snapshots\n"
1038 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1040 die "you can't convert a template to a template\n"
1041 if PVE
::LXC
::Config-
>is_template($conf);
1043 die "you can't convert a CT to template if the CT is running\n"
1044 if PVE
::LXC
::check_running
($vmid);
1047 PVE
::LXC
::template_create
($vmid, $conf);
1050 $conf->{template
} = 1;
1052 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1053 # and remove lxc config
1054 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1056 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1059 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1064 __PACKAGE__-
>register_method({
1066 path
=> '{vmid}/clone',
1070 description
=> "Create a container clone/copy",
1072 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1073 "and 'VM.Allocate' permissions " .
1074 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1075 "'Datastore.AllocateSpace' on any used storage.",
1078 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1080 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1081 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1086 additionalProperties
=> 0,
1088 node
=> get_standard_option
('pve-node'),
1089 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1090 newid
=> get_standard_option
('pve-vmid', {
1091 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1092 description
=> 'VMID for the clone.' }),
1095 type
=> 'string', format
=> 'dns-name',
1096 description
=> "Set a hostname for the new CT.",
1101 description
=> "Description for the new CT.",
1105 type
=> 'string', format
=> 'pve-poolid',
1106 description
=> "Add the new CT to the specified pool.",
1108 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1111 storage
=> get_standard_option
('pve-storage-id', {
1112 description
=> "Target storage for full clone.",
1119 description
=> "Create a full copy of all disk. This is always done when " .
1120 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1125 description
=> "The clone feature is experimental, set this " .
1126 "flag if you know what you are doing.",
1129 # target => get_standard_option('pve-node', {
1130 # description => "Target node. Only allowed if the original VM is on shared storage.",
1141 my $rpcenv = PVE
::RPCEnvironment
::get
();
1143 my $authuser = $rpcenv->get_user();
1145 my $node = extract_param
($param, 'node');
1147 my $vmid = extract_param
($param, 'vmid');
1149 my $newid = extract_param
($param, 'newid');
1151 my $pool = extract_param
($param, 'pool');
1153 if (defined($pool)) {
1154 $rpcenv->check_pool_exist($pool);
1157 my $snapname = extract_param
($param, 'snapname');
1159 my $storage = extract_param
($param, 'storage');
1161 my $localnode = PVE
::INotify
::nodename
();
1163 my $storecfg = PVE
::Storage
::config
();
1166 # check if storage is enabled on local node
1167 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1170 PVE
::Cluster
::check_cfs_quorum
();
1172 my $running = PVE
::LXC
::check_running
($vmid) || 0;
1176 # do all tests after lock
1177 # we also try to do all tests before we fork the worker
1178 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1180 PVE
::LXC
::Config-
>check_lock($conf);
1182 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1184 die "unexpected state change\n" if $verify_running != $running;
1186 die "snapshot '$snapname' does not exist\n"
1187 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1189 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1191 my $conffile = PVE
::LXC
::Config-
>config_file($newid);
1192 die "unable to create CT $newid: config file already exists\n"
1195 my $newconf = { lock => 'clone' };
1196 my $mountpoints = {};
1200 foreach my $opt (keys %$oldconf) {
1201 my $value = $oldconf->{$opt};
1203 # no need to copy unused images, because VMID(owner) changes anyways
1204 next if $opt =~ m/^unused\d+$/;
1206 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1207 my $mp = $opt eq 'rootfs' ?
1208 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1209 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1211 if ($mp->{type
} eq 'volume') {
1212 my $volid = $mp->{volume
};
1213 if ($param->{full
}) {
1214 die "fixme: full clone not implemented";
1216 die "Full clone feature for '$volid' is not available\n"
1217 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $volid, $snapname, $running);
1218 $fullclone->{$opt} = 1;
1220 # not full means clone instead of copy
1221 die "Linked clone feature for '$volid' is not available\n"
1222 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1225 $mountpoints->{$opt} = $mp;
1226 push @$vollist, $volid;
1229 # TODO: allow bind mounts?
1230 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1234 # copy everything else
1235 $newconf->{$opt} = $value;
1239 delete $newconf->{template
};
1240 if ($param->{hostname
}) {
1241 $newconf->{hostname
} = $param->{hostname
};
1244 if ($param->{description
}) {
1245 $newconf->{description
} = $param->{description
};
1248 # create empty/temp config - this fails if CT already exists on other node
1249 PVE
::Tools
::file_set_contents
($conffile, "# ctclone temporary file\nlock: clone\n");
1254 my $newvollist = [];
1257 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1259 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1261 foreach my $opt (keys %$mountpoints) {
1262 my $mp = $mountpoints->{$opt};
1263 my $volid = $mp->{volume
};
1265 if ($fullclone->{$opt}) {
1266 die "fixme: full clone not implemented\n";
1268 print "create linked clone of mount point $opt ($volid)\n";
1269 my $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1270 push @$newvollist, $newvolid;
1271 $mp->{volume
} = $newvolid;
1273 $newconf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs');
1274 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1278 delete $newconf->{lock};
1279 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1281 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1286 sleep 1; # some storage like rbd need to wait before release volume - really?
1288 foreach my $volid (@$newvollist) {
1289 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1292 die "clone failed: $err";
1298 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1300 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1304 return PVE
::LXC
::Config-
>lock_config($vmid, $clonefn);
1308 __PACKAGE__-
>register_method({
1309 name
=> 'resize_vm',
1310 path
=> '{vmid}/resize',
1314 description
=> "Resize a container mount point.",
1316 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1319 additionalProperties
=> 0,
1321 node
=> get_standard_option
('pve-node'),
1322 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1325 description
=> "The disk you want to resize.",
1326 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1330 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1331 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.",
1335 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1343 description
=> "the task ID.",
1348 my $rpcenv = PVE
::RPCEnvironment
::get
();
1350 my $authuser = $rpcenv->get_user();
1352 my $node = extract_param
($param, 'node');
1354 my $vmid = extract_param
($param, 'vmid');
1356 my $digest = extract_param
($param, 'digest');
1358 my $sizestr = extract_param
($param, 'size');
1359 my $ext = ($sizestr =~ s/^\+//);
1360 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1361 die "invalid size string" if !defined($newsize);
1363 die "no options specified\n" if !scalar(keys %$param);
1365 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1367 my $storage_cfg = cfs_read_file
("storage.cfg");
1371 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1372 PVE
::LXC
::Config-
>check_lock($conf);
1374 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1376 my $running = PVE
::LXC
::check_running
($vmid);
1378 my $disk = $param->{disk
};
1379 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1380 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1382 my $volid = $mp->{volume
};
1384 my (undef, undef, $owner, undef, undef, undef, $format) =
1385 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1387 die "can't resize mount point owned by another container ($owner)"
1390 die "can't resize volume: $disk if snapshot exists\n"
1391 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1393 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1395 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1397 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1399 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1400 $newsize += $size if $ext;
1401 $newsize = int($newsize);
1403 die "unable to shrink disk size\n" if $newsize < $size;
1405 return if $size == $newsize;
1407 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1409 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1410 # we pass 0 here (parameter only makes sense for qemu)
1411 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1413 $mp->{size
} = $newsize;
1414 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1416 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1418 if ($format eq 'raw') {
1419 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1423 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1424 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1425 die "internal error: CT running but mount point not attached to a loop device"
1427 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1429 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1430 # to be visible to it in its namespace.
1431 # To not interfere with the rest of the system we unshare the current mount namespace,
1432 # mount over /tmp and then run resize2fs.
1434 # interestingly we don't need to e2fsck on mounted systems...
1435 my $quoted = PVE
::Tools
::shellquote
($path);
1436 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1438 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1440 warn "Failed to update the container's filesystem: $@\n" if $@;
1443 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1444 PVE
::Tools
::run_command
(['resize2fs', $path]);
1446 warn "Failed to update the container's filesystem: $@\n" if $@;
1451 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1454 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;