X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=src%2FPVE%2FAPI2%2FLXC.pm;h=1cbd3d83b82fe365c3cf03598bbcffff5a54941d;hb=80440faa0ce47df75fab407f9f87dccf200965b2;hp=92052151dcb0a358b4c3e8d2351e163f802a4b4b;hpb=d53e5c58d517cd7450dbc3d4ebee45ab023d57bb;p=pve-container.git diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm index 9205215..1cbd3d8 100644 --- a/src/PVE/API2/LXC.pm +++ b/src/PVE/API2/LXC.pm @@ -5,7 +5,7 @@ use warnings; use PVE::SafeSyslog; use PVE::Tools qw(extract_param run_command); -use PVE::Exception qw(raise raise_param_exc); +use PVE::Exception qw(raise raise_param_exc raise_perm_exc); use PVE::INotify; use PVE::Cluster qw(cfs_read_file); use PVE::AccessControl; @@ -74,7 +74,7 @@ __PACKAGE__->register_method({ type => 'array', items => { type => "object", - properties => {}, + properties => $PVE::LXC::vmstatus_return_properties, }, links => [ { rel => 'child', href => "{vmid}" } ], }, @@ -91,7 +91,6 @@ __PACKAGE__->register_method({ next if !$rpcenv->check($authuser, "/vms/$vmid", [ 'VM.Audit' ], 1); my $data = $vmstatus->{$vmid}; - $data->{vmid} = $vmid; push @$res, $data; } @@ -161,6 +160,18 @@ __PACKAGE__->register_method({ description => "Setup public SSH keys (one key per line, " . "OpenSSH format).", }, + bwlimit => { + description => "Override i/o bandwidth limit (in KiB/s).", + optional => 1, + type => 'number', + minimum => '0', + }, + start => { + optional => 1, + type => 'boolean', + default => 0, + description => "Start the CT after its creation finished successfully.", + }, }), }, returns => { @@ -169,28 +180,26 @@ __PACKAGE__->register_method({ code => sub { my ($param) = @_; - my $rpcenv = PVE::RPCEnvironment::get(); + PVE::Cluster::check_cfs_quorum(); + my $rpcenv = PVE::RPCEnvironment::get(); my $authuser = $rpcenv->get_user(); my $node = extract_param($param, 'node'); - my $vmid = extract_param($param, 'vmid'); - my $ignore_unpack_errors = extract_param($param, 'ignore-unpack-errors'); + my $bwlimit = extract_param($param, 'bwlimit'); + my $start_after_create = extract_param($param, 'start'); my $basecfg_fn = PVE::LXC::Config->config_file($vmid); - my $same_container_exists = -f $basecfg_fn; # 'unprivileged' is read-only, so we can't pass it to update_pct_config my $unprivileged = extract_param($param, 'unprivileged'); - my $restore = extract_param($param, 'restore'); if ($restore) { # fixme: limit allowed parameters - } my $force = extract_param($param, 'force'); @@ -198,17 +207,16 @@ __PACKAGE__->register_method({ if (!($same_container_exists && $restore && $force)) { PVE::Cluster::check_vmid_unused($vmid); } else { + die "can't overwrite running container\n" if PVE::LXC::check_running($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)) { $rpcenv->check_pool_exist($pool); $rpcenv->check_perm_modify($authuser, "/pool/$pool"); @@ -232,9 +240,7 @@ __PACKAGE__->register_method({ my $storage_cfg = cfs_read_file("storage.cfg"); - my $archive; - if ($ostemplate eq '-') { die "pipe requires cli environment\n" if $rpcenv->{type} ne 'cli'; @@ -247,6 +253,7 @@ __PACKAGE__->register_method({ $archive = PVE::Storage::abs_filesystem_path($storage_cfg, $ostemplate); } + my %used_storages; my $check_and_activate_storage = sub { my ($sid) = @_; @@ -258,10 +265,13 @@ __PACKAGE__->register_method({ $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']); PVE::Storage::activate_storage($storage_cfg, $sid); + $used_storages{$sid} = 1; }; my $conf = {}; + my $is_root = $authuser eq 'root@pam'; + my $no_disk_param = {}; my $mp_param = {}; my $storage_only_mode = 1; @@ -296,7 +306,7 @@ __PACKAGE__->register_method({ if ($mountpoint->{type} ne 'volume') { # bind or device die "Only root can pass arbitrary filesystem paths.\n" - if $authuser ne 'root@pam'; + if !$is_root; } else { my ($sid, $volname) = PVE::Storage::parse_volume_id($volid); &$check_and_activate_storage($sid); @@ -310,40 +320,35 @@ __PACKAGE__->register_method({ $conf->{unprivileged} = 1 if $unprivileged; - my $check_vmid_usage = sub { - if ($force) { - die "can't overwrite running container\n" - if PVE::LXC::check_running($vmid); - } else { - PVE::Cluster::check_vmid_unused($vmid); - } - }; - - my $code = sub { - &$check_vmid_usage(); # final check after locking - my $old_conf; + my $emsg = $restore ? "unable to restore CT $vmid -" : "unable to create CT $vmid -"; - 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, {}); - }; + eval { PVE::LXC::Config->create_and_lock_config($vmid, $force) }; + die "$emsg $@" if $@; - # another node was faster, abort - die "Could not reserve ID $vmid, already taken\n" if $@; - } + my $code = sub { + my $old_conf = PVE::LXC::Config->load_config($vmid); - PVE::Cluster::check_cfs_quorum(); my $vollist = []; - eval { + my $orig_mp_param; # only used if $restore + if ($restore) { + die "can't overwrite running container\n" if PVE::LXC::check_running($vmid); + if ($is_root && $archive ne '-') { + my $orig_conf; + ($orig_conf, $orig_mp_param) = PVE::LXC::Create::recover_config($archive); + # When we're root call 'restore_configuration' with ristricted=0, + # causing it to restore the raw lxc entries, among which there may be + # 'lxc.idmap' entries. We need to make sure that the extracted contents + # of the container match up with the restored configuration afterwards: + $conf->{lxc} = [grep { $_->[0] eq 'lxc.idmap' } @{$orig_conf->{lxc}}]; + } + } if ($storage_only_mode) { if ($restore) { - (undef, $mp_param) = PVE::LXC::Create::recover_config($archive); + if (!defined($orig_mp_param)) { + (undef, $orig_mp_param) = PVE::LXC::Create::recover_config($archive); + } + $mp_param = $orig_mp_param; 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 { @@ -361,7 +366,7 @@ __PACKAGE__->register_method({ die "restoring rootfs to $type mount is only possible by specifying -rootfs manually!\n" if ($ms eq 'rootfs'); die "restoring '$ms' to $type mount is only possible for root\n" - if $authuser ne 'root@pam'; + if !$is_root; if ($mountpoint->{backup}) { warn "WARNING - unsupported configuration!\n"; @@ -380,17 +385,19 @@ __PACKAGE__->register_method({ $vollist = PVE::LXC::create_disks($storage_cfg, $vmid, $mp_param, $conf); - if (defined($old_conf)) { + # we always have the 'create' lock so check for more than 1 entry + if (scalar(keys %$old_conf) > 1) { # destroy old container volumes - PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $old_conf, {}); + PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $old_conf, { lock => 'create' }); } eval { my $rootdir = PVE::LXC::mount_all($vmid, $storage_cfg, $conf, 1); - PVE::LXC::Create::restore_archive($archive, $rootdir, $conf, $ignore_unpack_errors); + $bwlimit = PVE::Storage::get_bandwidth_limit('restore', [keys %used_storages], $bwlimit); + PVE::LXC::Create::restore_archive($archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit); if ($restore) { - PVE::LXC::Create::restore_configuration($vmid, $rootdir, $conf, $authuser ne 'root@pam'); + PVE::LXC::Create::restore_configuration($vmid, $rootdir, $conf, !$is_root); } else { my $lxc_setup = PVE::LXC::Setup->new($conf, $rootdir); # detect OS PVE::LXC::Config->write_config($vmid, $conf); # safe config (after OS detection) @@ -412,19 +419,20 @@ __PACKAGE__->register_method({ }; if (my $err = $@) { PVE::LXC::destroy_disks($storage_cfg, $vollist); - PVE::LXC::destroy_config($vmid); - die $err; + eval { PVE::LXC::destroy_config($vmid) }; + warn $@ if $@; + die "$emsg $err"; } PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool; + + PVE::API2::LXC::Status->vm_start({ vmid => $vmid, node => $node }) + if $start_after_create; }; + my $workername = $restore ? 'vzrestore' : 'vzcreate'; my $realcmd = sub { PVE::LXC::Config->lock_config($vmid, $code); }; - &$check_vmid_usage(); # first check before locking - - return $rpcenv->fork_worker($restore ? 'vzrestore' : 'vzcreate', - $vmid, $authuser, $realcmd); - + return $rpcenv->fork_worker($workername, $vmid, $authuser, $realcmd); }}); __PACKAGE__->register_method({ @@ -711,7 +719,7 @@ __PACKAGE__->register_method ({ ['/usr/bin/ssh', '-e', 'none', '-t', $remip] : []; my $conf = PVE::LXC::Config->load_config($vmid, $node); - my $concmd = PVE::LXC::get_console_command($vmid, $conf, 1); + my $concmd = PVE::LXC::get_console_command($vmid, $conf, -1); my $shcmd = [ '/usr/bin/dtach', '-A', "/var/run/dtach/vzctlconsole$vmid", @@ -814,7 +822,7 @@ __PACKAGE__->register_method ({ ['/usr/bin/ssh', '-e', 'none', '-t', $remip, '--'] : []; my $conf = PVE::LXC::Config->load_config($vmid, $node); - my $concmd = PVE::LXC::get_console_command($vmid, $conf, 1); + my $concmd = PVE::LXC::get_console_command($vmid, $conf, -1); my $shcmd = [ '/usr/bin/dtach', '-A', "/var/run/dtach/vzctlconsole$vmid", @@ -1154,15 +1162,24 @@ __PACKAGE__->register_method({ die "you can't convert a CT to template if the CT is running\n" if PVE::LXC::check_running($vmid); + my $scfg = PVE::Storage::config(); + PVE::LXC::Config->foreach_mountpoint($conf, sub { + my ($ms, $mp) = @_; + + my ($sid) =PVE::Storage::parse_volume_id($mp->{volume}, 0); + die "Directory storage '$sid' does not support container templates!\n" + if $scfg->{ids}->{$sid}->{path}; + }); + my $realcmd = sub { PVE::LXC::template_create($vmid, $conf); - }; - $conf->{template} = 1; + $conf->{template} = 1; - PVE::LXC::Config->write_config($vmid, $conf); - # and remove lxc config - PVE::LXC::update_lxc_config($vmid, $conf); + PVE::LXC::Config->write_config($vmid, $conf); + # and remove lxc config + PVE::LXC::update_lxc_config($vmid, $conf); + }; return $rpcenv->fork_worker('vztemplate', $vmid, $authuser, $realcmd); }; @@ -1229,10 +1246,10 @@ __PACKAGE__->register_method({ description => "Create a full copy of all disks. This is always done when " . "you clone a normal CT. For CT templates, we try to create a linked clone by default.", }, -# target => get_standard_option('pve-node', { -# description => "Target node. Only allowed if the original VM is on shared storage.", -# optional => 1, -# }), + target => get_standard_option('pve-node', { + description => "Target node. Only allowed if the original VM is on shared storage.", + optional => 1, + }), }, }, returns => { @@ -1261,13 +1278,26 @@ __PACKAGE__->register_method({ my $storage = extract_param($param, 'storage'); + my $target = extract_param($param, 'target'); + my $localnode = PVE::INotify::nodename(); + undef $target if $target && ($target eq $localnode || $target eq 'localhost'); + + PVE::Cluster::check_node_exists($target) if $target; + my $storecfg = PVE::Storage::config(); if ($storage) { # check if storage is enabled on local node PVE::Storage::storage_check_enabled($storecfg, $storage); + if ($target) { + # check if storage is available on target node + PVE::Storage::storage_check_node($storecfg, $storage, $target); + # clone only works if target storage is shared + my $scfg = PVE::Storage::storage_config($storecfg, $storage); + die "can't clone to non-shared storage '$storage'\n" if !$scfg->{shared}; + } } PVE::Cluster::check_cfs_quorum(); @@ -1277,10 +1307,13 @@ __PACKAGE__->register_method({ my $mountpoints = {}; my $fullclone = {}; my $vollist = []; + my $running; PVE::LXC::Config->lock_config($vmid, sub { my $src_conf = PVE::LXC::Config->set_lock($vmid, 'disk'); + $running = PVE::LXC::check_running($vmid) || 0; + my $full = extract_param($param, 'full'); if (!defined($full)) { $full = !PVE::LXC::Config->is_template($src_conf); @@ -1291,7 +1324,6 @@ __PACKAGE__->register_method({ die "snapshot '$snapname' does not exist\n" if $snapname && !defined($src_conf->{snapshots}->{$snapname}); - my $running = PVE::LXC::check_running($vmid) || 0; my $src_conf = $snapname ? $src_conf->{snapshots}->{$snapname} : $src_conf; @@ -1299,6 +1331,7 @@ __PACKAGE__->register_method({ die "unable to create CT $newid: config file already exists\n" if -f $conffile; + my $sharedvm = 1; foreach my $opt (keys %$src_conf) { next if $opt =~ m/^unused\d+$/; @@ -1311,6 +1344,17 @@ __PACKAGE__->register_method({ if ($mp->{type} eq 'volume') { my $volid = $mp->{volume}; + + my ($sid, $volname) = PVE::Storage::parse_volume_id($volid); + $sid = $storage if defined($storage); + my $scfg = PVE::Storage::storage_config($storecfg, $sid); + if (!$scfg->{shared}) { + $sharedvm = 0; + warn "found non-shared volume: $volid\n" if $target; + } + + $rpcenv->check($authuser, "/storage/$sid", ['Datastore.AllocateSpace']); + if ($full) { die "Cannot do full clones on a running container without snapshots\n" if $running && !defined($snapname); @@ -1328,11 +1372,19 @@ __PACKAGE__->register_method({ # TODO: allow bind mounts? die "unable to clone mountpint '$opt' (type $mp->{type})\n"; } + } elsif ($opt =~ m/^net(\d+)$/) { + # always change MAC! address + my $dc = PVE::Cluster::cfs_read_file('datacenter.cfg'); + my $net = PVE::LXC::Config->parse_lxc_network($value); + $net->{hwaddr} = PVE::Tools::random_ether_addr($dc->{mac_prefix}); + $newconf->{$opt} = PVE::LXC::Config->print_lxc_network($net); } else { # copy everything else $newconf->{$opt} = $value; } } + die "can't clone CT to node '$target' (CT uses local storage)\n" + if $target && !$sharedvm; # Replace the 'disk' lock with a 'create' lock. $newconf->{lock} = 'create'; @@ -1372,6 +1424,9 @@ __PACKAGE__->register_method({ my $newvollist = []; + my $verify_running = PVE::LXC::check_running($vmid) || 0; + die "unexpected state change\n" if $verify_running != $running; + eval { local $SIG{INT} = local $SIG{TERM} = @@ -1402,6 +1457,16 @@ __PACKAGE__->register_method({ PVE::AccessControl::add_vm_to_pool($newid, $pool) if $pool; PVE::LXC::Config->remove_lock($newid, 'create'); + + if ($target) { + # always deactivate volumes - avoid lvm LVs to be active on several nodes + PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running; + PVE::Storage::deactivate_volumes($storecfg, $newvollist); + + my $newconffile = PVE::LXC::Config->config_file($newid, $target); + die "Failed to move config to node '$target' - rename failed: $!\n" + if !rename($conffile, $newconffile); + } }; my $err = $@; @@ -1541,7 +1606,7 @@ __PACKAGE__->register_method({ PVE::LXC::Config->write_config($vmid, $conf); if ($format eq 'raw') { - my $path = PVE::Storage::path($storage_cfg, $volid, undef); + my $path = PVE::Storage::map_volume($storage_cfg, $volid) // PVE::Storage::path($storage_cfg, $volid); if ($running) { $mp->{mp} = '/'; @@ -1569,6 +1634,8 @@ __PACKAGE__->register_method({ PVE::Tools::run_command(['resize2fs', $path]); }; warn "Failed to update the container's filesystem: $@\n" if $@; + + PVE::Storage::unmap_volume($storage_cfg, $volid); } } };