#!/usr/local/bin/python import os,sys from BaseHTTPServer import BaseHTTPRequestHandler from ZSI import * from M2Crypto import SSL from M2Crypto.m2xmlrpclib import SSL_Transport from M2Crypto.SSL.SSLServer import SSLServer import M2Crypto.httpslib import xmlrpclib import re import random import string import subprocess import tempfile from fedd_services import * from fedd_internal_services import * from fedd_util import * import parse_detail from service_error import * import logging # Configure loggers to dump to /dev/null which avoids errors if calling classes # don't configure them. class nullHandler(logging.Handler): def emit(self, record): pass fl = logging.getLogger("fedd.allocate.local") fl.addHandler(nullHandler()) fl = logging.getLogger("fedd.allocate.remote") fl.addHandler(nullHandler()) class fedd_allocate_project_local: """ Allocate projects on this machine in response to an access request. """ def __init__(self, dp=False, url=None, certs=None): """ Initializer. Parses a configuration if one is given. """ self.dynamic_projects = dp self.wap = '/usr/testbed/sbin/wap' self.newproj = '/usr/testbed/sbin/newproj' self.mkproj = '/usr/testbed/sbin/mkproj' self.grantnodetype = '/usr/testbed/sbin/grantnodetype' self.log = logging.getLogger("fedd.allocate.local") # Internal services are SOAP only self.soap_services = {\ "AllocateProject": make_soap_handler(\ AllocateProjectRequestMessage.typecode,\ self.dynamic_project, AllocateProjectResponseMessage,\ "AllocateProjectResponseBody")\ } self.xmlrpc_services = { } def random_string(self, s, n=3): """Append n random ASCII characters to s and return the string""" rv = s for i in range(0,n): rv += random.choice(string.ascii_letters) return rv def write_attr_xml(self, file, root, lines): """ Write an emulab config file for a dynamic project. Format is lines[1] """ # Convert a pair to an attribute line out_attr = lambda a,v : \ '%s' % (a, v) f = os.fdopen(file, "w") f.write("<%s>\n" % root) f.write("\n".join([out_attr(*l) for l in lines])) f.write("\n" % root) f.close() def dynamic_project(self, req, fedid=None): """ Create a dynamic project with ssh access Req includes the project and resources as a dictionary """ # tempfiles for the parameter files uf, userfile = tempfile.mkstemp(prefix="usr", suffix=".xml", dir="/tmp") pf, projfile = tempfile.mkstemp(prefix="proj", suffix=".xml", dir="/tmp") if req.has_key('AllocateProjectRequestBody') and \ req['AllocateProjectRequestBody'].has_key('project'): proj = req['AllocateProjectRequestBody']['project'] else: raise service_error(service_error.req, "Badly formed allocation request") # Take the first user and ssh key name = proj.get('name', None) or self.random_string("proj",4) user = proj.get('user', None) if user != None: user = user[0] # User is a list, take the first entry if not user.has_key("userID"): uname = self.random_string("user", 3) else: uid = proj['userID'] # XXX: fedid uname = uid.get('localname', None) or \ uid.get('kerberosUsername', None) or \ uid.get('uri', None) if uname == None: raise fedd_proj.service_error(fedd_proj.service_error.req, "No ID for user"); access = user.get('access', None) if access != None: ssh = access[0].get('sshPubkey', None) if ssh == None: raise fedd_proj.service_error(fedd_proj.service_error.req, "No ssh key for user"); else: raise fedd_proj.service_error(fedd_proj.service_error.req, "No access information for project"); # uname, name and ssh are set user_fields = [ ("name", "Federation User %s" % uname), ("email", "%s-fed@isi.deterlab.net" % uname), ("password", self.random_string("", 8)), ("login", uname), ("address", "4676 Admiralty"), ("city", "Marina del Rey"), ("state", "CA"), ("zip", "90292"), ("country", "USA"), ("phone", "310-448-9190"), ("title", "None"), ("affiliation", "USC/ISI"), ("pubkey", ssh) ] proj_fields = [ ("name", name), ("short description", "dynamic federated project"), ("URL", "http://www.isi.edu/~faber"), ("funders", "USC/USU"), ("long description", "Federation access control"), ("public", "1"), ("num_pcs", "100"), ("linkedtous", "1"), ("newuser_xml", userfile) ] # Write out the files self.write_attr_xml(uf, "user", user_fields) self.write_attr_xml(pf, "project", proj_fields) # Generate the commands (only grantnodetype's are dynamic) cmds = [ (self.wap, self.newproj, projfile), (self.wap, self.mkproj, name) ] # Add commands to grant access to any resources in the request. for nt in [ h for r in req.get('resources', []) \ if r.has_key('node') and r['node'].has_key('hardware')\ for h in r['node']['hardware'] ] : cmds.append((self.wap, self.grantnodetype, '-p', name, nt)) # Create the projects rc = 0 for cmd in cmds: self.log.debug("[dynamic_project]: %s" % ' '.join(cmd)) if self.dynamic_projects: try: rc = subprocess.call(cmd) except OSerror, e: raise fedd_proj.service_error(\ fedd_proj.service_error.internal, "Dynamic project subprocess creation error "+ \ "[%s] (%s)" % (cmd[1], e.strerror)) if rc != 0: raise fedd_proj.service_error(\ fedd_proj.service_error.internal, "Dynamic project subprocess error " +\ "[%s] (%d)" % (cmd[1], rc)) # Clean up tempfiles os.unlink(userfile) os.unlink(projfile) rv = {\ 'project': {\ 'name': { 'localname': name }, 'user' : [ {\ 'userID': { 'localname' : uname }, 'access': [ { 'sshPubkey' : ssh } ], } ]\ }\ } return rv class fedd_allocate_project_remote: """ Allocate projects on a remote machine using the internal SOAP interface """ def __init__(self, dp=False, url=None, certs=None): """ Initializer. Parses a configuration if one is given. """ self.dynamic_projects = dp self.url = url if certs != None and isinstance(certs, type(tuple())): self.cert_file, self.trusted_certs, self.cert_pwd = certs else: self.cert_file, self.trusted_certs, self.cert_pwd = \ (None, None, None) self.soap_services = { } self.xmlrpc_services = { } def dynamic_project(self, req, fedid=None): """ Send req on to a remote project instantiator. Req is just the projectAllocType object. This function re-wraps it. It also rethrows any faults. """ # No retry loop here. Proxy servers must correctly authenticate # themselves without help try: ctx = fedd_ssl_context(self.cert_file, self.trusted_certs, password=self.cert_pwd) except SSL.SSLError: raise service_error(service_error.server_config, "Server certificates misconfigured") loc = feddInternalServiceLocator(); port = loc.getfeddInternalPortType(self.url, transport=M2Crypto.httpslib.HTTPSConnection, transdict={ 'ssl_context' : ctx }) if req.has_key('AllocateProjectRequestBody'): req = req['AllocateProjectRequestBody'] else: raise service_error(service_error.req, "Bad formated request"); # Reconstruct the full request message msg = AllocateProjectRequestMessage() msg.set_element_AllocateProjectRequestBody( pack_soap(msg, "AllocateProjectRequestBody", req)) try: resp = port.AllocateProject(msg) except ZSI.ParseException, e: raise service_error(service_error.proxy, "Bad format message (XMLRPC??): %s" % str(e)) r = unpack_soap(resp) if r.has_key('AllocateProjectResponseBody'): return r['AllocateProjectResponseBody'] else: raise service_error(service_error.proxy, "Bad proxy response")