Metronet Local Service
Change-Id: I92e13f49bbdfc60d27496b3c11207a72310731d4
diff --git a/xos/__init__.py b/xos/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/xos/__init__.py
diff --git a/xos/admin.py b/xos/admin.py
new file mode 100644
index 0000000..5ffc243
--- /dev/null
+++ b/xos/admin.py
@@ -0,0 +1,47 @@
+from core.admin import XOSBaseAdmin
+from django.contrib import admin
+from services.vnodlocal.models import *
+from django import forms
+
+class VnodLocalServiceAdmin(XOSBaseAdmin):
+ verbose_name = "VNOD Local Service"
+ verbose_name_plural = "VNOD Local Services"
+ list_display = ('servicehandle', 'portid', 'vlanid', 'administrativeState', 'operstate', 'autoattached')
+ list_display_links = ('servicehandle', 'portid', 'vlanid', 'administrativeState', 'operstate', 'autoattached')
+
+ fields = ('id', 'servicehandle', 'portid', 'vlanid', 'administrativeState', 'operstate', 'autoattached')
+ readonly_fields = ('id','autoattached')
+
+
+class VnodLocalSystemAdminForm(forms.ModelForm):
+
+ password = forms.CharField(required=False, widget = forms.PasswordInput(render_value=True))
+
+ class Meta:
+ model = VnodLocalSystem
+ fields = '__all__'
+
+class VnodLocalSystemAdmin(XOSBaseAdmin):
+ verbose_name = "VNOD Local System"
+ verbose_name_plural = "VNOD Local Systems"
+ form = VnodLocalSystemAdminForm
+ list_display = ('name', 'administrativeState', 'restUrl', 'username', 'pseudowireprovider', 'networkControllerUrl')
+ list_display_links = ('name', 'administrativeState', 'restUrl', 'username', 'pseudowireprovider', 'networkControllerUrl')
+
+ fields = ('name', 'administrativeState', 'restUrl', 'username', 'password', 'pseudowireprovider', 'networkControllerUrl')
+
+class VnodLocalPseudowireConnectorServiceAdmin(XOSBaseAdmin):
+ verbose_name = "VNOD Local Pseudowire Connector Service"
+ verbose_name_plural = "VNOD Local Pseudowire Connector Service"
+ list_display = ('servicehandle', 'internalport', 'pseudowirehandle','vnodlocal', 'administrativeState', 'operstate')
+ list_display_links = ('servicehandle', 'internalport', 'pseudowirehandle','vnodlocal', 'administrativeState', 'operstate')
+
+ fields = ('servicehandle', 'internalport', 'pseudowirehandle','vnodlocal', 'administrativeState', 'operstate')
+ readonly_fields = ('vnodlocal', 'operstate', 'pseudowirehandle')
+
+
+admin.site.register(VnodLocalSystem, VnodLocalSystemAdmin)
+admin.site.register(VnodLocalService, VnodLocalServiceAdmin)
+admin.site.register(VnodLocalPseudowireConnectorService, VnodLocalPseudowireConnectorServiceAdmin)
+
+
diff --git a/xos/api/service/vnodlocalservice/vnodlocalservice.py b/xos/api/service/vnodlocalservice/vnodlocalservice.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/xos/api/service/vnodlocalservice/vnodlocalservice.py
diff --git a/xos/make_synchronizer_manifest.sh b/xos/make_synchronizer_manifest.sh
new file mode 100755
index 0000000..4058982
--- /dev/null
+++ b/xos/make_synchronizer_manifest.sh
@@ -0,0 +1,2 @@
+#! /bin/bash
+find synchronizer -type f | cut -b 14- > synchronizer/manifest
diff --git a/xos/models.py b/xos/models.py
new file mode 100644
index 0000000..1484129
--- /dev/null
+++ b/xos/models.py
@@ -0,0 +1,167 @@
+# models.py - VNOD Local Service
+
+from django.db import models
+from core.models import Service
+from core.models import PlCoreBase
+
+VNODLOCAL_KIND = "vnodlocal"
+SERVICE_NAME = 'vnodlocal'
+
+class VnodLocalSystem(PlCoreBase):
+ class Meta:
+ app_label = VNODLOCAL_KIND
+ verbose_name = "VNOD Local System"
+
+ ADMINISTRATIVE_STATE = (
+ ('enabled', 'Enabled'),
+ ('disabled', 'Disabled')
+ )
+
+ name = models.CharField(unique=True,
+ verbose_name="Name",
+ max_length=256,
+ editable=True)
+
+ description = models.CharField(verbose_name="Description",
+ max_length=1024,
+ editable=True)
+
+ restUrl = models.CharField(verbose_name="MetroNetwork Rest URL",
+ max_length=256,
+ editable=True)
+
+ username = models.CharField(verbose_name='Username',
+ max_length=32,
+ editable=True,
+ blank=True)
+
+ password = models.CharField(max_length=32,
+ verbose_name='Password',
+ editable=True,
+ blank=True)
+
+ administrativeState = models.CharField(choices=ADMINISTRATIVE_STATE,
+ default='enabled',
+ verbose_name="AdministrativeState",
+ max_length=16,
+ editable=True)
+
+ pseudowireprovider = models.CharField(unique=False,
+ verbose_name="Pseudowire Provider",
+ default='none',
+ max_length=256,
+ editable=True)
+
+ networkControllerUrl = models.CharField(verbose_name="Network Controller URL",
+ blank=True,
+ max_length=256,
+ editable=True)
+
+ def __init__(self, *args, **kwargs):
+ super(VnodLocalSystem, self).__init__(*args, **kwargs)
+
+
+ def getAdminstrativeState(self):
+ return self.administrativeState
+
+
+ def setAdminstrativeState(self, value):
+ self.administrativeState = value
+
+
+ def getRestUrl(self):
+ return self.restUrl
+
+
+class VnodLocalService(Service):
+
+ class Meta:
+ app_label = VNODLOCAL_KIND
+ verbose_name = "Virtual Network On Demand Local Service"
+
+ ADMINISTRATIVE_STATE = (
+ ('disabled', 'Disabled'),
+ ('configurationrequested', 'ConfigurationRequested'),
+ ('configurationfailed', 'ConfigurationFailed'),
+ ('configured', 'Configured'),
+ ('activationrequested', 'ActivationRequested'),
+ ('activationfailed', 'ActivationFailed'),
+ ('enabled', 'Enabled'),
+ ('deactivationrequested', 'DeactivationRequested')
+ )
+
+ OPERATIONALSTATE = (
+ ('active', 'Active'),
+ ('inactivereported', 'InactiveReported'),
+ ('inactive', 'Inactive'),
+ ('activereported', 'ActiveReported')
+ )
+
+ portid = models.CharField(verbose_name="PortId", blank=True, max_length=256, editable=True)
+ vlanid = models.CharField(verbose_name="VlanId", blank=True, max_length=256, editable=True)
+ servicehandle = models.CharField(verbose_name="Service Handle", max_length=256, editable=True)
+ autoattached = models.BooleanField(verbose_name="Auto-Attached", default=False, editable=True)
+
+ administrativeState = models.CharField(choices=ADMINISTRATIVE_STATE,
+ default='disabled',
+ verbose_name="AdministrativeState",
+ max_length=64,
+ editable=True)
+
+ operstate = models.CharField(choices=OPERATIONALSTATE,
+ default='inactive',
+ verbose_name="OperationalState",
+ max_length=64,
+ editable=True)
+
+
+ def __init__(self, *args, **kwargs):
+ super(VnodLocalService, self).__init__(*args, **kwargs)
+
+ def __unicode__(self): return u'%s:%s' % (self.servicehandle, self.portid)
+
+
+class VnodLocalPseudowireConnectorService(Service):
+
+ class Meta:
+ app_label = VNODLOCAL_KIND
+ verbose_name = "Virtual Network On Demand Local Pseudo-wire Connector Service"
+
+ ADMINISTRATIVE_STATE = (
+ ('disabled', 'Disabled'),
+ ('activationrequested', 'ActivationRequested'),
+ ('enabled', 'Enabled'),
+ ('deactivationrequested', 'DeactivationRequested')
+ )
+
+ OPERATIONALSTATE = (
+ ('active', 'Active'),
+ ('inactive', 'Inactive')
+ )
+
+ servicehandle = models.CharField(verbose_name="Service Handle", max_length=256, editable=True)
+ pseudowirehandle = models.CharField(verbose_name="Pseudowirehandle", blank=True, max_length=256, editable=True)
+ internalport = models.CharField(verbose_name="Internal Port", max_length=256, editable=True)
+
+ vnodlocal = models.ForeignKey(VnodLocalService,
+ related_name='VnodLocalService',
+ verbose_name="VnodLocalService",
+ null=True,
+ editable=True,
+ on_delete=models.CASCADE)
+
+ administrativeState = models.CharField(choices=ADMINISTRATIVE_STATE,
+ default='disabled',
+ verbose_name="AdministrativeState",
+ max_length=64,
+ editable=True)
+
+ operstate = models.CharField(choices=OPERATIONALSTATE,
+ default='inactive',
+ verbose_name="OperationalState",
+ max_length=64,
+ editable=True)
+
+
+ def __init__(self, *args, **kwargs):
+ super(VnodLocalPseudowireConnectorService, self).__init__(*args, **kwargs)
diff --git a/xos/synchronizer/__init__.py b/xos/synchronizer/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/xos/synchronizer/__init__.py
@@ -0,0 +1 @@
+
diff --git a/xos/synchronizer/manifest b/xos/synchronizer/manifest
new file mode 100644
index 0000000..fc4907c
--- /dev/null
+++ b/xos/synchronizer/manifest
@@ -0,0 +1,14 @@
+pseudowireproviders/providerfactory.py
+pseudowireproviders/metronetworkpseudowireprovider.py
+pseudowireproviders/__init__.py
+pseudowireproviders/pseudowireprovider.py
+vnodlocal-synchronizer.py
+__init__.py
+model-deps
+vnodlocal-synchronizer-devel.py
+run_devel.sh
+manifest
+steps/sync_vnodlocalpseudowireconnectorservice.py
+steps/sync_vnodlocalservice.py
+vnodlocal_synchronizer_config
+run.sh
\ No newline at end of file
diff --git a/xos/synchronizer/model-deps b/xos/synchronizer/model-deps
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/xos/synchronizer/model-deps
@@ -0,0 +1 @@
+{}
diff --git a/xos/synchronizer/pseudowireproviders/__init__.py b/xos/synchronizer/pseudowireproviders/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/xos/synchronizer/pseudowireproviders/__init__.py
@@ -0,0 +1 @@
+
diff --git a/xos/synchronizer/pseudowireproviders/metronetworkpseudowireprovider.py b/xos/synchronizer/pseudowireproviders/metronetworkpseudowireprovider.py
new file mode 100644
index 0000000..f2762e2
--- /dev/null
+++ b/xos/synchronizer/pseudowireproviders/metronetworkpseudowireprovider.py
@@ -0,0 +1,66 @@
+from xos.logger import Logger, logging
+from synchronizers.vnodlocal.pseudowireproviders.pseudowireprovider import PseudowireProvider
+from services.metronetwork.models import NetworkEdgeToEdgePointConnection, NetworkEdgePort
+
+logger = Logger(level=logging.INFO)
+
+class MetronetworkPseudowireProvider(PseudowireProvider):
+
+ def __init__(self, **args):
+ pass
+
+ # Methods to support creation
+ #
+ # Returns: handle
+ #
+ def create(self, port1, port2, vlanid, psuedowireservice):
+ # Create method - create eline with the ports
+ # Vlan is TBD
+ pseudowirename = ("port1: %s, port2: %s, vlan: %s" % (port1, port2, vlanid))
+ logger.info("Metronetwork create called, name: %s" % pseudowirename )
+ # Edge to Edge Point Connectivity creation
+ edgetoedgeconnectivity = NetworkEdgeToEdgePointConnection()
+ uni1port = NetworkEdgePort.objects.filter(pid__icontains=port1)
+ if uni1port:
+ uni1port = uni1port[0]
+ uni2port = NetworkEdgePort.objects.filter(pid__icontains=port2)
+ if uni2port:
+ uni2port = uni2port[0]
+ edgetoedgeconnectivity.uni1 = uni1port
+ edgetoedgeconnectivity.uni2 = uni2port
+ edgetoedgeconnectivity.vlanid = vlanid
+ edgetoedgeconnectivity.type = 'Point_To_Point'
+ edgetoedgeconnectivity.operstate = 'inactive'
+ edgetoedgeconnectivity.adminstate = 'disabled'
+ edgetoedgeconnectivity.sid = pseudowirename
+ edgetoedgeconnectivity.name = 'Metronetwork'
+ edgetoedgeconnectivity.save()
+ return pseudowirename
+
+ # Method to support connect
+ #
+ def connect(self, handle):
+ # Connect method - simply transition the state of the underlying object - the Metronet sync will do the rest
+ logger.info("Metronetwork Pseudowire connect called, handle = %s" % handle)
+ edgetoedgeconnectivity = NetworkEdgeToEdgePointConnection.objects.get(sid=handle)
+ edgetoedgeconnectivity.adminstate = 'activationrequested'
+ edgetoedgeconnectivity.save()
+
+ # Method to support disconnect connect
+ #
+ def disconnect(self, handle):
+ # Connect method - simply transition the state of the underlying object - the Metronet sync will do the rest
+ logger.info("Metronetwork Pseudowire disconnect called, handle = %s" % handle)
+ edgetoedgeconnectivity = NetworkEdgeToEdgePointConnection.objects.get(sid=handle)
+ edgetoedgeconnectivity.adminstate = 'deactivationrequested'
+ edgetoedgeconnectivity.save()
+
+ # Method to support deletion
+ #
+ def delete(self, handle):
+ # Delete method - simply set the state to deleted and the Metronet sync will do the rest
+ logger.info("Metronetwork Pseudowire delete called, handle = %s" % handle)
+ edgetoedgeconnectivity = NetworkEdgeToEdgePointConnection.objects.get(sid=handle)
+ edgetoedgeconnectivity.deleted = True
+ edgetoedgeconnectivity.save()
+
diff --git a/xos/synchronizer/pseudowireproviders/providerfactory.py b/xos/synchronizer/pseudowireproviders/providerfactory.py
new file mode 100644
index 0000000..6735ecc
--- /dev/null
+++ b/xos/synchronizer/pseudowireproviders/providerfactory.py
@@ -0,0 +1,24 @@
+import sys
+
+from services.vnodlocal.models import VnodLocalSystem
+from synchronizers.vnodlocal.pseudowireproviders.metronetworkpseudowireprovider import MetronetworkPseudowireProvider
+from synchronizers.vnodlocal.pseudowireproviders.segmentroutingvlanxconnectpseudowireprovider import SegmentRoutingVlanXconnectPseudowireProvider
+
+
+class ProviderFactory(object):
+ @staticmethod
+ def getprovider():
+
+ # We look up the VnodLocal Configuration to see what to do
+ vnodlocalsystems = VnodLocalSystem.objects.all()
+ if not vnodlocalsystems:
+ return None
+
+ vnodlocalsystem = vnodlocalsystems[0]
+
+ if vnodlocalsystem.pseudowireprovider == 'metronetwork':
+ return MetronetworkPseudowireProvider()
+ elif vnodlocalsystem.pseudowireprovider == 'segmentroutingxconnect':
+ return SegmentRoutingVlanXconnectPseudowireProvider()
+ else:
+ return None
\ No newline at end of file
diff --git a/xos/synchronizer/pseudowireproviders/pseudowireprovider.py b/xos/synchronizer/pseudowireproviders/pseudowireprovider.py
new file mode 100644
index 0000000..5107499
--- /dev/null
+++ b/xos/synchronizer/pseudowireproviders/pseudowireprovider.py
@@ -0,0 +1,36 @@
+from xos.logger import Logger, logging
+
+logger = Logger(level=logging.INFO)
+
+
+class PseudowireProvider(object):
+
+ def __init__(self, **args):
+ pass
+
+ # Methods to support creation
+ #
+ # Returns: handle
+ #
+ def create(self, port1, port2, vlanid, pseudowireservice):
+ # Default method needs to be overriden
+ logger.info("create called - should be overriden")
+
+ # Method to support connection
+ #
+ def connect(self, handle):
+ # Default method needs to be overriden
+ logger.info("connect called - should be overriden")
+ return None
+
+ # Method to support disconnection
+ #
+ def disconnect(self, handle):
+ # Default method needs to be overriden
+ logger.info("discoconnect called - should be overriden")
+
+ # Methods to support deletion
+ #
+ def delete(self, handle):
+ # Default method needs to be overriden
+ logger.info("delete called - should be overriden")
\ No newline at end of file
diff --git a/xos/synchronizer/pseudowireproviders/segmentroutingvlanxconnectpseudowireprovider.py b/xos/synchronizer/pseudowireproviders/segmentroutingvlanxconnectpseudowireprovider.py
new file mode 100644
index 0000000..b98e1c5
--- /dev/null
+++ b/xos/synchronizer/pseudowireproviders/segmentroutingvlanxconnectpseudowireprovider.py
@@ -0,0 +1,94 @@
+from xos.logger import Logger, logging
+from synchronizers.vnodlocal.pseudowireproviders.pseudowireprovider import PseudowireProvider
+from services.metronetwork.models import NetworkEdgeToEdgePointConnection, NetworkEdgePort
+
+import requests, json
+from requests.auth import HTTPBasicAuth
+
+logger = Logger(level=logging.INFO)
+
+class SegmentRoutingVlanXconnectPseudowireProvider(PseudowireProvider):
+
+ def __init__(self, **args):
+ pass
+
+ # Methods to support creation
+ #
+ # Returns: handle
+ #
+ def create(self, port1, port2, vlanid, pseudowireservice):
+ # Create method - create xconnect
+ # Vlan is TBD
+ pseudowirename = ("port1: %s, port2: %s, vlan: %s" % (port1, port2, vlanid))
+ logger.info("SegmentRoutingXConnect create called, name: %s" % pseudowirename )
+
+ # Pull out Ports from FQN
+
+
+ # Pull out Device from FQN
+ # Use default user/password?
+
+ # curl --user onos:rocks -X POST -H "Content-Type: application/json" http://138.120.151.126:8181/onos/v1/network/configuration/apps/org.onosproject.segmentrouting/xconnect -d '{ "of:0000000000000001" : [{"vlan" : "100", "ports" : [1, 2], "name" : "Mike"}] }'
+
+ # Port 1 Device and Num
+ port1IdToken = port1.split('/', 1)
+ port1Devicename = port1IdToken[0]
+ port1Num = port1IdToken[1]
+
+ # Port 2 Device and Num
+ port2IdToken = port2.split('/', 1)
+ port2Devicename = port2IdToken[0]
+ port2Num = port2IdToken[1]
+
+ # Lets make sure the Devices are the same - otherwise its an error - Xconnect must be on same device
+
+ if (port1Devicename != port2Devicename):
+ Exception("XConnect Device must be the same. D1= % D2=%" % (port1Devicename, port2Devicename))
+
+ # Get URL from PwaaS Ojbect
+ restCtrlUrl = pseudowireservice.networkControllerUrl
+
+ data = {
+ port2Devicename : [
+ {
+ "vlan" : vlanid,
+ "ports" : [port1Num, port2Num],
+ "name" : pseudowirename
+ }
+ ]
+ }
+
+ headers = {'Content-Type': 'application/json'}
+
+ resp = requests.post('{}/v1/network/configuration/apps/org.onosproject.segmentrouting/xconnect'.format(restCtrlUrl),
+ data=json.dumps(data), headers=headers, auth=HTTPBasicAuth('karaf', 'karaf'))
+
+ if resp.status_code == 200:
+ logger.info("SegmentRoutingXConnect create successful")
+ return pseudowirename
+ else:
+ Exception("Pseudowire create failed Error Code: %s" % resp.status_code)
+
+
+ # Method to support connect
+ #
+ def connect(self, handle):
+ # Connect method - this is a no-op for this module, it does not support a complext state machine
+ logger.info("SegmentRoutingXConnect Pseudowire connect called, handle = %s" % handle)
+
+ # Method to support disconnect connect
+ #
+ def disconnect(self, handle):
+ # Disconnect method - impl is TBD
+ logger.info("SegmentRoutingXConnect Pseudowire disconnect called, handle = %s" % handle)
+
+ # Example command line syntax:
+ # curl --user onos:rocks -X DELETE http://138.120.151.126:8181/onos/v1/network/configuration/apps/org.onosproject.segmentrouting/xconnect
+
+ # Method to support deletion
+ #
+ def delete(self, handle):
+ # Delete method - impl is TBD
+ logger.info("SegmentRoutingXConnect Pseudowire delete called, handle = %s" % handle)
+
+
diff --git a/xos/synchronizer/run.sh b/xos/synchronizer/run.sh
new file mode 100755
index 0000000..43c5cc5
--- /dev/null
+++ b/xos/synchronizer/run.sh
@@ -0,0 +1,2 @@
+export XOS_DIR=/opt/xos
+python vnodlocal-synchronizer.py -C $XOS_DIR/synchronizers/vnodlocal/vnodlocal_synchronizer_config
diff --git a/xos/synchronizer/run_devel.sh b/xos/synchronizer/run_devel.sh
new file mode 100755
index 0000000..2575b2c
--- /dev/null
+++ b/xos/synchronizer/run_devel.sh
@@ -0,0 +1,2 @@
+export XOS_DIR=/opt/xos
+python vnodlocal-synchronizer-devel.py -C $XOS_DIR/synchronizers/vnodlocal/vnodlocal_synchronizer_config
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())
+
diff --git a/xos/synchronizer/vnodlocal-synchronizer-devel.py b/xos/synchronizer/vnodlocal-synchronizer-devel.py
new file mode 100755
index 0000000..df697ec
--- /dev/null
+++ b/xos/synchronizer/vnodlocal-synchronizer-devel.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+
+# This imports and runs ../../xos-observer.py
+
+import importlib
+import os
+import sys
+
+#observer_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../synchronizers/base")
+sys.path.append("/opt/xos/synchronizers/base")
+print sys.path
+mod = importlib.import_module("xos-synchronizer")
+mod.main()
diff --git a/xos/synchronizer/vnodlocal-synchronizer.py b/xos/synchronizer/vnodlocal-synchronizer.py
new file mode 100755
index 0000000..64d0b08
--- /dev/null
+++ b/xos/synchronizer/vnodlocal-synchronizer.py
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+
+# This imports and runs ../../xos-observer.py
+
+import importlib
+import os
+import sys
+
+observer_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../../synchronizers/base")
+sys.path.append(observer_path)
+mod = importlib.import_module("xos-synchronizer")
+mod.main()
diff --git a/xos/synchronizer/vnodlocal_synchronizer_config b/xos/synchronizer/vnodlocal_synchronizer_config
new file mode 100644
index 0000000..1b2bd33
--- /dev/null
+++ b/xos/synchronizer/vnodlocal_synchronizer_config
@@ -0,0 +1,37 @@
+[plc]
+name=plc
+deployment=VICCI
+
+[db]
+name=xos
+user=postgres
+password=password
+host=localhost
+port=5432
+
+[api]
+host=128.112.171.237
+port=8000
+ssl_key=None
+ssl_cert=None
+ca_ssl_cert=None
+ratelimit_enabled=0
+omf_enabled=0
+mail_support_address=support@localhost
+nova_enabled=True
+
+[observer]
+name=vnodlocal
+dependency_graph=/opt/xos/synchronizers/vnodlocal/model-deps
+steps_dir=/opt/xos/synchronizers/vnodlocal/steps
+sys_dir=/opt/xos/synchronizers/vnodlocal/sys
+deleters_dir=/opt/xos/synchronizers/vnodlocal/deleters
+log_file=console
+driver=None
+pretend=False
+backoff_disabled=True
+fofum_disabled=True
+
+[feefie]
+client_id='vicci_dev_central'
+user_id='pl'
diff --git a/xos/vnodlocalservice-onboard.yaml b/xos/vnodlocalservice-onboard.yaml
new file mode 100644
index 0000000..937cfab
--- /dev/null
+++ b/xos/vnodlocalservice-onboard.yaml
@@ -0,0 +1,20 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Onboard the vnodlocal service
+
+imports:
+ - custom_types/xos.yaml
+
+topology_template:
+ node_templates:
+ vnodlocal:
+ type: tosca.nodes.ServiceController
+ properties:
+ base_url: file:///opt/xos_services/metronet-local/xos/
+ # The following will concatenate with base_url automatically, if
+ # base_url is non-null.
+ models: models.py
+ admin: admin.py
+ rest_service: subdirectory:vnodlocalservice api/service/vnodlocalservice/vnodlocalservice.py
+ synchronizer: synchronizer/manifest
+ synchronizer_run: vnodlocal-synchronizer.py