Merge branch 'master' of github.com:open-cloud/xos
diff --git a/synchronizers b/synchronizers
new file mode 120000
index 0000000..d587898
--- /dev/null
+++ b/synchronizers
@@ -0,0 +1 @@
+observers
\ No newline at end of file
diff --git a/xos/apigen/hpc-api.template.py b/xos/apigen/hpc-api.template.py
new file mode 100644
index 0000000..291403a
--- /dev/null
+++ b/xos/apigen/hpc-api.template.py
@@ -0,0 +1,211 @@
+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 rest_framework.generics import GenericAPIView
+from hpc.models import *
+from django.forms import widgets
+from rest_framework import filters
+from django.conf.urls import patterns, url
+from rest_framework.exceptions import PermissionDenied as RestFrameworkPermissionDenied
+from django.core.exceptions import PermissionDenied as DjangoPermissionDenied
+from apibase import XOSRetrieveUpdateDestroyAPIView, XOSListCreateAPIView, XOSNotAuthenticated
+
+if hasattr(serializers, "ReadOnlyField"):
+ # rest_framework 3.x
+ IdField = serializers.ReadOnlyField
+else:
+ # rest_framework 2.x
+ IdField = serializers.Field
+
+"""
+ Schema of the generator object:
+ all: Set of all Model objects
+ all_if(regex): Set of Model objects that match regex
+
+ Model object:
+ plural: English plural of object name
+ camel: CamelCase version of object name
+ refs: list of references to other Model objects
+ props: list of properties minus refs
+
+ TODO: Deal with subnets
+"""
+
+def get_hpc_REST_patterns():
+ return patterns('',
+ url(r'^hpcapi/$', hpc_api_root),
+ {% for object in generator.all %}
+ url(r'hpcapi/{{ object.rest_name }}/$', {{ object.camel }}List.as_view(), name='{{ object.singular }}-list'),
+ url(r'hpcapi/{{ object.rest_name }}/(?P<pk>[a-zA-Z0-9\-]+)/$', {{ object.camel }}Detail.as_view(), name ='{{ object.singular }}-detail'),
+ {% endfor %}
+ )
+
+@api_view(['GET'])
+def hpc_api_root(request, format=None):
+ return Response({
+ {% for object in generator.all %}'{{ object.plural }}': reverse('{{ object }}-list', request=request, format=format),
+ {% endfor %}
+ })
+
+# Based on serializers.py
+
+class XOSModelSerializer(serializers.ModelSerializer):
+ def save_object(self, obj, **kwargs):
+
+ """ rest_framework can't deal with ManyToMany relations that have a
+ through table. In xos, most of the through tables we have
+ use defaults or blank fields, so there's no reason why we shouldn't
+ be able to save these objects.
+
+ So, let's strip out these m2m relations, and deal with them ourself.
+ """
+ obj._complex_m2m_data={};
+ if getattr(obj, '_m2m_data', None):
+ for relatedObject in obj._meta.get_all_related_many_to_many_objects():
+ if (relatedObject.field.rel.through._meta.auto_created):
+ # These are non-trough ManyToMany relations and
+ # can be updated just fine
+ continue
+ fieldName = relatedObject.get_accessor_name()
+ if fieldName in obj._m2m_data.keys():
+ obj._complex_m2m_data[fieldName] = (relatedObject, obj._m2m_data[fieldName])
+ del obj._m2m_data[fieldName]
+
+ serializers.ModelSerializer.save_object(self, obj, **kwargs);
+
+ for (accessor, stuff) in obj._complex_m2m_data.items():
+ (relatedObject, data) = stuff
+ through = relatedObject.field.rel.through
+ local_fieldName = relatedObject.field.m2m_reverse_field_name()
+ remote_fieldName = relatedObject.field.m2m_field_name()
+
+ # get the current set of existing relations
+ existing = through.objects.filter(**{local_fieldName: obj});
+
+ data_ids = [item.id for item in data]
+ existing_ids = [getattr(item,remote_fieldName).id for item in existing]
+
+ #print "data_ids", data_ids
+ #print "existing_ids", existing_ids
+
+ # remove relations that are in 'existing' but not in 'data'
+ for item in list(existing):
+ if (getattr(item,remote_fieldName).id not in data_ids):
+ print "delete", getattr(item,remote_fieldName)
+ item.delete() #(purge=True)
+
+ # add relations that are in 'data' but not in 'existing'
+ for item in data:
+ if (item.id not in existing_ids):
+ #print "add", item
+ newModel = through(**{local_fieldName: obj, remote_fieldName: item})
+ newModel.save()
+
+{% for object in generator.all %}
+
+class {{ object.camel }}Serializer(serializers.HyperlinkedModelSerializer):
+ id = IdField()
+ {% for ref in object.refs %}
+ {% if ref.multi %}
+ {{ ref.plural }} = serializers.HyperlinkedRelatedField(many=True, read_only=True, view_name='{{ ref }}-detail')
+ {% else %}
+ {{ ref }} = serializers.HyperlinkedRelatedField(read_only=True, view_name='{{ ref }}-detail')
+ {% endif %}
+ {% endfor %}
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = {{ object.camel }}
+ fields = ('humanReadableName', 'validators', {% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
+
+class {{ object.camel }}IdSerializer(XOSModelSerializer):
+ id = IdField()
+ {% for ref in object.refs %}
+ {% if ref.multi %}
+ {{ ref.plural }} = serializers.PrimaryKeyRelatedField(many=True, queryset = {{ ref.camel }}.objects.all())
+ {% else %}
+ {{ ref }} = serializers.PrimaryKeyRelatedField( queryset = {{ ref.camel }}.objects.all())
+ {% endif %}
+ {% endfor %}
+ humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
+ validators = serializers.SerializerMethodField("getValidators")
+ def getHumanReadableName(self, obj):
+ return str(obj)
+ def getValidators(self, obj):
+ try:
+ return obj.getValidators()
+ except:
+ return None
+ class Meta:
+ model = {{ object.camel }}
+ fields = ('humanReadableName', 'validators', {% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
+
+
+{% endfor %}
+
+serializerLookUp = {
+{% for object in generator.all %}
+ {{ object.camel }}: {{ object.camel }}Serializer,
+{% endfor %}
+ None: None,
+ }
+
+# Based on core/views/*.py
+{% for object in generator.all %}
+
+class {{ object.camel }}List(XOSListCreateAPIView):
+ queryset = {{ object.camel }}.objects.select_related().all()
+ serializer_class = {{ object.camel }}Serializer
+ id_serializer_class = {{ object.camel }}IdSerializer
+ filter_backends = (filters.DjangoFilterBackend,)
+ filter_fields = ({% for prop in object.props %}'{{ prop }}',{% endfor %}{% for ref in object.refs %}{%if ref.multi %}'{{ ref.plural }}'{% else %}'{{ ref }}'{% endif %},{% endfor %})
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return {{ object.camel }}.select_by_user(self.request.user)
+
+
+class {{ object.camel }}Detail(XOSRetrieveUpdateDestroyAPIView):
+ queryset = {{ object.camel }}.objects.select_related().all()
+ serializer_class = {{ object.camel }}Serializer
+ id_serializer_class = {{ object.camel }}IdSerializer
+
+ def get_serializer_class(self):
+ no_hyperlinks=False
+ if hasattr(self.request,"QUERY_PARAMS"):
+ no_hyperlinks = self.request.QUERY_PARAMS.get('no_hyperlinks', False)
+ if (no_hyperlinks):
+ return self.id_serializer_class
+ else:
+ return self.serializer_class
+
+ def get_queryset(self):
+ if (not self.request.user.is_authenticated()):
+ raise XOSNotAuthenticated()
+ return {{ object.camel }}.select_by_user(self.request.user)
+
+ # update() is handled by XOSRetrieveUpdateDestroyAPIView
+
+ # destroy() is handled by XOSRetrieveUpdateDestroyAPIView
+
+{% endfor %}
diff --git a/xos/configurations/common/Dockerfile.common b/xos/configurations/common/Dockerfile.common
index 5329142..03b479a 100644
--- a/xos/configurations/common/Dockerfile.common
+++ b/xos/configurations/common/Dockerfile.common
@@ -48,7 +48,7 @@
RUN pip install django-ipware
RUN pip install django-encrypted-fields
RUN pip install python-keyczar
-RUN pip install pygraphviz
+RUN pip install pygraphviz --install-option="--include-path=/usr/include/graphviz" --install-option="--library-path=/usr/lib/graphviz/"
RUN pip install dnslib
RUN DEBIAN_FRONTEND=noninteractive apt-get install -y python-keystoneclient
diff --git a/xos/configurations/cord/README.md b/xos/configurations/cord/README.md
index 5683601..423f4d6 100644
--- a/xos/configurations/cord/README.md
+++ b/xos/configurations/cord/README.md
@@ -38,9 +38,20 @@
## How to run it
-The configuration is intended to be run on [CloudLab](http://cloudlab.us), on the *ctl* node set up by the OpenStack profile.
+The configuration is intended to be run on [CloudLab](http://cloudlab.us).
It launches an XOS container on Cloudlab that runs the XOS develserver. The container is left running in the background.
+To get started on CloudLab:
+* Create an experiment using the *OpenStack-CORD* profile. (You can also use the *OpenStack* profile, but choose *Kilo*
+and disable security groups.)
+* Wait until you get an email from CloudLab with title "OpenStack Instance Finished Setting Up".
+* Login to the *ctl* node of your experiment and run:
+```
+$ git clone https://github.com/open-cloud/xos.git
+$ cd xos/xos/configurations/cord/
+$ make
+```
+
Running `make` in this directory creates the XOS Docker container and runs the TOSCA engine with `cord.yaml` to
configure XOS with the CORD services. In addition, a number of VMs are created:
@@ -50,10 +61,6 @@
1. *Slice mysite_volt*: for running OvS with the `olt` app as controller
1. *Slice mysite_clients*: a subscriber client for end-to-end testing
-After the first VM is created (for running the `virtualbng` app) it is necessary to configure XOS's *service_vbng* with its URL.
-Log into XOS, click on *Services* tab at left, then *service_vbng* icon. Change **Vbng url:** to point to the IP address on
-`flat-lan-1-net` of the VM (it will start with 10.11).
-
Once all the VMs are up and the ONOS apps are configured, XOS should be able to get an address mapping from the `virtualbng`
ONOS app when creating a vCPE. To test this, enter the XOS Docker container and run:
diff --git a/xos/core/views/services.py b/xos/core/views/services.py
index 6d18c26..e11988b 100644
--- a/xos/core/views/services.py
+++ b/xos/core/views/services.py
@@ -121,14 +121,14 @@
g.graph_attr.update(graphdir="TB")
for service in Service.objects.all():
- provided_tenants = CoarseTenant.get_tenant_objects().filter(provider_service=service)
- subscribed_tenants = CoarseTenant.get_tenant_objects().filter(subscriber_service=service)
+ provided_tenants = Tenant.objects.filter(provider_service=service, subscriber_service__isnull=False)
+ subscribed_tenants = Tenant.objects.filter(subscriber_service=service, provider_service__isnull=False)
if not (provided_tenants or subscribed_tenants):
# nodes with no edges aren't interesting
continue
g.add_node(service.id, label=service.name)
- for tenant in CoarseTenant.get_tenant_objects().all():
+ for tenant in Tenant.objects.all():
if (not tenant.provider_service) or (not tenant.subscriber_service):
continue
g.add_edge(tenant.subscriber_service.id, tenant.provider_service.id)
diff --git a/xos/openstack_synchronizer b/xos/openstack_synchronizer
new file mode 120000
index 0000000..ae75af5
--- /dev/null
+++ b/xos/openstack_synchronizer
@@ -0,0 +1 @@
+openstack_observer
\ No newline at end of file
diff --git a/xos/tosca/custom_types/xos.m4 b/xos/tosca/custom_types/xos.m4
index 7a94d8b..055817d 100644
--- a/xos/tosca/custom_types/xos.m4
+++ b/xos/tosca/custom_types/xos.m4
@@ -111,7 +111,7 @@
tosca.nodes.ONOSvBNGApp:
derived_from: tosca.nodes.Root
description: >
- An ONOS Application.
+ An ONOS vBNG Application.
properties:
xos_base_tenant_props
dependencies:
@@ -124,6 +124,16 @@
type: string
required: false
+ tosca.nodes.ONOSvOLTApp:
+ derived_from: tosca.nodes.Root
+ description: >
+ An ONOS vOLT Application.
+ properties:
+ xos_base_tenant_props
+ dependencies:
+ type: string
+ required: false
+
tosca.nodes.VCPEService:
description: >
CORD: The vCPE Service.
diff --git a/xos/tosca/custom_types/xos.yaml b/xos/tosca/custom_types/xos.yaml
index 9b307d6..409e86b 100644
--- a/xos/tosca/custom_types/xos.yaml
+++ b/xos/tosca/custom_types/xos.yaml
@@ -115,7 +115,7 @@
tosca.nodes.ONOSvBNGApp:
derived_from: tosca.nodes.Root
description: >
- An ONOS Application.
+ An ONOS vBNG Application.
properties:
kind:
type: string
@@ -135,6 +135,23 @@
type: string
required: false
+ tosca.nodes.ONOSvOLTApp:
+ derived_from: tosca.nodes.Root
+ description: >
+ An ONOS vOLT Application.
+ properties:
+ kind:
+ type: string
+ default: generic
+ description: Kind of tenant
+ service_specific_id:
+ type: string
+ required: false
+ description: Service specific ID opaque to XOS but meaningful to service
+ dependencies:
+ type: string
+ required: false
+
tosca.nodes.VCPEService:
description: >
CORD: The vCPE Service.
diff --git a/xos/tosca/resources/onosapp.py b/xos/tosca/resources/onosapp.py
index 03c4eb5..111cf9a 100644
--- a/xos/tosca/resources/onosapp.py
+++ b/xos/tosca/resources/onosapp.py
@@ -12,7 +12,7 @@
from xosresource import XOSResource
class XOSONOSApp(XOSResource):
- provides = ["tosca.nodes.ONOSApp", "tosca.nodes.ONOSvBNGApp"]
+ provides = ["tosca.nodes.ONOSApp", "tosca.nodes.ONOSvBNGApp", "tosca.nodes.ONOSvOLTApp"]
xos_model = ONOSApp
copyin_props = ["service_specific_id", "dependencies"]
diff --git a/xos/tosca/samples/onos.yaml b/xos/tosca/samples/onos.yaml
index fc6a3d5..a549515 100644
--- a/xos/tosca/samples/onos.yaml
+++ b/xos/tosca/samples/onos.yaml
@@ -56,6 +56,15 @@
"xosRestPort" : "9999"
}
+ vOLT:
+ type: tosca.nodes.ONOSvOLTApp
+ requirements:
+ - onos_tenant:
+ node: ONOS
+ relationship: tosca.relationships.TenantOfService
+ properties:
+ dependencies: org.onosproject.olt
+
mysite:
type: tosca.nodes.Site