Index: fedd/federation/protogeni_access.py
===================================================================
--- fedd/federation/protogeni_access.py	(revision a65a65a860e3841f04c8fdb1bdf28364ed22c007)
+++ fedd/federation/protogeni_access.py	(revision 3551ae16efcbff61f2a479021f7b323bac7c2e11)
@@ -10,4 +10,5 @@
 import logging
 import subprocess
+import traceback
 
 from threading import *
@@ -25,4 +26,6 @@
 from urlparse import urlparse
 
+from access import access_base
+
 import topdl
 import list_log
@@ -37,5 +40,5 @@
 fl.addHandler(nullHandler())
 
-class access:
+class access(access_base):
     """
     The implementation of access control based on mapping users to projects.
@@ -44,10 +47,4 @@
     dynamically.  This implements both direct requests and proxies.
     """
-
-    class parse_error(RuntimeError): pass
-
-
-    proxy_RequestAccess= service_caller('RequestAccess')
-    proxy_ReleaseAccess= service_caller('ReleaseAccess')
 
     def __init__(self, config=None, auth=None):
@@ -56,25 +53,7 @@
         """
 
-	def software_list(v):
-	    l = [ ]
-	    if v:
-		ps = v.split(" ")
-		while len(ps):
-		    loc, file = ps[0:2]
-		    del ps[0:2]
-		    if loc == 'rpm':
-			loc = None
-		    l.append((loc, file))
-	    return l
-
-        # Make sure that the configuration is in place
-        if not config: 
-            raise RunTimeError("No config to fedd.access")
-
-        self.project_priority = config.getboolean("access", "project_priority")
-        self.allow_proxy = config.getboolean("access", "allow_proxy")
+	access_base.__init__(self, config, auth)
 
         self.domain = config.get("access", "domain")
-	self.certdir = config.get("access","certdir")
 	self.userconfdir = config.get("access","userconfdir")
 	self.userconfcmd = config.get("access","userconfcmd")
@@ -85,6 +64,4 @@
 	self.sshd = config.get("access","sshd")
 	self.sshd_config = config.get("access", "sshd_config")
-	self.create_debug = config.getboolean("access", "create_debug")
-	self.cleanup = not config.getboolean("access", "leave_tmpfiles")
 	self.access_type = config.get("access", "type")
 	self.staging_dir = config.get("access", "staging_dir") or "/tmp"
@@ -106,7 +83,7 @@
 	self.node_startcommand = config.get("access", "node_startcommand")
 
-	self.federation_software = software_list(self.federation_software)
-	self.portal_software = software_list(self.portal_software)
-	self.local_seer_software = software_list(self.local_seer_software)
+	self.federation_software = self.software_list(self.federation_software)
+	self.portal_software = self.software_list(self.portal_software)
+	self.local_seer_software = self.software_list(self.local_seer_software)
 
 	self.renewal_interval = config.get("access", "renewal") or (3 * 60 )
@@ -117,50 +94,33 @@
 	self.cm_url = config.get("access", "cm_url")
 
-        self.access = { }
         self.restricted = [ ]
-        self.projects = { }
-        self.keys = { }
-        self.types = { }
-        self.allocation = { }
-        self.state = { 
-            'projects': self.projects,
-            'allocation' : self.allocation,
-            'keys' : self.keys,
-            'types': self.types
-        }
+
+	# read_state in the base_class
+	self.state_lock.acquire()
+	for a  in ('allocation', 'projects', 'keys', 'types'):
+	    if a not in self.state:
+		self.state[a] = { }
+	self.allocation = self.state['allocation']
+	self.projects = self.state['projects']
+	self.keys = self.state['keys']
+	self.types = self.state['types']
+	# Add the ownership attributes to the authorizer.  Note that the
+	# indices of the allocation dict are strings, but the attributes are
+	# fedids, so there is a conversion.
+	for k in self.state.get('allocation', {}).keys():
+	    for o in self.state['allocation'][k].get('owners', []):
+		self.auth.set_attribute(o, fedid(hexstr=k))
+	    self.auth.set_attribute(fedid(hexstr=k),fedid(hexstr=k))
+
+	self.state_lock.release()
+
+
         self.log = logging.getLogger("fedd.access")
         set_log_level(config, "access", self.log)
-        self.state_lock = Lock()
-	# XXX: Configurable
-	self.exports = set(('SMB', 'seer', 'tmcd', 'userconfig'))
-	self.imports = set(('SMB', 'seer', 'userconfig'))
-
-        if auth: self.auth = auth
-        else:
-            self.log.error(\
-                    "[access]: No authorizer initialized, creating local one.")
-            auth = authorizer()
-
-        tb = config.get('access', 'testbed')
-        if tb: self.testbed = [ t.strip() for t in tb.split(',') ]
-        else: self.testbed = [ ]
-
+
+	self.access = { }
         if config.has_option("access", "accessdb"):
-            self.read_access(config.get("access", "accessdb"))
-
-        self.state_filename = config.get("access", "access_state")
-	print "Calling read_state %s" % self.state_filename
-        self.read_state()
-
-        # Keep cert_file and cert_pwd coming from the same place
-        self.cert_file = config.get("access", "cert_file")
-        if self.cert_file:
-            self.sert_pwd = config.get("access", "cert_pw")
-        else:
-            self.cert_file = config.get("globals", "cert_file")
-            self.sert_pwd = config.get("globals", "cert_pw")
-
-        self.trusted_certs = config.get("access", "trusted_certs") or \
-                config.get("globals", "trusted_certs")
+            self.read_access(config.get("access", "accessdb"), 
+		    access_obj=self.make_access_info)
 
 	self.start_segment = proxy_protogeni_segment.start_segment
@@ -168,6 +128,23 @@
 	self.renew_segment = proxy_protogeni_segment.renew_segment
 
+	self.lookup_access = self.lookup_access_base
+
 	self.call_SetValue = service_caller('SetValue')
 	self.call_GetValue = service_caller('GetValue')
+	self.exports = {
+		'local_seer_control': self.export_local_seer,
+		'seer_master': self.export_seer_master,
+		'hide_hosts': self.export_hide_hosts,
+		}
+
+	if not self.local_seer_image or not self.local_seer_software or \
+		not self.local_seer_start:
+	    if 'local_seer_control' in self.exports:
+		del self.exports['local_seer_control']
+
+	if not self.local_seer_image or not self.local_seer_software or \
+		not self.seer_master_start:
+	    if 'seer_master' in self.exports:
+		del self.exports['seer_master']
 
 	self.RenewSlices()
@@ -190,239 +167,23 @@
             }
 
-    def read_access(self, config):
-        """
-        Read a configuration file and set internal parameters.
-
-        There are access lines of the
-        form (tb, proj, user) -> user that map the first tuple of
-        names to the user for for access purposes.  Names in the key (left side)
-        can include "<NONE> or <ANY>" to act as wildcards or to require the
-        fields to be empty.  Similarly aproj or auser can be <SAME> or
-        <DYNAMIC> indicating that either the matching key is to be used or a
-        dynamic user or project will be created.  These names can also be
-	federated IDs (fedid's) if prefixed with fedid:.  The user is the
-	ProtoGENI identity certificate.
-        Testbed attributes outside the forms above can be given using the
-        format attribute: name value: value.  The name is a single word and the
-        value continues to the end of the line.  Empty lines and lines startin
-        with a # are ignored.
-
-        Parsing errors result in a self.parse_error exception being raised.
-        """
-        lineno=0
-        name_expr = "["+string.ascii_letters + string.digits + "\/\.\-_]+"
-        fedid_expr = "fedid:[" + string.hexdigits + "]+"
-        key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")"
-
-	access_str = '\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+ \
-                key_name+'\s*\)\s*->\s*\(('+name_expr +')\s*,\s*('\
-			+ name_expr + ')\s*,\s*('+name_expr+')\s*,?\s*(' + \
-			name_expr+ ')?\)'
-        access_re = re.compile(access_str, re.IGNORECASE)
-
-
-        def parse_name(n):
-            if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):])
-            else: return n
-        
-        def auth_name(n):
-            if isinstance(n, basestring):
-                if n =='<any>' or n =='<none>': return None
-                else: return unicode(n)
-            else:
-                return n
-
-        f = open(config, "r");
-        for line in f:
-            lineno += 1
-            line = line.strip();
-            if len(line) == 0 or line.startswith('#'):
-                continue
-
-            # Access line (t, p, u) -> (a, pw) line
-            m = access_re.match(line)
-            if m != None:
-                access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
-                auth_key = tuple([ auth_name(x) for x in access_key])
-                cert = auth_name(parse_name(m.group(4)))
-                user_name = auth_name(parse_name(m.group(5)))
-		ssh_key = unicode(m.group(6))
-		if m.group(6): pw = unicode(m.group(7))
-		else: pw = None
-
-                self.access[access_key] = (cert, user_name, ssh_key, pw)
-                self.auth.set_attribute(auth_key, "access")
-                continue
-
-            # Nothing matched to here: unknown line - raise exception
-            f.close()
-            raise self.parse_error("Unknown statement at line %d of %s" % \
-                    (lineno, config))
-        f.close()
-
-    def write_state(self):
-        if self.state_filename:
-            try:
-                f = open(self.state_filename, 'w')
-                pickle.dump(self.state, f)
-            except EnvironmentError, e:
-                self.log.error("Can't write file %s: %s" % \
-                        (self.state_filename, e))
-            except pickle.PicklingError, e:
-                self.log.error("Pickling problem: %s" % e)
-            except TypeError, e:
-                self.log.error("Pickling problem (TypeError): %s" % e)
-
-
-    def read_state(self):
-        """
-        Read a new copy of access state.  Old state is overwritten.
-
-        State format is a simple pickling of the state dictionary.
-        """
-        if self.state_filename:
-            try:
-                f = open(self.state_filename, "r")
-                self.state = pickle.load(f)
-                self.log.debug("[read_state]: Read state from %s" % \
-                        self.state_filename)
-            except EnvironmentError, e:
-                self.log.warning(("[read_state]: No saved state: " +\
-                        "Can't open %s: %s") % (self.state_filename, e))
-            except EOFError, e:
-                self.log.warning(("[read_state]: " +\
-                        "Empty or damaged state file: %s:") % \
-                        self.state_filename)
-            except pickle.UnpicklingError, e:
-                self.log.warning(("[read_state]: No saved state: " + \
-                        "Unpickling failed: %s") % e)
-
-            # Add the ownership attributes to the authorizer.  Note that the
-            # indices of the allocation dict are strings, but the attributes are
-            # fedids, so there is a conversion.
-            for k in self.state.get('allocation', {}).keys():
-                for o in self.state['allocation'][k].get('owners', []):
-                    self.auth.set_attribute(o, fedid(hexstr=k))
-		self.auth.set_attribute(fedid(hexstr=k),fedid(hexstr=k))
-
-	    if self.allocation != self.state['allocation']:
-		self.allocation = self.state['allocation']
-
-    def permute_wildcards(self, a, p):
-        """Return a copy of a with various fields wildcarded.
-
-        The bits of p control the wildcards.  A set bit is a wildcard
-        replacement with the lowest bit being user then project then testbed.
-        """
-        if p & 1: user = ["<any>"]
-        else: user = a[2]
-        if p & 2: proj = "<any>"
-        else: proj = a[1]
-        if p & 4: tb = "<any>"
-        else: tb = a[0]
-
-        return (tb, proj, user)
-
-
-    def find_access(self, search):
-        """
-        Search the access DB for a match on this tuple.  Return the matching
-        user (protoGENI cert).
-        
-        NB, if the initial tuple fails to match we start inserting wildcards in
-        an order determined by self.project_priority.  Try the list of users in
-        order (when wildcarded, there's only one user in the list).
-        """
-        if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
-        else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
-
-        for p in perm: 
-            s = self.permute_wildcards(search, p)
-            # s[2] is None on an anonymous, unwildcarded request
-            if s[2] != None:
-                for u in s[2]:
-                    if self.access.has_key((s[0], s[1], u)):
-                        return self.access[(s[0], s[1], u)]
-            else:
-                if self.access.has_key(s):
-                    return self.access[s]
-        return None
-
-    def lookup_access(self, req, fid):
-        """
-        Determine the allowed access for this request.  Return the access and
-        which fields are dynamic.
-
-        The fedid is needed to construct the request
-        """
-        # Search keys
-        tb = None
-        project = None
-        user = None
-        # Return values
-        rp = access_project(None, ())
-        ru = None
-	user_re = re.compile("user:\s(.*)")
-	project_re = re.compile("project:\s(.*)")
-
-	user = [ user_re.findall(x)[0] for x in req.get('credential', []) \
-		if user_re.match(x)]
-	project = [ project_re.findall(x)[0] \
-		for x in req.get('credential', []) \
-		    if project_re.match(x)]
-
-	if len(project) == 1: project = project[0]
-	elif len(project) == 0: project = None
-	else: 
-	    raise service_error(service_error.req, 
-		    "More than one project credential")
-
-
-        user_fedids = [ u for u in user if isinstance(u, fedid)]
-
-        # Determine how the caller is representing itself.  If its fedid shows
-        # up as a project or a singleton user, let that stand.  If neither the
-        # usernames nor the project name is a fedid, the caller is a testbed.
-        if project and isinstance(project, fedid):
-            if project == fid:
-                # The caller is the project (which is already in the tuple
-                # passed in to the authorizer)
-                owners = user_fedids
-                owners.append(project)
-            else:
-                raise service_error(service_error.req,
-                        "Project asserting different fedid")
-        else:
-            if fid not in user_fedids:
-                tb = fid
-                owners = user_fedids
-                owners.append(fid)
-            else:
-                if len(fedids) > 1:
-                    raise service_error(service_error.req,
-                            "User asserting different fedid")
-                else:
-                    # Which is a singleton
-                    owners = user_fedids
-        # Confirm authorization
-
-        for u in user:
-            self.log.debug("[lookup_access] Checking access for %s" % \
-                    ((tb, project, u),))
-            if self.auth.check_attribute((tb, project, u), 'access'):
-                self.log.debug("[lookup_access] Access granted")
-                break
-            else:
-                self.log.debug("[lookup_access] Access Denied")
-        else:
-            raise service_error(service_error.access, "Access denied")
-
-        # This maps a valid user to the ProtoGENI credentials to use
-        found = self.find_access((tb, project, user))
-        
-        if found == None:
-            raise service_error(service_error.access,
-                    "Access denied - cannot map access")
-	return found, owners
+    @staticmethod
+    def make_access_info(s):
+	"""
+	Split a string of the form (id, id, id, id) ito its constituent tuples
+	and return them as a tuple.  Use to import access info from the
+	access_db.
+	"""
+
+	ss = s.strip()
+	if ss.startswith('(') and ss.endswith(')'):
+	    l = [ s.strip() for s  in ss[1:-1].split(",")]
+	    if len(l) == 4:
+		return tuple(l)
+	    else:
+		raise self.parse_error(
+			"Exactly 4 elements in access info required")
+	else:
+	    raise self.parse_error("Expecting parenthezied values")
+
 
     def get_handler(self, path, fid):
@@ -433,105 +194,5 @@
 	    return (None, None)
 
-    def export_userconf(self, project):
-	dev_null = None
-	confid, confcert = generate_fedid("test", dir=self.userconfdir, 
-		log=self.log)
-	conffilename = "%s/%s" % (self.userconfdir, str(confid))
-	cf = None
-	try:
-	    cf = open(conffilename, "w")
-	    os.chmod(conffilename, stat.S_IRUSR | stat.S_IWUSR)
-	except EnvironmentError, e:
-	    raise service_error(service_error.internal, 
-		    "Cannot create user configuration data")
-
-	try:
-	    dev_null = open("/dev/null", "a")
-	except EnvironmentError, e:
-	    self.log.error("export_userconf: can't open /dev/null: %s" % e)
-
-	cmd = "%s %s" % (self.userconfcmd, project)
-	conf = subprocess.call(cmd.split(" "),
-		stdout=cf, stderr=dev_null, close_fds=True)
-
-	self.auth.set_attribute(confid, "/%s" % str(confid))
-
-	return confid, confcert
-
-
-    def export_SMB(self, id, state, project, user):
-	return { 
-		'id': id,
-		'name': 'SMB',
-		'visibility': 'export',
-		'server': 'http://fs:139',
-		'fedAttr': [
-			{ 'attribute': 'SMBSHARE', 'value': 'USERS' },
-			{ 'attribute': 'SMBUSER', 'value': user },
-			{ 'attribute': 'SMBPROJ', 'value': project },
-		    ]
-		}
-
-    def export_seer(self, id, state, project, user):
-	return { 
-		'id': id,
-		'name': 'seer',
-		'visibility': 'export',
-		'server': 'http://control:16606',
-		}
-
-    def export_tmcd(self, id, state, project, user):
-	return { 
-		'id': id,
-		'name': 'seer',
-		'visibility': 'export',
-		'server': 'http://boss:7777',
-		}
-
-    def export_userconfig(self, id, state, project, user):
-	if self.userconfdir and self.userconfcmd \
-		and self.userconfurl:
-	    cid, cert = self.export_userconf(project)
-	    state['userconfig'] = unicode(cid)
-	    return {
-		    'id': id,
-		    'name': 'userconfig',
-		    'visibility': 'export',
-		    'server': "%s/%s" % (self.userconfurl, str(cid)),
-		    'fedAttr': [
-			{ 'attribute': 'cert', 'value': cert },
-		    ]
-		    }
-	else:
-	    return None
-
-    def export_services(self, sreq, project, user):
-	exp = [ ]
-	state = { }
-	# XXX: Filthy shortcut here using http: so urlparse will give the right
-	# answers.
-	for s in sreq:
-	    sname = s.get('name', '')
-	    svis = s.get('visibility', '')
-	    if svis == 'export':
-		if sname in self.exports:
-		    id = s.get('id', 'no_id')
-		    if sname == 'SMB':
-			exp.append(self.export_SMB(id, state, project, user))
-		    elif sname == 'seer':
-			exp.append(self.export_seer(id, state, project, user))
-		    elif sname == 'tmcd':
-			exp.append(self.export_tmcd(id, state, project, user))
-		    elif sname == 'userconfig':
-			exp.append(self.export_userconfig(id, state,
-			    project, user))
-		    elif sname == 'project_export':
-			exp.append(self.export_SMB(id, state, project, user))
-			exp.append(self.export_seer(id, state, project, user))
-			exp.append(self.export_userconfig(id, state,
-			    project, user))
-	return (exp, state)
-
-    def build_response(self, alloc_id, ap, services):
+    def build_access_response(self, alloc_id, services):
         """
         Create the SOAP response.
@@ -548,6 +209,4 @@
 		'fedAttr': [
 		    { 'attribute': 'domain', 'value': self.domain } , 
-		    { 'attribute': 'project', 'value': 
-			ap['project'].get('name', {}).get('localname', "???") },
 		]
             }
@@ -556,8 +215,6 @@
 		'value': self.dragon_endpoint})
 	if self.deter_internal:
-	    print 'adding internal'
 	    msg['fedAttr'].append({'attribute': 'deter_internal',
 		'value': self.deter_internal})
-	else: print "internal: %s" % self.deter_internal
 	#XXX: ??
 	if self.dragon_vlans:
@@ -571,8 +228,5 @@
     def RequestAccess(self, req, fid):
         """
-        Handle the access request.  Proxy if not for us.
-
-        Parse out the fields and make the allocations or rejections if for us,
-        otherwise, assuming we're willing to proxy, proxy the request out.
+        Handle the access request.
         """
 
@@ -586,43 +240,31 @@
             dt = unpack_id(req['destinationTestbed'])
 
-        if dt == None or dt in self.testbed:
-            # Request for this fedd
-            found, owners = self.lookup_access(req, fid)
-            # keep track of what's been added
-            allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
-            aid = unicode(allocID)
-
-            self.state_lock.acquire()
-            self.allocation[aid] = { }
-	    # The protoGENI certificate
-	    self.allocation[aid]['credentials'] = found
-	    # The list of owner FIDs
-	    self.allocation[aid]['owners'] = owners
-            self.write_state()
-            self.state_lock.release()
-            for o in owners:
-                self.auth.set_attribute(o, allocID)
-	    self.auth.set_attribute(allocID, allocID)
-
-	    try:
-		f = open("%s/%s.pem" % (self.certdir, aid), "w")
-		print >>f, alloc_cert
-		f.close()
-	    except EnvironmentError, e:
-		raise service_error(service_error.internal, 
-			"Can't open %s/%s : %s" % (self.certdir, aid, e))
-	    return { 'allocID': { 'fedid': allocID } }
-        else:
-            if self.allow_proxy:
-                resp = self.proxy_RequestAccess.call_service(dt, req,
-                            self.cert_file, self.cert_pwd,
-                            self.trusted_certs)
-                if resp.has_key('RequestAccessResponseBody'):
-                    return resp['RequestAccessResponseBody']
-                else:
-                    return None
-            else:
-                raise service_error(service_error.access,
-                        "Access proxying denied")
+	# Request for this fedd
+	found, match = self.lookup_access(req, fid)
+	services, svc_state = self.export_services(req.get('service',[]),
+		None, None)
+	# keep track of what's been added
+	allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
+	aid = unicode(allocID)
+
+	self.state_lock.acquire()
+	self.allocation[aid] = { }
+	# The protoGENI certificate
+	self.allocation[aid]['credentials'] = found
+	# The list of owner FIDs
+	self.allocation[aid]['owners'] = [ fid ]
+	self.write_state()
+	self.state_lock.release()
+	self.auth.set_attribute(fid, allocID)
+	self.auth.set_attribute(allocID, allocID)
+
+	try:
+	    f = open("%s/%s.pem" % (self.certdir, aid), "w")
+	    print >>f, alloc_cert
+	    f.close()
+	except EnvironmentError, e:
+	    raise service_error(service_error.internal, 
+		    "Can't open %s/%s : %s" % (self.certdir, aid, e))
+	return self.build_access_response({ 'fedid': allocID }, None)
 
 
@@ -634,248 +276,38 @@
             raise service_error(service_error.req, "No request!?")
 
-        if req.has_key('destinationTestbed'):
-            dt = unpack_id(req['destinationTestbed'])
-        else:
-            dt = None
-
-        if dt == None or dt in self.testbed:
-            # Local request
-            try:
-                if req['allocID'].has_key('localname'):
-                    auth_attr = aid = req['allocID']['localname']
-                elif req['allocID'].has_key('fedid'):
-                    aid = unicode(req['allocID']['fedid'])
-                    auth_attr = req['allocID']['fedid']
-                else:
-                    raise service_error(service_error.req,
-                            "Only localnames and fedids are understood")
-            except KeyError:
-                raise service_error(service_error.req, "Badly formed request")
-
-            self.log.debug("[access] deallocation requested for %s", aid)
-            if not self.auth.check_attribute(fid, auth_attr):
-                self.log.debug("[access] deallocation denied for %s", aid)
-                raise service_error(service_error.access, "Access Denied")
-
-	    self.state_lock.acquire()
-            if self.allocation.has_key(aid):
-                self.log.debug("Found allocation for %s" %aid)
-                del self.allocation[aid]
-                self.write_state()
-                self.state_lock.release()
-		# And remove the access cert
-		cf = "%s/%s.pem" % (self.certdir, aid)
-		self.log.debug("Removing %s" % cf)
-		os.remove(cf)
-                return { 'allocID': req['allocID'] } 
-            else:
-                self.state_lock.release()
-                raise service_error(service_error.req, "No such allocation")
-
-        else:
-            if self.allow_proxy:
-                resp = self.proxy_ReleaseAccess.call_service(dt, req,
-                            self.cert_file, self.cert_pwd,
-                            self.trusted_certs)
-                if resp.has_key('ReleaseAccessResponseBody'):
-                    return resp['ReleaseAccessResponseBody']
-                else:
-                    return None
-            else:
-                raise service_error(service_error.access,
-                        "Access proxying denied")
-
-    def import_store_info(self, cf, connInfo):
-	"""
-	Pull any import parameters in connInfo in.  We translate them either
-	into known member names or fedAddrs.
-	"""
-
-	for c in connInfo:
-	    for p in [ p for p in c.get('parameter', []) \
-		    if p.get('type', '') == 'input']:
-		name = p.get('name', None)
-		key = p.get('key', None)
-		store = p.get('store', None)
-
-		if name and key and store :
-		    req = { 'name': key, 'wait': True }
-		    r = self.call_GetValue(store, req, cf)
-		    r = r.get('GetValueResponseBody', None)
-		    if r :
-			if r.get('name', '') == key:
-			    v = r.get('value', None)
-			    if v is not None:
-				if name == 'peer':
-				    c['peer'] = v
-				else:
-				    if c.has_key('fedAttr'):
-					c['fedAttr'].append({
-					    'attribute': name, 'value': v})
-				    else:
-					c['fedAttr']= [{
-					    'attribute': name, 'value': v}]
-			    else:
-				raise service_error(service_error.internal, 
-					'None value exported for %s'  % key)
-			else:
-			    raise service_error(service_error.internal, 
-				    'Different name returned for %s: %s' \
-					    % (key, r.get('name','')))
-		    else:
-			raise service_error(service_error.internal, 
-			    'Badly formatted response: no GetValueResponseBody')
-		else:
-		    raise service_error(service_error.internal, 
-			'Bad Services missing info for import %s' % c)
-
-    def generate_portal_configs(self, topo, pubkey_base, secretkey_base, 
-	    tmpdir, master, leid, connInfo, services):
-
-	def conninfo_to_dict(key, info):
-	    """
-	    Make a cpoy of the connection information about key, and flatten it
-	    into a single dict by parsing out any feddAttrs.
-	    """
-
-	    rv = None
-	    for i in info:
-		if key == i.get('portal', "") or \
-			key in [e.get('element', "") \
-			for e in i.get('member', [])]:
-		    rv = i.copy()
-		    break
-
+	# Local request
+	try:
+	    if req['allocID'].has_key('localname'):
+		auth_attr = aid = req['allocID']['localname']
+	    elif req['allocID'].has_key('fedid'):
+		aid = unicode(req['allocID']['fedid'])
+		auth_attr = req['allocID']['fedid']
 	    else:
-		return rv
-
-	    if 'fedAttr' in rv:
-		for a in rv['fedAttr']:
-		    attr = a.get('attribute', "")
-		    val = a.get('value', "")
-		    if attr and attr not in rv:
-			rv[attr] = val
-		del rv['fedAttr']
-	    return rv
-
-	# XXX: un hardcode this
-	def client_null(f, s):
-	    print >>f, "Service: %s" % s['name']
-
-	def client_smb(f, s):
-	    print >>f, "Service: %s" % s['name']
-	    smbshare = None
-	    smbuser = None
-	    smbproj = None
-	    for a in s.get('fedAttr', []):
-		if a.get('attribute', '') == 'SMBSHARE':
-		    smbshare = a.get('value', None)
-		elif a.get('attribute', '') == 'SMBUSER':
-		    smbuser = a.get('value', None)
-		elif a.get('attribute', '') == 'SMBPROJ':
-		    smbproj = a.get('value', None)
-
-	    if all((smbshare, smbuser, smbproj)):
-		print >>f, "SMBshare: %s" % smbshare
-		print >>f, "ProjectUser: %s" % smbuser
-		print >>f, "ProjectName: %s" % smbproj
-
-	client_service_out = {
-		'SMB': client_smb,
-		'tmcd': client_null,
-		'seer': client_null,
-		'userconfig': client_null,
-	    }
-	# XXX: end un hardcode this
-
-
-	seer_out = False
-	client_out = False
-	for e in [ e for e in topo.elements \
-		if isinstance(e, topdl.Computer) and e.get_attribute('portal')]:
-	    myname = e.name[0]
-	    type = e.get_attribute('portal_type')
-
-	    info = conninfo_to_dict(myname, connInfo)
-
-	    if not info:
 		raise service_error(service_error.req,
-			"No connectivity info for %s" % myname)
-
-	    peer = info.get('peer', "")
-	    ldomain = self.domain;
-
-	    mexp = info.get('masterexperiment',"")
-	    mproj, meid = mexp.split("/", 1)
-	    mdomain = info.get('masterdomain',"")
-	    muser = info.get('masteruser','root')
-	    smbshare = info.get('smbshare', 'USERS')
-	    ssh_port = info.get('ssh_port', '22')
-
-	    active = info.get('active', 'False')
-
-	    cfn = "%s/%s.gw.conf" % (tmpdir, myname.lower())
-	    tunnelconfig = self.tunnel_config
-	    try:
-		f = open(cfn, "w")
-		if active == 'True':
-		    print >>f, "active: True"
-		    print >>f, "ssh_port: %s" % ssh_port
-		    if type in ('control', 'both'):
-			for s in [s for s in services \
-				if s.get('name', "") in self.imports]:
-			    p = urlparse(s.get('server', 'http://localhost'))
-			    print >>f, 'port: remote:%s:%s:%s' % \
-				    (p.port, p.hostname, p.port)
-
-		if tunnelconfig:
-		    print >>f, "tunnelip: %s" % tunnelconfig
-		# XXX: send this an fedattr
-		#print >>f, "seercontrol: control.%s.%s%s" % \
-			#(meid.lower(), mproj.lower(), mdomain)
-		print >>f, "peer: %s" % peer.lower()
-		print >>f, "ssh_pubkey: /usr/local/federation/etc/%s" % \
-			pubkey_base
-		print >>f, "ssh_privkey: /usr/local/federation/etc/%s" % \
-			secretkey_base
-		f.close()
-	    except EnvironmentError, e:
-		raise service_error(service_error.internal,
-			"Can't write protal config %s: %s" % (cfn, e))
-	    
-	    # XXX: This little seer config file needs to go away.
-	    if not seer_out:
-		try:
-		    seerfn = "%s/seer.conf" % tmpdir
-		    f = open(seerfn, "w")
-		    if not master:
-			print >>f, "ControlNode: control.%s.%s%s" % \
-			    (meid.lower(), mproj.lower(), mdomain)
-		    print >>f, "ExperimentID: %s" % mexp
-		    f.close()
-		except EnvironmentError, e:
-		    raise service_error(service_error.internal, 
-			    "Can't write seer.conf: %s" %e)
-		seer_out = True
-
-	    if not client_out and type in ('control', 'both'):
-		try:
-		    f = open("%s/client.conf" % tmpdir, "w")
-		    print >>f, "ControlGateway: %s%s" % \
-			(myname.lower(), ldomain.lower())
-		    for s in services:
-			if s.get('name',"") in self.imports and \
-				s.get('visibility','') == 'import':
-			    client_service_out[s['name']](f, s)
-		    # Does seer need this?
-		    # print >>f, "ExperimentID: %s/%s" % (mproj, meid)
-		    f.close()
-		except EnvironmentError, e:
-		    raise service_error(service_error.internal,
-			    "Cannot write client.conf: %s" %s)
-		client_out = True
-
-
-    def generate_rspec(self, topo, softdir, master, connInfo):
+			"Only localnames and fedids are understood")
+	except KeyError:
+	    raise service_error(service_error.req, "Badly formed request")
+
+	self.log.debug("[access] deallocation requested for %s", aid)
+	if not self.auth.check_attribute(fid, auth_attr):
+	    self.log.debug("[access] deallocation denied for %s", aid)
+	    raise service_error(service_error.access, "Access Denied")
+
+	self.state_lock.acquire()
+	if self.allocation.has_key(aid):
+	    self.log.debug("Found allocation for %s" %aid)
+	    del self.allocation[aid]
+	    self.write_state()
+	    self.state_lock.release()
+	    # And remove the access cert
+	    cf = "%s/%s.pem" % (self.certdir, aid)
+	    self.log.debug("Removing %s" % cf)
+	    os.remove(cf)
+	    return { 'allocID': req['allocID'] } 
+	else:
+	    self.state_lock.release()
+	    raise service_error(service_error.req, "No such allocation")
+
+    def generate_rspec(self, topo, softdir, connInfo):
 	t = topo.clone()
 
@@ -928,8 +360,131 @@
 	return exp_rspec
 
+    def retrieve_software(self, topo, certfile, softdir):
+	"""
+	Collect the software that nodes in the topology need loaded and stage
+	it locally.  This implies retrieving it from the experiment_controller
+	and placing it into softdir.  Certfile is used to prove that this node
+	has access to that data (it's the allocation/segment fedid).  Finally
+	local portal and federation software is also copied to the same staging
+	directory for simplicity - all software needed for experiment creation
+	is in softdir.
+	"""
+	sw = set()
+	for e in topo.elements:
+	    for s in getattr(e, 'software', []):
+		sw.add(s.location)
+	os.mkdir(softdir)
+	for s in sw:
+	    self.log.debug("Retrieving %s" % s)
+	    try:
+		get_url(s, certfile, softdir)
+	    except:
+		t, v, st = sys.exc_info()
+		raise service_error(service_error.internal,
+			"Error retrieving %s: %s" % (s, v))
+
+	# Copy local portal node software to the tempdir
+	for s in (self.portal_software, self.federation_software):
+	    for l, f in s:
+		base = os.path.basename(f)
+		copy_file(f, "%s/%s" % (softdir, base))
+
+	# Ick.  Put this python rpm in a place that it will get moved into
+	# the staging area.  It's a hack to install a modern (in a Roman
+	# sense of modern) python on ProtoGENI
+	python_rpm ="python2.4-2.4-1pydotorg.i586.rpm"
+	if os.access("./%s" % python_rpm, os.R_OK):
+	    copy_file("./%s" % python_rpm, "%s/%s" % (softdir, python_rpm))
+
+
+    def initialize_experiment_info(self, attrs, aid, certfile, tmpdir):
+	"""
+	Gather common configuration files, retrieve or create an experiment
+	name and project name, and return the ssh_key filenames.  Create an
+	allocation log bound to the state log variable as well. 
+	"""
+	configs = set(('hosts', 'ssh_pubkey', 'ssh_secretkey'))
+	ename = None
+	pubkey_base = None
+	secretkey_base = None
+	alloc_log = None
+
+	for a in attrs:
+	    if a['attribute'] in configs:
+		try:
+		    self.log.debug("Retrieving %s" % a['value'])
+		    get_url(a['value'], certfile, tmpdir)
+		except:
+		    t, v, st = sys.exc_info()
+		    raise service_error(service_error.internal,
+			    "Error retrieving %s: %s" % (a.get('value', ""), v))
+	    if a['attribute'] == 'ssh_pubkey':
+		pubkey_base = a['value'].rpartition('/')[2]
+	    if a['attribute'] == 'ssh_secretkey':
+		secretkey_base = a['value'].rpartition('/')[2]
+	    if a['attribute'] == 'experiment_name':
+		ename = a['value']
+
+	if not ename:
+	    ename = ""
+	    for i in range(0,5):
+		ename += random.choice(string.ascii_letters)
+	    self.log.warn("No experiment name: picked one randomly: %s" \
+		    % ename)
+
+	self.state_lock.acquire()
+	if self.allocation.has_key(aid):
+	    cf, user, ssh_key, cpw = self.allocation[aid]['credentials']
+	    self.allocation[aid]['experiment'] = ename
+	    self.allocation[aid]['log'] = [ ]
+	    # Create a logger that logs to the experiment's state object as
+	    # well as to the main log file.
+	    alloc_log = logging.getLogger('fedd.access.%s' % ename)
+	    h = logging.StreamHandler(
+		    list_log.list_log(self.allocation[aid]['log']))
+	    # XXX: there should be a global one of these rather than
+	    # repeating the code.
+	    h.setFormatter(logging.Formatter(
+		"%(asctime)s %(name)s %(message)s",
+			'%d %b %y %H:%M:%S'))
+	    alloc_log.addHandler(h)
+	    self.write_state()
+	else:
+	    self.log.error("No allocation for %s!?" % aid)
+	self.state_lock.release()
+
+	return (ename, pubkey_base, secretkey_base, cf, user, ssh_key, 
+		cpw, alloc_log)
+
+    def finalize_experiment(self, topo, starter, aid, alloc_id):
+	# Copy the assigned names into the return topology
+	rvtopo = topo.clone()
+	embedding = [ ]
+	for n in starter.node:
+	    embedding.append({ 
+		'toponame': n,
+		'physname': ["%s%s" %  (starter.node[n], self.domain)],
+		})
+	# Grab the log (this is some anal locking, but better safe than
+	# sorry)
+	self.state_lock.acquire()
+	logv = "".join(self.allocation[aid]['log'])
+	# It's possible that the StartSegment call gets retried (!). 
+	# if the 'started' key is in the allocation, we'll return it rather
+	# than redo the setup.
+	self.allocation[aid]['started'] = { 
+		'allocID': alloc_id,
+		'allocationLog': logv,
+		'segmentdescription': { 
+		    'topdldescription': rvtopo.to_dict() },
+		'embedding': embedding,
+		}
+	retval = copy.deepcopy(self.allocation[aid]['started'])
+	self.write_state()
+	self.state_lock.release()
+
+	return retval
+
     def StartSegment(self, req, fid):
-
-	configs = set(('hosts', 'ssh_pubkey', 'ssh_secretkey'))
-
 	err = None  # Any service_error generated after tmpdir is created
 	rv = None   # Return value from segment creation
@@ -937,4 +492,5 @@
 	try:
 	    req = req['StartSegmentRequestBody']
+	    topref = req['segmentdescription']['topdldescription']
 	except KeyError:
 	    raise service_error(service_error.req, "Badly formed request")
@@ -959,14 +515,9 @@
 		return retval
 
-
-	if req.has_key('segmentdescription') and \
-		req['segmentdescription'].has_key('topdldescription'):
-	    topo = \
-		topdl.Topology(**req['segmentdescription']['topdldescription'])
+	if topref:
+	    topo = topdl.Topology(**topref)
 	else:
 	    raise service_error(service_error.req, 
 		    "Request missing segmentdescription'")
-
-	master = req.get('master', False)
 
 	certfile = "%s/%s.pem" % (self.certdir, auth_attr)
@@ -979,116 +530,14 @@
 	# Try block alllows us to clean up temporary files.
 	try:
-	    sw = set()
-	    for e in topo.elements:
-		for s in getattr(e, 'software', []):
-		    sw.add(s.location)
-	    os.mkdir(softdir)
-	    for s in sw:
-		self.log.debug("Retrieving %s" % s)
-		try:
-		    get_url(s, certfile, softdir)
-		except:
-		    t, v, st = sys.exc_info()
-		    raise service_error(service_error.internal,
-			    "Error retrieving %s: %s" % (s, v))
-
-	    # Copy local portal node software to the tempdir
-	    for s in (self.portal_software, self.federation_software):
-		for l, f in s:
-		    base = os.path.basename(f)
-		    copy_file(f, "%s/%s" % (softdir, base))
-
-	    # Ick.  Put this python rpm in a place that it will get moved into
-	    # the staging area.  It's a hack to install a modern (in a Roman
-	    # sense of modern) python on ProtoGENI
-	    python_rpm ="python2.4-2.4-1pydotorg.i586.rpm"
-	    if os.access("./%s" % python_rpm, os.R_OK):
-		copy_file("./%s" % python_rpm, "%s/%s" % (softdir, python_rpm))
-
-	    for a in attrs:
-		if a['attribute'] in configs:
-		    try:
-			self.log.debug("Retrieving %s" % a['value'])
-			get_url(a['value'], certfile, tmpdir)
-		    except:
-			t, v, st = sys.exc_info()
-			raise service_error(service_error.internal,
-				"Error retrieving %s: %s" % (s, v))
-		if a['attribute'] == 'ssh_pubkey':
-		    pubkey_base = a['value'].rpartition('/')[2]
-		if a['attribute'] == 'ssh_secretkey':
-		    secretkey_base = a['value'].rpartition('/')[2]
-		if a['attribute'] == 'experiment_name':
-		    ename = a['value']
-
-	    # If the userconf service was imported, collect the configuration
-	    # data.
-	    for s in services:
-		if s.get("name", "") == 'userconfig' \
-			and s.get('visibility',"") == 'import':
-
-		    # Collect ther server and certificate info.
-		    u = s.get('server', None)
-		    for a in s.get('fedAttr', []):
-			if a.get('attribute',"") == 'cert':
-			    cert = a.get('value', None)
-			    break
-		    else:
-			cert = None
-
-		    if cert:
-			# Make a temporary certificate file for get_url.  The
-			# finally clause removes it whether something goes
-			# wrong (including an exception from get_url) or not.
-			try:
-			    tfos, tn = tempfile.mkstemp(suffix=".pem")
-			    tf = os.fdopen(tfos, 'w')
-			    print >>tf, cert
-			    tf.close()
-			    get_url(u, tn, tmpdir, "userconf")
-			except EnvironmentError, e:
-			    raise service_error(service.error.internal, 
-				    "Cannot create temp file for " + 
-				    "userconfig certificates: %s e")
-			except:
-			    t, v, st = sys.exc_info()
-			    raise service_error(service_error.internal,
-				    "Error retrieving %s: %s" % (u, v))
-			finally:
-			    if tn: os.remove(tn)
-		    else:
-			raise service_error(service_error.req,
-				"No certificate for retreiving userconfig")
-		    break
-
-	    self.state_lock.acquire()
-	    if self.allocation.has_key(aid):
-		cf, user, ssh_key, cpw = self.allocation[aid]['credentials']
-		self.allocation[aid]['experiment'] = ename
-		self.allocation[aid]['log'] = [ ]
-		# Create a logger that logs to the experiment's state object as
-		# well as to the main log file.
-		alloc_log = logging.getLogger('fedd.access.%s' % ename)
-		h = logging.StreamHandler(
-			list_log.list_log(self.allocation[aid]['log']))
-		# XXX: there should be a global one of these rather than
-		# repeating the code.
-		h.setFormatter(logging.Formatter(
-		    "%(asctime)s %(name)s %(message)s",
-			    '%d %b %y %H:%M:%S'))
-		alloc_log.addHandler(h)
-		self.write_state()
-	    else:
-		self.log.error("No allocation for %s!?" % aid)
-	    self.state_lock.release()
-
+	    self.retrieve_software(topo, certfile, softdir)
+	    self.configure_userconf(services, tmpdir)
+	    ename, pubkey_base, secretkey_base, cf, user, ssh_key, \
+		cpw, alloc_log = self.initialize_experiment_info(attrs,
+			aid, certfile, tmpdir)
 	    # XXX: we really need to put the import and connection info
 	    # generation off longer.
 	    self.import_store_info(certfile, connInfo)
-	    #self.generate_portal_configs(topo, pubkey_base, 
-		    #secretkey_base, tmpdir, master, ename, connInfo, 
-		    #services)
 	    rspec = self.generate_rspec(topo, "%s/%s/" \
-		    % (self.staging_dir, ename), master, connInfo)
+		    % (self.staging_dir, ename), connInfo)
 
 	    starter = self.start_segment(keyfile=ssh_key,
@@ -1097,58 +546,22 @@
 		    cm_url=self.cm_url)
 	    rv = starter(self, aid, user, rspec, pubkey_base, secretkey_base,
-		    master, ename,
+		    ename,
 		    "%s/%s" % (self.staging_dir, ename), tmpdir, cf, cpw,
 		    certfile, topo, connInfo, services)
-	    # Copy the assigned names into the return topology
-	    rvtopo = topo.clone()
-	    for e in rvtopo.elements:
-		if isinstance(e, topdl.Computer) and e.get_attribute('testbed'):
-		    myname = e.get_attribute('testbed')
-		    break
-	    else: myname = None
-
-	    embedding = [ ]
-	    for n in starter.node:
-		embedding.append({ 
-		    'toponame': n,
-		    'physname': ["%s%s" %  (starter.node[n], self.domain)],
-		    })
-
+	except EnvironmentError:
+	    err = service_error(service_error.internal, "%s" % e)
 	except service_error, e:
 	    err = e
-	except e:
-	    err = service_error(service_error.internal, str(e))
+	except:
+	    t, v, st = sys.exc_info()
+	    err = service_error(service_error.internal, "%s: %s" % \
+		    (v, traceback.extract_tb(st)))
 
         # Walk up tmpdir, deleting as we go
-	if self.cleanup:
-	    self.log.debug("[StartSegment]: removing %s" % tmpdir)
-	    for path, dirs, files in os.walk(tmpdir, topdown=False):
-		for f in files:
-		    os.remove(os.path.join(path, f))
-		for d in dirs:
-		    os.rmdir(os.path.join(path, d))
-	    os.rmdir(tmpdir)
-	else:
-	    self.log.debug("[StartSegment]: not removing %s" % tmpdir)
+	if self.cleanup: self.remove_dirs(tmpdir)
+	else: self.log.debug("[StartSegment]: not removing %s" % tmpdir)
 
 	if rv:
-	    # Grab the log (this is some anal locking, but better safe than
-	    # sorry)
-	    self.state_lock.acquire()
-	    logv = "".join(self.allocation[aid]['log'])
-	    # It's possible that the StartSegment call gets retried (!). 
-	    # if the 'started' key is in the allocation, we'll return it rather
-	    # than redo the setup.
-	    self.allocation[aid]['started'] = { 
-		    'allocID': req['allocID'],
-		    'allocationLog': logv,
-		    'segmentdescription': { 
-			'topdldescription': rvtopo.to_dict() },
-		    'embedding': embedding,
-		    }
-	    self.write_state()
-	    self.state_lock.release()
-
-	    return retval
+	    return self.finalize_experiment(topo, starter, aid, req['allocID'])
 	elif err:
 	    raise service_error(service_error.federant,
@@ -1207,4 +620,9 @@
 		scred = None
 	    self.state_lock.release()
+
+	    if not os.access(cf, os.R_OK):
+		self.log.error(
+			"[RenewSlices] cred.file %s unreadable, ignoring" % cf)
+		continue
 
 	    # There's a ProtoGENI slice associated with the segment; renew it.
