CORD-1907: Establish dependencies between deleted proxy objects
Change-Id: I0c1189beabecc7ad99288736ba8d4758c4c83035
(cherry picked from commit ff857d78e651965f9a55536b0c0021ba7c6b03c1)
diff --git a/xos/synchronizers/new_base/event_loop.py b/xos/synchronizers/new_base/event_loop.py
index 7841d5d..68398a4 100644
--- a/xos/synchronizers/new_base/event_loop.py
+++ b/xos/synchronizers/new_base/event_loop.py
@@ -12,12 +12,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# TODO:
+# Add unit tests:
+# - 2 sets of Instance, ControllerSlice, ControllerNetworks - delete and create case
+
import time
import sys
import threading
import json
import pprint
import traceback
+
from collections import defaultdict
from networkx import DiGraph, dfs_edges, weakly_connected_component_subgraphs, all_shortest_paths, NetworkXNoPath
from networkx.algorithms.dag import topological_sort
@@ -395,6 +400,17 @@
legacy_steps=pending_steps)
return pending_objects, pending_steps
+ def linked_objects(self, o):
+ if o is None:
+ return [], None
+ try:
+ o_lst = [o for o in o.all()]
+ edge_type = PROXY_EDGE
+ except (AttributeError, TypeError):
+ o_lst = [o]
+ edge_type = DIRECT_EDGE
+ return o_lst, edge_type
+
""" Automatically test if a real dependency path exists between two objects. e.g.
given an Instance, and a ControllerSite, the test amounts to:
instance.slice.site == controller.site
@@ -407,13 +423,8 @@
def same_object(self, o1, o2):
if not o1 or not o2:
return False, None
-
- try:
- o1_lst = (o for o in o1.all())
- edge_type = PROXY_EDGE
- except (AttributeError, TypeError):
- o1_lst = [o1]
- edge_type = DIRECT_EDGE
+
+ o1_lst,edge_type = self.linked_objects(o1)
try:
found = next(obj for obj in o1_lst if obj.leaf_model_name ==
@@ -422,7 +433,13 @@
self.log.exception('Compared objects could not be identified', e=e)
raise e
except StopIteration:
- found = False
+ # This is a temporary workaround to establish dependencies between
+ # deleted proxy objects. A better solution would be for the ORM to
+ # return the set of deleted objects via foreign keys. At that point,
+ # the following line would change back to found = False
+ # - Sapan
+
+ found = getattr(o2, 'deleted', False)
return found, edge_type
@@ -448,6 +465,7 @@
for p in paths:
src_object = o1
+ edge_type = DIRECT_EDGE
for i in range(len(p) - 1):
src = p[i]
@@ -455,11 +473,35 @@
edge_label = G[src][dst]
sa = edge_label['src_accessor']
try:
- dst_object = getattr(src_object, sa)
+ dst_accessor = getattr(src_object, sa)
+ dst_objects,link_edge_type = self.linked_objects(dst_accessor)
+ if link_edge_type == PROXY_EDGE:
+ edge_type = link_edge_type
+
+ """
+
+ True If no linked objects and deletion
+ False If no linked objects
+ True If multiple linked objects
+ <continue traversal> If single linked object
+
+ """
+
+ if dst_objects==[]:
+ if o2.deleted:
+ return True, edge_type
+ else:
+ dst_object = None
+ elif len(dst_objects)>1:
+ # Multiple linked objects. Assume anything could be among those multiple objects.
+ raise AttributeError
+ else:
+ dst_object = dst_objects[0]
except AttributeError as e:
- self.log.debug(
- 'Could not check object dependencies, making conservative choice', src_object=src_object, sa=sa, o1=o1, o2=o2)
- return True, DIRECT_EDGE
+ if sa!='fake_accessor':
+ self.log.debug(
+ 'Could not check object dependencies, making conservative choice', src_object=src_object, sa=sa, o1=o1, o2=o2)
+ return True, edge_type
src_object = dst_object
@@ -531,6 +573,7 @@
cohorts = [[objects[i] for i in cohort_index]
for cohort_index in cohort_indexes]
+
return cohorts
def run_once(self):
diff --git a/xos/synchronizers/new_base/tests/test_controller_dependencies.py b/xos/synchronizers/new_base/tests/test_controller_dependencies.py
index 0ac1f7c..f5deb84 100644
--- a/xos/synchronizers/new_base/tests/test_controller_dependencies.py
+++ b/xos/synchronizers/new_base/tests/test_controller_dependencies.py
@@ -116,7 +116,7 @@
self.assertFalse(verdict)
self.assertEqual(edge_type, None)
- def test_controller_path_simple(self):
+ def test_controller_path_simple_negative(self):
p = Instance()
s = Slice()
t = Site()
@@ -129,7 +129,7 @@
self.assertIn([ct], cohorts)
self.assertIn([p], cohorts)
- def test_controller_deletion_path(self):
+ def test_controller_deletion_path_negative(self):
p = Instance()
s = Slice()
t = Site()
@@ -148,7 +148,23 @@
self.assertIn([ct], cohorts)
- def test_multi_controller_schedule(self):
+ def test_multi_controller_deletion_schedule(self):
+ csl = ControllerSlice()
+ cn = ControllerNetwork()
+ site = Site()
+ slice = Slice()
+ slice.site = site
+ slice.controllerslices = mock_enumerator([])
+ site.controllersite = mock_enumerator([])
+ i = Instance()
+ i.slice = slice
+
+ cohorts = self.synchronizer.compute_dependent_cohorts([i, slice, site, csl, csi], False)
+ self.assertIn([site, slice, i], cohorts)
+ self.assertIn([csl], cohorts)
+ self.assertIn([csi], cohorts)
+
+ def test_multi_controller_schedule_negative(self):
csl = ControllerSlice()
csi = ControllerSite()
site = Site()
@@ -164,8 +180,5 @@
self.assertIn([csl], cohorts)
self.assertIn([csi], cohorts)
-
if __name__ == '__main__':
unittest.main()
-
-# ControllerNetworks, ControllerSlice, ControllerSite, instance, slice, site