murano/murano/policy/congress_rules.py

272 lines
9.5 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 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_property_rules(obj_rule.obj_id, obj))
cls = obj['?']['type']
if 'classVersion' in obj['?']:
version_spec = obj['?']['classVersion']
else:
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_property_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_property_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):
type_name, version_spec, _ = helpers.parse_type_string(
type_name, version_spec, None)
version_spec = helpers.parse_version_spec(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 = helpers.parse_type_string(type_name, None, None)[0]
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)