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)
+