Commit c61858c7 authored by Timothy Stack's avatar Timothy Stack

Make the hurting stop.  Make sshxmlrpc auto-detect things, fails over
properly, and dump useful information when it is unable to deal with
the peer.

  * xmlrpc/sshxmlrpc.py: Major update.  It now tries to autoconfigure
    itself by scanning the path for "ssh" and "plink.exe" (although I
    haven't actually tried it on windows).  Environment variables can
    now be used to turn on debugging and set the command to use for
    doing the ssh.  Before running ssh, it will check for an agent or
    a passphrase-less key and prints a warning if it finds neither.
    The last five lines read from the server, as well as the standard
    error output, are stored so they can be dumped later; helpful for
    figuring out what is actually being run on the other side.  The
    protocol layer between ssh and xml-rpc will now respond to a
    "probe" header so that clients can figure out who they are talking
    too.  The server side will now properly detect a closed connection
    and not write anything, which means no more annoying "Write to
    stdout failed" messages.  You can now pass additional options to
    ssh and set the identity.  The module can be run standalone, with
    the default action being to probe the peer:

      $ ./sshxmlrpc.py ssh://boss/xmlrpc
      Probe results for: ssh://boss/xmlrpc
        response time=1.49 s
      Response Headers
        date: Wed Oct 27 16:10:58 2004
	content-length: 0
	probe: /usr/testbed/devel/stack/lib/sshxmlrpc.py
	probe-response: EmulabServer

  * xmlrpc/sshxmlrpc_server.py.in: Set the value returned by a "probe"
    to the name of the invoked module.  This way, the other side can
    figure out who they are talking to (e.g. EmulabServer
    vs. experiment vs. fs vs. osid).

  * event/sched/event-sched.c, event/sched/rpc.cc, event/sched/rpc.h,
    xmlrpc/script_wrapper.py.in: Multiple paths (e.g. xmlrpc,
    $prefix/sbin/sshxmlrpc_server.py) are now probed before giving up.
    Force the use of the user's default identity and protocol one.
    For event-sched, a single connection is now made at startup and
    dropped before going into the event loop.

  * event/sched/GNUmakefile.in: Add a dependency for the install
    target and add -I$(OBJDIR) to the CXXFLAGS.

  * install/ports/ulsshxmlrpcpp/Makefile,
    install/ports/ulsshxmlrpcpp/distinfo,
    install/ports/ulsshxmlrpcpp/pkg-descr: Bump version number to 1.1
    and tweak the description.

  * config.h.in, configure, configure.in: Add a "#define TBROOT" that
    has the install prefix.
parent c285f12e
......@@ -27,7 +27,7 @@ DBLIBS = -L/usr/local/lib/mysql -lmysqlclient -lz
LIBS += -levent_r -ltb -lcipher -lz
ULXRINC = -I/usr/local/include -I/usr/local/include/ulxmlrpcpp
CXXFLAGS += -pthread -O $(ULXRINC)
CXXFLAGS += -pthread -O $(ULXRINC) -I$(OBJDIR)
ULXRLIBS = -L/usr/local/lib -lulsshxmlrpcpp -lulxmlrpcpp -lexpat
#
......@@ -62,7 +62,7 @@ rpc.o: rpc.cc rpc.h
rrpc.o: rpc.cc rpc.h
$(CXX) $(CXXFLAGS) -DSSLRPC $(ULXRINC) -c -o rrpc.o $<
install:
install: event-sched_rpc
-mkdir -p $(INSTALL_DIR)/opsdir/sbin
$(INSTALL_PROGRAM) event-sched_rpc $(INSTALL_DIR)/opsdir/sbin/event-sched
......
......@@ -154,7 +154,9 @@ main(int argc, char **argv)
if (!server)
server = "localhost";
#ifdef RPC
RPC_init(NULL, BOSSNODE, 0);
if (RPC_init(NULL, BOSSNODE, 0)) {
fatal("could not connect to rpc server");
}
#endif
snprintf(buf, sizeof(buf), "elvin://%s%s%s",
......@@ -193,6 +195,10 @@ main(int argc, char **argv)
fatal("could not get static event list");
}
#ifdef RPC
RPC_kill();
#endif
/* Dequeue events and process them at the appropriate times: */
dequeue(handle);
......
......@@ -3,9 +3,14 @@
* Copyright (c) 2004 University of Utah and the Flux Group.
* All rights reserved.
*/
#include "config.h"
#include "rpc.h"
#define ULXR_INCLUDE_SSL_STUFF
#include <limits.h>
#include <sys/types.h>
#include <pwd.h>
#include <ulxmlrpcpp.h> // always first header
#include <iostream>
......@@ -27,38 +32,111 @@
// This is just a stub that calls the realmain in event-sched.c
//
extern "C" int realmain(int argc, char **argv);
/**
* We cache the connection to the server until all of the RPCs have completed
* so we do not have to reconnect.
*/
static struct {
ulxr::Connection *conn; // The cached connection.
ulxr::Protocol *proto; // The cached protocol layer.
} rpc_data;
int
main(int argc, char **argv)
{
return realmain(argc, argv);
return realmain(argc, argv);
}
/*
* Simply save the stuff we need for making the connections later.
*/
static char *CERTPATH;
static char *PATH;
static char *HOST = "localhost";
static int PORT = 3069;
int RPC_init(char *certpath, char *host, int port)
{
printf("%s %s %d\n", certpath, host, port);
int retval = -1;
#ifdef SSHRPC
PATH = "xmlrpc";
{
char identity_path[PATH_MAX];
struct passwd *pwd;
/* Construct the path to the identity and */
pwd = getpwuid(getuid());
snprintf(identity_path,
sizeof(identity_path),
"%s/.ssh/identity",
pwd->pw_dir);
/* ... check to make sure it is passphrase-less. */
if (!ulxr::SSHConnection::has_passphraseless_login(identity_path)) {
/* XXX We should just automatically restore from backup here. */
fprintf(stderr, " *** ~/.ssh/identity is not a passphrase-less key\n");
fprintf(stderr, " You will need to regenerate the key manually\n");
return 1;
}
else {
static char *XMLRPC_PATHS[] = {
"xmlrpc",
TBROOT "/sbin/sshxmlrpc_server.py",
NULL
};
int lpc;
/*
* Probe each path looking for one that has the server on the other side.
*/
for (lpc = 0; XMLRPC_PATHS[lpc]; lpc++) {
try {
ulxr::RFC822Protocol *proto;
ulxr::CppString server_name;
rpc_data.conn = new ulxr::SSHConnection(
pwd->pw_name, host, XMLRPC_PATHS[lpc],
port ? port : ulxr::SSHConnection::DEFAULT_PORT,
ulxr::SSHConnection::DEFAULT_SSH_OPTS +
ULXR_PCHAR(" -1 -i ") +
ULXR_PCHAR(identity_path));
rpc_data.proto = proto = new ulxr::RFC822Protocol(rpc_data.conn);
server_name = proto->probe();
if (strcmp(server_name.c_str(), "EmulabServer") == 0) {
break;
}
else {
RPC_kill();
}
}
catch (ulxr::ConnectionException &ce) {
RPC_kill();
}
}
if (rpc_data.proto == NULL) {
/* Bah, could not connect to the server. */
return 1;
}
}
}
#else
if (certpath)
CERTPATH = strdup(certpath);
else
CERTPATH = "/usr/testbed/etc/client.pem";
/* XXX THIS IS OUT OF DATE */
{
ulxr::SSLConnection conn(false, host, port);
ulxr::HttpProtocol proto(&conn, conn.getHostName());
if (certpath == NULL)
certpath = "/usr/testbed/etc/client.pem";
conn.setCryptographyData("", certpath, certpath);
}
#endif
HOST = strdup(host);
PORT = port;
return 0;
}
void RPC_kill(void)
{
if (rpc_data.proto != NULL) {
delete rpc_data.proto;
rpc_data.proto = NULL;
delete rpc_data.conn;
rpc_data.conn = NULL;
}
}
/*
* Contact the server and invoke the method. All of the methods we care
* about using return a string, which we return to the caller after
......@@ -73,25 +151,21 @@ RPC_invoke(char *pid, char *eid, char *method, emulab::EmulabResponse *er)
try
{
#ifdef SSHRPC
const char *user = getenv("USER");
ulxr::SSHConnection conn(user, HOST, PATH);
ulxr::RFC822Protocol proto(&conn);
emulab::ServerProxy proxy(&proto);
emulab::ServerProxy proxy(rpc_data.proto);
#else
ulxr::SSLConnection conn(false, HOST, PORT);
ulxr::HttpProtocol proto(&conn, conn.getHostName());
conn.setCryptographyData("", CERTPATH, CERTPATH);
emulab::ServerProxy proxy(&proto, false, "/RPC2");
/* XXX THIS IS OUT OF DATE */
emulab::ServerProxy proxy(rpc_data.proto, false, "/RPC2");
#endif
*er = proxy.invoke(method,
emulab::SPA_String, "proj", pid,
emulab::SPA_String, "exp", eid,
emulab::SPA_TAG_DONE);
emulab::SPA_String, "proj", pid,
emulab::SPA_String, "exp", eid,
emulab::SPA_TAG_DONE);
if (! er->isSuccess()){
ULXR_CERR << "SSL_waitforactive failed: "
ULXR_CERR << "RPC_invoke failed: "
<< method
<< " "
<< er->getOutput()
<< std::endl;
return -1;
......
......@@ -23,6 +23,7 @@ typedef struct address_tuple * address_tuple_t;
#endif
extern CD int RPC_init(char *certpath, char *host, int port);
extern CD void RPC_kill(void);
extern CD int RPC_waitforactive(char *pid, char *eid);
extern CD int RPC_agentlist(char *pid, char *eid);
extern CD int RPC_grouplist(char *pid, char *eid);
......
......@@ -60,6 +60,11 @@ admin = 0
devel = 0
needhelp = 0
# The set of paths to try when connecting to the server.
XMLRPC_PATH = [ "xmlrpc", SERVER_PATH + "/sbin/sshxmlrpc_server.py", ]
# The ssh options that should be added to the default.
SSH_OPTS = { "-1" : "-1" }
API = {
"node_admin" : { "func" : "adminmode",
"help" : "Boot selected nodes into FreeBSD MFS" },
......@@ -133,6 +138,52 @@ def wrapperoptions():
print " --debug Turn on semi-useful debugging"
return
##
# Construct an SSHTransport object that is connected to an EmulabServer object
# on the peer. Multiple paths are attempted until one succeeds.
#
# @param user_agent The user-agent identifier.
# @param ssh_identity The ssh identity file to use when connecting.
# @return A pair containing the ssh transport and the path used, in that order.
#
def make_transport(user_agent=None, ssh_identity=None):
if path:
retval = (SSHTransport(user_agent=user_agent, ssh_opts=SSH_OPTS), path)
pass
else:
for xrpath in XMLRPC_PATH:
try:
retval = (SSHTransport(user_agent=user_agent,
ssh_identity=ssh_identity,
ssh_opts=SSH_OPTS),
xrpath)
hdrs = retval[0].probe(xmlrpc_server,
"/" + xrpath,
verbose=debug)
if hdrs["probe-response"] == "EmulabServer":
if debug:
print "make_transport: found path " + xrpath
pass
break
else:
retval = None
pass
pass
except BadResponse, e:
if debug:
print ("make_transport: bad response for "
+ xrpath + "; " + str(e))
pass
pass
pass
pass
if not retval:
print "error - Unable to connect to RPC server"
pass
return retval
#
# Process a single command line
#
......@@ -143,15 +194,16 @@ def do_method(module, method, params):
if impotent:
return 0;
transport, fullpath = make_transport(user_agent="sshxmlrpc_wrapper-v0.2")
# Get a handle on the server,
server = SSHServerProxy("ssh://" + login_id + "@" + xmlrpc_server +
"/xmlrpc/" + module,
path=path,
user_agent="sshxmlrpc_wrapper-v0.1")
"/" + fullpath,
transport=transport,
user_agent="sshxmlrpc_wrapper-v0.2")
# Get a pointer to the function we want to invoke.
meth = getattr(server, method)
meth = getattr(server, module + "." + method)
meth_args = [ PACKAGE_VERSION, params ]
#
......@@ -1558,6 +1610,15 @@ if admin:
handler = None;
command_argv = None;
if not conf_passphraseless_login():
sys.stderr.write(sys.argv[0]
+ ": error - No agent or passphrase-less key found\n")
sys.stderr.write(sys.argv[0]
+ ": You will need to regenerate your passphrase-less "
+ "key manually\n")
sys.exit(-1)
pass
if API.has_key(os.path.basename(sys.argv[0])):
handler = API[os.path.basename(sys.argv[0])]["func"];
command_argv = sys.argv[len(wrapper_argv) + 1:];
......
This diff is collapsed.
......@@ -43,7 +43,7 @@ if len(sys.argv) > 1:
#
# Construct and wrap our object.
server = eval(module + "(readonly=" + str(ReadOnly) + ")")
wrapper = sshxmlrpc.SSHServerWrapper(server)
wrapper = sshxmlrpc.SSHServerWrapper(server, module)
# Handle the request on stdin and send the response to stdout.
wrapper.serve_forever((sys.stdin, sys.stdout))
wrapper.serve_stdio_forever()
sys.exit(0)
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