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
;
16 use PVE
::ReplicationConfig
;
19 use PVE
::LXC
::Migrate
;
20 use PVE
::API2
::LXC
::Config
;
21 use PVE
::API2
::LXC
::Status
;
22 use PVE
::API2
::LXC
::Snapshot
;
23 use PVE
::JSONSchema
qw(get_standard_option);
24 use base
qw(PVE::RESTHandler);
27 if (!$ENV{PVE_GENERATING_DOCS
}) {
28 require PVE
::HA
::Env
::PVE2
;
29 import PVE
::HA
::Env
::PVE2
;
30 require PVE
::HA
::Config
;
31 import PVE
::HA
::Config
;
35 __PACKAGE__-
>register_method ({
36 subclass
=> "PVE::API2::LXC::Config",
37 path
=> '{vmid}/config',
40 __PACKAGE__-
>register_method ({
41 subclass
=> "PVE::API2::LXC::Status",
42 path
=> '{vmid}/status',
45 __PACKAGE__-
>register_method ({
46 subclass
=> "PVE::API2::LXC::Snapshot",
47 path
=> '{vmid}/snapshot',
50 __PACKAGE__-
>register_method ({
51 subclass
=> "PVE::API2::Firewall::CT",
52 path
=> '{vmid}/firewall',
55 __PACKAGE__-
>register_method({
59 description
=> "LXC container index (per node).",
61 description
=> "Only list CTs where you have VM.Audit permissons on /vms/<vmid>.",
65 protected
=> 1, # /proc files are only readable by root
67 additionalProperties
=> 0,
69 node
=> get_standard_option
('pve-node'),
78 links
=> [ { rel
=> 'child', href
=> "{vmid}" } ],
83 my $rpcenv = PVE
::RPCEnvironment
::get
();
84 my $authuser = $rpcenv->get_user();
86 my $vmstatus = PVE
::LXC
::vmstatus
();
89 foreach my $vmid (keys %$vmstatus) {
90 next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1);
92 my $data = $vmstatus->{$vmid};
93 $data->{vmid
} = $vmid;
101 __PACKAGE__-
>register_method({
105 description
=> "Create or restore a container.",
107 user
=> 'all', # check inside
108 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid} or on the VM pool /pool/{pool}. " .
109 "For restore, it is enough if the user has 'VM.Backup' permission and the VM already exists. " .
110 "You also need 'Datastore.AllocateSpace' permissions on the storage.",
115 additionalProperties
=> 0,
116 properties
=> PVE
::LXC
::Config-
>json_config_properties({
117 node
=> get_standard_option
('pve-node'),
118 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::Cluster
::complete_next_vmid
}),
120 description
=> "The OS template or backup file.",
123 completion
=> \
&PVE
::LXC
::complete_os_templates
,
128 description
=> "Sets root password inside container.",
131 storage
=> get_standard_option
('pve-storage-id', {
132 description
=> "Default Storage.",
135 completion
=> \
&PVE
::Storage
::complete_storage_enabled
,
140 description
=> "Allow to overwrite existing container.",
145 description
=> "Mark this as restore task.",
149 type
=> 'string', format
=> 'pve-poolid',
150 description
=> "Add the VM to the specified pool.",
152 'ignore-unpack-errors' => {
155 description
=> "Ignore errors when extracting the template.",
157 'ssh-public-keys' => {
160 description
=> "Setup public SSH keys (one key per line, " .
171 my $rpcenv = PVE
::RPCEnvironment
::get
();
173 my $authuser = $rpcenv->get_user();
175 my $node = extract_param
($param, 'node');
177 my $vmid = extract_param
($param, 'vmid');
179 my $ignore_unpack_errors = extract_param
($param, 'ignore-unpack-errors');
181 my $basecfg_fn = PVE
::LXC
::Config-
>config_file($vmid);
183 my $same_container_exists = -f
$basecfg_fn;
185 # 'unprivileged' is read-only, so we can't pass it to update_pct_config
186 my $unprivileged = extract_param
($param, 'unprivileged');
188 my $restore = extract_param
($param, 'restore');
191 # fixme: limit allowed parameters
195 my $force = extract_param
($param, 'force');
197 if (!($same_container_exists && $restore && $force)) {
198 PVE
::Cluster
::check_vmid_unused
($vmid);
200 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
201 PVE
::LXC
::Config-
>check_protection($conf, "unable to restore CT $vmid");
204 my $password = extract_param
($param, 'password');
206 my $ssh_keys = extract_param
($param, 'ssh-public-keys');
207 PVE
::Tools
::validate_ssh_public_keys
($ssh_keys) if defined($ssh_keys);
209 my $pool = extract_param
($param, 'pool');
211 if (defined($pool)) {
212 $rpcenv->check_pool_exist($pool);
213 $rpcenv->check_perm_modify($authuser, "/pool/$pool");
216 if ($rpcenv->check($authuser, "/vms/$vmid", ['VM.Allocate'], 1)) {
218 } elsif ($pool && $rpcenv->check($authuser, "/pool/$pool", ['VM.Allocate'], 1)) {
220 } elsif ($restore && $force && $same_container_exists &&
221 $rpcenv->check($authuser, "/vms/$vmid", ['VM.Backup'], 1)) {
222 # OK: user has VM.Backup permissions, and want to restore an existing VM
227 my $ostemplate = extract_param
($param, 'ostemplate');
228 my $storage = extract_param
($param, 'storage') // 'local';
230 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, $pool, $param, []);
232 my $storage_cfg = cfs_read_file
("storage.cfg");
237 if ($ostemplate eq '-') {
238 die "pipe requires cli environment\n"
239 if $rpcenv->{type
} ne 'cli';
240 die "pipe can only be used with restore tasks\n"
243 die "restore from pipe requires rootfs parameter\n" if !defined($param->{rootfs
});
245 PVE
::Storage
::check_volume_access
($rpcenv, $authuser, $storage_cfg, $vmid, $ostemplate);
246 $archive = PVE
::Storage
::abs_filesystem_path
($storage_cfg, $ostemplate);
249 my $check_and_activate_storage = sub {
252 my $scfg = PVE
::Storage
::storage_check_node
($storage_cfg, $sid, $node);
254 raise_param_exc
({ storage
=> "storage '$sid' does not support container directories"})
255 if !$scfg->{content
}->{rootdir
};
257 $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']);
259 PVE
::Storage
::activate_storage
($storage_cfg, $sid);
264 my $no_disk_param = {};
266 my $storage_only_mode = 1;
267 foreach my $opt (keys %$param) {
268 my $value = $param->{$opt};
269 if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) {
270 # allow to use simple numbers (add default storage in that case)
271 if ($value =~ m/^\d+(\.\d+)?$/) {
272 $mp_param->{$opt} = "$storage:$value";
274 $mp_param->{$opt} = $value;
276 $storage_only_mode = 0;
277 } elsif ($opt =~ m/^unused\d+$/) {
278 warn "ignoring '$opt', cannot create/restore with unused volume\n";
279 delete $param->{$opt};
281 $no_disk_param->{$opt} = $value;
285 die "mount points configured, but 'rootfs' not set - aborting\n"
286 if !$storage_only_mode && !defined($mp_param->{rootfs
});
288 # check storage access, activate storage
289 my $delayed_mp_param = {};
290 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
291 my ($ms, $mountpoint) = @_;
293 my $volid = $mountpoint->{volume
};
294 my $mp = $mountpoint->{mp
};
296 if ($mountpoint->{type
} ne 'volume') { # bind or device
297 die "Only root can pass arbitrary filesystem paths.\n"
298 if $authuser ne 'root@pam';
300 my ($sid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
301 &$check_and_activate_storage($sid);
305 # check/activate default storage
306 &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs
});
308 PVE
::LXC
::Config-
>update_pct_config($vmid, $conf, 0, $no_disk_param);
310 $conf->{unprivileged
} = 1 if $unprivileged;
312 my $check_vmid_usage = sub {
314 die "can't overwrite running container\n"
315 if PVE
::LXC
::check_running
($vmid);
317 PVE
::Cluster
::check_vmid_unused
($vmid);
322 &$check_vmid_usage(); # final check after locking
325 my $config_fn = PVE
::LXC
::Config-
>config_file($vmid);
327 die "container exists" if !$restore; # just to be sure
328 $old_conf = PVE
::LXC
::Config-
>load_config($vmid);
331 # try to create empty config on local node, we have an flock
332 PVE
::LXC
::Config-
>write_config($vmid, {});
335 # another node was faster, abort
336 die "Could not reserve ID $vmid, already taken\n" if $@;
339 PVE
::Cluster
::check_cfs_quorum
();
343 if ($storage_only_mode) {
345 (undef, $mp_param) = PVE
::LXC
::Create
::recover_config
($archive);
346 die "rootfs configuration could not be recovered, please check and specify manually!\n"
347 if !defined($mp_param->{rootfs
});
348 PVE
::LXC
::Config-
>foreach_mountpoint($mp_param, sub {
349 my ($ms, $mountpoint) = @_;
350 my $type = $mountpoint->{type
};
351 if ($type eq 'volume') {
352 die "unable to detect disk size - please specify $ms (size)\n"
353 if !defined($mountpoint->{size
});
354 my $disksize = $mountpoint->{size
} / (1024 * 1024 * 1024); # create_disks expects GB as unit size
355 delete $mountpoint->{size
};
356 $mountpoint->{volume
} = "$storage:$disksize";
357 $mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
359 my $type = $mountpoint->{type
};
360 die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n"
361 if ($ms eq 'rootfs');
362 die "restoring '$ms' to $type mount is only possible for root\n"
363 if $authuser ne 'root@pam';
365 if ($mountpoint->{backup
}) {
366 warn "WARNING - unsupported configuration!\n";
367 warn "backup was enabled for $type mount point $ms ('$mountpoint->{mp}')\n";
368 warn "mount point configuration will be restored after archive extraction!\n";
369 warn "contained files will be restored to wrong directory!\n";
371 delete $mp_param->{$ms}; # actually delay bind/dev mps
372 $delayed_mp_param->{$ms} = PVE
::LXC
::Config-
>print_ct_mountpoint($mountpoint, $ms eq 'rootfs');
376 $mp_param->{rootfs
} = "$storage:4"; # defaults to 4GB
380 $vollist = PVE
::LXC
::create_disks
($storage_cfg, $vmid, $mp_param, $conf);
382 if (defined($old_conf)) {
383 # destroy old container volumes
384 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $old_conf, {});
388 my $rootdir = PVE
::LXC
::mount_all
($vmid, $storage_cfg, $conf, 1);
389 PVE
::LXC
::Create
::restore_archive
($archive, $rootdir, $conf, $ignore_unpack_errors);
392 PVE
::LXC
::Create
::restore_configuration
($vmid, $rootdir, $conf, $authuser ne 'root@pam');
394 my $lxc_setup = PVE
::LXC
::Setup-
>new($conf, $rootdir); # detect OS
395 PVE
::LXC
::Config-
>write_config($vmid, $conf); # safe config (after OS detection)
396 $lxc_setup->post_create_hook($password, $ssh_keys);
400 PVE
::LXC
::umount_all
($vmid, $storage_cfg, $conf, $err ?
1 : 0);
401 PVE
::Storage
::deactivate_volumes
($storage_cfg, PVE
::LXC
::Config-
>get_vm_volumes($conf));
404 $conf->{hostname
} ||= "CT$vmid";
405 $conf->{memory
} ||= 512;
406 $conf->{swap
} //= 512;
407 foreach my $mp (keys %$delayed_mp_param) {
408 $conf->{$mp} = $delayed_mp_param->{$mp};
410 PVE
::LXC
::Config-
>write_config($vmid, $conf);
413 PVE
::LXC
::destroy_disks
($storage_cfg, $vollist);
414 PVE
::LXC
::destroy_config
($vmid);
417 PVE
::AccessControl
::add_vm_to_pool
($vmid, $pool) if $pool;
420 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
422 &$check_vmid_usage(); # first check before locking
424 return $rpcenv->fork_worker($restore ?
'vzrestore' : 'vzcreate',
425 $vmid, $authuser, $realcmd);
429 __PACKAGE__-
>register_method({
434 description
=> "Directory index",
439 additionalProperties
=> 0,
441 node
=> get_standard_option
('pve-node'),
442 vmid
=> get_standard_option
('pve-vmid'),
450 subdir
=> { type
=> 'string' },
453 links
=> [ { rel
=> 'child', href
=> "{subdir}" } ],
459 my $conf = PVE
::LXC
::Config-
>load_config($param->{vmid
});
462 { subdir
=> 'config' },
463 { subdir
=> 'status' },
464 { subdir
=> 'vncproxy' },
465 { subdir
=> 'vncwebsocket' },
466 { subdir
=> 'spiceproxy' },
467 { subdir
=> 'migrate' },
468 { subdir
=> 'clone' },
469 # { subdir => 'initlog' },
471 { subdir
=> 'rrddata' },
472 { subdir
=> 'firewall' },
473 { subdir
=> 'snapshot' },
474 { subdir
=> 'resize' },
481 __PACKAGE__-
>register_method({
483 path
=> '{vmid}/rrd',
485 protected
=> 1, # fixme: can we avoid that?
487 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
489 description
=> "Read VM RRD statistics (returns PNG)",
491 additionalProperties
=> 0,
493 node
=> get_standard_option
('pve-node'),
494 vmid
=> get_standard_option
('pve-vmid'),
496 description
=> "Specify the time frame you are interested in.",
498 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
501 description
=> "The list of datasources you want to display.",
502 type
=> 'string', format
=> 'pve-configid-list',
505 description
=> "The RRD consolidation function",
507 enum
=> [ 'AVERAGE', 'MAX' ],
515 filename
=> { type
=> 'string' },
521 return PVE
::Cluster
::create_rrd_graph
(
522 "pve2-vm/$param->{vmid}", $param->{timeframe
},
523 $param->{ds
}, $param->{cf
});
527 __PACKAGE__-
>register_method({
529 path
=> '{vmid}/rrddata',
531 protected
=> 1, # fixme: can we avoid that?
533 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
535 description
=> "Read VM RRD statistics",
537 additionalProperties
=> 0,
539 node
=> get_standard_option
('pve-node'),
540 vmid
=> get_standard_option
('pve-vmid'),
542 description
=> "Specify the time frame you are interested in.",
544 enum
=> [ 'hour', 'day', 'week', 'month', 'year' ],
547 description
=> "The RRD consolidation function",
549 enum
=> [ 'AVERAGE', 'MAX' ],
564 return PVE
::Cluster
::create_rrd_data
(
565 "pve2-vm/$param->{vmid}", $param->{timeframe
}, $param->{cf
});
568 __PACKAGE__-
>register_method({
569 name
=> 'destroy_vm',
574 description
=> "Destroy the container (also delete all uses files).",
576 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
579 additionalProperties
=> 0,
581 node
=> get_standard_option
('pve-node'),
582 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
591 my $rpcenv = PVE
::RPCEnvironment
::get
();
593 my $authuser = $rpcenv->get_user();
595 my $vmid = $param->{vmid
};
597 # test if container exists
598 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
600 my $storage_cfg = cfs_read_file
("storage.cfg");
602 PVE
::LXC
::Config-
>check_protection($conf, "can't remove CT $vmid");
604 die "unable to remove CT $vmid - used in HA resources\n"
605 if PVE
::HA
::Config
::vm_is_ha_managed
($vmid);
607 # do not allow destroy if there are replication jobs
608 my $repl_conf = PVE
::ReplicationConfig-
>new();
609 $repl_conf->check_for_existing_jobs($vmid);
611 my $running_error_msg = "unable to destroy CT $vmid - container is running\n";
613 die $running_error_msg if PVE
::LXC
::check_running
($vmid); # check early
616 # reload config after lock
617 $conf = PVE
::LXC
::Config-
>load_config($vmid);
618 PVE
::LXC
::Config-
>check_lock($conf);
620 die $running_error_msg if PVE
::LXC
::check_running
($vmid);
622 PVE
::LXC
::destroy_lxc_container
($storage_cfg, $vmid, $conf);
623 PVE
::AccessControl
::remove_vm_access
($vmid);
624 PVE
::Firewall
::remove_vmfw_conf
($vmid);
627 my $realcmd = sub { PVE
::LXC
::Config-
>lock_config($vmid, $code); };
629 return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd);
634 __PACKAGE__-
>register_method ({
636 path
=> '{vmid}/vncproxy',
640 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
642 description
=> "Creates a TCP VNC proxy connections.",
644 additionalProperties
=> 0,
646 node
=> get_standard_option
('pve-node'),
647 vmid
=> get_standard_option
('pve-vmid'),
651 description
=> "use websocket instead of standard VNC.",
656 additionalProperties
=> 0,
658 user
=> { type
=> 'string' },
659 ticket
=> { type
=> 'string' },
660 cert
=> { type
=> 'string' },
661 port
=> { type
=> 'integer' },
662 upid
=> { type
=> 'string' },
668 my $rpcenv = PVE
::RPCEnvironment
::get
();
670 my $authuser = $rpcenv->get_user();
672 my $vmid = $param->{vmid
};
673 my $node = $param->{node
};
675 my $authpath = "/vms/$vmid";
677 my $ticket = PVE
::AccessControl
::assemble_vnc_ticket
($authuser, $authpath);
679 $sslcert = PVE
::Tools
::file_get_contents
("/etc/pve/pve-root-ca.pem", 8192)
682 my ($remip, $family);
684 if ($node ne PVE
::INotify
::nodename
()) {
685 ($remip, $family) = PVE
::Cluster
::remote_node_ip
($node);
687 $family = PVE
::Tools
::get_host_address_family
($node);
690 my $port = PVE
::Tools
::next_vnc_port
($family);
692 # NOTE: vncterm VNC traffic is already TLS encrypted,
693 # so we select the fastest chipher here (or 'none'?)
694 my $remcmd = $remip ?
695 ['/usr/bin/ssh', '-t', $remip] : [];
697 my $conf = PVE
::LXC
::Config-
>load_config($vmid, $node);
698 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
700 my $shcmd = [ '/usr/bin/dtach', '-A',
701 "/var/run/dtach/vzctlconsole$vmid",
702 '-r', 'winch', '-z', @$concmd];
707 syslog
('info', "starting lxc vnc proxy $upid\n");
711 my $cmd = ['/usr/bin/vncterm', '-rfbport', $port,
712 '-timeout', $timeout, '-authpath', $authpath,
713 '-perm', 'VM.Console'];
715 if ($param->{websocket
}) {
716 $ENV{PVE_VNC_TICKET
} = $ticket; # pass ticket to vncterm
717 push @$cmd, '-notls', '-listen', 'localhost';
720 push @$cmd, '-c', @$remcmd, @$shcmd;
722 run_command
($cmd, keeplocale
=> 1);
727 my $upid = $rpcenv->fork_worker('vncproxy', $vmid, $authuser, $realcmd);
729 PVE
::Tools
::wait_for_vnc_port
($port);
740 __PACKAGE__-
>register_method({
741 name
=> 'vncwebsocket',
742 path
=> '{vmid}/vncwebsocket',
745 description
=> "You also need to pass a valid ticket (vncticket).",
746 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
748 description
=> "Opens a weksocket for VNC traffic.",
750 additionalProperties
=> 0,
752 node
=> get_standard_option
('pve-node'),
753 vmid
=> get_standard_option
('pve-vmid'),
755 description
=> "Ticket from previous call to vncproxy.",
760 description
=> "Port number returned by previous vncproxy call.",
770 port
=> { type
=> 'string' },
776 my $rpcenv = PVE
::RPCEnvironment
::get
();
778 my $authuser = $rpcenv->get_user();
780 my $authpath = "/vms/$param->{vmid}";
782 PVE
::AccessControl
::verify_vnc_ticket
($param->{vncticket
}, $authuser, $authpath);
784 my $port = $param->{port
};
786 return { port
=> $port };
789 __PACKAGE__-
>register_method ({
790 name
=> 'spiceproxy',
791 path
=> '{vmid}/spiceproxy',
796 check
=> ['perm', '/vms/{vmid}', [ 'VM.Console' ]],
798 description
=> "Returns a SPICE configuration to connect to the CT.",
800 additionalProperties
=> 0,
802 node
=> get_standard_option
('pve-node'),
803 vmid
=> get_standard_option
('pve-vmid'),
804 proxy
=> get_standard_option
('spice-proxy', { optional
=> 1 }),
807 returns
=> get_standard_option
('remote-viewer-config'),
811 my $vmid = $param->{vmid
};
812 my $node = $param->{node
};
813 my $proxy = $param->{proxy
};
815 my $authpath = "/vms/$vmid";
816 my $permissions = 'VM.Console';
818 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
820 die "CT $vmid not running\n" if !PVE
::LXC
::check_running
($vmid);
822 my $concmd = PVE
::LXC
::get_console_command
($vmid, $conf);
824 my $shcmd = ['/usr/bin/dtach', '-A',
825 "/var/run/dtach/vzctlconsole$vmid",
826 '-r', 'winch', '-z', @$concmd];
828 my $title = "CT $vmid";
830 return PVE
::API2Tools
::run_spiceterm
($authpath, $permissions, $vmid, $node, $proxy, $title, $shcmd);
834 __PACKAGE__-
>register_method({
835 name
=> 'migrate_vm',
836 path
=> '{vmid}/migrate',
840 description
=> "Migrate the container to another node. Creates a new migration task.",
842 check
=> ['perm', '/vms/{vmid}', [ 'VM.Migrate' ]],
845 additionalProperties
=> 0,
847 node
=> get_standard_option
('pve-node'),
848 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
849 target
=> get_standard_option
('pve-node', {
850 description
=> "Target node.",
851 completion
=> \
&PVE
::Cluster
::complete_migration_target
,
855 description
=> "Use online/live migration.",
860 description
=> "Use restart migration",
865 description
=> "Timeout in seconds for shutdown for restart migration",
871 description
=> "Force migration despite local bind / device" .
872 " mounts. NOTE: deprecated, use 'shared' property of mount point instead.",
879 description
=> "the task ID.",
884 my $rpcenv = PVE
::RPCEnvironment
::get
();
886 my $authuser = $rpcenv->get_user();
888 my $target = extract_param
($param, 'target');
890 my $localnode = PVE
::INotify
::nodename
();
891 raise_param_exc
({ target
=> "target is local node."}) if $target eq $localnode;
893 PVE
::Cluster
::check_cfs_quorum
();
895 PVE
::Cluster
::check_node_exists
($target);
897 my $targetip = PVE
::Cluster
::remote_node_ip
($target);
899 my $vmid = extract_param
($param, 'vmid');
902 PVE
::LXC
::Config-
>load_config($vmid);
904 # try to detect errors early
905 if (PVE
::LXC
::check_running
($vmid)) {
906 die "can't migrate running container without --online or --restart\n"
907 if !$param->{online
} && !$param->{restart
};
910 if (PVE
::HA
::Config
::vm_is_ha_managed
($vmid) && $rpcenv->{type
} ne 'ha') {
915 my $service = "ct:$vmid";
917 my $cmd = ['ha-manager', 'migrate', $service, $target];
919 print "Executing HA migrate for CT $vmid to node $target\n";
921 PVE
::Tools
::run_command
($cmd);
926 return $rpcenv->fork_worker('hamigrate', $vmid, $authuser, $hacmd);
933 PVE
::LXC
::Migrate-
>migrate($target, $targetip, $vmid, $param);
938 return $rpcenv->fork_worker('vzmigrate', $vmid, $authuser, $realcmd);
942 __PACKAGE__-
>register_method({
943 name
=> 'vm_feature',
944 path
=> '{vmid}/feature',
948 description
=> "Check if feature for virtual machine is available.",
950 check
=> ['perm', '/vms/{vmid}', [ 'VM.Audit' ]],
953 additionalProperties
=> 0,
955 node
=> get_standard_option
('pve-node'),
956 vmid
=> get_standard_option
('pve-vmid'),
958 description
=> "Feature to check.",
960 enum
=> [ 'snapshot' ],
962 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
970 hasFeature
=> { type
=> 'boolean' },
973 #items => { type => 'string' },
980 my $node = extract_param
($param, 'node');
982 my $vmid = extract_param
($param, 'vmid');
984 my $snapname = extract_param
($param, 'snapname');
986 my $feature = extract_param
($param, 'feature');
988 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
991 my $snap = $conf->{snapshots
}->{$snapname};
992 die "snapshot '$snapname' does not exist\n" if !defined($snap);
995 my $storage_cfg = PVE
::Storage
::config
();
997 #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg);
998 my $hasFeature = PVE
::LXC
::Config-
>has_feature($feature, $conf, $storage_cfg, $snapname);
1001 hasFeature
=> $hasFeature,
1002 #nodes => [ keys %$nodelist ],
1006 __PACKAGE__-
>register_method({
1008 path
=> '{vmid}/template',
1012 description
=> "Create a Template.",
1014 description
=> "You need 'VM.Allocate' permissions on /vms/{vmid}",
1015 check
=> [ 'perm', '/vms/{vmid}', ['VM.Allocate']],
1018 additionalProperties
=> 0,
1020 node
=> get_standard_option
('pve-node'),
1021 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid_stopped
}),
1024 description
=> "The template feature is experimental, set this " .
1025 "flag if you know what you are doing.",
1030 returns
=> { type
=> 'null'},
1034 my $rpcenv = PVE
::RPCEnvironment
::get
();
1036 my $authuser = $rpcenv->get_user();
1038 my $node = extract_param
($param, 'node');
1040 my $vmid = extract_param
($param, 'vmid');
1042 my $updatefn = sub {
1044 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1045 PVE
::LXC
::Config-
>check_lock($conf);
1047 die "unable to create template, because CT contains snapshots\n"
1048 if $conf->{snapshots
} && scalar(keys %{$conf->{snapshots
}});
1050 die "you can't convert a template to a template\n"
1051 if PVE
::LXC
::Config-
>is_template($conf);
1053 die "you can't convert a CT to template if the CT is running\n"
1054 if PVE
::LXC
::check_running
($vmid);
1057 PVE
::LXC
::template_create
($vmid, $conf);
1060 $conf->{template
} = 1;
1062 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1063 # and remove lxc config
1064 PVE
::LXC
::update_lxc_config
($vmid, $conf);
1066 return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd);
1069 PVE
::LXC
::Config-
>lock_config($vmid, $updatefn);
1074 __PACKAGE__-
>register_method({
1076 path
=> '{vmid}/clone',
1080 description
=> "Create a container clone/copy",
1082 description
=> "You need 'VM.Clone' permissions on /vms/{vmid}, " .
1083 "and 'VM.Allocate' permissions " .
1084 "on /vms/{newid} (or on the VM pool /pool/{pool}). You also need " .
1085 "'Datastore.AllocateSpace' on any used storage.",
1088 ['perm', '/vms/{vmid}', [ 'VM.Clone' ]],
1090 [ 'perm', '/vms/{newid}', ['VM.Allocate']],
1091 [ 'perm', '/pool/{pool}', ['VM.Allocate'], require_param
=> 'pool'],
1096 additionalProperties
=> 0,
1098 node
=> get_standard_option
('pve-node'),
1099 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1100 newid
=> get_standard_option
('pve-vmid', {
1101 completion
=> \
&PVE
::Cluster
::complete_next_vmid
,
1102 description
=> 'VMID for the clone.' }),
1105 type
=> 'string', format
=> 'dns-name',
1106 description
=> "Set a hostname for the new CT.",
1111 description
=> "Description for the new CT.",
1115 type
=> 'string', format
=> 'pve-poolid',
1116 description
=> "Add the new CT to the specified pool.",
1118 snapname
=> get_standard_option
('pve-lxc-snapshot-name', {
1121 storage
=> get_standard_option
('pve-storage-id', {
1122 description
=> "Target storage for full clone.",
1129 description
=> "Create a full copy of all disk. This is always done when " .
1130 "you clone a normal CT. For CT templates, we try to create a linked clone by default.",
1135 description
=> "The clone feature is experimental, set this " .
1136 "flag if you know what you are doing.",
1139 # target => get_standard_option('pve-node', {
1140 # description => "Target node. Only allowed if the original VM is on shared storage.",
1151 my $rpcenv = PVE
::RPCEnvironment
::get
();
1153 my $authuser = $rpcenv->get_user();
1155 my $node = extract_param
($param, 'node');
1157 my $vmid = extract_param
($param, 'vmid');
1159 my $newid = extract_param
($param, 'newid');
1161 my $pool = extract_param
($param, 'pool');
1163 if (defined($pool)) {
1164 $rpcenv->check_pool_exist($pool);
1167 my $snapname = extract_param
($param, 'snapname');
1169 my $storage = extract_param
($param, 'storage');
1171 my $localnode = PVE
::INotify
::nodename
();
1173 my $storecfg = PVE
::Storage
::config
();
1176 # check if storage is enabled on local node
1177 PVE
::Storage
::storage_check_enabled
($storecfg, $storage);
1180 PVE
::Cluster
::check_cfs_quorum
();
1182 my $running = PVE
::LXC
::check_running
($vmid) || 0;
1186 # do all tests after lock
1187 # we also try to do all tests before we fork the worker
1188 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1190 PVE
::LXC
::Config-
>check_lock($conf);
1192 my $verify_running = PVE
::LXC
::check_running
($vmid) || 0;
1194 die "unexpected state change\n" if $verify_running != $running;
1196 die "snapshot '$snapname' does not exist\n"
1197 if $snapname && !defined( $conf->{snapshots
}->{$snapname});
1199 my $oldconf = $snapname ?
$conf->{snapshots
}->{$snapname} : $conf;
1201 my $conffile = PVE
::LXC
::Config-
>config_file($newid);
1202 die "unable to create CT $newid: config file already exists\n"
1205 my $newconf = { lock => 'clone' };
1206 my $mountpoints = {};
1210 foreach my $opt (keys %$oldconf) {
1211 my $value = $oldconf->{$opt};
1213 # no need to copy unused images, because VMID(owner) changes anyways
1214 next if $opt =~ m/^unused\d+$/;
1216 if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) {
1217 my $mp = $opt eq 'rootfs' ?
1218 PVE
::LXC
::Config-
>parse_ct_rootfs($value) :
1219 PVE
::LXC
::Config-
>parse_ct_mountpoint($value);
1221 if ($mp->{type
} eq 'volume') {
1222 my $volid = $mp->{volume
};
1223 if ($param->{full
}) {
1224 die "fixme: full clone not implemented";
1226 die "Full clone feature for '$volid' is not available\n"
1227 if !PVE
::Storage
::volume_has_feature
($storecfg, 'copy', $volid, $snapname, $running);
1228 $fullclone->{$opt} = 1;
1230 # not full means clone instead of copy
1231 die "Linked clone feature for '$volid' is not available\n"
1232 if !PVE
::Storage
::volume_has_feature
($storecfg, 'clone', $volid, $snapname, $running);
1235 $mountpoints->{$opt} = $mp;
1236 push @$vollist, $volid;
1239 # TODO: allow bind mounts?
1240 die "unable to clone mountpint '$opt' (type $mp->{type})\n";
1244 # copy everything else
1245 $newconf->{$opt} = $value;
1249 delete $newconf->{template
};
1250 if ($param->{hostname
}) {
1251 $newconf->{hostname
} = $param->{hostname
};
1254 if ($param->{description
}) {
1255 $newconf->{description
} = $param->{description
};
1258 # create empty/temp config - this fails if CT already exists on other node
1259 PVE
::Tools
::file_set_contents
($conffile, "# ctclone temporary file\nlock: clone\n");
1264 my $newvollist = [];
1267 local $SIG{INT
} = $SIG{TERM
} = $SIG{QUIT
} = $SIG{HUP
} = sub { die "interrupted by signal\n"; };
1269 PVE
::Storage
::activate_volumes
($storecfg, $vollist, $snapname);
1271 foreach my $opt (keys %$mountpoints) {
1272 my $mp = $mountpoints->{$opt};
1273 my $volid = $mp->{volume
};
1275 if ($fullclone->{$opt}) {
1276 die "fixme: full clone not implemented\n";
1278 print "create linked clone of mount point $opt ($volid)\n";
1279 my $newvolid = PVE
::Storage
::vdisk_clone
($storecfg, $volid, $newid, $snapname);
1280 push @$newvollist, $newvolid;
1281 $mp->{volume
} = $newvolid;
1283 $newconf->{$opt} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $opt eq 'rootfs');
1284 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1288 delete $newconf->{lock};
1289 PVE
::LXC
::Config-
>write_config($newid, $newconf);
1291 PVE
::AccessControl
::add_vm_to_pool
($newid, $pool) if $pool;
1296 sleep 1; # some storage like rbd need to wait before release volume - really?
1298 foreach my $volid (@$newvollist) {
1299 eval { PVE
::Storage
::vdisk_free
($storecfg, $volid); };
1302 die "clone failed: $err";
1308 PVE
::Firewall
::clone_vmfw_conf
($vmid, $newid);
1310 return $rpcenv->fork_worker('vzclone', $vmid, $authuser, $realcmd);
1314 return PVE
::LXC
::Config-
>lock_config($vmid, $clonefn);
1318 __PACKAGE__-
>register_method({
1319 name
=> 'resize_vm',
1320 path
=> '{vmid}/resize',
1324 description
=> "Resize a container mount point.",
1326 check
=> ['perm', '/vms/{vmid}', ['VM.Config.Disk'], any
=> 1],
1329 additionalProperties
=> 0,
1331 node
=> get_standard_option
('pve-node'),
1332 vmid
=> get_standard_option
('pve-vmid', { completion
=> \
&PVE
::LXC
::complete_ctid
}),
1335 description
=> "The disk you want to resize.",
1336 enum
=> [PVE
::LXC
::Config-
>mountpoint_names()],
1340 pattern
=> '\+?\d+(\.\d+)?[KMGT]?',
1341 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.",
1345 description
=> 'Prevent changes if current configuration file has different SHA1 digest. This can be used to prevent concurrent modifications.',
1353 description
=> "the task ID.",
1358 my $rpcenv = PVE
::RPCEnvironment
::get
();
1360 my $authuser = $rpcenv->get_user();
1362 my $node = extract_param
($param, 'node');
1364 my $vmid = extract_param
($param, 'vmid');
1366 my $digest = extract_param
($param, 'digest');
1368 my $sizestr = extract_param
($param, 'size');
1369 my $ext = ($sizestr =~ s/^\+//);
1370 my $newsize = PVE
::JSONSchema
::parse_size
($sizestr);
1371 die "invalid size string" if !defined($newsize);
1373 die "no options specified\n" if !scalar(keys %$param);
1375 PVE
::LXC
::check_ct_modify_config_perm
($rpcenv, $authuser, $vmid, undef, $param, []);
1377 my $storage_cfg = cfs_read_file
("storage.cfg");
1381 my $conf = PVE
::LXC
::Config-
>load_config($vmid);
1382 PVE
::LXC
::Config-
>check_lock($conf);
1384 PVE
::Tools
::assert_if_modified
($digest, $conf->{digest
});
1386 my $running = PVE
::LXC
::check_running
($vmid);
1388 my $disk = $param->{disk
};
1389 my $mp = $disk eq 'rootfs' ? PVE
::LXC
::Config-
>parse_ct_rootfs($conf->{$disk}) :
1390 PVE
::LXC
::Config-
>parse_ct_mountpoint($conf->{$disk});
1392 my $volid = $mp->{volume
};
1394 my (undef, undef, $owner, undef, undef, undef, $format) =
1395 PVE
::Storage
::parse_volname
($storage_cfg, $volid);
1397 die "can't resize mount point owned by another container ($owner)"
1400 die "can't resize volume: $disk if snapshot exists\n"
1401 if %{$conf->{snapshots
}} && $format eq 'qcow2';
1403 my ($storeid, $volname) = PVE
::Storage
::parse_volume_id
($volid);
1405 $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']);
1407 PVE
::Storage
::activate_volumes
($storage_cfg, [$volid]);
1409 my $size = PVE
::Storage
::volume_size_info
($storage_cfg, $volid, 5);
1410 $newsize += $size if $ext;
1411 $newsize = int($newsize);
1413 die "unable to shrink disk size\n" if $newsize < $size;
1415 return if $size == $newsize;
1417 PVE
::Cluster
::log_msg
('info', $authuser, "update CT $vmid: resize --disk $disk --size $sizestr");
1419 # Note: PVE::Storage::volume_resize doesn't do anything if $running=1, so
1420 # we pass 0 here (parameter only makes sense for qemu)
1421 PVE
::Storage
::volume_resize
($storage_cfg, $volid, $newsize, 0);
1423 $mp->{size
} = $newsize;
1424 $conf->{$disk} = PVE
::LXC
::Config-
>print_ct_mountpoint($mp, $disk eq 'rootfs');
1426 PVE
::LXC
::Config-
>write_config($vmid, $conf);
1428 if ($format eq 'raw') {
1429 my $path = PVE
::Storage
::path
($storage_cfg, $volid, undef);
1433 my $use_loopdev = (PVE
::LXC
::mountpoint_mount_path
($mp, $storage_cfg))[1];
1434 $path = PVE
::LXC
::query_loopdev
($path) if $use_loopdev;
1435 die "internal error: CT running but mount point not attached to a loop device"
1437 PVE
::Tools
::run_command
(['losetup', '--set-capacity', $path]) if $use_loopdev;
1439 # In order for resize2fs to know that we need online-resizing a mountpoint needs
1440 # to be visible to it in its namespace.
1441 # To not interfere with the rest of the system we unshare the current mount namespace,
1442 # mount over /tmp and then run resize2fs.
1444 # interestingly we don't need to e2fsck on mounted systems...
1445 my $quoted = PVE
::Tools
::shellquote
($path);
1446 my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted";
1448 PVE
::Tools
::run_command
(['unshare', '-m', '--', 'sh', '-c', $cmd]);
1450 warn "Failed to update the container's filesystem: $@\n" if $@;
1453 PVE
::Tools
::run_command
(['e2fsck', '-f', '-y', $path]);
1454 PVE
::Tools
::run_command
(['resize2fs', $path]);
1456 warn "Failed to update the container's filesystem: $@\n" if $@;
1461 return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd);
1464 return PVE
::LXC
::Config-
>lock_config($vmid, $code);;