bc8943c8cc
Closes-Bug: #1563270 Change-Id: I987d846f1a86ecee1fcfb0f99877563a10481478
271 lines
9.4 KiB
Python
271 lines
9.4 KiB
Python
# 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
|
|
|
|
The Congress rules are:
|
|
- 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)
|