CORD-770 Change TenantWithContainer from proxy to real model, rebuild openstack synchronizer when models are added, fix reaper to ignore parent classes

Change-Id: Ic54a9cdd36583dc65b28404c5d50a97f078e8526
diff --git a/xos/core/models/service.py b/xos/core/models/service.py
index fd0a51a..18d2c38 100644
--- a/xos/core/models/service.py
+++ b/xos/core/models/service.py
@@ -821,78 +821,39 @@
 
 
 class TenantWithContainer(Tenant):
-    """ A tenant that manages a container """
+    """ A tenant that manages an Instance
+        Note: Should probably be called TenantWithInstance instead of TenantWithContainer
+    """
 
-    class Meta:
-        proxy = True
+    instance = models.ForeignKey("Instance", related_name='+', help_text="Instance used by this Tenant", null=True, blank=True)
+    creator = models.ForeignKey("User", related_name='+', help_text="Creator of this Tenant", null=True, blank=True)
+    external_hostname = StrippedCharField(max_length=30, help_text="External host name", null=True, blank=True) # is this still used?
+    external_container = StrippedCharField(max_length=30, help_text="External host name", null=True, blank=True) # is this still used?
 
     def __init__(self, *args, **kwargs):
         super(TenantWithContainer, self).__init__(*args, **kwargs)
-        self.cached_instance = None
-        self.orig_instance_id = self.get_initial_attribute("instance_id")
 
-    @property
-    def instance(self):
-        from core.models import Instance
-        if getattr(self, "cached_instance", None):
-            return self.cached_instance
-        instance_id = self.get_attribute("instance_id")
-        if not instance_id:
-            return None
-        instances = Instance.objects.filter(id=instance_id)
-        if not instances:
-            return None
-        instance = instances[0]
-        instance.caller = self.creator
-        self.cached_instance = instance
-        return instance
+        # vSG service relies on knowing when instance id has changed
+        self.orig_instance_id = self.get_attribute("instance_id")
 
-    @instance.setter
-    def instance(self, value):
-        if value:
-            value = value.id
-        if (value != self.get_attribute("instance_id", None)):
-            self.cached_instance = None
-        self.set_attribute("instance_id", value)
+    # vSG service relies on instance_id attribute
+    def get_attribute(self, name, default=None):
+        if name=="instance_id":
+            if self.instance:
+                return self.instance.id
+            else:
+                return None
+        else:
+            return super(TenantWithContainer, self).get_attribute(name, default)
 
-    @property
-    def external_hostname(self):
-        return self.get_attribute("external_hostname", "")
-
-    @external_hostname.setter
-    def external_hostname(self, value):
-        self.set_attribute("external_hostname", value)
-
-    @property
-    def external_container(self):
-        return self.get_attribute("external_container", "")
-
-    @external_container.setter
-    def external_container(self, value):
-        self.set_attribute("external_container", value)
-
-    @property
-    def creator(self):
-        from core.models import User
-        if getattr(self, "cached_creator", None):
-            return self.cached_creator
-        creator_id = self.get_attribute("creator_id")
-        if not creator_id:
-            return None
-        users = User.objects.filter(id=creator_id)
-        if not users:
-            return None
-        user = users[0]
-        self.cached_creator = users[0]
-        return user
-
-    @creator.setter
-    def creator(self, value):
-        if value:
-            value = value.id
-        if (value != self.get_attribute("creator_id", None)):
-            self.cached_creator = None
-        self.set_attribute("creator_id", value)
+    # Services may wish to override the image() function to return different
+    # images based on criteria in the tenant object. For example,
+    #    if (self.has_feature_A):
+    #        return Instance.object.get(name="image_with_feature_a")
+    #    elif (self.has_feature_B):
+    #        return Instance.object.get(name="image_with_feature_b")
+    #    else:
+    #        return super(MyTenantClass,self).image()
 
     @property
     def image(self):
diff --git a/xos/synchronizers/base/SyncInstanceUsingAnsible.py b/xos/synchronizers/base/SyncInstanceUsingAnsible.py
index d21cc1f..ebab1c4 100644
--- a/xos/synchronizers/base/SyncInstanceUsingAnsible.py
+++ b/xos/synchronizers/base/SyncInstanceUsingAnsible.py
@@ -148,21 +148,22 @@
 
         fields["private_key"] = key
 
-        # now the ceilometer stuff
+        # Now the ceilometer stuff
+        # Only do this if the instance is not being deleted.
+        if not instance.deleted:
+            cslice = ControllerSlice.objects.get(slice=instance.slice)
+            if not cslice:
+                raise Exception("Controller slice object for %s does not exist" % instance.slice.name)
 
-        cslice = ControllerSlice.objects.get(slice=instance.slice)
-        if not cslice:
-            raise Exception("Controller slice object for %s does not exist" % instance.slice.name)
+            cuser = ControllerUser.objects.get(user=instance.creator)
+            if not cuser:
+                raise Exception("Controller user object for %s does not exist" % instance.creator)
 
-        cuser = ControllerUser.objects.get(user=instance.creator)
-        if not cuser:
-            raise Exception("Controller user object for %s does not exist" % instance.creator)
-
-        fields.update({"keystone_tenant_id": cslice.tenant_id,
-                       "keystone_user_id": cuser.kuser_id,
-                       "rabbit_user": getattr(instance.controller,"rabbit_user", None),
-                       "rabbit_password": getattr(instance.controller, "rabbit_password", None),
-                       "rabbit_host": getattr(instance.controller, "rabbit_host", None)})
+            fields.update({"keystone_tenant_id": cslice.tenant_id,
+                           "keystone_user_id": cuser.kuser_id,
+                           "rabbit_user": getattr(instance.controller,"rabbit_user", None),
+                           "rabbit_password": getattr(instance.controller, "rabbit_password", None),
+                           "rabbit_host": getattr(instance.controller, "rabbit_host", None)})
 
         return fields
 
@@ -237,6 +238,10 @@
             # the instance is gone. There's nothing left for us to do.
             return
 
+        if instance.deleted:
+            # the instance is being deleted. There's nothing left for us to do.
+            return
+
         if isinstance(instance, basestring):
             # sync to some external host
 
@@ -258,14 +263,14 @@
             for attribute_name in o.sync_attributes:
                 fields[attribute_name] = getattr(o, attribute_name)
 
-        fields.update(self.map_delete_inputs(o))
+        if hasattr(self, "map_delete_inputs"):
+            fields.update(self.map_delete_inputs(o))
 
         fields['delete']=True
         res = self.run_playbook(o,fields)
-        try:
-                self.map_delete_outputs(o,res)
-        except AttributeError:
-                pass
+
+        if hasattr(self, "map_delete_outputs"):
+            self.map_delete_outputs(o,res)
 
     #In order to enable the XOS watcher functionality for a synchronizer, define the 'watches' attribute 
     #in the derived class: eg. watches = [ModelLink(CoarseTenant,via='coarsetenant')]
diff --git a/xos/synchronizers/model_policy.py b/xos/synchronizers/model_policy.py
index ef5f4b0..4b4ae24 100644
--- a/xos/synchronizers/model_policy.py
+++ b/xos/synchronizers/model_policy.py
@@ -156,7 +156,16 @@
     for (k, models) in collector.data.items():

         for model in models:
             if model==m:
+                # collector will return ourself; ignore it.
                 continue
+            if issubclass(m.__class__, model.__class__):
+                # collector will return our parent classes; ignore them.
+                continue
+# We don't actually need this check, as with multiple passes the reaper can
+# clean up a hierarchy of objects.
+#            if getattr(model, "backend_need_reap", False):
+#                # model is already marked for reaping; ignore it.
+#                continue
             deps.append(model)
     return deps
 
diff --git a/xos/synchronizers/onboarding/steps/sync_servicecontrollerresource.py b/xos/synchronizers/onboarding/steps/sync_servicecontrollerresource.py
index a089bec..238b149 100644
--- a/xos/synchronizers/onboarding/steps/sync_servicecontrollerresource.py
+++ b/xos/synchronizers/onboarding/steps/sync_servicecontrollerresource.py
@@ -28,11 +28,22 @@
         logger.info("Sync'ing ServiceControllerResource %s" % scr)
         self.download_resource(scr)
 
+        # TODO: The following should be redone with watchers
+
         if scr.loadable_module and scr.loadable_module.xos:
             # Make sure the xos UI is resynced
             xos = scr.loadable_module.xos
             xos.save(update_fields=["updated"], always_update_timestamp=True)
 
+        if (scr.kind=="models") and scr.loadable_module and (scr.loadable_module.name != "openstack"):
+            # Make sure the openstack controller is restarted. This is necessary
+            # as the OpenStack controller is the only one that handles model
+            # policies.
+            os_scr = ServiceController.objects.filter(name="openstack")
+            if os_scr:
+                os_scr = os_scr[0]
+                os_scr.save(update_fields=["updated"], always_update_timestamp=True)
+
     def delete_record(self, m):
         pass