Index: fedd/federation/emulab_access.py
===================================================================
--- fedd/federation/emulab_access.py	(revision ef252e9986af22a54ce335935c60b0650531a9f5)
+++ fedd/federation/emulab_access.py	(revision 2761484680c5609adfc4d1acb82a927b3f339e07)
@@ -152,4 +152,6 @@
             }
 
+	self.call_SetValue = service_caller('SetValue')
+	self.call_GetValue = service_caller('GetValue')
 
         if not config.has_option("allocate", "uri"):
@@ -1199,4 +1201,73 @@
 		    "Cannot write experiment file %s: %s" % (expfn,e))
 
+    def export_store_info(self, cf, proj, ename, 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', '') != 'peer':
+		    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 and k.index('/') != -1:
+		    value = "%s.%s.%s%s" % \
+			    (k[k.index('/')+1:], ename, proj, self.domain)
+		    req = { 'name': k, 'value': value }
+		    self.call_SetValue(surl, req, cf)
+		else:
+		    self.log.error("Bad export request: %s" % p)
+
+    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': value})
+				    else:
+					c['fedAttr']= [{
+					    'attribute': name, 'value': value}]
+			    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 StartSegment(self, req, fid):
 	def get_url(url, cf, destdir, fn=None):
@@ -1251,5 +1322,5 @@
 	    raise service_error(service_error.req, 
 		    "Request missing segmentdescription'")
-
+	
 	master = req.get('master', False)
 
@@ -1353,4 +1424,7 @@
 			"Can't find creation user for %s" %aid)
 
+	    self.export_store_info(certfile, proj, ename, connInfo)
+	    self.import_store_info(certfile, connInfo)
+
 	    expfile = "%s/experiment.tcl" % tmpdir
 
Index: fedd/federation/experiment_control.py
===================================================================
--- fedd/federation/experiment_control.py	(revision ef252e9986af22a54ce335935c60b0650531a9f5)
+++ fedd/federation/experiment_control.py	(revision 2761484680c5609adfc4d1acb82a927b3f339e07)
@@ -28,4 +28,5 @@
 from remote_service import xmlrpc_handler, soap_handler, service_caller
 from service_error import service_error
+from synch_store import synch_store
 
 import topdl
@@ -239,4 +240,7 @@
         self.state_filename = config.get("experiment_control", 
                 "experiment_state")
+        self.store_filename = config.get("experiment_control", 
+                "synch_store")
+        self.store_url = config.get("experiment_control", "store_url")
         self.splitter_url = config.get("experiment_control", "splitter_uri")
         self.fedkit = parse_tarfile_list(\
@@ -332,4 +336,10 @@
             self.read_state()
 
+	if self.store_filename:
+	    self.read_store()
+	else:
+	    self.log.warning("No saved synch store")
+	    self.synch_store = synch_store
+
         # Dispatch tables
         self.soap_services = {\
@@ -342,4 +352,6 @@
                 'Terminate': soap_handler('Terminate', 
                     self.terminate_experiment),
+		'GetValue': soap_handler('GetValue', self.GetValue),
+		'SetValue': soap_handler('SetValue', self.SetValue),
         }
 
@@ -353,4 +365,6 @@
                 'Terminate': xmlrpc_handler('Terminate',
                     self.terminate_experiment),
+		'GetValue': xmlrpc_handler('GetValue', self.GetValue),
+		'SetValue': xmlrpc_handler('SetValue', self.SetValue),
         }
 
@@ -377,4 +391,18 @@
             self.log.error("Pickling problem (TypeError): %s" % e)
 
+    @staticmethod
+    def get_alloc_ids(state):
+	"""
+	Pull the fedids of the identifiers of each allocation from the
+	state.  Again, a dict dive that's best isolated.
+
+	Used by read_store and read state
+	"""
+
+	return [ f['allocID']['fedid'] 
+		for f in state.get('federant',[]) \
+		    if f.has_key('allocID') and \
+			f['allocID'].has_key('fedid')]
+
     # Call while holding self.state_lock
     def read_state(self):
@@ -399,16 +427,4 @@
 	    else:
 		return None
-
-	def get_alloc_ids(state):
-	    """
-	    Pull the fedids of the identifiers of each allocation from the
-	    state.  Again, a dict dive that's best isolated.
-	    """
-
-	    return [ f['allocID']['fedid'] 
-		    for f in state.get('federant',[]) \
-			if f.has_key('allocID') and \
-			    f['allocID'].has_key('fedid')]
-
 
         try:
@@ -438,5 +454,5 @@
 		    # Set permissions to allow reading of the software repo, if
 		    # any, as well.
-		    for a in get_alloc_ids(s):
+		    for a in self.get_alloc_ids(s):
 			self.auth.set_attribute(a, 'repo/%s' % eid)
 		else:
@@ -529,4 +545,48 @@
                     "open %s: %s" % (file, e))
         f.close()
+
+    def read_store(self):
+        try:
+	    self.synch_store = synch_store()
+	    self.synch_store.load(self.store_filename)
+	    self.log.debug("[read_store]: Read store from %s" % \
+                    self.store_filename)
+        except IOError, e:
+	    self.log.warning("[read_store]: No saved store: Can't open %s: %s"\
+                    % (self.state_filename, e))
+	    self.synch_store = synch_store()
+
+	# Set the initial permissions on data in the store.  XXX: This ad hoc
+	# authorization attribute initialization is getting out of hand.
+	for k in self.synch_store.all_keys():
+	    try:
+		if k.startswith('fedid:'):
+		    fid = fedid(hexstr=k[6:46])
+		    if self.state.has_key(fid):
+			for a in self.get_alloc_ids(self.state[fid]):
+			    self.auth.set_attribute(a, k)
+	    except ValueError, e:
+		self.log.warn("Cannot deduce permissions for %s" % k)
+
+
+    def write_store(self):
+        """
+        Write a new copy of synch_store after writing current state
+	to a backup.  We use the internal synch_store pickle method to avoid
+	incinsistent data.
+
+        State format is a simple pickling of the store.
+        """
+        if os.access(self.store_filename, os.W_OK):
+            copy_file(self.store_filename, \
+		    "%s.bak" % self.store_filename)
+        try:
+	    self.synch_store.save(self.store_filename)
+        except IOError, e:
+            self.log.error("Can't write file %s: %s" % \
+                    (self.store_filename, e))
+        except TypeError, e:
+            self.log.error("Pickling problem (TypeError): %s" % e)
+
         
     def generate_ssh_keys(self, dest, type="rsa" ):
@@ -1324,5 +1384,6 @@
 
     def new_portal_node(self, st, dt, tbparams, master, eid, myname, desthost,
-	    portal_type, iface_desc=(), conn_type="ssh", conn_attrs=[]):
+	    portal_type, iface_desc=(), conn_type="ssh", conn_attrs=[], 
+	    expid=None):
 	"""
 	Return a new internet portal node and a dict with the connectionInfo to
@@ -1355,8 +1416,12 @@
 	    ifaces.append(inf)
 	if conn_type == "ssh":
+	    try:
+		aid = tbparams[st]['allocID']['fedid']
+	    except:
+		self.log.debug("Can't get allocation id?")
+		aid = None
 	    info = {
 		    "type" : conn_type, 
 		    "portal": myname,
-		    'peer': desthost,
 		    'fedAttr': [ 
 			    { 'attribute': 'masterdomain', 'value': mdomain},
@@ -1368,5 +1433,26 @@
 			    { 'attribute': 'smbshare', 'value': smbshare},
 			],
+		    'parameter': [
+			{
+			    'name': 'peer',
+			    'key': 'fedid:%s/%s' % (expid, myname),
+			    'store': self.store_url,
+			    'type': 'output',
+			},
+			{
+			    'name': 'peer',
+			    'key': 'fedid:%s/%s' % (expid, desthost),
+			    'store': self.store_url,
+			    'type': 'input',
+			},
+			]
 		    }
+	    # Give this allocation the rights to access the key of the
+	    # peers
+	    if aid:
+		for h in (myname, desthost):
+		    self.auth.set_attribute(aid, 'fedid:%s/%s' % (expid, h))
+	    else:
+		self.log.error("No aid for %s in new_portal_node" % st)
 	else:
 	    info = None
@@ -1384,5 +1470,5 @@
 		), info)
 
-    def new_portal_substrate(self, st, dt, eid, tbparams):
+    def new_portal_substrate(self, st, dt, eid, tbparams, expid):
 	ddomain = tbparams[dt].get('domain', ".example.com")
 	dproject = tbparams[dt].get('project', 'project')
@@ -1414,5 +1500,5 @@
 	return (tsubstrate, segment_element)
 
-    def new_dragon_topo(self, idx, sub, topo, tbs, tbparams):
+    def new_dragon_topo(self, idx, sub, topo, tbs, tbparams, connInfo, expid):
 	if sub.capacity is None:
 	    raise service_error(service_error.internal,
@@ -1424,4 +1510,5 @@
 		    for n, v, in (\
 			    ('vlan', 'unassigned%d' % idx),)])
+	name = "dragon%d" % idx
 	for tb in tbs.keys():
 	    seg = topdl.Segment(
@@ -1442,5 +1529,25 @@
 	    segs.append(seg)
 
-	topo["dragon%d" %idx] = \
+
+	try:
+	    aid = tbparams[tb]['allocID']['fedid']
+	except:
+	    self.log.debug("Can't get allocation id?")
+	    aid = None
+	connInfo[name] = [ { 
+	    'parameter': [ { 
+		'name': 'vlan_id',
+		'key': "fedid:%s/vlan%d" % (expid, idx),
+		'store': self.store_url,
+		'type': 'output'
+		} ]
+	    } ]
+
+	# Give this allocation the rights to access the key of the
+	# vlan_id
+	if aid:
+	    self.auth.set_attribute(aid, 'fedid:%s/vlan%d' % (expid, idx))
+
+	topo[name] = \
 		topdl.Topology(substrates=[substr], elements=segs,
 			attribute=[
@@ -1452,5 +1559,5 @@
 
     def create_dragon_substrate(self, sub, topo, tbs, tbparams, master, eid,
-	    connInfo, peer={ }):
+	    connInfo, peer={ }, expid=None):
 	"""
 	Add attribiutes to the various elements indicating that they are to be
@@ -1486,4 +1593,10 @@
 		if not connInfo.has_key(tb):
 		    connInfo[tb] = [ ]
+
+		try:
+		    aid = tbparams[tb]['allocID']['fedid']
+		except:
+		    self.log.debug("Can't get allocation id?")
+		    aid = None
 
 		# This may need another look, but only a service gateway will
@@ -1506,8 +1619,20 @@
 			    { 'attribute': 'active', 'value': active},
 			    ],
+			'parameter': [ {
+			    'name': 'vlan_id',
+			    'key': 'fedid:%s/vlan%d' % (expid, dn),
+			    'store': self.store_url,
+			    'type': 'input',
+			    } ]
 			}
 		if peer.has_key(tb):
 		    info['peer'] = peer[tb]
 		connInfo[tb].append(info)
+
+		# Give this allocation the rights to access the key of the
+		# vlan_id
+		if aid:
+		    self.auth.set_attribute(aid, 
+			    'fedid:%s/vlan%d' % (expid, dn))
 	    else:
 		raise service_error(service_error.internal,
@@ -1517,5 +1642,5 @@
 
     def insert_internet_portals(self, sub, topo, tbs, tbparams, master, eid,
-	    segment_substrate, portals, connInfo):
+	    segment_substrate, portals, connInfo, expid):
 	# More than one testbed is on this substrate.  Insert
 	# some portals into the subtopologies.  st == source testbed,
@@ -1546,5 +1671,6 @@
 		    # testbed in there.
 		    tsubstrate, segment_element = \
-			    self.new_portal_substrate(st, dt, eid, tbparams)
+			    self.new_portal_substrate(st, dt, eid, tbparams,
+				    expid)
 		    segment_substrate[st][dt] = tsubstrate
 		    topo[st].substrates.append(tsubstrate)
@@ -1567,7 +1693,6 @@
 			portal_type = "experiment"
 			myname = "%stunnel%d" % (dt, len(portals[st][dt]))
-			desthost = "%stunnel%d.%s.%s%s" % (st, 
-				len(portals[st][dt]), eid.lower(),
-				dproject.lower(), ddomain.lower())
+			desthost = "%stunnel%d" % (st.lower(), 
+				len(portals[st][dt]))
 		    else:
 			new_i = topdl.Interface(
@@ -1586,7 +1711,5 @@
 		    portals[st][dt] = [ ]
 		    myname = "%stunnel%d" % (dt, len(portals[st][dt]))
-		    desthost = "%stunnel%d.%s.%s%s" % (st.lower(), 
-			    len(portals[st][dt]), eid.lower(), 
-			    dproject.lower(), ddomain.lower())
+		    desthost = "%stunnel%d" % (st.lower(), len(portals[st][dt]))
 
 		    if dt == master or st == master: portal_type = "both"
@@ -1602,5 +1725,5 @@
 		    portal, info  =  self.new_portal_node(st, dt, tbparams, 
 			    master, eid, myname, desthost, portal_type,
-			    infs)
+			    infs, conn_type="ssh", conn_attrs=[], expid=expid)
 		    if self.fedkit:
 			self.add_kit(portal, self.fedkit)
@@ -1612,8 +1735,8 @@
 		    connInfo[st].append(info)
 
-    def add_control_portal(self, st, dt, master, eid, topo, tbparams, connInfo):
+    def add_control_portal(self, st, dt, master, eid, topo, tbparams, connInfo, expid):
 	# Add to the master testbed
 	tsubstrate, segment_element = \
-		self.new_portal_substrate(st, dt, eid, tbparams)
+		self.new_portal_substrate(st, dt, eid, tbparams, expid)
 	myname = "%stunnel" % dt
 	desthost = "%stunnel" % st
@@ -1621,5 +1744,6 @@
 	portal, info = self.new_portal_node(st, dt, tbparams, master,
 		eid, myname, desthost, "control", 
-		((tsubstrate.name,(('portal','true'),)),))
+		((tsubstrate.name,(('portal','true'),)),), conn_type="ssh",
+		conn_attrs=[], expid=expid)
 	if self.fedkit:
 	    self.add_kit(portal, self.fedkit)
@@ -1635,5 +1759,5 @@
 
     def new_dragon_portal(self, st, dt, master, eid, myip, dip, idx, 
-	    substrate, tbparams):
+	    substrate, tbparams, expid):
 	# Add to the master testbed
 	myname = "%stunnel" % dt
@@ -1645,5 +1769,5 @@
 		    ('portal','true'),
 		    ('ip4_address', "%s" % ip_addr(myip)),)),),
-		conn_type="transit")
+		conn_type="transit", conn_attrs=[], expid=expid)
 	if self.fedkit:
 	    self.add_kit(portal, self.fedkit)
@@ -1654,5 +1778,5 @@
 
     def add_portals(self, top, topo, eid, master, tbparams, ip_allocator, 
-	    connInfo):
+	    connInfo, expid):
 	"""
 	For each substrate in the main topology, find those that
@@ -1680,8 +1804,8 @@
 		    all([tbparams[x].has_key('dragon') for x in tbs]):
 		self.create_dragon_substrate(s, topo, tbs, tbparams, 
-			master, eid, connInfo)
+			master, eid, connInfo, expid)
 	    else:
 		self.insert_internet_portals(s, topo, tbs, tbparams, master,
-			eid, segment_substrate, portals, connInfo)
+			eid, segment_substrate, portals, connInfo, expid)
 
 	# Make sure that all the slaves have a control portal back to the
@@ -1733,5 +1857,5 @@
 			    )
 		    portal = self.new_dragon_portal(tb, master,
-			    master, eid, dip, mip, idx, csub, tbparams)
+			    master, eid, dip, mip, idx, csub, tbparams, expid)
 		    topo[tb].substrates.append(csub)
 		    topo[tb].elements.append(portal)
@@ -1760,5 +1884,5 @@
 			    )
 		    portal = self.new_dragon_portal(master, tb, master,
-			    eid, mip, dip, idx, mcsub, tbparams)
+			    eid, mip, dip, idx, mcsub, tbparams, expid)
 		    topo[master].substrates.append(mcsub)
 		    topo[master].elements.append(portal)
@@ -1769,10 +1893,10 @@
 		    self.create_dragon_substrate(csub, topo, 
 			    {tb: 1, master:1}, tbparams, master, eid, connInfo,
-			    {tb: ip_addr(mip), master: ip_addr(dip)})
+			    {tb: ip_addr(mip), master: ip_addr(dip)}, expid)
 		else:
 		    self.add_control_portal(master, tb, master, eid, topo, 
-			    tbparams, connInfo)
+			    tbparams, connInfo, expid)
 		    self.add_control_portal(tb, master, master, eid, topo, 
-			    tbparams, connInfo)
+			    tbparams, connInfo, expid)
 
 	# Connect the portal nodes into the topologies and clear out
@@ -2103,5 +2227,5 @@
 
 	    self.add_portals(top, topo, eid, master, tbparams, ip_allocator,
-		    connInfo)
+		    connInfo, expid)
 	    # Now get access to the dynamic testbeds
 	    for k, t in topo.items():
@@ -2585,4 +2709,16 @@
             self.state_lock.release()
 
+	    # Delete any synch points associated with this experiment.  All
+	    # synch points begin with the fedid of the experiment.
+	    fedid_keys = set(["fedid:%s" % f for f in ids \
+		    if isinstance(f, fedid)])
+	    for k in self.synch_store.all_keys():
+		try:
+		    if len(k) > 45 and k[0:46] in fedid_keys:
+			self.synch_store.del_value(k)
+		except synch_store.BadDeleteionError:
+		    pass
+	    self.write_store()
+		
             return { 
                     'experiment': exp , 
@@ -2593,2 +2729,49 @@
             self.state_lock.release()
             raise service_error(service_error.req, "No saved state")
+
+
+    def GetValue(self, req, fid):
+	"""
+	Get a value from the synchronized store
+	"""
+        req = req.get('GetValueRequestBody', None)
+        if not req:
+            raise service_error(service_error.req,
+                    "Bad request format (no GetValueRequestBody)")
+	
+	name = req['name']
+	wait = req['wait']
+	rv = { 'name': name }
+
+        if self.auth.check_attribute(fid, name):
+	    v = self.synch_store.get_value(name, wait)
+	    if v is not None:
+		rv['value'] = v
+	    return rv
+        else:
+            raise service_error(service_error.access, "Access Denied")
+	
+
+    def SetValue(self, req, fid):
+	"""
+	Set a value in the synchronized store
+	"""
+        req = req.get('SetValueRequestBody', None)
+        if not req:
+            raise service_error(service_error.req,
+                    "Bad request format (no SetValueRequestBody)")
+	
+	name = req['name']
+	v = req['value']
+
+        if self.auth.check_attribute(fid, name):
+	    try:
+		self.synch_store.set_value(name, v)
+		self.write_store()
+	    except synch_store.CollisionError:
+		# Translate into a service_error
+		raise service_error(service_error.req,
+			"Value already set: %s" %name)
+	    return { 'name': name, 'value': v }
+        else:
+            raise service_error(service_error.access, "Access Denied")
