Commit 1017ccce authored by Mike Hibler's avatar Mike Hibler

Implement limited backward compatibility with the old frisbee setup.

The big backward compatibility issue is that we no longer store running
frisbeed info in the DB.  This means that loadinfo could not return
address:port info to clients and thus old frisbee MFSes could no longer
work.  While not a show stopper to require people to update their MFS first,
I made a token effort to implement backward compat as follows.

When an old frisbee MFS does "tmcc loadinfo" (as identified by a tmcd
version < 33), tmcd will invoke "frisbeehelper" to startup a daemon.
Sound like frisbeelauncher?  Well sorta, but vastly simplified and I only
want this to be temporary.  The helper just uses the frisbee client to make
a "proxy" request to the localhost master server.  The Emulab configuration
of the master server now allows requests from localhost to proxy for another
node.

frisbeehelper is also used by webfrisbeekiller to kill a running daemon
(yes, just like frisbeelauncher).  It makes a proxy status request on
localhost and uses the returned info to identify the particular instance
and kill it.
parent 7219d8ae
......@@ -135,6 +135,8 @@ mfrisbeed: $(MSERVEROBJS)
cp mfrisbeed mfrisbeed.debug
strip mfrisbeed
mserver.o: $(SRCDIR)/mserver.c decls.h configdefs.h utils.h
$(CC) -c $(MSERVERFLAGS) $(SRCDIR)/mserver.c
config.o: $(SRCDIR)/config.c configdefs.h log.h
$(CC) -c $(MSERVERFLAGS) $(SRCDIR)/config.c
config_emulab.o: $(SRCDIR)/config_emulab.c configdefs.h log.h
......
/*
* EMULAB-COPYRIGHT
* Copyright (c) 2000-2010 University of Utah and the Flux Group.
* Copyright (c) 2000-2011 University of Utah and the Flux Group.
* All rights reserved.
*/
......@@ -65,6 +65,7 @@ struct in_addr mcastif;
char *imageid;
int askonly;
int busywait = 0;
char *proxyfor = NULL;
static struct timeval stamp;
static struct in_addr serverip;
......@@ -208,7 +209,7 @@ main(int argc, char **argv)
int dostype = -1;
int slice = 0;
while ((ch = getopt(argc, argv, "dqhp:m:s:i:tbznT:r:E:D:C:W:S:M:R:I:ONK:B:F:Q:")) != -1)
while ((ch = getopt(argc, argv, "dqhp:m:s:i:tbznT:r:E:D:C:W:S:M:R:I:ONK:B:F:Q:P:")) != -1)
switch(ch) {
case 'd':
debug++;
......@@ -271,6 +272,9 @@ main(int argc, char **argv)
imageid = optarg;
askonly = 1;
break;
case 'P':
proxyfor = optarg;
break;
#endif
case 't':
......@@ -370,11 +374,21 @@ main(int argc, char **argv)
GetReply reply;
int method = askonly ? MS_METHOD_ANY : MS_METHOD_MULTICAST;
int timo = 5; /* XXX */
int host = 0;
if (proxyfor) {
struct in_addr in;
if (!GetIP(proxyfor, &in))
fatal("Could not resolve host '%s'\n",
proxyfor);
host = ntohl(in.s_addr);
}
while (1) {
if (!ClientNetFindServer(ntohl(serverip.s_addr),
portnum, 0, imageid, method,
askonly, timo, &reply, NULL))
portnum, host, imageid,
method, askonly, timo,
&reply, NULL))
fatal("Could not get download info for '%s'",
imageid);
......@@ -382,7 +396,6 @@ main(int argc, char **argv)
PrintGetInfo(imageid, &reply, 1);
exit(0);
}
if (reply.error) {
if (busywait == 0 ||
reply.error != MS_ERROR_TRYAGAIN)
......@@ -404,6 +417,16 @@ main(int argc, char **argv)
break;
}
}
/*
* XXX if proxying for another node, assume that we are only
* interested in starting up the frisbeed and don't care about
* the image ourselves. So, our work is done!
*/
if (proxyfor) {
log("server started on behalf of %s", proxyfor);
exit(0);
}
#endif
ClientNetInit();
......
......@@ -565,6 +565,16 @@ emulab_get_host_authinfo(struct in_addr *req, struct in_addr *host,
* the requester is listed as one of those.
*/
if (req->s_addr != host->s_addr) {
/*
* XXX if node_id is localhost (i.e., boss), then allow
* proxying for any node.
*/
if (req->s_addr == htonl(INADDR_LOOPBACK)) {
role = "boss";
proxy = mystrdup(role);
goto isboss;
}
proxy = emulab_nodeid(req);
if (proxy == NULL) {
emulab_free_host_authinfo(get);
......@@ -609,6 +619,7 @@ emulab_get_host_authinfo(struct in_addr *req, struct in_addr *host,
} else
proxy = NULL;
isboss:
/*
* Find the node name from its control net IP.
* If the node doesn't exist, we return an empty list.
......@@ -626,39 +637,46 @@ emulab_get_host_authinfo(struct in_addr *req, struct in_addr *host,
put->hostid = mystrdup(node);
/*
* We have a proxy node. It should be either:
* - a subboss and a frisbee subboss for the node
* - an inner-boss and in the same experiment as the node
* We have a proxy node. It should be one of:
* - boss (for any node)
* - a subboss (that is a frisbee subboss for the node)
* - an inner-boss (in the same experiment as the node)
* Note that we could not do this check until we had the node name.
* Note also that we no longer care about proxy or not after this.
*/
if (proxy) {
if (strcmp(role, "subboss") == 0)
if (strcmp(role, "boss") == 0) {
res = NULL;
} else if (strcmp(role, "subboss") == 0) {
res = mydb_query("SELECT node_id"
" FROM subbosses"
" WHERE subboss_id='%s'"
" AND node_id='%s'"
" AND service='frisbee'",
1, proxy, node);
else
assert(res != NULL);
} else {
res = mydb_query("SELECT r1.node_id"
" FROM reserved as r1,reserved as r2"
" FROM reserved as r1,"
" reserved as r2"
" WHERE r1.node_id='%s'"
" AND r2.node_id='%s'"
" AND r1.pid=r2.pid"
" AND r1.eid=r2.eid",
1, proxy, node);
assert(res != NULL);
if (mysql_num_rows(res) == 0) {
assert(res != NULL);
}
if (res) {
if (mysql_num_rows(res) == 0) {
mysql_free_result(res);
free(proxy);
free(node);
emulab_free_host_authinfo(get);
emulab_free_host_authinfo(put);
return 1;
}
mysql_free_result(res);
free(proxy);
free(node);
emulab_free_host_authinfo(get);
emulab_free_host_authinfo(put);
return 1;
}
mysql_free_result(res);
free(proxy);
}
......
......@@ -167,6 +167,7 @@ struct childinfo {
int ptype;
int method;
int pid;
char *pidfile;
int retries;
in_addr_t servaddr; /* -S arg */
in_addr_t ifaceaddr; /* -i arg */
......@@ -187,7 +188,7 @@ struct clientextra {
uint32_t losize;
};
static struct childinfo *findchild(struct config_imageinfo *, int, int);
static struct childinfo *findchild(char *, int, int);
static int startchild(struct childinfo *);
static struct childinfo *startserver(struct config_imageinfo *,
in_addr_t, in_addr_t, int, int *);
......@@ -280,7 +281,7 @@ fetch_parent(struct in_addr *myip, struct in_addr *hostip,
* If so we will either return "try again later" or point them to
* our parent.
*/
ci = findchild(ii, PTYPE_CLIENT, methods);
ci = findchild(ii->imageid, PTYPE_CLIENT, methods);
if (ci != NULL) {
if (debug)
info("%s: fetch from %s in progress",
......@@ -520,6 +521,34 @@ handle_get(int sock, struct sockaddr_in *sip, struct sockaddr_in *cip,
}
}
#ifdef USE_EMULAB_CONFIG
/*
* XXX Emulab special case if boss is making a non-proxied
* status request for an image that is running.
*
* We allow boss access to any image that has a daemon
* currently running. This is a hack to allow boss to get info
* for a running daemon so it can kill it. I do it here because
* it is easiest (access to the running process info).
*
* This should move to the Emulab config module and
* perhaps boss should be able to access ANY valid image
* or image directory, but I don't want to go there right
* now...
*/
if (wantstatus &&
cip->sin_addr.s_addr == htonl(INADDR_LOOPBACK) &&
host.s_addr == htonl(INADDR_LOOPBACK) &&
(ci = findchild(imageid, PTYPE_SERVER, methods)) != NULL) {
/* XXX only fill in the info boss cares about */
msg->body.getreply.method = ci->method;
msg->body.getreply.isrunning = 1;
msg->body.getreply.addr = htonl(ci->addr);
msg->body.getreply.port = htons(ci->port);
goto reply;
}
#endif
/*
* See if node has access to the image.
* If not, return an error code immediately.
......@@ -751,7 +780,7 @@ handle_get(int sock, struct sockaddr_in *sip, struct sockaddr_in *cip,
* Otherwise see if there is a frisbeed already running, starting
* one if not. Then construct a reply with the available info.
*/
ci = findchild(ii, PTYPE_SERVER, methods);
ci = findchild(ii->imageid, PTYPE_SERVER, methods);
/*
* XXX right now frisbeed doesn't support mutiple clients
......@@ -1091,7 +1120,7 @@ static struct childinfo *children;
static int nchildren;
static struct childinfo *
findchild(struct config_imageinfo *ii, int ptype, int methods)
findchild(char *imageid, int ptype, int methods)
{
struct childinfo *ci, *bestci;
assert((methods & ~onlymethods) == 0);
......@@ -1099,7 +1128,7 @@ findchild(struct config_imageinfo *ii, int ptype, int methods)
bestci = NULL;
for (ci = children; ci != NULL; ci = ci->next)
if (ci->ptype == ptype && (ci->method & methods) != 0 &&
!strcmp(ci->imageinfo->imageid, ii->imageid)) {
!strcmp(ci->imageinfo->imageid, imageid)) {
if (bestci == NULL)
bestci = ci;
else if (ci->method == MS_METHOD_BROADCAST)
......@@ -1109,7 +1138,7 @@ findchild(struct config_imageinfo *ii, int ptype, int methods)
bestci = ci;
else
pfatal("multiple unicast servers for %s",
ii->imageid);
imageid);
}
return bestci;
......@@ -1181,6 +1210,22 @@ startchild(struct childinfo *ci)
exit(-1);
}
/* create a pid file */
if (ci->ptype == PTYPE_SERVER) {
char pidfile[128];
struct in_addr in;
FILE *fd;
in.s_addr = htonl(ci->addr);
snprintf(pidfile, sizeof pidfile, "%s/frisbeed-%s-%d.pid",
_PATH_VARRUN, inet_ntoa(in), ci->port);
fd = fopen(pidfile, "w");
if (fd != NULL) {
fprintf(fd, "%d\n", ci->pid);
fclose(fd);
ci->pidfile = strdup(pidfile);
}
}
ci->next = children;
children = ci;
nchildren++;
......@@ -1194,7 +1239,7 @@ startserver(struct config_imageinfo *ii, in_addr_t meaddr, in_addr_t youaddr,
{
struct childinfo *ci;
assert(findchild(ii, PTYPE_SERVER, methods) == NULL);
assert(findchild(ii->imageid, PTYPE_SERVER, methods) == NULL);
assert(errorp != NULL);
ci = calloc(1, sizeof(struct childinfo));
......@@ -1297,7 +1342,7 @@ startclient(struct config_imageinfo *ii, in_addr_t meaddr, in_addr_t youaddr,
char *tmpname;
int len;
assert(findchild(ii, PTYPE_CLIENT, methods) == NULL);
assert(findchild(ii->imageid, PTYPE_CLIENT, methods) == NULL);
assert(errorp != NULL);
ci = calloc(1, sizeof(struct childinfo));
......@@ -1388,6 +1433,11 @@ reapchildren(int wpid, int *statusp)
continue;
}
*cip = ci->next;
if (ci->pidfile) {
unlink(ci->pidfile);
free(ci->pidfile);
ci->pidfile = NULL;
}
nchildren--;
in.s_addr = htonl(ci->addr);
log("%s: %s process %d on %s:%d exited (status=0x%x)",
......
......@@ -42,6 +42,7 @@ stop)
kill `cat /var/run/mfrisbeed.pid` >/dev/null 2>&1
rm -f /var/run/mfrisbeed.pid
fi
rm -f /var/run/frisbeed-*.pid
exit 0
;;
*)
......@@ -51,6 +52,7 @@ stop)
esac
echo -n " mfrisbeed"
rm -f /var/run/frisbeed-*.pid
args="-C emulab"
# if an inner elab, use outer boss as our parent
if [ -n "@OUTERBOSS_NODENAME@" ]; then
......
......@@ -35,7 +35,7 @@ BIN_STUFF = power snmpit tbend tbprerun tbreport \
SBIN_STUFF = resetvlans console_setup.proxy sched_reload named_setup \
batch_daemon exports_setup reload_daemon sched_reserve \
console_reset db2ns bwconfig \
console_reset db2ns bwconfig frisbeehelper \
rmgroup mkgroup setgroups mkproj modgroups \
exports_setup.proxy vnode_setup eventsys_start \
sfskey_update sfskey_update.proxy rmuser idleswap \
......@@ -92,7 +92,7 @@ LIB_STUFF = libtbsetup.pm exitonwarn.pm libtestbed.pm snmpit_intel.pm \
# These scripts installed setuid, with sudo.
SETUID_BIN_SCRIPTS = node_reboot eventsys_control tarfiles_setup savelogs \
tbrsync
SETUID_SBIN_SCRIPTS = mkproj rmgroup mkgroup \
SETUID_SBIN_SCRIPTS = mkproj rmgroup mkgroup frisbeehelper \
rmuser idleswap named_setup exports_setup \
sfskey_update setgroups newnode_reboot vnode_setup \
elabinelab nfstrace rmproj
......@@ -231,6 +231,8 @@ endif
chmod u+s $(INSTALL_SBINDIR)/rmgroup
chown root $(INSTALL_SBINDIR)/mkgroup
chmod u+s $(INSTALL_SBINDIR)/mkgroup
chown root $(INSTALL_SBINDIR)/frisbeehelper
chmod u+s $(INSTALL_SBINDIR)/frisbeehelper
chown root $(INSTALL_SBINDIR)/rmuser
chmod u+s $(INSTALL_SBINDIR)/rmuser
chown root $(INSTALL_SBINDIR)/idleswap
......
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2011 University of Utah and the Flux Group.
# All rights reserved.
#
use strict;
use Getopt::Std;
use Sys::Syslog;
use English;
use Socket;
use Errno;
#
# This is a compatibility shim for the pre-mfrisbeed frisbee world.
#
# It can be used to start or kill a frisbeed via the master server.
#
#
# This also kills a running frisbee.
#
sub usage()
{
print "Usage: $0 [-d] -n nodeid <imageid>\n";
print " or\n";
print "Usage: $0 [-d] -k <imageid>\n";
print "-n nodeid Node used for authentication of image access\n";
print "-k: Kill (rather than start) a frisbeed.\n";
print "-d: Print debugging output.\n";
exit(1);
}
my $optlist = "dkn:";
my $debug = 0;
my $killmode = 0;
my $nodeid;
# Configure variables
my $TB = "@prefix@";
#
# Untaint the path
#
$ENV{'PATH'} = "/bin:/usr/bin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
#
# Turn off line buffering on output
#
$| = 1;
use lib "@prefix@/lib";
use libdb;
use libtestbed;
use libtblog;
use Node;
use User;
use Image;
# Protos.
sub Fatal($);
sub debug($);
# Defines
my $FRISBEE = "$TB/sbin/frisbee";
my $PIDDIR = "/var/run";
#
# 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{"d"})) {
$debug = 1;
}
if (defined($options{"k"})) {
$killmode = 1;
}
if (defined($options{"n"})) {
$nodeid = $options{"n"};
if ($nodeid =~ /^([-\w]+)$/) {
$nodeid = $1;
} else {
Fatal("invalid nodeid");
}
}
usage()
if (!@ARGV || (!$killmode && !$nodeid));
my $imagename = $ARGV[0];
if ($imagename =~ /^([-\w\.+\/]+)$/) {
$imagename = $1;
} else {
Fatal("invalid imagename");
}
#
# Authentication.
#
# Note that an imageid can be a traditional disk image or the pathname
# of a file, the master frisbee server worries about that distinction.
#
# For starting an image server, we provide a node_id which the master
# server can authenticate with. We would also like to make sure the calling
# user has permission to manipulate the node given, but unfortunately we
# are called from tmcd which runs as "nobody". So since this is a temporary
# shim, and we only run on boss where access is limited to authorized users
# or through constrained interfaces, we allow any user to start up a
# daemon for an image.
#
# For killing a server, we don't always have a node context. In the
# case where we don't, it will be a true image and we can perform an access
# check with respect to the image.
#
# So, the strategy is:
#
# * If we have a node context, and it is a regular Emulab image,
# we let the master server worry about whether the node can access
# the image. This is the "relaxed" case for tmcd.
#
# * If we have a node context, but it is not an Emulab image,
# we check that the user can modify the node, and let the master server
# worry about the node/user's ability to access the file.
# We do the user check here because tmcd will never call us to check
# on a non-image file.
#
# * If there is no node context, it can only be a "kill" and we check
# the user's ability to modify the image (if it isn't an image, this
# check will fail).
#
my ($this_user, $isadmin, $image);
if ($UID) {
$this_user = User->ThisUser();
if (!defined($this_user)) {
# XXX allow unknown user to start a server for a specified node
if (!$nodeid || $killmode) {
Fatal("$UID: not a valid Emulab user");
}
$isadmin = 1;
} else {
$isadmin = $this_user->IsAdmin();
}
} else {
$isadmin = 1;
}
$image = Image->Lookup($imagename);
if (!$image) {
# If not an Emulab image, we must have a node context to check
if (!$nodeid) {
Fatal("$imagename: not a valid Emulab image");
}
# We must also have a valid user
if (!$this_user) {
Fatal("$UID: not a valid Emulab user");
}
}
if ($nodeid) {
my $node = Node->Lookup($nodeid);
if (!$node) {
Fatal("$nodeid: no such node");
}
if (!$isadmin &&
!$node->AccessCheck($this_user, TB_NODEACCESS_LOADIMAGE)) {
Fatal("$nodeid: cannot access node");
}
} else {
# must be a kill operation; we checked this above
# must be a real image; ditto
# only called this way when deleting an image, so check access accordingly
if (!$isadmin &&
!$image->AccessCheck($this_user, TB_IMAGEID_DESTROY)) {
Fatal("$imagename: no permission to destroy");
}
}
#
# If it is a real image, we have to construct the proper master server
# imageid for it (pid/imagename), otherwise the given name is treated
# as a filename path.
#
my $imageid;
if ($image) {
$imageid = $image->pid() . "/" . $image->imagename();
} else {
$imageid = $imagename;
}
#
# Now we use the frisbee client to contact the master server
# and either request starting of a server or to get back info that
# we can use to kill the server instance.
#
my $cmd = "$FRISBEE -S localhost";
if ($nodeid) {
$cmd .= " -P $nodeid";
}
if ($killmode) {
$cmd .= " -Q $imageid";
} else {
$cmd .= " -F $imageid /dev/null";
}
debug("Invoking: $cmd\n");
#
# Parse the query/get output to get the address/port to either report
# to the user on startup or to use to form a pid file name that we can
# use to kill a running server.
#
my ($addr,$port,$running);
if (!open(FRIS, "$cmd 2>&1 |")) {
Fatal("frisbee invocation failed!");
}
while (<FRIS>) {
if (/^error=(.*)$/) {
if ($1 != 0) {
Fatal("frisbee returned error=$1");
}
next;
}
if (/^running=(\d)$/) {
$running = $1;
last
if ($running == 0);
next;
}
if (/^addr=([\d.]+)$/) {
$addr = $1;
next;
}
if (/^port=(\d+)$/) {
$port = $1;
next;
}
if (/: address: ([\d.]+):(\d+)/) {
$running = 1;
$addr = $1;
$port = $2;
last;
}
}
close(FRIS);
if ($running && (!$addr || !$port)) {
Fatal("$imageid: could not determine address/port for server");
}
#
# Starting: just report information
#
if (!$killmode) {
if (!$running) {
Fatal("$imageid: could not start server");
}
print "Address is $addr:$port\n";
exit(0);
}
#
# Killing: kill daemon if running
#
if ($running) {
my $pidfile = "$PIDDIR/frisbeed-$addr-$port.pid";
if (! -r "$pidfile") {
debug("no pidfile '$pidfile' found\n");
} else {
my $pid = `cat $pidfile`;
if ($?) {
debug("could not open '$pidfile'\n");
} else {
if ($pid =~ /^(\d+)/) {
$pid = $1;
if (!kill('TERM', $pid)) {
debug("could not kill $imageid process $pid\n");
} else {
print "Killed process $pid\n";
}
} else {
debug("bogus pid in $pidfile\n");
}
}
}