CORD-2944 do not mark objects deleted if ansible returns a nonzero rc

Change-Id: I1d1b3968524d3bb613d28b64df1791fc52e72a4c
diff --git a/xos/synchronizers/new_base/syncstep.py b/xos/synchronizers/new_base/syncstep.py
index 8aa2afa..4ca697e 100644
--- a/xos/synchronizers/new_base/syncstep.py
+++ b/xos/synchronizers/new_base/syncstep.py
@@ -141,9 +141,13 @@
 
         tenant_fields['delete'] = True
         res = run_template(self.playbook, tenant_fields, path=path)
-        try:
+
+        if hasattr(self, "map_delete_outputs"):
             self.map_delete_outputs(o, res)
-        except AttributeError:
-            pass
+        else:
+            # "rc" is often only returned when something bad happens, so assume that no "rc" implies a successful rc
+            # of 0.
+            if res[0].get("rc", 0) != 0:
+                raise Exception("Nonzero rc from Ansible during delete_record")
 
         self.log.debug("Finished default delete record", **o.tologdict())
diff --git a/xos/synchronizers/new_base/tests/test_payload.py b/xos/synchronizers/new_base/tests/test_payload.py
index 524c5ea..a72a5da 100644
--- a/xos/synchronizers/new_base/tests/test_payload.py
+++ b/xos/synchronizers/new_base/tests/test_payload.py
@@ -30,6 +30,12 @@
 def run_fake_ansible_template(*args,**kwargs):
     opts = args[1]
     open(ANSIBLE_FILE,'w').write(json.dumps(opts))
+    return [{"rc": 0}]
+
+def run_fake_ansible_template_fail(*args,**kwargs):
+    opts = args[1]
+    open(ANSIBLE_FILE,'w').write(json.dumps(opts))
+    return [{"rc": 1}]
 
 def get_ansible_output():
     ansible_str = open(ANSIBLE_FILE).read()
@@ -92,7 +98,21 @@
             a = get_ansible_output()
             self.assertDictContainsSubset({'delete':True, 'name':o.name}, a)
             o.save.assert_called_with(update_fields=['backend_need_reap'])
-        
+
+    @mock.patch("steps.sync_instances.syncstep.run_template",side_effect=run_fake_ansible_template_fail)
+    @mock.patch("event_loop.model_accessor")
+    def test_delete_record_fail(self, mock_run_template, mock_modelaccessor):
+        with mock.patch.object(Instance, "save") as instance_save:
+            o = Instance()
+            o.name = "Sisi Pascal"
+
+            o.synchronizer_step = steps.sync_instances.SyncInstances()
+
+            with self.assertRaises(Exception) as e:
+                self.synchronizer.delete_record(o, log)
+
+            self.assertEqual(e.exception.message, "Nonzero rc from Ansible during delete_record")
+
     @mock.patch("steps.sync_instances.syncstep.run_template",side_effect=run_fake_ansible_template)
     @mock.patch("event_loop.model_accessor")
     def test_sync_record(self, mock_run_template, mock_modelaccessor):