Merge branch 'feature/api-cleanup' of github.com:open-cloud/xos into feature/api-cleanup
diff --git a/xos/api/examples/exampleservice/get_exampletenant_message.sh b/xos/api/examples/exampleservice/get_exampletenant_message.sh
new file mode 100755
index 0000000..96ce65c
--- /dev/null
+++ b/xos/api/examples/exampleservice/get_exampletenant_message.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+# this example illustrates using a custom REST API endpoint
+
+source ./config.sh
+
+if [[ "$#" -ne 1 ]]; then
+ echo "Syntax: get_exampletenant_message.sh <id>"
+ exit -1
+fi
+
+ID=$1
+
+curl -H "Accept: application/json; indent=4" -u $AUTH -X GET $HOST/api/tenant/exampletenant/$ID/message/
diff --git a/xos/api/examples/exampleservice/put_exampletenant_message.sh b/xos/api/examples/exampleservice/put_exampletenant_message.sh
new file mode 100755
index 0000000..bf0810d
--- /dev/null
+++ b/xos/api/examples/exampleservice/put_exampletenant_message.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# this example illustrates using a custom REST API endpoint
+
+source ./config.sh
+
+if [[ "$#" -ne 2 ]]; then
+ echo "Syntax: put_exampletenant_message.sh <id> <message>"
+ exit -1
+fi
+
+ID=$1
+NEW_MESSAGE=$2
+
+DATA=$(cat <<EOF
+{"tenant_message": "$NEW_MESSAGE"}
+EOF
+)
+
+curl -H "Accept: application/json; indent=4" -H "Content-Type: application/json" -u $AUTH -X PUT -d "$DATA" $HOST/api/tenant/exampletenant/$ID/message/
diff --git a/xos/api/examples/util.sh b/xos/api/examples/util.sh
index 8373498..b3d3060 100644
--- a/xos/api/examples/util.sh
+++ b/xos/api/examples/util.sh
@@ -29,4 +29,24 @@
# echo "(found volt id %1)" >&2
echo $ID
-}
\ No newline at end of file
+}
+
+function lookup_subscriber_vsg {
+ JSON=`curl -f -s -u $AUTH -X GET $HOST/api/tenant/cord/subscriber/$1/`
+ if [[ $? != 0 ]]; then
+ echo "function lookup_subscriber_vsg failed to read subscriber with arg $1" >&2
+ echo "See CURL output below:" >&2
+ curl -s -u $AUTH -X GET $HOST/api/tenant/cord/account_num_lookup/$1/ >&2
+ exit -1
+ fi
+ ID=`echo $JSON | python -c "import json,sys; print json.load(sys.stdin)['related'].get('vsg_id','')"`
+ if [[ $ID == "" ]]; then
+ echo "there is no volt for this subscriber" >&2
+ exit -1
+ fi
+
+ # echo "(found vsg id %1)" >&2
+
+ echo $ID
+}
+
diff --git a/xos/api/examples/vtr/add_truckroll_for_subscriber.sh b/xos/api/examples/vtr/add_truckroll_for_subscriber.sh
new file mode 100755
index 0000000..3a3ec41
--- /dev/null
+++ b/xos/api/examples/vtr/add_truckroll_for_subscriber.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+source ./config.sh
+source ./util.sh
+
+ACCOUNT_NUM=1238
+
+SUBSCRIBER_ID=$(lookup_account_num $ACCOUNT_NUM)
+if [[ $? != 0 ]]; then
+ exit -1
+fi
+
+VSG_ID=$(lookup_subscriber_vsg $SUBSCRIBER_ID)
+if [[ $? != 0 ]]; then
+ exit -1
+fi
+
+DATA=$(cat <<EOF
+{"target_id": $SUBSCRIBER_ID,
+ "scope": "container",
+ "test": "ping",
+ "argument": "8.8.8.8"}
+EOF
+)
+
+curl -H "Accept: application/json; indent=4" -H "Content-Type: application/json" -u $AUTH -X POST -d "$DATA" $HOST/api/tenant/truckroll/
diff --git a/xos/api/examples/vtr/config.sh b/xos/api/examples/vtr/config.sh
new file mode 100644
index 0000000..92d703c
--- /dev/null
+++ b/xos/api/examples/vtr/config.sh
@@ -0,0 +1,2 @@
+# see config.sh in the parent directory
+source ../config.sh
diff --git a/xos/api/examples/vtr/delete_truckroll.sh b/xos/api/examples/vtr/delete_truckroll.sh
new file mode 100755
index 0000000..ee31aeb
--- /dev/null
+++ b/xos/api/examples/vtr/delete_truckroll.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+source ./config.sh
+
+if [[ "$#" -ne 1 ]]; then
+ echo "Syntax: delete_truckroll.sh <id>"
+ exit -1
+fi
+
+ID=$1
+
+curl -H "Accept: application/json; indent=4" -u $AUTH -X DELETE $HOST/api/tenant/truckroll/$ID/
diff --git a/xos/api/examples/vtr/list_truckrolls.sh b/xos/api/examples/vtr/list_truckrolls.sh
new file mode 100755
index 0000000..f1d7e87
--- /dev/null
+++ b/xos/api/examples/vtr/list_truckrolls.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+source ./config.sh
+
+curl -H "Accept: application/json; indent=4" -u $AUTH -X GET $HOST/api/tenant/truckroll/
diff --git a/xos/api/examples/vtr/util.sh b/xos/api/examples/vtr/util.sh
new file mode 100644
index 0000000..7b66903
--- /dev/null
+++ b/xos/api/examples/vtr/util.sh
@@ -0,0 +1 @@
+source ../util.sh
diff --git a/xos/api/import_methods.py b/xos/api/import_methods.py
index d53556c..1b5e3ca 100644
--- a/xos/api/import_methods.py
+++ b/xos/api/import_methods.py
@@ -1,6 +1,7 @@
from django.views.generic import View
from django.conf.urls import patterns, url, include
from rest_framework.routers import DefaultRouter
+from xosapi_helpers import XOSIndexViewSet
import os, sys
import inspect
import importlib
@@ -35,6 +36,7 @@
return module
def import_api_methods(dirname=None, api_path="api", api_module="api"):
+ has_index_view = False
subdirs=[]
urlpatterns=[]
@@ -61,10 +63,12 @@
method_name = os.path.join(api_path, method_name)
else:
method_name = api_path
+ has_index_view = True
view_urls.append( (method_kind, method_name, classname, c) )
elif os.path.isdir(pathname):
urlpatterns.extend(import_api_methods(pathname, os.path.join(api_path, fn), api_module+"." + fn))
+ subdirs.append(fn)
for view_url in view_urls:
if view_url[0] == "list":
@@ -75,6 +79,9 @@
viewset = view_url[3]
urlpatterns.extend(viewset.get_urlpatterns(api_path="^"+api_path+"/"))
+ if not has_index_view:
+ urlpatterns.append(url('^' + api_path + '/$', XOSIndexViewSet.as_view({'get': 'list'}, view_urls=view_urls, subdirs=subdirs), name="api_path"+"_index"))
+
return urlpatterns
urlpatterns = import_api_methods()
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/cord/subscriber.py b/xos/api/tenant/cord/subscriber.py
index 89f42b9..b33c7ad 100644
--- a/xos/api/tenant/cord/subscriber.py
+++ b/xos/api/tenant/cord/subscriber.py
@@ -129,6 +129,11 @@
queryset = CordSubscriberNew.get_tenant_objects().select_related().all()
serializer_class = CordSubscriberSerializer
+ custom_serializers = {"set_features": FeatureSerializer,
+ "set_feature": FeatureSerializer,
+ "set_identities": IdentitySerializer,
+ "set_identity": IdentitySerializer}
+
@classmethod
def get_urlpatterns(self, api_path="^"):
patterns = super(CordSubscriberViewSet, self).get_urlpatterns(api_path=api_path)
diff --git a/xos/api/tenant/exampletenant.py b/xos/api/tenant/exampletenant.py
index b046a88..c50680f 100644
--- a/xos/api/tenant/exampletenant.py
+++ b/xos/api/tenant/exampletenant.py
@@ -44,6 +44,9 @@
def get_urlpatterns(self, api_path="^"):
patterns = super(ExampleTenantViewSet, self).get_urlpatterns(api_path=api_path)
+ # example to demonstrate adding a custom endpoint
+ patterns.append( self.detail_url("message/$", {"get": "get_message", "put": "set_message"}, "message") )
+
return patterns
def list(self, request):
@@ -53,3 +56,13 @@
return Response(serializer.data)
+ def get_message(self, request, pk=None):
+ example_tenant = self.get_object()
+ return Response({"tenant_message": example_tenant.tenant_message})
+
+ def set_message(self, request, pk=None):
+ example_tenant = self.get_object()
+ example_tenant.tenant_message = request.data["tenant_message"]
+ example_tenant.save()
+ return Response({"tenant_message": example_tenant.tenant_message})
+
diff --git a/xos/api/tenant/onos/__init__.py b/xos/api/tenant/onos/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/xos/api/tenant/onos/__init__.py
@@ -0,0 +1 @@
+
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/api/tenant/truckroll.py b/xos/api/tenant/truckroll.py
index ea0200d..b5e9e3f 100644
--- a/xos/api/tenant/truckroll.py
+++ b/xos/api/tenant/truckroll.py
@@ -14,7 +14,7 @@
def get_default_vtr_service():
vtr_services = VTRService.get_service_objects().all()
if vtr_services:
- return vtr_services[0].id
+ return vtr_services[0]
return None
class VTRTenantForAPI(VTRTenant):
diff --git a/xos/api/xosapi_helpers.py b/xos/api/xosapi_helpers.py
index ee3ed00..6f5665c 100644
--- a/xos/api/xosapi_helpers.py
+++ b/xos/api/xosapi_helpers.py
@@ -97,3 +97,30 @@
patterns.append(url(self.get_api_method_path() + '(?P<pk>[a-zA-Z0-9\-]+)/$', self.as_view({'get': 'retrieve', 'put': 'update', 'post': 'update', 'delete': 'destroy', 'patch': 'partial_update'}), name=self.base_name+'_detail'))
return patterns
+
+ def get_serializer_class(self):
+ if hasattr(self, "custom_serializers") and hasattr(self, "action") and (self.action in self.custom_serializers):
+ return self.custom_serializers[self.action]
+ else:
+ return super(XOSViewSet, self).get_serializer_class()
+
+class XOSIndexViewSet(viewsets.ViewSet):
+ view_urls=[]
+ subdirs=[]
+
+ def __init__(self, view_urls, subdirs):
+ self.view_urls = view_urls
+ self.subdirs = subdirs
+ super(XOSIndexViewSet, self).__init__()
+
+ def list(self, request):
+ endpoints = []
+ for view_url in self.view_urls:
+ method_name = view_url[1].split("/")[-1]
+ endpoints.append(method_name)
+
+ for subdir in self.subdirs:
+ endpoints.append(subdir)
+
+ return Response({"endpoints": endpoints})
+
diff --git a/xos/core/admin.py b/xos/core/admin.py
index 0608e4e..3ace086 100644
--- a/xos/core/admin.py
+++ b/xos/core/admin.py
@@ -1266,7 +1266,7 @@
list_display_links = ('backend_status_icon', 'name', )
class NodeForm(forms.ModelForm):
- labels = forms.ModelMultipleChoiceField(
+ nodelabels = forms.ModelMultipleChoiceField(
queryset=NodeLabel.objects.all(),
required=False,
help_text="Select which labels apply to this node",
@@ -1286,12 +1286,12 @@
super(NodeForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk:
- self.fields['labels'].initial = self.instance.labels.all()
+ self.fields['nodelabels'].initial = self.instance.nodelabels.all()
def save(self, commit=True):
node = super(NodeForm, self).save(commit=False)
- node.labels = self.cleaned_data['labels']
+ node.nodelabels = self.cleaned_data['nodelabels']
if commit:
node.save()
@@ -1314,7 +1314,7 @@
inlines = [TagInline,InstanceInline]
fieldsets = [('Node Details', {'fields': ['backend_status_text', 'name', 'site_deployment'], 'classes':['suit-tab suit-tab-details']}),
- ('Labels', {'fields': ['labels'], 'classes':['suit-tab suit-tab-labels']})]
+ ('Labels', {'fields': ['nodelabels'], 'classes':['suit-tab suit-tab-labels']})]
readonly_fields = ('backend_status_text', )
user_readonly_fields = ['name','site_deployment']
diff --git a/xos/core/models/node.py b/xos/core/models/node.py
index b825787..d464532 100644
--- a/xos/core/models/node.py
+++ b/xos/core/models/node.py
@@ -31,6 +31,6 @@
class NodeLabel(PlCoreBase):
name = StrippedCharField(max_length=200, help_text="label name", unique=True)
- node = models.ManyToManyField(Node, related_name="labels", blank=True)
+ node = models.ManyToManyField(Node, related_name="nodelabels", blank=True)
def __unicode__(self): return u'%s' % (self.name)
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index 124feb5..aca4bb0 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -458,7 +458,7 @@
nodes = Node.objects.all()
if self.label:
- nodes = nodes.filter(labels__name=self.label)
+ nodes = nodes.filter(nodelabels__name=self.label)
nodes = list(nodes)
@@ -778,6 +778,8 @@
value = models.TextField(help_text="Attribute Value")
tenant = models.ForeignKey(Tenant, related_name='tenantattributes', help_text="The Tenant this attribute is associated with")
+ def __unicode__(self): return u'%s-%s' % (self.name, self.id)
+
class TenantRootRole(PlCoreBase):
ROLE_CHOICES = (('admin','Admin'), ('access','Access'))