Merge branch 'master' of git.planet-lab.org:/git/plstackapi
diff --git a/planetstack/core/admin.py b/planetstack/core/admin.py
index 114382f..a1a21d6 100644
--- a/planetstack/core/admin.py
+++ b/planetstack/core/admin.py
@@ -549,14 +549,17 @@
         return super(UserAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
 
 class ServiceResourceInline(admin.TabularInline):
+    exclude = ['enacted']
     model = ServiceResource
     extra = 0
 
 class ServiceClassAdmin(admin.ModelAdmin):
+    exclude = ['enacted']
     list_display = ('name', 'commitment', 'membershipFee')
     inlines = [ServiceResourceInline]
 
 class ReservedResourceInline(admin.TabularInline):
+    exclude = ['enacted']
     model = ReservedResource
     extra = 0
 
@@ -625,6 +628,7 @@
         return False
 
 class ReservationAdmin(admin.ModelAdmin):
+    exclude = ['enacted']
     list_display = ('startTime', 'duration')
     inlines = [ReservedResourceInline]
     form = ReservationAddForm
@@ -702,6 +706,8 @@
 admin.site.register(Site, SiteAdmin)
 admin.site.register(Slice, SliceAdmin)
 admin.site.register(Project, ProjectAdmin)
+admin.site.register(ServiceClass, ServiceClassAdmin)
+admin.site.register(Reservation, ReservationAdmin)
 
 if showAll:
     admin.site.register(Tag, TagAdmin)
@@ -710,7 +716,5 @@
     admin.site.register(SitePrivilege, SitePrivilegeAdmin)
     admin.site.register(Role, RoleAdmin)
     admin.site.register(Sliver, SliverAdmin)
-    admin.site.register(ServiceClass, ServiceClassAdmin)
-    admin.site.register(Reservation, ReservationAdmin)
     admin.site.register(Image, ImageAdmin)
 
diff --git a/planetstack/core/models/plcorebase.py b/planetstack/core/models/plcorebase.py
index 709fdc6..30d4df3 100644
--- a/planetstack/core/models/plcorebase.py
+++ b/planetstack/core/models/plcorebase.py
@@ -1,6 +1,8 @@
 import os
 from django.db import models
 from django.forms.models import model_to_dict
+from openstack.event_manager import EventSender
+
 
 class PlCoreBase(models.Model):
 
@@ -36,6 +38,10 @@
 
     def save(self, *args, **kwargs):
         super(PlCoreBase, self).save(*args, **kwargs)
+        
+        # Tell the observer that the source database has been updated
+        EventSender().fire()
+
         self.__initial = self._dict
 
     @property
diff --git a/planetstack/openstack/backend.py b/planetstack/openstack/backend.py
index 340b29b..2f4aa71 100644
--- a/planetstack/openstack/backend.py
+++ b/planetstack/openstack/backend.py
@@ -1,7 +1,6 @@
 import threading
-from openstack.sliveragent import SliverAgent
 from openstack.observer import OpenStackObserver
-from openstack.event_listener import EventListener
+from openstack.event_manager import EventListener
 
 class Backend:
     
@@ -12,7 +11,7 @@
         observer_thread.start()
 
         # start event listene
-        event_listener = EventListener()
-        event_listener_thread = threading.Thread(target=event_listener.run)
-        event_listener_thread.start()
+        event_manager = EventListener(wake_up=observer.wake_up)
+        event_manager_thread = threading.Thread(target=event_manager.run)
+        event_manager_thread.start()
                 
diff --git a/planetstack/openstack/driver.py b/planetstack/openstack/driver.py
index 4c0791e..c01fede 100644
--- a/planetstack/openstack/driver.py
+++ b/planetstack/openstack/driver.py
@@ -51,8 +51,17 @@
         return self.shell.keystone.tenants.update(id, **kwds)
 
     def delete_tenant(self, id):
+        ctx = self.shell.nova_db.ctx
         tenants = self.shell.keystone.tenants.findall(id=id)
         for tenant in tenants:
+            # nova does not automatically delete the tenant's instances
+            # so we manually delete instances before deleteing the tenant   
+            instances = self.shell.nova_db.instance_get_all_by_filters(ctx, 
+                       {'project_id': tenant.id}, 'id', 'asc')
+            client = OpenStackClient(tenant=tenant)
+            driver = OpenStackDriver(client=client)
+            for instance in instances:
+                driver.destroy_instance(instance.id)
             self.shell.keystone.tenants.delete(tenant)
         return 1
 
@@ -230,7 +239,15 @@
                 self.delete_external_route(subnet)
         return 1
 
-    def add_external_route(self, subnet):
+    def get_external_routes(self):
+        status, output = commands.getstatusoutput('route')
+        routes = output.split('\n')[3:]
+        return routes
+
+    def add_external_route(self, subnet, routes=[]):
+        if not routes:
+            routes = self.get_external_routes()
+ 
         ports = self.shell.quantum.list_ports()['ports']
 
         gw_ip = subnet['gateway_ip']
@@ -247,14 +264,23 @@
                     gw_port = port
                     router_id = gw_port['device_id']
                     router = self.shell.quantum.show_router(router_id)['router']
-                    ext_net = router['external_gateway_info']['network_id']
-                    for port in ports:
-                        if port['device_id'] == router_id and port['network_id'] == ext_net:
-                            ip_address = port['fixed_ips'][0]['ip_address']
+                    if router:
+                        ext_net = router['external_gateway_info']['network_id']
+                        for port in ports:
+                            if port['device_id'] == router_id and port['network_id'] == ext_net:
+                                ip_address = port['fixed_ips'][0]['ip_address']
 
         if ip_address:
-            cmd = "route add -net %s dev br-ex gw %s" % (subnet['cidr'], ip_address)
-            commands.getstatusoutput(cmd)
+            # check if external route already exists
+            route_exists = False
+            if routes:
+                for route in routes:
+                    if subnet['cidr'] in route and ip_address in route:
+                        route_exists = True
+            if not route_exists:
+                cmd = "route add -net %s dev br-ex gw %s" % (subnet['cidr'], ip_address)
+                s, o = commands.getstatusoutput(cmd)
+                #print cmd, "\n", s, o
 
         return 1
 
diff --git a/planetstack/openstack/event_listener.py b/planetstack/openstack/event_listener.py
deleted file mode 100644
index d3f0abf..0000000
--- a/planetstack/openstack/event_listener.py
+++ /dev/null
@@ -1,114 +0,0 @@
-import threading
-import requests, json
-from core.models import *
-from openstack.manager import OpenStackManager
-
-# decorator that marks dispatachable event methods  
-def event(func):
-    setattr(func, 'event', func.__name__)
-    return func      
-
-class EventHandler:
-
-    def __init__(self):
-        self.manager = OpenStackManager()
-
-    def get_events(self):
-        events = []
-        for attrib in dir(self):
-            if hasattr(attrib, 'event'):
-                events.append(getattr(attrib, 'event'))
-        return events
-
-    def dispatch(self, event, *args, **kwds):
-        if hasattr(self, event):
-            return getattr(self, event)(*args, **kwds)
-            
-        
-    @event
-    def save_site(self, id):
-        sites = Site.objects.filter(id=id)
-        if sites:
-            self.manager.save_site(sites[0])
-    
-    @event
-    def delete_site(self, tenant_id):
-        self.manager.driver.delete_tenant(tenant_id)
-
-    @event
-    def save_site_privilege(self, id):
-        site_privileges = SitePrivilege.objects.filter(id=id)
-        if site_privileges:
-            site_priv = self.manager.save_site_privilege(site_privileges[0])
-
-    @event
-    def delete_site_privilege(self, kuser_id, tenant_id, role_type):
-        self.manager.driver.delete_user_role(kuser_id, tenant_id, role_type)
-
-    @event
-    def save_slice(self, id):
-        slices = Slice.objects.filter(id=id)
-        if slices:
-            self.manager.save_slice(slices[0])
-    
-    @event
-    def delete_slice(self, tenant_id, network_id, router_id, subnet_id):
-        self.manager._delete_slice(tenant_id, network_id, router_id, subnet_id)
-
-    @event
-    def save_user(self, id):
-        users = User.objects.filter(id=id)
-        if users:
-            self.manager.save_user(users[0])
-        
-    @event
-    def delete_user(self, kuser_id):
-        self.manager.driver.delete_user(kuser_id)
-    
-    @event
-    def save_sliver(self, id):
-        slivers = Sliver.objects.filter(id=id)
-        if slivers:
-            self.manager.save_sliver(slivers[0])
-
-    @event
-    def delete_sliver(self, instance_id):
-        self.manager.destroy_instance(instance_id)                            
-
-    
-
-class EventListener:
-
-    def __init__(self):
-        self.handler = EventHandler()
-
-    def listen_for_event(self, event, hash):
-        url = 'http://www.feefie.com/command'
-        params = {'action': 'subscribe',
-                  'hash': hash,
-                  'htm': 1}
-        while True:
-            r = requests.get(url, params=params)
-            r_data = json.loads(r)
-            payload = r_data.get('payload')
-            self.handler.dispatch(event, **payload)
-
-
-    def run(self):
-        # register events
-        event_names = [{'title': name} for name in self.handler.get_events()]
-        url = 'http://www.feefie.com/command'
-        params = {'action': 'add',
-                  'u': 'pl',
-                  'events': event_names}
-        r = requests.get(url, params=params)
-        print dir(r)
-        print r
-        r_data = json.loads(r)
-        events = r_data.get('events', [])
-        # spanw a  thread for each event
-        for event in events:
-            args = (event['title'], event['hash'])
-            listener_thread = threading.Thread(target=self.listen_for_event, args=args)
-            listener_tread.start()
-                                    
diff --git a/planetstack/openstack/event_manager.py b/planetstack/openstack/event_manager.py
new file mode 100644
index 0000000..d7102b6
--- /dev/null
+++ b/planetstack/openstack/event_manager.py
@@ -0,0 +1,131 @@
+import threading
+import requests, json
+
+from core.models import *
+from openstack.manager import OpenStackManager
+from planetstack.config import Config
+
+import os
+import base64
+import fofum
+
+# decorator that marks dispatachable event methods  
+def event(func):
+    setattr(func, 'event', func.__name__)
+    return func      
+
+class EventHandler:
+    # This code is currently not in use.
+    def __init__(self):
+        self.manager = OpenStackManager()
+
+    @staticmethod
+    def get_events():
+        events = []
+        for name in dir(EventHandler):
+            attribute = getattr(EventHandler, name)
+            if hasattr(attribute, 'event'):
+                events.append(getattr(attribute, 'event'))
+        return events
+
+    def dispatch(self, event, *args, **kwds):
+        if hasattr(self, event):
+            return getattr(self, event)(*args, **kwds)
+            
+        
+    @event
+    def save_site(self, id):
+        sites = Site.objects.filter(id=id)
+        if sites:
+            self.manager.save_site(sites[0])
+    
+    @event
+    def delete_site(self, tenant_id):
+        self.manager.driver.delete_tenant(tenant_id)
+
+    @event
+    def save_site_privilege(self, id):
+        site_privileges = SitePrivilege.objects.filter(id=id)
+        if site_privileges:
+            site_priv = self.manager.save_site_privilege(site_privileges[0])
+
+    @event
+    def delete_site_privilege(self, kuser_id, tenant_id, role_type):
+        self.manager.driver.delete_user_role(kuser_id, tenant_id, role_type)
+
+    @event
+    def save_slice(self, id):
+        slices = Slice.objects.filter(id=id)
+        if slices:
+            self.manager.save_slice(slices[0])
+    
+    @event
+    def delete_slice(self, tenant_id, network_id, router_id, subnet_id):
+        self.manager._delete_slice(tenant_id, network_id, router_id, subnet_id)
+
+    @event
+    def save_user(self, id):
+        users = User.objects.filter(id=id)
+        if users:
+            self.manager.save_user(users[0])
+        
+    @event
+    def delete_user(self, kuser_id):
+        self.manager.driver.delete_user(kuser_id)
+    
+    @event
+    def save_sliver(self, id):
+        slivers = Sliver.objects.filter(id=id)
+        if slivers:
+            self.manager.save_sliver(slivers[0])
+
+    @event
+    def delete_sliver(self, instance_id):
+        self.manager.destroy_instance(instance_id)                            
+
+    
+class EventSender:
+    def __init__(self,user=None,clientid=None):
+        try:
+            clid = Config().feefie_client_id
+            user = Config().feefie_client_user
+        except:
+            clid = 'planetstack_core_team'
+            user = 'pl'
+
+        self.fofum = Fofum(user=user)
+        self.fofum.make(clid)
+
+    def fire(self):
+        self.fofum.fire()
+
+class EventListener:
+    def __init__(self,wake_up=None):
+        self.handler = EventHandler()
+        self.wake_up = wake_up()
+
+    def handle_event(self, payload):
+        payload_dict = json.loads(payload)
+        event = payload_dict['event']
+        ctx = payload_dict['ctx']
+        self.handler.dispatch(event,**ctx)   
+
+        if (self.wake_up):
+            self.wake_up()
+        
+
+    def run(self):
+        # This is our unique client id, to be used when firing and receiving events
+        # It needs to be generated once and placed in the config file
+
+        try:
+            clid = Config().feefie_client_id
+            user = Config().feefie_client_user
+        except:
+            clid = 'planetstack_core_team'
+            user = 'pl'
+
+        f = Fofum(user=user)
+        
+        listener_thread = threading.Thread(target=f.listen_for_event,args=(clid,self.handle_event))
+        listener_thread.start()
diff --git a/planetstack/openstack/manager.py b/planetstack/openstack/manager.py
index 8016888..0b20d79 100644
--- a/planetstack/openstack/manager.py
+++ b/planetstack/openstack/manager.py
@@ -1,3 +1,4 @@
+w
 import os
 #os.environ.setdefault("DJANGO_SETTINGS_MODULE", "planetstack.settings")
 import string
@@ -305,7 +306,7 @@
     def save_sliver(self, sliver):
         if not sliver.instance_id:
             slice_memberships = SliceMembership.objects.filter(slice=sliver.slice)
-            pubkeys = [sm.user.public_key for sm in slice_memberships if sm.user.public_key != null]
+            pubkeys = [sm.user.public_key for sm in slice_memberships if sm.user.public_key]
             pubkeys.append(sliver.creator.public_key) 
             instance = self.driver.spawn_instance(name=sliver.name,
                                    key_name = sliver.creator.keyname,
diff --git a/planetstack/openstack/observer.py b/planetstack/openstack/observer.py
index aeda8d1..dce26aa 100644
--- a/planetstack/openstack/observer.py
+++ b/planetstack/openstack/observer.py
@@ -1,11 +1,15 @@
 import time
 import traceback
+import commands
+import threading
+
 from datetime import datetime
 from collections import defaultdict
 from core.models import *
 from django.db.models import F, Q
 from openstack.manager import OpenStackManager
 from util.logger import Logger, logging
+from timeout import timeout
 
 
 logger = Logger(logfile='observer.log', level=logging.INFO)
@@ -14,6 +18,18 @@
     
     def __init__(self):
         self.manager = OpenStackManager()
+        # The Condition object that gets signalled by Feefie events
+        self.event_cond = threading.Condition()
+
+    def wait_for_event(self, timeout):
+        self.event_cond.acquire()
+        self.event_cond.wait(timeout)
+        self.event_cond.release()
+        
+    def wake_up(self):
+        self.event_cond.acquire()
+        self.event_cond.notify()
+        self.event_cond.release()
 
     def run(self):
         if not self.manager.enabled or not self.manager.has_openstack:
@@ -26,7 +42,10 @@
                 self.sync_user_tenant_roles()
                 self.sync_slivers()
                 self.sync_sliver_ips()
-                time.sleep(7)
+                self.sync_external_routes()
+
+                self.wait_for_event(timeout=30)
+
             except:
                 traceback.print_exc() 
 
@@ -226,16 +245,16 @@
         # get all users that need to be synced (enacted < updated or enacted is None)
         pending_slivers = Sliver.objects.filter(Q(enacted__lt=F('updated')) | Q(enacted=None))
         for sliver in pending_slivers:
-            if not sliver.instance_id and sliver.creator: 
+            if sliver.creator: 
                 try: 
                     # update manager context
                     self.manager.init_caller(sliver.creator, sliver.slice.name)
                     self.manager.save_sliver(sliver)
-                    logger.info("saved sliver: %s %s" % (sliver))
+                    logger.info("saved sliver: %s" % (sliver))
                 except:
                     logger.log_exc("save sliver failed: %s" % sliver) 
 
-        # get all slivers that where enacted != null. We can assume these users
+        # get all slivers where enacted != null. We can assume these users
         # have previously been synced and need to be checed for deletion.
         slivers = Sliver.objects.filter(enacted__isnull=False)
         sliver_dict = {}
@@ -246,12 +265,12 @@
         ctx = self.manager.driver.shell.nova_db.ctx 
         instances = self.manager.driver.shell.nova_db.instance_get_all(ctx)
         for instance in instances:
-            if instance.id not in sliver_dict:
+            if instance.uuid not in sliver_dict:
                 try:
                     # lookup tenant and update context  
                     tenant = self.manager.driver.shell.keystone.tenants.find(id=instance.project_id) 
                     self.manager.init_admin(tenant=tenant.name)  
-                    self.manager.driver.destroy_instance(instance.id)
+                    self.manager.driver.destroy_instance(instance.uuid)
                     logger.info("destroyed sliver: %s" % (instance))
                 except:
                     logger.log_exc("destroy sliver failed: %s" % instance) 
@@ -263,7 +282,7 @@
         for sliver in slivers:
             # update connection
             self.manager.init_admin(tenant=sliver.slice.name)
-            servers = self.manager.client.nova.servers.findall(id=sliver.instance_id)
+            servers = self.manager.driver.shell.nova.servers.findall(id=sliver.instance_id)
             if not servers:
                 continue
             server = servers[0]
@@ -273,3 +292,13 @@
             sliver.ip = ips[0]['addr']
             sliver.save()
             logger.info("saved sliver ip: %s %s" % (sliver, ips[0]))
+
+    def sync_external_routes(self):
+        routes = self.manager.driver.get_external_routes() 
+        subnets = self.manager.driver.shell.quantum.list_subnets()['subnets']
+        for subnet in subnets:
+            try: 
+                self.manager.driver.add_external_route(subnet, routes)         
+            except: 
+                logger.log_exc("failed to add external route for subnet %s" % subnet)
+ 
diff --git a/planetstack/planetstack-backend.py b/planetstack/planetstack-backend.py
new file mode 100644
index 0000000..0270264
--- /dev/null
+++ b/planetstack/planetstack-backend.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import os
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "planetstack.settings")
+from openstack.backend import Backend 
+
+if __name__ == '__main__':
+
+    backend = Backend()
+    backend.run()
+ 
diff --git a/planetstack/plstackapi-debug-server.py b/planetstack/plstackapi-debug-server.py
deleted file mode 100644
index e120d72..0000000
--- a/planetstack/plstackapi-debug-server.py
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env python
-import os
-import sys
-
-os.environ.setdefault("DJANGO_SETTINGS_MODULE", "planetstack.settings")
-from planetstack.config import Config
-from openstack.backend import Backend 
-
-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] 
-
-    
-    backend = Backend()
-    backend.run()
- 
-    # start the server
-    server = ManagementUtility(args)
-    server.execute()
diff --git a/setup.py b/setup.py
index 4f8f050..79a90ef 100644
--- a/setup.py
+++ b/setup.py
@@ -17,7 +17,7 @@
 setup(name='planetstack',
       version='0.1',
       description='PlanetStack',
-      scripts=['planetstack/plstackapi-debug-server.py'],
+      scripts=['planetstack/planetstack-backend.py'],
       data_files=[
         ('/etc/planetstack/', ['planetstack/plstackapi_config']),
         ])