Metronet Local Service

Change-Id: I92e13f49bbdfc60d27496b3c11207a72310731d4
diff --git a/xos/synchronizer/steps/sync_vnodlocalpseudowireconnectorservice.py b/xos/synchronizer/steps/sync_vnodlocalpseudowireconnectorservice.py
new file mode 100644
index 0000000..97b1604
--- /dev/null
+++ b/xos/synchronizer/steps/sync_vnodlocalpseudowireconnectorservice.py
@@ -0,0 +1,196 @@
+import os
+import sys
+
+from synchronizers.base.syncstep import SyncStep
+from synchronizers.vnodlocal.pseudowireproviders.providerfactory import ProviderFactory
+from services.vnodlocal.models import *
+
+from xos.logger import Logger, logging
+
+# vnod local will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__), "..")
+sys.path.insert(0, parentdir)
+
+logger = Logger(level=logging.INFO)
+
+
+class SyncVnodLocalPseudowireConnectorServiceSystem(SyncStep):
+    provides = [VnodLocalPseudowireConnectorService]
+    observes = VnodLocalPseudowireConnectorService
+    requested_interval = 0
+    initialized = False
+
+    def __init__(self, **args):
+        SyncStep.__init__(self, **args)
+
+    def fetch_pending(self, deletion=False):
+        logger.info("VnodLocalPseudowireConnector fetch pending called")
+
+        # Some comments to replace as we write the code
+
+        #    The AdministrativeState state machine:
+        #
+        #
+        #                     Disabled---------DeactivationRequested
+        #                         \                      |
+        #               ActivationRequested              |
+        #               /      /        \                |
+        #              /      /          \               |
+        #     ActivationFailed         Enabled -----------
+        #
+        #
+
+        #  The  OperationalState state machine
+        #
+        #           active
+        #              |
+        #          inactive
+
+        objs = []
+
+
+        # The whole thing needs to be conditional on the VnodLocalSystem existing and being 'enabled'
+        # This is the 'kill switch' in the system that is the first thing to check
+        vnodlocalsystem = self.get_vnodlocal_system()
+        if not vnodlocalsystem:
+            logger.debug("No VnodLocal System Configured, skipping sync")
+            return objs
+
+        # Check to make sure the Metro Network System is enabled
+        if vnodlocalsystem.administrativeState == 'disabled':
+            # Nothing to do
+            logger.debug("VnodLocal System configured - state is Disabled, skipping sync")
+            return objs
+
+
+
+        # Handle call when deletion is False
+        if deletion is False:
+
+            # Check for admin status 'ActivationRequested'
+            activationreqs = VnodLocalPseudowireConnectorService.objects.filter(administrativeState='activationrequested')
+            for activationreq in activationreqs:
+                # Handle the case where we don't yet have a VnodLocalSerive
+                if activationreq.vnodlocal is None:
+                    # Create VnodLocalService
+                    # We save the changes right here in this case to avoid having to to 'pre-save' semnatics
+                    # to cover the foreign key
+                    vnodlocalservice = VnodLocalService()
+                    vnodlocalservice.servicehandle = activationreq.servicehandle
+                    vnodlocalservice.administrativeState = 'configurationrequested'
+                    vnodlocalservice.save()
+                    activationreq.vnodlocal = vnodlocalservice
+                    activationreq.save()
+                elif activationreq.vnodlocal.administrativeState == 'configured':
+                    # Once the underlying VnodLocal is configured then activated it
+                    vnodlocalservice = activationreq.vnodlocal
+                    # Call our underlying provider to connect the pseudo wire
+                    self.activate_pseudowire(activationreq, vnodlocalsystem)
+                    activationreq.administrativeState = 'enabled'
+                    activationreq.operstate = 'active'
+                    objs.append(activationreq)
+                    vnodlocalservice.administrativeState = 'activationrequested'
+                    vnodlocalservice.operstate = 'activereported'
+                    objs.append(vnodlocalservice)
+
+
+            # Check for admin status 'DeactivationRequested'
+            deactivationreqs = VnodLocalPseudowireConnectorService.objects.filter(administrativeState='deactivationrequested')
+            for deactivationreq in deactivationreqs:
+                # Call the XOS Interface to de-actiavte the spoke
+                logger.debug("Attempting to de-activate VnodLocalService servicehandle: %s" % deactivationreq.servicehandle)
+                # De-activate the underlying service
+                vnodlocalservice = deactivationreq.vnodlocal
+                # Call our underlying provider to connect the pseudo wire
+                self.deactivate_pseudowire(deactivationreq)
+                deactivationreq.administrativeState = 'disabled'
+                deactivationreq.operstate = 'inactive'
+                objs.append(deactivationreq)
+                vnodlocalservice.administrativeState = 'deactivationrequested'
+                objs.append(vnodlocalservice)
+
+
+        elif deletion:
+            # Apply Deletion Semantics:
+            logger.debug("Applying Deletion Semanctics")
+            # TODO: Figure out the odd scenario of Service deletion
+            deletedobjs = VnodLocalPseudowireConnectorService.deleted_objects.all()
+
+            # Delete the underlying VnodLocalService objects
+            for deletedobj in deletedobjs:
+                # Set the VnodLocal to Deleted - its Synchronizer will take care of deletion
+                vnodlocalobj = deletedobj.vnodlocal
+                vnodlocalobj.deleted = True
+                vnodlocalobj.save()
+                # Delete the underlying pseudowire
+                self.delete_pseudowire(deletedobj)
+                # Finally - add the Service for deletion
+                objs.append(deletedobj)
+
+        # Finally just return the set of changed objects
+        return objs
+
+
+    def sync_record(self, o):
+
+        # Simply save the record to the DB - both updates and adds are handled the same way
+        o.save()
+
+
+    def delete_record(self, o):
+        # Overriden to customize our behaviour - the core sync step for will remove the record directly
+        # We just log and return
+        logger.debug("deleting Object %s" % str(o), extra=o.tologdict())
+
+    def get_vnodlocal_system(self):
+        # We only expect to have one of these objects in the system in the curent design
+        # So get the first element from the query
+        vnodlocalsystems = VnodLocalSystem.objects.all()
+        if not vnodlocalsystems:
+            return None
+
+        return vnodlocalsystems[0]
+
+    def activate_pseudowire(self, o, vnodlocalsystem):
+        # Call the underlying pseudowire provicers and call
+        logger.debug("activating pseudowire %s" % o.servicehandle)
+
+        pseudowireprovier = ProviderFactory.getprovider()
+
+        if pseudowireprovier is not None:
+            # Pass it the two ports - the internal port configured on the Pseudowire and the NNI port from
+            # the VnodLocal
+            if o.pseudowirehandle == '':
+                o.pseudowirehandle = pseudowireprovier.create(o.internalport, o.vnodlocal.portid, o.vnodlocal.vlanid, vnodlocalsystem)
+
+            # handle already exists - just connect it
+            pseudowireprovier.connect(o.pseudowirehandle)
+        else:
+            # No Provider configured - lets put a handle that reflects thsi
+            o.pseudowirehandle = 'No Pseudowire Provider configured'
+
+
+    def deactivate_pseudowire(self, o):
+        # Call the underlying pseudowire provicers and call
+        logger.debug("deactivating pseudowire %s" % o.servicehandle)
+
+        pseudowireprovier = ProviderFactory.getprovider()
+
+        if pseudowireprovier is not None:
+            # Pass it the handle
+            pseudowireprovier.disconnect(o.pseudowirehandle)
+
+
+    def delete_pseudowire(self, o):
+        # Call the underlying pseudowire provicers and call
+        logger.debug("deleting pseudowire %s" % o.servicehandle)
+
+        pseudowireprovier = ProviderFactory.getprovider()
+
+        if pseudowireprovier is not None:
+            # Pass it the handle
+            if o.pseudowirehandle != '':
+                pseudowireprovier.delete(o.pseudowirehandle)
+
+        # Either way blank out the handle name
+        o.pseudowirehandle = ''
diff --git a/xos/synchronizer/steps/sync_vnodlocalservice.py b/xos/synchronizer/steps/sync_vnodlocalservice.py
new file mode 100644
index 0000000..90e2ded
--- /dev/null
+++ b/xos/synchronizer/steps/sync_vnodlocalservice.py
@@ -0,0 +1,319 @@
+import os
+import sys
+
+from synchronizers.base.syncstep import SyncStep
+from services.vnodlocal.models import *
+import requests, json
+from requests.auth import HTTPBasicAuth
+
+from xos.logger import Logger, logging
+
+# vnod local will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__), "..")
+sys.path.insert(0, parentdir)
+
+logger = Logger(level=logging.INFO)
+
+
+class SyncVnodLocalSystem(SyncStep):
+    provides = [VnodLocalService]
+    observes = VnodLocalService
+    requested_interval = 0
+    initialized = False
+
+    def __init__(self, **args):
+        SyncStep.__init__(self, **args)
+
+    def fetch_pending(self, deletion=False):
+        logger.info("VnodLocal fetch pending called")
+
+        # Some comments to replace as we write the code
+
+        #    The AdministrativeState state machine:
+        #
+        #               Diasabled (initial)
+        #                    |
+        #             ConfigurationRequested
+        #             /      /            \
+        #            /      /              \
+        #      ConfigurationFailed      Configured---------DeactivationRequested
+        #                                   \                      |
+        #                         ActivationRequested              |
+        #                         /      /        \                |
+        #                        /      /          \               |
+        #               ActivationFailed         Enabled -----------
+        #
+        #
+
+        #  The  OperationalState state machine
+        #
+        #           active-----------------|
+        #              |                   |
+        #       inactivereported           |
+        #              |                   |
+        #          inactive----------activereported
+
+        objs = []
+
+
+        # The whole thing needs to be conditional on the VnodLocalSystem existing and being 'enabled'
+        # This is the 'kill switch' in the system that is the first thing to check
+        vnodlocalsystem = self.get_vnodlocal_system()
+
+        if not vnodlocalsystem:
+            logger.debug("No VnodLocal System Configured, skipping sync")
+            return objs
+
+        # Check to make sure the Metro Network System is enabled
+        if vnodlocalsystem.administrativeState == 'disabled':
+            # Nothing to do
+            logger.debug("VnodLocal System configured - state is Disabled, skipping sync")
+            return objs
+
+
+
+        # Handle call when deletion is False
+        if deletion is False:
+
+            # First Part of Auto-attachement: What we need to do is ask the ECORD if there are any Spokes for our site
+            # that are set to 'auto-attached' but are not currently actually attached
+            # it will send back a list of servicehandles that meet that criteria. We will simply
+            # check if we have already created a VnodLocal for that service handle, if we have do
+            # nothing it should be still in progress. If we haven't create it, mark it as 'autoattached', set the
+            # servicehandle and mark it as 'ConfigurationRequested'
+            rest_url = vnodlocalsystem.restUrl
+            sitename = vnodlocalsystem.name
+            username = vnodlocalsystem.username
+            password = vnodlocalsystem.password
+
+            autoattachhandles = self.get_autoattachhandles(vnodlocalsystem)
+            for autoattachhandle in autoattachhandles:
+                # Check to see if it already exists - if not add it
+                if not VnodLocalService.objects.filter(servicehandle=autoattachhandle).exists():
+                    vnodlocal = VnodLocalService()
+                    vnodlocal.servicehandle = autoattachhandle
+                    vnodlocal.autoattached = True
+                    vnodlocal.administrativeState = 'configurationrequested'
+                    logger.debug("Adding Auto-attached VnodLocalService servicehandle: %s" % vnodlocal.servicehandle)
+                    objs.append(vnodlocal)
+
+            # Second Part of Auto-attachment
+            # Look for auto-attachmed Services that are Configured, move them automaticaly to activationrequested
+            autoattachconfigures = self.get_autoattachconfigured()
+            for autoattachconfigure in autoattachconfigures:
+                # Just bounce these forward to activationrequested to get them activated
+                autoattachconfigure.administrativeState = 'activationrequested'
+                objs.append(autoattachconfigure)
+
+
+            # Check for admin status 'ConfigurationRequested'
+            configreqs = VnodLocalService.objects.filter(administrativeState='configurationrequested')
+            for configreq in configreqs:
+                # Call the XOS Interface to configure the service
+                logger.debug("Attempting to configure VnodLocalService servicehandle: %s" % configreq.servicehandle)
+                # Add code to call REST api on the ECORD - For this state - we call VnodGlobal
+                # with the servciehandle and sitename it
+                # it gives us back the NNI port and Vlan Config
+                # we then set our state to 'Configured' or 'ConfigurationFailed'
+                servicehandle = configreq.servicehandle
+                query = {"sitename": sitename, "servicehandle" : servicehandle}
+
+                resp = requests.get("{}/vnodglobal_api_configuration/".format(rest_url), params=query,
+                                    auth=HTTPBasicAuth(username, password))
+
+                if resp.status_code == 200:
+                    resp = resp.json()
+                    # Success-path transitions to 'configured'
+                    configreq.vlanid = resp['vlanid']
+                    configreq.portid = resp['port']['name']
+                    configreq.administrativeState = 'configured'
+
+                    #update proxy adminstate in ecord
+                    data = {"sitename": sitename, "servicehandle": servicehandle, "adminstate": 'configured',
+                            "vlanid": configreq.vlanid, "portid": configreq.portid}
+                    resp = requests.post("{}/vnodglobal_api_status/".format(rest_url), data=json.dumps(data),
+                                     auth=HTTPBasicAuth(username, password))
+
+                else:
+                    configreq.administrativeState = 'configurationfailed'
+
+                objs.append(configreq)
+
+
+            # Check for admin status 'ActivationRequested'
+            activationreqs = VnodLocalService.objects.filter(administrativeState='activationrequested')
+            for acivationreq in activationreqs:
+                # Call the XOS Interface to activate the service
+                logger.debug("Attempting to activate VnodLocalService servicehandle: %s" % acivationreq.servicehandle)
+                # Add code to call REST api on the ECORD - For this state we send the VnodGlobal
+                # service our service handle, subscriber,
+                # VnodLocalId (this id)
+                # Once this is accepted we transition to the
+                # Final state of 'Enabled' or 'ActivationFailed'
+                servicehandle = acivationreq.servicehandle
+                vnodlocalid = acivationreq.id
+                vlanid = acivationreq.vlanid
+                portid = acivationreq.portid
+
+                data = {"sitename": sitename, "servicehandle": servicehandle, "vnodlocalid": vnodlocalid,
+                        "vlanid": vlanid, "portid": portid, "activate": "true"}
+
+                resp = requests.post("{}/vnodglobal_api_activation/".format(rest_url), data=json.dumps(data),
+                                    auth=HTTPBasicAuth(username, password))
+
+                if resp.status_code == 200:
+                    # Success-path transitions to 'enabled'
+                    acivationreq.administrativeState = 'enabled'
+
+                    # update proxy adminstate in ecord
+                    data = {"sitename": sitename, "servicehandle": servicehandle, "adminstate": 'enabled',
+                            "vlanid": vlanid, "portid": portid, "operstate": "active"}
+                    resp = requests.post("{}/vnodglobal_api_status/".format(rest_url), data=json.dumps(data),
+                                         auth=HTTPBasicAuth(username, password))
+                else:
+                    acivationreq.administrativeState = 'activationfailed'
+
+                    # update proxy adminstate in ecord
+                    data = {"sitename": sitename, "servicehandle": servicehandle, "adminstate": 'impaired',
+                            "operstate": "inactive", "vlanid": vlanid, "portid": portid}
+                    resp = requests.post("{}/vnodglobal_api_status/".format(rest_url), data=json.dumps(data),
+                                         auth=HTTPBasicAuth(username, password))
+
+                objs.append(acivationreq)
+
+
+            # Check for admin status 'DeactivationRequested'
+            deactivationreqs = VnodLocalService.objects.filter(administrativeState='deactivationrequested')
+            for deacivationreq in deactivationreqs:
+                # Call the XOS Interface to de-actiavte the spoke
+                logger.debug("Attempting to de-activate VnodLocalService servicehandle: %s" % deacivationreq.servicehandle)
+                # Add code to call REST api on the ECORD - Report change to VnodGlobal
+                servicehandle = deacivationreq.servicehandle
+                vnodlocalid = deacivationreq.id
+                vlanid = deacivationreq.vlanid
+                portid = deacivationreq.portid
+
+
+                data = {"sitename": sitename, "servicehandle": servicehandle, "vnodlocalid": vnodlocalid,
+                        "vlanid": vlanid, "portid": portid, "activate": "false"}
+
+                resp = requests.post("{}/vnodglobal_api_activation/".format(rest_url), data=json.dumps(data),
+                                     auth=HTTPBasicAuth(username, password))
+
+                if resp.status_code == 200:
+                    # Success-path transitions to 'enabled'
+                    deacivationreq.administrativeState = 'configured'
+                else:
+                    deacivationreq.administrativeState = 'deactivationfailed'
+
+                # update proxy adminstate in ecord
+                data = {"sitename": sitename, "servicehandle": servicehandle, "adminstate": 'impaired',
+                        "vlanid": vlanid, "portid": portid}
+                resp = requests.post("{}/vnodglobal_api_status/".format(rest_url), data=json.dumps(data),
+                                     auth=HTTPBasicAuth(username, password))
+
+                objs.append(deacivationreq)
+
+
+            # Check for oper status inactive reported
+            inactivereports = VnodLocalService.objects.filter(operstate='inactivereported')
+            for inactivereport in inactivereports:
+                # Call the XOS Interface to report operstate issue
+                logger.debug("Attempting to report inactive VnodLocalService servicehandle: %s" % inactivereport.servicehandle)
+                # Add code to call REST api on the ECORD - Report change to VnodGlobal
+
+                servicehandle = inactivereport.servicehandle
+                vlanid = inactivereport.vlanid
+                portid = inactivereport.portid
+
+                # update proxy operstate in ecord
+                data = {"sitename": sitename, "servicehandle": servicehandle, "operstate": "inactive",
+                        "adminstate":"impaired", "vlanid": vlanid, "portid": portid}
+                resp = requests.post("{}/vnodglobal_api_status/".format(rest_url), data=json.dumps(data),
+                                     auth=HTTPBasicAuth(username, password))
+
+                # transition to 'inactive' state regardless of whether call to ECORD was successful?!?
+                inactivereport.operstate = 'inactive'
+                objs.append(inactivereport)
+
+
+            # Check for oper status active reported
+            activereports = VnodLocalService.objects.filter(operstate='activereported')
+            for activereport in activereports:
+                # Call the XOS Interface to report operstate issue
+                logger.debug(
+                    "Attempting to report active VnodLocalService servicehandle: %s" % activereport.servicehandle)
+
+                servicehandle = activereport.servicehandle
+                vlanid = activereport.vlanid
+                portid = activereport.portid
+                # Add code to call REST api on the ECORD - Report change to VnodGlobal.
+                # update proxy operstate in ecord
+                data = {"sitename": sitename, "servicehandle": servicehandle, "operstate": "active",
+                        "vlanid": vlanid, "portid": portid}
+                resp = requests.post("{}/vnodglobal_api_status/".format(rest_url), data=json.dumps(data),
+                                 auth=HTTPBasicAuth(username, password))
+
+                activereport.operstate = 'active'
+                objs.append(activereport)
+        elif deletion:
+            # Apply Deletion Semantics:
+            logger.debug("Applying Deletion Semanctics")
+            # TODO: Figure out the odd scenario of Service deletion
+            deletedobjs = VnodLocalService.deleted_objects.all()
+            objs.extend(deletedobjs)
+
+        # Finally just return the set of changed objects
+        return objs
+
+    def get_vnodlocal_system(self):
+        # We only expect to have one of these objects in the system in the curent design
+        # So get the first element from the query
+        vnodlocalsystems = VnodLocalSystem.objects.all()
+        if not vnodlocalsystems:
+            return None
+
+        return vnodlocalsystems[0]
+
+    def get_autoattachhandles(self, vnodlocalsystem):
+        # Figure out API call to actually get this to work
+        rest_url = vnodlocalsystem.restUrl
+        sitename = vnodlocalsystem.name
+        username=vnodlocalsystem.username
+        password=vnodlocalsystem.password
+        query = {"sitename":sitename}
+
+
+        resp = requests.get("{}/vnodglobal_api_autoattach/".format(rest_url), params=query,
+                            auth=HTTPBasicAuth(username, password))
+
+        handles = []
+        if resp.status_code == 200:
+            resp = resp.json()
+            handles = resp['servicehandles']
+        else:
+            logger.debug("Request for autoattach servicehandles failed.")
+
+        return handles
+
+    def get_autoattachconfigured(self):
+        # Query for the set of auto-attached handles that are  in the 'Configured' state
+        autoattachedconfigured = VnodLocalService.objects.filter(autoattached=True, administrativeState='configured')
+
+        if not autoattachedconfigured:
+            return []
+
+        return autoattachedconfigured
+
+
+    def sync_record(self, o):
+
+        # Simply save the record to the DB - both updates and adds are handled the same way
+        o.save()
+
+
+    def delete_record(self, o):
+        # Overriden to customize our behaviour - the core sync step for will remove the record directly
+        # We just log and return
+        logger.debug("deleting Object %s" % str(o), extra=o.tologdict())
+