Merge branch 'feature/api-cleanup'
diff --git a/xos/api/examples/cord/add_device_to_subscriber.sh b/xos/api/examples/cord/add_device_to_subscriber.sh
new file mode 100755
index 0000000..260b652
--- /dev/null
+++ b/xos/api/examples/cord/add_device_to_subscriber.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+source ./config.sh
+source ./util.sh
+
+ACCOUNT_NUM=1238
+MAC="19:28:37:46:55"
+
+SUBSCRIBER_ID=$(lookup_account_num $ACCOUNT_NUM)
+if [[ $? != 0 ]]; then
+ exit -1
+fi
+
+DATA=$(cat <<EOF
+{"mac": "$MAC"}
+EOF
+)
+
+curl -H "Accept: application/json; indent=4" -H "Content-Type: application/json" -u $AUTH -X POST -d "$DATA" $HOST/api/tenant/cord/subscriber/$SUBSCRIBER_ID/devices/
diff --git a/xos/api/examples/cord/all.sh b/xos/api/examples/cord/all.sh
new file mode 100644
index 0000000..88fce47
--- /dev/null
+++ b/xos/api/examples/cord/all.sh
@@ -0,0 +1,28 @@
+echo add_subscriber
+./add_subscriber.sh
+echo -e "\n update_subscriber"
+./update_subscriber.sh
+echo -e "\n add_volt_to_subscriber"
+./add_volt_to_subscriber.sh
+echo -e "\n get_subscriber"
+./get_subscriber.sh
+echo -e "\n delete_volt_from_subscriber"
+./delete_volt_from_subscriber.sh
+echo -e "\n add_device_to_subscriber"
+./add_device_to_subscriber.sh
+echo -e "\n set_subscriber_device_feature"
+./set_subscriber_device_feature.sh
+echo -e "\n set_subscriber_device_identity"
+./set_subscriber_device_identity.sh
+echo -e "\n get_subscriber_device_feature"
+./get_subscriber_device_feature.sh
+echo -e "\n get_subscriber_device_identity"
+./get_subscriber_device_identity.sh
+echo -e "\n get_subscriber"
+./get_subscriber.sh
+echo -e "\n list_subscriber_devices"
+./list_subscriber_devices.sh
+echo -e "\n delete_subscriber_device"
+./delete_subscriber_device.sh
+echo -e "\n delete_subscriber"
+./delete_subscriber.sh
diff --git a/xos/api/examples/cord/delete_subscriber_device.sh b/xos/api/examples/cord/delete_subscriber_device.sh
new file mode 100755
index 0000000..13c357f
--- /dev/null
+++ b/xos/api/examples/cord/delete_subscriber_device.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+source ./config.sh
+source ./util.sh
+
+ACCOUNT_NUM=1238
+MAC="19:28:37:46:55"
+
+SUBSCRIBER_ID=$(lookup_account_num $ACCOUNT_NUM)
+if [[ $? != 0 ]]; then
+ exit -1
+fi
+
+curl -H "Accept: application/json; indent=4" -H "Content-Type: application/json" -u $AUTH -X DELETE $HOST/api/tenant/cord/subscriber/$SUBSCRIBER_ID/devices/$MAC/
diff --git a/xos/api/examples/cord/get_subscriber_device_feature.sh b/xos/api/examples/cord/get_subscriber_device_feature.sh
new file mode 100755
index 0000000..7c02c05
--- /dev/null
+++ b/xos/api/examples/cord/get_subscriber_device_feature.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+source ./config.sh
+source ./util.sh
+
+ACCOUNT_NUM=1238
+MAC="19:28:37:46:55"
+
+SUBSCRIBER_ID=$(lookup_account_num $ACCOUNT_NUM)
+if [[ $? != 0 ]]; then
+ exit -1
+fi
+
+curl -H "Accept: application/json; indent=4" -H "Content-Type: application/json" -u $AUTH -X GET $HOST/api/tenant/cord/subscriber/$SUBSCRIBER_ID/devices/$MAC/features/uplink_speed/
diff --git a/xos/api/examples/cord/get_subscriber_device_identity.sh b/xos/api/examples/cord/get_subscriber_device_identity.sh
new file mode 100755
index 0000000..e5cff59
--- /dev/null
+++ b/xos/api/examples/cord/get_subscriber_device_identity.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+source ./config.sh
+source ./util.sh
+
+ACCOUNT_NUM=1238
+MAC="19:28:37:46:55"
+
+SUBSCRIBER_ID=$(lookup_account_num $ACCOUNT_NUM)
+if [[ $? != 0 ]]; then
+ exit -1
+fi
+
+curl -H "Accept: application/json; indent=4" -H "Content-Type: application/json" -u $AUTH -X GET $HOST/api/tenant/cord/subscriber/$SUBSCRIBER_ID/devices/$MAC/identity/name/
diff --git a/xos/api/examples/cord/set_subscriber_device_feature.sh b/xos/api/examples/cord/set_subscriber_device_feature.sh
new file mode 100755
index 0000000..73d84b0
--- /dev/null
+++ b/xos/api/examples/cord/set_subscriber_device_feature.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+source ./config.sh
+source ./util.sh
+
+ACCOUNT_NUM=1238
+MAC="19:28:37:46:55"
+
+SUBSCRIBER_ID=$(lookup_account_num $ACCOUNT_NUM)
+if [[ $? != 0 ]]; then
+ exit -1
+fi
+
+DATA=$(cat <<EOF
+{"uplink_speed": 111111}
+EOF
+)
+
+curl -H "Accept: application/json; indent=4" -H "Content-Type: application/json" -u $AUTH -X PUT -d "$DATA" $HOST/api/tenant/cord/subscriber/$SUBSCRIBER_ID/devices/$MAC/features/uplink_speed/
diff --git a/xos/api/examples/cord/set_subscriber_device_identity.sh b/xos/api/examples/cord/set_subscriber_device_identity.sh
new file mode 100755
index 0000000..faaeba1
--- /dev/null
+++ b/xos/api/examples/cord/set_subscriber_device_identity.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+source ./config.sh
+source ./util.sh
+
+ACCOUNT_NUM=1238
+MAC="19:28:37:46:55"
+
+SUBSCRIBER_ID=$(lookup_account_num $ACCOUNT_NUM)
+if [[ $? != 0 ]]; then
+ exit -1
+fi
+
+DATA=$(cat <<EOF
+{"name": "foo"}
+EOF
+)
+
+curl -H "Accept: application/json; indent=4" -H "Content-Type: application/json" -u $AUTH -X PUT -d "$DATA" $HOST/api/tenant/cord/subscriber/$SUBSCRIBER_ID/devices/$MAC/identity/name/
diff --git a/xos/api/import_methods.py b/xos/api/import_methods.py
index fbcd990..ec07be6 100644
--- a/xos/api/import_methods.py
+++ b/xos/api/import_methods.py
@@ -91,7 +91,10 @@
# Only add an index_view if 1) the is not already an index view, and
# 2) we have found some methods in this directory.
if (not has_index_view) and (urlpatterns):
- urlpatterns.append(url('^' + api_path + '/$', XOSIndexViewSet.as_view({'get': 'list'}, view_urls=view_urls, subdirs=subdirs, api_path=api_path), name=api_path+"_index"))
+ # The browseable API uses the classname as the breadcrumb and page
+ # title, so try to create index views with descriptive classnames
+ viewset = type("IndexOf"+api_path.split("/")[-1].title(), (XOSIndexViewSet,), {})
+ urlpatterns.append(url('^' + api_path + '/$', viewset.as_view({'get': 'list'}, view_urls=view_urls, subdirs=subdirs, api_path=api_path), name=api_path+"_index"))
return urlpatterns
diff --git a/xos/api/tenant/cord/subscriber.py b/xos/api/tenant/cord/subscriber.py
index b33c7ad..eab6cb3 100644
--- a/xos/api/tenant/cord/subscriber.py
+++ b/xos/api/tenant/cord/subscriber.py
@@ -87,6 +87,55 @@
def save(self, *args, **kwargs):
super(CordSubscriberNew, self).save(*args, **kwargs)
+class CordDevice(object):
+ def __init__(self, d={}, subscriber=None):
+ self.d = d
+ self.subscriber = subscriber
+
+ @property
+ def mac(self):
+ return self.d.get("mac", None)
+
+ @mac.setter
+ def mac(self, value):
+ self.d["mac"] = value
+
+ @property
+ def identity(self):
+ return {"name": self.d.get("name", None)}
+
+ @identity.setter
+ def identity(self, value):
+ self.d["name"] = value.get("name", None)
+
+ @property
+ def features(self):
+ return {"uplink_speed": self.d.get("uplink_speed", None),
+ "downlink_speed": self.d.get("downlink_speed", None)}
+
+ @features.setter
+ def features(self, value):
+ self.d["uplink_speed"] = value.get("uplink_speed", None)
+ self.d["downlink_speed"] = value.get("downlink_speed", None)
+
+ def update_features(self, value):
+ d=self.features
+ d.update(value)
+ self.features = d
+
+ def update_identity(self, value):
+ d=self.identity
+ d.update(value)
+ self.identity = d
+
+ def save(self):
+ if self.subscriber:
+ dev=self.subscriber.find_device(self.mac)
+ if dev:
+ self.subscriber.update_device(**self.d)
+ else:
+ self.subscriber.create_device(**self.d)
+
# Add some structure to the REST API by subdividing the object into
# features, identity, and related.
@@ -101,6 +150,21 @@
account_num = serializers.CharField(required=False)
name = serializers.CharField(required=False)
+class DeviceFeatureSerializer(serializers.Serializer):
+ uplink_speed = serializers.IntegerField(required=False)
+ downlink_speed = serializers.IntegerField(required=False)
+
+class DeviceIdentitySerializer(serializers.Serializer):
+ name = serializers.CharField(required=False)
+
+class DeviceSerializer(serializers.Serializer):
+ mac = serializers.CharField(required=True)
+ identity = DeviceIdentitySerializer(required=False)
+ features = DeviceFeatureSerializer(required=False)
+
+ class Meta:
+ fields = ('mac', 'identity', 'features')
+
class CordSubscriberSerializer(PlusModelSerializer):
id = ReadOnlyField()
humanReadableName = serializers.SerializerMethodField("getHumanReadableName")
@@ -132,7 +196,11 @@
custom_serializers = {"set_features": FeatureSerializer,
"set_feature": FeatureSerializer,
"set_identities": IdentitySerializer,
- "set_identity": IdentitySerializer}
+ "set_identity": IdentitySerializer,
+ "get_devices": DeviceSerializer,
+ "add_device": DeviceSerializer,
+ "get_device_feature": DeviceFeatureSerializer,
+ "set_device_feature": DeviceFeatureSerializer}
@classmethod
def get_urlpatterns(self, api_path="^"):
@@ -142,6 +210,11 @@
patterns.append( self.detail_url("identity/$", {"get": "get_identities", "put": "set_identities"}, "identities") )
patterns.append( self.detail_url("identity/(?P<identity>[a-zA-Z0-9\-_]+)/$", {"get": "get_identity", "put": "set_identity"}, "get_identity") )
+ patterns.append( self.detail_url("devices/$", {"get": "get_devices", "post": "add_device"}, "devicees") )
+ patterns.append( self.detail_url("devices/(?P<mac>[a-zA-Z0-9\-_:]+)/$", {"get": "get_device", "delete": "delete_device"}, "getset_device") )
+ patterns.append( self.detail_url("devices/(?P<mac>[a-zA-Z0-9\-_:]+)/features/(?P<feature>[a-zA-Z0-9\-_]+)/$", {"get": "get_device_feature", "put": "set_device_feature"}, "getset_device_feature") )
+ patterns.append( self.detail_url("devices/(?P<mac>[a-zA-Z0-9\-_:]+)/identity/(?P<identity>[a-zA-Z0-9\-_]+)/$", {"get": "get_device_identity", "put": "set_device_identity"}, "getset_device_identity") )
+
patterns.append( url(self.api_path + "account_num_lookup/(?P<account_num>[0-9\-]+)/$", self.as_view({"get": "account_num_detail"}), name="account_num_detail") )
patterns.append( url(self.api_path + "ssidmap/(?P<ssid>[0-9\-]+)/$", self.as_view({"get": "ssiddetail"}), name="ssiddetail") )
@@ -208,6 +281,82 @@
subscriber.save()
return Response({identity: IdentitySerializer(subscriber.identity).data[identity]})
+ def get_devices(self, request, pk=None):
+ subscriber = self.get_object()
+ result = []
+ for device in subscriber.devices:
+ device = CordDevice(device, subscriber)
+ result.append(DeviceSerializer(device).data)
+ return Response(result)
+
+ def add_device(self, request, pk=None):
+ subscriber = self.get_object()
+ ser = DeviceSerializer(subscriber.devices, data=request.data)
+ ser.is_valid(raise_exception = True)
+ newdevice = CordDevice(subscriber.create_device(**ser.validated_data), subscriber)
+ subscriber.save()
+ return Response(DeviceSerializer(newdevice).data)
+
+ def get_device(self, request, pk=None, mac=None):
+ subscriber = self.get_object()
+ device = subscriber.find_device(mac)
+ if not device:
+ return Response("Failed to find device %s" % mac, status=status.HTTP_404_NOT_FOUND)
+ return Response(DeviceSerializer(CordDevice(device, subscriber)).data)
+
+ def delete_device(self, request, pk=None, mac=None):
+ subscriber = self.get_object()
+ device = subscriber.find_device(mac)
+ if not device:
+ return Response("Failed to find device %s" % mac, status=status.HTTP_404_NOT_FOUND)
+ subscriber.delete_device(mac)
+ subscriber.save()
+ return Response("Okay")
+
+ def get_device_feature(self, request, pk=None, mac=None, feature=None):
+ subscriber = self.get_object()
+ device = subscriber.find_device(mac)
+ if not device:
+ return Response("Failed to find device %s" % mac, status=status.HTTP_404_NOT_FOUND)
+ return Response({feature: DeviceFeatureSerializer(CordDevice(device, subscriber).features).data[feature]})
+
+ def set_device_feature(self, request, pk=None, mac=None, feature=None):
+ subscriber = self.get_object()
+ device = subscriber.find_device(mac)
+ if not device:
+ return Response("Failed to find device %s" % mac, status=status.HTTP_404_NOT_FOUND)
+ if [feature] != request.data.keys():
+ raise serializers.ValidationError("feature %s does not match keys in request body (%s)" % (feature, ",".join(request.data.keys())))
+ device = CordDevice(device, subscriber)
+ ser = DeviceFeatureSerializer(device.features, data=request.data)
+ ser.is_valid(raise_exception = True)
+ device.update_features(ser.validated_data)
+ device.save()
+ subscriber.save()
+ return Response({feature: DeviceFeatureSerializer(device.features).data[feature]})
+
+ def get_device_identity(self, request, pk=None, mac=None, identity=None):
+ subscriber = self.get_object()
+ device = subscriber.find_device(mac)
+ if not device:
+ return Response("Failed to find device %s" % mac, status=status.HTTP_404_NOT_FOUND)
+ return Response({identity: DeviceIdentitySerializer(CordDevice(device, subscriber).identity).data[identity]})
+
+ def set_device_identity(self, request, pk=None, mac=None, identity=None):
+ subscriber = self.get_object()
+ device = subscriber.find_device(mac)
+ if not device:
+ return Response("Failed to find device %s" % mac, status=status.HTTP_404_NOT_FOUND)
+ if [identity] != request.data.keys():
+ raise serializers.ValidationError("identity %s does not match keys in request body (%s)" % (feature, ",".join(request.data.keys())))
+ device = CordDevice(device, subscriber)
+ ser = DeviceIdentitySerializer(device.identity, data=request.data)
+ ser.is_valid(raise_exception = True)
+ device.update_identity(ser.validated_data)
+ device.save()
+ subscriber.save()
+ return Response({identity: DeviceIdentitySerializer(device.identity).data[identity]})
+
def account_num_detail(self, pk=None, account_num=None):
object_list = CordSubscriberNew.get_tenant_objects().all()
object_list = [x for x in object_list if x.service_specific_id == account_num]
diff --git a/xos/services/cord/models.py b/xos/services/cord/models.py
index c57d9fb..48c9597 100644
--- a/xos/services/cord/models.py
+++ b/xos/services/cord/models.py
@@ -47,7 +47,7 @@
("url_filter_rules", "allow all"),
("url_filter_level", "PG"),
("cdn_enable", False),
- ("users", []),
+ ("devices", []),
("is_demo_user", False),
("uplink_speed", 1000000000), # 1 gigabit, a reasonable default?
@@ -95,58 +95,65 @@
raise Exception("invalid status %s" % value)
self.set_attribute("status", value)
- def find_user(self, uid):
- uid = int(uid)
- for user in self.users:
- if user["id"] == uid:
- return user
+ def find_device(self, mac):
+ for device in self.devices:
+ if device["mac"] == mac:
+ return device
return None
- def update_user(self, uid, **kwargs):
+ def update_device(self, mac, **kwargs):
# kwargs may be "level" or "mac"
# Setting one of these to None will cause None to be stored in the db
- uid = int(uid)
- users = self.users
- for user in users:
- if user["id"] == uid:
+ devices = self.devices
+ for device in devices:
+ if device["mac"] == mac:
for arg in kwargs.keys():
- user[arg] = kwargs[arg]
- self.users = users
- return user
- raise ValueError("User %d not found" % uid)
+ device[arg] = kwargs[arg]
+ self.devices = devices
+ return device
+ raise ValueError("Device with mac %s not found" % mac)
- def create_user(self, **kwargs):
- if "name" not in kwargs:
- raise XOSMissingField("The name field is required")
+ def create_device(self, **kwargs):
+ if "mac" not in kwargs:
+ raise XOSMissingField("The mac field is required")
- for user in self.users:
- if kwargs["name"] == user["name"]:
- raise XOSDuplicateKey("User %s already exists" % kwargs["name"])
+ if self.find_device(kwargs['mac']):
+ raise XOSDuplicateKey("Device with mac %s already exists" % kwargs["mac"])
- uids = [x["id"] for x in self.users]
- if uids:
- uid = max(uids)+1
- else:
- uid = 0
- newuser = kwargs.copy()
- newuser["id"] = uid
+ device = kwargs.copy()
- users = self.users
- users.append(newuser)
- self.users = users
+ devices = self.devices
+ devices.append(device)
+ self.devices = devices
- return newuser
+ return device
- def delete_user(self, uid):
- uid = int(uid)
- users = self.users
- for user in users:
- if user["id"]==uid:
- users.remove(user)
- self.users = users
+ def delete_device(self, mac):
+ devices = self.devices
+ for device in devices:
+ if device["mac"]==mac:
+ devices.remove(device)
+ self.devices = devices
return
- raise ValueError("Users %d not found" % uid)
+ raise ValueError("Device with mac %s not found" % mac)
+
+ #--------------------------------------------------------------------------
+ # Deprecated -- devices used to be called users
+
+ def find_user(self, uid):
+ return self.find_device(uid)
+
+ def update_user(self, uid, **kwargs):
+ return self.update_device(uid, **kwargs)
+
+ def create_user(self, **kwargs):
+ return self.create_device(**kwargs)
+
+ def delete_user(self, uid):
+ return self.delete_user(uid)
+
+ # ------------------------------------------------------------------------
@property
def services(self):
diff --git a/xos/tosca/resources/CORDUser.py b/xos/tosca/resources/CORDUser.py
index 705a895..ff2dc8f 100644
--- a/xos/tosca/resources/CORDUser.py
+++ b/xos/tosca/resources/CORDUser.py
@@ -27,7 +27,7 @@
sub = self.get_subscriber_root(throw_exception=False)
if not sub:
return []
- for user in sub.users:
+ for user in sub.devices:
if user["name"] == self.obj_name:
result.append(user)
return result
@@ -43,7 +43,7 @@
xos_args = self.get_xos_args()
sub = self.get_subscriber_root()
- sub.create_user(**xos_args)
+ sub.create_device(**xos_args)
sub.save()
self.info("Created CORDUser %s for Subscriber %s" % (self.obj_name, sub.name))