only the reaper handles deleting objects

Change-Id: I8b2ce61eecc0ed1da8766b0b0dfaf5d3f7ebd03d
diff --git a/xos/core/models/plcorebase.py b/xos/core/models/plcorebase.py
index 9d3d06d..0671e0b 100644
--- a/xos/core/models/plcorebase.py
+++ b/xos/core/models/plcorebase.py
@@ -219,8 +219,21 @@
     backend_register = models.CharField(max_length=1024,
                                       default="{}", null=True)
 
-    # If True, then the backend wants to delete this object
+    # backend_need_delete and backend_need_reap are used by the synchronizer to
+    # handle deleting and reaping objects.
+    #
+    # backend_need_delete is set by a syncstep when it starts to work on an
+    # object. It indicates that the syncstep will, in the future, want an
+    # opportunity to delete the object. This bit prevents the reaper from
+    # auto-deleting the object.
+    #
+    # backend_need_reap is set when a syncstep has successfully enacted a delete
+    # and is ready for it to be permanently deleted. The reaper will, assuming
+    # the object has no cascades to other objects that need deleting, permanently
+    # delete the object.
+
     backend_need_delete = models.BooleanField(default=False)
+    backend_need_reap = models.BooleanField(default=False)
 
     backend_status = models.CharField(max_length=1024,
                                       default="0 - Provisioning in progress")
diff --git a/xos/core/models/user.py b/xos/core/models/user.py
index e3e5fd2..16a9d18 100644
--- a/xos/core/models/user.py
+++ b/xos/core/models/user.py
@@ -142,6 +142,7 @@
     backend_status = StrippedCharField(max_length=1024,
                                        default="Provisioning in progress")
     backend_need_delete = models.BooleanField(default=False)
+    backend_need_reap = models.BooleanField(default=False)
     deleted = models.BooleanField(default=False)
     write_protect = models.BooleanField(default=False)
     lazy_blocked = models.BooleanField(default=False)
diff --git a/xos/synchronizers/base/SyncInstanceUsingAnsible.py b/xos/synchronizers/base/SyncInstanceUsingAnsible.py
index 88d636e..d21cc1f 100644
--- a/xos/synchronizers/base/SyncInstanceUsingAnsible.py
+++ b/xos/synchronizers/base/SyncInstanceUsingAnsible.py
@@ -232,6 +232,11 @@
             pass
 
         instance = self.get_instance(o)
+
+        if not instance:
+            # the instance is gone. There's nothing left for us to do.
+            return
+
         if isinstance(instance, basestring):
             # sync to some external host
 
diff --git a/xos/synchronizers/base/syncstep.py b/xos/synchronizers/base/syncstep.py
index a8951e9..a1e59c1 100644
--- a/xos/synchronizers/base/syncstep.py
+++ b/xos/synchronizers/base/syncstep.py
@@ -225,10 +225,16 @@
                     for f in failed:
                         self.check_dependencies(o,f) # Raises exception if failed
                     if (deletion):
-                        journal_object(o,"syncstep.call.delete_record")
-                        self.delete_record(o)
-                        journal_object(o,"syncstep.call.delete_purge")
-                        o.delete(purge=True)
+                        if getattr(o, "backend_need_reap", False):
+                            # the object has already been deleted and marked for reaping
+                            journal_object(o,"syncstep.call.already_marked_reap")
+                        else:
+                            journal_object(o,"syncstep.call.delete_record")
+                            self.delete_record(o)
+                            journal_object(o,"syncstep.call.delete_set_reap")
+                            o.backend_need_reap = True
+                            o.save(update_fields=['backend_need_reap'])
+                            #o.delete(purge=True)
                     else:
                         new_enacted = timezone.now()
                         try:
diff --git a/xos/synchronizers/model_policy.py b/xos/synchronizers/model_policy.py
index cfc9e90..ef5f4b0 100644
--- a/xos/synchronizers/model_policy.py
+++ b/xos/synchronizers/model_policy.py
@@ -195,7 +195,7 @@
                 if hasattr(d,"_meta") and hasattr(d._meta,"proxy") and d._meta.proxy:
                     # skip proxy objects; we'll get the base instead
                     continue
-                if getattr(d, "backend_need_delete", False):
+                if (not getattr(d, "backend_need_reap", False)) and getattr(d, "backend_need_delete", False):
                     journal_object(d, "reaper.need_delete")
                     print "Reaper: skipping %r because it has need_delete set" % d
                     continue