Metronet Local Service
Change-Id: I92e13f49bbdfc60d27496b3c11207a72310731d4
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..ded4f63
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2016 Open Networking Laboratory
+
+ 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d7e8965
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+## VNOD Local Service
+
+This repository contains components needed for the XOS VNOD Local service
+
+Sub-directories
+
+* xos: A service definition for the VNOD Local. This follows the XOS component design for onboarding.
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