X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=src%2FPVE%2FAPI2%2FLXC.pm;h=9b92c13491e5d34bbfaafa830ad2185904af2723;hb=f91f36694f750a1aa96abd645dd07c963819ce8e;hp=09b71c880bda86dba3a6c8c218d7b0f1d8036020;hpb=5d6b10b3abee13018e05db43235835c0a88f34fd;p=pve-container.git diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm index 09b71c8..9b92c13 100644 --- a/src/PVE/API2/LXC.pm +++ b/src/PVE/API2/LXC.pm @@ -107,7 +107,7 @@ __PACKAGE__->register_method({ proxyto => 'node', parameters => { additionalProperties => 0, - properties => PVE::LXC::json_config_properties({ + properties => PVE::LXC::Config->json_config_properties({ node => get_standard_option('pve-node'), vmid => get_standard_option('pve-vmid', { completion => \&PVE::Cluster::complete_next_vmid }), ostemplate => { @@ -148,6 +148,12 @@ __PACKAGE__->register_method({ type => 'boolean', description => "Ignore errors when extracting the template.", }, + 'ssh-public-keys' => { + optional => 1, + type => 'string', + description => "Setup public SSH keys (one key per line, " . + "OpenSSH format).", + }, }), }, returns => { @@ -166,7 +172,7 @@ __PACKAGE__->register_method({ my $ignore_unpack_errors = extract_param($param, 'ignore-unpack-errors'); - my $basecfg_fn = PVE::LXC::config_file($vmid); + my $basecfg_fn = PVE::LXC::Config->config_file($vmid); my $same_container_exists = -f $basecfg_fn; @@ -185,12 +191,15 @@ __PACKAGE__->register_method({ if (!($same_container_exists && $restore && $force)) { PVE::Cluster::check_vmid_unused($vmid); } else { - my $conf = PVE::LXC::load_config($vmid); - PVE::LXC::check_protection($conf, "unable to restore CT $vmid"); + my $conf = PVE::LXC::Config->load_config($vmid); + PVE::LXC::Config->check_protection($conf, "unable to restore CT $vmid"); } my $password = extract_param($param, 'password'); + my $ssh_keys = extract_param($param, 'ssh-public-keys'); + PVE::Tools::validate_ssh_public_keys($ssh_keys) if defined($ssh_keys); + my $pool = extract_param($param, 'pool'); if (defined($pool)) { @@ -209,7 +218,7 @@ __PACKAGE__->register_method({ raise_perm_exc(); } - PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]); + PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, $pool, $param, []); my $storage = extract_param($param, 'storage') // 'local'; @@ -247,18 +256,32 @@ __PACKAGE__->register_method({ my $conf = {}; my $no_disk_param = {}; + my $mp_param = {}; + my $storage_only_mode = 1; foreach my $opt (keys %$param) { my $value = $param->{$opt}; if ($opt eq 'rootfs' || $opt =~ m/^mp\d+$/) { # allow to use simple numbers (add default storage in that case) - $param->{$opt} = "$storage:$value" if $value =~ m/^\d+(\.\d+)?$/; + if ($value =~ m/^\d+(\.\d+)?$/) { + $mp_param->{$opt} = "$storage:$value"; + } else { + $mp_param->{$opt} = $value; + } + $storage_only_mode = 0; + } elsif ($opt =~ m/^unused\d+$/) { + warn "ignoring '$opt', cannot create/restore with unused volume\n"; + delete $param->{$opt}; } else { $no_disk_param->{$opt} = $value; } } + die "mountpoints configured, but 'rootfs' not set - aborting\n" + if !$storage_only_mode && !defined($mp_param->{rootfs}); + # check storage access, activate storage - PVE::LXC::foreach_mountpoint($param, sub { + my $delayed_mp_param = {}; + PVE::LXC::Config->foreach_mountpoint($mp_param, sub { my ($ms, $mountpoint) = @_; my $volid = $mountpoint->{volume}; @@ -274,9 +297,9 @@ __PACKAGE__->register_method({ }); # check/activate default storage - &$check_and_activate_storage($storage) if !defined($param->{rootfs}); + &$check_and_activate_storage($storage) if !defined($mp_param->{rootfs}); - PVE::LXC::update_pct_config($vmid, $conf, 0, $no_disk_param); + PVE::LXC::Config->update_pct_config($vmid, $conf, 0, $no_disk_param); $conf->{unprivileged} = 1 if $unprivileged; @@ -291,31 +314,91 @@ __PACKAGE__->register_method({ my $code = sub { &$check_vmid_usage(); # final check after locking - + my $old_conf; + + my $config_fn = PVE::LXC::Config->config_file($vmid); + if (-f $config_fn) { + die "container exists" if !$restore; # just to be sure + $old_conf = PVE::LXC::Config->load_config($vmid); + } else { + eval { + # try to create empty config on local node, we have an flock + PVE::LXC::Config->write_config($vmid, {}); + }; + + # another node was faster, abort + die "Could not reserve ID $vmid, already taken\n" if $@; + } + PVE::Cluster::check_cfs_quorum(); my $vollist = []; eval { - if (!defined($param->{rootfs})) { + if ($storage_only_mode) { if ($restore) { - my (undef, $disksize) = PVE::LXC::Create::recover_config($archive); - die "unable to detect disk size - please specify rootfs (size)\n" - if !$disksize; - $disksize /= 1024 * 1024 * 1024; # create_disks expects GB as unit size - $param->{rootfs} = "$storage:$disksize"; + (undef, $mp_param) = PVE::LXC::Create::recover_config($archive); + die "rootfs configuration could not be recovered, please check and specify manually!\n" + if !defined($mp_param->{rootfs}); + PVE::LXC::Config->foreach_mountpoint($mp_param, sub { + my ($ms, $mountpoint) = @_; + my $type = $mountpoint->{type}; + if ($type eq 'volume') { + die "unable to detect disk size - please specify $ms (size)\n" + if !defined($mountpoint->{size}); + my $disksize = $mountpoint->{size} / (1024 * 1024 * 1024); # create_disks expects GB as unit size + delete $mountpoint->{size}; + $mountpoint->{volume} = "$storage:$disksize"; + $mp_param->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs'); + } else { + my $type = $mountpoint->{type}; + die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n" + if ($ms eq 'rootfs'); + + if ($mountpoint->{backup}) { + warn "WARNING - unsupported configuration!\n"; + warn "backup was enabled for $type mountpoint $ms ('$mountpoint->{mp}')\n"; + warn "mountpoint configuration will be restored after archive extraction!\n"; + warn "contained files will be restored to wrong directory!\n"; + } + $delayed_mp_param->{$ms} = PVE::LXC::Config->print_ct_mountpoint($mountpoint, $ms eq 'rootfs'); + } + }); } else { - $param->{rootfs} = "$storage:4"; # defaults to 4GB + $mp_param->{rootfs} = "$storage:4"; # defaults to 4GB } } - $vollist = PVE::LXC::create_disks($storage_cfg, $vmid, $param, $conf); + $vollist = PVE::LXC::create_disks($storage_cfg, $vmid, $mp_param, $conf); + + if (defined($old_conf)) { + # destroy old container volumes + PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $old_conf, {}); + } + + eval { + my $rootdir = PVE::LXC::mount_all($vmid, $storage_cfg, $conf, 1); + PVE::LXC::Create::restore_archive($archive, $rootdir, $conf, $ignore_unpack_errors); - PVE::LXC::Create::create_rootfs($storage_cfg, $vmid, $conf, $archive, $password, $restore, $ignore_unpack_errors); + if ($restore) { + PVE::LXC::Create::restore_configuration($vmid, $rootdir, $conf); + } else { + my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir); # detect OS + PVE::LXC::Config->write_config($vmid, $conf); # safe config (after OS detection) + $lxc_setup->post_create_hook($password, $ssh_keys); + } + }; + my $err = $@; + PVE::LXC::umount_all($vmid, $storage_cfg, $conf, $err ? 1 : 0); + PVE::Storage::deactivate_volumes($storage_cfg, PVE::LXC::Config->get_vm_volumes($conf)); + die $err if $err; # set some defaults $conf->{hostname} ||= "CT$vmid"; $conf->{memory} ||= 512; $conf->{swap} //= 512; - PVE::LXC::create_config($vmid, $conf); + foreach my $mp (keys %$delayed_mp_param) { + $conf->{$mp} = $delayed_mp_param->{$mp}; + } + PVE::LXC::Config->write_config($vmid, $conf); }; if (my $err = $@) { PVE::LXC::destroy_disks($storage_cfg, $vollist); @@ -325,7 +408,7 @@ __PACKAGE__->register_method({ PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool; }; - my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); }; + my $realcmd = sub { PVE::LXC::Config->lock_config($vmid, $code); }; &$check_vmid_usage(); # first check before locking @@ -364,7 +447,7 @@ __PACKAGE__->register_method({ my ($param) = @_; # test if VM exists - my $conf = PVE::LXC::load_config($param->{vmid}); + my $conf = PVE::LXC::Config->load_config($param->{vmid}); my $res = [ { subdir => 'config' }, @@ -503,11 +586,11 @@ __PACKAGE__->register_method({ my $vmid = $param->{vmid}; # test if container exists - my $conf = PVE::LXC::load_config($vmid); + my $conf = PVE::LXC::Config->load_config($vmid); my $storage_cfg = cfs_read_file("storage.cfg"); - PVE::LXC::check_protection($conf, "can't remove CT $vmid"); + PVE::LXC::Config->check_protection($conf, "can't remove CT $vmid"); die "unable to remove CT $vmid - used in HA resources\n" if PVE::HA::Config::vm_is_ha_managed($vmid); @@ -518,8 +601,8 @@ __PACKAGE__->register_method({ my $code = sub { # reload config after lock - $conf = PVE::LXC::load_config($vmid); - PVE::LXC::check_lock($conf); + $conf = PVE::LXC::Config->load_config($vmid); + PVE::LXC::Config->check_lock($conf); die $running_error_msg if PVE::LXC::check_running($vmid); @@ -528,7 +611,7 @@ __PACKAGE__->register_method({ PVE::Firewall::remove_vmfw_conf($vmid); }; - my $realcmd = sub { PVE::LXC::lock_container($vmid, 1, $code); }; + my $realcmd = sub { PVE::LXC::Config->lock_config($vmid, $code); }; return $rpcenv->fork_worker('vzdestroy', $vmid, $authuser, $realcmd); }}); @@ -598,7 +681,7 @@ __PACKAGE__->register_method ({ my $remcmd = $remip ? ['/usr/bin/ssh', '-t', $remip] : []; - my $conf = PVE::LXC::load_config($vmid, $node); + my $conf = PVE::LXC::Config->load_config($vmid, $node); my $concmd = PVE::LXC::get_console_command($vmid, $conf); my $shcmd = [ '/usr/bin/dtach', '-A', @@ -719,7 +802,7 @@ __PACKAGE__->register_method ({ my $authpath = "/vms/$vmid"; my $permissions = 'VM.Console'; - my $conf = PVE::LXC::load_config($vmid); + my $conf = PVE::LXC::Config->load_config($vmid); die "CT $vmid not running\n" if !PVE::LXC::check_running($vmid); @@ -759,6 +842,13 @@ __PACKAGE__->register_method({ description => "Use online/live migration.", optional => 1, }, + force => { + type => 'boolean', + description => "Force migration despite local bind / device" . + " mounts. WARNING: identical bind / device mounts need to ". + " be available on the target node.", + optional => 1, + }, }, }, returns => { @@ -786,7 +876,7 @@ __PACKAGE__->register_method({ my $vmid = extract_param($param, 'vmid'); # test if VM exists - PVE::LXC::load_config($vmid); + PVE::LXC::Config->load_config($vmid); # try to detect errors early if (PVE::LXC::check_running($vmid)) { @@ -872,7 +962,7 @@ __PACKAGE__->register_method({ my $feature = extract_param($param, 'feature'); - my $conf = PVE::LXC::load_config($vmid); + my $conf = PVE::LXC::Config->load_config($vmid); if($snapname){ my $snap = $conf->{snapshots}->{$snapname}; @@ -882,7 +972,7 @@ __PACKAGE__->register_method({ my $storage_cfg = PVE::Storage::config(); #Maybe include later #my $nodelist = PVE::LXC::shared_nodes($conf, $storage_cfg); - my $hasFeature = PVE::LXC::has_feature($feature, $conf, $storage_cfg, $snapname); + my $hasFeature = PVE::LXC::Config->has_feature($feature, $conf, $storage_cfg, $snapname); return { hasFeature => $hasFeature, @@ -906,6 +996,12 @@ __PACKAGE__->register_method({ properties => { node => get_standard_option('pve-node'), vmid => get_standard_option('pve-vmid', { completion => \&PVE::LXC::complete_ctid_stopped }), + experimental => { + type => 'boolean', + description => "The template feature is experimental, set this " . + "flag if you know what you are doing.", + default => 0, + }, }, }, returns => { type => 'null'}, @@ -922,14 +1018,14 @@ __PACKAGE__->register_method({ my $updatefn = sub { - my $conf = PVE::LXC::load_config($vmid); - PVE::LXC::check_lock($conf); + my $conf = PVE::LXC::Config->load_config($vmid); + PVE::LXC::Config->check_lock($conf); die "unable to create template, because CT contains snapshots\n" if $conf->{snapshots} && scalar(keys %{$conf->{snapshots}}); die "you can't convert a template to a template\n" - if PVE::LXC::is_template($conf); + if PVE::LXC::Config->is_template($conf); die "you can't convert a CT to template if the CT is running\n" if PVE::LXC::check_running($vmid); @@ -940,14 +1036,14 @@ __PACKAGE__->register_method({ $conf->{template} = 1; - PVE::LXC::write_config($vmid, $conf); + PVE::LXC::Config->write_config($vmid, $conf); # and remove lxc config - PVE::LXC::update_lxc_config(undef, $vmid, $conf); + PVE::LXC::update_lxc_config($vmid, $conf); return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd); }; - PVE::LXC::lock_container($vmid, undef, $updatefn); + PVE::LXC::Config->lock_config($vmid, $updatefn); return undef; }}); @@ -1011,6 +1107,12 @@ __PACKAGE__->register_method({ "you clone a normal CT. For CT templates, we try to create a linked clone by default.", default => 0, }, + experimental => { + type => 'boolean', + description => "The clone feature is experimental, set this " . + "flag if you know what you are doing.", + default => 0, + }, # target => get_standard_option('pve-node', { # description => "Target node. Only allowed if the original VM is on shared storage.", # optional => 1, @@ -1060,9 +1162,9 @@ __PACKAGE__->register_method({ # do all tests after lock # we also try to do all tests before we fork the worker - my $conf = PVE::LXC::load_config($vmid); + my $conf = PVE::LXC::Config->load_config($vmid); - PVE::LXC::check_lock($conf); + PVE::LXC::Config->check_lock($conf); my $verify_running = PVE::LXC::check_running($vmid) || 0; @@ -1073,7 +1175,7 @@ __PACKAGE__->register_method({ my $oldconf = $snapname ? $conf->{snapshots}->{$snapname} : $conf; - my $conffile = PVE::LXC::config_file($newid); + my $conffile = PVE::LXC::Config->config_file($newid); die "unable to create CT $newid: config file already exists\n" if -f $conffile; @@ -1090,8 +1192,8 @@ __PACKAGE__->register_method({ if (($opt eq 'rootfs') || ($opt =~ m/^mp\d+$/)) { my $mp = $opt eq 'rootfs' ? - PVE::LXC::parse_ct_rootfs($value) : - PVE::LXC::parse_ct_mountpoint($value); + PVE::LXC::Config->parse_ct_rootfs($value) : + PVE::LXC::Config->parse_ct_mountpoint($value); if ($mp->{type} eq 'volume') { my $volid = $mp->{volume}; @@ -1155,13 +1257,13 @@ __PACKAGE__->register_method({ push @$newvollist, $newvolid; $mp->{volume} = $newvolid; - $newconf->{$opt} = PVE::LXC::print_ct_mountpoint($mp, $opt eq 'rootfs'); - PVE::LXC::write_config($newid, $newconf); + $newconf->{$opt} = PVE::LXC::Config->print_ct_mountpoint($mp, $opt eq 'rootfs'); + PVE::LXC::Config->write_config($newid, $newconf); } } delete $newconf->{lock}; - PVE::LXC::write_config($newid, $newconf); + PVE::LXC::Config->write_config($newid, $newconf); PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool; }; @@ -1186,7 +1288,7 @@ __PACKAGE__->register_method({ }; - return PVE::LXC::lock_container($vmid, undef, $clonefn); + return PVE::LXC::Config->lock_config($vmid, $clonefn); }}); @@ -1208,7 +1310,7 @@ __PACKAGE__->register_method({ disk => { type => 'string', description => "The disk you want to resize.", - enum => [PVE::LXC::mountpoint_names()], + enum => [PVE::LXC::Config->mountpoint_names()], }, size => { type => 'string', @@ -1247,22 +1349,22 @@ __PACKAGE__->register_method({ die "no options specified\n" if !scalar(keys %$param); - PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]); + PVE::LXC::check_ct_modify_config_perm($rpcenv, $authuser, $vmid, undef, $param, []); my $storage_cfg = cfs_read_file("storage.cfg"); my $code = sub { - my $conf = PVE::LXC::load_config($vmid); - PVE::LXC::check_lock($conf); + my $conf = PVE::LXC::Config->load_config($vmid); + PVE::LXC::Config->check_lock($conf); PVE::Tools::assert_if_modified($digest, $conf->{digest}); my $running = PVE::LXC::check_running($vmid); my $disk = $param->{disk}; - my $mp = $disk eq 'rootfs' ? PVE::LXC::parse_ct_rootfs($conf->{$disk}) : - PVE::LXC::parse_ct_mountpoint($conf->{$disk}); + my $mp = $disk eq 'rootfs' ? PVE::LXC::Config->parse_ct_rootfs($conf->{$disk}) : + PVE::LXC::Config->parse_ct_mountpoint($conf->{$disk}); my $volid = $mp->{volume}; @@ -1279,6 +1381,8 @@ __PACKAGE__->register_method({ $rpcenv->check($authuser, "/storage/$storeid", ['Datastore.AllocateSpace']); + PVE::Storage::activate_volumes($storage_cfg, [$volid]); + my $size = PVE::Storage::volume_size_info($storage_cfg, $volid, 5); $newsize += $size if $ext; $newsize = int($newsize); @@ -1294,9 +1398,9 @@ __PACKAGE__->register_method({ PVE::Storage::volume_resize($storage_cfg, $volid, $newsize, 0); $mp->{size} = $newsize; - $conf->{$disk} = PVE::LXC::print_ct_mountpoint($mp, $disk eq 'rootfs'); + $conf->{$disk} = PVE::LXC::Config->print_ct_mountpoint($mp, $disk eq 'rootfs'); - PVE::LXC::write_config($vmid, $conf); + PVE::LXC::Config->write_config($vmid, $conf); if ($format eq 'raw') { my $path = PVE::Storage::path($storage_cfg, $volid, undef); @@ -1317,10 +1421,16 @@ __PACKAGE__->register_method({ # interestingly we don't need to e2fsck on mounted systems... my $quoted = PVE::Tools::shellquote($path); my $cmd = "mount --make-rprivate / && mount $quoted /tmp && resize2fs $quoted"; - PVE::Tools::run_command(['unshare', '-m', '--', 'sh', '-c', $cmd]); + eval { + PVE::Tools::run_command(['unshare', '-m', '--', 'sh', '-c', $cmd]); + }; + warn "Failed to update the container's filesystem: $@\n" if $@; } else { - PVE::Tools::run_command(['e2fsck', '-f', '-y', $path]); - PVE::Tools::run_command(['resize2fs', $path]); + eval { + PVE::Tools::run_command(['e2fsck', '-f', '-y', $path]); + PVE::Tools::run_command(['resize2fs', $path]); + }; + warn "Failed to update the container's filesystem: $@\n" if $@; } } }; @@ -1328,7 +1438,7 @@ __PACKAGE__->register_method({ return $rpcenv->fork_worker('resize', $vmid, $authuser, $realcmd); }; - return PVE::LXC::lock_container($vmid, undef, $code);; + return PVE::LXC::Config->lock_config($vmid, $code);; }}); 1;