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 %}&nbsp;<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()
+
+