Commit 57089d52 authored by David Johnson's avatar David Johnson

Docker clientside update: use our perl API lib instead of cli and cURL.

(Of course, there is no standard perl Docker Engine API, so I wrote my
own thin perl library, basically error handling and JSON marshalling
goo.  But had to deal with LWP/http over UNIX socket to Docker, which
is whacky; fortunately, there was a module, but I can confirm that LWP
is a pain when you get off the beaten track.  Anyway, I added a little
client that is useful for debugging.)

Also, I refactored things a bit so there is now an emulabize-image
script that can be run to test emulabization of docker images
separately from vnode creation.

Also, add a specific Docker version of the prepare script.  This
actually gets run in image commit as an ONBUILD instruction.

Also, always copy the image's master passwd files into /etc/emulab
instead of using those installed by client-install.  Those just don't
apply at all here since we have no idea what the base image is; the
uids/gids in the clientside dirs could be a complete mismatch for the
image.
parent 07c50ff1
......@@ -469,7 +469,10 @@ 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/dockerclient.pm $(BINDIR)/
$(INSTALL) -m 755 $(SRCDIR)/docker/dockerclient-cli $(BINDIR)/
$(INSTALL) -m 755 $(SRCDIR)/docker/create-docker-image $(LBINDIR)/
$(INSTALL) -m 755 $(SRCDIR)/docker/emulabize-image $(LBINDIR)/
$(INSTALL) -m 755 $(SRCDIR)/vnodectl $(BINDIR)/
echo "docker" > $(ETCDIR)/genvmtype
$(INSTALL) -m 755 -o root -g $(DIRGROUP) -d $(ETCDIR)/docker
......@@ -483,3 +486,4 @@ docker-install: dir-install
# $(INSTALL) -m 755 $(SRCDIR)/docker/analyze-image.sh $(ETCDIR)/docker/
docker-guest-install:
$(INSTALL) -m 755 $(SRCDIR)/docker/prepare $(BINDIR)/prepare
......@@ -37,11 +37,11 @@ my $VNODESETUP = "$BINDIR/vnodesetup";
sub usage()
{
print STDOUT "" .
"Usage: create-docker-image -R <registry> -r <repository> -t <tag>" .
"Usage: create-docker-image [-d <level>] [-c] -R <registry> -r <repository> -t <tag>" .
" <vnodeid>\n";
exit(-1);
}
my $optlist = "R:r:t:u:p:";
my $optlist = "cd:R:r:t:u:p:";
my $filename;
if ($UID != 0) {
......@@ -59,12 +59,15 @@ use libvnode_docker;
use libgenvnode;
#use libvnode;
use libutil;
use dockerclient;
#
# No configure vars.
#
my $vnodeid;
my ($registry,$repo,$tag,$user,$pass);
my $usecli = 0;
my $debug = 0;
#
# Parse command arguments. Once we return from getopts, all that should be
......@@ -104,18 +107,36 @@ if (defined($options{"p"})) {
else {
die("No pass (-p) supplied!");
}
if (defined($options{"c"})) {
$usecli = 1;
}
if (defined($options{"d"})) {
$debug = $options{"d"};
}
$vnodeid = shift(@ARGV);
if (!defined($vnodeid) || $vnodeid eq '') {
print STDERR "ERROR: no vnodeid specified!\n";
usage();
}
my $client;
if (!$usecli) {
$client = dockerclient->new();
$client->debug($debug);
}
#
# First, try to login to the registry with user/pass:
# First, try to login to the registry with user/pass.
#
my $i = 10;
while ($i > 0) {
system("docker login -p '$pass' -u '$user' $registry");
if ($usecli) {
system("docker login -p '$pass' -u '$user' $registry");
}
else {
my ($code) = $client->registry_auth($registry,$user,$pass);
$? = $code;
}
last
if ($? == 0);
print STDERR "ERROR: failed to login to registry $registry; sleeping and trying again...\n";
......@@ -132,7 +153,8 @@ 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");
system("docker logout $registry")
if ($usecli);
exit(1);
}
......@@ -141,13 +163,19 @@ if ($status eq VNODE_STATUS_UNKNOWN()) {
#
$i = 10;
while ($i > 0 && $status ne VNODE_STATUS_STOPPED()) {
system("docker stop $vnodeid");
if ($usecli) {
system("docker stop $vnodeid");
}
else {
$client->container_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");
system("docker logout $registry")
if ($usecli);
exit(1);
}
......@@ -155,15 +183,29 @@ if ($status ne VNODE_STATUS_STOPPED()) {
# (Locally) commit the image.
#
my $fullimagename = "$registry/$repo:$tag";
system("docker commit $vnodeid $fullimagename");
if ($usecli) {
system("docker commit $vnodeid $fullimagename");
}
else {
my ($code,$content,$resp) = $client->container_commit(
$vnodeid,$fullimagename,$user,$pass);
$? = $code;
}
if ($?) {
print STDERR "ERROR: failed to commit image $fullimagename for container $vnodeid; aborting!\n";
system("docker logout $registry");
system("docker logout $registry")
if ($usecli);
exit(1);
}
if ($initstatus ne VNODE_STATUS_STOPPED()) {
system("docker start $vnodeid");
if ($usecli) {
system("docker start $vnodeid");
}
else {
my ($code,$content,$resp) = $client->container_start($vnodeid);
$? = code;
}
if ($?) {
print STDERR "WARNING: error restarting container $vnodeid; ignoring!\n";
}
......@@ -176,7 +218,13 @@ else {
$i = 10;
my $success = 0;
while ($i > 0) {
system("docker push $fullimagename");
if ($usecli) {
system("docker push $fullimagename");
}
else {
my ($code,$content,$resp) = $client->image_push($fullimagename);
$? = $code;
}
if ($? == 0) {
$success = 1;
last;
......@@ -186,7 +234,8 @@ while ($i > 0) {
$i -= 1;
}
system("docker logout $registry");
system("docker logout $registry")
if ($usecli);
if (!$success) {
exit(1);
......
#!/usr/bin/perl
BEGIN {
if (-e "./dockerclient.pm") {
use lib ".";
}
if (-e "/etc/emulab") {
require "/etc/emulab/paths.pm";
import emulabpaths;
}
}
use strict;
use warnings;
use Data::Dumper;
use English;
use Getopt::Std;
use JSON::PP;
use dockerclient;
# Turn off line buffering on output
$| = 1;
sub usage(;$$) {
my ($msg,$method) = @_;
if ($msg) {
print STDERR "Error: $msg\n\n";
}
print STDERR "Usage: dockerclient-cli [-d] [-c] [-s sockpath] <method> [<args>,...]\n";
print STDERR " -d <level>\tSpecify the debug output level.\n";
print STDERR " -c\tPrint JSON in a compact format instead of pretty.\n";
print STDERR " -s <sockpath>\tThe path to the Docker UNIX socket (default: /var/run/docker.sock)\n";
print STDERR "\nNB: some commands take JSONish arguments (i.e. container_create::args); for those, please supply a perlish hashref/arrayref expression that starts with '{' or '[', and this client will eval it to obtain the argument.\n";
print STDERR "\n";
if (!$method) {
print STDERR "Available methods:\n";
my @methods = keys(%dockerclient::METHODS);
foreach my $method (sort(@methods)) {
my $helpstr = "";
if (exists($dockerclient::METHODS{$method}{'help'})) {
$helpstr = "\n\t".$dockerclient::METHODS{$method}{'help'};
}
print STDERR " $method$helpstr\n";
}
}
else {
print STDERR "Help for method '$method'\n";
my $helpstr = "";
if (exists($dockerclient::METHODS{$method}{'help'})) {
$helpstr = "\n ".$dockerclient::METHODS{$method}{'help'}."\n";
}
print STDERR "\t$helpstr\n";
if (exists($dockerclient::METHODS{$method}{'required'})
&& @{$dockerclient::METHODS{$method}{'required'}} > 0) {
print STDERR " Required parameters:\n";
for my $param (@{$dockerclient::METHODS{$method}{'required'}}) {
my $phelpstr = "";
if (exists($dockerclient::METHODS{$method}{'phelp'})
&& exists($dockerclient::METHODS{$method}{'phelp'}{$param})) {
$phelpstr = "\t".$dockerclient::METHODS{$method}{'phelp'}{$param};
}
print STDERR " $param$phelpstr\n";
}
}
if (exists($dockerclient::METHODS{$method}{'optional'})
&& @{$dockerclient::METHODS{$method}{'optional'}} > 0) {
print STDERR " Optional parameters:\n";
for my $param (@{$dockerclient::METHODS{$method}{'optional'}}) {
my $phelpstr = "";
if (exists($dockerclient::METHODS{$method}{'phelp'})
&& exists($dockerclient::METHODS{$method}{'phelp'}{$param})) {
$phelpstr = "\t".$dockerclient::METHODS{$method}{'phelp'}{$param};
}
print STDERR " $param$phelpstr\n";
}
}
}
exit(-1);
}
my $sockpath;
my $debug = 0;
my $compact = 0;
my $optlist = "d:hcs:";
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
$debug = $options{"d"}
if (defined($options{"d"}));
$sockpath = $options{"s"}
if (defined($options{"s"}));
$compact = 1
if (defined($options{"c"}));
usage()
if (defined($options{"h"}));
if (!@ARGV) {
usage("must specify a method to invoke!");
}
my $method = shift(@ARGV);
if (!grep(/^$method$/,keys(%dockerclient::METHODS))) {
usage("unknown method '$method'!");
}
if (exists($dockerclient::METHODS{$method}{'required'})
|| exists($dockerclient::METHODS{$method}{'required'})) {
my $reqpcount = 0;
my $optpcount = 0;
if (exists($dockerclient::METHODS{$method}{'required'})) {
$reqpcount = @{$dockerclient::METHODS{$method}{'required'}}
}
if (exists($dockerclient::METHODS{$method}{'optional'})) {
$optpcount = @{$dockerclient::METHODS{$method}{'optional'}}
}
if (@ARGV < $reqpcount) {
usage("insufficient parameters to method '$method'",$method);
}
if (@ARGV > ($reqpcount + $optpcount)) {
usage("too many parameters to method '$method'",$method);
}
}
my $client = dockerclient->new($sockpath,$debug);
my ($code,$content,$resp);
my @args = ();
foreach my $arg (@ARGV) {
if ($arg =~ /^\{/ || $arg =~ /^\[/) {
$arg = eval "sub true { return JSON::PP::true; }; sub false { return JSON::PP::false; }; $arg";
if ($@) {
print STDERR "Error while eval'ing arg '$arg' -- must be a valid perl hashref/arrayref expression\n";
exit(1);
}
}
push(@args,$arg);
}
eval {
($code,$content,$resp) = $client->$method(@args);
};
if ($@) {
print STDERR "Error: during method '$method': $@\n";
exit(-2);
}
if (defined($content)) {
if (!defined(ref($content)) || ref($content) eq '') {
chomp($content);
}
elsif (ref($content) eq 'SCALAR') {
$content = $$content;
chomp($content);
}
else {
my $j = JSON::PP->new->utf8;
$j->allow_nonref(1);
$j->pretty(1)
if (!$compact);
$content = $j->encode($content);
}
}
if ($code) {
print STDERR "Error ($code): $content\n";
}
elsif (defined($content)) {
print STDOUT "$content\n";
}
exit($code);
This diff is collapsed.
--security-opt seccomp=unconfined --tmpfs /run --tmpfs /run/lock --stop-signal=SIGRTMIN+3 -v /sys/fs/cgroup:/sys/fs/cgroup:ro
{
"StopSignal" : "SIGRTMIN+3",
"HostConfig" : {
"SecurityOpt" : [ "seccomp=unconfined" ],
"Binds" : [ "/sys/fs/cgroup:/sys/fs/cgroup:ro" ],
"Tmpfs" : {
"/run" : "",
"/run/lock":""
}
}
}
/sbin/init-systemd.sh
{
"Cmd": [ "/sbin/init-systemd.sh" ]
}
--security-opt seccomp=unconfined --tmpfs /run --tmpfs /run/lock --stop-signal=SIGRTMIN+3 -v /sys/fs/cgroup:/sys/fs/cgroup:ro
{
"StopSignal" : "SIGRTMIN+3",
"HostConfig" : {
"SecurityOpt" : [ "seccomp=unconfined" ],
"Binds" : [ "/sys/fs/cgroup:/sys/fs/cgroup:ro" ],
"Tmpfs" : {
"/run" : "",
"/run/lock":""
}
}
}
/sbin/init-systemd.sh
{
"Cmd": [ "/sbin/init-systemd.sh" ]
}
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Std;
use English;
use POSIX;
use Data::Dumper;
#
# A simple CLI wrapper around libvnode_docker::emulabizeImage; should
# only be used for testing or image import.
#
sub usage()
{
print "Usage: emulabize-image [-d <level>] [-e <emulabizationlevel>]\n".
" [-P pullpolicy] [-u user] [-p pass] image [newimagename]\n".
"\n".
" -d <level> Debug mode\n" .
" -e <level> Emulabization level (none,basic,core,full,buildenv)\n".
" -P <policy> Pull policy for base image (should we update to the\n".
" latest, or use a locally-cached version). Valid\n".
" values: latest, cached\n".
" -f Update the new image even if it already exists,\n".
" or if the base image has a new version\n".
" -u <user> A username to use to pull the base image\n".
" -p <pass> A password to use to pull the base image\n".
" -h Show this usage message.\n".
"\n".
"<image> is the base image name; it will be pulled if it does not\n".
"already exist. The optional <newimagename> argument will be used\n".
"as the new image name if it is specified; otherwise one will be\n".
"created for you. So, if you specify 'ubuntu:16.04' as the base,\n".
"the new image name would be 'ubuntu-16.04:emulab-core' if you\n".
"didn't specify <newimagename>.\n";
exit(1);
}
my $optlist = "hfd:e:P:u:p:";
my $debug = 1;
my $emulabization;
my $pullpolicy;
my $update = 0;
my $user;
my $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{"h"})) {
usage();
}
if (defined($options{"f"})) {
$update = 1;
}
if (defined($options{"d"})) {
$debug = $options{"d"};
}
if (defined($options{"e"})) {
$emulabization = $options{"e"};
}
if (defined($options{"P"})) {
$pullpolicy = $options{"P"};
}
if (defined($options{"u"})) {
$user = $options{"u"};
}
if (defined($options{"p"})) {
$pass = $options{"p"};
}
usage()
if (@ARGV < 1 || @ARGV > 2);
my ($image,$newimageref) = @ARGV;
#
# Must be root.
#
if ($UID != 0) {
die("*** $0:\n".
" Must be root to run this script!\n");
}
#
# Turn off line buffering on output
#
$| = 1;
# Drag in path stuff so we can find emulab stuff.
BEGIN { require "/etc/emulab/paths.pm"; import emulabpaths; }
use libsetup;
use libvnode_docker;
TBDebugTimeStampsOn();
libvnode_docker::setDebug($debug);
my $rc = libvnode_docker::emulabizeImage(
$image,\$newimageref,$emulabization,$update,$pullpolicy,$user,$pass);
if ($rc) {
fatal("ERROR: failed to emulabize $image!\n");
}
else {
print "Successfully emulabized $image: new image $newimageref.\n";
}
exit(0);
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment