#!/usr/local/bin/python import sys import os import pwd from fedd_services import * from M2Crypto import SSL, X509 from M2Crypto.m2xmlrpclib import SSL_Transport import M2Crypto.httpslib from xmlrpclib import ServerProxy, Error, dumps, loads from ZSI import SoapWriter from ZSI.TC import QName, String, URI, AnyElement, UNBOUNDED, Any from ZSI.wstools.Namespaces import SOAP from ZSI.fault import FaultType, Detail import xmlrpclib from fedd_util import fedid, fedd_ssl_context, pack_soap, unpack_soap, \ pack_id, unpack_id, encapsulate_binaries, decapsulate_binaries from optparse import OptionParser, OptionValueError import parse_detail # Turn off the matching of hostname to certificate ID SSL.Connection.clientPostConnectionCheck = None class IDFormatException(RuntimeError): pass class access_method: """Encapsulates an access method generically.""" (type_ssh, type_x509, type_pgp) = ('sshPubkey', 'X509', 'pgpPubkey') default_type = type_ssh def __init__(self, buf=None, type=None, file=None): self.buf = buf if type != None: self.type = type else: self.type = access_method.default_type if file != None: self.readfile(file) def readfile(self, file, type=None): f = open(file, "r") self.buf = f.read(); f.close() if type == None: if self.type == None: self.type = access_method.default_type else: self.type = type; class node_desc: def __init__(self, image, hardware, count=1): if getattr(image, "__iter__", None) == None: if image == None: self.image = [ ] else: self.image = [ image ] else: self.image = image if getattr(hardware, "__iter__", None) == None: if hardware == None: self.hardware = [ ] else: self.hardware = [ hardware ] else: self.hardware = hardware if count != None: self.count = int(count) else: self.count = 1 class fedd_client_opts(OptionParser): """Encapsulate option processing in this class, rather than in main""" def __init__(self): OptionParser.__init__(self, usage="%prog [opts] (--help for details)", version="0.1") self.add_option("-c","--cert", action="store", dest="cert", type="string", help="my certificate file") self.add_option("-d", "--debug", action="count", dest="debug", default=0, help="Set debug. Repeat for more information") self.add_option("-s", "--serializeOnly", action="store_true", dest="serialize_only", default=False, help="Print the SOAP request that would be sent and exit") self.add_option("-T","--trusted", action="store", dest="trusted", type="string", help="Trusted certificates (required)") self.add_option("-u", "--url", action="store", dest="url", type="string",default="https://localhost:23235", help="URL to connect to (default %default)") self.add_option("-x","--transport", action="store", type="choice", choices=("xmlrpc", "soap"), default="soap", help="Transport for request (xmlrpc|soap) (Default: %default)") self.add_option("--trace", action="store_const", dest="tracefile", const=sys.stderr, help="Print SOAP exchange to stderr") class fedd_create_opts(fedd_client_opts): def __init__(self, access_keys, add_key_callback=None, add_cert_callback=None): fedd_client_opts.__init__(self) self.add_option("-e", "--experiment_cert", dest="out_certfile", type="string", help="output certificate file") self.add_option("-F","--useFedid", action="store_true", dest="use_fedid", default=False, help="Use a fedid derived from my certificate as user identity") self.add_option("-f", "--file", dest="file", help="experiment description file") if add_key_callback: self.add_option("-k", "--sshKey", action="callback", type="string", callback=add_key_callback, callback_args=(access_keys,), help="ssh key for access (can be supplied more than once") if add_cert_callback: self.add_option("-K", "--x509Key", action="callback", type="string", callback=add_cert_callback, callback_args=(access_keys,), help="X509 certificate for access " + \ "(can be supplied more than once") self.add_option("-m", "--master", dest="master", help="Master testbed in the federation") self.add_option("-U", "--username", action="store", dest="user", type="string", help="Use this username instead of the uid") def exit_with_fault(dict, out=sys.stderr): """ Print an error message and exit. The dictionary contains the FeddFaultBody elements.""" codestr = "" if dict.has_key('errstr'): codestr = "Error: %s" % dict['errstr'] if dict.has_key('code'): if len(codestr) > 0 : codestr += " (%d)" % dict['code'] else: codestr = "Error Code: %d" % dict['code'] print>>out, codestr print>>out, "Description: %s" % dict['desc'] sys.exit(dict.get('code', 20)) class fedd_exp_data_opts(fedd_client_opts): def __init__(self): fedd_client_opts.__init__(self) self.add_option("-e", "--experiment_cert", dest="exp_certfile", type="string", help="output certificate file") # Querying experiment data follows the same control flow regardless of the # specific data retrieved. This class encapsulates that control flow. class exp_data: def __init__(self, op): """ Specialize the class for the type of data requested (op) """ if op =='vtopo': self.RequestMessage = VtopoRequestMessage; self.ResponseMessage = VtopoResponseMessage; self.RequestBody="VtopoRequestBody" self.ResponseBody="VtopoResponseBody" self.method = "Vtopo" self.key="vtopo" self.xml='experiment' elif op == 'vis': self.RequestMessage = VisRequestMessage; self.ResponseMessage = VisResponseMessage; self.RequestBody="VisRequestBody" self.ResponseBody="VisResponseBody" self.method = "Vis" self.key="vis" self.xml='vis' else: raise TypeError("Bad op: %s" % op) def print_xml(self, d, out=sys.stdout): """ Print the retrieved data is a simple xml representation of the dict. """ str = "<%s>\n" % self.xml for t in ('node', 'lan'): if d.has_key(t): for x in d[t]: str += "<%s>" % t for k in x.keys(): str += "<%s>%s" % (k, x[k],k) str += "\n" % t str+= "" % self.xml print >>out, str def __call__(self): """ The control flow. Compose the request and print the response. """ # Process the options using the customized option parser defined above parser = fedd_exp_data_opts() (opts, args) = parser.parse_args() if opts.trusted != None: if ( not os.access(opts.trusted, os.R_OK) ) : sys.exit("Cannot read trusted certificates (%s)" % opts.trusted) else: parser.error("--trusted is required") if opts.debug > 0: opts.tracefile=sys.stderr if opts.cert != None: cert = opts.cert if cert == None: sys.exit("No certificate given (--cert) or found") if os.access(cert, os.R_OK): fid = fedid(file=cert) else: sys.exit("Cannot read certificate (%s)" % cert) if opts.exp_certfile: exp_fedid = fedid(file=opts.exp_certfile) else: sys.exit("Experiment certfile required") context = None while context == None: try: context = fedd_ssl_context(cert, opts.trusted) except SSL.SSLError, e: # Yes, doing this on message type is not ideal. The string # comes from OpenSSL, so check there is this stops working. if str(e) == "bad decrypt": print >>sys.stderr, "Bad Passphrase given." else: raise msg = { 'experiment': { 'fedid': exp_fedid } } if opts.debug > 1: print >>sys.stderr, msg if opts.transport == "soap": loc = feddServiceLocator(); port = loc.getfeddPortType(opts.url, transport=M2Crypto.httpslib.HTTPSConnection, transdict={ 'ssl_context' : context }, tracefile=opts.tracefile) req = self.RequestMessage() set_req = getattr(req, "set_element_%s" % self.RequestBody, None) set_req(pack_soap(req, self.RequestBody, msg)) if opts.serialize_only: sw = SoapWriter() sw.serialize(req) print str(sw) sys.exit(0) try: method_call = getattr(port, self.method, None) resp = method_call(req) except ZSI.ParseException, e: sys.exit("Malformed response (XMLPRC?): %s" % e) except ZSI.FaultException, e: resp = e.fault.detail[0] if resp: resp_call = getattr(resp, "get_element_%s" %self.ResponseBody, None) if resp_call: resp_body = resp_call() if ( resp_body != None): try: resp_dict = unpack_soap(resp_body) if opts.debug > 1: print >>sys.stderr, resp_dict if resp_dict.has_key(self.key): self.print_xml(resp_dict[self.key]) except RuntimeError, e: sys.exit("Bad response. %s" % e.message) elif 'get_element_FeddFaultBody' in dir(resp): resp_body = resp.get_element_FeddFaultBody() if resp_body != None: exit_with_fault(unpack_soap(resp_body)) else: sys.exit("No body in response!?") else: sys.exit("No response?!?") elif opts.transport == "xmlrpc": if opts.serialize_only: ser = dumps((msg,)) print ser sys.exit(0) transport = SSL_Transport(context) port = ServerProxy(opts.url, transport=transport) try: method_call = getattr(port, self.method, None) resp = method_call( encapsulate_binaries({ self.RequestBody: msg},\ ('fedid',))) except Error, e: resp = { 'FeddFaultBody': \ { 'errstr' : e.faultCode, 'desc' : e.faultString } } if resp: if resp.has_key(self.ResponseBody): try: resp_dict = resp[self.ResponseBody] if opts.debug > 1: print >>sys.stderr, resp_dict if resp_dict.has_key(self.key): self.print_xml(resp_dict[self.key]) except RuntimeError, e: sys.exit("Bad response. %s" % e.messgae) elif resp.has_key('FeddFaultBody'): exit_with_fault(resp['FeddFaultBody']) else: sys.exit("No body in response!?") else: sys.exit("No response?!?") class create: def __init__(self): pass def add_ssh_key(self, option, opt_str, value, parser, access_keys): try: access_keys.append(access_method(file=value, type=access_method.type_ssh)) except IOError, (errno, strerror): raise OptionValueError("Cannot generate sshPubkey from %s: "\ "%s (%d)" % (value,strerror,errno)) def add_x509_cert(self, option, opt_str, value, parser, access_keys): try: access_keys.append(access_method(file=value, type=access_method.type_x509)) except IOError, (errno, strerror): raise OptionValueError("Cannot read x509 cert from %s: %s (%d)" % (value,strerror,errno)) def get_user_info(self, access_keys): pw = pwd.getpwuid(os.getuid()); try_cert=None user = None if pw != None: user = pw[0] try_cert = "%s/.ssl/emulab.pem" % pw[5]; if not os.access(try_cert, os.R_OK): try_cert = None if len(access_keys) == 0: for k in ["%s/.ssh/id_rsa.pub", "%s/.ssh/id_dsa.pub", "%s/.ssh/identity.pub"]: try_key = k % pw[5]; if os.access(try_key, os.R_OK): access_keys.append(access_method(file=try_key, type=access_method.type_ssh)) break return (user, try_cert) def __call__(self): access_keys = [] # Process the options using the customized option parser defined above parser = fedd_create_opts(access_keys, self.add_ssh_key, self.add_x509_cert) (opts, args) = parser.parse_args() if opts.trusted != None: if ( not os.access(opts.trusted, os.R_OK) ) : sys.exit("Cannot read trusted certificates (%s)" % opts.trusted) else: parser.error("--trusted is required") if opts.debug > 0: opts.tracefile=sys.stderr (user, cert) = self.get_user_info(access_keys) if opts.user: user = opts.user if opts.cert != None: cert = opts.cert if cert == None: sys.exit("No certificate given (--cert) or found") if os.access(cert, os.R_OK): fid = fedid(file=cert) if opts.use_fedid == True: user = fid else: sys.exit("Cannot read certificate (%s)" % cert) if opts.file: exp_desc = "" try: f = open(opts.file, 'r') for line in f: exp_desc += line f.close() except IOError: sys.exit("Cannot read description file (%s)" %opts.file) else: sys.exit("Must specify an experiment description (--file)") if not opts.master: sys.exit("Must specify a master testbed (--master)") out_certfile = opts.out_certfile context = None while context == None: try: context = fedd_ssl_context(cert, opts.trusted) except SSL.SSLError, e: # Yes, doing this on message type is not ideal. The string comes # from OpenSSL, so check there is this stops working. if str(e) == "bad decrypt": print >>sys.stderr, "Bad Passphrase given." else: raise msg = { 'experimentdescription': exp_desc, 'master': opts.master, 'user' : [ {\ 'userID': pack_id(user), \ 'access': [ { a.type: a.buf } for a in access_keys]\ } ] } if opts.debug > 1: print >>sys.stderr, msg if opts.transport == "soap": loc = feddServiceLocator(); port = loc.getfeddPortType(opts.url, transport=M2Crypto.httpslib.HTTPSConnection, transdict={ 'ssl_context' : context }, tracefile=opts.tracefile) req = CreateRequestMessage() req.set_element_CreateRequestBody( pack_soap(req, "CreateRequestBody", msg)) if opts.serialize_only: sw = SoapWriter() sw.serialize(req) print str(sw) sys.exit(0) try: resp = port.Create(req) except ZSI.ParseException, e: sys.exit("Malformed response (XMLPRC?): %s" % e) except ZSI.FaultException, e: resp = e.fault.detail[0] if resp: if 'get_element_CreateResponseBody' in dir(resp): resp_body = resp.get_element_CreateResponseBody() if ( resp_body != None): try: resp_dict = unpack_soap(resp_body) if opts.debug > 1: print >>sys.stderr, resp_dict ea = resp_dict.get('experimentAccess', None) if out_certfile and ea and ea.has_key('X509'): try: f = open(out_certfile, "w") print >>f, ea['X509'] f.close() except IOError: sys.exit('Could not write to %s' % \ out_certfile) except RuntimeError, e: sys.exit("Bad response. %s" % e.message) elif 'get_element_FeddFaultBody' in dir(resp): resp_body = resp.get_element_FeddFaultBody() if resp_body != None: exit_with_fault(unpack_soap(resp_body)) else: sys.exit("No body in response!?") else: sys.exit("No response?!?") elif opts.transport == "xmlrpc": if opts.serialize_only: ser = dumps((msg,)) print ser sys.exit(0) transport = SSL_Transport(context) port = ServerProxy(opts.url, transport=transport) try: resp = port.Create({ 'CreateRequestBody': msg}) except Error, e: resp = { 'FeddFaultBody': \ { 'errstr' : e.faultCode, 'desc' : e.faultString } } if resp: if resp.has_key('CreateResponseBody'): try: resp_dict = resp['CreateResponseBody'] decapsulate_binaries(resp_dict, ('fedid',)) if opts.debug > 1: print >>sys.stderr, resp_dict ea = resp_dict.get('experimentAccess', None) if out_certfile and ea and ea.has_key('X509'): try: f = open(out_certfile, "w") print >>f, ea['X509'] f.close() except IOError: sys.exit('Could not write to %s' % out_certfile) except RuntimeError, e: sys.exit("Bad response. %s" % e.messgae) elif resp.has_key('FeddFaultBody'): exit_with_fault(resp['FeddFaultBody']) else: sys.exit("No body in response!?") else: sys.exit("No response?!?") valid_cmds = ['create'] f = None if sys.argv[1] == 'create': del sys.argv[1] f = create() elif sys.argv[1] == 'vtopo': del sys.argv[1] f = exp_data('vtopo') elif sys.argv[1] == 'vis': del sys.argv[1] f = exp_data('vis') else: sys.exit("Bad command: %s. Valid ones are: %s" % \ (sys.argv[1], ", ".join(valid_cmds))) if f: f() else: sys.exit("Null function?!?")