observer now supports remote openstack deployments
Tony Mack [Wed, 12 Mar 2014 00:45:25 +0000 (20:45 -0400)]
24 files changed:
planetstack/core/admin.py
planetstack/core/models/__init__.py
planetstack/core/models/site.py
planetstack/core/models/slice.py
planetstack/core/models/sliver.py
planetstack/core/models/user.py
planetstack/deployment_auth.py [new file with mode: 0644]
planetstack/observer/deleters/site_deleter.py
planetstack/observer/steps/garbage_collector.py
planetstack/observer/steps/sync_network_slivers.py
planetstack/observer/steps/sync_nodes.py
planetstack/observer/steps/sync_site_deployments.py [new file with mode: 0644]
planetstack/observer/steps/sync_site_privileges.py
planetstack/observer/steps/sync_sites.py
planetstack/observer/steps/sync_slice_deployments.py [new file with mode: 0644]
planetstack/observer/steps/sync_slice_memberships.py
planetstack/observer/steps/sync_slices.py
planetstack/observer/steps/sync_sliver_ips.py
planetstack/observer/steps/sync_slivers.py
planetstack/observer/steps/sync_user_deployments.py [new file with mode: 0644]
planetstack/observer/steps/sync_users.py
planetstack/openstack/client.py
planetstack/openstack/driver.py
setup.py

index 853e5ce..996e22d 100644 (file)
@@ -345,6 +345,32 @@ class SitePrivilegeInline(PlStackTabularInline):
     def queryset(self, request):
         return SitePrivilege.select_by_user(request.user)
 
+class SiteDeploymentROInline(ReadOnlyTabularInline):
+    model = SiteDeployments
+    #model = Site.deployments.through
+    extra = 0
+    suit_classes = 'suit-tab suit-tab-sitedeployments'
+    fields = ['deployment','site']
+
+class SiteDeploymentInline(PlStackTabularInline):
+    model = SiteDeployments
+    #model = Site.deployments.through
+    extra = 0
+    suit_classes = 'suit-tab suit-tab-deployments'
+    fields = ['deployment','site']
+
+    def formfield_for_foreignkey(self, db_field, request, **kwargs):
+        if db_field.name == 'site':
+            kwargs['queryset'] = Site.select_by_user(request.user)
+
+        if db_field.name == 'deployment':
+            kwargs['queryset'] = Deployment.select_by_user(request.user)
+        return super(SiteDeploymentInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
+
+    def queryset(self, request):
+        return SiteDeployments.select_by_user(request.user)
+
+
 class SlicePrivilegeROInline(ReadOnlyTabularInline):
     model = SlicePrivilege
     extra = 0
@@ -492,7 +518,7 @@ class SiteAdmin(PlanetStackBaseAdmin):
     fieldList = ['name', 'site_url', 'enabled', 'is_public', 'login_base', 'accountLink','location']
     fieldsets = [
         (None, {'fields': fieldList, 'classes':['suit-tab suit-tab-general']}),
-        ('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
+        #('Deployment Networks', {'fields': ['deployments'], 'classes':['suit-tab suit-tab-deployments']}),
     ]
     suit_form_tabs =(('general', 'Site Details'),
         ('users','Users'),
@@ -505,11 +531,11 @@ class SiteAdmin(PlanetStackBaseAdmin):
     readonly_fields = ['accountLink']
 
     user_readonly_fields = ['name', 'deployments','site_url', 'enabled', 'is_public', 'login_base', 'accountLink']
-    user_readonly_inlines = [SliceROInline,UserROInline,TagROInline, NodeROInline, SitePrivilegeROInline]
+    user_readonly_inlines = [SliceROInline,UserROInline,TagROInline, NodeROInline, SitePrivilegeROInline,SiteDeploymentROInline]
 
     list_display = ('name', 'login_base','site_url', 'enabled')
     filter_horizontal = ('deployments',)
-    inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline]
+    inlines = [SliceInline,UserInline,TagInline, NodeInline, SitePrivilegeInline, SiteDeploymentInline]
     search_fields = ['name']
 
     def queryset(self, request):
index 6926d90..7c10fc8 100644 (file)
@@ -8,10 +8,10 @@ from .tag import Tag
 from .role import Role
 #from .deployment import Deployment
 from .site import Site,Deployment, DeploymentRole, DeploymentPrivilege, SiteDeployments
-from .user import User
+from .user import User, UserDeployments
 from .serviceclass import ServiceClass
-from .slice import Slice
-from .site import SitePrivilege
+from .slice import Slice, SliceDeployments
+from .site import SitePrivilege, SiteDeployments
 from .image import Image
 from .node import Node
 from .serviceresource import ServiceResource
index edd9373..4256e75 100644 (file)
@@ -21,8 +21,8 @@ class Site(PlCoreBase):
     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('Deployment', blank=True, related_name='sites')
-    #deployments = models.ManyToManyField('Deployment', through='SiteDeployments', blank=True)
+    #deployments = models.ManyToManyField('Deployment', blank=True, related_name='sites')
+    deployments = models.ManyToManyField('Deployment', through='SiteDeployments', blank=True)
     tags = generic.GenericRelation(Tag)
 
     def __unicode__(self):  return u'%s' % (self.name)
@@ -88,6 +88,9 @@ class Deployment(PlCoreBase):
 
     def __unicode__(self):  return u'%s' % (self.name)
 
+    @staticmethod
+    def select_by_user(user):
+        return Deployment.objects.all()
 
 class DeploymentRole(PlCoreBase):
 
@@ -127,6 +130,11 @@ class DeploymentPrivilege(PlCoreBase):
 class SiteDeployments(PlCoreBase):
     site = models.ForeignKey(Site)
     deployment = models.ForeignKey(Deployment)
+    tenant_id = models.CharField(null=True, blank=True, max_length=200, help_text="Keystone tenant id")    
+
+    @staticmethod
+    def select_by_user(user):
+        return SiteDeployments.objects.all()
 
     #class Meta:
     #    db_table = 'core_site_deployments'
index c39d09b..15d317f 100644 (file)
@@ -9,6 +9,7 @@ from core.models import ServiceClass
 from core.models import Tag
 from django.contrib.contenttypes import generic
 from core.models import Service
+from core.models import Deployment
 
 # Create your models here.
 
@@ -87,3 +88,22 @@ class SlicePrivilege(PlCoreBase):
             sp_ids = [sp.id for sp in SlicePrivilege.objects.filter(user=user)]
             qs = SlicePrivilege.objects.filter(id__in=sp_ids)
         return qs
+
+class SliceDeployments(PlCoreBase):
+    slice = models.ForeignKey(Slice)
+    deployment = models.ForeignKey(Deployment)
+    tenant_id = models.CharField(max_length=200, help_text="Keystone tenant id")
+    network_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum network")
+    router_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum router id")
+    subnet_id = models.CharField(null=True, blank=True, max_length=256, help_text="Quantum subnet id")
+
+    def __unicode__(self):  return u'%s %s %s' % (self.slice, self.deployment)
+
+    @staticmethod
+    def select_by_user(user):
+        if user.is_admin:
+            qs = SliceDeployments.objects.all()
+        else:
+            slices = Slice.select_by_user(user)
+            qs = SliceDeployments.objects.filter(slice__in=slices)
+        return qs    
index 29ae0d4..ce46cac 100644 (file)
@@ -52,6 +52,6 @@ class Sliver(PlCoreBase):
         if user.is_admin:
             qs = Sliver.objects.all()
         else:
-            slice_ids = [s.id for s in Slice.select_by_user(user)]
-            qs = Sliver.objects.filter(id__in=slice_ids)
+            slices = Slice.select_by_user(user)
+            qs = Sliver.objects.filter(slice__in=slices)
         return qs
index 1afb5fc..62e5b91 100644 (file)
@@ -4,6 +4,7 @@ from collections import defaultdict
 from django.db import models
 from django.db.models import F, Q
 from core.models import PlCoreBase,Site
+from core.models.deployment import Deployment
 from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
 from timezones.fields import TimeZoneField
 
@@ -149,4 +150,19 @@ class User(AbstractBaseUser):
         return qs            
 
              
-     
+    
+class UserDeployments(PlCoreBase):
+    user = models.ForeignKey(User)
+    deployment = models.ForeignKey(Deployment)
+    kuser_id = models.CharField(null=True, blank=True, max_length=200, help_text="Keystone user id")
+
+    def __unicode__(self):  return u'%s %s %s' % (self.user, self.deployment.name)
+
+    @staticmethod
+    def select_by_user(user):
+        if user.is_admin:
+            qs = UserDeployments.objects.all()
+        else:
+            users = Users.select_by_user(user)
+            qs = Usereployments.objects.filter(user__in=slices)
+        return qs 
diff --git a/planetstack/deployment_auth.py b/planetstack/deployment_auth.py
new file mode 100644 (file)
index 0000000..f7383ea
--- /dev/null
@@ -0,0 +1,25 @@
+## 
+# This file contains the auth credentials used to access openstack deployments 
+# we wish to manage. The 'default' credentials will be used for any deployments
+# not specifed here.
+#
+
+deployment_auth = {
+# Example
+#   'deployment_name': {
+#       'user': 'email@domain.com',
+#       'pasword': 'password',
+#       'tenant': 'tenant',    
+#       'url': 'http://localhost:5000/v2.0/',
+#       'token': 'ADMIN',
+#       'endpoint': 'http://localhost:35357/v2.0/'    
+#    }, 
+    
+    'default': {
+        'user': 'admin@domain.com',
+        'password': 'admin',
+        'tenant': 'admin', 
+        'url': 'http://localhost:5000/v2.0/'     
+    },
+
+}
index bb29c94..a558eaf 100644 (file)
@@ -1,5 +1,5 @@
 from core.models import Site
-from observer.delete import Deleter
+from observer.deleter import Deleter
 
 class SiteDeleter(Deleter):
     model='Site'
index a13fd22..34ecd77 100644 (file)
@@ -6,6 +6,7 @@ from django.db.models import F, Q
 from planetstack.config import Config
 from util.logger import Logger, logging
 from observer.openstacksyncstep import OpenStackSyncStep
+from deployment_auth import deployment_auth
 from core.models import *
 
 logger = Logger(level=logging.INFO)
@@ -69,17 +70,20 @@ class GarbageCollector(OpenStackSyncStep):
             slice_dict[slice.name] = slice
 
         # delete keystone tenants that don't have a site record
-        tenants = self.driver.shell.keystone.tenants.findall()
-        system_tenants = ['admin','service', 'invisible_to_admin']
-        for tenant in tenants:
-            if tenant.name in system_tenants: 
-                continue
-            if tenant.name not in site_dict and tenant.name not in slice_dict:
-                try:
-                    self.driver.delete_tenant(tenant.id)
-                    logger.info("deleted tenant: %s" % (tenant))
-                except:
-                    logger.log_exc("delete tenant failed: %s" % tenant)
+        for deployment in deployment_auth:
+            driver = self.driver.admin_driver(deployment=deployment)
+            tenants = driver.shell.keystone.tenants.findall()
+
+            system_tenants = ['admin','service', 'invisible_to_admin']
+            for tenant in tenants:
+                if tenant.name in system_tenants: 
+                    continue
+                if tenant.name not in site_dict and tenant.name not in slice_dict:
+                    try:
+                        driver.delete_tenant(tenant.id)
+                        logger.info("deleted tenant: %s" % (tenant))
+                    except:
+                        logger.log_exc("delete tenant failed: %s" % tenant)
 
 
     def gc_users(self):
@@ -96,16 +100,18 @@ class GarbageCollector(OpenStackSyncStep):
 
         # delete keystone users that don't have a user record
         system_users = ['admin', 'nova', 'quantum', 'glance', 'cinder', 'swift', 'service', 'demo']
-        users = self.driver.shell.keystone.users.findall()
-        for user in users:
-            if user.name in system_users:
-                continue
-            if user.id not in user_dict:
-                try:
-                    self.driver.delete_user(user.id)
-                    logger.info("deleted user: %s" % user)
-                except:
-                    logger.log_exc("delete user failed: %s" % user)
+        for deployment in deployment_auth:
+            driver = self.driver.admin_driver(deployment=deployment)
+            users = driver.shell.keystone.users.findall()
+            for user in users:
+                if user.name in system_users:
+                    continue
+                if user.id not in user_dict:
+                    try:
+                        self.driver.delete_user(user.id)
+                        logger.info("deleted user: %s" % user)
+                    except:
+                        logger.log_exc("delete user failed: %s" % user)
                     
 
     def gc_user_tenant_roles(self):
@@ -126,39 +132,43 @@ class GarbageCollector(OpenStackSyncStep):
         # 2. Never remove a user's role at a slice they've created.
         # Keep track of all roles that must be preserved.     
         users = User.objects.all()
-        preserved_roles = {}
-        for user in users:
-            tenant_ids = [s['tenant_id'] for s in user.slices.values()]
-            if user.site:
-                tenant_ids.append(user.site.tenant_id) 
-            preserved_roles[user.kuser_id] = tenant_ids
+        for deployment in deployment_auth:
+            driver = self.driver.admin_driver(deployment=deployment)
+            tenants = driver.shell.keystone.tenants.list() 
+            for user in users:
+                # skip admin roles
+                if user.kuser_id == self.driver.admin_user.id:
+                    continue
+     
+                ignore_tenant_ids = []
+                k_user = driver.shell.keystone.users.find(id=user.kuser_id)
+                ignore_tenant_ids = [s['tenant_id'] for s in user.slices.values()]
+                if user.site:
+                    ignore_tenant_ids.append(user.site.tenant_id) 
 
-        # begin removing user tenant roles from keystone. This is stored in the 
-        # Metadata table.
-        for metadata in self.driver.shell.keystone_db.get_metadata():
-            # skip admin roles
-            if metadata.user_id == self.driver.admin_user.id:
-                continue
-            # skip preserved tenant ids
-            if metadata.user_id in preserved_roles and \
-               metadata.tenant_id in preserved_roles[metadata.user_id]: 
-                continue           
-            # get roles for user at this tenant
-            user_tenant_role_ids = user_tenant_roles.get((metadata.user_id, metadata.tenant_id), [])
-
-            if user_tenant_role_ids:
-                # The user has roles at the tenant. Check if roles need to 
-                # be updated.
-                user_keystone_role_ids = metadata.data.get('roles', [])
-                for role_id in user_keystone_role_ids:
-                    if role_id not in user_tenant_role_ids: 
-                        user_keystone_role_ids.pop(user_keystone_role_ids.index(role_id))
-            else:
-                # The user has no roles at this tenant. 
-                metadata.data['roles'] = [] 
-            #session.add(metadata)
-            logger.info("pruning metadata for %s at %s" % (metadata.user_id, metadata.tenant_id))
+                # get user roles in keystone
+                for tenant in tenants:
+                    # skip preserved tenant ids
+                    if tenant.tenant_id in ignore_tenant_ids: 
+                        continue          
+                    # compare user tenant roles
+                    user_tenant_role_ids = user_tenant_roles.get((user.kuser_id, tenant.id), [])
+
+                    if user_tenant_role_ids:
+                        # The user has roles at the tenant. Check if roles need to 
+                        # be updated.
+                        k_user_roles =  driver.shell.keystone.roles.roles_for_user(k_user, tenant)
+                        for k_user_role in k_user_roles:
+                            if k_user_role.role_id not in user_tenant_role_ids: 
+                                driver.shell.keyston.remove_user_role(k_user, k_user_role, tenant) 
+                                logger.info("removed user role %s for %s at %s" % \
+                                           (k_user_role, k_user.username, tenant.name))
+                    else:
+                        # remove all roles the user has at the tenant. 
+                        for k_user_role in k_user_roles:
+                            driver.shell.keyston.remove_user_role(k_user, k_user_role, tenant) 
+                            logger.info("removed user role %s for %s at %s" % \
+                                       (k_user_role, k_user.username, tenant.name))
  
     def gc_slivers(self):
         """
@@ -172,20 +182,17 @@ class GarbageCollector(OpenStackSyncStep):
         for sliver in slivers:
             sliver_dict[sliver.instance_id] = sliver
 
-        # delete sliver that don't have a sliver record
-        ctx = self.driver.shell.nova_db.ctx 
-        instances = self.driver.shell.nova_db.instance_get_all(ctx)
-        for instance in instances:
-            if instance.uuid not in sliver_dict:
-                try:
-                    # lookup tenant and update context  
-                    tenant = self.driver.shell.keystone.tenants.find(id=instance.project_id)
-                    driver = self.driver.client_driver(tenant=tenant.name) 
-                    driver.destroy_instance(instance.uuid)
-                    logger.info("destroyed sliver: %s" % (instance))
-                except:
-                    logger.log_exc("destroy sliver failed: %s" % instance) 
-                
+        for tenant in self.driver.shell.keystone.tenants.list():
+            # delete sliver that don't have a sliver record
+            tenant_driver = self.driver.client_driver(tenant=tenant.name, deployment=sliver.node.deployment)
+            for instance in tenant_driver.nova.servers.list():
+                if instance.uuid not in sliver_dict:
+                    try:
+                        tenant_driver.destroy_instance(instance.uuid)
+                        logger.info("destroyed sliver: %s" % (instance))
+                    except:
+                        logger.log_exc("destroy sliver failed: %s" % instance)
+               
 
     def gc_sliver_ips(self):
         """
@@ -195,7 +202,8 @@ class GarbageCollector(OpenStackSyncStep):
         slivers = Sliver.objects.filter(ip=None)
         for sliver in slivers:
             # update connection
-            driver = self.driver.client_driver(tenant=sliver.slice.name)
+            
+            driver = self.driver.client_driver(tenant=sliver.slice.name, deployment=sliver.node.deployment)
             servers = driver.shell.nova.servers.findall(id=sliver.instance_id)
             if not servers:
                 continue
@@ -217,10 +225,12 @@ class GarbageCollector(OpenStackSyncStep):
             nodes_dict[node.name] = node
 
         # 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
+        for deployment in deployment_auth:
+            driver = self.driver.admin_driver(deployment=deployment) 
+            compute_nodes = driver.nova.hypervisors.list()
+            for compute_node in compute_nodes:
+                compute_nodes_dict[compute_node.hypervisor_hostname] = compute_node
 
         # remove old nodes
         old_node_names = set(nodes_dict.keys()).difference(compute_nodes_dict.keys())
@@ -234,10 +244,12 @@ class GarbageCollector(OpenStackSyncStep):
             images_dict[image.name] = image
 
         # collect glance images
-        glance_images = self.driver.shell.glance.get_images()
         glance_images_dict = {}
-        for glance_image in glance_images:
-            glance_images_dict[glance_image['name']] = glance_image
+        for deployment in deployment_auth:
+            driver = self.driver.admin_driver(deployment=deployment)
+            glance_images = driver.shell.glance.get_images()
+            for glance_image in glance_images:
+                glance_images_dict[glance_image['name']] = glance_image
 
         # remove old images
         old_image_names = set(images_dict.keys()).difference(glance_images_dict.keys())
index 09dc7ed..7e69330 100644 (file)
@@ -30,7 +30,8 @@ class SyncNetworkSlivers(OpenStackSyncStep):
         for sliver in slivers:
             slivers_by_instance_id[sliver.instance_id] = sliver
 
-        ports = self.driver.shell.quantum.list_ports()["ports"]
+        driver = self.driver.admin_driver(caller=sliver.creator, tenant=sliver.slice.name, deployment=sliver.node.deployment.name)
+        ports = driver.shell.quantum.list_ports()["ports"]
         for port in ports:
             if port["id"] in networkSlivers_by_port:
                 # we already have it
index a1f0803..bef0ca0 100644 (file)
@@ -14,28 +14,26 @@ class SyncNodes(OpenStackSyncStep):
     requested_interval=0
 
     def fetch_pending(self):
-        config = Config()
-        deployment = Deployment.objects.filter(name=config.plc_deployment)[0]
-        login_bases = ['princeton', 'stanford', 'gt', 'uw', 'mpisws']
-        sites = Site.objects.filter(login_base__in=login_bases)
-        
         # collect local nodes
+        sites = Site.objects.all()
         nodes = Node.objects.all()
         node_hostnames  = [node.name for node in nodes]
 
-        # collect nova nodes
-        # generate list of new nodes
-        new_nodes = []
-        compute_nodes = self.driver.shell.nova.hypervisors.list()
-        for compute_node in compute_nodes:
+        # fetch all nodes from each deployment 
+        deployments = Deployment.objects.all()
+        for deployment in deployments:
+            driver = self.driver.admin_driver(deployment=deployment.name)
+            compute_nodes = driver.shell.nova.hypervisors.list()
             if compute_node.hypervisor_hostname not in node_hostnames:
-                # pick a random site to add the node to for now
+                # XX TODO:figure out how to correctly identify a node's site.
+                # XX pick a random site to add the node to for now
                 site_index = random.randint(0, len(sites))
-                node = Node(name=compute_node.hypervisor_hostname, 
+                node = Node(name=compute_node.hypervisor_hostname,
                             site=sites[site_index], deployment=deployment)
-                new_nodes.append(node) 
-        
-        return new_nodes
+                new_nodes.append(node)
+
+        return new_nodes    
+                 
 
     def sync_record(self, node):
         node.save()
diff --git a/planetstack/observer/steps/sync_site_deployments.py b/planetstack/observer/steps/sync_site_deployments.py
new file mode 100644 (file)
index 0000000..a996c85
--- /dev/null
@@ -0,0 +1,28 @@
+import os
+import base64
+from django.db.models import F, Q
+from planetstack.config import Config
+from observer.openstacksyncstep import OpenStackSyncStep
+from core.models.site import *
+
+class SyncSiteDeployments(OpenStackSyncStep):
+    requested_interval=0
+    provides=[Site, SiteDeployments]
+
+    def fetch_pending(self):
+        return SiteDeployments.objects.filter(Q(enacted__lt=F('updated')) | Q(enacted=None))
+
+    def sync_record(self, site_deployment):
+        if not site_deployment.tenant_id:
+            driver = self.driver.admin_driver(deployment=site_deployment.deployment.name)
+            tenant = driver.create_tenant(tenant_name=site_deployment.site.login_base,
+                                               description=site_deployment.site.name,
+                                               enabled=site_deployment.site.enabled)
+            site_deployment.tenant_id = tenant.id
+            site_deployment.save()
+        elif site_deployment.site.id and site_deployment.tenant_id:
+            driver = self.driver.admin_driver(deployment=site_deployment.name)
+            driver.update_tenant(site_deployment.tenant_id,
+                                 description=site_deployment.site.name,
+                                 enabled=site_deployment.site.enabled)
+            
index 8287d44..922f579 100644 (file)
@@ -4,6 +4,7 @@ from django.db.models import F, Q
 from planetstack.config import Config
 from observer.openstacksyncstep import OpenStackSyncStep
 from core.models.site import *
+from core.models.user import UserDeployments    
 
 class SyncSitePrivileges(OpenStackSyncStep):
     requested_interval=0
@@ -17,3 +18,14 @@ class SyncSitePrivileges(OpenStackSyncStep):
             self.driver.add_user_role(site_priv.user.kuser_id,
                                       site_priv.site.tenant_id,
                                       site_priv.role.role) 
+
+        # sync site privileges at all site deployments
+        site_deployments = SiteDeployments.objects.filter(site=site_priv.site)
+        for site_deployment in site_deployments:
+            user_deployments = UserDeployments.objects.filter(deployment=site_deployment.deployment)
+            if user_deployments:
+                kuser_id  = user_deployments[0].kuser_id
+                driver = self.driver.admin_driver(deployment=site_deployment.name)
+                driver.add_user_role(kuser_id,
+                                     site_deployment.tenant_id,
+                                     slice_memb.role.role)
index 2013c6d..e128e9a 100644 (file)
@@ -13,22 +13,5 @@ class SyncSites(OpenStackSyncStep):
         return Site.objects.filter(Q(enacted__lt=F('updated')) | Q(enacted=None))
 
     def sync_record(self, site):
-        save_site = False
-        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
-            save_site = True
-            # XXX - What's caller?
-            # 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)
-
-        if (save_site):
-            site.save() # 
+        site.save()
 
diff --git a/planetstack/observer/steps/sync_slice_deployments.py b/planetstack/observer/steps/sync_slice_deployments.py
new file mode 100644 (file)
index 0000000..0f92796
--- /dev/null
@@ -0,0 +1,105 @@
+import os
+import base64
+from collections import defaultdict
+from netaddr import IPAddress, IPNetwork
+from django.db.models import F, Q
+from planetstack.config import Config
+from observer.openstacksyncstep import OpenStackSyncStep
+from core.models.site import SiteDeployments
+from core.models.slice import Slice, SliceDeployments
+from util.logger import Logger, logging
+
+logger = Logger(level=logging.INFO)
+
+class SyncSliceDeployments(OpenStackSyncStep):
+    provides=[Slice, SliceDeployments]
+    requested_interval=0
+
+    def fetch_pending(self):
+        # slice deployments are not visible to users. We must ensure
+        # slices are deployed at all deploymets available to their site.
+        site_deployments = SiteDeployment.objects.all()
+        site_deploy_lookup = defaultdict(list)
+        for site_deployment in site_deployments:
+            site_deploy_lookup[site_deployment.site].append(site_deployment.deployment)
+        
+        slice_deployments = SliceDeployment.objects.all()
+        slice_deploy_lookup = defaultdict(list)
+        for slice_deployment in slice_deployments:
+            slice_deploy_lookup[slice_deployment.slice].append(slice_deployment.deployment)
+        
+        for slice in Slice.objects.all():
+            expected_deployments = site_deploy_lookup[slice.site]
+            for expected_deployment in expected_deployments:
+                if slice not in slice_deploy_lookup or \
+                   expected_deployment not in slice_deploy_lookup[slice]:
+                    sd = SliceDeployments(slice=slice, deployment=expected_deployment)
+                    sd.save()
+
+        # now we can return all slice deployments that need to be enacted   
+        return SliceDeployments.objects.filter(Q(enacted__lt=F('updated')) | Q(enacted=None))
+
+    def get_next_subnet(self, deployment=None):
+        # limit ourself to 10.0.x.x for now
+        valid_subnet = lambda net: net.startswith('10.0')
+        driver = self.driver.admin_driver(deployment=deployment)
+        subnets = driver.shell.quantum.list_subnets()['subnets']
+        ints = [int(IPNetwork(subnet['cidr']).ip) for subnet in subnets \
+                if valid_subnet(subnet['cidr'])]
+        ints.sort()
+        last_ip = IPAddress(ints[-1])
+        last_network = IPNetwork(str(last_ip) + "/24")
+        next_network = IPNetwork(str(IPAddress(last_network) + last_network.size) + "/24")
+        return next_network
+
+    def sync_record(self, slice_deployment):
+        logger.info("sync'ing slice deployment %s" % slice_deployment)
+        if not slice_deployment.tenant_id:
+            nova_fields = {'tenant_name': slice_deployment.slice.name,
+                   'description': slice_deployment.slice.description,
+                   'enabled': slice_deployment.slice.enabled}
+            driver = self.driver.admin_driver(deployment=slice_deployment.deployment.name)
+            tenant = driver.create_tenant(**nova_fields)
+            slice_deployment.tenant_id = tenant.id
+
+            # XXX give caller an admin role at the tenant they've created
+            driver.add_user_role(slice_deployment.slice.creator.kuser_id, tenant.id, 'admin')
+
+            # refresh credentials using this tenant
+            client_driver = self.driver.client_driver(tenant=tenant.name, 
+                                                      deployment=slice_deployment.deployment.name)
+
+            # create network
+            network = client_driver.create_network(slice.name)
+            slice_deployment.network_id = network['id']
+
+            # create router
+            router = client_driver.create_router(slice.name)
+            slice_deployment.router_id = router['id']
+
+            # create subnet for slice's private network
+            next_subnet = self.get_next_subnet(deployment=slice_deployment.deployment.name)
+            cidr = str(next_subnet.cidr)
+            ip_version = next_subnet.version
+            start = str(next_subnet[2])
+            end = str(next_subnet[-2]) 
+            subnet = client_driver.create_subnet(name=slice.name,
+                                               network_id = network['id'],
+                                               cidr_ip = cidr,
+                                               ip_version = ip_version,
+                                               start = start,
+                                               end = end)
+            slice_deployment.subnet_id = subnet['id']
+            # add subnet as interface to slice's router
+            client_driver.add_router_interface(router['id'], subnet['id'])
+            # add external route
+            client_driver.add_external_route(subnet)
+
+
+        if slice_deployment.id and slice_deployment.tenant_id:
+            driver = self.driver.admin_driver(deployment=slice_deployment.deployment.name)
+            driver.update_tenant(slice_deployment.tenant_id,
+                                 description=slice_deployment.slice.description,
+                                 enabled=slice_deployment.slice.enabled)   
+
+        slice_deployment.save()
index ffc6eb8..6def794 100644 (file)
@@ -4,6 +4,7 @@ from django.db.models import F, Q
 from planetstack.config import Config
 from observer.openstacksyncstep import OpenStackSyncStep
 from core.models.slice import *
+from core.models.user import UserDeployments
 
 class SyncSliceMemberships(OpenStackSyncStep):
     requested_interval=0
@@ -17,3 +18,14 @@ class SyncSliceMemberships(OpenStackSyncStep):
                 self.driver.add_user_role(slice_memb.user.kuser_id,
                                           slice_memb.slice.tenant_id,
                                           slice_memb.role.role)
+
+        # sync slice memberships at all slice deployments 
+        slice_deployments = SliceDeployments.objects.filter(slice=slice_memb.slice)
+        for slice_deployment in slice_deployments:
+            user_deployments = UserDeployments.objects.filter(deployment=slice_deployment.deployment)
+            if user_deployments:
+                kuser_id  = user_deployments[0].kuser_id
+                driver = self.driver.admin_driver(deployment=slice_deployment.name)
+                driver.add_user_role(kuser_id,
+                                     slice_deployment.tenant_id,
+                                     slice_memb.role.role)
index f91c0fc..cc1220c 100644 (file)
@@ -16,63 +16,5 @@ class SyncSlices(OpenStackSyncStep):
     def fetch_pending(self):
         return Slice.objects.filter(Q(enacted__lt=F('updated')) | Q(enacted=None))
 
-    def get_next_subnet(self):
-        # limit ourself to 10.0.x.x for now
-        valid_subnet = lambda net: net.startswith('10.0')
-        subnets = self.driver.shell.quantum.list_subnets()['subnets']
-        ints = [int(IPNetwork(subnet['cidr']).ip) for subnet in subnets \
-                if valid_subnet(subnet['cidr'])]
-        ints.sort()
-        last_ip = IPAddress(ints[-1])
-        last_network = IPNetwork(str(last_ip) + "/24")
-        next_network = IPNetwork(str(IPAddress(last_network) + last_network.size) + "/24")
-        return next_network
-
     def sync_record(self, slice):
-        logger.info("sync'ing slice %s" % slice.name)
-        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
-
-            # XXX give caller an admin role at the tenant they've created
-            self.driver.add_user_role(slice.creator.kuser_id, tenant.id, 'admin')
-
-            # refresh credentials using this tenant
-            client_driver = self.driver.client_driver(tenant=tenant.name)
-
-            # create network
-            network = client_driver.create_network(slice.name)
-            slice.network_id = network['id']
-
-            # create router
-            router = client_driver.create_router(slice.name)
-            slice.router_id = router['id']
-
-            # create subnet for slice's private network
-            next_subnet = self.get_next_subnet()
-            cidr = str(next_subnet.cidr)
-            ip_version = next_subnet.version
-            start = str(next_subnet[2])
-            end = str(next_subnet[-2]) 
-            subnet = client_driver.create_subnet(name=slice.name,
-                                               network_id = network['id'],
-                                               cidr_ip = cidr,
-                                               ip_version = ip_version,
-                                               start = start,
-                                               end = end)
-            slice.subnet_id = subnet['id']
-            # add subnet as interface to slice's router
-            client_driver.add_router_interface(router['id'], subnet['id'])
-            # add external route
-            client_driver.add_external_route(subnet)
-
-
-        if slice.id and slice.tenant_id:
-            self.driver.update_tenant(slice.tenant_id,
-                                      description=slice.description,
-                                      enabled=slice.enabled)   
-
         slice.save()
index 2d7f1f8..83e33eb 100644 (file)
@@ -14,7 +14,8 @@ class SyncSliverIps(OpenStackSyncStep):
         return slivers
 
     def sync_record(self, sliver):
-        driver = self.driver.client_driver(tenant=sliver.slice.name)  
+        driver = self.driver.client_driver(tenant=sliver.slice.name, 
+                                           deployment=sliver.node.deployment.name)  
         servers = driver.shell.nova.servers.findall(id=sliver.instance_id)
         if not servers:
             return
index a3f423c..03383d9 100644 (file)
@@ -40,7 +40,7 @@ class SyncSlivers(OpenStackSyncStep):
             slice_memberships = SlicePrivilege.objects.filter(slice=sliver.slice)
             pubkeys = [sm.user.public_key for sm in slice_memberships if sm.user.public_key]
             pubkeys.append(sliver.creator.public_key)
-            driver = self.driver.client_driver(caller=sliver.creator, tenant=sliver.slice.name)
+            driver = self.driver.client_driver(caller=sliver.creator, tenant=sliver.slice.name, deployment=sliver.node.deployment.name)
             instance = driver.spawn_instance(name=sliver.name,
                                 key_name = sliver.creator.keyname,
                                 image_id = sliver.image.image_id,
diff --git a/planetstack/observer/steps/sync_user_deployments.py b/planetstack/observer/steps/sync_user_deployments.py
new file mode 100644 (file)
index 0000000..e7454b8
--- /dev/null
@@ -0,0 +1,76 @@
+import os
+import base64
+from collections import defaultdict
+from django.db.models import F, Q
+from planetstack.config import Config
+from observer.openstacksyncstep import OpenStackSyncStep
+from core.models.site import SiteDeployments
+from core.models.user import User, UserDeployments
+from util.logger import Logger, logging
+
+logger = Logger(level=logging.INFO)
+
+class SyncUserDeployments(OpenStackSyncStep):
+    provides=[User, UserDeployments]
+    requested_interval=0
+
+    def fetch_pending(self):
+        # user deployments are not visible to users. We must ensure
+        # user are deployed at all deploymets available to their sites.
+        site_deployments = SiteDeployment.objects.all()
+        site_deploy_lookup = defaultdict(list)
+        for site_deployment in site_deployments:
+            site_deploy_lookup[site_deployment.site].append(site_deployment.deployment)
+        
+        user_deployments = UserDeployment.objects.all()
+        user_deploy_lookup = defaultdict(list)
+        for user_deployment in user_deployments:
+            user_deploy_lookup[user_deployment.user].append(user_deployment.deployment)
+        
+        for user in User.objects.all():
+            expected_deployments = site_deploy_lookup[user.site]
+            for expected_deployment in expected_deployments:
+                if expected_deployment not in user_deploy_lookup[user]:
+                    ud = UserDeployments(user=user, deployment=expected_deployment)
+                    ud.save()
+
+        # now we can return all slice deployments that need to be enacted   
+        return UserDeployments.objects.filter(Q(enacted__lt=F('updated')) | Q(enacted=None))
+
+    def sync_record(self, user_deployment):
+        logger.info("sync'ing user deployment %s" % user_deployment.name)
+        name = user_deployment.user.email[:user_deployment.user.email.find('@')]
+        user_fields = {'name': name,
+                       'email': user_deployment.user.email,
+                       'password': hashlib.md5(user_deployment.user.password).hexdigest()[:6],
+                       'enabled': True}    
+        driver = self.driver.admin_driver(deployment=user_deployment.deployment.name)
+        if not user_deployment.kuser_id:
+            keystone_user = self.driver.create_user(**user_fields)
+            user_deployment.kuser_id = keystone_user.id
+        else:
+            driver.update_user(user_deployment.kuser_id, user_fields)
+
+        # setup user deployment site roles  
+        if user_deployment.user.site:
+            site_deployments = SiteDeployments.objects.filter(site=user_deployment.user.site,
+                                                              deployment=user_deployment.deployment)
+            if site_deployments:
+                # need the correct tenant id for site at the deployment
+                tenant_id = site_deployments[0].tenant_id  
+                driver.add_user_role(user_deployment.kuser_id, 
+                                     tenant_id, 'user')
+                if user_deployment.user.is_admin:
+                    driver.add_user_role(user_deployment.kuser_id, tenant_id, 'admin')
+                else:
+                    # may have admin role so attempt to remove it
+                    driver.delete_user_role(user_deployment.kuser_id, tenant_id, 'admin')
+
+        if user_deployment.user.public_key:
+            user_driver = self.driver.client_driver(caller=user, tenant=user.site.login_base, 
+                                                    deployment=user_deployment.deployment.name)
+            key_fields =  {'name': user_deployment.user.keyname,
+                           'public_key': user_deployment.user.public_key}
+            user_driver.create_keypair(**key_fields)
+
+        user_deployment.save()
index 25f093e..4bd2826 100644 (file)
@@ -14,29 +14,4 @@ class SyncUsers(OpenStackSyncStep):
         return User.objects.filter(Q(enacted__lt=F('updated')) | Q(enacted=None))
 
     def sync_record(self, user):
-        name = user.email[:user.email.find('@')]
-        user_fields = {'name': name,
-                       'email': user.email,
-                       'password': hashlib.md5(user.password).hexdigest()[:6],
-                       'enabled': True}
-        if not user.kuser_id:
-            keystone_user = self.driver.create_user(**user_fields)
-            user.kuser_id = keystone_user.id
-        else:
-            self.driver.update_user(user.kuser_id, user_fields)        
-
-        if user.site:
-            self.driver.add_user_role(user.kuser_id, user.site.tenant_id, 'user')
-            if user.is_admin:
-                self.driver.add_user_role(user.kuser_id, user.site.tenant_id, 'admin')
-            else:
-                # may have admin role so attempt to remove it
-                self.driver.delete_user_role(user.kuser_id, user.site.tenant_id, 'admin')
-
-        if user.public_key:
-            driver = self.driver.client_driver(caller=user, tenant=user.site.login_base) 
-            key_fields =  {'name': user.keyname,
-                           'public_key': user.public_key}
-            driver.create_keypair(**key_fields)
-
         user.save()
index 1a6386d..c6b6b16 100644 (file)
@@ -13,6 +13,7 @@ except:
     has_openstack = False
 
 from planetstack.config import Config
+from deployment_auth import deployment_auth
 
 def require_enabled(callable):
     def wrapper(*args, **kwds):
@@ -39,16 +40,22 @@ def parse_novarc(filename):
     return opts
 
 class Client:
-    def __init__(self, username=None, password=None, tenant=None, url=None, config=None, *args, **kwds):
-        if config:
-            config = Config(config)
+    def __init__(self, username=None, password=None, tenant=None, url=None, token=None, endpoint=None, deployment=None, *args, **kwds):
+        
+            
+        if not deployment or deployment not in deployment_auth:
+            auth = deployment_auth['default']
         else:
-            config = Config()
+            auth = deployment_auth[deployment]
+            
+            
         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
+        self.username = auth['user']
+        self.password = auth['password']
+        self.tenant = auth['tenant']
+        self.url = auth['url']
+        self.endpoint = auth['endpoint']
+        self.token = auth['token']  
 
         if username:
             self.username = username
@@ -58,6 +65,10 @@ class Client:
             self.tenant = tenant
         if url:
             self.url = url
+        if token:
+            self.token = token    
+        if endpoint:
+            self.endpoint = endpoint
 
         if '@' in self.username:
             self.username = self.username[:self.username.index('@')]
@@ -80,7 +91,10 @@ class KeystoneClient(Client):
             self.client = keystone_client.Client(username=self.username,
                                                  password=self.password,
                                                  tenant_name=self.tenant,
-                                                 auth_url=self.url)
+                                                 auth_url=self.url,
+                                                 endpoint=self.endpoint,
+                                                 token=self.token
+                                                )
 
     @require_enabled
     def connect(self, *args, **kwds):
index 8224c17..174cf85 100644 (file)
@@ -12,37 +12,39 @@ manager_enabled = Config().api_nova_enabled
 
 class OpenStackDriver:
 
-    def __init__(self, config = None, client=None): 
+    def __init__(self, config = None, client=None, deployment=None):
         if config:
             self.config = Config(config)
         else:
-            self.config = Config() 
+            self.config = Config()
 
-        self.admin_client = OpenStackClient()
+        self.admin_client = OpenStackClient(deployment=deployment)
         self.admin_user = self.admin_client.keystone.users.find(name=self.admin_client.keystone.username)
 
         if client:
             self.shell = client
         else:
-            self.shell = OpenStackClient()
+            self.shell = OpenStackClient(deployment=deployment)
 
         self.enabled = manager_enabled
         self.has_openstack = has_openstack
 
-    def client_driver(self, caller=None, tenant=None):
+    def client_driver(self, caller=None, tenant=None, deployment=None):
         if caller:
             auth = {'username': caller.email,
                     'password': hashlib.md5(caller.password).hexdigest()[:6],
                     'tenant': tenant}
-            client = OpenStackClient(**auth)
+            client = OpenStackClient(deployment=deployment, **auth)
         else:
-            client = OpenStackClient(tenant=tenant)
-        driver = OpenStackDriver(client=client)
+            client = OpenStackClient(tenant=tenant, deployment=deployment)
+
+        driver = OpenStackDriver(client=client, deployment=deployment)
         return driver
 
-    def admin_driver(self, tenant=None):
-        client = OpenStackClient(tenant=tenant)
-        driver = OpenStackDriver(client=client) 
+    def admin_driver(self, tenant=None, deployment=None):
+        client = OpenStackClient(tenant=tenant, deployment=deployment)
+        driver = OpenStackDriver(client=client, deployment=deployment)
+        return driver    
 
     def create_role(self, name):
         roles = self.shell.keystone.roles.findall(name=name)
index a250b29..267f282 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -19,7 +19,7 @@ setup(name='planetstack',
       description='PlanetStack',
       scripts=['planetstack/planetstack-backend.py'],
       data_files=[
-        ('/etc/planetstack/', ['planetstack/plstackapi_config']),
+        ('/etc/planetstack/', ['planetstack/plstackapi_config', 'planetstack/deployment_auth.py']),
         ('/lib/systemd/system/', ['planetstack/redhat/planetstack-backend.service']),
         ])