From: Sapan Bhatia Date: Wed, 21 Aug 2013 12:55:47 +0000 (-0400) Subject: Merge branch 'master' of ssh://git.planet-lab.org/git/plstackapi X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=78ce7694b6fb4a1decf3859f563ef1e3bbdaaa0b;hp=b47da43097e2fd4c230330388c1548ad14c18a37;p=plstackapi.git Merge branch 'master' of ssh://git.planet-lab.org/git/plstackapi --- diff --git a/planetstack/core/admin.py b/planetstack/core/admin.py index 60a589c..93c14ee 100644 --- a/planetstack/core/admin.py +++ b/planetstack/core/admin.py @@ -39,12 +39,62 @@ class TagInline(generic.GenericTabularInline): exclude = ['enacted'] extra = 1 +class NetworkLookerUpper: + """ This is a callable that looks up a network name in a sliver and returns + the ip address for that network. + """ + + def __init__(self, name): + self.short_description = name + self.__name__ = name + self.network_name = name + + def __call__(self, obj): + if obj is not None: + for nbs in obj.networksliver_set.all(): + if (nbs.network.name == self.network_name): + return nbs.ip + return "" + + def __str__(self): + return self.network_name + class SliverInline(PlStackTabularInline): model = Sliver fields = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork'] extra = 0 - #readonly_fields = ['ip', 'instance_name', 'image'] readonly_fields = ['ip', 'instance_name'] + + + def _declared_fieldsets(self): + # Return None so django will call get_fieldsets and we can insert our + # dynamic fields + return None + + def get_readonly_fields(self, request, obj=None): + readonly_fields = super(SliverInline, self).get_readonly_fields(request, obj) + + # Lookup the networks that are bound to the slivers, and add those + # network names to the list of readonly fields. + + for sliver in obj.slivers.all(): + for nbs in sliver.networksliver_set.all(): + if nbs.ip: + network_name = nbs.network.name + if network_name not in [str(x) for x in readonly_fields]: + readonly_fields.append(NetworkLookerUpper(network_name)) + + return readonly_fields + + def get_fieldsets(self, request, obj=None): + form = self.get_formset(request, obj).form + # fields = the read/write files + the read-only fields + fields = self.fields + for fieldName in self.get_readonly_fields(request,obj): + if not fieldName in fields: + fields.append(fieldName) + + return [(None, {'fields': fields})] class SiteInline(PlStackTabularInline): @@ -121,6 +171,12 @@ class SliceMembershipInline(PlStackTabularInline): return super(SliceMembershipInline, self).formfield_for_foreignkey(db_field, request, **kwargs) +class SliceNetworkInline(PlStackTabularInline): + model = Network.slices.through + extra = 0 + verbose_name = "Network Connection" + verbose_name_plural = "Network Connections" + class SliceTagInline(PlStackTabularInline): model = SliceTag extra = 0 @@ -271,7 +327,7 @@ class SitePrivilegeAdmin(PlanetStackBaseAdmin): class SliceAdmin(PlanetStackBaseAdmin): fields = ['name', 'site', 'serviceClass', 'description', 'slice_url'] list_display = ('name', 'site','serviceClass', 'slice_url') - inlines = [SliverInline, SliceMembershipInline, TagInline, SliceTagInline] + inlines = [SliverInline, SliceMembershipInline, TagInline, SliceTagInline, SliceNetworkInline] def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == 'site': @@ -402,9 +458,9 @@ class TagAdmin(admin.ModelAdmin): class SliverAdmin(PlanetStackBaseAdmin): form = SliverForm fieldsets = [ - ('Sliver', {'fields': ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'key', 'node', 'deploymentNetwork']}) + ('Sliver', {'fields': ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork']}) ] - list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'key', 'node', 'deploymentNetwork'] + list_display = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork'] inlines = [TagInline] def formfield_for_foreignkey(self, db_field, request, **kwargs): @@ -549,10 +605,12 @@ class UserAdmin(UserAdmin): return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs) class ServiceResourceInline(admin.TabularInline): + exclude = ['enacted'] model = ServiceResource extra = 0 class ServiceClassAdmin(admin.ModelAdmin): + exclude = ['enacted'] list_display = ('name', 'commitment', 'membershipFee') inlines = [ServiceResourceInline] @@ -677,6 +735,53 @@ class ReservationAdmin(admin.ModelAdmin): else: return [] +class NetworkParameterTypeAdmin(admin.ModelAdmin): + exclude = ['enacted'] + list_display = ("name", ) + +class RouterAdmin(admin.ModelAdmin): + exclude = ['enacted'] + list_display = ("name", ) + +class RouterInline(admin.TabularInline): + # exclude = ['enacted'] + model = Router.networks.through + extra = 0 + verbose_name_plural = "Routers" + verbose_name = "Router" + +class NetworkParameterInline(generic.GenericTabularInline): + exclude = ['enacted'] + model = NetworkParameter + extra = 1 + verbose_name_plural = "Parameters" + verbose_name = "Parameter" + +class NetworkSliversInline(admin.TabularInline): + exclude = ['enacted'] + readonly_fields = ("ip", ) + model = NetworkSliver + extra = 0 + verbose_name_plural = "Slivers" + verbose_name = "Sliver" + +class NetworkSlicesInline(admin.TabularInline): + exclude = ['enacted'] + model = NetworkSlice + extra = 0 + verbose_name_plural = "Slices" + verbose_name = "Slice" + +class NetworkAdmin(admin.ModelAdmin): + exclude = ['enacted'] + list_display = ("name", "subnet", "ports", "labels") + readonly_fields = ("subnet", ) + inlines = [NetworkParameterInline, NetworkSliversInline, NetworkSlicesInline, RouterInline] + +class NetworkTemplateAdmin(admin.ModelAdmin): + exclude = ['enacted'] + list_display = ("name", "guaranteedBandwidth", "visibility") + # register a signal that caches the user's credentials when they log in def cache_credentials(sender, user, request, **kwds): auth = {'username': request.POST['username'], @@ -706,6 +811,10 @@ admin.site.register(Slice, SliceAdmin) admin.site.register(Project, ProjectAdmin) admin.site.register(ServiceClass, ServiceClassAdmin) admin.site.register(Reservation, ReservationAdmin) +admin.site.register(Network, NetworkAdmin) +admin.site.register(Router, RouterAdmin) +admin.site.register(NetworkParameterType, NetworkParameterTypeAdmin) +admin.site.register(NetworkTemplate, NetworkTemplateAdmin) if showAll: admin.site.register(Tag, TagAdmin) diff --git a/planetstack/core/api_root.py b/planetstack/core/api_root.py index 61e76da..4ac267a 100644 --- a/planetstack/core/api_root.py +++ b/planetstack/core/api_root.py @@ -5,14 +5,17 @@ from rest_framework.reverse import reverse @api_view(['GET']) def api_root(request, format=None): return Response({ + 'deployments': reverse('deployment-list', request=request, format=format), + 'images': reverse('image-list', request=request, format=format), + 'nodes': reverse('node-list', request=request, format=format), + 'projects': reverse('project-list', request=request, format=format), + 'reservations': reverse('reservation-list', request=request, format=format), 'roles': reverse('role-list', request=request, format=format), - 'users': reverse('user-list', request=request, format=format), - 'keys': reverse('key-list', request=request, format=format), - #'nodes': reverse('node-list', request=request, format=format), + 'serviceclasses': reverse('serviceclass-list', request=request, format=format), + 'serviceresources': reverse('serviceresource-list', request=request, format=format), 'sites': reverse('site-list', request=request, format=format), - 'deploymentNetworks': reverse('deploymentnetwork-list', request=request, format=format), 'slices': reverse('slice-list', request=request, format=format), - 'subnets': reverse('subnet-list', request=request, format=format), 'slivers': reverse('sliver-list', request=request, format=format), - 'images': reverse('image-list', request=request, format=format), + 'tags': reverse('tag-list', request=request, format=format), + 'users': reverse('user-list', request=request, format=format), }) diff --git a/planetstack/core/models/__init__.py b/planetstack/core/models/__init__.py index 2280822..9b19821 100644 --- a/planetstack/core/models/__init__.py +++ b/planetstack/core/models/__init__.py @@ -16,3 +16,4 @@ from .slicetag import SliceTag from .sliver import Sliver from .reservation import ReservedResource from .reservation import Reservation +from .network import Network, NetworkParameterType, NetworkParameter, NetworkSliver, NetworkTemplate, Router, NetworkSlice diff --git a/planetstack/core/models/network.py b/planetstack/core/models/network.py new file mode 100644 index 0000000..55711a4 --- /dev/null +++ b/planetstack/core/models/network.py @@ -0,0 +1,122 @@ +import os +import socket +from django.db import models +from core.models import PlCoreBase, Site, Slice, Sliver +from django.contrib.contenttypes.models import ContentType +from django.contrib.contenttypes import generic + +# If true, then IP addresses will be allocated by the model. If false, then +# we will assume the observer handles it. +NO_OBSERVER=False + +class NetworkTemplate(PlCoreBase): + VISIBILITY_CHOICES = (('public', 'public'), ('private', 'private')) + TRANSLATION_CHOICES = (('none', 'none'), ('NAT', 'NAT')) + + name = models.CharField(max_length=32) + description = models.CharField(max_length=1024, blank=True, null=True) + guaranteedBandwidth = models.IntegerField(default=0) + visibility = models.CharField(max_length=30, choices=VISIBILITY_CHOICES, default="private") + translation = models.CharField(max_length=30, choices=TRANSLATION_CHOICES, default="none") + sharedNetworkName = models.CharField(max_length=30, blank=True, null=True) + sharedNetworkId = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum network") + + def __unicode__(self): return u'%s' % (self.name) + +class Network(PlCoreBase): + name = models.CharField(max_length=32) + template = models.ForeignKey(NetworkTemplate) + subnet = models.CharField(max_length=32, blank=True) + ports = models.CharField(max_length=1024, blank=True, null=True) + labels = models.CharField(max_length=1024, blank=True, null=True) + owner = models.ForeignKey(Slice, related_name="ownedNetworks") + + guaranteedBandwidth = models.IntegerField(default=0) + permitAllSlices = models.BooleanField(default=False) + permittedSlices = models.ManyToManyField(Slice, blank=True, related_name="availableNetworks") + slices = models.ManyToManyField(Slice, blank=True, related_name="networks", through="NetworkSlice") + slivers = models.ManyToManyField(Sliver, blank=True, related_name="networks", through="NetworkSliver") + + # for observer/manager + network_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum network") + router_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum router id") + subnet_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum subnet id") + + def __unicode__(self): return u'%s' % (self.name) + + def save(self, *args, **kwds): + if (not self.subnet) and (NO_OBSERVER): + from util.network_subnet_allocator import find_unused_subnet + self.subnet = find_unused_subnet(existing_subnets=[x.subnet for x in Network.objects.all()]) + super(Network, self).save(*args, **kwds) + +class NetworkSlice(PlCoreBase): + # This object exists solely so we can implement the permission check when + # adding slices to networks. It adds no additional fields to the relation. + + network = models.ForeignKey(Network) + slice = models.ForeignKey(Slice) + + def save(self, *args, **kwds): + slice = self.slice + if (slice not in self.network.permittedSlices.all()) and (slice != self.network.owner) and (not self.network.permitAllSlices): + # to add a sliver to the network, then one of the following must be true: + # 1) sliver's slice is in network's permittedSlices list, + # 2) sliver's slice is network's owner, or + # 3) network's permitAllSlices is true + raise ValueError("Slice %s is not allowed to connect to network %s" % (str(slice), str(self.network))) + + super(NetworkSlice, self).save(*args, **kwds) + + def __unicode__(self): return u'%s-%s' % (self.network.name, self.slice.name) + +class NetworkSliver(PlCoreBase): + network = models.ForeignKey(Network) + sliver = models.ForeignKey(Sliver) + ip = models.GenericIPAddressField(help_text="Sliver ip address", blank=True, null=True) + port_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum port id") + + def save(self, *args, **kwds): + slice = self.sliver.slice + if (slice not in self.network.permittedSlices.all()) and (slice != self.network.owner) and (not self.network.permitAllSlices): + # to add a sliver to the network, then one of the following must be true: + # 1) sliver's slice is in network's permittedSlices list, + # 2) sliver's slice is network's owner, or + # 3) network's permitAllSlices is true + raise ValueError("Slice %s is not allowed to connect to network %s" % (str(slice), str(self.network))) + + if (not self.ip) and (NO_OBSERVER): + from util.network_subnet_allocator import find_unused_address + self.ip = find_unused_address(self.network.subnet, + [x.ip for x in self.network.networksliver_set.all()]) + super(NetworkSliver, self).save(*args, **kwds) + + def __unicode__(self): return u'%s-%s' % (self.network.name, self.sliver.instance_name) + +class Router(PlCoreBase): + name = models.CharField(max_length=32) + owner = models.ForeignKey(Slice, related_name="routers") + permittedNetworks = models.ManyToManyField(Network, blank=True, related_name="availableRouters") + networks = models.ManyToManyField(Network, blank=True, related_name="routers") + + def __unicode__(self): return u'%s' % (self.name) + +class NetworkParameterType(PlCoreBase): + name = models.SlugField(help_text="The name of this parameter", max_length=128) + description = models.CharField(max_length=1024) + + def __unicode__(self): return u'%s' % (self.name) + +class NetworkParameter(PlCoreBase): + parameter = models.ForeignKey(NetworkParameterType, related_name="parameters", help_text="The type of the parameter") + value = models.CharField(help_text="The value of this parameter", max_length=1024) + + # The required fields to do a ObjectType lookup, and object_id assignment + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + content_object = generic.GenericForeignKey('content_type', 'object_id') + + def __unicode__(self): + return self.parameter.name + + diff --git a/planetstack/core/models/plcorebase.py b/planetstack/core/models/plcorebase.py index 709fdc6..30d4df3 100644 --- a/planetstack/core/models/plcorebase.py +++ b/planetstack/core/models/plcorebase.py @@ -1,6 +1,8 @@ import os from django.db import models from django.forms.models import model_to_dict +from openstack.event_manager import EventSender + class PlCoreBase(models.Model): @@ -36,6 +38,10 @@ class PlCoreBase(models.Model): def save(self, *args, **kwargs): super(PlCoreBase, self).save(*args, **kwargs) + + # Tell the observer that the source database has been updated + EventSender().fire() + self.__initial = self._dict @property diff --git a/planetstack/core/models/role.py b/planetstack/core/models/role.py index f6c2f2c..fd29848 100644 --- a/planetstack/core/models/role.py +++ b/planetstack/core/models/role.py @@ -5,8 +5,8 @@ from core.models import PlCoreBase class Role(PlCoreBase): - #ROLE_CHOICES = (('admin', 'Admin'), ('pi', 'Principle Investigator'), ('user','User')) - role = models.CharField(null=True, blank=True,max_length=256, unique=True) + ROLE_CHOICES = (('admin', 'Admin'), ('pi', 'Principle Investigator'), ('tech', 'Technician'), ('user','User')) + role = models.CharField(null=True, blank=True,max_length=256, unique=True, choices=ROLE_CHOICES) role_type = models.CharField(max_length=80, unique=True) def __unicode__(self): return u'%s' % (self.role_type) diff --git a/planetstack/core/serializers.py b/planetstack/core/serializers.py index 00d7160..94f5c3c 100644 --- a/planetstack/core/serializers.py +++ b/planetstack/core/serializers.py @@ -9,7 +9,6 @@ class RoleSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Role fields = ('id', - 'role_id', 'role', 'role_type') @@ -19,11 +18,10 @@ class UserSerializer(serializers.HyperlinkedModelSerializer): id = serializers.Field() site = serializers.HyperlinkedRelatedField(view_name='site-detail') slice_memberships = serializers.HyperlinkedRelatedField(view_name='slice-membership-detail') - site_privileges = serializers.HyperlinkedRelatedField(view_name='site-privilege-detail') + site_privileges = serializers.HyperlinkedRelatedField(view_name='siteprivilege-detail') class Meta: model = User fields = ('id', - 'user_id', 'kuser_id', 'firstname', 'lastname', @@ -66,7 +64,7 @@ class SliceMembershipSerializer(serializers.HyperlinkedModelSerializer): user = serializers.HyperlinkedRelatedField(view_name='user-detail') role = serializers.HyperlinkedRelatedField(view_name='role-detail') class Meta: - model = SitePrivilege + model = SliceMembership fields = ('id', 'user', 'slice', @@ -115,7 +113,7 @@ class DeploymentSerializer(serializers.HyperlinkedModelSerializer): # HyperlinkedModelSerializer doesn't include the id by default id = serializers.Field() - sites = serializers.HyperlinkedRelatedField(view_name='deploymentnetwork-detail') + sites = serializers.HyperlinkedRelatedField(view_name='site-detail') class Meta: model = Deployment fields = ('id', @@ -127,9 +125,8 @@ class SliverSerializer(serializers.HyperlinkedModelSerializer): # HyperlinkedModelSerializer doesn't include the id by default id = serializers.Field() image = serializers.HyperlinkedRelatedField(view_name='image-detail') - key = serializers.HyperlinkedRelatedField(view_name='key-detail') slice = serializers.HyperlinkedRelatedField(view_name='slice-detail') - deployment_network = serializers.HyperlinkedRelatedField(view_name='deployment_network-detail') + deployment = serializers.HyperlinkedRelatedField(view_name='deployment-detail') node = serializers.HyperlinkedRelatedField(view_name='node-detail') @@ -143,9 +140,8 @@ class SliverSerializer(serializers.HyperlinkedModelSerializer): 'instance_name', 'ip', 'image', - 'key', 'slice', - 'deploymentNetwork', + 'deployment', 'node') class NodeSerializer(serializers.HyperlinkedModelSerializer): diff --git a/planetstack/core/views/deployment_networks.py b/planetstack/core/views/deployment_networks.py deleted file mode 100644 index ef569ac..0000000 --- a/planetstack/core/views/deployment_networks.py +++ /dev/null @@ -1,59 +0,0 @@ -from django.http import Http404 -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import status - -from core.api.deployment_networks import add_deployment_network, delete_deployment_network, get_deployment_networks -from core.serializers import DeploymentSerializer -from util.request import parse_request - - -class DeploymentListCreate(APIView): - """ - List all deployment networks or create a new role. - """ - - def post(self, request, format = None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'deploymentNetwork' in data: - - deployment = add_deployment_network(data['auth'], data['deploymentNetwork'].get('name')) - serializer = DeploymentSerializer(deployment) - return Response(serializer.data, status=status.HTTP_201_CREATED) - else: - deployment_networks = get_deployment_networks(data['auth']) - serializer = DeploymentSerializer(deployment_networks, many=True) - return Response(serializer.data) - - -class DeploymentRetrieveUpdateDestroy(APIView): - """ - Retrieve, update or delete a deployment network - """ - - def post(self, request, pk, format=None): - """Retrieve a deployment network""" - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - deployment_networks = get_deployment_networks(data['auth'], pk) - if not deployment_networks: - return Response(status=status.HTTP_404_NOT_FOUND) - serializer = DeploymentSerializer(deployment_networks[0]) - return Response(serializer.data) - - def put(self, request, pk, format=None): - """deployment network update not implemnted""" - return Response(status=status.HTTP_404_NOT_FOUND) - - def delete(self, request, pk, format=None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - delete_deployment_network(data['auth'], pk) - return Response(status=status.HTTP_204_NO_CONTENT) - - - diff --git a/planetstack/core/views/deployments.py b/planetstack/core/views/deployments.py new file mode 100644 index 0000000..285a53a --- /dev/null +++ b/planetstack/core/views/deployments.py @@ -0,0 +1,12 @@ +from core.serializers import DeploymentSerializer +from rest_framework import generics +from core.models import Deployment + +class DeploymentList(generics.ListCreateAPIView): + queryset = Deployment.objects.all() + serializer_class = DeploymentSerializer + +class DeploymentDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Deployment.objects.all() + serializer_class = DeploymentSerializer + diff --git a/planetstack/core/views/images.py b/planetstack/core/views/images.py index 7e0ab59..5ea5d76 100644 --- a/planetstack/core/views/images.py +++ b/planetstack/core/views/images.py @@ -1,55 +1,12 @@ -from django.http import Http404 -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import status - -from core.api.images import add_image, delete_image, get_images from core.serializers import ImageSerializer -from util.request import parse_request - - -class ImageListCreate(APIView): - """ - List all images or create a new image. - """ - - def post(self, request, format = None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'image' in data: - """Not Implemented""" - return Response(status=status.HTTP_404_NOT_FOUND) - else: - images = get_images(data['auth']) - serializer = ImageSerializer(images, many=True) - return Response(serializer.data) - - -class ImageRetrieveUpdateDestroy(APIView): - """ - Retrieve, update or delete an image - """ - - def post(self, request, pk, format=None): - """Retrieve an image """ - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - images = get_images(data['auth'], pk) - if not images: - return Response(status=status.HTTP_404_NOT_FOUND) - serializer = ImageSerializer(images[0]) - return Response(serializer.data) +from rest_framework import generics +from core.models import Image - def put(self, request, pk, format=None): - """update image not implemnted""" - return Response(status=status.HTTP_404_NOT_FOUND) +class ImageList(generics.ListCreateAPIView): + queryset = Image.objects.all() + serializer_class = ImageSerializer - def delete(self, request, pk, format=None): - """delete image not implemnted""" - return Response(status=status.HTTP_404_NOT_FOUND) +class ImageDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Image.objects.all() + serializer_class = ImageSerializer - - - diff --git a/planetstack/core/views/nodes.py b/planetstack/core/views/nodes.py index 0f1977e..8706114 100644 --- a/planetstack/core/views/nodes.py +++ b/planetstack/core/views/nodes.py @@ -1,55 +1,13 @@ -from django.http import Http404 -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import status - -from core.api.nodes import add_node, delete_node, get_nodes, update_node from core.serializers import NodeSerializer -from util.request import parse_request - - -class NodeListCreate(APIView): - """ - List all nodes or create a new node. - """ - - def post(self, request, format = None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'node' in data: - """Not Implemented""" - return Response(status=status.HTTP_404_NOT_FOUND) - else: - nodes = get_nodes(data['auth']) - serializer = NodeSerializer(nodes, many=True) - return Response(serializer.data) - - -class NodeRetrieveUpdateDestroy(APIView): - """ - Retrieve, update or delete an node - """ +from rest_framework import generics +from core.models import Node - def post(self, request, pk, format=None): - """Retrieve an node """ - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - nodes = get_nodes(data['auth'], pk) - if not nodes: - return Response(status=status.HTTP_404_NOT_FOUND) - serializer = NodeSerializer(nodes[0]) - return Response(serializer.data) +class NodeList(generics.ListCreateAPIView): + queryset = Node.objects.all() + serializer_class = NodeSerializer - def put(self, request, pk, format=None): - """update node not implemnted""" - return Response(status=status.HTTP_404_NOT_FOUND) +class NodeDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Node.objects.all() + serializer_class = NodeSerializer - def delete(self, request, pk, format=None): - """delete node not implemnted""" - return Response(status=status.HTTP_404_NOT_FOUND) - - - diff --git a/planetstack/core/views/projects.py b/planetstack/core/views/projects.py new file mode 100644 index 0000000..c5311d5 --- /dev/null +++ b/planetstack/core/views/projects.py @@ -0,0 +1,13 @@ +from core.serializers import ProjectSerializer +from rest_framework import generics +from core.models import Project + +class ProjectList(generics.ListCreateAPIView): + queryset = Project.objects.all() + serializer_class = ProjectSerializer + +class ProjectDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Project.objects.all() + serializer_class = ProjectSerializer + + diff --git a/planetstack/core/views/reservations.py b/planetstack/core/views/reservations.py new file mode 100644 index 0000000..03f79eb --- /dev/null +++ b/planetstack/core/views/reservations.py @@ -0,0 +1,13 @@ +from core.serializers import ReservationSerializer +from rest_framework import generics +from core.models import Reservation + +class ReservationList(generics.ListCreateAPIView): + queryset = Reservation.objects.all() + serializer_class = ReservationSerializer + +class ReservationDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Reservation.objects.all() + serializer_class = ReservationSerializer + + diff --git a/planetstack/core/views/roles.py b/planetstack/core/views/roles.py index 37bb149..13c9917 100644 --- a/planetstack/core/views/roles.py +++ b/planetstack/core/views/roles.py @@ -1,58 +1,13 @@ -from django.http import Http404 -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import status - -from core.api.roles import add_role, delete_role, get_roles from core.serializers import RoleSerializer -from util.request import parse_request - - -class RoleListCreate(APIView): - """ - List all roles or create a new role. - """ +from rest_framework import generics +from core.models import Role - def post(self, request, format = None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'role' in data: - role = add_role(data['auth'], data['role']['role_type']) - serializer = RoleSerializer(data=role) - return Response(serializer.data, status=status.HTTP_201_CREATED) - else: - roles = get_roles(data['auth']) - serializer = RoleSerializer(roles, many=True) - return Response(serializer.data) - - -class RoleRetrieveUpdateDestroy(APIView): - """ - Retrieve, update or delete a role - """ +class RoleList(generics.ListCreateAPIView): + queryset = Role.objects.all() + serializer_class = RoleSerializer - def post(self, request, pk, format=None): - """Retrieve a role""" - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - roles = get_roles(data['auth'], pk) - if not roles: - return Response(status=status.HTTP_404_NOT_FOUND) - serializer = RoleSerializer(roles[0]) - return Response(serializer.data) +class RoleDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Role.objects.all() + serializer_class = RoleSerializer - def put(self, request, pk, format=None): - """role update not implemnted""" - return Response(status=status.HTTP_404_NOT_FOUND) - def delete(self, request, pk, format=None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - delete_role(data['auth'], pk) - return Response(status=status.HTTP_204_NO_CONTENT) - - - diff --git a/planetstack/core/views/serviceclasses.py b/planetstack/core/views/serviceclasses.py new file mode 100644 index 0000000..b8b1b70 --- /dev/null +++ b/planetstack/core/views/serviceclasses.py @@ -0,0 +1,13 @@ +from core.serializers import ServiceClassSerializer +from rest_framework import generics +from core.models import ServiceClass + +class ServiceClassList(generics.ListCreateAPIView): + queryset = ServiceClass.objects.all() + serializer_class = ServiceClassSerializer + +class ServiceClassDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = ServiceClass.objects.all() + serializer_class = ServiceClassSerializer + + diff --git a/planetstack/core/views/serviceresources.py b/planetstack/core/views/serviceresources.py new file mode 100644 index 0000000..e394c18 --- /dev/null +++ b/planetstack/core/views/serviceresources.py @@ -0,0 +1,13 @@ +from core.serializers import ServiceResourceSerializer +from rest_framework import generics +from core.models import ServiceResource + +class ServiceResourceList(generics.ListCreateAPIView): + queryset = ServiceResource.objects.all() + serializer_class = ServiceResourceSerializer + +class ServiceResourceDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = ServiceResource.objects.all() + serializer_class = ServiceResourceSerializer + + diff --git a/planetstack/core/views/site_privileges.py b/planetstack/core/views/site_privileges.py index 37fc371..90053e5 100644 --- a/planetstack/core/views/site_privileges.py +++ b/planetstack/core/views/site_privileges.py @@ -1,66 +1,13 @@ -from django.http import Http404 -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import status - -from core.api.site_privileges import add_site_privilege, delete_site_privilege, get_site_privileges, update_site_privilege from core.serializers import SitePrivilegeSerializer -from util.request import parse_request - - -class SitePrivilegeListCreate(APIView): - """ - List all site_privileges or create a new site_privilege. - """ - - def post(self, request, format = None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'site_privilege' in data: - site_privilege = add_site_privilege(data['auth'], data['site_privilege']) - serializer = SitePrivilegeSerializer(site_privilege) - return Response(serializer.data, status=status.HTTP_201_CREATED) - else: - site_privileges = get_site_privileges(data['auth']) - serializer = SitePrivilegeSerializer(site_privileges, many=True) - return Response(serializer.data) - - -class SitePrivilegeRetrieveUpdateDestroy(APIView): - """ - Retrieve, update or delete a site_privilege - """ +from rest_framework import generics +from core.models import SitePrivilege - def post(self, request, pk, format=None): - """Retrieve a site_privilege""" - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - site_privileges = get_site_privileges(data['auth'], pk) - if not site_privileges: - return Response(status=status.HTTP_404_NOT_FOUND) - serializer = SitePrivilegeSerializer(site_privileges[0]) - return Response(serializer.data) +class SitePrivilegeList(generics.ListCreateAPIView): + queryset = SitePrivilege.objects.all() + serializer_class = SitePrivilegeSerializer - def put(self, request, pk, format=None): - """update a site_privilege""" - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'site_privilege' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) +class SitePrivilegeDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = SitePrivilege.objects.all() + serializer_class = SitePrivilegeSerializer - site_privilege = update_site_privilege(pk, data['site_privilege']) - serializer = SitePrivilegeSerializer(site_privilege) - return Response(serializer.data) - def delete(self, request, pk, format=None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - delete_site_privilege(data['auth'], pk) - return Response(status=status.HTTP_204_NO_CONTENT) - - - diff --git a/planetstack/core/views/sites.py b/planetstack/core/views/sites.py index 6449b67..4ec9cb2 100644 --- a/planetstack/core/views/sites.py +++ b/planetstack/core/views/sites.py @@ -1,66 +1,11 @@ -from django.http import Http404 -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import status - -from core.api.sites import add_site, delete_site, get_sites from core.serializers import SiteSerializer -from util.request import parse_request - - -class SiteListCreate(APIView): - """ - List all sites or create a new site. - """ - - def post(self, request, format = None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'site' in data: - site = add_site(data['auth'], data['site']) - serializer = SiteSerializer(site) - return Response(serializer.data, status=status.HTTP_201_CREATED) - else: - sites = get_sites(data['auth']) - serializer = SiteSerializer(sites, many=True) - return Response(serializer.data) - - -class SiteRetrieveUpdateDestroy(APIView): - """ - Retrieve, update or delete a site - """ - - def post(self, request, pk, format=None): - """Retrieve a site""" - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - sites = get_sites(data['auth'], {'id': pk}) - if not sites: - return Response(status=status.HTTP_404_NOT_FOUND) - serializer = SiteSerializer(sites[0]) - return Response(serializer.data) - - def put(self, request, pk, format=None): - """update a site""" - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'site' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) +from rest_framework import generics +from core.models import Site - site = update_site(pk, data['site']) - serializer = SiteSerializer(site) - return Response(serializer.data) +class SiteList(generics.ListCreateAPIView): + queryset = Site.objects.all() + serializer_class = SiteSerializer - def delete(self, request, pk, format=None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - delete_site(data['auth'], {'id': pk}) - return Response(status=status.HTTP_204_NO_CONTENT) - - - +class SiteDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Site.objects.all() + serializer_class = SiteSerializer diff --git a/planetstack/core/views/slice_memberships.py b/planetstack/core/views/slice_memberships.py index 4bb581c..13f0707 100644 --- a/planetstack/core/views/slice_memberships.py +++ b/planetstack/core/views/slice_memberships.py @@ -1,66 +1,13 @@ -from django.http import Http404 -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import status - -from core.api.slice_memberships import add_slice_membership, delete_slice_membership, get_slice_memberships, update_slice_membership from core.serializers import SliceMembershipSerializer -from util.request import parse_request - - -class SliceMembershipListCreate(APIView): - """ - List all slice_memberships or create a new slice_membership. - """ - - def post(self, request, format = None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'slice_membership' in data: - slice_membership = add_slice_membership(data['auth'], data['slice_membership']) - serializer = SliceMembershipSerializer(slice_membership) - return Response(serializer.data, status=status.HTTP_201_CREATED) - else: - slice_memberships = get_slice_memberships(data['auth']) - serializer = SliceMembershipSerializer(slice_memberships, many=True) - return Response(serializer.data) - - -class SliceMembershipRetrieveUpdateDestroy(APIView): - """ - Retrieve, update or delete a slice_membership - """ +from rest_framework import generics +from core.models import SliceMembership - def post(self, request, pk, format=None): - """Retrieve a slice_membership""" - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - slice_memberships = get_slice_memberships(data['auth'], pk) - if not slice_memberships: - return Response(status=status.HTTP_404_NOT_FOUND) - serializer = SliceMembershipSerializer(slice_memberships[0]) - return Response(serializer.data) +class SliceMembershipList(generics.ListCreateAPIView): + queryset = SliceMembership.objects.all() + serializer_class = SliceMembershipSerializer - def put(self, request, pk, format=None): - """update a slice_membership""" - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'slice_membership' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) +class SliceMembershipDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = SliceMembership.objects.all() + serializer_class = SliceMembershipSerializer - slice_membership = update_slice_membership(pk, data['slice_membership']) - serializer = SliceMembershipSerializer(slice_membership) - return Response(serializer.data) - def delete(self, request, pk, format=None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - delete_slice_membership(data['auth'], pk) - return Response(status=status.HTTP_204_NO_CONTENT) - - - diff --git a/planetstack/core/views/slices.py b/planetstack/core/views/slices.py index 5954d0c..e3ab139 100644 --- a/planetstack/core/views/slices.py +++ b/planetstack/core/views/slices.py @@ -1,66 +1,13 @@ -from django.http import Http404 -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import status - -from core.api.slices import add_slice, delete_slice, get_slices, update_slice from core.serializers import SliceSerializer -from util.request import parse_request - - -class SliceListCreate(APIView): - """ - List all slices or create a new slice. - """ - - def post(self, request, format = None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'slice' in data: - slice = add_slice(data['auth'], data['slice']) - serializer = SliceSerializer(slice) - return Response(serializer.data, status=status.HTTP_201_CREATED) - else: - slices = get_slices(data['auth']) - serializer = SliceSerializer(slices, many=True) - return Response(serializer.data) - - -class SliceRetrieveUpdateDestroy(APIView): - """ - Retrieve, update or delete a slice - """ +from rest_framework import generics +from core.models import Slice - def post(self, request, pk, format=None): - """Retrieve a slice""" - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - slices = get_slices(data['auth'], pk) - if not slices: - return Response(status=status.HTTP_404_NOT_FOUND) - serializer = SliceSerializer(slices[0]) - return Response(serializer.data) +class SliceList(generics.ListCreateAPIView): + queryset = Slice.objects.all() + serializer_class = SliceSerializer - def put(self, request, pk, format=None): - """update a slice""" - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'slice' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) +class SliceDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Slice.objects.all() + serializer_class = SliceSerializer - slice = update_slice(pk, data['slice']) - serializer = SliceSerializer(slice) - return Response(serializer.data) - def delete(self, request, pk, format=None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - delete_slice(data['auth'], pk) - return Response(status=status.HTTP_204_NO_CONTENT) - - - diff --git a/planetstack/core/views/slivers.py b/planetstack/core/views/slivers.py index 3741cce..bb310da 100644 --- a/planetstack/core/views/slivers.py +++ b/planetstack/core/views/slivers.py @@ -1,66 +1,13 @@ -from django.http import Http404 -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import status - -from core.api.slivers import add_sliver, delete_sliver, get_slivers, update_sliver from core.serializers import SliverSerializer -from util.request import parse_request - - -class SliverListCreate(APIView): - """ - List all slivers or create a new sliver. - """ - - def post(self, request, format = None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'sliver' in data: - sliver = add_sliver(data['auth'], data['sliver']) - serializer = SliverSerializer(sliver) - return Response(serializer.data, status=status.HTTP_201_CREATED) - else: - slivers = get_slivers(data['auth']) - serializer = SliverSerializer(slivers, many=True) - return Response(serializer.data) - - -class SliverRetrieveUpdateDestroy(APIView): - """ - Retrieve, update or delete a sliver - """ +from rest_framework import generics +from core.models import Sliver - def post(self, request, pk, format=None): - """Retrieve a sliver""" - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - slivers = get_slivers(data['auth'], pk) - if not slivers: - return Response(status=status.HTTP_404_NOT_FOUND) - serializer = SliverSerializer(slivers[0]) - return Response(serializer.data) +class SliverList(generics.ListCreateAPIView): + queryset = Sliver.objects.all() + serializer_class = SliverSerializer - def put(self, request, pk, format=None): - """update a sliver""" - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'sliver' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) +class SliverDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Sliver.objects.all() + serializer_class = SliverSerializer - sliver = update_sliver(pk, data['sliver']) - serializer = SliverSerializer(sliver) - return Response(serializer.data) - def delete(self, request, pk, format=None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - delete_sliver(data['auth'], pk) - return Response(status=status.HTTP_204_NO_CONTENT) - - - diff --git a/planetstack/core/views/tags.py b/planetstack/core/views/tags.py new file mode 100644 index 0000000..ba8a035 --- /dev/null +++ b/planetstack/core/views/tags.py @@ -0,0 +1,13 @@ +from core.serializers import TagSerializer +from rest_framework import generics +from core.models import Tag + +class TagList(generics.ListCreateAPIView): + queryset = Tag.objects.all() + serializer_class = TagSerializer + +class TagDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = Tag.objects.all() + serializer_class = TagSerializer + + diff --git a/planetstack/core/views/users.py b/planetstack/core/views/users.py index 8b27928..06ac0f3 100644 --- a/planetstack/core/views/users.py +++ b/planetstack/core/views/users.py @@ -1,66 +1,11 @@ -from django.http import Http404 -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import status - -from core.api.users import add_user, delete_user, get_users, update_user from core.serializers import UserSerializer -from util.request import parse_request - - -class UserListCreate(APIView): - """ - List all users or create a new user. - """ - - def post(self, request, format = None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'user' in data: - user = add_user(data['auth'], data['user']) - serializer = UserSerializer(user) - return Response(serializer.data, status=status.HTTP_201_CREATED) - else: - users = get_users(data['auth']) - serializer = UserSerializer(users, many=True) - return Response(serializer.data) - - -class UserRetrieveUpdateDestroy(APIView): - """ - Retrieve, update or delete a user - """ - - def post(self, request, pk, format=None): - """Retrieve a user""" - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - users = get_users(data['auth'], {'id': pk}) - if not users: - return Response(status=status.HTTP_404_NOT_FOUND) - serializer = UserSerializer(users[0]) - return Response(serializer.data) - - def put(self, request, pk, format=None): - """update a user""" - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - elif 'user' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) +from rest_framework import generics +from core.models import User - user = update_user(pk, data['user']) - serializer = UserSerializer(user) - return Response(serializer.data) +class UserList(generics.ListCreateAPIView): + queryset = User.objects.all() + serializer_class = UserSerializer - def delete(self, request, pk, format=None): - data = parse_request(request.DATA) - if 'auth' not in data: - return Response(status=status.HTTP_400_BAD_REQUEST) - delete_user(data['auth'], {'id': pk}) - return Response(status=status.HTTP_204_NO_CONTENT) - - - +class UserDetail(generics.RetrieveUpdateDestroyAPIView): + queryset = User.objects.all() + serializer_class = UserSerializer diff --git a/planetstack/openstack/backend.py b/planetstack/openstack/backend.py index 43afba7..2f4aa71 100644 --- a/planetstack/openstack/backend.py +++ b/planetstack/openstack/backend.py @@ -1,6 +1,6 @@ import threading from openstack.observer import OpenStackObserver -from openstack.event_listener import EventListener +from openstack.event_manager import EventListener class Backend: @@ -11,7 +11,7 @@ class Backend: observer_thread.start() # start event listene - event_listener = EventListener() - event_listener_thread = threading.Thread(target=event_listener.run) - event_listener_thread.start() + event_manager = EventListener(wake_up=observer.wake_up) + event_manager_thread = threading.Thread(target=event_manager.run) + event_manager_thread.start() diff --git a/planetstack/openstack/driver.py b/planetstack/openstack/driver.py index b9faa97..5bb1eb2 100644 --- a/planetstack/openstack/driver.py +++ b/planetstack/openstack/driver.py @@ -18,12 +18,12 @@ class OpenStackDriver: else: self.shell = OpenStackClient() - def create_role(self, name): + def create_role(self, name): roles = self.shell.keystone.roles.findall(name=name) if not roles: role = self.shell.keystone.roles.create(name) else: - role = roles[0] + role = roles[0] return role def delete_role(self, filter): @@ -56,9 +56,9 @@ class OpenStackDriver: for tenant in tenants: # nova does not automatically delete the tenant's instances # so we manually delete instances before deleteing the tenant - instances = self.shell.nova_db.instance_get_all_by_filters(ctx, + instances = self.shell.nova_db.instance_get_all_by_filters(ctx, {'project_id': tenant.id}, 'id', 'asc') - client = OpenStackClient(tenant=tenant) + client = OpenStackClient(tenant=tenant.name) driver = OpenStackDriver(client=client) for instance in instances: driver.destroy_instance(instance.id) @@ -160,12 +160,12 @@ class OpenStackDriver: if router and subnet: self.shell.quantum.remove_interface_router(router_id, {'subnet_id': subnet_id}) - def create_network(self, name): + def create_network(self, name, shared=False): nets = self.shell.quantum.list_networks(name=name)['networks'] if nets: net = nets[0] else: - net = self.shell.quantum.create_network({'network': {'name': name}})['network'] + net = self.shell.quantum.create_network({'network': {'name': name, 'shared': shared}})['network'] return net def delete_network(self, id): @@ -210,7 +210,7 @@ class OpenStackDriver: for snet in subnets: if snet['cidr'] == cidr_ip and snet['network_id'] == network_id: subnet = snet - + if not subnet: allocation_pools = [{'start': start, 'end': end}] subnet = {'subnet': {'name': name, @@ -218,7 +218,7 @@ class OpenStackDriver: 'ip_version': ip_version, 'cidr': cidr_ip, 'dns_nameservers': ['8.8.8.8', '8.8.4.4'], - 'allocation_pools': allocation_pools}} + 'allocation_pools': allocation_pools}} subnet = self.shell.quantum.create_subnet(subnet)['subnet'] self.add_external_route(subnet) # TODO: Add route to external network @@ -239,7 +239,15 @@ class OpenStackDriver: self.delete_external_route(subnet) return 1 - def add_external_route(self, subnet): + def get_external_routes(self): + status, output = commands.getstatusoutput('route') + routes = output.split('\n')[3:] + return routes + + def add_external_route(self, subnet, routes=[]): + if not routes: + routes = self.get_external_routes() + ports = self.shell.quantum.list_ports()['ports'] gw_ip = subnet['gateway_ip'] @@ -256,14 +264,23 @@ class OpenStackDriver: gw_port = port router_id = gw_port['device_id'] router = self.shell.quantum.show_router(router_id)['router'] - ext_net = router['external_gateway_info']['network_id'] - for port in ports: - if port['device_id'] == router_id and port['network_id'] == ext_net: - ip_address = port['fixed_ips'][0]['ip_address'] + if router and router.get('external_gateway_info'): + ext_net = router['external_gateway_info']['network_id'] + for port in ports: + if port['device_id'] == router_id and port['network_id'] == ext_net: + ip_address = port['fixed_ips'][0]['ip_address'] if ip_address: - cmd = "route add -net %s dev br-ex gw %s" % (subnet['cidr'], ip_address) - commands.getstatusoutput(cmd) + # check if external route already exists + route_exists = False + if routes: + for route in routes: + if subnet['cidr'] in route and ip_address in route: + route_exists = True + if not route_exists: + cmd = "route add -net %s dev br-ex gw %s" % (subnet['cidr'], ip_address) + s, o = commands.getstatusoutput(cmd) + #print cmd, "\n", s, o return 1 @@ -311,9 +328,37 @@ class OpenStackDriver: keys = self.shell.nova.keypairs.findall(id=id) for key in keys: self.shell.nova.keypairs.delete(key) - return 1 + return 1 - def spawn_instance(self, name, key_name=None, hostname=None, image_id=None, security_group=None, pubkeys=[]): + def get_private_networks(self, tenant=None): + if not tenant: + tenant = self.shell.nova.tenant + tenant = self.shell.keystone.tenants.find(name=tenant) + search_opts = {"tenant_id": tenant.id, "shared": False} + private_networks = self.shell.quantum.list_networks(**search_opts) + return private_networks + + def get_shared_networks(self): + search_opts = {"shared": True} + shared_networks = self.shell.quantum.list_networks(**search_opts) + return shared_networks + + def get_network_subnet(self, network_id): + subnet_id = None + subnet = None + if network_id: + os_networks = self.shell.quantum.list_networks(id=network_id)["networks"] + if os_networks: + os_network = os_networks[0] + if os_network['subnets']: + subnet_id = os_network['subnets'][0] + os_subnets = self.shell.quantum.list_subnets(id=subnet_id)['subnets'] + if os_subnets: + subnet = os_subnets[0]['cidr'] + + return (subnet_id, subnet) + + def spawn_instance(self, name, key_name=None, hostname=None, image_id=None, security_group=None, pubkeys=[], nics=None): flavor_name = self.config.nova_default_flavor flavor = self.shell.nova.flavors.find(name=flavor_name) #if not image: @@ -337,7 +382,8 @@ class OpenStackDriver: security_group = security_group, files=files, scheduler_hints=hints, - availability_zone=availability_zone) + availability_zone=availability_zone, + nics=nics) return server def destroy_instance(self, id): diff --git a/planetstack/openstack/event_listener.py b/planetstack/openstack/event_manager.py similarity index 68% rename from planetstack/openstack/event_listener.py rename to planetstack/openstack/event_manager.py index 5e0afff..ab50f66 100644 --- a/planetstack/openstack/event_listener.py +++ b/planetstack/openstack/event_manager.py @@ -1,11 +1,13 @@ import threading import requests, json + from core.models import * -from openstack.manager import OpenStackManager +#from openstack.manager import OpenStackManager +from planetstack.config import Config import os import base64 -import fofum +from fofum import Fofum # decorator that marks dispatachable event methods def event(func): @@ -13,9 +15,9 @@ def event(func): return func class EventHandler: - + # This code is currently not in use. def __init__(self): - self.manager = OpenStackManager() + pass #self.manager = OpenStackManager() @staticmethod def get_events(): @@ -82,22 +84,52 @@ class EventHandler: self.manager.destroy_instance(instance_id) +class EventSender: + def __init__(self,user=None,clientid=None): + try: + clid = Config().feefie_client_id + user = Config().feefie_client_user + except: + clid = 'planetstack_core_team' + user = 'pl' + + self.fofum = Fofum(user=user) + self.fofum.make(clid) + + def fire(self): + self.fofum.fire() class EventListener: - def __init__(self): + def __init__(self,wake_up=None): self.handler = EventHandler() + self.wake_up = wake_up def handle_event(self, payload): payload_dict = json.loads(payload) - event = payload_dict['event'] - ctx = payload_dict['ctx'] - self.handler.dispatch(event,**ctx) + + # The code below will come back when we optimize the observer syncs + # into 'small' and 'big' syncs. + + #event = payload_dict['event'] + #ctx = payload_dict['ctx'] + #self.handler.dispatch(event,**ctx) + + if (self.wake_up): + self.wake_up() + def run(self): # This is our unique client id, to be used when firing and receiving events - clid = base64.urlsafe_b64encode(os.urandom(12)) + # It needs to be generated once and placed in the config file + + try: + clid = Config().feefie_client_id + user = Config().feefie_client_user + except: + clid = 'planetstack_core_team' + user = 'pl' - f = Fofum() + f = Fofum(user=user) listener_thread = threading.Thread(target=f.listen_for_event,args=(clid,self.handle_event)) listener_thread.start() diff --git a/planetstack/openstack/manager.py b/planetstack/openstack/manager.py index 3ae7dea..2fb4ff8 100644 --- a/planetstack/openstack/manager.py +++ b/planetstack/openstack/manager.py @@ -301,17 +301,32 @@ class OpenStackManager: #del_route = 'route del -net %s' % self.cidr #commands.getstatusoutput(del_route) + def get_requested_networks(self, slice): + network_ids = [x.network_id for x in slice.networks.all()] + + if slice.network_id is not None: + network_ids.append(slice.network_id) + + networks = [] + for network_id in network_ids: + networks.append({"net-id": network_id}) + + return networks + @require_enabled def save_sliver(self, sliver): if not sliver.instance_id: + nics = self.get_requested_networks(sliver.slice) + file("/tmp/scott-manager","a").write("slice: %s\nreq: %s\n" % (str(sliver.slice.name), str(nics))) slice_memberships = SliceMembership.objects.filter(slice=sliver.slice) pubkeys = [sm.user.public_key for sm in slice_memberships if sm.user.public_key] - pubkeys.append(sliver.creator.public_key) + pubkeys.append(sliver.creator.public_key) instance = self.driver.spawn_instance(name=sliver.name, key_name = sliver.creator.keyname, image_id = sliver.image.image_id, hostname = sliver.node.name, - pubkeys = pubkeys ) + pubkeys = pubkeys, + nics = nics ) sliver.instance_id = instance.id sliver.instance_name = getattr(instance, 'OS-EXT-SRV-ATTR:instance_name') @@ -368,7 +383,7 @@ class OpenStackManager: from core.models.image import Image # collect local images images = Image.objects.all() - images_dict = {} + images_dict = {} for image in images: images_dict[image.name] = image @@ -391,4 +406,149 @@ class OpenStackManager: old_image_names = set(images_dict.keys()).difference(glance_images_dict.keys()) Image.objects.filter(name__in=old_image_names).delete() + @require_enabled + def save_network(self, network): + if not network.network_id: + if network.template.sharedNetworkName: + network.network_id = network.template.sharedNetworkId + (network.subnet_id, network.subnet) = self.driver.get_network_subnet(network.network_id) + else: + network_name = network.name + + # create network + os_network = self.driver.create_network(network_name, shared=True) + network.network_id = os_network['id'] + + # create router + router = self.driver.create_router(network_name) + network.router_id = router['id'] + + # create subnet + next_subnet = self.get_next_subnet() + cidr = str(next_subnet.cidr) + ip_version = next_subnet.version + start = str(next_subnet[2]) + end = str(next_subnet[-2]) + subnet = self.driver.create_subnet(name=network_name, + network_id = network.network_id, + cidr_ip = cidr, + ip_version = ip_version, + start = start, + end = end) + network.subnet = cidr + network.subnet_id = subnet['id'] + # add subnet as interface to slice's router + self.driver.add_router_interface(router['id'], subnet['id']) + # add external route + self.driver.add_external_route(subnet) + + network.save() + network.enacted = datetime.now() + network.save(update_fields=['enacted']) + + def delete_network(self, network): + if (network.router_id) and (network.subnet_id): + self.driver.delete_router_interface(network.router_id, network.subnet_id) + if network.subnet_id: + self.driver.delete_subnet(network.subnet_id) + if network.router_id: + self.driver.delete_router(network.router_id) + if network.network_id: + self.driver.delete_network(network.network_id) + + def save_network_template(self, template): + if (template.sharedNetworkName) and (not template.sharedNetworkId): + os_networks = self.driver.shell.quantum.list_networks(name=template.sharedNetworkName)['networks'] + if os_networks: + template.sharedNetworkId = os_networks[0]["id"] + + template.save() + template.enacted = datetime.now() + template.save(update_fields=['enacted']) + + def find_or_make_template_for_network(self, name): + """ Given a network name, try to guess the right template for it """ + + # templates for networks we may encounter + if name=='nat-net': + template_dict = None # {"name": "private-nat", "visibility": "private", "translation": "nat"} + elif name=='sharednet1': + template_dict = {"name": "dedicated-public", "visibility": "public", "translation": "none"} + else: + template_dict = {"name": "private", "visibility": "private", "translation": "none"} + + # if we have an existing template return it + templates = NetworkTemplate.objects.filter(name=template_dict["name"]) + if templates: + return templates[0] + + if template_dict == None: + return None + + template = NetworkTemplate(**template_dict) + template.save() + return template + + def refresh_network_templates(self): + for template in NetworkTemplate.objects.all(): + if (template.sharedNetworkName) and (not template.sharedNetworkId): + # this will cause us to try to fill in the sharedNetworkId + self.save_network_template(template) + + def refresh_networks(self): + # get a list of all networks in the model + + networks = Network.objects.all() + networks_by_name = {} + networks_by_id = {} + for network in networks: + networks_by_name[network.name] = network + networks_by_id[network.network_id] = network + + # Get a list of all shared networks in OS + + os_networks = self.driver.shell.quantum.list_networks()['networks'] + os_networks_by_name = {} + os_networks_by_id = {} + for os_network in os_networks: + os_networks_by_name[os_network['name']] = os_network + os_networks_by_id[os_network['id']] = os_network + + for (uuid, os_network) in os_networks_by_id.items(): + #print "checking OS network", os_network['name'] + if (os_network['shared']) and (uuid not in networks_by_id): + # Only automatically create shared networks. This is for Andy's + # nat-net and sharednet1. + + owner_slice = Slice.objects.get(tenant_id = os_network['tenant_id']) + template = self.find_or_make_template_for_network(os_network['name']) + + if (template is None): + # This is our way of saying we don't want to auto-instantiate + # this network type. + continue + + (subnet_id, subnet) = self.driver.get_network_subnet(os_network['id']) + + if owner_slice: + #print "creating model object for OS network", os_network['name'] + new_network = Network(name = os_network['name'], + template = template, + owner = owner_slice, + network_id = uuid, + subnet_id = subnet_id) + new_network.save() + + for (network_id, network) in networks_by_id.items(): + # If the network disappeared from OS, then reset its network_id to None + if (network.network_id is not None) and (network.network_id not in os_networks_by_id): + network.network_id = None + + # If no OS object exists, then saving the network will create one + if (network.network_id is None): + #print "creating OS network for", network.name + self.save_network(network) + else: + pass #print "network", network.name, "has its OS object" + diff --git a/planetstack/openstack/observer.py b/planetstack/openstack/observer.py index 6fcb3b4..73bb114 100644 --- a/planetstack/openstack/observer.py +++ b/planetstack/openstack/observer.py @@ -1,11 +1,15 @@ import time import traceback +import commands +import threading + from datetime import datetime from collections import defaultdict from core.models import * from django.db.models import F, Q from openstack.manager import OpenStackManager -from util.logger import Logger, logging +from util.logger import Logger, logging, logger +#from timeout import timeout logger = Logger(logfile='observer.log', level=logging.INFO) @@ -14,21 +18,98 @@ class OpenStackObserver: def __init__(self): self.manager = OpenStackManager() + # The Condition object that gets signalled by Feefie events + self.event_cond = threading.Condition() + + def wait_for_event(self, timeout): + self.event_cond.acquire() + self.event_cond.wait(timeout) + self.event_cond.release() + + def wake_up(self): + logger.info('Wake up routine called. Event cond %r'%self.event_cond) + self.event_cond.acquire() + self.event_cond.notify() + self.event_cond.release() def run(self): if not self.manager.enabled or not self.manager.has_openstack: return while True: try: + logger.info('Observer run loop') #self.sync_roles() - self.sync_tenants() - self.sync_users() - self.sync_user_tenant_roles() - self.sync_slivers() - self.sync_sliver_ips() - time.sleep(7) + + logger.info('Calling sync tenants') + try: + self.sync_tenants() + except: + logger.log_exc("Exception in sync_tenants") + traceback.print_exc() + + logger.info('Calling sync users') + try: + self.sync_users() + except: + logger.log_exc("Exception in sync_users") + traceback.print_exc() + + logger.info('Calling sync tenant roles') + try: + self.sync_user_tenant_roles() + except: + logger.log_exc("Exception in sync_users") + traceback.print_exc() + + logger.info('Calling sync slivers') + try: + self.sync_slivers() + except: + logger.log_exc("Exception in sync slivers") + traceback.print_exc() + + logger.info('Calling sync sliver ips') + try: + self.sync_sliver_ips() + except: + logger.log_exc("Exception in sync_sliver_ips") + traceback.print_exc() + + logger.info('Calling sync networks') + try: + self.sync_networks() + except: + logger.log_exc("Exception in sync_networks") + traceback.print_exc() + + logger.info('Calling sync network slivers') + try: + self.sync_network_slivers() + except: + logger.log_exc("Exception in sync_network_slivers") + traceback.print_exc() + + logger.info('Calling sync external routes') + try: + self.sync_external_routes() + except: + logger.log_exc("Exception in sync_external_routes") + traceback.print_exc() + + logger.info('Waiting for event') + tBeforeWait = time.time() + self.wait_for_event(timeout=300) + + # Enforce 5 minutes between wakeups + tSleep = 300 - (time.time() - tBeforeWait) + if tSleep > 0: + logger.info('Sleeping for %d seconds' % tSleep) + time.sleep(tSleep) + + logger.info('Observer woken up') except: - traceback.print_exc() + logger.log_exc("Exception in observer run loop") + traceback.print_exc() def sync_roles(self): """ @@ -231,7 +312,7 @@ class OpenStackObserver: # update manager context self.manager.init_caller(sliver.creator, sliver.slice.name) self.manager.save_sliver(sliver) - logger.info("saved sliver: %s %s" % (sliver)) + logger.info("saved sliver: %s" % (sliver)) except: logger.log_exc("save sliver failed: %s" % sliver) @@ -243,7 +324,7 @@ class OpenStackObserver: sliver_dict[sliver.instance_id] = sliver # delete sliver that don't have a sliver record - ctx = self.manager.driver.shell.nova_db.ctx + ctx = self.manager.driver.shell.nova_db.ctx instances = self.manager.driver.shell.nova_db.instance_get_all(ctx) for instance in instances: if instance.uuid not in sliver_dict: @@ -263,7 +344,7 @@ class OpenStackObserver: for sliver in slivers: # update connection self.manager.init_admin(tenant=sliver.slice.name) - servers = self.manager.client.nova.servers.findall(id=sliver.instance_id) + servers = self.manager.driver.shell.nova.servers.findall(id=sliver.instance_id) if not servers: continue server = servers[0] @@ -273,3 +354,106 @@ class OpenStackObserver: sliver.ip = ips[0]['addr'] sliver.save() logger.info("saved sliver ip: %s %s" % (sliver, ips[0])) + + def sync_external_routes(self): + routes = self.manager.driver.get_external_routes() + subnets = self.manager.driver.shell.quantum.list_subnets()['subnets'] + for subnet in subnets: + try: + self.manager.driver.add_external_route(subnet, routes) + except: + logger.log_exc("failed to add external route for subnet %s" % subnet) + + def sync_network_slivers(self): + networkSlivers = NetworkSliver.objects.all() + networkSlivers_by_id = {} + networkSlivers_by_port = {} + for networkSliver in networkSlivers: + networkSlivers_by_id[networkSliver.id] = networkSliver + networkSlivers_by_port[networkSliver.port_id] = networkSliver + + networks = Network.objects.all() + networks_by_id = {} + for network in networks: + networks_by_id[network.network_id] = network + + slivers = Sliver.objects.all() + slivers_by_instance_id = {} + for sliver in slivers: + slivers_by_instance_id[sliver.instance_id] = sliver + + ports = self.manager.driver.shell.quantum.list_ports()["ports"] + for port in ports: + if port["id"] in networkSlivers_by_port: + # we already have it + print "already accounted for port", port["id"] + continue + + if port["device_owner"] != "compute:nova": + # we only want the ports that connect to instances + continue + + network = networks_by_id.get(port['network_id'], None) + if not network: + #print "no network for port", port["id"], "network", port["network_id"] + continue + + sliver = slivers_by_instance_id.get(port['device_id'], None) + if not sliver: + print "no sliver for port", port["id"], "device_id", port['device_id'] + continue + + if network.template.sharedNetworkId is not None: + # If it's a shared network template, then more than one network + # object maps to the quantum network. We have to do a whole bunch + # of extra work to find the right one. + networks = network.template.network_set.all() + network = None + for candidate_network in networks: + if (candidate_network.owner == sliver.slice): + print "found network", candidate_network + network = candidate_network + + if not network: + print "failed to find the correct network for a shared template for port", port["id"], "network", port["network_id"] + continue + + if not port["fixed_ips"]: + print "port", port["id"], "has no fixed_ips" + continue + +# print "XXX", port + + ns = NetworkSliver(network=network, + sliver=sliver, + ip=port["fixed_ips"][0]["ip_address"], + port_id=port["id"]) + ns.save() + + def sync_networks(self): + """ + save all networks where enacted < updated or enacted == None. Remove networks that + no don't exist in openstack db if they have an enacted time (enacted != None). + """ + # get all users that need to be synced (enacted < updated or enacted is None) + pending_networks = Network.objects.filter(Q(enacted__lt=F('updated')) | Q(enacted=None)) + for network in pending_networks: + if network.owner and network.owner.creator: + try: + # update manager context + self.manager.init_caller(network.owner.creator, network.owner.name) + self.manager.save_network(network) + logger.info("saved network: %s" % (network)) + except: + logger.log_exc("save network failed: %s" % network) + + # get all networks where enacted != null. We can assume these users + # have previously been synced and need to be checed for deletion. + networks = Network.objects.filter(enacted__isnull=False) + network_dict = {} + for network in networks: + network_dict[network.network_id] = network + + # TODO: delete Network objects if quantum network doesn't exist + # (need to write self.manager.driver.shell.quantum_db) + diff --git a/planetstack/openstack/openstack-db-cleanup.sh b/planetstack/openstack/openstack-db-cleanup.sh new file mode 100755 index 0000000..9baca6e --- /dev/null +++ b/planetstack/openstack/openstack-db-cleanup.sh @@ -0,0 +1,16 @@ +#! /bin/bash + +# to install +# chmod 0755 /opt/planetstack/openstack/openstack-db-cleanup.sh +# ln -s /opt/planetstack/openstack/openstack-db-cleanup.sh /etc/cron.daily/openstack-db-cleanup.cron + +mkdir -p /opt/planetstack/ovs-backups +BACKUP_NAME=/opt/planetstack/ovs-backups/backup-`date "+%Y-%M-%d"`.sql +mysqldump --create-options --routines --triggers --databases keystone ovs_quantum nova glance cinder > $BACKUP_NAME +gzip $BACKUP_NAME + +mysql keystone -e "DELETE FROM token WHERE NOT DATE_SUB(CURDATE(),INTERVAL 2 DAY) <= expires;" +mysqlcheck --optimize --databases keystone ovs_quantum nova glance cinder + +date >> /var/log/openstack-db-cleanup.log +mysql keystone -e "select count(*) from token;" >> /var/log/openstack-db-cleanup.log diff --git a/planetstack/planetstack/urls.py b/planetstack/planetstack/urls.py index 30eed05..66f376a 100644 --- a/planetstack/planetstack/urls.py +++ b/planetstack/planetstack/urls.py @@ -2,17 +2,17 @@ from django.conf.urls import patterns, include, url # Uncomment the next two lines to enable the admin: from django.contrib import admin -from core.views.roles import RoleListCreate, RoleRetrieveUpdateDestroy -from core.views.sites import SiteListCreate, SiteRetrieveUpdateDestroy -from core.views.site_privileges import SitePrivilegeListCreate, SitePrivilegeRetrieveUpdateDestroy -from core.views.users import UserListCreate, UserRetrieveUpdateDestroy -from core.views.slices import SliceListCreate, SliceRetrieveUpdateDestroy -from core.views.slice_memberships import SliceMembershipListCreate, SliceMembershipRetrieveUpdateDestroy -from core.views.slivers import SliverListCreate, SliverRetrieveUpdateDestroy -from core.views.deployment_networks import DeploymentListCreate, DeploymentRetrieveUpdateDestroy -from core.views.images import ImageListCreate, ImageRetrieveUpdateDestroy -from core.views.nodes import NodeListCreate, NodeRetrieveUpdateDestroy -from core.models import Site +from core.views.roles import RoleList, RoleDetail +from core.views.sites import SiteList, SiteDetail +from core.views.site_privileges import SitePrivilegeList, SitePrivilegeDetail +from core.views.users import UserList, UserDetail +from core.views.slices import SliceList, SliceDetail +from core.views.slice_memberships import SliceMembershipList, SliceMembershipDetail +from core.views.slivers import SliverList, SliverDetail +from core.views.deployments import DeploymentList, DeploymentDetail +from core.views.images import ImageList, ImageDetail +from core.views.nodes import NodeList, NodeDetail +from core.models import * from core.api_root import api_root from rest_framework import generics @@ -31,35 +31,36 @@ urlpatterns = patterns('', url(r'^plstackapi/$', api_root), - url(r'^plstackapi/roles/$', RoleListCreate.as_view(), name='role-list'), - url(r'^plstackapi/roles/(?P[a-zA-Z0-9]+)/$', RoleRetrieveUpdateDestroy.as_view(), name='role-detail'), + url(r'^plstackapi/roles/$', RoleList.as_view(), name='role-list'), + url(r'^plstackapi/roles/(?P[a-zA-Z0-9]+)/$', RoleDetail.as_view(), name='role-detail'), - url(r'^plstackapi/users/$', UserListCreate.as_view(), name='user-list'), - url(r'^plstackapi/users/(?P[a-zA-Z0-9_\-]+)/$', UserRetrieveUpdateDestroy.as_view(), name='user-detail'), + url(r'^plstackapi/users/$', UserList.as_view(), name='user-list'), + url(r'^plstackapi/users/(?P[a-zA-Z0-9_\-]+)/$', UserDetail.as_view(), name='user-detail'), - url(r'^plstackapi/sites/$', SiteListCreate.as_view(), name='site-list'), - url(r'^plstackapi/sites/(?P[a-zA-Z0-9_\-]+)/$', SiteRetrieveUpdateDestroy.as_view(), name='site-detail'), + url(r'^plstackapi/sites/$', SiteList.as_view(), name='site-list'), + url(r'^plstackapi/sites/(?P[a-zA-Z0-9_\-]+)/$', SiteDetail.as_view(), name='site-detail'), - url(r'^plstackapi/site_privileges/$', SitePrivilegeListCreate.as_view(), name='siteprivilege-list'), - url(r'^plstackapi/site_privileges/(?P[a-zA-Z0-9_]+)/$', SitePrivilegeRetrieveUpdateDestroy.as_view(), name='siteprivilege-detail'), + url(r'^plstackapi/site_privileges/$', SitePrivilegeList.as_view(), name='siteprivilege-list'), + url(r'^plstackapi/site_privileges/(?P[a-zA-Z0-9_]+)/$', SitePrivilegeDetail.as_view(), name='siteprivilege-detail'), + + url(r'^plstackapi/slices/$', SliceList.as_view(), name='slice-list'), - url(r'^plstackapi/slices/$', SliceListCreate.as_view(), name='slice-list'), - url(r'^plstackapi/slices/(?P[a-zA-Z0-9_\-]+)/$', SliceRetrieveUpdateDestroy.as_view(), name='slice-detail'), + url(r'^plstackapi/slices/(?P[a-zA-Z0-9_\-]+)/$', SliceDetail.as_view(), name='slice-detail'), - url(r'^plstackapi/slice_memberships/$', SliceMembershipListCreate.as_view(), name='slice_membership-list'), - url(r'^plstackapi/slice_memberships/(?P[0-9]+)/$', SliceMembershipRetrieveUpdateDestroy.as_view(), name='slice_membership-detail'), + url(r'^plstackapi/slice_memberships/$', SliceMembershipList.as_view(), name='slice-membership-list'), + url(r'^plstackapi/slice_memberships/(?P[0-9]+)/$', SliceMembershipDetail.as_view(), name='slice-membership-detail'), - url(r'^plstackapi/slivers/$', SliverListCreate.as_view(), name='sliver-list'), - url(r'^plstackapi/slivers/(?P[a-zA-Z0-9_\-]+)/$', SliverRetrieveUpdateDestroy.as_view(), name='sliver-detail'), + url(r'^plstackapi/slivers/$', SliverList.as_view(), name='sliver-list'), + url(r'^plstackapi/slivers/(?P[a-zA-Z0-9_\-]+)/$', SliverDetail.as_view(), name='sliver-detail'), - url(r'^plstackapi/nodes/$', NodeListCreate.as_view(), name='node-list'), - url(r'^plstackapi/nodes/(?P[a-zA-Z0-9_\-]+)/$', NodeRetrieveUpdateDestroy.as_view(), name='node-detail'), + url(r'^plstackapi/nodes/$', NodeList.as_view(), name='node-list'), + url(r'^plstackapi/nodes/(?P[a-zA-Z0-9_\-]+)/$', NodeDetail.as_view(), name='node-detail'), - url(r'^plstackapi/deploymentnetworks/$', DeploymentListCreate.as_view(), name='deploymentnetwork-list'), - url(r'^plstackapi/deploymentnetworks/(?P[a-zA-Z0-9\-]+)/$', DeploymentRetrieveUpdateDestroy.as_view(), name='deploymentnetwork-detail'), + url(r'^plstackapi/deployments/$', DeploymentList.as_view(), name='deployment-list'), + url(r'^plstackapi/deployments/(?P[a-zA-Z0-9\-]+)/$', DeploymentDetail.as_view(), name='deployment-detail'), - url(r'^plstackapi/images/$', ImageListCreate.as_view(), name='image-list'), - url(r'^plstackapi/images/(?P[a-zA-Z0-9_\-]+)/$', ImageRetrieveUpdateDestroy.as_view(), name='image-detail'), + url(r'^plstackapi/images/$', ImageList.as_view(), name='image-list'), + url(r'^plstackapi/images/(?P[a-zA-Z0-9_\-]+)/$', ImageDetail.as_view(), name='image-detail'), #Adding in rest_framework urls url(r'^plstackapi/', include('rest_framework.urls', namespace='rest_framework')),