[SEBA-179] Fabric-crossconnect service should react to changes in BNGPortMapping

Change-Id: Ie7174ac31af300dabb50e0d850fff0c474e5366a
diff --git a/xos/synchronizer/steps/sync_bng_port_mapping.py b/xos/synchronizer/steps/sync_bng_port_mapping.py
new file mode 100644
index 0000000..c704e6d
--- /dev/null
+++ b/xos/synchronizer/steps/sync_bng_port_mapping.py
@@ -0,0 +1,125 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os, sys
+import json
+from xossynchronizer.steps.syncstep import SyncStep, DeferredException
+from xossynchronizer.modelaccessor import model_accessor, FabricCrossconnectServiceInstance, ServiceInstance, BNGPortMapping
+
+import requests
+from requests import ConnectionError
+from requests.auth import HTTPBasicAuth
+from requests.models import InvalidURL
+
+from xosconfig import Config
+from multistructlog import create_logger
+
+from helpers import Helpers
+log = create_logger(Config().get('logging'))
+
+
+class SyncBNGPortMapping(SyncStep):
+    provides = [BNGPortMapping]
+    observes = BNGPortMapping
+
+    def remove_crossconnect(self, fcsis):
+        for fcsi in fcsis:
+            onos = Helpers.get_fabric_onos_info(self.model_accessor, fcsi.owner)
+
+            url = onos['url'] + '/onos/segmentrouting/xconnect'
+            data = {"deviceId": fcsi.switch_datapath_id,
+                    "vlanId": fcsi.s_tag}
+            log.info("Sending request to ONOS", url=url)
+            r = requests.delete(url, json=data, auth=HTTPBasicAuth(onos['user'], onos['pass']))
+            if r.status_code != 204:
+                raise Exception("Failed to remove fabric crossconnect in ONOS: %s" % r.text)
+            fcsi.save(always_update_timestamp = True)
+
+    def find_crossconnect(self, bng_s_tag):
+        if(bng_s_tag.isnumeric()):
+            xconnect_si = self.model_accessor.FabricCrossconnectServiceInstance.objects.filter(s_tag=int(bng_s_tag))
+            if xconnect_si:
+                log.info("Crossconnects belonging having s-tag equal to s-tags: %s" % xconnect_si)
+                return xconnect_si
+        else:
+            [fcsis_range, fcsis_any] = [[], []]
+            for fcsi in self.model_accessor.FabricCrossconnectServiceInstance.objects.all():
+                if Helpers.range_matches(fcsi.s_tag, bng_s_tag):
+                    fcsis_range.append(fcsi)
+                else:
+                    fcsis_any.append(fcsi)
+            if fcsis_range:
+                log.info("Crossconnects belonging to bng range s-tags: %s" % fcsis_range)
+                return fcsis_range
+            else:
+                log.info("Crossconnects belonging to bng any s-tags: %s" % fcsis_any)
+                return fcsis_any
+
+    def check_switch_port_change(self, model):
+        fcsis = self.find_crossconnect(model.s_tag)
+        isChanged = False
+        remove_xconnect = []
+        if fcsis:
+            for fcsi in fcsis:
+                onos = Helpers.get_fabric_onos_info(self.model_accessor, fcsi.owner)
+                log.info("ONOS belonging to fabric crossconnect instance: %s" % onos)
+
+                url = onos['url'] + '/onos/segmentrouting/xconnect'
+                log.info("Sending request to ONOS", url=url)
+                r = requests.get(url, auth=HTTPBasicAuth(onos['user'], onos['pass']))
+                if r.status_code != 200:
+                    log.error(r.text)
+                    raise Exception("Failed to get onos devices")
+                else:
+                    try:
+                        log.info("Get devices response", json=r.json())
+                    except Exception:
+                        log.info("Get devices exception response", text=r.text) 
+                xconnects = r.json()["xconnects"]
+                for xconn in xconnects:
+                    val = xconn['deviceId']
+                    if(str(fcsi.switch_datapath_id) == str(val)):
+                        if model.switch_port not in xconn['endpoints']:
+                            remove_xconnect.append(fcsi)
+            self.remove_crossconnect(remove_xconnect)
+            isChanged = True
+        else:
+            log.info("No Fabric-xconnect-si found & saving bng instance.")
+        return isChanged
+
+    def sync_record(self, model):
+        log.info("Sync started for BNGPortMapping instance: %s" % model.id)
+        log.info('Syncing BNGPortMapping instance', object=str(model), **model.tologdict()) 
+        if model.old_s_tag:
+            if (model.old_s_tag != model.s_tag):
+                fcsis = self.find_crossconnect(model.old_s_tag)
+                if fcsis:
+                    log.info("Xconnect-instance linked to bng : %s" % fcsis)
+                    self.remove_crossconnect(fcsis)
+                else:
+                    log.info("No crossconnect is found for current bng instance")
+            else:
+                self.check_switch_port_change(model)
+        else:
+            if self.check_switch_port_change(model):
+                log.info("Changed bng switch port is repushed to ONOS")
+        log.info("Completing Synchronization for BNGPortMapping instance: %s" % model.id)
+
+    def delete_record(self,model):
+        log.info('Deleting BNGPortMapping instance', object=str(model), **model.tologdict()) 
+        fcsis = self.find_crossconnect(model.s_tag)
+        if fcsis:
+            log.info("Xconnect-instance linked to bng : %s" % fcsis)
+            self.remove_crossconnect(fcsis)
+        log.info("Completing deletion of bng instance")