Metronet Local Service
Change-Id: I92e13f49bbdfc60d27496b3c11207a72310731d4
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())
+