move over onos service from xos repo
diff --git a/xos/admin.py b/xos/admin.py
new file mode 100644
index 0000000..fb0f1d7
--- /dev/null
+++ b/xos/admin.py
@@ -0,0 +1,124 @@
+from django.contrib import admin
+
+from services.onos.models import *
+from django import forms
+from django.utils.safestring import mark_safe
+from django.contrib.auth.admin import UserAdmin
+from django.contrib.admin.widgets import FilteredSelectMultiple
+from django.contrib.auth.forms import ReadOnlyPasswordHashField
+from django.contrib.auth.signals import user_logged_in
+from django.utils import timezone
+from django.contrib.contenttypes import generic
+from suit.widgets import LinkedSelect
+from core.admin import ServiceAppAdmin,SliceInline,ServiceAttrAsTabInline, ReadOnlyAwareAdmin, XOSTabularInline, ServicePrivilegeInline, TenantRootTenantInline, TenantRootPrivilegeInline, TenantAttrAsTabInline
+from core.middleware import get_request
+
+from functools import update_wrapper
+from django.contrib.admin.views.main import ChangeList
+from django.core.urlresolvers import reverse
+from django.contrib.admin.utils import quote
+
+class ONOSServiceForm(forms.ModelForm):
+    rest_hostname = forms.CharField(required=False)
+    rest_port = forms.CharField(required=False)
+    no_container = forms.BooleanField(required=False)
+#    external_hostname = forms.CharField(required=False)
+#    external_container = forms.CharField(required=False)
+
+    def __init__(self,*args,**kwargs):
+        super (ONOSServiceForm,self ).__init__(*args,**kwargs)
+        if self.instance:
+            # fields for the attributes
+            self.fields['rest_hostname'].initial = self.instance.rest_hostname
+            self.fields['rest_port'].initial = self.instance.rest_port
+            self.fields['no_container'].initial = self.instance.no_container
+#            self.fields['external_hostname'].initial = self.instance.external_hostname
+#            self.fields['external_container'].initial = self.instance.external_hostname
+
+    def save(self, commit=True):
+        self.instance.rest_hostname = self.cleaned_data.get("rest_hostname")
+        self.instance.rest_port = self.cleaned_data.get("rest_port")
+        self.instance.no_container = self.cleaned_data.get("no_container")
+#        self.instance.external_hostname = self.cleaned_data.get("external_hostname")
+#        self.instance.external_container = self.cleaned_data.get("external_container")
+        return super(ONOSServiceForm, self).save(commit=commit)
+
+    class Meta:
+        model = ONOSService
+
+class ONOSServiceAdmin(ReadOnlyAwareAdmin):
+    model = ONOSService
+    verbose_name = "ONOS Service"
+    verbose_name_plural = "ONOS Services"
+    list_display = ("backend_status_icon", "name", "enabled")
+    list_display_links = ('backend_status_icon', 'name', )
+    fieldsets = [(None, {'fields': ['backend_status_text', 'name','enabled','versionNumber', 'description',"view_url","icon_url", "rest_hostname", "rest_port", "no_container" ], 'classes':['suit-tab suit-tab-general']})]
+    readonly_fields = ('backend_status_text', )
+    inlines = [SliceInline,ServiceAttrAsTabInline,ServicePrivilegeInline]
+    form = ONOSServiceForm
+
+    extracontext_registered_admins = True
+
+    user_readonly_fields = ["name", "enabled", "versionNumber", "description"]
+
+    suit_form_tabs =(('general', 'ONOS Service Details'),
+        ('administration', 'Administration'),
+        ('slices','Slices'),
+        ('serviceattrs','Additional Attributes'),
+        ('serviceprivileges','Privileges'),
+    )
+
+    suit_form_includes = (('onosadmin.html', 'top', 'administration'),
+                           )
+
+    def queryset(self, request):
+        return ONOSService.get_service_objects_by_user(request.user)
+
+class ONOSAppForm(forms.ModelForm):
+    creator = forms.ModelChoiceField(queryset=User.objects.all())
+    name = forms.CharField()
+    dependencies = forms.CharField(required=False)
+
+    def __init__(self,*args,**kwargs):
+        super (ONOSAppForm,self ).__init__(*args,**kwargs)
+        self.fields['kind'].widget.attrs['readonly'] = True
+        self.fields['provider_service'].queryset = ONOSService.get_service_objects().all()
+        if self.instance:
+            # fields for the attributes
+            self.fields['creator'].initial = self.instance.creator
+            self.fields['name'].initial = self.instance.name
+            self.fields['dependencies'].initial = self.instance.dependencies
+        if (not self.instance) or (not self.instance.pk):
+            # default fields for an 'add' form
+            self.fields['kind'].initial = ONOS_KIND
+            self.fields['creator'].initial = get_request().user
+            if ONOSService.get_service_objects().exists():
+               self.fields["provider_service"].initial = ONOSService.get_service_objects().all()[0]
+
+    def save(self, commit=True):
+        self.instance.creator = self.cleaned_data.get("creator")
+        self.instance.name = self.cleaned_data.get("name")
+        self.instance.dependencies = self.cleaned_data.get("dependencies")
+        return super(ONOSAppForm, self).save(commit=commit)
+
+    class Meta:
+        model = ONOSApp
+
+class ONOSAppAdmin(ReadOnlyAwareAdmin):
+    list_display = ('backend_status_icon', 'name', )
+    list_display_links = ('backend_status_icon', 'name')
+    fieldsets = [ (None, {'fields': ['backend_status_text', 'kind', 'name', 'provider_service', 'subscriber_service', 'service_specific_attribute', "dependencies",
+                                     'creator'],
+                          'classes':['suit-tab suit-tab-general']})]
+    readonly_fields = ('backend_status_text', 'instance', 'service_specific_attribute')
+    inlines = [TenantAttrAsTabInline]
+    form = ONOSAppForm
+
+    suit_form_tabs = (('general','Details'), ('tenantattrs', 'Attributes'))
+
+    def queryset(self, request):
+        return ONOSApp.get_tenant_objects_by_user(request.user)
+
+admin.site.register(ONOSService, ONOSServiceAdmin)
+admin.site.register(ONOSApp, ONOSAppAdmin)
+
diff --git a/xos/api/service/onos.py b/xos/api/service/onos.py
new file mode 100644
index 0000000..a143b3d
--- /dev/null
+++ b/xos/api/service/onos.py
@@ -0,0 +1,87 @@
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+from rest_framework.reverse import reverse
+from rest_framework import serializers
+from rest_framework import generics
+from rest_framework import status
+from core.models import *
+from django.forms import widgets
+from services.onos.models import ONOSService
+from xos.apibase import XOSListCreateAPIView, XOSRetrieveUpdateDestroyAPIView, XOSPermissionDenied
+from api.xosapi_helpers import PlusModelSerializer, XOSViewSet, ReadOnlyField
+
+class ONOSServiceSerializer(PlusModelSerializer):
+    id = ReadOnlyField()
+    rest_hostname = serializers.CharField(required=False)
+    rest_port = serializers.CharField(default="8181")
+    no_container = serializers.BooleanField(default=False)
+    node_key = serializers.CharField(required=False)
+
+    humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+    class Meta:
+        model = ONOSService
+        fields = ('humanReadableName', 'id', 'rest_hostname', 'rest_port', 'no_container', 'node_key')
+
+    def getHumanReadableName(self, obj):
+        return obj.__unicode__()
+
+class ServiceAttributeSerializer(serializers.Serializer):
+    id = ReadOnlyField()
+    name = serializers.CharField(required=False)
+    value = serializers.CharField(required=False)
+
+class ONOSServiceViewSet(XOSViewSet):
+    base_name = "onos"
+    method_name = "onos"
+    method_kind = "viewset"
+    queryset = ONOSService.get_service_objects().all()
+    serializer_class = ONOSServiceSerializer
+
+    custom_serializers = {"set_attribute": ServiceAttributeSerializer}
+
+    @classmethod
+    def get_urlpatterns(self, api_path="^"):
+        patterns = super(ONOSServiceViewSet, self).get_urlpatterns(api_path=api_path)
+
+        patterns.append( self.detail_url("attributes/$", {"get": "get_attributes", "post": "add_attribute"}, "attributes") )
+        patterns.append( self.detail_url("attributes/(?P<attribute>[0-9]+)/$", {"get": "get_attribute", "put": "set_attribute", "delete": "delete_attribute"}, "attribute") )
+
+        return patterns
+
+    def get_attributes(self, request, pk=None):
+        svc = self.get_object()
+        return Response(ServiceAttributeSerializer(svc.serviceattributes.all(), many=True).data)
+
+    def add_attribute(self, request, pk=None):
+        svc = self.get_object()
+        ser = ServiceAttributeSerializer(data=request.data)
+        ser.is_valid(raise_exception = True)
+        att = ServiceAttribute(service=svc, **ser.validated_data)
+        att.save()
+        return Response(ServiceAttributeSerializer(att).data)
+
+    def get_attribute(self, request, pk=None, attribute=None):
+        svc = self.get_object()
+        att = ServiceAttribute.objects.get(pk=attribute)
+        return Response(ServiceAttributeSerializer(att).data)
+
+    def set_attribute(self, request, pk=None, attribute=None):
+        svc = self.get_object()
+        att = ServiceAttribute.objects.get(pk=attribute)
+        ser = ServicettributeSerializer(att, data=request.data)
+        ser.is_valid(raise_exception = True)
+        att.name = ser.validated_data.get("name", att.name)
+        att.value = ser.validated_data.get("value", att.value)
+        att.save()
+        return Response(ServiceAttributeSerializer(att).data)
+
+    def delete_attribute(self, request, pk=None, attribute=None):
+        att = ServiceAttribute.objects.get(pk=attribute)
+        att.delete()
+        return Response(status=status.HTTP_204_NO_CONTENT)
+
+
+
+
+
+
diff --git a/xos/api/tenant/onos/app.py b/xos/api/tenant/onos/app.py
new file mode 100644
index 0000000..481057d
--- /dev/null
+++ b/xos/api/tenant/onos/app.py
@@ -0,0 +1,91 @@
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+from rest_framework.reverse import reverse
+from rest_framework import serializers
+from rest_framework import generics
+from rest_framework import status
+from core.models import *
+from django.forms import widgets
+from services.onos.models import ONOSService, ONOSApp
+from xos.apibase import XOSListCreateAPIView, XOSRetrieveUpdateDestroyAPIView, XOSPermissionDenied
+from api.xosapi_helpers import PlusModelSerializer, XOSViewSet, ReadOnlyField
+
+def get_default_onos_service():
+    onos_services = ONOSService.get_service_objects().all()
+    if onos_services:
+        return onos_services[0].id
+    return None
+
+class ONOSAppSerializer(PlusModelSerializer):
+    id = ReadOnlyField()
+    name = serializers.CharField()
+    dependencies = serializers.CharField()
+
+    humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+    class Meta:
+        model = ONOSApp
+        fields = ('humanReadableName', 'id', 'name', 'dependencies')
+
+    def getHumanReadableName(self, obj):
+        return obj.__unicode__()
+
+class TenantAttributeSerializer(serializers.Serializer):
+    id = ReadOnlyField()
+    name = serializers.CharField(required=False)
+    value = serializers.CharField(required=False)
+
+class ONOSAppViewSet(XOSViewSet):
+    base_name = "app"
+    method_name = "app"
+    method_kind = "viewset"
+    queryset = ONOSApp.get_tenant_objects().all()
+    serializer_class = ONOSAppSerializer
+
+    custom_serializers = {"set_attribute": TenantAttributeSerializer}
+
+    @classmethod
+    def get_urlpatterns(self, api_path="^"):
+        patterns = super(ONOSAppViewSet, self).get_urlpatterns(api_path=api_path)
+
+        patterns.append( self.detail_url("attributes/$", {"get": "get_attributes", "post": "add_attribute"}, "attributes") )
+        patterns.append( self.detail_url("attributes/(?P<attribute>[0-9]+)/$", {"get": "get_attribute", "put": "set_attribute", "delete": "delete_attribute"}, "attribute") )
+
+        return patterns
+
+    def get_attributes(self, request, pk=None):
+        app = self.get_object()
+        return Response(TenantAttributeSerializer(app.tenantattributes.all(), many=True).data)
+
+    def add_attribute(self, request, pk=None):
+        app = self.get_object()
+        ser = TenantAttributeSerializer(data=request.data)
+        ser.is_valid(raise_exception = True)
+        att = TenantAttribute(tenant=app, **ser.validated_data)
+        att.save()
+        return Response(TenantAttributeSerializer(att).data)
+
+    def get_attribute(self, request, pk=None, attribute=None):
+        app = self.get_object()
+        att = TenantAttribute.objects.get(pk=attribute)
+        return Response(TenantAttributeSerializer(att).data)
+
+    def set_attribute(self, request, pk=None, attribute=None):
+        app = self.get_object()
+        att = TenantAttribute.objects.get(pk=attribute)
+        ser = TenantAttributeSerializer(att, data=request.data)
+        ser.is_valid(raise_exception = True)
+        att.name = ser.validated_data.get("name", att.name)
+        att.value = ser.validated_data.get("value", att.value)
+        att.save()
+        return Response(TenantAttributeSerializer(att).data)
+
+    def delete_attribute(self, request, pk=None, attribute=None):
+        att = TenantAttribute.objects.get(pk=attribute)
+        att.delete()
+        return Response(status=status.HTTP_204_NO_CONTENT)
+
+
+
+
+
+
diff --git a/xos/models.py b/xos/models.py
new file mode 100644
index 0000000..20fa73f
--- /dev/null
+++ b/xos/models.py
@@ -0,0 +1,146 @@
+from django.db import models
+from core.models import Service, PlCoreBase, Slice, Instance, Tenant, TenantWithContainer, Node, Image, User, Flavor, Subscriber
+from core.models.plcorebase import StrippedCharField
+import os
+from django.db import models, transaction
+from django.forms.models import model_to_dict
+from django.db.models import Q
+from operator import itemgetter, attrgetter, methodcaller
+import traceback
+from xos.exceptions import *
+from core.models import SlicePrivilege, SitePrivilege
+from sets import Set
+
+ONOS_KIND = "onos"
+
+class ONOSService(Service):
+    KIND = ONOS_KIND
+
+    class Meta:
+        app_label = "onos"
+        verbose_name = "ONOS Service"
+        proxy = True
+
+    default_attributes = {"rest_hostname": "",
+                          "rest_port": "8181",
+                          "no_container": False,
+                          "node_key": ""}
+
+    @property
+    def rest_hostname(self):
+        return self.get_attribute("rest_hostname", self.default_attributes["rest_hostname"])
+
+    @rest_hostname.setter
+    def rest_hostname(self, value):
+        self.set_attribute("rest_hostname", value)
+
+    @property
+    def rest_port(self):
+        return self.get_attribute("rest_port", self.default_attributes["rest_port"])
+
+    @rest_port.setter
+    def rest_port(self, value):
+        self.set_attribute("rest_port", value)
+
+    @property
+    def no_container(self):
+        return self.get_attribute("no_container", self.default_attributes["no_container"])
+
+    @no_container.setter
+    def no_container(self, value):
+        self.set_attribute("no_container", value)
+
+    @property
+    def node_key(self):
+        return self.get_attribute("node_key", self.default_attributes["node_key"])
+
+    @node_key.setter
+    def node_key(self, value):
+        self.set_attribute("node_key", value)
+
+
+class ONOSApp(Tenant):   # aka 'ONOSTenant'
+    class Meta:
+        proxy = True
+
+    KIND = ONOS_KIND
+
+    default_attributes = {"name": "",
+                          "install_dependencies": "",
+                          "dependencies": ""}
+    def __init__(self, *args, **kwargs):
+        onos_services = ONOSService.get_service_objects().all()
+        if onos_services:
+            self._meta.get_field("provider_service").default = onos_services[0].id
+        super(ONOSApp, self).__init__(*args, **kwargs)
+
+    @property
+    def creator(self):
+        from core.models import User
+        if getattr(self, "cached_creator", None):
+            return self.cached_creator
+        creator_id=self.get_attribute("creator_id")
+        if not creator_id:
+            return None
+        users=User.objects.filter(id=creator_id)
+        if not users:
+            return None
+        user=users[0]
+        self.cached_creator = users[0]
+        return user
+
+    @creator.setter
+    def creator(self, value):
+        if value:
+            value = value.id
+        if (value != self.get_attribute("creator_id", None)):
+            self.cached_creator=None
+        self.set_attribute("creator_id", value)
+
+    @property
+    def name(self):
+        return self.get_attribute("name", self.default_attributes["name"])
+
+    @name.setter
+    def name(self, value):
+        self.set_attribute("name", value)
+
+    @property
+    def dependencies(self):
+        return self.get_attribute("dependencies", self.default_attributes["dependencies"])
+
+    @dependencies.setter
+    def dependencies(self, value):
+        self.set_attribute("dependencies", value)
+
+    @property
+    def install_dependencies(self):
+        return self.get_attribute("install_dependencies", self.default_attributes["install_dependencies"])
+
+    @install_dependencies.setter
+    def install_dependencies(self, value):
+        self.set_attribute("install_dependencies", value)
+
+    def save(self, *args, **kwargs):
+        if not self.creator:
+            if not getattr(self, "caller", None):
+                # caller must be set when creating a vCPE since it creates a slice
+                raise XOSProgrammingError("ONOSApp's self.caller was not set")
+            self.creator = self.caller
+            if not self.creator:
+                raise XOSProgrammingError("ONOSApp's self.creator was not set")
+
+        super(ONOSApp, self).save(*args, **kwargs)
+        model_policy_onos_app(self.pk)
+
+# TODO: Probably don't need this...
+def model_policy_onos_app(pk):
+    # TODO: this should be made in to a real model_policy
+    with transaction.atomic():
+        oa = ONOSApp.objects.select_for_update().filter(pk=pk)
+        if not oa:
+            return
+        oa = oa[0]
+        #oa.manage_container()
+
+
diff --git a/xos/onos-onboard.yaml b/xos/onos-onboard.yaml
new file mode 100644
index 0000000..eece363
--- /dev/null
+++ b/xos/onos-onboard.yaml
@@ -0,0 +1,27 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+description: Onboard the exampleservice
+
+imports:
+   - custom_types/xos.yaml
+
+topology_template:
+  node_templates:
+    servicecontroller#onos:
+      type: tosca.nodes.ServiceController
+      properties:
+          base_url: file:///opt/xos_services/onos-service/xos/
+          # The following will concatenate with base_url automatically, if
+          # base_url is non-null.
+          models: models.py
+          admin: admin.py
+          admin_template: templates/onosadmin.html
+          synchronizer: synchronizer/manifest
+          synchronizer_run: onos-synchronizer.py
+          #tosca_custom_types: exampleservice.yaml
+          tosca_resource: tosca/resources/onosservice.py, tosca/resources/onosapp.py
+          rest_service: subdirectory:vsg api/service/onos.py
+          rest_tenant: subdirectory:onos api/tenant/onos/app.py
+          private_key: file:///opt/xos/key_import/onos_rsa
+          public_key: file:///opt/xos/key_import/onos_rsa.pub
+
diff --git a/xos/synchronizer/manifest b/xos/synchronizer/manifest
new file mode 100644
index 0000000..b96216a
--- /dev/null
+++ b/xos/synchronizer/manifest
@@ -0,0 +1,16 @@
+manifest
+onos-ext-volt-event-publisher-1.0-SNAPSHOT.oar
+scripts/dockerip.sh
+steps/sync_onosapp.py
+steps/sync_onosapp_nocontainer.yaml
+steps/sync_onosservice.py
+steps/sync_onosservice.yaml
+steps/sync_onosapp.yaml
+onos-ext-notifier-1.0-SNAPSHOT.oar
+start.sh
+stop.sh
+model-deps
+onos_synchronizer_config
+supervisor/onos-observer.conf
+run.sh
+onos-synchronizer.py
diff --git a/xos/synchronizer/model-deps b/xos/synchronizer/model-deps
new file mode 100644
index 0000000..2da80e0
--- /dev/null
+++ b/xos/synchronizer/model-deps
@@ -0,0 +1,5 @@
+{
+    "ONOSApp": [
+        "ONOSService"
+    ]
+}
diff --git a/xos/synchronizer/onos-ext-notifier-1.0-SNAPSHOT.oar b/xos/synchronizer/onos-ext-notifier-1.0-SNAPSHOT.oar
new file mode 100644
index 0000000..23c6fcd
--- /dev/null
+++ b/xos/synchronizer/onos-ext-notifier-1.0-SNAPSHOT.oar
Binary files differ
diff --git a/xos/synchronizer/onos-ext-volt-event-publisher-1.0-SNAPSHOT.oar b/xos/synchronizer/onos-ext-volt-event-publisher-1.0-SNAPSHOT.oar
new file mode 100644
index 0000000..244f589
--- /dev/null
+++ b/xos/synchronizer/onos-ext-volt-event-publisher-1.0-SNAPSHOT.oar
Binary files differ
diff --git a/xos/synchronizer/onos-synchronizer.py b/xos/synchronizer/onos-synchronizer.py
new file mode 100755
index 0000000..84bec4f
--- /dev/null
+++ b/xos/synchronizer/onos-synchronizer.py
@@ -0,0 +1,11 @@
+#!/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/onos_synchronizer_config b/xos/synchronizer/onos_synchronizer_config
new file mode 100644
index 0000000..c6ceece
--- /dev/null
+++ b/xos/synchronizer/onos_synchronizer_config
@@ -0,0 +1,41 @@
+
+[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=onos
+dependency_graph=/opt/xos/synchronizers/onos/model-deps
+steps_dir=/opt/xos/synchronizers/onos/steps
+sys_dir=/opt/xos/synchronizers/onos/sys
+deleters_dir=/opt/xos/synchronizers/onos/deleters
+log_file=console
+driver=None
+pretend=False
+backoff_disabled=True
+save_ansible_output=True
+# set proxy_ssh to false on cloudlab
+proxy_ssh=False
+full_setup=True
+
+[feefie]
+client_id='vicci_dev_central'
+user_id='pl'
diff --git a/xos/synchronizer/run.sh b/xos/synchronizer/run.sh
new file mode 100755
index 0000000..b108d5b
--- /dev/null
+++ b/xos/synchronizer/run.sh
@@ -0,0 +1,6 @@
+#if [[ ! -e ./vcpe-observer.py ]]; then
+#    ln -s ../../xos-observer.py vcpe-observer.py
+#fi
+
+export XOS_DIR=/opt/xos
+python onos-synchronizer.py  -C $XOS_DIR/synchronizers/onos/onos_synchronizer_config
diff --git a/xos/synchronizer/scripts/dockerip.sh b/xos/synchronizer/scripts/dockerip.sh
new file mode 100644
index 0000000..732c3fe
--- /dev/null
+++ b/xos/synchronizer/scripts/dockerip.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+MODE=`docker inspect --format '{{ .HostConfig.NetworkMode }}' $1  | tr -d '\n' | tr -d '\r'`
+if [[ "$MODE" == "host" ]]; then
+    echo -n "127.0.0.1"
+else
+    docker inspect --format '{{ .NetworkSettings.IPAddress }}' $1 | tr -d '\n' | tr -d '\r'
+fi
+
diff --git a/xos/synchronizer/start.sh b/xos/synchronizer/start.sh
new file mode 100755
index 0000000..f0a1535
--- /dev/null
+++ b/xos/synchronizer/start.sh
@@ -0,0 +1,6 @@
+#if [[ ! -e ./vcpe-observer.py ]]; then
+#    ln -s ../../xos-observer.py vcpe-observer.py
+#fi
+
+export XOS_DIR=/opt/xos
+nohup python onos-synchronizer.py  -C $XOS_DIR/synchronizers/onos/onos_synchronizer_config > /dev/null 2>&1 &
diff --git a/xos/synchronizer/steps/sync_onosapp.py b/xos/synchronizer/steps/sync_onosapp.py
new file mode 100644
index 0000000..78a8cc8
--- /dev/null
+++ b/xos/synchronizer/steps/sync_onosapp.py
@@ -0,0 +1,536 @@
+import hashlib
+import os
+import socket
+import sys
+import base64
+import time
+import re
+import json
+from collections import OrderedDict
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.ansible import run_template
+from synchronizers.base.syncstep import SyncStep
+from synchronizers.base.ansible import run_template_ssh
+from synchronizers.base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
+from core.models import Service, Slice, Controller, ControllerSlice, ControllerUser, Node, TenantAttribute, Tag
+from services.onos.models import ONOSService, ONOSApp
+from xos.logger import Logger, logging
+from services.vrouter.models import VRouterService
+from services.vtn.models import VTNService
+from services.volt.models import VOLTService, VOLTDevice, AccessDevice
+
+# hpclibrary will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+logger = Logger(level=logging.INFO)
+
+class SyncONOSApp(SyncInstanceUsingAnsible):
+    provides=[ONOSApp]
+    observes=ONOSApp
+    requested_interval=0
+    template_name = "sync_onosapp.yaml"
+    #service_key_name = "/opt/xos/synchronizers/onos/onos_key"
+
+    def __init__(self, *args, **kwargs):
+        super(SyncONOSApp, self).__init__(*args, **kwargs)
+
+    def fetch_pending(self, deleted):
+        if (not deleted):
+            objs = ONOSApp.get_tenant_objects().filter(Q(enacted__lt=F('updated')) | Q(enacted=None),Q(lazy_blocked=False))
+        else:
+            objs = ONOSApp.get_deleted_tenant_objects()
+
+        return objs
+
+    def get_instance(self, o):
+        # We assume the ONOS service owns a slice, so pick one of the instances
+        # inside that slice to sync to.
+
+        serv = self.get_onos_service(o)
+
+        if serv.no_container:
+            raise Exception("get_instance() was called on a service that was marked no_container")
+
+        if serv.slices.exists():
+            slice = serv.slices.all()[0]
+            if slice.instances.exists():
+                return slice.instances.all()[0]
+
+        return None
+
+    def get_onos_service(self, o):
+        if not o.provider_service:
+            return None
+
+        onoses = ONOSService.get_service_objects().filter(id=o.provider_service.id)
+        if not onoses:
+            return None
+
+        return onoses[0]
+
+    def is_no_container(self, o):
+        return self.get_onos_service(o).no_container
+
+    def skip_ansible_fields(self, o):
+        return self.is_no_container(o)
+
+    def get_files_dir(self, o):
+        if not hasattr(Config(), "observer_steps_dir"):
+            # make steps_dir mandatory; there's no valid reason for it to not
+            # be defined.
+            raise Exception("observer_steps_dir is not defined in config file")
+
+        step_dir = Config().observer_steps_dir
+
+        return os.path.join(step_dir, "..", "files", str(self.get_onos_service(o).id), o.name)
+
+    def get_cluster_configuration(self, o):
+        instance = self.get_instance(o)
+        if not instance:
+           raise Exception("No instance for ONOS App")
+        node_ips = [socket.gethostbyname(instance.node.name)]
+
+        ipPrefix = ".".join(node_ips[0].split(".")[:3]) + ".*"
+        result = '{ "nodes": ['
+        result = result + ",".join(['{ "ip": "%s"}' % ip for ip in node_ips])
+        result = result + '], "ipPrefix": "%s"}' % ipPrefix
+        return result
+
+    def get_dynamic_parameter_value(self, o, param):
+        instance = self.get_instance(o)
+        if not instance:
+           raise Exception("No instance for ONOS App")
+        if param == 'rabbit_host':
+            return instance.controller.rabbit_host
+        if param == 'rabbit_user':
+            return instance.controller.rabbit_user
+        if param == 'rabbit_password':
+            return instance.controller.rabbit_password
+        if param == 'keystone_tenant_id':
+            cslice = ControllerSlice.objects.get(slice=instance.slice)
+            if not cslice:
+                raise Exception("Controller slice object for %s does not exist" % instance.slice.name)
+            return cslice.tenant_id
+        if param == 'keystone_user_id':
+            cuser = ControllerUser.objects.get(user=instance.creator)
+            if not cuser:
+                raise Exception("Controller user object for %s does not exist" % instance.creator)
+            return cuser.kuser_id
+
+    def get_node_tag(self, o, node, tagname):
+        tags = Tag.select_by_content_object(node).filter(name=tagname)
+        return tags[0].value
+
+    # Scan attrs for attribute name
+    # If it's not present, save it as a TenantAttribute
+    def attribute_default(self, tenant, attrs, name, default):
+        if name in attrs:
+            value = attrs[name]
+        else:
+            value = default
+            logger.info("saving default value %s for attribute %s" % (value, name))
+            ta = TenantAttribute(tenant=tenant, name=name, value=value)
+            ta.save()
+        return value
+
+    # This function currently assumes a single Deployment and Site
+    def get_vtn_config(self, o, attrs):
+
+        privateGatewayMac = None
+        localManagementIp = None
+        ovsdbPort = None
+        sshPort = None
+        sshUser = None
+        sshKeyFile = None
+        mgmtSubnetBits = None
+        xosEndpoint = None
+        xosUser = None
+        xosPassword = None
+
+        # VTN-specific configuration from the VTN Service
+        vtns = VTNService.get_service_objects().all()
+        if vtns:
+            vtn = vtns[0]
+            privateGatewayMac = vtn.privateGatewayMac
+            localManagementIp = vtn.localManagementIp
+            ovsdbPort = vtn.ovsdbPort
+            sshPort = vtn.sshPort
+            sshUser = vtn.sshUser
+            sshKeyFile = vtn.sshKeyFile
+            mgmtSubnetBits = vtn.mgmtSubnetBits
+            xosEndpoint = vtn.xosEndpoint
+            xosUser = vtn.xosUser
+            xosPassword = vtn.xosPassword
+
+        # OpenStack endpoints and credentials
+        keystone_server = "http://keystone:5000/v2.0/"
+        user_name = "admin"
+        password = "ADMIN_PASS"
+        controllers = Controller.objects.all()
+        if controllers:
+            controller = controllers[0]
+            keystone_server = controller.auth_url
+            user_name = controller.admin_user
+            tenant_name = controller.admin_tenant
+            password = controller.admin_password
+
+        data = {
+            "apps" : {
+                "org.onosproject.cordvtn" : {
+                    "cordvtn" : {
+                        "privateGatewayMac" : privateGatewayMac,
+                        "localManagementIp": localManagementIp,
+                        "ovsdbPort": ovsdbPort,
+                        "ssh": {
+                            "sshPort": sshPort,
+                            "sshUser": sshUser,
+                            "sshKeyFile": sshKeyFile
+                        },
+                        "openstack": {
+                            "endpoint": keystone_server,
+                            "tenant": tenant_name,
+                            "user": user_name,
+                            "password": password
+                        },
+                        "xos": {
+                            "endpoint": xosEndpoint,
+                            "user": xosUser,
+                            "password": xosPassword
+                        },
+                        "publicGateways": [],
+                        "nodes" : []
+                    }
+                }
+            }
+        }
+
+        # Generate apps->org.onosproject.cordvtn->cordvtn->nodes
+        nodes = Node.objects.all()
+        for node in nodes:
+            nodeip = socket.gethostbyname(node.name)
+
+            try:
+                bridgeId = self.get_node_tag(o, node, "bridgeId")
+                dataPlaneIntf = self.get_node_tag(o, node, "dataPlaneIntf")
+                dataPlaneIp = self.get_node_tag(o, node, "dataPlaneIp")
+            except:
+                logger.error("not adding node %s to the VTN configuration" % node.name)
+                continue
+
+            node_dict = {
+                "hostname": node.name,
+                "hostManagementIp": "%s/%s" % (nodeip, mgmtSubnetBits),
+                "bridgeId": bridgeId,
+                "dataPlaneIntf": dataPlaneIntf,
+                "dataPlaneIp": dataPlaneIp
+            }
+            data["apps"]["org.onosproject.cordvtn"]["cordvtn"]["nodes"].append(node_dict)
+
+        # Generate apps->org.onosproject.cordvtn->cordvtn->publicGateways
+        # Pull the gateway information from vRouter
+        vrouters = VRouterService.get_service_objects().all()
+        if vrouters:
+            for gateway in vrouters[0].get_gateways():
+                gatewayIp = gateway['gateway_ip'].split('/',1)[0]
+                gatewayMac = gateway['gateway_mac']
+                gateway_dict = {
+                    "gatewayIp": gatewayIp,
+                    "gatewayMac": gatewayMac
+                }
+                data["apps"]["org.onosproject.cordvtn"]["cordvtn"]["publicGateways"].append(gateway_dict)
+
+        return json.dumps(data, indent=4, sort_keys=True)
+
+    def get_volt_network_config(self, o, attrs):
+        try:
+            volt = VOLTService.get_service_objects().all()[0]
+        except:
+            return None
+
+        devices = []
+        for voltdev in volt.volt_devices.all():
+            access_devices = []
+            for access in voltdev.access_devices.all():
+                access_device = {
+                    "uplink" : access.uplink,
+                    "vlan" : access.vlan
+                }
+                access_devices.append(access_device)
+
+            if voltdev.access_agent:
+                agent = voltdev.access_agent
+                olts = {}
+                for port_mapping in agent.port_mappings.all():
+                    olts[port_mapping.port] = port_mapping.mac
+                agent_config = {
+                    "olts" : olts,
+                    "mac" : agent.mac
+                }
+
+            device = {
+                voltdev.openflow_id : {
+                    "accessDevice" : access_devices,
+                    "accessAgent" : agent_config
+                },
+                "basic" : {
+                    "driver" : voltdev.driver
+                }
+            }
+            devices.append(device)
+
+        data = {
+            "devices" : devices
+        }
+        return json.dumps(data, indent=4, sort_keys=True)
+
+    def get_volt_component_config(self, o, attrs):
+        data = {
+            "org.ciena.onos.ext_notifier.KafkaNotificationBridge":{
+                "rabbit.user": "<rabbit_user>",
+                "rabbit.password": "<rabbit_password>",
+                "rabbit.host": "<rabbit_host>",
+                "publish.kafka": "false",
+                "publish.rabbit": "true",
+                "volt.events.rabbit.topic": "notifications.info",
+                "volt.events.rabbit.exchange": "voltlistener",
+                "volt.events.opaque.info": "{project_id: <keystone_tenant_id>, user_id: <keystone_user_id>}",
+                "publish.volt.events": "true"
+            }
+        }
+        return json.dumps(data, indent=4, sort_keys=True)
+
+    def get_vrouter_network_config(self, o, attrs):
+        # From the onosproject wiki:
+        # https://wiki.onosproject.org/display/ONOS/vRouter
+        data = {
+            "devices" : {
+                "of:00000000000000b1" : {
+                    "basic" : {
+                        "driver" : "softrouter"
+                    }
+                }
+            },
+            "ports" : {
+                "of:00000000000000b1/1" : {
+                    "interfaces" : [
+                        {
+                            "name" : "b1-1",
+                            "ips"  : [ "10.0.1.2/24" ],
+                            "mac"  : "00:00:00:00:00:01"
+                        }
+                    ]
+                },
+                "of:00000000000000b1/2" : {
+                    "interfaces" : [
+                        {
+                            "name" : "b1-2",
+                            "ips"  : [ "10.0.2.2/24" ],
+                            "mac"  : "00:00:00:00:00:01"
+                        }
+                    ]
+                },
+                "of:00000000000000b1/3" : {
+                    "interfaces" : [
+                        {
+                            "name" : "b1-3",
+                            "ips"  : [ "10.0.3.2/24" ],
+                            "mac"  : "00:00:00:00:00:01"
+                        }
+                    ]
+                },
+                "of:00000000000000b1/4" : {
+                    "interfaces" : [
+                        {
+                            "name" : "b1-4",
+                            "ips"  : [ "10.0.4.2/24" ],
+                            "mac"  : "00:00:00:00:00:02",
+                            "vlan" : "100"
+                        }
+                    ]
+                }
+            },
+            "apps" : {
+                "org.onosproject.router" : {
+                    "router" : {
+                        "controlPlaneConnectPoint" : "of:00000000000000b1/5",
+                        "ospfEnabled" : "true",
+                        "interfaces" : [ "b1-1", "b1-2", "b1-2", "b1-4" ]
+                    }
+                }
+            }
+        }
+        return json.dumps(data, indent=4, sort_keys=True)
+
+    def write_configs(self, o):
+        o.config_fns = []
+        o.rest_configs = []
+        o.component_configs = []
+        o.files_dir = self.get_files_dir(o)
+
+        if not os.path.exists(o.files_dir):
+            os.makedirs(o.files_dir)
+
+        # Combine the service attributes with the tenant attributes. Tenant
+        # attribute can override service attributes.
+        attrs = o.provider_service.serviceattribute_dict
+        attrs.update(o.tenantattribute_dict)
+
+        ordered_attrs = attrs.keys()
+
+        onos = self.get_onos_service(o)
+        if onos.node_key:
+            file(os.path.join(o.files_dir, "node_key"),"w").write(onos.node_key)
+            o.node_key_fn="node_key"
+        else:
+            o.node_key_fn=None
+
+        o.early_rest_configs=[]
+        if ("cordvtn" in o.dependencies) and (not self.is_no_container(o)):
+            # For VTN, since it's running in a docker host container, we need
+            # to make sure it configures the cluster using the right ip addresses.
+            # NOTE: rest_onos/v1/cluster/configuration/ will reboot the cluster and
+            #   must go first.
+            name="rest_onos/v1/cluster/configuration/"
+            value= self.get_cluster_configuration(o)
+            fn = name[5:].replace("/","_")
+            endpoint = name[5:]
+            file(os.path.join(o.files_dir, fn),"w").write(" " +value)
+            o.early_rest_configs.append( {"endpoint": endpoint, "fn": fn} )
+
+        # Generate config files and save them to the appropriate tenant attributes
+        configs = []
+        for key, value in attrs.iteritems():
+            if key == "autogenerate" and value:
+                for config in value.split(','):
+                    configs.append(config.strip())
+
+        for label in configs:
+            config = None
+            value = None
+            if label == "vtn-network-cfg":
+                # Generate the VTN config file... where should this live?
+                config = "rest_onos/v1/network/configuration/"
+                value = self.get_vtn_config(o, attrs)
+            elif label == "volt-network-cfg":
+                config = "rest_onos/v1/network/configuration/"
+                value = self.get_volt_network_config(o, attrs)
+            elif label == "volt-component-cfg":
+                config = "component_config"
+                value = self.get_volt_component_config(o, attrs)
+            elif label == "vrouter-network-cfg":
+                config = "rest_onos/v1/network/configuration/"
+                value = self.get_vrouter_network_config(o, attrs)
+
+            if config:
+                tas = TenantAttribute.objects.filter(tenant=o, name=config)
+                if tas:
+                    ta = tas[0]
+                    if ta.value != value:
+                        logger.info("updating %s with autogenerated config" % config)
+                        ta.value = value
+                        ta.save()
+                        attrs[config] = value
+                else:
+                    logger.info("saving autogenerated config %s" % config)
+                    ta = TenantAttribute(tenant=o, name=config, value=value)
+                    ta.save()
+                    attrs[config] = value
+
+        for name in attrs.keys():
+            value = attrs[name]
+            if name.startswith("config_"):
+                fn = name[7:] # .replace("_json",".json")
+                o.config_fns.append(fn)
+                file(os.path.join(o.files_dir, fn),"w").write(value)
+            if name.startswith("rest_"):
+                fn = name[5:].replace("/","_")
+                endpoint = name[5:]
+                # Ansible goes out of it's way to make our life difficult. If
+                # 'lookup' sees a file that it thinks contains json, then it'll
+                # insist on parsing and return a json object. We just want
+                # a string, so prepend a space and then strip the space off
+                # later.
+                file(os.path.join(o.files_dir, fn),"w").write(" " +value)
+                o.rest_configs.append( {"endpoint": endpoint, "fn": fn} )
+            if name.startswith("component_config"):
+                components = json.loads(value,object_pairs_hook=OrderedDict)
+                for component in components.keys():
+                    config = components[component]
+                    for key in config.keys():
+                         config_val = config[key]
+                         found = re.findall('<(.+?)>',config_val)
+                         for x in found:
+                            #Get value corresponding to that string
+                            val = self.get_dynamic_parameter_value(o, x)
+                            if val:
+	                       config_val = re.sub('<'+x+'>', val, config_val)
+                            #TODO: else raise an exception?
+	                 o.component_configs.append( {"component": component, "config_params": "'{\""+key+"\":\""+config_val+"\"}'"} )
+
+    def prepare_record(self, o):
+        self.write_configs(o)
+
+    def get_extra_attributes_common(self, o):
+        fields = {}
+
+        # These are attributes that are not dependent on Instance. For example,
+        # REST API stuff.
+
+        onos = self.get_onos_service(o)
+
+        fields["files_dir"] = o.files_dir
+        fields["appname"] = o.name
+        fields["rest_configs"] = o.rest_configs
+        fields["rest_hostname"] = onos.rest_hostname
+        fields["rest_port"] = onos.rest_port
+
+        if o.dependencies:
+            fields["dependencies"] = [x.strip() for x in o.dependencies.split(",")]
+        else:
+            fields["dependencies"] = []
+
+        return fields
+
+    def get_extra_attributes_full(self, o):
+        instance = self.get_instance(o)
+
+        fields = self.get_extra_attributes_common(o)
+
+        fields["config_fns"] = o.config_fns
+        fields["early_rest_configs"] = o.early_rest_configs
+        fields["component_configs"] = o.component_configs
+        fields["node_key_fn"] = o.node_key_fn
+
+        if o.install_dependencies:
+            fields["install_dependencies"] = [x.strip() for x in o.install_dependencies.split(",")]
+        else:
+            fields["install_dependencies"] = []
+
+        if (instance.isolation=="container"):
+            fields["ONOS_container"] = "%s-%s" % (instance.slice.name, str(instance.id))
+        else:
+            fields["ONOS_container"] = "ONOS"
+        return fields
+
+    def get_extra_attributes(self, o):
+        if self.is_no_container(o):
+            return self.get_extra_attributes_common(o)
+        else:
+            return self.get_extra_attributes_full(o)
+
+    def sync_fields(self, o, fields):
+        # the super causes the playbook to be run
+        super(SyncONOSApp, self).sync_fields(o, fields)
+
+    def run_playbook(self, o, fields):
+        if self.is_no_container(o):
+            # There is no machine to SSH to, so use the synchronizer's
+            # run_template method directly.
+            run_template("sync_onosapp_nocontainer.yaml", fields)
+        else:
+            super(SyncONOSApp, self).run_playbook(o, fields)
+
+    def delete_record(self, m):
+        pass
diff --git a/xos/synchronizer/steps/sync_onosapp.yaml b/xos/synchronizer/steps/sync_onosapp.yaml
new file mode 100644
index 0000000..8235286
--- /dev/null
+++ b/xos/synchronizer/steps/sync_onosapp.yaml
@@ -0,0 +1,172 @@
+---
+- hosts: {{ instance_name }}
+  gather_facts: False
+  connection: ssh
+  user: {{ username }}
+  sudo: yes
+  vars:
+    appname: {{ appname }}
+    dependencies: {{ dependencies }}
+{% if component_configs %}
+    component_configs:
+{% for component_config in component_configs %}
+       - component: {{ component_config.component }}
+         config_params: {{  component_config.config_params }}
+{% endfor %}
+{% endif %}
+{% if rest_configs %}
+    rest_configs:
+{% for rest_config in rest_configs %}
+       - endpoint: {{ rest_config.endpoint }}
+         body: "{{ '{{' }} lookup('file', '{{ files_dir }}/{{ rest_config.fn }}') {{ '}}' }}"
+{% endfor %}
+{% endif %}
+{% if early_rest_configs %}
+    early_rest_configs:
+{% for early_rest_config in early_rest_configs %}
+       - endpoint: {{ early_rest_config.endpoint }}
+         body: "{{ '{{' }} lookup('file', '{{ files_dir }}/{{ early_rest_config.fn }}') {{ '}}' }}"
+{% endfor %}
+{% endif %}
+
+  tasks:
+
+  - name: Get Docker IP
+    script: /opt/xos/synchronizers/onos/scripts/dockerip.sh {{ ONOS_container }}
+    register: onosaddr
+
+  - name: Wait for ONOS to come up
+    wait_for:
+      host={{ '{{' }} onosaddr.stdout {{ '}}' }}
+      port={{ '{{' }} item {{ '}}' }}
+      state=present
+    with_items:
+    - 8101
+    - 8181
+    - 9876
+
+  - name: Config file directory
+    file:
+      path=/home/ubuntu/{{ appname }}/
+      state=directory
+
+{% if node_key_fn %}
+  - name: Copy over key
+    copy:
+      src={{ files_dir }}/{{ node_key_fn }}
+      dest=/home/ubuntu/node_key
+
+  - name: Copy node key into container
+    shell: docker cp /home/ubuntu/node_key {{ ONOS_container }}:/root/node_key
+{% endif %}
+
+{% if config_fns %}
+  - name: Copy over configuration files
+    copy:
+      src={{ files_dir }}/{{ '{{' }} item {{ '}}' }}
+      dest=/home/ubuntu/{{ appname }}/{{ '{{' }} item {{ '}}' }}
+    with_items:
+        {% for config_fn in config_fns %}
+        - {{ config_fn }}
+        {% endfor %}
+
+  - name: Make sure config directory exists
+    shell: docker exec {{ ONOS_container }} mkdir -p /root/onos/config/
+    sudo: yes
+
+  - name: Copy config files into container
+    shell: docker cp {{ appname }}/{{ '{{' }} item {{ '}}' }} {{ ONOS_container }}:/root/onos/config/
+    sudo: yes
+    with_items:
+        {% for config_fn in config_fns %}
+        - {{ config_fn }}
+        {% endfor %}
+{% endif %}
+
+  # Don't know how to check for this condition, just wait
+  - name: Wait for ONOS to install the apps
+    wait_for: timeout=15
+
+{% if early_rest_configs %}
+  - name: Add ONOS early configuration values
+    uri:
+      url: http://{{ '{{' }} onosaddr.stdout {{ '}}' }}:8181/{{ '{{' }} item.endpoint {{ '}}' }}
+      body: "{{ '{{' }} item.body {{ '}}' }}"
+      body_format: raw
+      method: POST
+      user: karaf
+      password: karaf
+    with_items: "early_rest_configs"
+
+  # Don't know how to check for this condition, just wait
+  - name: Wait for ONOS to restart
+    wait_for: timeout=15
+{% endif %}
+
+{% if install_dependencies %}
+  - name: Install app file directory
+    file:
+      path=/home/ubuntu/{{ appname }}/apps/
+      state=directory
+
+  - name: Copy over app install files to ONOS host
+    copy:
+      src=/opt/xos/synchronizers/onos/{{ '{{' }} item {{ '}}' }}
+      dest=/home/ubuntu/{{ appname }}/apps/{{ '{{' }} item {{ '}}' }}
+    with_items:
+        {% for install_app in install_dependencies %}
+        - {{ install_app }}
+        {% endfor %}
+
+  - name: POST onos-app install command
+    command: >
+        curl -XPOST -HContent-Type:application/octet-stream -u karaf:karaf --data-binary @/home/ubuntu/{{ appname }}/apps/{{ '{{' }} item {{ '}}' }} http://{{ '{{' }} onosaddr.stdout  {{ '}}' }}:8181/onos/v1/applications
+    with_items:
+        {% for dependency in install_dependencies %}
+        - {{ dependency }}
+        {% endfor %}
+{% endif %}
+
+{% if dependencies %}
+  - name: Add dependencies to ONOS
+    uri:
+      url: http://{{ '{{' }} onosaddr.stdout {{ '}}' }}:8181/onos/v1/applications/{{ '{{' }} item {{ '}}' }}/active
+      method: POST
+      user: karaf
+      password: karaf
+    with_items:
+        {% for dependency in dependencies %}
+        - {{ dependency }}
+        {% endfor %}
+{% endif %}
+
+{% if component_configs %}
+  - name: Add ONOS component configuration values
+    command: >
+        curl -XPOST -HContent-Type:application/json -u karaf:karaf -d {{ '{{' }} item.config_params | to_json {{ '}}' }} http://{{ '{{' }} onosaddr.stdout  {{ '}}' }}:8181/onos/v1/configuration/{{
+ '{{' }} item.component {{ '}}' }}
+    with_items: "component_configs"
+
+#    uri:
+#      url: http://{{ '{{' }} onosaddr.stdout {{ '}}' }}:8181/onos/v1/configuration/{{ '{{' }} item.component {{ '}}' }} #http://localhost:8181/onos/v1/configuration/
+#      body: "{{ '{{' }} item.config_params | to_json {{ '}}' }}"
+#      body_format: json
+#      method: POST
+#      user: karaf
+#      password: karaf
+#    with_items: "component_configs"
+{% endif %}
+
+{% if rest_configs %}
+# Do this after services have been activated, or it will cause an exception.
+# vOLT will re-read its net config; vbng may not.
+  - name: Add ONOS configuration values
+    uri:
+      url: http://{{ '{{' }} onosaddr.stdout {{ '}}' }}:8181/{{ '{{' }} item.endpoint {{ '}}' }} #http://localhost:8181/onos/v1/network/configuration/
+      body: "{{ '{{' }} item.body {{ '}}' }}"
+      body_format: raw
+      method: POST
+      user: karaf
+      password: karaf
+    with_items: "rest_configs"
+{% endif %}
diff --git a/xos/synchronizer/steps/sync_onosapp_nocontainer.yaml b/xos/synchronizer/steps/sync_onosapp_nocontainer.yaml
new file mode 100644
index 0000000..5aad569
--- /dev/null
+++ b/xos/synchronizer/steps/sync_onosapp_nocontainer.yaml
@@ -0,0 +1,57 @@
+---
+- hosts: 127.0.0.1
+  connection: local
+  vars:
+    appname: {{ appname }}
+    dependencies: {{ dependencies }}
+{% if component_configs %}
+    component_configs:
+{% for component_config in component_configs %}
+       - component: {{ component_config.component }}
+         config_params: {{  component_config.config_params }}
+{% endfor %}
+{% endif %}
+{% if rest_configs %}
+    rest_configs:
+{% for rest_config in rest_configs %}
+       - endpoint: {{ rest_config.endpoint }}
+         body: "{{ '{{' }} lookup('file', '{{ files_dir }}/{{ rest_config.fn }}') {{ '}}' }}"
+{% endfor %}
+{% endif %}
+{% if early_rest_configs %}
+    early_rest_configs:
+{% for early_rest_config in early_rest_configs %}
+       - endpoint: {{ early_rest_config.endpoint }}
+         body: "{{ '{{' }} lookup('file', '{{ files_dir }}/{{ early_rest_config.fn }}') {{ '}}' }}"
+{% endfor %}
+{% endif %}
+    rest_hostname: {{ rest_hostname }}
+    rest_port: {{ rest_port }}
+
+  tasks:
+{% if dependencies %}
+  - name: Add dependencies to ONOS
+    uri:
+      url: http://{{ '{{' }} rest_hostname {{ '}}' }}:{{ '{{' }} rest_port {{ '}}' }}/onos/v1/applications/{{ '{{' }} item {{ '}}' }}/active
+      method: POST
+      user: karaf
+      password: karaf
+    with_items:
+        {% for dependency in dependencies %}
+        - {{ dependency }}
+        {% endfor %}
+{% endif %}
+
+{% if rest_configs %}
+# Do this after services have been activated, or it will cause an exception.
+# vOLT will re-read its net config; vbng may not.
+  - name: Add ONOS configuration values
+    uri:
+      url: http://{{ '{{' }} rest_hostname {{ '}}' }}:{{ '{{' }} rest_port {{ '}}' }}/{{ '{{' }} item.endpoint {{ '}}' }} #http://localhost:8181/onos/v1/network/configuration/
+      body: "{{ '{{' }} item.body {{ '}}' }}"
+      body_format: raw
+      method: POST
+      user: karaf
+      password: karaf
+    with_items: "rest_configs"
+{% endif %}
diff --git a/xos/synchronizer/steps/sync_onosservice.py b/xos/synchronizer/steps/sync_onosservice.py
new file mode 100644
index 0000000..ce446cf
--- /dev/null
+++ b/xos/synchronizer/steps/sync_onosservice.py
@@ -0,0 +1,80 @@
+import hashlib
+import os
+import socket
+import sys
+import base64
+import time
+from django.db.models import F, Q
+from xos.config import Config
+from synchronizers.base.syncstep import SyncStep
+from synchronizers.base.ansible import run_template_ssh
+from synchronizers.base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
+from core.models import Service, Slice
+from services.onos.models import ONOSService, ONOSApp
+from xos.logger import Logger, logging
+
+# hpclibrary will be in steps/..
+parentdir = os.path.join(os.path.dirname(__file__),"..")
+sys.path.insert(0,parentdir)
+
+logger = Logger(level=logging.INFO)
+
+class SyncONOSService(SyncInstanceUsingAnsible):
+    provides=[ONOSService]
+    observes=ONOSService
+    requested_interval=0
+    template_name = "sync_onosservice.yaml"
+    #service_key_name = "/opt/xos/synchronizers/onos/onos_key"
+
+    def __init__(self, *args, **kwargs):
+        super(SyncONOSService, self).__init__(*args, **kwargs)
+
+    def fetch_pending(self, deleted):
+        if (not deleted):
+            objs = ONOSService.get_service_objects().filter(Q(enacted__lt=F('updated')) | Q(enacted=None),Q(lazy_blocked=False))
+        else:
+            objs = ONOSService.get_deleted_service_objects()
+
+        return objs
+
+    def get_instance(self, o):
+        # We assume the ONOS service owns a slice, so pick one of the instances
+        # inside that slice to sync to.
+
+        serv = o
+
+        if serv.slices.exists():
+            slice = serv.slices.all()[0]
+            if slice.instances.exists():
+                return slice.instances.all()[0]
+
+        return None
+
+    def get_extra_attributes(self, o):
+        fields={}
+        fields["instance_hostname"] = self.get_instance(o).instance_name.replace("_","-")
+        fields["appname"] = o.name
+        fields["ONOS_container"] = "ONOS"
+        return fields
+
+    def sync_record(self, o):
+        if o.no_container:
+            logger.info("no work to do for onos service, because o.no_container is set",extra=o.tologdict())
+            o.save()
+        else:
+            super(SyncONOSService, self).sync_record(o)
+
+    def sync_fields(self, o, fields):
+        # the super causes the playbook to be run
+        super(SyncONOSService, self).sync_fields(o, fields)
+
+    def run_playbook(self, o, fields):
+        instance = self.get_instance(o)
+        if (instance.isolation=="container"):
+            # If the instance is already a container, then we don't need to
+            # install ONOS.
+            return
+        super(SyncONOSService, self).run_playbook(o, fields)
+
+    def delete_record(self, m):
+        pass
diff --git a/xos/synchronizer/steps/sync_onosservice.yaml b/xos/synchronizer/steps/sync_onosservice.yaml
new file mode 100644
index 0000000..a51fde5
--- /dev/null
+++ b/xos/synchronizer/steps/sync_onosservice.yaml
@@ -0,0 +1,66 @@
+---
+- hosts: {{ instance_name }}
+  gather_facts: False
+  connection: ssh
+  user: ubuntu
+  sudo: yes
+
+  tasks:
+
+  - name: Fix /etc/hosts
+    lineinfile:
+      dest=/etc/hosts
+      regexp="127.0.0.1 localhost"
+      line="127.0.0.1 localhost {{ instance_hostname }}"
+
+  - name: Add repo key
+    apt_key:
+      keyserver=hkp://pgp.mit.edu:80
+      id=58118E89F3A912897C070ADBF76221572C52609D
+
+  - name: Install Docker repo
+    apt_repository:
+      repo="deb https://apt.dockerproject.org/repo ubuntu-trusty main"
+      state=present
+
+  - name: Install Docker
+    apt:
+      name={{ '{{' }} item {{ '}}' }}
+      state=latest
+      update_cache=yes
+    with_items:
+    - docker-engine
+    - python-pip
+    - python-httplib2
+
+  - name: Install docker-py
+    pip:
+      name=docker-py
+      state=latest
+
+  - name: Start ONOS container
+    docker:
+      docker_api_version: "1.18"
+      name: {{ ONOS_container }}
+      # was: reloaded
+      state: running
+      image: onosproject/onos
+      ports:
+      - "6653:6653"
+      - "8101:8101"
+      - "8181:8181"
+      - "9876:9876"
+
+  - name: Get Docker IP
+    script: /opt/xos/synchronizers/onos/scripts/dockerip.sh {{ ONOS_container }}
+    register: dockerip
+
+  - name: Wait for ONOS to come up
+    wait_for:
+      host={{ '{{' }} dockerip.stdout {{ '}}' }}
+      port={{ '{{' }} item {{ '}}' }}
+      state=present
+    with_items:
+    - 8101
+    - 8181
+    - 9876
diff --git a/xos/synchronizer/stop.sh b/xos/synchronizer/stop.sh
new file mode 100755
index 0000000..17d6eb7
--- /dev/null
+++ b/xos/synchronizer/stop.sh
@@ -0,0 +1 @@
+pkill -9 -f onos-observer.py
diff --git a/xos/synchronizer/supervisor/onos-observer.conf b/xos/synchronizer/supervisor/onos-observer.conf
new file mode 100644
index 0000000..995644e
--- /dev/null
+++ b/xos/synchronizer/supervisor/onos-observer.conf
@@ -0,0 +1,9 @@
+[supervisord]
+logfile=/var/log/supervisord.log ; (main log file;default $CWD/supervisord.log)
+pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
+nodaemon=true
+
+[program:synchronizer]
+command=python /opt/xos/synchronizers/onos/onos-synchronizer.py -C /opt/xos/synchronizers/onos/onos_synchronizer_config
+stderr_logfile=/var/log/supervisor/synchronizer.err.log
+stdout_logfile=/var/log/supervisor/synchronizer.out.log
diff --git a/xos/templates/onosadmin.html b/xos/templates/onosadmin.html
new file mode 100644
index 0000000..e50660e
--- /dev/null
+++ b/xos/templates/onosadmin.html
@@ -0,0 +1,6 @@
+<div class = "row text-center">
+    <div class="col-xs-12">
+        <a class="btn btn-primary" href="/admin/onos/onosapp/">ONOS Apps</a>
+    </div>
+</div>
+
diff --git a/xos/tosca/resources/onosapp.py b/xos/tosca/resources/onosapp.py
new file mode 100644
index 0000000..a65c717
--- /dev/null
+++ b/xos/tosca/resources/onosapp.py
@@ -0,0 +1,68 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+import pdb
+
+from core.models import User, TenantAttribute, Service
+from services.onos.models import ONOSApp, ONOSService
+
+from xosresource import XOSResource
+
+class XOSONOSApp(XOSResource):
+    provides = ["tosca.nodes.ONOSApp", "tosca.nodes.ONOSvBNGApp", "tosca.nodes.ONOSvOLTApp", "tosca.nodes.ONOSVTNApp", "tosca.nodes.ONOSvRouterApp"]
+    xos_model = ONOSApp
+    copyin_props = ["service_specific_id", "dependencies", "install_dependencies"]
+
+    def get_xos_args(self, throw_exception=True):
+        args = super(XOSONOSApp, self).get_xos_args()
+
+        # provider_service is mandatory and must be the ONOS Service
+        provider_name = self.get_requirement("tosca.relationships.TenantOfService", throw_exception=throw_exception)
+        if provider_name:
+            args["provider_service"] = self.get_xos_object(ONOSService, throw_exception=throw_exception, name=provider_name)
+
+        # subscriber_service is optional and can be any service
+        subscriber_name = self.get_requirement("tosca.relationships.UsedByService", throw_exception=False)
+        if subscriber_name:
+            args["subscriber_service"] = self.get_xos_object(Service, throw_exception=throw_exception, name=subscriber_name)
+
+        return args
+
+    def get_existing_objs(self):
+        objs = ONOSApp.get_tenant_objects().all()
+        objs = [x for x in objs if x.name == self.obj_name]
+        return objs
+
+    def set_tenant_attr(self, obj, prop_name, value):
+        value = self.try_intrinsic_function(value)
+        if value:
+            attrs = TenantAttribute.objects.filter(tenant=obj, name=prop_name)
+            if attrs:
+                attr = attrs[0]
+                if attr.value != value:
+                    self.info("updating attribute %s" % prop_name)
+                    attr.value = value
+                    attr.save()
+            else:
+                self.info("adding attribute %s" % prop_name)
+                ta = TenantAttribute(tenant=obj, name=prop_name, value=value)
+                ta.save()
+
+    def postprocess(self, obj):
+        props = self.nodetemplate.get_properties()
+        for (k,d) in props.items():
+            v = d.value
+            if k.startswith("config_"):
+                self.set_tenant_attr(obj, k, v)
+            elif k.startswith("rest_") and (k!="rest_hostname") and (k!="rest_port"):
+                self.set_tenant_attr(obj, k, v)
+            elif k.startswith("component_config"):
+                self.set_tenant_attr(obj, k, v)
+            elif k == "autogenerate":
+                self.set_tenant_attr(obj, k, v)
+
+    def can_delete(self, obj):
+        return super(XOSONOSApp, self).can_delete(obj)
diff --git a/xos/tosca/resources/onosservice.py b/xos/tosca/resources/onosservice.py
new file mode 100644
index 0000000..3540dd0
--- /dev/null
+++ b/xos/tosca/resources/onosservice.py
@@ -0,0 +1,41 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+
+from core.models import ServiceAttribute
+from services.onos.models import ONOSService
+
+from service import XOSService
+
+class XOSONOSService(XOSService):
+    provides = "tosca.nodes.ONOSService"
+    xos_model = ONOSService
+    copyin_props = ["view_url", "icon_url", "enabled", "published", "public_key", "versionNumber", "rest_hostname", "rest_port", "no_container", "node_key"]
+
+    def set_service_attr(self, obj, prop_name, value):
+        value = self.try_intrinsic_function(value)
+        if value:
+            attrs = ServiceAttribute.objects.filter(service=obj, name=prop_name)
+            if attrs:
+                attr = attrs[0]
+                if attr.value != value:
+                    self.info("updating attribute %s" % prop_name)
+                    attr.value = value
+                    attr.save()
+            else:
+                self.info("adding attribute %s" % prop_name)
+                ta = ServiceAttribute(service=obj, name=prop_name, value=value)
+                ta.save()
+
+    def postprocess(self, obj):
+        props = self.nodetemplate.get_properties()
+        for (k,d) in props.items():
+            v = d.value
+            if k.startswith("config_"):
+                self.set_service_attr(obj, k, v)
+            elif k.startswith("rest_")  and (k!="rest_hostname") and (k!="rest_port"):
+                self.set_service_attr(obj, k, v)
+