Index: fedd/Makefile/federation/deter_internal_access.py
===================================================================
--- fedd/Makefile/federation/deter_internal_access.py	(revision 88e7f2f76ed0f9ce0f3042e8d7ae0c319b72036b)
+++ fedd/Makefile/federation/deter_internal_access.py	(revision 88e7f2f76ed0f9ce0f3042e8d7ae0c319b72036b)
@@ -0,0 +1,366 @@
+#!/usr/local/bin/python
+
+import os,sys
+import re
+import string
+import copy
+import pickle
+import logging
+import time
+
+from threading import Thread, Lock
+from subprocess import Popen, call, PIPE, STDOUT
+
+from util import *
+from allocate_project import allocate_project_local, allocate_project_remote
+from access_project import access_project
+from fedid import fedid, generate_fedid
+from authorizer import authorizer
+from service_error import service_error
+from remote_service import xmlrpc_handler, soap_handler, service_caller
+
+import httplib
+import tempfile
+from urlparse import urlparse
+
+import topdl
+import list_log
+import proxy_emulab_segment
+import local_emulab_segment
+
+from access import access_base
+
+# Make log messages disappear if noone configures a fedd logger
+class nullHandler(logging.Handler):
+    def emit(self, record): pass
+
+fl = logging.getLogger("fedd.access")
+fl.addHandler(nullHandler())
+
+class access(access_base):
+    @staticmethod
+    def parse_vlans(v, log=None):
+	"""
+	Parse a vlan parameter into a set of vlan ids.  Comma separated
+	sequences of vlan ranges are acceptable.
+	"""
+	# the vlans can be a single integer, a comma separated list or a
+	# comma separated lists of dashed ranges.  E.g 100 or 100,300 or
+	# 100,300-305,400
+	vset = set()
+	if v:
+	    for v in [ x.strip() for x in v.split(",") ]:
+		try:
+		    if v.count("-") == 1:
+			f, t = v.split("-", 1)
+			for i in range(int(f), int(t)+1):
+			    vset.add(i)
+		    else:
+			vset.add(int(v))
+		except ValueError:
+		    if log:
+			log.warn("Invalid expression in vlan list: %s" % v)
+	return vset
+
+    def __init__(self, config=None, auth=None):
+        """
+        Initializer.  Pulls parameters out of the ConfigParser's access section.
+        """
+
+	access_base.__init__(self, config, auth)
+	self.domain = config.get("access", "domain")
+	vlan_str = config.get("access", "vlans")
+	self.vlans = self.parse_vlans(vlan_str)
+
+        self.attrs = { }
+        self.access = { }
+	# State is a dict of dicts indexed by segment fedid that includes the
+	# owners of the segment as fedids (who can manipulate it, key: owners),
+	# the repo dir/user for the allocation (key: user),  Current allocation
+	# log (key: log), and GRI of the reservation once made (key: gri)
+        self.log = logging.getLogger("fedd.access")
+        set_log_level(config, "access", self.log)
+
+        if config.has_option("access", "accessdb"):
+            self.read_access(config.get("access", "accessdb"))
+
+	# 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.
+	self.state_lock.acquire()
+	for k in self.state.keys():
+	    for o in self.state[k].get('owners', []):
+		self.auth.set_attribute(o, fedid(hexstr=k))
+	    self.auth.set_attribute(fedid(hexstr=k),fedid(hexstr=k))
+	    # If the allocation has a vlan assigned, remove it from the
+	    # available vlans
+	    v = self.state[k].get('vlan', None)
+	    if v:
+		self.vlans.discard(v)
+	self.state_lock.release()
+
+	self.lookup_access = self.lookup_access_base
+
+	self.call_GetValue= service_caller('GetValue')
+	self.call_SetValue= service_caller('SetValue')
+
+        self.soap_services = {\
+            'RequestAccess': soap_handler("RequestAccess", self.RequestAccess),
+            'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess),
+            'StartSegment': soap_handler("StartSegment", self.StartSegment),
+            'TerminateSegment': soap_handler("TerminateSegment", 
+		self.TerminateSegment),
+            }
+        self.xmlrpc_services =  {\
+            'RequestAccess': xmlrpc_handler('RequestAccess',
+                self.RequestAccess),
+            'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
+                self.ReleaseAccess),
+            'StartSegment': xmlrpc_handler("StartSegment", self.StartSegment),
+            'TerminateSegment': xmlrpc_handler('TerminateSegment',
+                self.TerminateSegment),
+            }
+
+    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.
+        """
+
+        # The dance to get into the request body
+        if req.has_key('RequestAccessRequestBody'):
+            req = req['RequestAccessRequestBody']
+        else:
+            raise service_error(service_error.req, "No request!?")
+
+	found, match = 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.state[aid] = { }
+	self.state[aid]['user'] = found
+	self.state[aid]['owners'] = [ fid ]
+	self.state[aid]['vlan'] = None
+	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 { 'allocID': { 'fedid': allocID } }
+
+    def ReleaseAccess(self, req, fid):
+        # The dance to get into the request body
+        if req.has_key('ReleaseAccessRequestBody'):
+            req = req['ReleaseAccessRequestBody']
+        else:
+            raise service_error(service_error.req, "No request!?")
+
+	# 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.state.has_key(aid):
+	    self.log.debug("Found allocation for %s" %aid)
+	    del self.state[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 extract_parameters(self, top):
+	"""
+	DETER's internal networking currently supports a fixed capacity link
+	between two endpoints.  Those endpoints may specify a VPN (or list or
+	range) to use.  This extracts the and vpn preferences from the segments
+	(as attributes) and the capacity of the connection as given by the
+	substrate.  The two endpoints VLAN choices are intersected to get set
+	of VLANs that are acceptable (no VLAN requiremnets means any is
+	acceptable).
+	"""
+	segments = filter(lambda x: isinstance(x, topdl.Segment), top.elements)
+
+	if len(segments) != 2 or len(top.substrates) != 1:
+	    raise service_error(service_error.req,
+		    "Requests to DRAGON must have exactlty two segments " +\
+			    "and one substrate")
+
+	vlans = set()
+	for s in segments:
+	    v = s.get_attribute('vlans')
+	    vlans &= self.parse_vlans(v)
+
+	if len(vlans) == 0:
+	    vlans = None
+
+	sub = top.substrates[0]
+	if sub.capacity:
+	    cap = int(sub.capacity.rate / 1000.0)
+	    if cap < 1: cap = 1
+	else:
+	    cap = 100
+
+	return cap, vlans
+
+    def export_store_info(self, cf, vlan, connInfo):
+	"""
+	For the export requests in the connection info, install the peer names
+	at the experiment controller via SetValue calls.
+	"""
+
+	for c in connInfo:
+	    for p in [ p for p in c.get('parameter', []) \
+		    if p.get('type', '') == 'output']:
+
+		if p.get('name', '') != 'vlan_id':
+		    self.log.error("Unknown export parameter: %s" % \
+			    p.get('name'))
+		    continue
+
+		k = p.get('key', None)
+		surl = p.get('store', None)
+		if surl and k:
+		    value = "%s" % vlan
+		    req = { 'name': k, 'value': value }
+		    self.call_SetValue(surl, req, cf)
+		else:
+		    self.log.error("Bad export request: %s" % p)
+
+    def StartSegment(self, req, fid):
+	err = None  # Any service_error generated after tmpdir is created
+	rv = None   # Return value from segment creation
+
+	try:
+	    req = req['StartSegmentRequestBody']
+	    topref = req['segmentdescription']['topdldescription']
+	except KeyError:
+	    raise service_error(server_error.req, "Badly formed request")
+
+	auth_attr = req['allocID']['fedid']
+	aid = "%s" % auth_attr
+	attrs = req.get('fedAttr', [])
+	if not self.auth.check_attribute(fid, auth_attr):
+	    raise service_error(service_error.access, "Access denied")
+	else:
+	    # See if this is a replay of an earlier succeeded StartSegment -
+	    # sometimes SSL kills 'em.  If so, replay the response rather than
+	    # redoing the allocation.
+	    self.state_lock.acquire()
+	    retval = self.state[aid].get('started', None)
+	    self.state_lock.release()
+	    if retval:
+		self.log.warning("Duplicate StartSegment for %s: " % aid + \
+			"replaying response")
+		return retval
+
+	certfile = "%s/%s.pem" % (self.certdir, aid)
+
+	if topref: topo = topdl.Topology(**topref)
+	else:
+	    raise service_error(service_error.req, 
+		    "Request missing segmentdescription'")
+
+	connInfo = req.get('connection', [])
+
+	cap, vlans = self.extract_parameters(topo)
+
+	# No vlans passes in, consider any vlan acceptable
+	if not vlans: 
+	    vlans = self.vlans
+
+	avail = self.vlans | vlans
+
+	if len(avail) != 0:
+	    vlan_no = avail.pop()
+	    self.vlans.discard(vlan_no)
+	else:
+	    raise service_error(service_error.federant, "No vlan available")
+
+	self.export_store_info(certfile, vlan_no, connInfo)
+
+
+	# This annotation isn't strictly necessary, but may help in debugging
+	rtopo = topo.clone()
+	for s in rtopo.substrates:
+	    s.set_attribute('vlan', vlan_no)
+
+	# Grab the log (this is some anal locking, but better safe than
+	# sorry)
+	self.state_lock.acquire()
+	self.state[aid]['vlan'] = vlan_no
+	logv = "Allocated vlan: %d" % vlan_no
+	# 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.state[aid]['started'] = { 
+		'allocID': req['allocID'],
+		'allocationLog': logv,
+		'segmentdescription': { 'topdldescription': rtopo.to_dict() }
+		}
+	retval = copy.deepcopy(self.state[aid]['started'])
+	self.write_state()
+	self.state_lock.release()
+
+	return retval
+
+    def TerminateSegment(self, req, fid):
+	try:
+	    req = req['TerminateSegmentRequestBody']
+	except KeyError:
+	    raise service_error(server_error.req, "Badly formed request")
+
+	auth_attr = req['allocID']['fedid']
+	aid = "%s" % auth_attr
+
+	self.log.debug("Terminate request for %s" %aid)
+	if not self.auth.check_attribute(fid, auth_attr):
+	    raise service_error(service_error.access, "Access denied")
+
+	self.state_lock.acquire()
+	if self.state.has_key(aid):
+	    vlan_no = self.state[aid].get('vlan', None)
+	else:
+	    vlan_no = None
+	self.state_lock.release()
+	self.log.debug("Stop segment for vlan: %s" % vlan_no)
+
+	if not vlan_no:
+	    raise service_error(service_error.internal, 
+		    "Can't find assigfned vlan for for %s" % aid)
+   
+	self.vlans.add(vlan_no)
+	self.state_lock.acquire()
+	self.state[aid]['vlan'] = None
+	self.state_lock.release()
+	return { 'allocID': req['allocID'] }
