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