# Copyright (c) 2014 OpenStack Foundation. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import semantic_version import six from murano.dsl import helpers class CongressRulesManager(object): """Converts murano model to list of congress rules: - murano:objects+(env_id, obj_id, type_name) - murano:properties+(obj_id, prop_name, prop_value) - murano:relationships+(source, target, name) - murano:parent_types+(obj_id, parent_name) - murano:states+(env_id, state) """ _rules = [] _env_id = '' _package_loader = None def convert(self, model, package_loader=None, tenant_id=None): self._rules = [] self._package_loader = package_loader if model is None: return self._rules self._env_id = model['?']['id'] state_rule = StateRule(self._env_id, 'pending') self._rules.append(state_rule) self._walk(model, owner_id=tenant_id) # Convert MuranoProperty containing reference to another object # to MuranoRelationship. object_ids = [rule.obj_id for rule in self._rules if isinstance(rule, ObjectRule)] self._rules = [self._create_relationship(rule, object_ids) for rule in self._rules] relations = [(rel.source_id, rel.target_id) for rel in self._rules if isinstance(rel, RelationshipRule)] closure = self.transitive_closure(relations) for rel in closure: self._rules.append(ConnectedRule(rel[0], rel[1])) return self._rules @staticmethod def transitive_closure(relations): """Computes transitive closure on a directed graph. In other words computes reachability within the graph. E.g. {(1, 2), (2, 3)} -> {(1, 2), (2, 3), (1, 3)} (1, 3) was added because there is path from 1 to 3 in the graph. :param relations: list of relations/edges in form of tuples :return: transitive closure including original relations """ closure = set(relations) while True: # Attempts to discover new transitive relations # by joining 2 subsequent relations/edges within the graph. new_relations = {(x, w) for x, y in closure for q, w in closure if q == y} # Creates union with already discovered relations. closure_until_now = closure | new_relations # If no new relations were discovered in last cycle # the computation is finished. if closure_until_now == closure: return closure closure = closure_until_now def _walk(self, obj, owner_id, path=()): if obj is None: return obj = self._to_dict(obj) new_owner = self._process_item(obj, owner_id, path) or owner_id if isinstance(obj, list) or isinstance(obj, tuple): for v in obj: self._walk(v, new_owner, path) elif isinstance(obj, dict): for key, value in six.iteritems(obj): self._walk(value, new_owner, path + (key, )) def _process_item(self, obj, owner_id, path): if isinstance(obj, dict) and '?' in obj: obj_rule = self._create_object_rule(obj, owner_id) self._rules.append(obj_rule) # the environment has 'services' relationships # to all its top-level applications # traversal path is used to test whether # we are at the right place within the tree if path == ('applications',): self._rules.append(RelationshipRule(self._env_id, obj_rule.obj_id, "services")) self._rules.extend( self._create_propety_rules(obj_rule.obj_id, obj)) cls = obj['?']['type'] if 'classVersion' in obj['?']: version_spec = helpers.parse_version_spec( semantic_version.Version(obj['?']['classVersion'])) else: version_spec = semantic_version.Spec('*') types = self._get_parent_types( cls, self._package_loader, version_spec) self._rules.extend(self._create_parent_type_rules(obj['?']['id'], types)) # current object will be the owner for its subtree return obj_rule.obj_id @staticmethod def _to_dict(obj): # If we have MuranoObject class we need to convert to dictionary. if 'to_dictionary' in dir(obj): return obj.to_dictionary() else: return obj def _create_object_rule(self, app, owner_id): return ObjectRule(app['?']['id'], owner_id, app['?']['type']) def _create_propety_rules(self, obj_id, obj, prefix=""): rules = [] # Skip when inside properties of other object. if '?' in obj and prefix != "": rules.append(RelationshipRule(obj_id, obj['?']['id'], prefix.split('.')[0])) return rules for key, value in six.iteritems(obj): if key == '?': continue if value is not None: value = self._to_dict(value) if isinstance(value, dict): rules.extend(self._create_propety_rules( obj_id, value, prefix + key + ".")) elif isinstance(value, list) or isinstance(obj, tuple): for v in value: v = self._to_dict(v) if not isinstance(v, dict): rule = PropertyRule(obj_id, prefix + key, v) rules.append(rule) else: rule = PropertyRule(obj_id, prefix + key, value) rules.append(rule) return rules @staticmethod def _is_relationship(rule, app_ids): if not isinstance(rule, PropertyRule): return False return rule.prop_value in app_ids def _create_relationship(self, rule, app_ids): if self._is_relationship(rule, app_ids): return RelationshipRule(rule.obj_id, rule.prop_value, rule.prop_name) else: return rule @staticmethod def _get_parent_types(type_name, package_loader, version_spec): result = {type_name} if package_loader: pkg = package_loader.load_class_package(type_name, version_spec) cls = pkg.find_class(type_name, False) if cls: result.update(t.name for t in cls.ancestors()) return result @staticmethod def _create_parent_type_rules(app_id, types): rules = [] for type_name in types: rules.append(ParentTypeRule(app_id, type_name)) return rules class ObjectRule(object): def __init__(self, obj_id, owner_id, type_name): self.obj_id = obj_id self.owner_id = owner_id self.type_name = type_name def __str__(self): return 'murano:objects+("{0}", "{1}", "{2}")'.format(self.obj_id, self.owner_id, self.type_name) class PropertyRule(object): def __init__(self, obj_id, prop_name, prop_value): self.obj_id = obj_id self.prop_name = prop_name self.prop_value = prop_value def __str__(self): return 'murano:properties+("{0}", "{1}", "{2}")'.format( self.obj_id, self.prop_name, self.prop_value) class RelationshipRule(object): def __init__(self, source_id, target_id, rel_name): self.source_id = source_id self.target_id = target_id self.rel_name = rel_name def __str__(self): return 'murano:relationships+("{0}", "{1}", "{2}")'.format( self.source_id, self.target_id, self.rel_name) class ConnectedRule(object): def __init__(self, source_id, target_id): self.source_id = source_id self.target_id = target_id def __str__(self): return 'murano:connected+("{0}", "{1}")'.format( self.source_id, self.target_id) class ParentTypeRule(object): def __init__(self, obj_id, type_name): self.obj_id = obj_id self.type_name = type_name def __str__(self): return 'murano:parent_types+("{0}", "{1}")'.format(self.obj_id, self.type_name) class StateRule(object): def __init__(self, obj_id, state): self.obj_id = obj_id self.state = state def __str__(self): return 'murano:states+("{0}", "{1}")'.format(self.obj_id, self.state)