diff --git a/awx/api/generics.py b/awx/api/generics.py index 7a818722bc..e474a1bafc 100644 --- a/awx/api/generics.py +++ b/awx/api/generics.py @@ -370,7 +370,7 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView): # Base class for a sublist view that allows for creating subobjects and # attaching/detaching them from the parent. - def is_valid_relation(self, parent, sub): + def is_valid_relation(self, parent, sub, created=False): return None def get_description_context(self): @@ -410,7 +410,7 @@ class SubListCreateAttachDetachAPIView(SubListCreateAPIView): raise PermissionDenied() # Verify that the relationship to be added is valid. - attach_errors = self.is_valid_relation(parent, sub) + attach_errors = self.is_valid_relation(parent, sub, created=created) if attach_errors is not None: if created: sub.delete() diff --git a/awx/api/views.py b/awx/api/views.py index 910eae869b..84306332b0 100644 --- a/awx/api/views.py +++ b/awx/api/views.py @@ -69,7 +69,6 @@ from awx.api.renderers import * # noqa from awx.api.serializers import * # noqa from awx.api.metadata import RoleMetadata from awx.main.consumers import emit_channel_notification -from awx.main.scheduler.dag_simple import SimpleDAG logger = logging.getLogger('awx.api.views') @@ -2657,31 +2656,36 @@ class WorkflowJobTemplateNodeChildrenBaseList(EnforceParentRelationshipMixin, Su self.check_parent_access(parent) return getattr(parent, self.relationship).all() - def is_valid_relation(self, parent, sub): - workflow_nodes = parent.workflow_job_template.workflow_job_template_nodes.all() - graph = SimpleDAG() + def is_valid_relation(self, parent, sub, created=False): + if created: + return None + + workflow_nodes = parent.workflow_job_template.workflow_job_template_nodes.all().\ + prefetch_related('success_nodes', 'failure_nodes', 'always_nodes') + graph = {} for workflow_node in workflow_nodes: - graph.add_node(workflow_node) + graph[workflow_node.pk] = dict(node_object=workflow_node, metadata={'parent': None}) find = False for node_type in ['success_nodes', 'failure_nodes', 'always_nodes']: for workflow_node in workflow_nodes: - related_nodes = getattr(workflow_node, node_type).all() + parent_node = graph[workflow_node.pk] + related_nodes = getattr(parent_node['node_object'], node_type).all() for related_node in related_nodes: - graph.add_edge(workflow_node, related_node, node_type) - if (not find and - parent == workflow_node and - sub == related_node and - self.relationship == node_type): + sub_node = graph[related_node.pk] + sub_node['metadata']['parent'] = parent_node + if not find and parent == workflow_node and sub == related_node and self.relationship == node_type: find = True if not find: - graph.add_edge(parent, sub, self.relationship) - - if graph.cycle_detected(): - return {"Error": "Cycle detected!"} - - if graph.multi_ancestor_detected(): - return {"Error": "Multiple ancestor detected!"} + sub_node = graph[sub.pk] + parent_node = graph[parent.pk] + if sub_node['metadata']['parent'] is not None: + return {"Error": "Multiple ancestor detected!"} + iter_node = parent_node + while iter_node is not None: + if iter_node == sub_node: + return {"Error": "Cycle detected!"} + iter_node = iter_node['metadata']['parent'] return None diff --git a/awx/main/scheduler/dag_simple.py b/awx/main/scheduler/dag_simple.py index e629eccd98..c6aa6247c0 100644 --- a/awx/main/scheduler/dag_simple.py +++ b/awx/main/scheduler/dag_simple.py @@ -137,53 +137,3 @@ class SimpleDAG(object): if len(self.get_dependents(n['node_object'])) < 1: roots.append(n) return roots - - def _find_cycle(self, node): - stack = [node] - node['metadata']['color'] = 'gray' - - while len(stack) > 0: - if stack[-1]['metadata']['count'] == len(stack[-1]['metadata']['adj_list']): - stack[-1]['metadata']['color'] = 'black' - stack.pop() - else: - to_push = stack[-1]['metadata']['adj_list'][stack[-1]['metadata']['count']] - stack[-1]['metadata']['count'] += 1 - if to_push['metadata']['color'] == 'gray': - return True - elif to_push['metadata']['color'] == 'white': - to_push['metadata']['color'] = 'gray' - stack.append(to_push) - - return False - - def _clean_meta(self): - for node in self.nodes: - node['metadata'] = None - - def cycle_detected(self): - for node in self.nodes: - node['metadata'] = {"adj_list": [], "color": "white", "count": 0} - for edge in self.edges: - self.nodes[edge[0]]['metadata']['adj_list'].append(self.nodes[edge[1]]) - - for node in self.nodes: - if node['metadata']['color'] == 'white' and self._find_cycle(node): - self._clean_meta() - return True - - self._clean_meta() - return False - - def multi_ancestor_detected(self): - for node in self.nodes: - node['metadata'] = {"ancestor": None} - for edge in self.edges: - if self.nodes[edge[1]]['metadata']['ancestor'] is None: - self.nodes[edge[1]]['metadata']['ancestor'] = self.nodes[edge[0]] - else: - self._clean_meta() - return True - - self._clean_meta() - return False