Commit eb7a9cfa authored by Mike Hibler's avatar Mike Hibler
Browse files

Merge branch 'master' of gitlab.flux.utah.edu:emulab/emulab-devel

parents 39acf3fa 292175ec
#!/usr/bin/perl -wT
#
# Copyright (c) 2000-2016 University of Utah and the Flux Group.
# Copyright (c) 2000-2017 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -378,7 +378,12 @@ if (exists($editnodetype_args{"class"})) {
if (exists($editnodetype_args{"architecture"})) {
my $architecture = $editnodetype_args{"architecture"};
push(@nodetype_data, "architecture='$architecture'");
if ($architecture eq "") {
push(@nodetype_data, "architecture=NULL");
}
else {
push(@nodetype_data, "architecture='$architecture'");
}
}
# The rest of them all have names starting with "is" at present.
......
......@@ -469,6 +469,7 @@ docker-install: dir-install
$(INSTALL) -m 755 $(SRCDIR)/../common/bootvnodes $(BINDIR)/
$(INSTALL) -m 755 $(SRCDIR)/libvnode.pm $(BINDIR)/
$(INSTALL) -m 755 $(SRCDIR)/docker/libvnode_docker.pm $(BINDIR)/
$(INSTALL) -m 755 $(SRCDIR)/docker/create-docker-image $(LBINDIR)/
$(INSTALL) -m 755 $(SRCDIR)/vnodectl $(BINDIR)/
echo "docker" > $(ETCDIR)/genvmtype
$(INSTALL) -m 755 -o root -g $(DIRGROUP) -d $(ETCDIR)/docker
......
#!/usr/bin/perl -w
#
# Copyright (c) 2017 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
# This file is part of the Emulab network testbed software.
#
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this file. If not, see <http://www.gnu.org/licenses/>.
#
# }}}
#
use English;
use Getopt::Std;
use strict;
# Drag in path stuff so we can find emulab stuff.
BEGIN { require "/etc/emulab/paths.pm"; import emulabpaths; }
my $VNODESETUP = "$BINDIR/vnodesetup";
#
# Client-side to create a disk image. Caller must be root!
# This is the Docker-specific version.
#
sub usage()
{
print STDOUT "" .
"Usage: create-docker-image -R <registry> -r <repository> -t <tag>" .
" <vnodeid>\n";
exit(-1);
}
my $optlist = "R:r:t:u:p:";
my $filename;
if ($UID != 0) {
print STDERR "Must be root!\n";
}
#
# Turn off line buffering on output
#
$| = 1;
# Need this for predicates.
use libsetup;
use libvnode_docker;
use libgenvnode;
#use libvnode;
use libutil;
#
# No configure vars.
#
my $vnodeid;
my ($registry,$repo,$tag,$user,$pass);
#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"R"})) {
$registry = $options{"R"};
}
else {
die("No registry (-R) supplied!");
}
if (defined($options{"r"})) {
$repo = $options{"r"};
}
else {
die("No repository (-r) supplied!");
}
if (defined($options{"t"})) {
$tag = $options{"t"};
}
else {
die("No tag (-t) supplied!");
}
if (defined($options{"u"})) {
$user = $options{"u"};
}
else {
die("No user (-u) supplied!");
}
if (defined($options{"p"})) {
$pass = $options{"p"};
}
else {
die("No pass (-p) supplied!");
}
$vnodeid = shift(@ARGV);
if (!defined($vnodeid) || $vnodeid eq '') {
print STDERR "ERROR: no vnodeid specified!\n";
usage();
}
#
# First, try to login to the registry with user/pass:
#
my $i = 10;
while ($i > 0) {
system("docker login -p '$pass' -u '$user' $registry");
last
if ($? == 0);
print STDERR "ERROR: failed to login to registry $registry; sleeping and trying again...\n";
sleep(4);
$i -= 1;
}
#
# Check container status. If it is running, we need to stop it. We
# don't run prepare on the way down; we run it as an ONBUILD instruction
# in the docker commit process.
#
my ($initstatus,$status);
$initstatus = $status = libvnode_docker::vnodeState($vnodeid);
if ($status eq VNODE_STATUS_UNKNOWN()) {
print STDERR "ERROR: docker container $vnodeid does not seem to exist!\n";
system("docker logout $registry");
exit(1);
}
#
# Try to stop the container.
#
$i = 10;
while ($i > 0 && $status ne VNODE_STATUS_STOPPED()) {
system("docker stop $vnodeid");
$i -=1;
$status = libvnode_docker::vnodeState($vnodeid);
}
if ($status ne VNODE_STATUS_STOPPED()) {
print STDERR "ERROR: failed to stop docker container $vnodeid; aborting!\n";
system("docker logout $registry");
exit(1);
}
#
# (Locally) commit the image.
#
my $fullimagename = "$registry/$repo:$tag";
system("docker commit $vnodeid $fullimagename");
if ($?) {
print STDERR "ERROR: failed to commit image $fullimagename for container $vnodeid; aborting!\n";
system("docker logout $registry");
exit(1);
}
if ($initstatus ne VNODE_STATUS_STOPPED()) {
system("docker start $vnodeid");
if ($?) {
print STDERR "WARNING: error restarting container $vnodeid; ignoring!\n";
}
}
else {
print STDERR "WARNING: not restarting previously stopped container $vnodeid\n";
}
# Push the image and logout.
$i = 10;
my $success = 0;
while ($i > 0) {
system("docker push $fullimagename");
if ($? == 0) {
$success = 1;
last;
}
print STDERR "ERROR: failed to push $fullimagename; sleeping and trying again...\n";
sleep(4);
$i -= 1;
}
system("docker logout $registry");
if (!$success) {
exit(1);
}
exit(0);
......@@ -76,7 +76,9 @@ use IO::Select;
use File::Basename;
use File::Path;
use File::Copy;
use File::Temp;
use File::Temp qw(tempdir);
use MIME::Base64 qw(encode_base64url encode_base64);
use URI::Escape;
use POSIX;
use JSON::PP;
......@@ -355,8 +357,8 @@ sub bindNetNS($$);
sub moveNetDeviceToNetNS($$$);
sub moveNetDeviceFromNetNS($$$);
sub unbindNetNS($$);
sub setupImage($$$$$$$);
sub pullImage($$;$);
sub setupImage($$$$$$$$$);
sub pullImage($$$$;$);
sub analyzeImage($$);
sub AllocateIFBs($$$);
sub ReleaseIFBs($$);
......@@ -423,8 +425,11 @@ sub ImageLockName($)
{
my ($imagename) = @_;
return "dockerimage." .
my $ln = "dockerimage." .
(defined($imagename) ? $imagename : $defaultImage{'name'});
$ln =~ tr/\//-/;
return $ln;
}
sub ImageLVName($)
......@@ -2305,9 +2310,9 @@ sub vnodeCreate($$$$)
my ($vnode_id, undef, $vnconfig, $private) = @_;
my $attributes = $vnconfig->{'attributes'};
my $imagename = $vnconfig->{'image'};
my $inreload = defined($imagename) ? 1 : 0;
my $raref = $vnconfig->{'reloadinfo'};
my $vninfo = $private;
my %image = %defaultImage;
my %mounts = ();
my $imagemetadata;
my $lvname;
......@@ -2326,6 +2331,50 @@ sub vnodeCreate($$$$)
my ($host_iface,$host_ip,$host_mask,$host_maskbits,$host_net,
$host_mac,$host_gw) = findControlNet();
if (defined($raref)) {
$raref = $raref->[0];
$inreload = 1;
}
#
# Figure out where/what we're pulling, and a username/password if
# necessary.
#
my ($user,$pass);
if ((!$imagename || $imagename =~ /^emulab-ops-emulab-ops-DOCKER-EXT/)
&& exists($attributes->{'DOCKER_EXTIMAGE'})) {
$imagename = $attributes->{'DOCKER_EXTIMAGE'};
if (exists($attributes->{'DOCKER_EXTUSER'})) {
$user = $attributes->{'DOCKER_EXTUSER'};
}
if (exists($attributes->{'DOCKER_EXTPASS'})) {
$pass = $attributes->{'DOCKER_EXTPASS'};
}
}
elsif ($inreload) {
# For local reloads, username is physical host shortname;
# password is the eventkey.
open(FD,"$BOOTDIR/nodeid")
or die("open($BOOTDIR/nodeid): $!");
$user = <FD>;
chomp($user);
close(FD);
open(FD,"$BOOTDIR/eventkey")
or die("open($BOOTDIR/eventkey): $!");
$pass = <FD>;
chomp($pass);
close(FD);
print "raref:" . Dumper($raref) . "\n";
if (!exists($raref->{"PATH"}) || !$raref->{"PATH"}) {
fatal("reload specified, but not external image, and no image PATH!");
}
$imagename = $raref->{"PATH"};
}
else {
$imagename = $defaultImage{'name'};
}
#
# XXX future optimization possibility.
#
......@@ -2351,20 +2400,32 @@ sub vnodeCreate($$$$)
fatal("CreateVnodeLock()");
}
if (exists($attributes->{'DOCKER_EXTIMAGE'})) {
$imagename = $attributes->{'DOCKER_EXTIMAGE'};
}
else {
$imagename = $image{'name'};
if ($inreload) {
# No real difference for us here; RELOADING has a longer timeout too.
libutil::setState("RELOADSETUP");
libutil::setState("RELOADING");
}
my ($newimagename,$newcreateargs,$newcmd);
$rc = setupImage($vnode_id,$vnconfig,$private,$imagename,
$rc = setupImage($vnode_id,$vnconfig,$private,$imagename,$user,$pass,
\$newimagename,\$newcreateargs,\$newcmd);
if ($rc) {
libutil::setState("RELOADFAILED");
fatal("Failed to setup $imagename for $vnode_id; aborting!");
}
if ($inreload) {
libutil::setState("RELOADDONE");
# XXX why do we need to wait for this to take effect?
TBDebugTimeStamp("waiting 4 sec after asserting RELOADDONE...");
sleep(4);
#
# Finish off the state transitions as necessary.
#
libutil::setState("SHUTDOWN");
}
CreateVnodeUnlock();
#
......@@ -2865,7 +2926,7 @@ sub vnodeConfigDevices($$$$)
return 0;
}
sub vnodeState($$$$)
sub vnodeState($;$$$)
{
my ($vnode_id, $vmid, $vnconfig, $private) = @_;
......@@ -3549,9 +3610,9 @@ sub DOCKER_INIT_RUNIT() { return "runit"; }
sub DOCKER_PULLPOLICY_LATEST() { return "latest"; }
sub DOCKER_PULLPOLICY_CACHED() { return "cached"; }
sub pullImage($$;$)
sub pullImage($$$$;$)
{
my ($image,$policy,$newref) = @_;
my ($image,$user,$pass,$policy,$newref) = @_;
if (!defined($policy) || $policy eq '' || SHAREDHOST()) {
$policy = DOCKER_PULLPOLICY_LATEST();
......@@ -3579,8 +3640,66 @@ sub pullImage($$;$)
TBDebugTimeStamp(" got image lock $imagelockname for $image")
if ($lockdebug);
my $output = `docker pull $image`;
my $rc = $? >> 8;
#
# Try one more time to inspect, and release the lock if we have it.
#
if ($policy eq DOCKER_PULLPOLICY_CACHED()) {
mysystem2("docker inspect $image");
if ($? == 0) {
TBDebugTimeStamp(" releasing image lock")
if ($lockdebug);
TBScriptUnlock();
return 0;
}
}
#
# Also, because we might need to authenticate, use the Docker API to
# pull the image instead of trying to lock the Docker config file in
# case we have separate creds for the same registry, or use a
# separate config file (the former of which is not preferable, the
# latter of which I couldn't make work).
#
# XXX: This also means the base64-encoded creds will show up in the
# process list, so fix that later.
#
my $authhdrstr = "";
if (defined($user) && $user ne "") {
# Default to the default registry if the image doesn't have a
# host:port part.
my $registry = "registry-1.docker.io";
if ($image =~ /^([a-zA-Z0-9-\.]+)(:\d+)?\/.*$/) {
$registry = "$1$2";
}
my $auth = encode_base64url('{"username":"'.$user.'","password":"'.$pass.'"}'."");
chomp($auth);
$auth =~ tr/\r\n//;
$authhdrstr = "-H 'X-Registry-Auth: $auth'";
}
my $output = "";
my $uimage = uri_escape($image);
my $ccmd = "$CURL --unix-socket /var/run/docker.sock".
" -H 'Content-Type: application/json' $authhdrstr".
" -X POST 'http:/images/create?fromImage=$uimage'";
my $retries = 10;
my $ret = 1;
while ($ret && $retries > 0) {
$output = `$ccmd`;
$ret = $?;
if ($ret == 0) {
TBDebugTimeStamp("pull $image succeeded ($ccmd)");
last;
}
my $ustr = "";
if (defined($user)) {
$ustr = " as user $user";
}
TBDebugTimeStamp("pull $image failed$ustr;" .
" sleeping and retrying...");
sleep(8);
$retries -= 1;
}
my $rc = $ret >> 8;
if ($rc) {
TBDebugTimeStamp("failed to pull image $image!");
}
......@@ -3595,9 +3714,9 @@ sub pullImage($$;$)
return $rc;
}
sub setupImage($$$$$$$)
sub setupImage($$$$$$$$$)
{
my ($vnode_id,$vnconfig,$private,$image,
my ($vnode_id,$vnconfig,$private,$image,$username,$password,
$newimageref,$newcreateargsref,$newcmdref) = @_;
my $rc;
my $cwd;
......@@ -3653,7 +3772,7 @@ sub setupImage($$$$$$$)
$pullpolicy = DOCKER_PULLPOLICY_CACHED();
}
my $havenewbase = 0;
if (pullImage($image,$pullpolicy,\$havenewbase)) {
if (pullImage($image,$username,$password,$pullpolicy,\$havenewbase)) {
warn("failed to pull Docker image $image");
return -1;
}
......
......@@ -1063,7 +1063,7 @@ sub LocalVersionURL($)
{
my ($self) = @_;
return $self->image()->LocalURL();
return $self->image()->LocalVersionURL();
}
sub HasCredential($)
......
......@@ -1246,7 +1246,18 @@ sub MaxSwapIn($$$) {
$reservation->SetNodes( $reservation->nodes() - $overflow );
}
return $reservation->nodes() > 0 ? $reservation->nodes() : 0;
my $avail = $reservation->nodes() > 0 ? $reservation->nodes() : 0;
# Now consider nodes prereserved to the project but currently unused.
my $query_result = DBQueryFatal( "SELECT COUNT(*) FROM nodes AS n " .
"LEFT OUTER JOIN reserved AS r " .
"ON n.node_id=r.node_id " .
"WHERE r.pid IS NULL AND n.type='" .
$type . "' AND n.reserved_pid='" .
$experiment->pid() . "'" );
my ($extra) = $query_result->fetchrow_array();
return $avail + $extra;
}
#
......
......@@ -1075,6 +1075,7 @@ sub Action($$$;$)
my %vnodes = ();
my %poweron = ();
my %reloads = ();
my %reloaded_nodes = ();
my %vnodekills = ();
my %imageinfo = ();
my @waitvnodes = ();
......@@ -1138,6 +1139,20 @@ sub Action($$$;$)
$msg .= "Could not map $sliver to a node";
goto bad;
}
#
# If we already setup this phys node as a side-effect of setting
# up a virtnode, don't try to handle it again. For a dedicated
# node, setting up two reloads would result in sending the same
# node twice to osload, which is a bug osload didn't previously
# catch. This condition is down in the isvirtnode()
# conditional, and skips a previously-handled vhost, but we need
# to skip it just in case the vhost is explicitly a sliver in
# the rspec.
#
next
if (exists($poweron{$node->node_id()}) ||
exists($reboots{$node->node_id()}) ||
exists($reloaded_nodes{$node->node_id()}));
my $reservation = $node->Reservation();
if (!defined($reservation)) {
$msg .= "$node no longer belongs to $self";
......@@ -1220,6 +1235,7 @@ sub Action($$$;$)
$reloads{$image->versid()} = [ ];
}
push(@{ $reloads{$image->versid()} }, $vnode);
$reloaded_nodes{$vnode->node_id()} = $vnode;
$imageinfo{$vnode->node_id()} = [$osinfo, $image];
$vnode->_reloaded(1);
$vnode->_image($image);
......@@ -1285,7 +1301,7 @@ sub Action($$$;$)
next
if (exists($poweron{$physnodeid}) ||
exists($reboots{$physnodeid}) ||
exists($reloads{$physnodeid}));
exists($reloaded_nodes{$physnodeid}));
#
# We continue below, but now looking at the physical node
# that the vnode is running one.
......@@ -1363,6 +1379,7 @@ sub Action($$$;$)
$reloads{$image->versid()} = [ ];
}
push(@{ $reloads{$image->versid()} }, $node);
$reloaded_nodes{$node->node_id()} = $node;
}
$node->_reloaded(1);
$node->_image($image);
......@@ -1928,6 +1945,7 @@ sub WaitForNodes($$$$@)
# We are now a monitor.
$slice->SetMonitorPid($PID);
print STDERR "Monitor PID $PID\n";
#
# This is essentially what libossetup (os_setup) does. I want to
......@@ -2296,6 +2314,7 @@ sub WaitForNodes($$$$@)
$experiment->SetCancelFlag(0);
}
$slice->UnLockTables();
print STDERR "WaitNodes finished (monitor PID $PID done).\n";
return 0;
}
......
......@@ -472,6 +472,7 @@ sub UpdateImage($$)
"visibility" => $blob->{'visibility'},
"virtualizaton" => $blob->{'virtualizaton'},
"osfeatures" => $blob->{'osfeatures'},
"metadata_url" => $blob->{'metadata_url'},
"types_known_working" => join(",", sort(keys(%types_known_working))),
);
my $created = timelocal(strptime($blob->{'created'}));
......
......@@ -38,12 +38,12 @@ use Data::Dumper;
#
sub usage()
{
print "Usage: postimagedata [-f] [-n] [-d] [-a] all [datasets] | [imageid ...]\n";
print "Usage: postimagedata [-u] [-n] [-d] [-a] all [datasets] | [imageid ...]\n";
exit(1);
}
my $optlist = "fndva";
my $optlist = "undva";
my $impotent = 0;
my $force = 0;
my $update = 0;
my $debug = 0;
my $verbose = 0;
my $errors = 0;
......@@ -115,8 +115,8 @@ my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"f"})) {
$force = 1;
if (defined($options{"u"})) {
$update = 1;
}
if (defined($options{"a"})) {
$allvers = 1;
......@@ -197,9 +197,9 @@ if (@ARGV) {
}
}