Refactor to /opt/planetstack, final tweaks to make sure planetstack can run in non-openstack mode, adjustments to GUI for model focus changes
diff --git a/planetstack/__init__.py b/planetstack/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/planetstack/__init__.py
diff --git a/planetstack/core/__init__.py b/planetstack/core/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/planetstack/core/__init__.py
diff --git a/planetstack/core/admin.py b/planetstack/core/admin.py
new file mode 100644
index 0000000..6a8b71e
--- /dev/null
+++ b/planetstack/core/admin.py
@@ -0,0 +1,387 @@
+from core.models import Site
+from core.models import *
+from openstack.manager import OpenStackManager
+
+from django.contrib import admin
+from django.contrib.auth.models import Group
+from django import forms
+from django.utils.safestring import mark_safe
+from django.contrib.auth.admin import UserAdmin
+from django.contrib.admin.widgets import FilteredSelectMultiple
+from django.contrib.auth.forms import ReadOnlyPasswordHashField
+from django.contrib.auth.signals import user_logged_in
+
+
+class ReadonlyTabularInline(admin.TabularInline):
+ can_delete = False
+ extra = 0
+ editable_fields = []
+
+ def get_readonly_fields(self, request, obj=None):
+ fields = []
+ for field in self.model._meta.get_all_field_names():
+ if (not field == 'id'):
+ if (field not in self.editable_fields):
+ fields.append(field)
+ return fields
+
+ def has_add_permission(self, request):
+ return False
+
+class SliverInline(admin.TabularInline):
+ model = Sliver
+ fields = ['ip', 'name', 'slice', 'numberCores', 'image', 'key', 'node', 'deploymentNetwork']
+ extra = 0
+
+class SiteInline(admin.TabularInline):
+ model = Site
+ extra = 0
+
+class UserInline(admin.TabularInline):
+ model = User
+ extra = 0
+
+class SliceInline(admin.TabularInline):
+ model = Slice
+ extra = 0
+
+class UserInline(admin.TabularInline):
+ model = User
+ extra = 0
+
+class RoleInline(admin.TabularInline):
+ model = Role
+ extra = 0
+
+class NodeInline(admin.TabularInline):
+ model = Node
+ extra = 0
+
+class PlainTextWidget(forms.Widget):
+ def render(self, _name, value, attrs):
+ return mark_safe(value) if value is not None else ''
+
+class PlanetStackBaseAdmin(admin.ModelAdmin):
+ save_on_top = False
+
+class OSModelAdmin(PlanetStackBaseAdmin):
+ """Attach client connection to openstack on delete() and save()"""
+
+ def save_model(self, request, obj, form, change):
+ auth = request.session.get('auth', {})
+ #auth['tenant'] = request.user.site.login_base
+ obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
+ obj.save()
+
+ def delete_model(self, request, obj):
+ auth = request.session.get('auth', {})
+ #auth['tenant'] = request.user.site.login_base
+ obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
+ obj.delete()
+
+class RoleAdmin(OSModelAdmin):
+ fieldsets = [
+ ('Role', {'fields': ['role_type']})
+ ]
+ list_display = ('role_type',)
+
+
+class DeploymentNetworkAdminForm(forms.ModelForm):
+ sites = forms.ModelMultipleChoiceField(
+ queryset=Site.objects.all(),
+ required=False,
+ widget=FilteredSelectMultiple(
+ verbose_name=('Sites'), is_stacked=False
+ )
+ )
+ class Meta:
+ model = DeploymentNetwork
+
+ def __init__(self, *args, **kwargs):
+ super(DeploymentNetworkAdminForm, self).__init__(*args, **kwargs)
+
+ if self.instance and self.instance.pk:
+ self.fields['sites'].initial = self.instance.sites.all()
+
+ def save(self, commit=True):
+ deploymentNetwork = super(DeploymentNetworkAdminForm, self).save(commit=False)
+ if commit:
+ deploymentNetwork.save()
+
+ if deploymentNetwork.pk:
+ deploymentNetwork.sites = self.cleaned_data['sites']
+ self.save_m2m()
+
+ return deploymentNetwork
+
+class DeploymentNetworkAdmin(PlanetStackBaseAdmin):
+ form = DeploymentNetworkAdminForm
+ inlines = [NodeInline,]
+
+ def get_formsets(self, request, obj=None):
+ for inline in self.get_inline_instances(request, obj):
+ # hide MyInline in the add view
+ if obj is None:
+ continue
+ # give inline object access to driver and caller
+ auth = request.session.get('auth', {})
+ auth['tenant'] = request.user.site.login_base
+ inline.model.os_manager = OpenStackManager(auth=auth, caller=request.user)
+ yield inline.get_formset(request, obj)
+
+class SiteAdmin(OSModelAdmin):
+ fieldsets = [
+ (None, {'fields': ['name', 'site_url', 'enabled', 'is_public', 'login_base']}),
+ ('Location', {'fields': ['latitude', 'longitude']}),
+ ('Deployment Networks', {'fields': ['deployments']})
+ ]
+ list_display = ('name', 'login_base','site_url', 'enabled')
+ filter_horizontal = ('deployments',)
+ inlines = [NodeInline, UserInline]
+ search_fields = ['name']
+
+ def get_formsets(self, request, obj=None):
+ for inline in self.get_inline_instances(request, obj):
+ # hide MyInline in the add view
+ if obj is None:
+ continue
+ # give inline object access to driver and caller
+ auth = request.session.get('auth', {})
+ #auth['tenant'] = request.user.site.login_base
+ inline.model.os_manager = OpenStackManager(auth=auth, caller=request.user)
+ yield inline.get_formset(request, obj)
+
+class SitePrivilegeAdmin(PlanetStackBaseAdmin):
+ fieldsets = [
+ (None, {'fields': ['user', 'site', 'role']})
+ ]
+ list_display = ('user', 'site', 'role')
+
+ def save_model(self, request, obj, form, change):
+ # update openstack connection to use this site/tenant
+ auth = request.session.get('auth', {})
+ #auth['tenant'] = obj.site.login_base
+ obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
+ obj.save()
+
+ def delete_model(self, request, obj):
+ # update openstack connection to use this site/tenant
+ auth = request.session.get('auth', {})
+ #auth['tenant'] = obj.site.login_base
+ obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
+ obj.delete()
+
+class KeyAdmin(OSModelAdmin):
+ fieldsets = [
+ ('Key', {'fields': ['name', 'key', 'type', 'blacklisted']})
+ ]
+ list_display = ['name', 'key', 'type', 'blacklisted']
+
+ def get_queryset(self, request):
+ # get keys user is allowed to see
+ qs = super(KeyAdmin, self).get_queryset(request)
+ if request.user.is_superuser:
+ return qs
+ # users can only see their own keys
+ return qs.filter(user=request.user)
+
+
+class SliceAdmin(OSModelAdmin):
+ fields = ['name', 'site', 'serviceClass', 'description', 'slice_url']
+ list_display = ('name', 'site','serviceClass', 'slice_url')
+ inlines = [SliverInline]
+
+ def get_formsets(self, request, obj=None):
+ for inline in self.get_inline_instances(request, obj):
+ # hide MyInline in the add view
+ if obj is None:
+ continue
+ # give inline object access to driver and caller
+ auth = request.session.get('auth', {})
+ auth['tenant'] = obj.name # meed to connect using slice's tenant
+ inline.model.os_manager = OpenStackManager(auth=auth, caller=request.user)
+ yield inline.get_formset(request, obj)
+
+ def get_queryset(self, request):
+ qs = super(SliceAdmin, self).get_queryset(request)
+ if request.user.is_superuser:
+ return qs
+ # users can only see slices at their site
+ return qs.filter(site=request.user.site)
+
+class SliceMembershipAdmin(PlanetStackBaseAdmin):
+ fieldsets = [
+ (None, {'fields': ['user', 'slice', 'role']})
+ ]
+ list_display = ('user', 'slice', 'role')
+
+ def save_model(self, request, obj, form, change):
+ # update openstack connection to use this site/tenant
+ auth = request.session.get('auth', {})
+ auth['tenant'] = obj.slice.name
+ obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
+ obj.save()
+
+ def delete_model(self, request, obj):
+ # update openstack connection to use this site/tenant
+ auth = request.session.get('auth', {})
+ auth['tenant'] = obj.slice.name
+ obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
+ obj.delete()
+
+
+class SubnetAdmin(PlanetStackBaseAdmin):
+ fields = ['cidr', 'ip_version', 'start', 'end', 'slice']
+ list_display = ('slice','cidr', 'start', 'end', 'ip_version')
+
+ def save_model(self, request, obj, form, change):
+ # update openstack connection to use this site/tenant
+ auth = request.session.get('auth', {})
+ auth['tenant'] = obj.slice.name
+ obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
+ obj.save()
+
+ def delete_model(self, request, obj):
+ # update openstack connection to use this site/tenant
+ auth = request.session.get('auth', {})
+ auth['tenant'] = obj.slice.name
+ obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
+ obj.delete()
+
+class ImageAdmin(admin.ModelAdmin):
+ fields = ['image_id', 'name', 'disk_format', 'container_format']
+
+class NodeAdmin(admin.ModelAdmin):
+ list_display = ('name', 'site', 'deploymentNetwork')
+ list_filter = ('deploymentNetwork',)
+
+
+class SliverForm(forms.ModelForm):
+ class Meta:
+ ip = forms.CharField(widget=PlainTextWidget)
+ instance_name = forms.CharField(widget=PlainTextWidget)
+ model = Sliver
+ widgets = {
+ 'ip': PlainTextWidget(),
+ 'instance_name': PlainTextWidget(),
+ }
+
+class SliverAdmin(PlanetStackBaseAdmin):
+ form = SliverForm
+ fieldsets = [
+ ('Sliver', {'fields': ['ip', 'instance_name', 'name', 'slice', 'numberCores', 'image', 'key', 'node', 'deploymentNetwork']})
+ ]
+ list_display = ['ip', 'instance_name', 'name', 'slice', 'numberCores', 'image', 'key', 'node', 'deploymentNetwork']
+
+ def save_model(self, request, obj, form, change):
+ # update openstack connection to use this site/tenant
+ auth = request.session.get('auth', {})
+ auth['tenant'] = obj.slice.name
+ obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
+ obj.save()
+
+ def delete_model(self, request, obj):
+ # update openstack connection to use this site/tenant
+ auth = request.session.get('auth', {})
+ auth['tenant'] = obj.slice.name
+ obj.os_manager = OpenStackManager(auth=auth, caller=request.user)
+ obj.delete()
+
+class UserCreationForm(forms.ModelForm):
+ """A form for creating new users. Includes all the required
+ fields, plus a repeated password."""
+ password1 = forms.CharField(label='Password', widget=forms.PasswordInput)
+ password2 = forms.CharField(label='Password confirmation', widget=forms.PasswordInput)
+
+ class Meta:
+ model = User
+ fields = ('email', 'firstname', 'lastname', 'phone', 'key', 'site')
+
+ def clean_password2(self):
+ # Check that the two password entries match
+ password1 = self.cleaned_data.get("password1")
+ password2 = self.cleaned_data.get("password2")
+ if password1 and password2 and password1 != password2:
+ raise forms.ValidationError("Passwords don't match")
+ return password2
+
+ def save(self, commit=True):
+ # Save the provided password in hashed format
+ user = super(UserCreationForm, self).save(commit=False)
+ user.password = self.cleaned_data["password1"]
+ #user.set_password(self.cleaned_data["password1"])
+ if commit:
+ user.save()
+ return user
+
+
+class UserChangeForm(forms.ModelForm):
+ """A form for updating users. Includes all the fields on
+ the user, but replaces the password field with admin's
+ password hash display field.
+ """
+ password = ReadOnlyPasswordHashField()
+
+ class Meta:
+ model = User
+
+ def clean_password(self):
+ # Regardless of what the user provides, return the initial value.
+ # This is done here, rather than on the field, because the
+ # field does not have access to the initial value
+ return self.initial["password"]
+
+
+class UserAdmin(UserAdmin, OSModelAdmin):
+ class Meta:
+ app_label = "core"
+
+ # The forms to add and change user instances
+ form = UserChangeForm
+ add_form = UserCreationForm
+
+ # The fields to be used in displaying the User model.
+ # These override the definitions on the base UserAdmin
+ # that reference specific fields on auth.User.
+ list_display = ('email', 'site', 'firstname', 'lastname', 'last_login')
+ list_filter = ('site',)
+ fieldsets = (
+ (None, {'fields': ('email', 'password')}),
+ ('Personal info', {'fields': ('firstname','lastname','phone','site', 'key')}),
+ #('Important dates', {'fields': ('last_login',)}),
+ )
+ add_fieldsets = (
+ (None, {
+ 'classes': ('wide',),
+ 'fields': ('email', 'firstname', 'lastname', 'phone', 'site', 'password1', 'password2', 'key')}
+ ),
+ )
+ search_fields = ('email',)
+ ordering = ('email',)
+ filter_horizontal = ()
+
+# 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'],
+ 'password': request.POST['password']}
+ request.session['auth'] = auth
+user_logged_in.connect(cache_credentials)
+
+# Now register the new UserAdmin...
+admin.site.register(User, UserAdmin)
+# ... and, since we're not using Django's builtin permissions,
+# unregister the Group model from admin.
+admin.site.unregister(Group)
+
+admin.site.register(Site, SiteAdmin)
+#admin.site.register(SitePrivilege, SitePrivilegeAdmin)
+admin.site.register(Slice, SliceAdmin)
+#admin.site.register(SliceMembership, SliceMembershipAdmin)
+admin.site.register(Subnet, SubnetAdmin)
+#admin.site.register(Image, ImageAdmin)
+#admin.site.register(Node, NodeAdmin)
+admin.site.register(Sliver, SliverAdmin)
+admin.site.register(Key, KeyAdmin)
+#admin.site.register(Role, RoleAdmin)
+admin.site.register(DeploymentNetwork, DeploymentNetworkAdmin)
+
diff --git a/planetstack/core/api/__init__.py b/planetstack/core/api/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/planetstack/core/api/__init__.py
diff --git a/planetstack/core/api/auth.py b/planetstack/core/api/auth.py
new file mode 100644
index 0000000..8796f14
--- /dev/null
+++ b/planetstack/core/api/auth.py
@@ -0,0 +1,8 @@
+from openstack.client import OpenStackClient
+
+def auth_check(username, password, tenant):
+ client = OpenStackClient(username=username,
+ password=password,
+ tenant=tenant)
+ client.authenticate()
+ return client
diff --git a/planetstack/core/api/deployment_networks.py b/planetstack/core/api/deployment_networks.py
new file mode 100644
index 0000000..51fa03e
--- /dev/null
+++ b/planetstack/core/api/deployment_networks.py
@@ -0,0 +1,39 @@
+from types import StringTypes
+from openstack.client import OpenStackClient
+from openstack.driver import OpenStackDriver
+from core.api.auth import auth_check
+from core.models import DeploymentNetwork
+
+def _get_deployment_networks(filter):
+ if isinstance(filter, StringTypes) and filter.isdigit():
+ filter = int(filter)
+ if isinstance(filter, int):
+ deployment_networks = DeploymentNetwork.objects.filter(id=filter)
+ elif isinstance(filter, StringTypes):
+ deployment_networks = DeploymentNetwork.objects.filter(name=filter)
+ elif isinstance(filter, dict):
+ deployment_networks = DeploymentNetwork.objects.filter(**filter)
+ else:
+ deployment_networks = []
+ return deployment_networks
+
+def add_deployment_network(auth, name):
+ auth_check(auth)
+ deployment = DeploymentNetwork(name=name)
+ deployment.save()
+ return deployment
+
+def delete_deployment_network(auth, filter={}):
+ auth_check(auth)
+ deployments = _get_deployment_networks(filter)
+ for deployment in deployments:
+ deployment.delete()
+ return 1
+
+def get_deployment_networks(auth, filter={}):
+ auth_check(auth)
+ deployments = _get_deployment_networks(filter)
+ return deployments
+
+
+
diff --git a/planetstack/core/api/images.py b/planetstack/core/api/images.py
new file mode 100644
index 0000000..c080a55
--- /dev/null
+++ b/planetstack/core/api/images.py
@@ -0,0 +1,34 @@
+from types import StringTypes
+from openstack.client import OpenStackClient
+from openstack.driver import OpenStackDriver
+from core.api.auth import auth_check
+from core.models import Image
+
+def _get_images(filter):
+ if isinstance(filter, StringTypes) and filter.isdigit():
+ filter = int(filter)
+ if isinstance(filter, int):
+ images = Image.objects.filter(id=filter)
+ elif isinstance(filter, StringTypes):
+ images = Image.objects.filter(name=filter)
+ elif isinstance(filter, dict):
+ images = Image.objects.filter(**filter)
+ else:
+ images = []
+ return images
+
+def add_image(auth, fields={}):
+ """not implemented"""
+ return
+
+def delete_image(auth, filter={}):
+ """not implemented"""
+ return 1
+
+def get_images(auth, filter={}):
+ auth_check(auth)
+ images = _get_images(filter)
+ return images
+
+
+
diff --git a/planetstack/core/api/keys.py b/planetstack/core/api/keys.py
new file mode 100644
index 0000000..e653690
--- /dev/null
+++ b/planetstack/core/api/keys.py
@@ -0,0 +1,51 @@
+from types import StringTypes
+from openstack.client import OpenStackClient
+from openstack.driver import OpenStackDriver
+from core.models import Key
+from core.api.auth import auth_check
+from core.api.users import _get_users
+
+
+def _get_keys(filter):
+ if isinstance(filter, StringTypes) and filter.isdigit():
+ filter = int(filter)
+ if isinstance(filter, int):
+ keys = Key.objects.filter(id=filter)
+ elif isinstance(filter, StringTypes):
+ keys = Key.objects.filter(name=filter)
+ elif isinstance(filter, dict):
+ keys = Key.objects.filter(**filter)
+ else:
+ keys = []
+ return keys
+
+def add_key(auth, fields):
+ driver = OpenStackDriver(client = auth_check(auth))
+ users = _get_users(fields.get('user'))
+ if users: fields['user'] = users[0]
+ key = Key(**fields)
+ nova_fields = {'name': key.name,
+ 'key': key.key}
+ nova_key = driver.create_keypair(**nova_fields)
+ key.nkey_id = nova_key.id
+ key.save()
+ return key
+
+def update_key(auth, id, **fields):
+ return
+
+def delete_key(auth, filter={}):
+ driver = OpenStackDriver(client = auth_check(auth))
+ keys = _get_keys(filter)
+ for key in keys:
+ driver.delete_keypair(id=key.nkey_id)
+ key.delete()
+ return 1
+
+def get_keys(auth, filter={}):
+ client = auth_check(auth)
+ keys = _get_keys(filter)
+ return keys
+
+
+
diff --git a/planetstack/core/api/nodes.py b/planetstack/core/api/nodes.py
new file mode 100644
index 0000000..6514e54
--- /dev/null
+++ b/planetstack/core/api/nodes.py
@@ -0,0 +1,37 @@
+from types import StringTypes
+from openstack.client import OpenStackClient
+from openstack.driver import OpenStackDriver
+from core.api.auth import auth_check
+from core.models import Node
+
+def _get_nodes(filter):
+ if isinstance(filter, StringTypes) and filter.isdigit():
+ filter = int(filter)
+ if isinstance(filter, int):
+ nodes = Node.objects.filter(id=filter)
+ elif isinstance(filter, StringTypes):
+ nodes = Node.objects.filter(name=filter)
+ elif isinstance(filter, dict):
+ nodes = Node.objects.filter(**filter)
+ else:
+ nodes = []
+ return nodes
+
+def add_node(auth, fields={}):
+ """not implemented"""
+ return
+
+def delete_node(auth, filter={}):
+ """not implemented"""
+ return 1
+
+def update_node(auth, id, fields={}):
+ return
+
+def get_nodes(auth, filter={}):
+ auth_check(auth)
+ nodes = _get_nodes(filter)
+ return nodes
+
+
+
diff --git a/planetstack/core/api/roles.py b/planetstack/core/api/roles.py
new file mode 100644
index 0000000..a799c01
--- /dev/null
+++ b/planetstack/core/api/roles.py
@@ -0,0 +1,39 @@
+from types import StringTypes
+from openstack.client import OpenStackClient
+from openstack.driver import OpenStackDriver
+from core.api.auth import auth_check
+from core.models import Role
+
+
+def _get_roles(filter):
+ if isinstance(filter, StringTypes) and filter.isdigit():
+ filter = int(filter)
+ if isinstance(filter, int):
+ roles = Role.objects.filter(id=filter)
+ elif isinstance(filter, StringTypes):
+ roles = Role.objects.filter(role_type=filter)
+ elif isinstance(filter, dict):
+ roles = Role.objects.filter(**filter)
+ else:
+ roles = []
+ return roles
+
+def add_role(auth, name):
+ driver = OpenStackDriver(client = auth_check(auth))
+ keystone_role = driver.create_role(name=name)
+ role = Role(role_type=name, role_id=keystone_role.id)
+ role.save()
+ return role
+
+def delete_role(auth, filter={}):
+ driver = OpenStackDriver(client = auth_check(auth))
+ roles = _get_roles(filter)
+ for role in roles:
+ driver.delete_role({'id': role.role_id})
+ role.delete()
+ return 1
+
+def get_roles(auth, filter={}):
+ client = auth_check(auth)
+ return _get_roles(filter)
+
diff --git a/planetstack/core/api/site_privileges.py b/planetstack/core/api/site_privileges.py
new file mode 100644
index 0000000..85a8a83
--- /dev/null
+++ b/planetstack/core/api/site_privileges.py
@@ -0,0 +1,72 @@
+from types import StringTypes
+import re
+from openstack.client import OpenStackClient
+from openstack.driver import OpenStackDriver
+from core.api.auth import auth_check
+from core.models import SitePrivilege
+from core.api.users import _get_users
+from core.api.sites import _get_sites
+from core.api.roles import _get_roles
+
+
+def _get_site_privileges(filter):
+ if isinstance(filter, StringTypes) and filter.isdigit():
+ filter = int(filter)
+ if isinstance(filter, int):
+ site_privileges = SitePrivilege.objects.filter(id=filter)
+ elif isinstance(filter, StringTypes):
+ site_privileges = SitePrivilege.objects.filter(name=filter)
+ elif isinstance(filter, dict):
+ site_privileges = SitePrivilege.objects.filter(**filter)
+ else:
+ site_privileges = []
+ return site_privileges
+
+def add_site_privilege(auth, fields):
+ driver = OpenStackDriver(client = auth_check(auth))
+ users = _get_user(fields.get('user'))
+ sites = _get_slice(fields.get('site'))
+ roles = _get_role(fields.get('role'))
+
+ if users: fields['user'] = users[0]
+ if slices: fields['site'] = sites[0]
+ if roles: fields['role'] = roles[0]
+
+ site_privilege = SitePrivilege(**fields)
+
+ # update nova role
+ driver.add_user_role(site_privilege.user.kuser_id,
+ site_privilege.site.tenant_id,
+ site_privilege.role.name)
+
+ site_privilege.save()
+ return site_privilege
+
+def update_site_privilege(auth, id, **fields):
+ return
+
+def delete_site_privilege(auth, filter={}):
+ driver = OpenStackDriver(client = auth_check(auth))
+ site_privileges = _get_site_privileges(filter)
+ for site_privilege in site_privileges:
+ driver.delete_user_role(kuser_id=site_privilege.user.id,
+ tenant_id = site_privilege.site.tenant_id,
+ role_name = site_privilege.role.name)
+ site_privilege.delete()
+ return 1
+
+def get_site_privileges(auth, filter={}):
+ client = auth_check(auth)
+ users = _get_users(filter.get('user'))
+ sites = _get_slices(filter.get('site'))
+ roles = _get_roles(filter.get('role'))
+
+ if users: filter['user'] = users[0]
+ if sites: filter['site'] = sites[0]
+ if roles: filter['role'] = roles[0]
+
+ site_privileges = _get_site_privileges(filter)
+ return site_privileges
+
+
+
diff --git a/planetstack/core/api/sites.py b/planetstack/core/api/sites.py
new file mode 100644
index 0000000..cfca6cf
--- /dev/null
+++ b/planetstack/core/api/sites.py
@@ -0,0 +1,62 @@
+from types import StringTypes
+from openstack.client import OpenStackClient
+from openstack.driver import OpenStackDriver
+from core.api.auth import auth_check
+from core.models import Site
+
+
+def _get_sites(filter):
+ if isinstance(filter, StringTypes) and filter.isdigit():
+ filter = int(filter)
+ if isinstance(filter, int):
+ sites = Site.objects.filter(id=filter)
+ elif isinstance(filter, StringTypes):
+ sites = Site.objects.filter(login_base=filter)
+ elif isinstance(filter, dict):
+ sites = Site.objects.filter(**filter)
+ else:
+ sites = []
+ return sites
+
+def add_site(auth, fields):
+ driver = OpenStackDriver(client = auth_check(auth))
+ site = Site(**fields)
+ nova_fields = {'tenant_name': site.login_base,
+ 'description': site.name,
+ 'enabled': site.enabled}
+ tenant = driver.create_tenant(**nova_fields)
+ site.tenant_id=tenant.id
+ site.save()
+ return site
+
+def update_site(auth, id, **fields):
+ driver = OpenStackDriver(client = auth_check(auth))
+ sites = _get_sites(id)
+ if not sites:
+ return
+
+ site = Site[0]
+ nova_fields = {}
+ if 'description' in fields:
+ nova_fields['description'] = fields['name']
+ if 'enabled' in fields:
+ nova_fields['enabled'] = fields['enabled']
+ driver.update_tenant(site.tenant_id, **nova_fields)
+ site.update(**fields)
+ return site
+
+def delete_site(auth, filter={}):
+ driver = OpenStackDriver(client = auth_check(auth))
+ sites = _get_sites(id)
+ for site in sites:
+ driver.delete_tenant(id=site.tenant_id)
+ site.delete()
+ return 1
+
+def get_sites(auth, filter={}):
+ client = auth_check(auth)
+ sites = _get_sites(filter)
+ return sites
+
+
+
diff --git a/planetstack/core/api/slice_memberships.py b/planetstack/core/api/slice_memberships.py
new file mode 100644
index 0000000..77d79bf
--- /dev/null
+++ b/planetstack/core/api/slice_memberships.py
@@ -0,0 +1,71 @@
+from types import StringTypes
+from openstack.client import OpenStackClient
+from openstack.driver import OpenStackDriver
+from core.api.auth import auth_check
+from core.models import SliceMembership
+from core.api.users import _get_users
+from core.api.slices import _get_slices
+from core.api.roles import _get_roles
+
+def _get_slice_memberships(filter):
+ if isinstance(filter, StringTypes) and filter.isdigit():
+ filter = int(filter)
+ if isinstance(filter, int):
+ slice_memberships = SitePrivilege.objects.filter(id=filter)
+ elif isinstance(filter, StringTypes):
+ slice_memberships = SitePrivilege.objects.filter(name=filter)
+ elif isinstance(filter, dict):
+ slice_memberships = SitePrivilege.objects.filter(**filter)
+ else:
+ slice_memberships = []
+ return slice_memberships
+
+
+def add_slice_membership(auth, fields):
+ driver = OpenStackDriver(client = auth_check(auth))
+ users = _get_users(fields.get('user'))
+ slices = _get_slices(fields.get('slice'))
+ roles = _get_roles(fields.get('role'))
+
+ if users: fields['user'] = users[0]
+ if slices: fields['slice'] = slices[0]
+ if roles: fields['role'] = roles[0]
+
+ slice_membership = SliceMembership(**fields)
+
+ # update nova role
+ driver.add_user_role(slice_membership.user.user_id,
+ slice_membership.slice.tenant_id,
+ slice_membership.role.name)
+
+ slice_membership.save()
+ return slice_membership
+
+def update_slice_membership(auth, id, **fields):
+ return
+
+def delete_slice_membership(auth, filter={}):
+ driver = OpenStackDriver(client = auth_check(auth))
+ slice_memberships = _get_slice_memberships(filter)
+ for slice_membership in slice_memberships:
+ driver.delete_user_role(kuser_id=slice_membership.user.id,
+ tenant_id = slice_membership.slice.tenant_id,
+ role_name = slice_membership.role.name)
+ slice_membership.delete()
+ return 1
+
+def get_slice_memberships(auth, filter={}):
+ client = auth_check(auth)
+ users = _get_users(fields.get('user'))
+ slices = _get_slices(fields.get('slice'))
+ roles = _get_roles(fields.get('role'))
+
+ if users: fields['user'] = users[0]
+ if slices: fields['slice'] = slices[0]
+ if roles: fields['role'] = roles[0]
+
+ slice_memberships = _get_slice_memberships(filter)
+ return slice_memberships
+
+
+
diff --git a/planetstack/core/api/slices.py b/planetstack/core/api/slices.py
new file mode 100644
index 0000000..f03dd6f
--- /dev/null
+++ b/planetstack/core/api/slices.py
@@ -0,0 +1,93 @@
+import re
+from types import StringTypes
+from django.contrib.auth import authenticate
+from openstack.client import OpenStackClient
+from openstack.driver import OpenStackDriver
+from core.api.auth import auth_check
+from core.models import Slice
+from core.api.sites import _get_sites
+
+def _get_slices(filter):
+ if isinstance(filter, StringTypes) and filter.isdigit():
+ filter = int(filter)
+ if isinstance(filter, int):
+ slices = Slice.objects.filter(id=filter)
+ elif isinstance(filter, StringTypes):
+ slices = Slice.objects.filter(name=filter)
+ elif isinstance(filter, dict):
+ slices = Slice.objects.filter(**filter)
+ else:
+ slices = []
+ return slices
+
+
+def add_slice(auth, fields):
+ driver = OpenStackDriver(client = auth_check(auth))
+ login_base = fields['name'][:fields['name'].find('_')]
+ sites = _get_sites(login_base)
+ if sites: fields['site'] = sites[0]
+ slice = Slice(**fields)
+
+ # create tenant
+ nova_fields = {'tenant_name': slice.name,
+ 'description': slice.description,
+ 'enabled': slice.enabled}
+ tenant = driver.create_tenant(**nova_fields)
+ slice.tenant_id=tenant.id
+
+ # create network
+ network = driver.create_network(slice.name)
+ slice.network_id = network['id']
+
+ # create router
+ router = driver.create_router(slice.name)
+ slice.router_id = router['id']
+
+ slice.save()
+ return slice
+
+def update_slice(auth, id, **fields):
+ driver = OpenStackDriver(client = auth_check(auth))
+ slices = _get_slices(id)
+ if not slices:
+ return
+
+ # update tenant
+ slice = slices[0]
+ nova_fields = {}
+ if 'name' in fields:
+ nova_fields['tenant_name'] = fields['name']
+ if 'description' in fields:
+ nova_fields['description'] = fields['description']
+ if 'enabled' in fields:
+ nova_fields['enabled'] = fields['enabled']
+ driver.update_tenant(slice.tenant_id, **nova_fields)
+
+ # update db record
+ sites = _get_sites(fields.get('site'))
+ if sites: fields['site'] = sites[0]
+ slice.update(**fields)
+
+ return slice
+
+def delete_slice(auth, filter={}):
+ driver = OpenStackDriver(client = auth_check(auth))
+ slices = _get_slices(id)
+ for slice in slices:
+ driver.delete_network(slice.network_id)
+ driver.delete_router(slice.router_id)
+ driver.delete_slice(id=slice.tenant_id)
+ slice.delete()
+ return 1
+
+def get_slices(auth, filter={}):
+ user = authenticate(username=auth.get('username'),
+ password=auth.get('password'))
+ if 'site' in filter:
+ sites = _get_sites(filter.get('site'))
+ if sites: filter['site'] = sites[0]
+ slices = _get_slices(filter)
+ return slices
+
+
+
diff --git a/planetstack/core/api/slivers.py b/planetstack/core/api/slivers.py
new file mode 100644
index 0000000..e3244a4
--- /dev/null
+++ b/planetstack/core/api/slivers.py
@@ -0,0 +1,67 @@
+from types import StringTypes
+from django.contrib.auth import authenticate
+from openstack.client import OpenStackClient
+from openstack.driver import OpenStackDriver
+from core.api.auth import auth_check
+from core.models import Sliver, Slice
+from core.api.images import _get_images
+from core.api.keys import _get_keys
+from core.api.slices import _get_slices
+from core.api.deployment_networks import _get_deployment_networks
+from core.api.nodes import _get_nodes
+
+
+def _get_slivers(filter):
+ if isinstance(filter, StringTypes) and filter.isdigit():
+ filter = int(filter)
+ if isinstance(filter, int):
+ slivers = Sliver.objects.filter(id=filter)
+ elif isinstance(filter, StringTypes):
+ slivers = Sliver.objects.filter(name=filter)
+ elif isinstance(filter, dict):
+ slivers = Sliver.objects.filter(**filter)
+ else:
+ slivers = []
+ return slivers
+
+def add_sliver(auth, fields):
+ driver = OpenStackDriver(client = auth_check(auth))
+
+ images = _get_images(fields.get('image'))
+ if images: fields['image'] = images[0]
+ keys = _get_keys(fields.get('key'))
+ if keys: fields['key'] = keys[0]
+ slices = _get_slices(fields.get('slice'))
+ if slices:
+ fields['slice'] = slices[0]
+ deployment_networks = _get_deployment_networks(fields.get('deploymentNetwork'))
+ if deployment_networks: fields['deploymentNetwork'] = deployment_networks[0]
+ nodes = _get_nodes(fields.get('node'))
+ if nodes: fields['node'] = nodes[0]
+ sliver = Sliver(**fields)
+ sliver.driver = driver
+ sliver.save()
+ return sliver
+
+def update_sliver(auth, sliver, **fields):
+ return
+
+def delete_sliver(auth, filter={}):
+ driver = OpenStackDriver(client = auth_check(auth))
+ slivers = _get_slivers(filter)
+ for sliver in slivers:
+ sliver.driver = driver
+ sliver.delete()
+ return 1
+
+def get_slivers(auth, filter={}):
+ user = authenticate(username=auth.get('username'),
+ password=auth.get('password'))
+ if 'slice' in filter:
+ slices = _get_slices(filter.get('slice'))
+ if slices: filter['slice'] = slices[0]
+ slivers = _get_slivers(filter)
+ return slivers
+
+
+
diff --git a/planetstack/core/api/subnets.py b/planetstack/core/api/subnets.py
new file mode 100644
index 0000000..d618eda
--- /dev/null
+++ b/planetstack/core/api/subnets.py
@@ -0,0 +1,74 @@
+import commands
+from types import StringTypes
+from openstack.client import OpenStackClient
+from openstack.driver import OpenStackDriver
+from core.api.auth import auth_check
+from core.models import Subnet
+from core.api.slices import _get_slices
+
+
+def _get_subnets(filter):
+ if isinstance(filter, StringTypes) and filter.isdigit():
+ filter = int(filter)
+ if isinstance(filter, int):
+ subnets = Subnet.objects.filter(id=filter)
+ elif isinstance(filter, StringTypes):
+ # the name is the subnet's slice's name
+ slices = _get_slices(filter)
+ slice = None
+ if slices: slice=slices[0]
+ subnets = Subnet.objects.filter(slice=slice)
+ elif isinstance(filter, dict):
+ subnets = Subnet.objects.filter(**filter)
+ else:
+ subnets = []
+ return subnets
+
+def add_subnet(auth, fields):
+ driver = OpenStackDriver(client = auth_check(auth))
+ slices = _get_slices(fields.get('slice'))
+ if slices: fields['slice'] = slices[0]
+ subnet = Subnet(**fields)
+ # create quantum subnet
+ quantum_subnet = driver.create_subnet(name= subnet.slice.name,
+ network_id=subnet.slice.network_id,
+ cidr_ip = subnet.cidr,
+ ip_version=subnet.ip_version,
+ start = subnet.start,
+ end = subnet.end)
+ subnet.subnet_id=quantum_subnet['id']
+ ## set dns servers
+ #driver.update_subnet(subnet.id, {'dns_nameservers': ['8.8.8.8', '8.8.4.4']})
+
+ # add subnet as interface to slice's router
+ try: driver.add_router_interface(subnet.slice.router_id, subnet.subnet_id)
+ except: pass
+ #add_route = 'route add -net %s dev br-ex gw 10.100.0.5' % self.cidr
+ commands.getstatusoutput(add_route)
+ subnet.save()
+ return subnet
+
+def update_subnet(auth, subnet, **fields):
+ return
+
+def delete_subnet(auth, filter={}):
+ driver = OpenStackDriver(client = auth_check(auth))
+ subnets = Subnet.objects.filter(**filter)
+ for subnet in subnets:
+ driver.delete_router_interface(subnet.slice.router_id, subnet.subnet_id)
+ driver.delete_subnet(subnet.subnet_id)
+ subnet.delete()
+ #del_route = 'route del -net %s' % subnet.cidr
+ commands.getstatusoutput(del_route)
+ return 1
+
+def get_subnets(auth, filter={}):
+ client = auth_check(auth)
+ if 'slice' in filter:
+ slice = _get_slice(filter.get('slice'))
+ if slice: filter['slice'] = slice
+ subnets = Subnet.objects.filter(**filter)
+ return subnets
+
+
+
diff --git a/planetstack/core/api/users.py b/planetstack/core/api/users.py
new file mode 100644
index 0000000..3b157ac
--- /dev/null
+++ b/planetstack/core/api/users.py
@@ -0,0 +1,71 @@
+from types import StringTypes
+from openstack.client import OpenStackClient
+from openstack.driver import OpenStackDriver
+from core.api.auth import auth_check
+from core.models import User, Site
+from core.api.sites import _get_sites
+
+def _get_users(filter):
+ if isinstance(filter, StringTypes) and filter.isdigit():
+ filter = int(filter)
+ if isinstance(filter, int):
+ users = User.objects.filter(id=filter)
+ elif isinstance(filter, StringTypes):
+ users = User.objects.filter(email=filter)
+ elif isinstance(filter, dict):
+ users = User.objects.filter(**filter)
+ else:
+ users = []
+ return users
+
+def add_user(auth, fields):
+ driver = OpenStackDriver(client = auth_check(auth))
+ sites = _get_sites(fields.get('site'))
+ if sites: fields['site'] = sites[0]
+ user = User(**fields)
+ nova_fields = {'name': user.email[:user.email.find('@')],
+ 'email': user.email,
+ 'password': fields.get('password'),
+ 'enabled': user.enabled}
+ nova_user = driver.create_user(**nova_fields)
+ #driver.add_user_user(user.id, user.site.tenant_id, 'user')
+ user.kuser_id=nova_user.id
+ user.save()
+ return user
+
+def update_user(auth, id, **fields):
+ driver = OpenStackDriver(client = auth_check(auth))
+ users = User.objects.filter(id=id)
+ if not users:
+ return
+
+ user = users[0]
+ nova_fields = {}
+ if 'email' in fields:
+ nova_fields['name'] = fields['email'][:self.email.find('@')]
+ nova_fields['email'] = fields['email']
+ if 'password' in fields:
+ nova_fields['password'] = fields['password']
+ if 'enabled' in fields:
+ nova_fields['enabled'] = fields['enabled']
+ driver.update_user(user.kuser_id, **nova_fields)
+ sites = _get_sites(fields.get('site'))
+ if sites: fields['site'] = sites[0]
+ user.update(**fields)
+ return user
+
+def delete_user(auth, filter={}):
+ driver = OpenStackDriver(client = auth_check(auth))
+ users = _get_users(filter)
+ for user in users:
+ driver.delete_user(id=user.kuser_id)
+ user.delete()
+ return 1
+
+def get_users(auth, filter={}):
+ client = auth_check(auth)
+ users = _get_users(filter)
+ return users
+
+
+
diff --git a/planetstack/core/api_root.py b/planetstack/core/api_root.py
new file mode 100644
index 0000000..61e76da
--- /dev/null
+++ b/planetstack/core/api_root.py
@@ -0,0 +1,18 @@
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+from rest_framework.reverse import reverse
+
+@api_view(['GET'])
+def api_root(request, format=None):
+ return Response({
+ '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),
+ '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),
+ })
diff --git a/planetstack/core/fixtures/__init__.py b/planetstack/core/fixtures/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/planetstack/core/fixtures/__init__.py
diff --git a/planetstack/core/fixtures/initial_data.json b/planetstack/core/fixtures/initial_data.json
new file mode 100644
index 0000000..638b00d
--- /dev/null
+++ b/planetstack/core/fixtures/initial_data.json
@@ -0,0 +1,167 @@
+[
+{
+ "pk": 1,
+ "model": "core.deploymentnetwork",
+ "fields": {
+ "updated": "2013-04-03T22:57:09.331Z",
+ "name": "VICCI",
+ "created": "2013-04-03T22:57:09.331Z"
+ }
+},
+{
+ "pk": 2,
+ "model": "core.deploymentnetwork",
+ "fields": {
+ "updated": "2013-04-03T22:57:15.013Z",
+ "name": "VINI",
+ "created": "2013-04-03T22:57:15.013Z"
+ }
+},
+{
+ "pk": 3,
+ "model": "core.deploymentnetwork",
+ "fields": {
+ "updated": "2013-04-03T22:57:23.015Z",
+ "name": "PlanetLab Classic",
+ "created": "2013-04-03T22:57:23.015Z"
+ }
+},
+{
+ "pk": 4,
+ "model": "core.deploymentnetwork",
+ "fields": {
+ "updated": "2013-04-03T22:57:29.569Z",
+ "name": "GENI",
+ "created": "2013-04-03T22:57:29.569Z"
+ }
+},
+{
+ "pk": 1,
+ "model": "core.site",
+ "fields": {
+ "updated": "2013-04-05T15:21:04.135Z",
+ "name": "Princeton University",
+ "created": "2013-04-03T23:00:10.085Z",
+ "tenant_id": "",
+ "enabled": true,
+ "longitude": -74.6524,
+ "site_url": "http://princeton.edu/",
+ "login_base": "princeton",
+ "latitude": 40.3502,
+ "is_public": true,
+ "deployments": [
+ 3,
+ 4
+ ],
+ "abbreviated_name": ""
+ }
+},
+{
+ "pk": 2,
+ "model": "core.site",
+ "fields": {
+ "updated": "2013-04-05T15:42:36.517Z",
+ "name": "Stanford University",
+ "created": "2013-04-03T23:03:51.742Z",
+ "tenant_id": "",
+ "enabled": true,
+ "longitude": -122.172,
+ "site_url": "http://www.stanford.edu/",
+ "login_base": "stanford",
+ "latitude": 37.4294,
+ "is_public": true,
+ "deployments": [],
+ "abbreviated_name": ""
+ }
+},
+{
+ "pk": 3,
+ "model": "core.site",
+ "fields": {
+ "updated": "2013-04-05T15:42:17.263Z",
+ "name": "Georgia Institute of Technology",
+ "created": "2013-04-03T23:05:51.984Z",
+ "tenant_id": "",
+ "enabled": true,
+ "longitude": -84.3976,
+ "site_url": "http://www.gatech.edu/",
+ "login_base": "gt",
+ "latitude": 33.7772,
+ "is_public": true,
+ "deployments": [],
+ "abbreviated_name": ""
+ }
+},
+{
+ "pk": 4,
+ "model": "core.site",
+ "fields": {
+ "updated": "2013-04-05T15:39:27.501Z",
+ "name": "University of Washington",
+ "created": "2013-04-03T23:09:52.337Z",
+ "tenant_id": "",
+ "enabled": true,
+ "longitude": -122.313,
+ "site_url": "https://www.washington.edu/",
+ "login_base": "uw",
+ "latitude": 47.6531,
+ "is_public": true,
+ "deployments": [],
+ "abbreviated_name": ""
+ }
+},
+{
+ "pk": 5,
+ "model": "core.site",
+ "fields": {
+ "updated": "2013-04-05T15:38:56.889Z",
+ "name": "ETH Zuerich - Computer Science",
+ "created": "2013-04-03T23:14:11.072Z",
+ "tenant_id": "",
+ "enabled": true,
+ "longitude": 8.54513,
+ "site_url": "http://www.inf.ethz.ch/",
+ "login_base": "ethzcs",
+ "latitude": 47.3794,
+ "is_public": true,
+ "deployments": [],
+ "abbreviated_name": ""
+ }
+},
+{
+ "pk": 6,
+ "model": "core.site",
+ "fields": {
+ "updated": "2013-04-05T15:38:15.960Z",
+ "name": "Max Planck Institute for Software Systems",
+ "created": "2013-04-03T23:19:38.789Z",
+ "tenant_id": "",
+ "enabled": true,
+ "longitude": 6.589,
+ "site_url": "http://www.mpi-sws.mpg.de/",
+ "login_base": "mpisws",
+ "latitude": 49.14,
+ "is_public": true,
+ "deployments": [],
+ "abbreviated_name": ""
+ }
+},
+{
+ "pk": 7,
+ "model": "core.site",
+ "fields": {
+ "updated": "2013-04-05T15:37:32.185Z",
+ "name": "University of Tokyo",
+ "created": "2013-04-03T23:20:49.815Z",
+ "tenant_id": "",
+ "enabled": true,
+ "longitude": 139.5,
+ "site_url": "http://www.planet-lab-jp.org/",
+ "login_base": "utokyo",
+ "latitude": 35.75,
+ "is_public": true,
+ "deployments": [],
+ "abbreviated_name": ""
+ }
+}
+]
diff --git a/planetstack/core/models/__init__.py b/planetstack/core/models/__init__.py
new file mode 100644
index 0000000..4d9387b
--- /dev/null
+++ b/planetstack/core/models/__init__.py
@@ -0,0 +1,14 @@
+from .plcorebase import PlCoreBase
+from .deploymentnetwork import DeploymentNetwork
+from .site import Site
+from .site import SitePrivilege
+from .image import Image
+from .key import Key
+from .user import User
+from .role import Role
+from .node import Node
+from .slice import Slice
+from .slice import SliceMembership
+from .sliver import Sliver
+from .subnet import Subnet
+
diff --git a/planetstack/core/models/deploymentnetwork.py b/planetstack/core/models/deploymentnetwork.py
new file mode 100644
index 0000000..4068ee9
--- /dev/null
+++ b/planetstack/core/models/deploymentnetwork.py
@@ -0,0 +1,11 @@
+import os
+from django.db import models
+from core.models import PlCoreBase
+
+# Create your models here.
+
+class DeploymentNetwork(PlCoreBase):
+ name = models.CharField(max_length=200, unique=True, help_text="Name of the Deployment Network")
+
+ def __unicode__(self): return u'%s' % (self.name)
+
diff --git a/planetstack/core/models/image.py b/planetstack/core/models/image.py
new file mode 100644
index 0000000..b4803e2
--- /dev/null
+++ b/planetstack/core/models/image.py
@@ -0,0 +1,13 @@
+import os
+from django.db import models
+from core.models import PlCoreBase
+
+# Create your models here.
+
+class Image(PlCoreBase):
+ image_id = models.CharField(max_length=256, unique=True)
+ name = models.CharField(max_length=256, unique=True)
+ disk_format = models.CharField(max_length=256)
+ container_format = models.CharField(max_length=256)
+
+ def __unicode__(self): return u'%s' % (self.name)
diff --git a/planetstack/core/models/key.py b/planetstack/core/models/key.py
new file mode 100644
index 0000000..98cfb9b
--- /dev/null
+++ b/planetstack/core/models/key.py
@@ -0,0 +1,23 @@
+import os
+from django.db import models
+from core.models import PlCoreBase
+
+# Create your models here.
+
+class Key(PlCoreBase):
+ name = models.CharField(max_length=256, unique=True)
+ nkey_id = models.CharField(max_length=256, unique=True)
+ key = models.CharField(max_length=512)
+ type = models.CharField(max_length=256)
+ blacklisted = models.BooleanField(default=False)
+
+ def __unicode__(self): return u'%s' % (self.name)
+
+ def save(self, *args, **kwds):
+ self.os_manager.save_key(self)
+ super(Key, self).save(*args, **kwds)
+
+ def delete(self, *args, **kwds):
+ self.os_manager.delete_key(self)
+ super(Key, self).delete(*args, **kwds)
+
diff --git a/planetstack/core/models/node.py b/planetstack/core/models/node.py
new file mode 100644
index 0000000..a249628
--- /dev/null
+++ b/planetstack/core/models/node.py
@@ -0,0 +1,14 @@
+import os
+from django.db import models
+from core.models import PlCoreBase
+from core.models import Site
+from core.models import DeploymentNetwork
+
+# Create your models here.
+
+class Node(PlCoreBase):
+ name = models.CharField(max_length=200, unique=True, help_text="Name of the Node")
+ site = models.ForeignKey(Site, related_name='nodes')
+ deploymentNetwork = models.ForeignKey(DeploymentNetwork, related_name='nodes')
+
+ def __unicode__(self): return u'%s' % (self.name)
diff --git a/planetstack/core/models/plcorebase.py b/planetstack/core/models/plcorebase.py
new file mode 100644
index 0000000..52aa0f7
--- /dev/null
+++ b/planetstack/core/models/plcorebase.py
@@ -0,0 +1,14 @@
+import os
+from django.db import models
+
+class PlCoreBase(models.Model):
+
+ created = models.DateTimeField(auto_now_add=True)
+ updated = models.DateTimeField(auto_now=True)
+
+ class Meta:
+ abstract = True
+ app_label = "core"
+
+
+
diff --git a/planetstack/core/models/role.py b/planetstack/core/models/role.py
new file mode 100644
index 0000000..b3611c1
--- /dev/null
+++ b/planetstack/core/models/role.py
@@ -0,0 +1,22 @@
+import os
+import datetime
+from django.db import models
+from core.models import PlCoreBase
+
+class Role(PlCoreBase):
+
+ #ROLE_CHOICES = (('admin', 'Admin'), ('pi', 'Principle Investigator'), ('user','User'))
+ role_id = models.CharField(max_length=256, unique=True)
+ role_type = models.CharField(max_length=80, unique=True)
+
+ def __unicode__(self): return u'%s' % (self.role_type)
+
+
+ def save(self, *args, **kwds):
+ self.os_manager.save_role(self)
+ super(Role, self).save(*args, **kwds)
+
+ def delete(self, *args, **kwds):
+ self.os_manager.delete_role(self)
+ super(Role, self).delete(*args, **kwds)
+
diff --git a/planetstack/core/models/site.py b/planetstack/core/models/site.py
new file mode 100644
index 0000000..ebf2ab9
--- /dev/null
+++ b/planetstack/core/models/site.py
@@ -0,0 +1,49 @@
+import os
+from django.db import models
+from core.models import PlCoreBase
+from core.models import DeploymentNetwork
+
+
+class Site(PlCoreBase):
+
+ tenant_id = models.CharField(max_length=200, help_text="Keystone tenant id")
+ name = models.CharField(max_length=200, help_text="Name for this Site")
+ site_url = models.URLField(null=True, blank=True, max_length=512, help_text="Site's Home URL Page")
+ enabled = models.BooleanField(default=True, help_text="Status for this Site")
+ longitude = models.FloatField(null=True, blank=True)
+ latitude = models.FloatField(null=True, blank=True)
+ login_base = models.CharField(max_length=50, unique=True, help_text="Prefix for Slices associated with this Site")
+ is_public = models.BooleanField(default=True, help_text="Indicates the visibility of this site to other members")
+ abbreviated_name = models.CharField(max_length=80)
+
+ deployments = models.ManyToManyField(DeploymentNetwork, blank=True, related_name='sites')
+
+ def __unicode__(self): return u'%s' % (self.name)
+
+ def save(self, *args, **kwds):
+ self.os_manager.save_site(self)
+ super(Site, self).save(*args, **kwds)
+
+
+ def delete(self, *args, **kwds):
+ self.os_manager.delete_site(self)
+ super(Site, self).delete(*args, **kwds)
+
+
+class SitePrivilege(PlCoreBase):
+
+ user = models.ForeignKey('User', related_name='site_privileges')
+ site = models.ForeignKey('Site', related_name='site_privileges')
+ role = models.ForeignKey('Role')
+
+ def __unicode__(self): return u'%s %s %s' % (self.site, self.user, self.role)
+
+ def save(self, *args, **kwds):
+ self.os_manager.driver.add_user_role(self.user.kuser_id, self.site.tenant_id, self.role.role_type)
+ super(SitePrivilege, self).save(*args, **kwds)
+
+ def delete(self, *args, **kwds):
+ self.os_manager.driver.delete_user_role(self.user.kuser_id, self.site.tenant_id, self.role.role_type)
+ super(SitePrivilege, self).delete(*args, **kwds)
+
+
diff --git a/planetstack/core/models/slice.py b/planetstack/core/models/slice.py
new file mode 100644
index 0000000..539be24
--- /dev/null
+++ b/planetstack/core/models/slice.py
@@ -0,0 +1,51 @@
+import os
+from django.db import models
+from core.models import PlCoreBase
+from core.models import Site
+from core.models import User
+from core.models import Role
+from core.models import DeploymentNetwork
+
+# Create your models here.
+
+class Slice(PlCoreBase):
+ tenant_id = models.CharField(max_length=200, help_text="Keystone tenant id")
+ name = models.CharField(unique=True, help_text="The Name of the Slice", max_length=80)
+ enabled = models.BooleanField(default=True, help_text="Status for this Slice")
+ SLICE_CHOICES = (('plc', 'PLC'), ('delegated', 'Delegated'), ('controller','Controller'), ('none','None'))
+ instantiation = models.CharField(help_text="The instantiation type of the slice", max_length=80, choices=SLICE_CHOICES)
+ omf_friendly = models.BooleanField()
+ description=models.TextField(blank=True,help_text="High level description of the slice and expected activities", max_length=1024)
+ slice_url = models.URLField(blank=True, max_length=512)
+ site = models.ForeignKey(Site, related_name='slices', help_text="The Site this Node belongs too")
+ network_id = models.CharField(max_length=256, help_text="Quantum network")
+ router_id = models.CharField(max_length=256, help_text="Quantum router id")
+
+ SVC_CLASS_CHOICES = (('besteffort', 'Best Effort'), ('silver', 'Silver'), ('gold','Gold'))
+ serviceClass = models.CharField(verbose_name="Service Class",default="besteffort",help_text="The Service Class of this slice", max_length=30, choices=SVC_CLASS_CHOICES)
+
+
+ def __unicode__(self): return u'%s' % (self.name)
+
+ def save(self, *args, **kwds):
+ self.os_manager.save_slice(self)
+ super(Slice, self).save(*args, **kwds)
+
+ def delete(self, *args, **kwds):
+ self.os_manager.delete_slice(self)
+ super(Slice, self).delete(*args, **kwds)
+
+class SliceMembership(PlCoreBase):
+ user = models.ForeignKey('User', related_name='slice_memberships')
+ slice = models.ForeignKey('Slice', related_name='slice_memberships')
+ role = models.ForeignKey('Role')
+
+ def __unicode__(self): return u'%s %s %s' % (self.slice, self.user, self.role)
+
+ def save(self, *args, **kwds):
+ self.os_manager.driver.add_user_role(self.user.kuser_id, self.slice.tenant_id, self.role.role_type)
+ super(SliceMembership, self).save(*args, **kwds)
+
+ def delete(self, *args, **kwds):
+ self.os_manager.driver.delete_user_role(self.user.kuser_id, self.slice.tenant_id, self.role.role_type)
+ super(SliceMembership, self).delete(*args, **kwds)
diff --git a/planetstack/core/models/sliver.py b/planetstack/core/models/sliver.py
new file mode 100644
index 0000000..580c2af
--- /dev/null
+++ b/planetstack/core/models/sliver.py
@@ -0,0 +1,37 @@
+import os
+from django.db import models
+from django.core import exceptions
+from core.models import PlCoreBase
+from core.models import Image
+from core.models import Key
+from core.models import Slice
+from core.models import Node
+from core.models import Site
+from core.models import DeploymentNetwork
+
+# Create your models here.
+class Sliver(PlCoreBase):
+ instance_id = models.CharField(max_length=200, help_text="Nova instance id")
+ name = models.CharField(max_length=200, help_text="Sliver name")
+ instance_name = models.CharField(blank=True, null=True, max_length=200, help_text="OpenStack generated name")
+ ip = models.GenericIPAddressField(help_text="Sliver ip address", blank=True, null=True)
+ image = models.ForeignKey(Image, related_name='slivers')
+ key = models.ForeignKey(Key, related_name='slivers')
+ slice = models.ForeignKey(Slice, related_name='slivers')
+ node = models.ForeignKey(Node, related_name='slivers')
+ deploymentNetwork = models.ForeignKey(DeploymentNetwork, verbose_name='deployment', related_name='sliver_deploymentNetwork')
+ numberCores = models.IntegerField(verbose_name="Number of Cores", help_text="Number of cores for sliver", default=2)
+
+
+ def __unicode__(self): return u'%s' % (self.instance_name)
+
+ def save(self, *args, **kwds):
+ if not self.slice.subnet.exists():
+ raise exceptions.ValidationError, "Slice %s has no subnet" % self.slice.name
+
+ self.os_manager.save_sliver(self)
+ super(Sliver, self).save(*args, **kwds)
+
+ def delete(self, *args, **kwds):
+ self.os_manager.delete_sliver(self)
+ super(Sliver, self).delete(*args, **kwds)
diff --git a/planetstack/core/models/subnet.py b/planetstack/core/models/subnet.py
new file mode 100644
index 0000000..cad9fea
--- /dev/null
+++ b/planetstack/core/models/subnet.py
@@ -0,0 +1,25 @@
+import os
+import commands
+from django.db import models
+from core.models import PlCoreBase
+from core.models import Slice
+
+# Create your models here.
+
+class Subnet(PlCoreBase):
+ subnet_id = models.CharField(max_length=256, unique=True)
+ cidr = models.CharField(max_length=20)
+ ip_version = models.IntegerField()
+ start = models.IPAddressField()
+ end = models.IPAddressField()
+ slice = models.ForeignKey(Slice, related_name='subnet')
+
+ def __unicode__(self): return u'%s' % (self.slice.name)
+
+ def save(self, *args, **kwds):
+ self.os_manager.save_subnet(self)
+ super(Subnet, self).save(*args, **kwds)
+
+ def delete(self, *args, **kwds):
+ self.os_manager.delete_subnet(self)
+ super(Subnet, self).delete(*args, **kwds)
diff --git a/planetstack/core/models/user.py b/planetstack/core/models/user.py
new file mode 100644
index 0000000..6c776b1
--- /dev/null
+++ b/planetstack/core/models/user.py
@@ -0,0 +1,122 @@
+import os
+import datetime
+from django.db import models
+from core.models import PlCoreBase
+from core.models import Site
+from core.models import Key
+from openstack.manager import OpenStackManager
+from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
+
+# Create your models here.
+has_openstack = False
+
+class UserManager(BaseUserManager):
+ def create_user(self, email, firstname, lastname, password=None):
+ """
+ Creates and saves a User with the given email, date of
+ birth and password.
+ """
+ if not email:
+ raise ValueError('Users must have an email address')
+
+ user = self.model(
+ email=UserManager.normalize_email(email),
+ firstname=firstname,
+ lastname=lastname,
+ password=password
+ )
+ #user.set_password(password)
+ user.is_admin = True
+ user.save(using=self._db)
+ return user
+
+ def create_superuser(self, email, firstname, lastname, password):
+ """
+ Creates and saves a superuser with the given email, date of
+ birth and password.
+ """
+ user = self.create_user(email,
+ password=password,
+ firstname=firstname,
+ lastname=lastname
+ )
+ user.is_admin = True
+ user.save(using=self._db)
+ return user
+
+
+class User(AbstractBaseUser):
+
+ class Meta:
+ app_label = "core"
+
+ email = models.EmailField(
+ verbose_name='email address',
+ max_length=255,
+ unique=True,
+ db_index=True,
+ )
+
+ kuser_id = models.CharField(help_text="keystone user id", max_length=200)
+ firstname = models.CharField(help_text="person's given name", max_length=200)
+ lastname = models.CharField(help_text="person's surname", max_length=200)
+
+ phone = models.CharField(null=True, blank=True, help_text="phone number contact", max_length=100)
+ user_url = models.URLField(null=True, blank=True)
+ site = models.ForeignKey(Site, related_name='users', verbose_name="Site this user will be homed too", null=True)
+ key = models.ForeignKey(Key, related_name='user', null=True, blank=True)
+
+ is_active = models.BooleanField(default=True)
+ is_admin = models.BooleanField(default=True)
+ is_staff = models.BooleanField(default=True)
+
+ objects = UserManager()
+
+ USERNAME_FIELD = 'email'
+ REQUIRED_FIELDS = ['firstname', 'lastname']
+
+ def get_full_name(self):
+ # The user is identified by their email address
+ return self.email
+
+ def get_short_name(self):
+ # The user is identified by their email address
+ return self.email
+
+ def __unicode__(self):
+ return self.email
+
+ def has_perm(self, perm, obj=None):
+ "Does the user have a specific permission?"
+ # Simplest possible answer: Yes, always
+ return True
+
+ def has_module_perms(self, app_label):
+ "Does the user have permissions to view the app `app_label`?"
+ # Simplest possible answer: Yes, always
+ return True
+
+ @property
+ def is_staff(self):
+ "Is the user a member of staff?"
+ # Simplest possible answer: All admins are staff
+ return self.is_admin
+
+
+ def save(self, *args, **kwds):
+ if has_openstack:
+ if not hasattr(self, 'os_manager'):
+ setattr(self, 'os_manager', OpenStackManager())
+
+ self.os_manager.save_user(self)
+ if not self.id:
+ self.set_password(self.password)
+ super(User, self).save(*args, **kwds)
+
+ def delete(self, *args, **kwds):
+ if has_openstack:
+ if not hasattr(self, 'os_manager'):
+ setattr(self, 'os_manager', OpenStackManager())
+
+ self.os_manager.delete_user(self)
+ super(User, self).delete(*args, **kwds)
diff --git a/planetstack/core/serializers.py b/planetstack/core/serializers.py
new file mode 100644
index 0000000..55cf7c8
--- /dev/null
+++ b/planetstack/core/serializers.py
@@ -0,0 +1,210 @@
+from django.forms import widgets
+from rest_framework import serializers
+from core.models import *
+
+
+class RoleSerializer(serializers.HyperlinkedModelSerializer):
+ # HyperlinkedModelSerializer doesn't include the id by default
+ id = serializers.Field()
+ class Meta:
+ model = Role
+ fields = ('id',
+ 'role_id',
+ 'role_type')
+
+
+class UserSerializer(serializers.HyperlinkedModelSerializer):
+ # HyperlinkedModelSerializer doesn't include the id by default
+ 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')
+ class Meta:
+ model = User
+ fields = ('id',
+ 'user_id',
+ 'kuser_id',
+ 'firstname',
+ 'lastname',
+ 'email',
+ 'password',
+ 'phone',
+ 'user_url',
+ 'is_admin',
+ 'site',
+ 'slice_memberships',
+ 'site_privileges')
+
+class KeySerializer(serializers.HyperlinkedModelSerializer):
+ id = serializers.Field()
+ user = serializers.HyperlinkedRelatedField(view_name='user-detail')
+ class Meta:
+ model = Key
+ fields = ('id',
+ 'name',
+ 'key',
+ 'type',
+ 'blacklisted',
+ 'user')
+
+
+class SliceSerializer(serializers.HyperlinkedModelSerializer):
+ # HyperlinkedModelSerializer doesn't include the id by default
+ id = serializers.Field()
+ site = serializers.HyperlinkedRelatedField(view_name='site-detail')
+ slivers = serializers.HyperlinkedRelatedField(view_name='sliver-detail')
+ subnet= serializers.HyperlinkedRelatedField(view_name='subnet-detail')
+ class Meta:
+ model = Slice
+ fields = ('id',
+ 'tenant_id',
+ 'enabled',
+ 'name',
+ 'url',
+ 'instantiation',
+ 'omf_friendly',
+ 'description',
+ 'slice_url',
+ 'network_id',
+ 'router_id',
+ 'site',
+ 'slivers',
+ 'updated',
+ 'created')
+
+class SliceMembershipSerializer(serializers.HyperlinkedModelSerializer):
+ id = serializers.Field()
+ slice = serializers.HyperlinkedRelatedField(view_name='slice-detail')
+ user = serializers.HyperlinkedRelatedField(view_name='user-detail')
+ role = serializers.HyperlinkedRelatedField(view_name='role-detail')
+ class Meta:
+ model = SitePrivilege
+ fields = ('id',
+ 'user',
+ 'slice',
+ 'role')
+
+class SubnetSerializer(serializers.HyperlinkedModelSerializer):
+ id = serializers.Field()
+ slice = serializers.HyperlinkedRelatedField(view_name='slice-detail')
+ class Meta:
+ model = Subnet
+ fields = ('id',
+ 'subnet_id',
+ 'cidr',
+ 'ip_version',
+ 'start',
+ 'end',
+ 'slice')
+
+class SiteSerializer(serializers.HyperlinkedModelSerializer):
+
+ #Experimenting with whether to use ids, hyperlinks, or nested includes
+ #slices = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
+ #slices = serializers.RelatedField(many=True, read_only=True)
+ #slices = SliceSerializer(many=True)
+ # HyperlinkedModelSerializer doesn't include the id by default
+ id = serializers.Field()
+ slices = serializers.HyperlinkedRelatedField(many=True, read_only=True,view_name='slice-detail')
+
+ class Meta:
+ model = Site
+ fields = ('id',
+ 'url',
+ 'name',
+ 'slices',
+ 'site_url',
+ 'enabled',
+ 'longitude',
+ 'latitude',
+ 'login_base',
+ 'tenant_id',
+ 'is_public',
+ 'abbreviated_name',
+ 'updated',
+ 'created')
+
+class SitePrivilegeSerializer(serializers.HyperlinkedModelSerializer):
+ id = serializers.Field()
+ site = serializers.HyperlinkedRelatedField(view_name='site-detail')
+ user = serializers.HyperlinkedRelatedField(view_name='user-detail')
+ role = serializers.HyperlinkedRelatedField(view_name='role-detail')
+ class Meta:
+ model = SitePrivilege
+ fields = ('id',
+ 'user',
+ 'site',
+ 'role')
+
+class DeploymentNetworkSerializer(serializers.HyperlinkedModelSerializer):
+
+ # HyperlinkedModelSerializer doesn't include the id by default
+ id = serializers.Field()
+ sites = serializers.HyperlinkedRelatedField(view_name='deploymentnetwork-detail')
+ class Meta:
+ model = DeploymentNetwork
+ fields = ('id',
+ 'name',
+ 'sites'
+ )
+
+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')
+ node = serializers.HyperlinkedRelatedField(view_name='node-detail')
+
+
+ #slice = serializers.PrimaryKeyRelatedField(read_only=True)
+
+ class Meta:
+ model = Sliver
+ fields = ('id',
+ 'instance_id',
+ 'name',
+ 'instance_name',
+ 'ip',
+ 'image',
+ 'key',
+ 'slice',
+ 'deploymentNetwork',
+ 'node')
+
+class NodeSerializer(serializers.HyperlinkedModelSerializer):
+ # HyperlinkedModelSerializer doesn't include the id by default
+ id = serializers.Field()
+ class Meta:
+ model = Node
+ fields = ('id',
+ 'name')
+
+class ImageSerializer(serializers.HyperlinkedModelSerializer):
+ # HyperlinkedModelSerializer doesn't include the id by default
+ id = serializers.Field()
+ class Meta:
+ model = Image
+ fields = ('id',
+ 'image_id',
+ 'name',
+ 'disk_format',
+ 'container_format')
+
+serializerLookUp = {
+ Role: RoleSerializer,
+ User: UserSerializer,
+ Key: KeySerializer,
+ Site: SiteSerializer,
+ SitePrivilege: SitePrivilegeSerializer,
+ Slice: SliceSerializer,
+ SliceMembership: SliceMembershipSerializer,
+ Subnet: SubnetSerializer,
+ Node: NodeSerializer,
+ Sliver: SliverSerializer,
+ DeploymentNetwork: DeploymentNetworkSerializer,
+ Image: ImageSerializer,
+ None: None,
+ }
+
diff --git a/planetstack/core/views/__init__.py b/planetstack/core/views/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/planetstack/core/views/__init__.py
diff --git a/planetstack/core/views/deployment_networks.py b/planetstack/core/views/deployment_networks.py
new file mode 100644
index 0000000..63220a4
--- /dev/null
+++ b/planetstack/core/views/deployment_networks.py
@@ -0,0 +1,59 @@
+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 DeploymentNetworkSerializer
+from util.request import parse_request
+
+
+class DeploymentNetworkListCreate(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 = DeploymentNetworkSerializer(deployment)
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+ else:
+ deployment_networks = get_deployment_networks(data['auth'])
+ serializer = DeploymentNetworkSerializer(deployment_networks, many=True)
+ return Response(serializer.data)
+
+
+class DeploymentNetworkRetrieveUpdateDestroy(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 = DeploymentNetworkSerializer(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/images.py b/planetstack/core/views/images.py
new file mode 100644
index 0000000..7e0ab59
--- /dev/null
+++ b/planetstack/core/views/images.py
@@ -0,0 +1,55 @@
+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)
+
+ def put(self, request, pk, format=None):
+ """update image not implemnted"""
+ return Response(status=status.HTTP_404_NOT_FOUND)
+
+ def delete(self, request, pk, format=None):
+ """delete image not implemnted"""
+ return Response(status=status.HTTP_404_NOT_FOUND)
+
+
+
+
diff --git a/planetstack/core/views/keys.py b/planetstack/core/views/keys.py
new file mode 100644
index 0000000..a5d0995
--- /dev/null
+++ b/planetstack/core/views/keys.py
@@ -0,0 +1,66 @@
+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.keys import add_key, delete_key, get_keys, update_key
+from core.serializers import KeySerializer
+from util.request import parse_request
+
+
+class KeyListCreate(APIView):
+ """
+ List all users or create a new key.
+ """
+
+ 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 'key' in data:
+ key = add_key(data['auth'], data['key'])
+ serializer = KeySerializer(key)
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+ else:
+ keys = get_keys(data['auth'])
+ serializer = KeySerializer(keys, many=True)
+ return Response(serializer.data)
+
+
+class KeyRetrieveUpdateDestroy(APIView):
+ """
+ Retrieve, update or delete a key
+ """
+
+ def post(self, request, pk, format=None):
+ """Retrieve a key"""
+ data = parse_request(request.DATA)
+ if 'auth' not in data:
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+ keys = get_keys(data['auth'], pk)
+ if not keys:
+ return Response(status=status.HTTP_404_NOT_FOUND)
+ serializer = KeySerializer(keys[0])
+ return Response(serializer.data)
+
+ def put(self, request, pk, format=None):
+ """update a key"""
+ data = parse_request(request.DATA)
+ if 'auth' not in data:
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+ elif 'key' not in data:
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+
+ key = update_key(pk, data['key'])
+ serializer = KeySerializer(key)
+ 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_key(data['auth'], pk)
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+
+
diff --git a/planetstack/core/views/nodes.py b/planetstack/core/views/nodes.py
new file mode 100644
index 0000000..0f1977e
--- /dev/null
+++ b/planetstack/core/views/nodes.py
@@ -0,0 +1,55 @@
+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
+ """
+
+ 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)
+
+ def put(self, request, pk, format=None):
+ """update node not implemnted"""
+ return Response(status=status.HTTP_404_NOT_FOUND)
+
+ 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/roles.py b/planetstack/core/views/roles.py
new file mode 100644
index 0000000..37bb149
--- /dev/null
+++ b/planetstack/core/views/roles.py
@@ -0,0 +1,58 @@
+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.
+ """
+
+ 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
+ """
+
+ 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)
+
+ 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/site_privileges.py b/planetstack/core/views/site_privileges.py
new file mode 100644
index 0000000..37fc371
--- /dev/null
+++ b/planetstack/core/views/site_privileges.py
@@ -0,0 +1,66 @@
+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
+ """
+
+ 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)
+
+ 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)
+
+ 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
new file mode 100644
index 0000000..6449b67
--- /dev/null
+++ b/planetstack/core/views/sites.py
@@ -0,0 +1,66 @@
+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)
+
+ site = update_site(pk, data['site'])
+ serializer = SiteSerializer(site)
+ 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(data['auth'], {'id': pk})
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+
+
diff --git a/planetstack/core/views/slice_memberships.py b/planetstack/core/views/slice_memberships.py
new file mode 100644
index 0000000..4bb581c
--- /dev/null
+++ b/planetstack/core/views/slice_memberships.py
@@ -0,0 +1,66 @@
+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
+ """
+
+ 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)
+
+ 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)
+
+ 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
new file mode 100644
index 0000000..5954d0c
--- /dev/null
+++ b/planetstack/core/views/slices.py
@@ -0,0 +1,66 @@
+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
+ """
+
+ 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)
+
+ 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)
+
+ 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
new file mode 100644
index 0000000..3741cce
--- /dev/null
+++ b/planetstack/core/views/slivers.py
@@ -0,0 +1,66 @@
+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
+ """
+
+ 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)
+
+ 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)
+
+ 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/subnets.py b/planetstack/core/views/subnets.py
new file mode 100644
index 0000000..881f615
--- /dev/null
+++ b/planetstack/core/views/subnets.py
@@ -0,0 +1,66 @@
+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.subnets import add_subnet, delete_subnet, get_subnets, update_subnet
+from core.serializers import SubnetSerializer
+from util.request import parse_request
+
+
+class SubnetListCreate(APIView):
+ """
+ List all subnets or create a new subnet.
+ """
+
+ 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 'subnet' in data:
+ subnet = add_subnet(data['auth'], data['subnet'])
+ serializer = SubnetSerializer(subnet)
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+ else:
+ subnets = get_subnets(data['auth'])
+ serializer = SubnetSerializer(subnets, many=True)
+ return Response(serializer.data)
+
+
+class SubnetRetrieveUpdateDestroy(APIView):
+ """
+ Retrieve, update or delete a subnet
+ """
+
+ def post(self, request, pk, format=None):
+ """Retrieve a subnet"""
+ data = parse_request(request.DATA)
+ if 'auth' not in data:
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+ subnets = get_subnets(data['auth'], {'id': pk})
+ if not subnets:
+ return Response(status=status.HTTP_404_NOT_FOUND)
+ serializer = SubnetSerializer(subnets[0])
+ return Response(serializer.data)
+
+ def put(self, request, pk, format=None):
+ """update a subnet"""
+ data = parse_request(request.DATA)
+ if 'auth' not in data:
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+ elif 'subnet' not in data:
+ return Response(status=status.HTTP_400_BAD_REQUEST)
+
+ subnet = update_subnet(pk, data['subnet'])
+ serializer = SubnetSerializer(subnet)
+ 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_subnet(data['auth'], {'id': pk})
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+
+
diff --git a/planetstack/core/views/users.py b/planetstack/core/views/users.py
new file mode 100644
index 0000000..8b27928
--- /dev/null
+++ b/planetstack/core/views/users.py
@@ -0,0 +1,66 @@
+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)
+
+ user = update_user(pk, data['user'])
+ serializer = UserSerializer(user)
+ 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_user(data['auth'], {'id': pk})
+ return Response(status=status.HTTP_204_NO_CONTENT)
+
+
+
diff --git a/planetstack/importer/__init__.py b/planetstack/importer/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/planetstack/importer/__init__.py
diff --git a/planetstack/importer/plclassic/__init__.py b/planetstack/importer/plclassic/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/planetstack/importer/plclassic/__init__.py
diff --git a/planetstack/importer/plclassic/importer.py b/planetstack/importer/plclassic/importer.py
new file mode 100644
index 0000000..3a65dc3
--- /dev/null
+++ b/planetstack/importer/plclassic/importer.py
@@ -0,0 +1,70 @@
+import os
+#os.environ.setdefault("DJANGO_SETTINGS_MODULE", "planetstack.settings")
+import sys
+from optparse import OptionParser
+from getpass import getpass
+import xmlrpclib
+from plclassic.site_importer import SiteImporter
+from plclassic.user_importer import UserImporter
+from plclassic.slice_importer import SliceImporter
+from plclassic.sliver_importer import SliverImporter
+
+
+class Call:
+ def __init__(self, callable, auth):
+ self.callable = callable
+ self.auth = auth
+
+ def __call__(self, *args, **kwds):
+ a = [self.auth] + list(args)
+ return self.callable(*a)
+
+class API():
+ def __init__(self, username, password, url):
+ self.auth = {'AuthMethod': 'password',
+ 'Username': username,
+ 'AuthString': password}
+ self.server = xmlrpclib.ServerProxy(url, allow_none=True)
+
+ def __getattr__(self, name):
+ return Call(getattr(self.server, name), self.auth)
+
+class Importer:
+
+ def __init__(self, username, password, url):
+ api = API(username, password, url)
+ self.sites = SiteImporter(api)
+ self.slices = SliceImporter(api)
+ self.users = UserImporter(api)
+ self.slivers = SliverImporter(api)
+
+ def run(self):
+ self.sites.run()
+ self.users.run()
+ self.slices.run(remote_sites=self.sites.remote_sites,
+ local_sites=self.sites.local_sites)
+ self.slivers.run()
+
+
+
+if __name__ == '__main__':
+ parser = OptionParser()
+
+ parser.add_option("-u", "--username", dest="username",
+ help="PLC username with which to authenticate")
+ parser.add_option("", "--url", dest="url",
+ help="PLC url to contact")
+
+ (config, args) = parser.parse_args()
+ if len(sys.argv) == 1:
+ parser.print_help()
+ sys.exit(1)
+
+ password = None
+ try:
+ password = getpass()
+ except (EOFError, KeyboardInterrupt):
+ print
+ sys.exit(0)
+
+ Importer(config.username, password, config.url).run()
diff --git a/planetstack/importer/plclassic/role_importer.py b/planetstack/importer/plclassic/role_importer.py
new file mode 100644
index 0000000..107587a
--- /dev/null
+++ b/planetstack/importer/plclassic/role_importer.py
@@ -0,0 +1,9 @@
+class RoleImporter:
+
+ def __init__(self, api):
+ self.api = api
+
+ def run(self):
+
+ return
+
diff --git a/planetstack/importer/plclassic/site_importer.py b/planetstack/importer/plclassic/site_importer.py
new file mode 100644
index 0000000..2ee8157
--- /dev/null
+++ b/planetstack/importer/plclassic/site_importer.py
@@ -0,0 +1,33 @@
+from core.models import Site
+
+class SiteImporter:
+
+ def __init__(self, api):
+ self.api = api
+ self.remote_sites = {}
+ self.local_sites = {}
+
+ def run(self):
+ db_sites = Site.objects.all()
+ for db_site in db_sites:
+ self.local_sites[db_site.login_base] = db_site
+ print "%s local sites" % len(db_sites)
+
+ sites = self.api.GetSites({'peer_id': None})
+ print "%s remote sites" % len(sites)
+ count = 0
+ for site in sites:
+ self.remote_sites[site['site_id']] = site
+ if site['login_base'] not in self.local_sites:
+ new_site = Site(name=site['name'],
+ login_base=site['login_base'],
+ site_url=site['url'],
+ enabled=site['enabled'],
+ longitude=site['longitude'],
+ latitude=site['latitude'],
+ is_public=site['is_public'],
+ abbreviated_name=site['abbreviated_name'])
+ new_site.save()
+ count += 1
+ self.local_sites[new_site.login_base] = new_site
+ print "imported %s sites" % count
diff --git a/planetstack/importer/plclassic/slice_importer.py b/planetstack/importer/plclassic/slice_importer.py
new file mode 100644
index 0000000..b25b483
--- /dev/null
+++ b/planetstack/importer/plclassic/slice_importer.py
@@ -0,0 +1,47 @@
+from core.models import Slice
+
+class SliceImporter:
+
+ def __init__(self, api):
+ self.api = api
+ self.remote_slices = {}
+ self.local_slices = {}
+
+ def run(self, remote_sites={}, local_sites={}):
+ if not remote_sites:
+ sites = self.api.GetSites({'peer_id': None})
+ for site in sites:
+ remote_sites[site['site_id']] = site
+
+
+ if not local_sites:
+ from core.models import Site
+ sites = Site.objects.all()
+ for site in sites:
+ local_sites[site.login_base] = site
+
+ db_slices = Slice.objects.all()
+ for db_slice in db_slices:
+ self.local_slices[db_slice.name] = db_slice
+ print "%s local slices" % len(db_slices)
+
+ slices = self.api.GetSlices({'peer_id': None})
+ print "%s remote slices" % len(slices)
+ count = 0
+ for slice in slices:
+ self.remote_slices[slice['slice_id']] = slice
+ if slice['name'] not in self.local_slices:
+ site = local_sites[remote_sites[slice['site_id']]['login_base']]
+ new_slice = Slice(name=slice['name'],
+ instantiation=slice['instantiation'],
+ omf_friendly = False,
+ description = slice['description'],
+ slice_url = slice['url'],
+ site = site)
+ new_slice.save()
+ count += 1
+ self.local_slices[new_slice.name] = new_slice
+ print "Imported %s slices" % count
+
+
+
diff --git a/planetstack/importer/plclassic/sliver_importer.py b/planetstack/importer/plclassic/sliver_importer.py
new file mode 100644
index 0000000..3f7912f
--- /dev/null
+++ b/planetstack/importer/plclassic/sliver_importer.py
@@ -0,0 +1,9 @@
+from PLC.Nodes import Nodes
+
+class SliverImporter:
+
+ def __init__(self, api):
+ self.api = api
+
+ def run(self):
+ return
diff --git a/planetstack/importer/plclassic/user_importer.py b/planetstack/importer/plclassic/user_importer.py
new file mode 100644
index 0000000..21d74b6
--- /dev/null
+++ b/planetstack/importer/plclassic/user_importer.py
@@ -0,0 +1,19 @@
+
+class UserImporter:
+
+ def __init__(self, api):
+ self.api = api
+ self.users = {}
+
+ def run(self):
+ users = self.api.GetPersons()
+
+ def save_site_privs(self, user):
+ # update site roles
+ pass
+
+ def save_slice_privs(self, user):
+ # update slice roles
+ pass
+
+
diff --git a/planetstack/manage.py b/planetstack/manage.py
new file mode 100644
index 0000000..c12bf36
--- /dev/null
+++ b/planetstack/manage.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "planetstack.settings")
+
+ from django.core.management import execute_from_command_line
+
+ execute_from_command_line(sys.argv)
diff --git a/planetstack/openstack/__init__.py b/planetstack/openstack/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/planetstack/openstack/__init__.py
diff --git a/planetstack/openstack/client.py b/planetstack/openstack/client.py
new file mode 100644
index 0000000..f3abbb2
--- /dev/null
+++ b/planetstack/openstack/client.py
@@ -0,0 +1,148 @@
+try:
+ from keystoneclient.v2_0 import client as keystone_client
+ from glance import client as glance_client
+ from novaclient.v1_1 import client as nova_client
+ from quantumclient.v2_0 import client as quantum_client
+ has_openstack = True
+except:
+ has_openstack = False
+
+from planetstack.config import Config
+
+def require_enabled(callable):
+ def wrapper(*args, **kwds):
+ if has_openstack:
+ return callable(*args, **kwds)
+ else:
+ return None
+ return wrapper
+
+def parse_novarc(filename):
+ opts = {}
+ f = open(filename, 'r')
+ for line in f:
+ try:
+ line = line.replace('export', '').strip()
+ parts = line.split('=')
+ if len(parts) > 1:
+ value = parts[1].replace("\'", "")
+ value = value.replace('\"', '')
+ opts[parts[0]] = value
+ except:
+ pass
+ f.close()
+ return opts
+
+class Client:
+ def __init__(self, username=None, password=None, tenant=None, url=None, config=None, *args, **kwds):
+ if config:
+ config = Config(config)
+ else:
+ config = Config()
+ self.has_openstack = has_openstack
+ self.username = config.nova_admin_user
+ self.password = config.nova_admin_password
+ self.tenant = config.nova_admin_tenant
+ self.url = config.nova_url
+
+ if username:
+ self.username = username
+ if password:
+ self.password = password
+ if tenant:
+ self.tenant = tenant
+ if url:
+ self.url = url
+
+ if '@' in self.username:
+ self.username = self.username[:self.username.index('@')]
+
+class KeystoneClient(Client):
+ def __init__(self, *args, **kwds):
+ Client.__init__(self, *args, **kwds)
+ if has_openstack:
+ self.client = keystone_client.Client(username=self.username,
+ password=self.password,
+ tenant_name=self.tenant,
+ auth_url=self.url)
+
+ @require_enabled
+ def connect(self, *args, **kwds):
+ self.__init__(*args, **kwds)
+
+ @require_enabled
+ def __getattr__(self, name):
+ return getattr(self.client, name)
+
+
+class GlanceClient(Client):
+ def __init__(self, *args, **kwds):
+ Client.__init__(self, *args, **kwds)
+ if has_openstack:
+ self.client = glance_client.get_client(host='0.0.0.0',
+ username=self.username,
+ password=self.password,
+ tenant=self.tenant,
+ auth_url=self.url)
+ @require_enabled
+ def __getattr__(self, name):
+ return getattr(self.client, name)
+
+class NovaClient(Client):
+ def __init__(self, *args, **kwds):
+ Client.__init__(self, *args, **kwds)
+ if has_openstack:
+ self.client = nova_client.Client(username=self.username,
+ api_key=self.password,
+ project_id=self.tenant,
+ auth_url=self.url,
+ region_name='',
+ extensions=[],
+ service_type='compute',
+ service_name='',
+ )
+
+ @require_enabled
+ def connect(self, *args, **kwds):
+ self.__init__(*args, **kwds)
+
+ @require_enabled
+ def __getattr__(self, name):
+ return getattr(self.client, name)
+
+class QuantumClient(Client):
+ def __init__(self, *args, **kwds):
+ Client.__init__(self, *args, **kwds)
+ if has_openstack:
+ self.client = quantum_client.Client(username=self.username,
+ password=self.password,
+ tenant_name=self.tenant,
+ auth_url=self.url)
+ @require_enabled
+ def connect(self, *args, **kwds):
+ self.__init__(*args, **kwds)
+
+ @require_enabled
+ def __getattr__(self, name):
+ return getattr(self.client, name)
+
+class OpenStackClient:
+ """
+ A simple native shell to the openstack backend services.
+ This class can receive all nova calls to the underlying testbed
+ """
+
+ def __init__ ( self, *args, **kwds) :
+ # instantiate managers
+ self.keystone = KeystoneClient(*args, **kwds)
+ self.glance = GlanceClient(*args, **kwds)
+ self.nova = NovaClient(*args, **kwds)
+ self.quantum = QuantumClient(*args, **kwds)
+
+ @require_enabled
+ def connect(self, *args, **kwds):
+ self.__init__(*args, **kwds)
+
+ @require_enabled
+ def authenticate(self):
+ return self.keystone.authenticate()
diff --git a/planetstack/openstack/driver.py b/planetstack/openstack/driver.py
new file mode 100644
index 0000000..6b04b5d
--- /dev/null
+++ b/planetstack/openstack/driver.py
@@ -0,0 +1,285 @@
+from planetstack.config import Config
+from openstack.client import OpenStackClient
+
+has_openstack = False
+class OpenStackDriver:
+
+ def __init__(self, config = None, client=None):
+ if config:
+ self.config = Config(config)
+ else:
+ self.config = Config()
+
+ self.admin_client = OpenStackClient()
+ if has_openstack:
+ self.admin_user = self.admin_client.keystone.users.find(name=self.admin_client.keystone.username)
+ else:
+ self.admin_user = None
+
+ if client:
+ self.shell = client
+ else:
+ self.shell = OpenStackClient()
+
+ 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]
+ return role
+
+ def delete_role(self, filter):
+ roles = self.shell.keystone.roles.findall(**filter)
+ for role in roles:
+ self.shell.keystone.roles.delete(role)
+ return 1
+
+ def create_tenant(self, tenant_name, enabled, description):
+ """Create keystone tenant. Suggested fields: name, description, enabled"""
+ tenants = self.shell.keystone.tenants.findall(name=tenant_name)
+ if not tenants:
+ fields = {'tenant_name': tenant_name, 'enabled': enabled,
+ 'description': description}
+ tenant = self.shell.keystone.tenants.create(**fields)
+ else:
+ tenant = tenants[0]
+
+ # always give the admin user the admin role to any tenant created
+ # by the driver.
+ self.add_user_role(self.admin_user.id, tenant.id, 'admin')
+ return tenant
+
+ def update_tenant(self, id, **kwds):
+ return self.shell.keystone.tenants.update(id, **kwds)
+
+ def delete_tenant(self, id):
+ tenants = self.shell.keystone.tenants.findall(id=id)
+ for tenant in tenants:
+ self.shell.keystone.tenants.delete(tenant)
+ return 1
+
+ def create_user(self, name, email, password, enabled):
+ users = self.shell.keystone.users.findall(email=email)
+ if not users:
+ fields = {'name': name, 'email': email, 'password': password,
+ 'enabled': enabled}
+ user = self.shell.keystone.users.create(**fields)
+ else:
+ user = users[0]
+ return user
+
+ def delete_user(self, id):
+ users = self.shell.keystone.users.findall(id=id)
+ for user in users:
+ self.shell.keystone.users.delete(user)
+ return 1
+
+ def add_user_role(self, kuser_id, tenant_id, role_name):
+ user = self.shell.keystone.users.find(id=kuser_id)
+ tenant = self.shell.keystone.tenants.find(id=tenant_id)
+ role = self.shell.keystone.roles.find(name=role_name)
+
+ role_found = False
+ user_roles = user.list_roles(tenant.id)
+ for user_role in user_roles:
+ if user_role.name == role.name:
+ role_found = True
+ if not role_found:
+ tenant.add_user(user, role)
+
+ return 1
+
+ def delete_user_role(self, kuser_id, tenant_id, role_name):
+ user = self.shell.keystone.users.find(id=kuser_id)
+ tenant = self.shell.keystone.tenants.find(id=tenant_id)
+ role = self.shell.keystone.roles.find(name=role_name)
+
+ role_found = False
+ user_roles = user.list_roles(tenant.id)
+ for user_role in user_roles:
+ if user_role.name == role.name:
+ role_found = True
+ if role_found:
+ tenant.remove_user(user, role)
+
+ return 1
+
+ def update_user(self, id, **kwds):
+ return self.shell.keystone.users.update(id, **kwds)
+
+ def create_router(self, name, set_gateway=True):
+ routers = self.shell.quantum.list_routers(name=name)['routers']
+ if routers:
+ router = routers[0]
+ else:
+ router = self.shell.quantum.create_router({'router': {'name': name}})['router']
+ # add router to external network
+ if set_gateway:
+ nets = self.shell.quantum.list_networks()['networks']
+ for net in nets:
+ if net['router:external'] == True:
+ self.shell.quantum.add_gateway_router(router['id'],
+ {'network_id': net['id']})
+
+ return router
+
+ def delete_router(self, id):
+ routers = self.shell.quantum.list_routers(id=id)['routers']
+ for router in routers:
+ self.shell.quantum.delete_router(router['id'])
+ # remove router form external network
+ #nets = self.shell.quantum.list_networks()['networks']
+ #for net in nets:
+ # if net['router:external'] == True:
+ # self.shell.quantum.remove_gateway_router(router['id'])
+
+ def add_router_interface(self, router_id, subnet_id):
+ router = self.shell.quantum.show_router(router_id)['router']
+ subnet = self.shell.quantum.show_subnet(subnet_id)['subnet']
+ if router and subnet:
+ self.shell.quantum.add_interface_router(router_id, {'subnet_id': subnet_id})
+
+ def delete_router_interface(self, router_id, subnet_id):
+ router = self.shell.quantum.show_router(router_id)
+ subnet = self.shell.quantum.show_subnet(subnet_id)
+ if router and subnet:
+ self.shell.quantum.remove_interface_router(router_id, {'subnet_id': subnet_id})
+
+ def create_network(self, name):
+ 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']
+ return net
+
+ def delete_network(self, id):
+ nets = self.shell.quantum.list_networks()['networks']
+ for net in nets:
+ if net['id'] == id:
+ # delete_all ports
+ self.delete_network_ports(net['id'])
+ # delete all subnets:
+ for subnet_id in net['subnets']:
+ self.delete_subnet(subnet_id)
+ self.shell.quantum.delete_network(net['id'])
+ return 1
+
+ def delete_network_ports(self, network_id):
+ ports = self.shell.quantum.list_ports()['ports']
+ for port in ports:
+ if port['network_id'] == network_id:
+ self.shell.quantum.delete_port(port['id'])
+ return 1
+
+ def delete_subnet_ports(self, subnet_id):
+ ports = self.shell.quantum.list_ports()['ports']
+ for port in ports:
+ delete = False
+ for fixed_ip in port['fixed_ips']:
+ if fixed_ip['subnet_id'] == subnet_id:
+ delete=True
+ break
+ if delete:
+ self.shell.quantum.delete_port(port['id'])
+ return 1
+
+ def create_subnet(self, name, network_id, cidr_ip, ip_version, start, end):
+ #nets = self.shell.quantum.list_networks(name=network_name)['networks']
+ #if not nets:
+ # raise Exception, "No such network: %s" % network_name
+ #net = nets[0]
+
+ subnet = None
+ subnets = self.shell.quantum.list_subnets()['subnets']
+ 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,
+ 'network_id': network_id,
+ 'ip_version': ip_version,
+ 'cidr': cidr_ip,
+ 'dns_nameservers': ['8.8.8.8', '8.8.4.4'],
+ 'allocation_pools': allocation_pools}}
+ subnet = self.shell.quantum.create_subnet(subnet)['subnet']
+
+ # TODO: Add route to external network
+ # e.g. # route add -net 10.0.3.0/24 dev br-ex gw 10.100.0.5
+ return subnet
+
+ def update_subnet(self, id, fields):
+ return self.shell.quantum.update_subnet(id, fields)
+
+ def delete_subnet(self, id):
+ #return self.shell.quantum.delete_subnet(id=id)
+ # inefficient but fault tolerant
+ subnets = self.shell.quantum.list_subnets()['subnets']
+ for subnet in subnets:
+ if subnet['id'] == id:
+ self.delete_subnet_ports(subnet['id'])
+ self.shell.quantum.delete_subnet(id)
+ return
+
+ def create_keypair(self, name, key):
+ keys = self.shell.nova.keypairs.findall(name=name)
+ if keys:
+ key = keys[0]
+ else:
+ key = self.shell.nova.keypairs.create(name=name, public_key=key)
+ return key
+
+ def delete_keypair(self, id):
+ keys = self.shell.nova.keypairs.findall(id=id)
+ for key in keys:
+ self.shell.nova.keypairs.delete(key)
+ return 1
+
+ def spawn_instance(self, name, key_name=None, hostname=None, image_id=None, security_group=None, pubkeys=[]):
+ flavor_name = self.config.nova_default_flavor
+ flavor = self.shell.nova.flavors.find(name=flavor_name)
+ #if not image:
+ # image = self.config.nova_default_imave
+ if not security_group:
+ security_group = self.config.nova_default_security_group
+
+ #authorized_keys = "\n".join(pubkeys)
+ #files = {'/root/.ssh/authorized_keys': authorized_keys}
+ files = {}
+
+ hints = {}
+ availability_zone = None
+ if hostname:
+ availability_zone = 'nova:%s' % hostname
+ server = self.shell.nova.servers.create(
+ name=name,
+ key_name = key_name,
+ flavor=flavor.id,
+ image=image_id,
+ security_group = security_group,
+ files=files,
+ scheduler_hints=hints,
+ availability_zone=availability_zone)
+ return server
+
+ def destroy_instance(self, id):
+ servers = self.shell.nova.servers.findall(id=id)
+ for server in servers:
+ self.shell.nova.servers.delete(server)
+
+ def update_instance_metadata(self, id, metadata):
+ servers = self.shell.nova.servers.findall(id=id)
+ for server in servers:
+ self.shell.nova.servers.set_meta(server, metadata)
+ # note: set_meta() returns a broken Server() object. Don't try to
+ # print it in the shell or it will fail in __repr__.
+
+ def delete_instance_metadata(self, id, metadata):
+ # note: metadata is a dict. Only the keys matter, not the values.
+ servers = self.shell.nova.servers.findall(id=id)
+ for server in servers:
+ self.shell.nova.servers.delete_meta(server, metadata)
+
diff --git a/planetstack/openstack/manager.py b/planetstack/openstack/manager.py
new file mode 100644
index 0000000..788a621
--- /dev/null
+++ b/planetstack/openstack/manager.py
@@ -0,0 +1,245 @@
+from planetstack import settings
+#from django.core import management
+#management.setup_environ(settings)
+import os
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "planetstack.settings")
+
+try:
+ from openstack.client import OpenStackClient
+ from openstack.driver import OpenStackDriver
+ from planetstack.config import Config
+ from core.models import *
+ has_openstack = True
+except:
+ has_openstack = False
+
+#manager_enabled = Config().api_nova_enabled
+manager_enabled = False
+
+def require_enabled(callable):
+ def wrapper(*args, **kwds):
+ if manager_enabled and has_openstack:
+ return callable(*args, **kwds)
+ else:
+ return None
+ return wrapper
+
+
+class OpenStackManager:
+
+ def __init__(self, auth={}, caller=None):
+ if auth:
+ self.client = OpenStackClient(**auth)
+ else:
+ self.client = OpenStackClient()
+ self.has_openstack = has_openstack
+ self.enabled = manager_enabled
+ self.driver = OpenStackDriver(client=self.client)
+ self.caller=caller
+ if not self.caller:
+ self.caller = self.driver.admin_user
+ self.caller.kuser_id = self.caller.id
+
+ @require_enabled
+ def save_role(self, role):
+ if not role.role_id:
+ keystone_role = self.driver.create_role(role.role_type)
+ role.role_id = keystone_role.id
+
+ @require_enabled
+ def delete_role(self, role):
+ if role.role_id:
+ self.driver.delete_role({'id': role.role_id})
+
+ @require_enabled
+ def save_key(self, key):
+ if not key.key_id:
+ key_fields = {'name': key.name,
+ 'key': key.key}
+ nova_key = self.driver.create_keypair(**key_fields)
+ key.key_id = nova_key.id
+
+ @require_enabled
+ def delete_key(self, key):
+ if key.key_id:
+ self.driver.delete_keypair(key.key_id)
+
+ @require_enabled
+ def save_user(self, user):
+ if not user.kuser_id:
+ name = user.email[:user.email.find('@')]
+ user_fields = {'name': name,
+ 'email': user.email,
+ 'password': user.password,
+ 'enabled': True}
+ keystone_user = self.driver.create_user(**user_fields)
+ user.kuser_id = keystone_user.id
+
+ @require_enabled
+ def delete_user(self, user):
+ if user.kuser_id:
+ self.driver.delete_user(user.kuser_id)
+
+
+
+ @require_enabled
+ def save_site(self, site, add_role=True):
+ if not site.tenant_id:
+ tenant = self.driver.create_tenant(tenant_name=site.login_base,
+ description=site.name,
+ enabled=site.enabled)
+ site.tenant_id = tenant.id
+ # give caller an admin role at the tenant they've created
+ self.driver.add_user_role(self.caller.kuser_id, tenant.id, 'admin')
+
+ # update the record
+ if site.id and site.tenant_id:
+ self.driver.update_tenant(site.tenant_id,
+ description=site.name,
+ enabled=site.enabled)
+
+ @require_enabled
+ def delete_site(self, site):
+ if site.tenant_id:
+ self.driver.delete_tenant(site.tenant_id)
+
+ @require_enabled
+ def save_slice(self, slice):
+ if not slice.tenant_id:
+ nova_fields = {'tenant_name': slice.name,
+ 'description': slice.description,
+ 'enabled': slice.enabled}
+ tenant = self.driver.create_tenant(**nova_fields)
+ slice.tenant_id = tenant.id
+
+ # give caller an admin role at the tenant they've created
+ self.driver.add_user_role(self.caller.kuser_id, tenant.id, 'admin')
+
+ # refresh credentials using this tenant
+ self.driver.shell.connect(username=self.driver.shell.keystone.username,
+ password=self.driver.shell.keystone.password,
+ tenant=tenant.name)
+
+ # create network
+ network = self.driver.create_network(slice.name)
+ slice.network_id = network['id']
+
+ # create router
+ router = self.driver.create_router(slice.name)
+ slice.router_id = router['id']
+
+ if slice.id and slice.tenant_id:
+ self.driver.update_tenant(slice.tenant_id,
+ description=slice.description,
+ enabled=slice.enabled)
+
+ @require_enabled
+ def delete_slice(self, slice):
+ if slice.tenant_id:
+ self.driver.delete_router(slice.router_id)
+ self.driver.delete_network(slice.network_id)
+ self.driver.delete_tenant(slice.tenant_id)
+
+ @require_enabled
+ def save_subnet(self, subnet):
+ if not subnet.subnet_id:
+ quantum_subnet = self.driver.create_subnet(name= subnet.slice.name,
+ network_id=subnet.slice.network_id,
+ cidr_ip = subnet.cidr,
+ ip_version=subnet.ip_version,
+ start = subnet.start,
+ end = subnet.end)
+ subnet.subnet_id = quantum_subnet['id']
+ # add subnet as interface to slice's router
+ self.driver.add_router_interface(subnet.slice.router_id, subnet.subnet_id)
+ #add_route = 'route add -net %s dev br-ex gw 10.100.0.5' % self.cidr
+ #commands.getstatusoutput(add_route)
+
+
+ @require_enabled
+ def delete_subnet(self, subnet):
+ if subnet.subnet_id:
+ self.driver.delete_router_interface(subnet.slice.router_id, subnet.subnet_id)
+ self.driver.delete_subnet(subnet.subnet_id)
+ #del_route = 'route del -net %s' % self.cidr
+ #commands.getstatusoutput(del_route)
+
+ @require_enabled
+ def save_sliver(self, sliver):
+ if not sliver.instance_id:
+ instance = self.driver.spawn_instance(name=sliver.name,
+ key_name = sliver.key.name,
+ image_id = sliver.image.image_id,
+ hostname = sliver.node.name )
+ sliver.instance_id = instance.id
+ sliver.instance_name = getattr(instance, 'OS-EXT-SRV-ATTR:instance_name')
+
+ @require_enabled
+ def delete_sliver(self, sliver):
+ if sliver.instance_id:
+ self.driver.destroy_instance(sliver.instance_id)
+
+
+ def refresh_nodes(self):
+ # collect local nodes
+ nodes = Node.objects.all()
+ nodes_dict = {}
+ for node in nodes:
+ if 'viccidev10' not in node.name:
+ nodes_dict[node.name] = node
+
+ deployment = DeploymentNetwork.objects.filter(name='VICCI')[0]
+ login_bases = ['princeton', 'stanford', 'gt', 'uw', 'mpisws']
+ sites = Site.objects.filter(login_base__in=login_bases)
+ # collect nova nodes:
+ compute_nodes = self.client.nova.hypervisors.list()
+
+ compute_nodes_dict = {}
+ for compute_node in compute_nodes:
+ compute_nodes_dict[compute_node.hypervisor_hostname] = compute_node
+
+ # add new nodes:
+ new_node_names = set(compute_nodes_dict.keys()).difference(nodes_dict.keys())
+ i = 0
+ max = len(sites)
+ for name in new_node_names:
+ if i == max:
+ i = 0
+ site = sites[i]
+ node = Node(name=compute_nodes_dict[name].hypervisor_hostname,
+ site=site,
+ deploymentNetwork=deployment)
+ node.save()
+ i+=1
+
+ # remove old nodes
+ old_node_names = set(nodes_dict.keys()).difference(compute_nodes_dict.keys())
+ Node.objects.filter(name__in=old_node_names).delete()
+
+ def refresh_images(self):
+ # collect local images
+ images = Image.objects.all()
+ images_dict = {}
+ for image in images:
+ images_dict[image.name] = image
+
+ # collect glance images
+ glance_images = self.client.glance.get_images()
+ glance_images_dict = {}
+ for glance_image in glance_images:
+ glance_images_dict[glance_image['name']] = glance_image
+
+ # add new images
+ new_image_names = set(glance_images_dict.keys()).difference(images_dict.keys())
+ for name in new_image_names:
+ image = Image(image_id=glance_images_dict[name]['id'],
+ name=glance_images_dict[name]['name'],
+ disk_format=glance_images_dict[name]['disk_format'],
+ container_format=glance_images_dict[name]['container_format'])
+ image.save()
+
+ # remove old images
+ old_image_names = set(images_dict.keys()).difference(glance_images_dict.keys())
+ Image.objects.filter(name__in=old_image_names).delete()
+
+
diff --git a/planetstack/openstack/siteagent.py b/planetstack/openstack/siteagent.py
new file mode 100644
index 0000000..a57fa0b
--- /dev/null
+++ b/planetstack/openstack/siteagent.py
@@ -0,0 +1,22 @@
+import os
+import sys
+#os.environ.setdefault("DJANGO_SETTINGS_MODULE", "planetstack.settings")
+import time
+from core.models.site import Site
+from openstack.manager import OpenStackManager
+
+class SiteAgent:
+ def run(self):
+ manager = OpenStackManager()
+ # exit if openstack is disable or unavailable
+ if manager.enabled and manager.has_openstack:
+ # fill in null tenant ids
+ sites = Site.objects.filter(tenant_id__in=[None, ''])
+ for site in sites:
+ # calling save() on the model should force the tenant_id to be set
+ site.os_manager = manager
+ site.save()
+
+if __name__ == '__main__':
+ SiteAgent().run()
+
diff --git a/planetstack/openstack/sliveragent.py b/planetstack/openstack/sliveragent.py
new file mode 100644
index 0000000..b2f29cf
--- /dev/null
+++ b/planetstack/openstack/sliveragent.py
@@ -0,0 +1,39 @@
+import os
+import sys
+#os.environ.setdefault("DJANGO_SETTINGS_MODULE", "planetstack.settings")
+import time
+from core.models.sliver import Sliver
+from openstack.manager import OpenStackManager
+
+class SliverAgent:
+
+ def run(self):
+ manager = OpenStackManager()
+ # exit if openstack is disable or unavailable
+ if not manager.enabled or not manager.has_openstack:
+ sys.exit()
+
+ while True :
+ # fill in null ip addresses
+ slivers = Sliver.objects.filter(ip=None)
+ for sliver in slivers:
+ # update connection
+ manager.client.connect(username=manager.client.keystone.username,
+ password=manager.client.keystone.password,
+ tenant=sliver.slice.name)
+ sliver.os_manager = manager
+ servers = manager.client.nova.servers.findall(id=sliver.instance_id)
+ if not servers:
+ continue
+ server = servers[0]
+ ips = server.addresses.get(sliver.slice.name, [])
+ if not ips:
+ continue
+ sliver.ip = ips[0]['addr']
+ sliver.save()
+ time.sleep(7)
+
+
+if __name__ == '__main__':
+ SliverAgent().run()
+
diff --git a/planetstack/planetstack/__init__.py b/planetstack/planetstack/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/planetstack/planetstack/__init__.py
diff --git a/planetstack/planetstack/config.py b/planetstack/planetstack/config.py
new file mode 100644
index 0000000..7927803
--- /dev/null
+++ b/planetstack/planetstack/config.py
@@ -0,0 +1,240 @@
+#!/usr/bin/python
+import sys
+import os
+import time
+import ConfigParser
+import tempfile
+import codecs
+from StringIO import StringIO
+from util.xml import Xml
+
+default_config = \
+"""
+"""
+
+def isbool(v):
+ return v.lower() in ("true", "false")
+
+def str2bool(v):
+ return v.lower() in ("true", "1")
+
+class Config:
+
+ def __init__(self, config_file='/opt/planetstack/plstackapi_config'):
+ self._files = []
+ self.config_path = os.path.dirname(config_file)
+ self.config = ConfigParser.ConfigParser()
+ self.filename = config_file
+ if not os.path.isfile(self.filename):
+ self.create(self.filename)
+ self.load(self.filename)
+
+
+ def _header(self):
+ header = """
+DO NOT EDIT. This file was automatically generated at
+%s from:
+
+%s
+""" % (time.asctime(), os.linesep.join(self._files))
+
+ # Get rid of the surrounding newlines
+ return header.strip().split(os.linesep)
+
+ def create(self, filename):
+ if not os.path.exists(os.path.dirname(filename)):
+ os.makedirs(os.path.dirname(filename))
+ configfile = open(filename, 'w')
+ configfile.write(default_config)
+ configfile.close()
+
+
+ def load(self, filename):
+ if filename:
+ try:
+ self.config.read(filename)
+ except ConfigParser.MissingSectionHeaderError:
+ if filename.endswith('.xml'):
+ self.load_xml(filename)
+ else:
+ self.load_shell(filename)
+ self._files.append(filename)
+ self.set_attributes()
+
+ def load_xml(self, filename):
+ xml = XML(filename)
+ categories = xml.xpath('//configuration/variables/category')
+ for category in categories:
+ section_name = category.get('id')
+ if not self.config.has_section(section_name):
+ self.config.add_section(section_name)
+ options = category.xpath('./variablelist/variable')
+ for option in options:
+ option_name = option.get('id')
+ value = option.xpath('./value')[0].text
+ if not value:
+ value = ""
+ self.config.set(section_name, option_name, value)
+
+ def load_shell(self, filename):
+ f = open(filename, 'r')
+ for line in f:
+ try:
+ if line.startswith('#'):
+ continue
+ parts = line.strip().split("=")
+ if len(parts) < 2:
+ continue
+ option = parts[0]
+ value = parts[1].replace('"', '').replace("'","")
+ section, var = self.locate_varname(option, strict=False)
+ if section and var:
+ self.set(section, var, value)
+ except:
+ pass
+ f.close()
+
+ def locate_varname(self, varname, strict=True):
+ varname = varname.lower()
+ sections = self.config.sections()
+ section_name = ""
+ var_name = ""
+ for section in sections:
+ if varname.startswith(section.lower()) and len(section) > len(section_name):
+ section_name = section.lower()
+ var_name = varname.replace(section_name, "")[1:]
+ if strict and not self.config.has_option(section_name, var_name):
+ raise ConfigParser.NoOptionError(var_name, section_name)
+ return (section_name, var_name)
+
+ def set_attributes(self):
+ sections = self.config.sections()
+ for section in sections:
+ for item in self.config.items(section):
+ name = "%s_%s" % (section, item[0])
+ value = item[1]
+ if isbool(value):
+ value = str2bool(value)
+ elif value.isdigit():
+ value = int(value)
+ setattr(self, name, value)
+ setattr(self, name.upper(), value)
+
+
+ def verify(self, config1, config2, validate_method):
+ return True
+
+ def validate_type(self, var_type, value):
+ return True
+
+ @staticmethod
+ def is_xml(config_file):
+ try:
+ x = Xml(config_file)
+ return True
+ except:
+ return False
+
+ @staticmethod
+ def is_ini(config_file):
+ try:
+ c = ConfigParser.ConfigParser()
+ c.read(config_file)
+ return True
+ except ConfigParser.MissingSectionHeaderError:
+ return False
+
+
+ def dump(self, sections = []):
+ sys.stdout.write(output_python())
+
+ def output_python(self, encoding = "utf-8"):
+ buf = codecs.lookup(encoding)[3](StringIO())
+ buf.writelines(["# " + line + os.linesep for line in self._header()])
+
+ for section in self.sections():
+ buf.write("[%s]%s" % (section, os.linesep))
+ for (name,value) in self.items(section):
+ buf.write("%s=%s%s" % (name,value,os.linesep))
+ buf.write(os.linesep)
+ return buf.getvalue()
+
+ def output_shell(self, show_comments = True, encoding = "utf-8"):
+ """
+ Return variables as a shell script.
+ """
+
+ buf = codecs.lookup(encoding)[3](StringIO())
+ buf.writelines(["# " + line + os.linesep for line in self._header()])
+
+ for section in self.sections():
+ for (name,value) in self.items(section):
+ # bash does not have the concept of NULL
+ if value:
+ option = "%s_%s" % (section.upper(), name.upper())
+ if isbool(value):
+ value = str(str2bool(value))
+ elif not value.isdigit():
+ value = '"%s"' % value
+ buf.write(option + "=" + value + os.linesep)
+ return buf.getvalue()
+
+ def output_php(self, encoding = "utf-8"):
+ """
+ Return variables as a PHP script.
+ """
+
+ buf = codecs.lookup(encoding)[3](StringIO())
+ buf.write("<?php" + os.linesep)
+ buf.writelines(["// " + line + os.linesep for line in self._header()])
+
+ for section in self.sections():
+ for (name,value) in self.items(section):
+ option = "%s_%s" % (section, name)
+ buf.write(os.linesep)
+ buf.write("// " + option + os.linesep)
+ if value is None:
+ value = 'NULL'
+ buf.write("define('%s', %s);" % (option, value) + os.linesep)
+
+ buf.write("?>" + os.linesep)
+
+ return buf.getvalue()
+
+ def output_xml(self, encoding = "utf-8"):
+ pass
+
+ def output_variables(self, encoding="utf-8"):
+ """
+ Return list of all variable names.
+ """
+
+ buf = codecs.lookup(encoding)[3](StringIO())
+ for section in self.sections():
+ for (name,value) in self.items(section):
+ option = "%s_%s" % (section,name)
+ buf.write(option + os.linesep)
+
+ return buf.getvalue()
+ pass
+
+ def write(self, filename=None):
+ if not filename:
+ filename = self.filename
+ configfile = open(filename, 'w')
+ self.config.write(configfile)
+
+ def save(self, filename=None):
+ self.write(filename)
+
+ def __getattr__(self, attr):
+ return getattr(self.config, attr)
+
+if __name__ == '__main__':
+ filename = None
+ if len(sys.argv) > 1:
+ filename = sys.argv[1]
+ config = Config(filename)
+ else:
+ config = Config()
+ config.dump()
diff --git a/planetstack/planetstack/settings.py b/planetstack/planetstack/settings.py
new file mode 100644
index 0000000..8970c42
--- /dev/null
+++ b/planetstack/planetstack/settings.py
@@ -0,0 +1,163 @@
+# Django settings for planetstack project.
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email@example.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.postgresql_psycopg2', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
+ 'NAME': 'planetstack', # Or path to database file if using sqlite3.
+ # The following settings are not used with sqlite3:
+ 'USER': 'postgres',
+ 'PASSWORD': 'admin',
+ 'HOST': 'localhost', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
+ 'PORT': '', # Set to empty string for default.
+ }
+}
+
+AUTH_USER_MODEL = 'core.User'
+
+# Hosts/domain names that are valid for this site; required if DEBUG is False
+# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
+ALLOWED_HOSTS = []
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# In a Windows environment this must be set to your system time zone.
+TIME_ZONE = 'America/New_York'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+# If you set this to False, Django will not format dates, numbers and
+# calendars according to the current locale.
+USE_L10N = True
+
+# If you set this to False, Django will not use timezone-aware datetimes.
+USE_TZ = True
+
+# Absolute filesystem path to the directory that will hold user-uploaded files.
+# Example: "/var/www/example.com/media/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash.
+# Examples: "http://example.com/media/", "http://media.example.com/"
+MEDIA_URL = ''
+
+# Absolute path to the directory static files should be collected to.
+# Don't put anything in this directory yourself; store your static files
+# in apps' "static/" subdirectories and in STATICFILES_DIRS.
+# Example: "/var/www/example.com/static/"
+STATIC_ROOT = ''
+
+# URL prefix for static files.
+# Example: "http://example.com/static/", "http://static.example.com/"
+STATIC_URL = '/static/'
+
+# Additional locations of static files
+STATICFILES_DIRS = (
+ # Put strings here, like "/home/html/static" or "C:/www/django/static".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+)
+
+# List of finder classes that know how to find static files in
+# various locations.
+STATICFILES_FINDERS = (
+ 'django.contrib.staticfiles.finders.FileSystemFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
+)
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = 'i0=a)c7_#2)5m%k_fu#%53xap$tlqc+#&z5as+bl7&)(@be_f9'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.Loader',
+ 'django.template.loaders.app_directories.Loader',
+# 'django.template.loaders.eggs.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ # Uncomment the next line for simple clickjacking protection:
+ # 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+)
+
+ROOT_URLCONF = 'planetstack.urls'
+
+# Python dotted path to the WSGI application used by Django's runserver.
+WSGI_APPLICATION = 'planetstack.wsgi.application'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+ "/opt/planetstack/templates"
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+# 'django.contrib.sites',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ # Uncomment the next line to enable the admin:
+ 'django.contrib.admin',
+ # Uncomment the next line to enable admin documentation:
+ 'django.contrib.admindocs',
+ 'rest_framework',
+ 'django_extensions',
+ 'core',
+ 'django_evolution',
+)
+
+# A sample logging configuration. The only tangible logging
+# performed by this configuration is to send an email to
+# the site admins on every HTTP 500 error when DEBUG=False.
+# See http://docs.djangoproject.com/en/dev/topics/logging for
+# more details on how to customize your logging configuration.
+LOGGING = {
+ 'version': 1,
+ 'disable_existing_loggers': False,
+ 'filters': {
+ 'require_debug_false': {
+ '()': 'django.utils.log.RequireDebugFalse'
+ }
+ },
+ 'handlers': {
+ 'mail_admins': {
+ 'level': 'ERROR',
+ 'filters': ['require_debug_false'],
+ 'class': 'django.utils.log.AdminEmailHandler'
+ }
+ },
+ 'loggers': {
+ 'django.request': {
+ 'handlers': ['mail_admins'],
+ 'level': 'ERROR',
+ 'propagate': True,
+ },
+ }
+}
diff --git a/planetstack/planetstack/tests.py b/planetstack/planetstack/tests.py
new file mode 100644
index 0000000..501deb7
--- /dev/null
+++ b/planetstack/planetstack/tests.py
@@ -0,0 +1,16 @@
+"""
+This file demonstrates writing tests using the unittest module. These will pass
+when you run "manage.py test".
+
+Replace this with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.assertEqual(1 + 1, 2)
diff --git a/planetstack/planetstack/urls.py b/planetstack/planetstack/urls.py
new file mode 100644
index 0000000..58d9ce7
--- /dev/null
+++ b/planetstack/planetstack/urls.py
@@ -0,0 +1,75 @@
+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.subnets import SubnetListCreate, SubnetRetrieveUpdateDestroy
+from core.views.slivers import SliverListCreate, SliverRetrieveUpdateDestroy
+from core.views.keys import KeyListCreate, KeyRetrieveUpdateDestroy
+from core.views.deployment_networks import DeploymentNetworkListCreate, DeploymentNetworkRetrieveUpdateDestroy
+from core.views.images import ImageListCreate, ImageRetrieveUpdateDestroy
+from core.views.nodes import NodeListCreate, NodeRetrieveUpdateDestroy
+from core.models import Site
+from core.api_root import api_root
+from rest_framework import generics
+
+admin.autodiscover()
+
+urlpatterns = patterns('',
+ # Examples:
+ # url(r'^$', 'planetstack.views.home', name='home'),
+ # url(r'^planetstack/', include('planetstack.foo.urls')),
+
+ # Uncomment the admin/doc line below to enable admin documentation:
+ # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
+
+ # Uncomment the next line to enable the admin:
+ url(r'^admin/', include(admin.site.urls)),
+
+ url(r'^plstackapi/$', api_root),
+
+ url(r'^plstackapi/roles/$', RoleListCreate.as_view(), name='role-list'),
+ url(r'^plstackapi/roles/(?P<pk>[a-zA-Z0-9]+)/$', RoleRetrieveUpdateDestroy.as_view(), name='role-detail'),
+
+ url(r'^plstackapi/users/$', UserListCreate.as_view(), name='user-list'),
+ url(r'^plstackapi/users/(?P<pk>[a-zA-Z0-9_\-]+)/$', UserRetrieveUpdateDestroy.as_view(), name='user-detail'),
+
+ url(r'^plstackapi/keys/$', KeyListCreate.as_view(), name='key-list'),
+ url(r'^plstackapi/keys/(?P<pk>[a-zA-Z0-9_\-]+)/$', KeyRetrieveUpdateDestroy.as_view(), name='key-detail'),
+
+ url(r'^plstackapi/sites/$', SiteListCreate.as_view(), name='site-list'),
+ url(r'^plstackapi/sites/(?P<pk>[a-zA-Z0-9_\-]+)/$', SiteRetrieveUpdateDestroy.as_view(), name='site-detail'),
+
+ url(r'^plstackapi/site_privileges/$', SitePrivilegeListCreate.as_view(), name='siteprivilege-list'),
+ url(r'^plstackapi/site_privileges/(?P<pk>[a-zA-Z0-9_]+)/$', SitePrivilegeRetrieveUpdateDestroy.as_view(), name='siteprivilege-detail'),
+
+ url(r'^plstackapi/slices/$', SliceListCreate.as_view(), name='slice-list'),
+ url(r'^plstackapi/slices/(?P<pk>[a-zA-Z0-9_\-]+)/$', SliceRetrieveUpdateDestroy.as_view(), name='slice-detail'),
+
+ url(r'^plstackapi/slice_memberships/$', SliceMembershipListCreate.as_view(), name='slice_membership-list'),
+ url(r'^plstackapi/slice_memberships/(?P<pk>[0-9]+)/$', SliceMembershipRetrieveUpdateDestroy.as_view(), name='slice_membership-detail'),
+
+ url(r'^plstackapi/subnets/$', SubnetListCreate.as_view(), name='subnet-list'),
+ url(r'^plstackapi/subnets/(?P<pk>[a-zA-Z0-9_\-]+)/$', SubnetRetrieveUpdateDestroy.as_view(), name='subnet-detail'),
+
+ url(r'^plstackapi/slivers/$', SliverListCreate.as_view(), name='sliver-list'),
+ url(r'^plstackapi/slivers/(?P<pk>[a-zA-Z0-9_\-]+)/$', SliverRetrieveUpdateDestroy.as_view(), name='sliver-detail'),
+
+ url(r'^plstackapi/nodes/$', NodeListCreate.as_view(), name='node-list'),
+ url(r'^plstackapi/nodes/(?P<pk>[a-zA-Z0-9_\-]+)/$', NodeRetrieveUpdateDestroy.as_view(), name='node-detail'),
+
+ url(r'^plstackapi/deploymentnetworks/$', DeploymentNetworkListCreate.as_view(), name='deploymentnetwork-list'),
+ url(r'^plstackapi/deploymentnetworks/(?P<pk>[a-zA-Z0-9\-]+)/$', DeploymentNetworkRetrieveUpdateDestroy.as_view(), name='deploymentnetwork-detail'),
+
+ url(r'^plstackapi/images/$', ImageListCreate.as_view(), name='image-list'),
+ url(r'^plstackapi/images/(?P<pk>[a-zA-Z0-9_\-]+)/$', ImageRetrieveUpdateDestroy.as_view(), name='image-detail'),
+
+ #Adding in rest_framework urls
+ url(r'^plstackapi/', include('rest_framework.urls', namespace='rest_framework')),
+
+)
diff --git a/planetstack/planetstack/wsgi.py b/planetstack/planetstack/wsgi.py
new file mode 100644
index 0000000..6e03e11
--- /dev/null
+++ b/planetstack/planetstack/wsgi.py
@@ -0,0 +1,32 @@
+"""
+WSGI config for planetstack project.
+
+This module contains the WSGI application used by Django's development server
+and any production WSGI deployments. It should expose a module-level variable
+named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
+this application via the ``WSGI_APPLICATION`` setting.
+
+Usually you will have the standard Django WSGI application here, but it also
+might make sense to replace the whole Django WSGI application with a custom one
+that later delegates to the Django one. For example, you could introduce WSGI
+middleware here, or combine a Django application with an application of another
+framework.
+
+"""
+import os
+
+# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks
+# if running multiple sites in the same mod_wsgi process. To fix this, use
+# mod_wsgi daemon mode with each site in its own daemon process, or use
+# os.environ["DJANGO_SETTINGS_MODULE"] = "planetstack.settings"
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "planetstack.settings")
+
+# This application object is used by any WSGI server configured to use this
+# file. This includes Django's development server, if the WSGI_APPLICATION
+# setting points here.
+from django.core.wsgi import get_wsgi_application
+application = get_wsgi_application()
+
+# Apply WSGI middleware here.
+# from helloworld.wsgi import HelloWorldApplication
+# application = HelloWorldApplication(application)
diff --git a/planetstack/plstackapi-debug-server.py b/planetstack/plstackapi-debug-server.py
new file mode 100644
index 0000000..aa60119
--- /dev/null
+++ b/planetstack/plstackapi-debug-server.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+import os
+import sys
+import threading
+
+from planetstack.config import Config
+from openstack.sliveragent import SliverAgent
+from openstack.siteagent import SiteAgent
+
+if __name__ == '__main__':
+
+ # bootstrap envirnment
+ from django.core.management import ManagementUtility
+ config = Config()
+ url = "%s:%s" % (config.api_host, config.api_port)
+ args = [__file__, 'runserver', url]
+
+ # run site agent once on startup
+ SiteAgent().run()
+
+ # start the sliver agent thread
+ sliver_agent = SliverAgent()
+ sliver_agent_thread = threading.Thread(target=sliver_agent.run)
+ sliver_agent_thread.start()
+
+ # start the server
+ server = ManagementUtility(args)
+ server.execute()
diff --git a/planetstack/plstackapi_config b/planetstack/plstackapi_config
new file mode 100644
index 0000000..1d7b761
--- /dev/null
+++ b/planetstack/plstackapi_config
@@ -0,0 +1,29 @@
+[plc]
+name=plc
+
+[db]
+name=planetstack
+user=postgres
+password=password
+host=localhost
+port=5432
+
+[api]
+host=localhost
+port=8000
+ssl_key=None
+ssl_cert=None
+ca_ssl_cert=None
+ratelimit_enabled=0
+omf_enabled=0
+mail_support_address=support@localhost
+nova_enabled=True
+
+[nova]
+admin_user=admin@domain.com
+admin_password=admin
+admin_tenant=admin
+url=http://localhost:5000/v2.0/
+default_image=None
+default_flavor=m1.small
+default_security_group=default
diff --git a/planetstack/templates/admin/base_site.html b/planetstack/templates/admin/base_site.html
new file mode 100644
index 0000000..2bd6c82
--- /dev/null
+++ b/planetstack/templates/admin/base_site.html
@@ -0,0 +1,18 @@
+{% extends "admin/base.html" %}
+{% load i18n %}
+{% block title %}{{ title }} | {% trans 'PlanetStack' %}{% endblock %}
+{% block extrastyle %}
+<style>
+#header{ background-color: #333940; border-bottom: solid 3px #999; }
+
+#branding h1{ color: #fff; }
+.module h2, .module caption, .inline-group h2 { background:#ccc url(/admin_media/img/admin/nav-bg.gif) bottom left repeat-x; color: #333940; }
+a.section:link, a.section:visited { color: #666666; }
+</style>
+{% endblock %}
+
+{% block branding %}
+<h1 id="site-name">{% trans 'PlanetStack Administration' %}</h1>
+{% endblock %}
+
+{% block nav-global %}{% endblock %}
diff --git a/planetstack/templates/admin/core/SiteDeploymentNetwork/sitedepnettabular.html b/planetstack/templates/admin/core/SiteDeploymentNetwork/sitedepnettabular.html
new file mode 100644
index 0000000..71a62ba
--- /dev/null
+++ b/planetstack/templates/admin/core/SiteDeploymentNetwork/sitedepnettabular.html
@@ -0,0 +1,79 @@
+{% load i18n admin_static admin_modify %}
+<div class="inline-group" id="{{ inline_admin_formset.formset.prefix }}-group">
+ <div class="tabular inline-related {% if forloop.last %}last-related{% endif %}">
+{{ inline_admin_formset.formset.management_form }}
+<fieldset class="module">
+ <h2>{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}</h2>
+ {{ inline_admin_formset.formset.non_form_errors }}
+ <table>
+ <thead><tr>
+ {% for field in inline_admin_formset.fields %}
+ {% if not field.widget.is_hidden %}
+ <th{% if forloop.first %} colspan="2"{% endif %}{% if field.required %} class="required"{% endif %}>{{ field.label|capfirst }}
+ {% if field.help_text %} <img src="{% static "admin/img/icon-unknown.gif" %}" class="help help-tooltip" width="10" height="10" alt="({{ field.help_text|striptags }})" title="{{ field.help_text|striptags }}" />{% endif %}
+ </th>
+ {% endif %}
+ {% endfor %}
+ {% if inline_admin_formset.formset.can_delete %}<th>{% trans "Delete?" %}</th>{% endif %}
+ </tr></thead>
+
+ <tbody>
+ {% for inline_admin_form in inline_admin_formset %}
+ {% if inline_admin_form.form.non_field_errors %}
+ <tr><td colspan="{{ inline_admin_form|cell_count }}">{{ inline_admin_form.form.non_field_errors }}</td></tr>
+ {% endif %}
+ <tr class="form-row {% cycle "row1" "row2" %} {% if inline_admin_form.original or inline_admin_form.show_url %}has_original{% endif %}{% if forloop.last %} empty-form{% endif %}"
+ id="{{ inline_admin_formset.formset.prefix }}-{% if not forloop.last %}{{ forloop.counter0 }}{% else %}empty{% endif %}">
+ <td class="original">
+ {% if inline_admin_form.original or inline_admin_form.show_url %}<p>
+ {% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %}
+ {% if inline_admin_form.show_url %}<a href="{% url 'admin:view_on_site' inline_admin_form.original_content_type_id inline_admin_form.original.pk %}">{% trans "View on site" %}</a>{% endif %}
+ </p>{% endif %}
+ {% if inline_admin_form.has_auto_field %}{{ inline_admin_form.pk_field.field }}{% endif %}
+ {{ inline_admin_form.fk_field.field }}
+ {% spaceless %}
+ {% for fieldset in inline_admin_form %}
+ {% for line in fieldset %}
+ {% for field in line %}
+ {% if field.is_hidden %} {{ field.field }} {% endif %}
+ {% endfor %}
+ {% endfor %}
+ {% endfor %}
+ {% endspaceless %}
+ </td>
+ {% for fieldset in inline_admin_form %}
+ {% for line in fieldset %}
+ {% for field in line %}
+ <td{% if field.field.name %} class="field-{{ field.field.name }}"{% endif %}>
+ {% if field.is_readonly %}
+ <p>{{ field.contents|linebreaksbr }}</p>
+ {% else %}
+ {{ field.field.errors.as_ul }}
+ {{ field.field }}
+ {% endif %}
+ </td>
+ {% endfor %}
+ {% endfor %}
+ {% endfor %}
+ {% if inline_admin_formset.formset.can_delete %}
+ <td class="delete">{% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %}</td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+</fieldset>
+ </div>
+</div>
+
+<script type="text/javascript">
+
+(function($) {
+ $("#{{ inline_admin_formset.formset.prefix }}-group .tabular.inline-related tbody tr").tabularFormset({
+ prefix: "{{ inline_admin_formset.formset.prefix }}",
+ adminStaticPrefix: '{% static "admin/" %}',
+ addText: "{% blocktrans with inline_admin_formset.opts.verbose_name|title as verbose_name %}Add another {{ verbose_name }}{% endblocktrans %}",
+ deleteText: "{% trans 'Remove' %}"
+ });
+})(django.jQuery);
+</script>
diff --git a/planetstack/util/__init__.py b/planetstack/util/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/planetstack/util/__init__.py
diff --git a/planetstack/util/glob.py b/planetstack/util/glob.py
new file mode 100644
index 0000000..833ce35
--- /dev/null
+++ b/planetstack/util/glob.py
@@ -0,0 +1,8 @@
+import os, fnmatch
+
+def recursive_glob(treeroot, pattern):
+ results = []
+ for base, dirs, files in os.walk(treeroot):
+ goodfiles = fnmatch.filter(files, pattern)
+ results.extend(os.path.join(base, f) for f in goodfiles)
+ return results
diff --git a/planetstack/util/request.py b/planetstack/util/request.py
new file mode 100644
index 0000000..3769b19
--- /dev/null
+++ b/planetstack/util/request.py
@@ -0,0 +1,14 @@
+import ast
+from django.http.request import QueryDict
+
+def parse_request(request):
+ d = {}
+ if isinstance(request, unicode):
+ d = ast.literal_eval(request)
+ elif isinstance(request, QueryDict):
+ for (k,v) in request.items():
+ d[k] = ast.literal_eval(v)
+ elif isinstance(request, dict):
+ d = request
+
+ return d
diff --git a/planetstack/util/xml.py b/planetstack/util/xml.py
new file mode 100644
index 0000000..7b6c72a
--- /dev/null
+++ b/planetstack/util/xml.py
@@ -0,0 +1,347 @@
+#!/usr/bin/python
+from types import StringTypes
+from lxml import etree
+from StringIO import StringIO
+
+# helper functions to help build xpaths
+class XpathFilter:
+ @staticmethod
+
+ def filter_value(key, value):
+ xpath = ""
+ if isinstance(value, str):
+ if '*' in value:
+ value = value.replace('*', '')
+ xpath = 'contains(%s, "%s")' % (key, value)
+ else:
+ xpath = '%s="%s"' % (key, value)
+ return xpath
+
+ @staticmethod
+ def xpath(filter={}):
+ xpath = ""
+ if filter:
+ filter_list = []
+ for (key, value) in filter.items():
+ if key == 'text':
+ key = 'text()'
+ else:
+ key = '@'+key
+ if isinstance(value, str):
+ filter_list.append(XpathFilter.filter_value(key, value))
+ elif isinstance(value, list):
+ stmt = ' or '.join([XpathFilter.filter_value(key, str(val)) for val in value])
+ filter_list.append(stmt)
+ if filter_list:
+ xpath = ' and '.join(filter_list)
+ xpath = '[' + xpath + ']'
+ return xpath
+
+# a wrapper class around lxml.etree._Element
+# the reason why we need this one is because of the limitations
+# we've found in xpath to address documents with multiple namespaces defined
+# in a nutshell, we deal with xml documents that have
+# a default namespace defined (xmlns="http://default.com/") and specific prefixes defined
+# (xmlns:foo="http://foo.com")
+# according to the documentation instead of writing
+# element.xpath ( "//node/foo:subnode" )
+# we'd then need to write xpaths like
+# element.xpath ( "//{http://default.com/}node/{http://foo.com}subnode" )
+# which is a real pain..
+# So just so we can keep some reasonable programming style we need to manage the
+# namespace map that goes with the _Element (its internal .nsmap being unmutable)
+class XmlElement:
+ def __init__(self, element, namespaces):
+ self.element = element
+ self.namespaces = namespaces
+
+ # redefine as few methods as possible
+ def xpath(self, xpath, namespaces=None):
+ if not namespaces:
+ namespaces = self.namespaces
+ elems = self.element.xpath(xpath, namespaces=namespaces)
+ return [XmlElement(elem, namespaces) for elem in elems]
+
+ def add_element(self, tagname, **kwds):
+ element = etree.SubElement(self.element, tagname, **kwds)
+ return XmlElement(element, self.namespaces)
+
+ def append(self, elem):
+ if isinstance(elem, XmlElement):
+ self.element.append(elem.element)
+ else:
+ self.element.append(elem)
+
+ def getparent(self):
+ return XmlElement(self.element.getparent(), self.namespaces)
+
+ def get_instance(self, instance_class=None, fields=[]):
+ """
+ Returns an instance (dict) of this xml element. The instance
+ holds a reference to this xml element.
+ """
+ if not instance_class:
+ instance_class = Object
+ if not fields and hasattr(instance_class, 'fields'):
+ fields = instance_class.fields
+
+ if not fields:
+ instance = instance_class(self.attrib, self)
+ else:
+ instance = instance_class({}, self)
+ for field in fields:
+ if field in self.attrib:
+ instance[field] = self.attrib[field]
+ return instance
+
+ def add_instance(self, name, instance, fields=[]):
+ """
+ Adds the specifed instance(s) as a child element of this xml
+ element.
+ """
+ if not fields and hasattr(instance, 'keys'):
+ fields = instance.keys()
+ elem = self.add_element(name)
+ for field in fields:
+ if field in instance and instance[field]:
+ elem.set(field, unicode(instance[field]))
+ return elem
+
+ def remove_elements(self, name):
+ """
+ Removes all occurences of an element from the tree. Start at
+ specified root_node if specified, otherwise start at tree's root.
+ """
+
+ if not element_name.startswith('//'):
+ element_name = '//' + element_name
+ elements = self.element.xpath('%s ' % name, namespaces=self.namespaces)
+ for element in elements:
+ parent = element.getparent()
+ parent.remove(element)
+
+ def delete(self):
+ parent = self.getparent()
+ parent.remove(self)
+
+ def remove(self, element):
+ if isinstance(element, XmlElement):
+ self.element.remove(element.element)
+ else:
+ self.element.remove(element)
+
+ def set_text(self, text):
+ self.element.text = text
+
+ # Element does not have unset ?!?
+ def unset(self, key):
+ del self.element.attrib[key]
+
+ def toxml(self):
+ return etree.tostring(self.element, encoding='UTF-8', pretty_print=True)
+
+ def __str__(self):
+ return self.toxml()
+
+ # are redirected on self.element
+ def __getattr__ (self, name):
+ if not hasattr(self.element, name):
+ raise AttributeError, name
+ return getattr(self.element, name)
+
+class Xml:
+
+ def __init__(self, xml=None, namespaces=None):
+ self.root = None
+ self.namespaces = namespaces
+ self.default_namespace = None
+ self.schema = None
+ if isinstance(xml, basestring):
+ self.parse_xml(xml)
+ if isinstance(xml, XmlElement):
+ self.root = xml
+ self.namespaces = xml.namespaces
+ elif isinstance(xml, etree._ElementTree) or isinstance(xml, etree._Element):
+ self.parse_xml(etree.tostring(xml))
+
+ def parse_xml(self, xml):
+ """
+ parse rspec into etree
+ """
+ parser = etree.XMLParser(remove_blank_text=True)
+ try:
+ tree = etree.parse(xml, parser)
+ except IOError:
+ # 'rspec' file doesnt exist. 'rspec' is proably an xml string
+ try:
+ tree = etree.parse(StringIO(xml), parser)
+ except Exception, e:
+ raise Exception, str(e)
+ root = tree.getroot()
+ self.namespaces = dict(root.nsmap)
+ # set namespaces map
+ if 'default' not in self.namespaces and None in self.namespaces:
+ # If the 'None' exist, then it's pointing to the default namespace. This makes
+ # it hard for us to write xpath queries for the default naemspace because lxml
+ # wont understand a None prefix. We will just associate the default namespeace
+ # with a key named 'default'.
+ self.namespaces['default'] = self.namespaces.pop(None)
+
+ else:
+ self.namespaces['default'] = 'default'
+
+ self.root = XmlElement(root, self.namespaces)
+ # set schema
+ for key in self.root.attrib.keys():
+ if key.endswith('schemaLocation'):
+ # schemaLocation should be at the end of the list.
+ # Use list comprehension to filter out empty strings
+ schema_parts = [x for x in self.root.attrib[key].split(' ') if x]
+ self.schema = schema_parts[1]
+ namespace, schema = schema_parts[0], schema_parts[1]
+ break
+
+ def parse_dict(self, d, root_tag_name='xml', element = None):
+ if element is None:
+ if self.root is None:
+ self.parse_xml('<%s/>' % root_tag_name)
+ element = self.root.element
+
+ if 'text' in d:
+ text = d.pop('text')
+ element.text = text
+
+ # handle repeating fields
+ for (key, value) in d.items():
+ if isinstance(value, list):
+ value = d.pop(key)
+ for val in value:
+ if isinstance(val, dict):
+ child_element = etree.SubElement(element, key)
+ self.parse_dict(val, key, child_element)
+ elif isinstance(val, basestring):
+ child_element = etree.SubElement(element, key).text = val
+
+ elif isinstance(value, int):
+ d[key] = unicode(d[key])
+ elif value is None:
+ d.pop(key)
+
+ # element.attrib.update will explode if DateTimes are in the
+ # dcitionary.
+ d=d.copy()
+ # looks like iteritems won't stand side-effects
+ for k in d.keys():
+ if not isinstance(d[k],StringTypes):
+ del d[k]
+
+ element.attrib.update(d)
+
+ def validate(self, schema):
+ """
+ Validate against rng schema
+ """
+ relaxng_doc = etree.parse(schema)
+ relaxng = etree.RelaxNG(relaxng_doc)
+ if not relaxng(self.root):
+ error = relaxng.error_log.last_error
+ message = "%s (line %s)" % (error.message, error.line)
+ raise Exception, message
+ return True
+
+ def xpath(self, xpath, namespaces=None):
+ if not namespaces:
+ namespaces = self.namespaces
+ return self.root.xpath(xpath, namespaces=namespaces)
+
+ def set(self, key, value):
+ return self.root.set(key, value)
+
+ def remove_attribute(self, name, element=None):
+ if not element:
+ element = self.root
+ element.remove_attribute(name)
+
+ def add_element(self, *args, **kwds):
+ """
+ Wrapper around etree.SubElement(). Adds an element to
+ specified parent node. Adds element to root node is parent is
+ not specified.
+ """
+ return self.root.add_element(*args, **kwds)
+
+ def remove_elements(self, name, element = None):
+ """
+ Removes all occurences of an element from the tree. Start at
+ specified root_node if specified, otherwise start at tree's root.
+ """
+ if not element:
+ element = self.root
+
+ element.remove_elements(name)
+
+ def add_instance(self, *args, **kwds):
+ return self.root.add_instance(*args, **kwds)
+
+ def get_instance(self, *args, **kwds):
+ return self.root.get_instnace(*args, **kwds)
+
+ def get_element_attributes(self, elem=None, depth=0):
+ if elem == None:
+ elem = self.root
+ if not hasattr(elem, 'attrib'):
+ # this is probably not an element node with attribute. could be just and an
+ # attribute, return it
+ return elem
+ attrs = dict(elem.attrib)
+ attrs['text'] = str(elem.text).strip()
+ attrs['parent'] = elem.getparent()
+ if isinstance(depth, int) and depth > 0:
+ for child_elem in list(elem):
+ key = str(child_elem.tag)
+ if key not in attrs:
+ attrs[key] = [self.get_element_attributes(child_elem, depth-1)]
+ else:
+ attrs[key].append(self.get_element_attributes(child_elem, depth-1))
+ else:
+ attrs['child_nodes'] = list(elem)
+ return attrs
+
+ def append(self, elem):
+ return self.root.append(elem)
+
+ def iterchildren(self):
+ return self.root.iterchildren()
+
+ def merge(self, in_xml):
+ pass
+
+ def __str__(self):
+ return self.toxml()
+
+ def toxml(self):
+ return etree.tostring(self.root.element, encoding='UTF-8', pretty_print=True)
+
+ # XXX smbaker, for record.load_from_string
+ def todict(self, elem=None):
+ if elem is None:
+ elem = self.root
+ d = {}
+ d.update(elem.attrib)
+ d['text'] = elem.text
+ for child in elem.iterchildren():
+ if child.tag not in d:
+ d[child.tag] = []
+ d[child.tag].append(self.todict(child))
+
+ if len(d)==1 and ("text" in d):
+ d = d["text"]
+
+ return d
+
+ def save(self, filename):
+ f = open(filename, 'w')
+ f.write(self.toxml())
+ f.close()
+
+