SEBA-89 Finish sync steps, add unit tests

Change-Id: I39495e6167fceffa4c86ad5e6c4d238271ecbcc7
diff --git a/xos/synchronizer/steps/sync_fabric_crossconnect_service_instance.py b/xos/synchronizer/steps/sync_fabric_crossconnect_service_instance.py
index 8f03075..46a4694 100644
--- a/xos/synchronizer/steps/sync_fabric_crossconnect_service_instance.py
+++ b/xos/synchronizer/steps/sync_fabric_crossconnect_service_instance.py
@@ -13,7 +13,7 @@
 # limitations under the License.
 
 from synchronizers.new_base.syncstep import SyncStep, DeferredException
-from synchronizers.new_base.modelaccessor import model_accessor, FabricCrossconnectServiceInstance, ServiceInstance
+from synchronizers.new_base.modelaccessor import model_accessor, FabricCrossconnectServiceInstance, ServiceInstance, BNGPortMapping
 
 from xosconfig import Config
 from multistructlog import create_logger
@@ -55,9 +55,16 @@
             'pass': fabric_onos.rest_password
         }
 
-    def get_bng_port(self, o):
-        # TODO(smbaker): Get BNG port from somewhere
-        return 2
+    def make_handle(self, s_tag, west_dpid):
+        # Generate a backend_handle that uniquely identifies the cross connect. ONOS doesn't provide us a handle, so
+        # we make up our own. This helps us to detect other FabricCrossconnectServiceInstance using the same
+        # entry, as well as to be able to extract the necessary information to delete the entry later.
+        return "%d/%s" % (s_tag, west_dpid)
+
+    def extract_handle(self, backend_handle):
+        (s_tag, dpid) = backend_handle.split("/",1)
+        s_tag = int(s_tag)
+        return (s_tag, dpid)
 
     def sync_record(self, o):
         self.log.info("Sync'ing Fabric Crossconnect Service Instance", service_instance=o)
@@ -67,15 +74,19 @@
         si = ServiceInstance.objects.get(id=o.id)
 
         s_tag = si.get_westbound_service_instance_properties("s_tag")
-        west_dpid = si.get_westbound_service_instance_properties("switch_datapath_id")
+        dpid = si.get_westbound_service_instance_properties("switch_datapath_id")
         west_port = si.get_westbound_service_instance_properties("switch_port")
-        east_port = self.get_bng_port(o)
 
-        data = { "deviceId": west_dpid,
+        bng_mappings = BNGPortMapping.objects.filter(s_tag = s_tag)
+        if not bng_mappings:
+            raise Exception("Unable to determine BNG port for s_tag %s" % s_tag)
+        east_port = bng_mappings[0].switch_port
+
+        data = { "deviceId": dpid,
                  "vlanId": s_tag,
                  "ports": [ west_port, east_port ] }
 
-        url = onos['url'] + '/onos/segmentsrouting/xconnect'
+        url = onos['url'] + '/onos/segmentrouting/xconnect'
 
         self.log.info("Sending request to ONOS", url=url, body=data)
 
@@ -84,25 +95,29 @@
         if r.status_code != 200:
             raise Exception("Failed to create fabric crossconnect in ONOS: %s" % r.text)
 
+        o.backend_handle = self.make_handle(s_tag, dpid)
+        o.save(update_fields=["backend_handle"])
+
         self.log.info("ONOS response", res=r.text)
 
     def delete_record(self, o):
         self.log.info("Deleting Fabric Crossconnect Service Instance", service_instance=o)
 
-        # TODO(smbaker): make sure it's not in use by some other subscriber
-
-        if o.enacted:
+        if o.backend_handle:
             onos = self.get_fabric_onos_info(o)
 
-            si = ServiceInstance.objects.get(id=o.id)
+            # If some other subscriber is using the same entry, then we shouldn't delete it
+            other_subscribers = FabricCrossconnectServiceInstance.objects.filter(backend_handle=o.backend_handle)
+            other_subscribers = [x for x in other_subscribers if x.id != o.id]
+            if other_subscribers:
+                self.log.info("Other subscribers exist using same fabric crossconnect entry. Not deleting.")
+                return
 
-            # TODO(smbaker): unable to get westbound service_instance while deleting?
+            # backend_handle has everything we need in it to delete this entry.
+            (s_tag, dpid) = self.extract_handle(o.backend_handle)
 
-            s_tag = si.get_westbound_service_instance_properties("s_tag")
-            west_dpid = si.get_westbound_service_instance_properties("switch_datapath_id")
-
-            data = { "deviceId": west_dpid,
-                     "vlanID": s_tag }
+            data = { "deviceId": dpid,
+                     "vlanId": s_tag }
 
             url = onos['url'] + '/onos/segmentrouting/xconnect'