Merge "Adds per-class configs"
This commit is contained in:
commit
fc0a050c51
@ -179,6 +179,8 @@ stats_opts = [
|
||||
engine_opts = [
|
||||
cfg.BoolOpt('disable_murano_agent', default=False,
|
||||
help=_('Disallow the use of murano-agent')),
|
||||
cfg.StrOpt('class_configs', default='/etc/murano/class-configs',
|
||||
help=_('Path to class configuration files')),
|
||||
cfg.BoolOpt('use_trusts', default=False,
|
||||
help=_("Create resources using trust token rather "
|
||||
"than user's token"))
|
||||
|
@ -73,7 +73,7 @@ class MuranoClassLoader(object):
|
||||
|
||||
properties = data.get('Properties', {})
|
||||
for property_name, property_spec in properties.iteritems():
|
||||
spec = typespec.PropertySpec(property_spec, ns_resolver)
|
||||
spec = typespec.PropertySpec(property_spec, type_obj)
|
||||
type_obj.add_property(property_name, spec)
|
||||
|
||||
methods = data.get('Methods') or data.get('Workflow') or {}
|
||||
@ -95,6 +95,9 @@ class MuranoClassLoader(object):
|
||||
def create_root_context(self):
|
||||
return yaql.create_context(True)
|
||||
|
||||
def get_class_config(self, name):
|
||||
return {}
|
||||
|
||||
def create_local_context(self, parent_context, murano_class):
|
||||
return yaql.context.Context(parent_context=parent_context)
|
||||
|
||||
|
@ -38,6 +38,7 @@ class MuranoClass(object):
|
||||
self._namespace_resolver = namespace_resolver
|
||||
self._name = namespace_resolver.resolve_name(name)
|
||||
self._properties = {}
|
||||
self._config = {}
|
||||
if self._name == 'io.murano.Object':
|
||||
self._parents = []
|
||||
else:
|
||||
@ -74,8 +75,7 @@ class MuranoClass(object):
|
||||
return self._methods.get(name)
|
||||
|
||||
def add_method(self, name, payload):
|
||||
method = murano_method.MuranoMethod(self._namespace_resolver,
|
||||
self, name, payload)
|
||||
method = murano_method.MuranoMethod(self, name, payload)
|
||||
self._methods[name] = method
|
||||
return method
|
||||
|
||||
|
@ -40,10 +40,9 @@ def methodusage(usage):
|
||||
|
||||
|
||||
class MuranoMethod(object):
|
||||
def __init__(self, namespace_resolver,
|
||||
murano_class, name, payload):
|
||||
def __init__(self, murano_class, name, payload):
|
||||
self._name = name
|
||||
self._namespace_resolver = namespace_resolver
|
||||
self._murano_class = murano_class
|
||||
|
||||
if callable(payload):
|
||||
self._body = payload
|
||||
@ -65,9 +64,7 @@ class MuranoMethod(object):
|
||||
raise ValueError()
|
||||
name = record.keys()[0]
|
||||
self._arguments_scheme[name] = typespec.ArgumentSpec(
|
||||
record[name], self._namespace_resolver)
|
||||
|
||||
self._murano_class = murano_class
|
||||
record[name], murano_class)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -99,8 +96,7 @@ class MuranoMethod(object):
|
||||
for i in xrange(len(defaults)):
|
||||
data[i + len(data) - len(defaults)][1]['Default'] = defaults[i]
|
||||
result = collections.OrderedDict([
|
||||
(name, typespec.ArgumentSpec(
|
||||
declaration, self._namespace_resolver))
|
||||
(name, typespec.ArgumentSpec(declaration, self.murano_class))
|
||||
for name, declaration in data])
|
||||
if '_context' in result:
|
||||
del result['_context']
|
||||
|
@ -36,6 +36,10 @@ class MuranoObject(object):
|
||||
self.__context = context
|
||||
self.__defaults = defaults or {}
|
||||
self.__this = this
|
||||
self.__config = object_store.class_loader.get_class_config(
|
||||
murano_class.name)
|
||||
if not isinstance(self.__config, dict):
|
||||
self.__config = {}
|
||||
known_classes[murano_class.name] = self
|
||||
for parent_class in murano_class.parents:
|
||||
name = parent_class.name
|
||||
@ -51,9 +55,20 @@ class MuranoObject(object):
|
||||
|
||||
def initialize(self, **kwargs):
|
||||
used_names = set()
|
||||
for property_name in self.__type.properties:
|
||||
spec = self.__type.get_property(property_name)
|
||||
if spec.usage == typespec.PropertyUsages.Config:
|
||||
if property_name in self.__config:
|
||||
property_value = self.__config[property_name]
|
||||
else:
|
||||
property_value = type_scheme.NoValue
|
||||
self.set_property(property_name, property_value)
|
||||
|
||||
for i in xrange(2):
|
||||
for property_name in self.__type.properties:
|
||||
spec = self.__type.get_property(property_name)
|
||||
if spec.usage == typespec.PropertyUsages.Config:
|
||||
continue
|
||||
needs_evaluation = murano.dsl.helpers.needs_evaluation
|
||||
if i == 0 and needs_evaluation(spec.default) or i == 1\
|
||||
and property_name in used_names:
|
||||
@ -137,7 +152,8 @@ class MuranoObject(object):
|
||||
or not derived:
|
||||
raise exceptions.NoWriteAccessError(name)
|
||||
|
||||
default = self.__defaults.get(name, spec.default)
|
||||
default = self.__config.get(name, spec.default)
|
||||
default = self.__defaults.get(name, default)
|
||||
child_context = yaql.context.Context(
|
||||
parent_context=self.__context)
|
||||
child_context.set_data(self)
|
||||
|
@ -22,17 +22,18 @@ class PropertyUsages(object):
|
||||
InOut = 'InOut'
|
||||
Runtime = 'Runtime'
|
||||
Const = 'Const'
|
||||
All = set([In, Out, InOut, Runtime, Const])
|
||||
Config = 'Config'
|
||||
All = set([In, Out, InOut, Runtime, Const, Config])
|
||||
Writable = set([Out, InOut, Runtime])
|
||||
|
||||
|
||||
class Spec(object):
|
||||
def __init__(self, declaration, namespace_resolver):
|
||||
self._namespace_resolver = namespace_resolver
|
||||
def __init__(self, declaration, owner_class):
|
||||
self._namespace_resolver = owner_class.namespace_resolver
|
||||
self._contract = type_scheme.TypeScheme(declaration['Contract'])
|
||||
self._usage = declaration.get('Usage') or 'In'
|
||||
self._default = declaration.get('Default')
|
||||
self._has_default = 'Default' in declaration
|
||||
self._usage = declaration.get('Usage') or 'In'
|
||||
if self._usage not in PropertyUsages.All:
|
||||
raise exceptions.DslSyntaxError(
|
||||
'Unknown type {0}. Must be one of ({1})'.format(
|
||||
|
@ -13,9 +13,12 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import json
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
from oslo.config import cfg
|
||||
import yaml
|
||||
|
||||
from murano.dsl import class_loader
|
||||
from murano.dsl import exceptions
|
||||
@ -70,3 +73,14 @@ class PackageClassLoader(class_loader.MuranoClassLoader):
|
||||
context = super(PackageClassLoader, self).create_root_context()
|
||||
yaql_functions.register(context)
|
||||
return context
|
||||
|
||||
def get_class_config(self, name):
|
||||
json_config = os.path.join(CONF.engine.class_configs, name + '.json')
|
||||
if os.path.exists(json_config):
|
||||
with open(json_config) as f:
|
||||
return json.load(f)
|
||||
yaml_config = os.path.join(CONF.engine.class_configs, name + '.yaml')
|
||||
if os.path.exists(yaml_config):
|
||||
with open(yaml_config) as f:
|
||||
return yaml.safe_load(f)
|
||||
return {}
|
||||
|
@ -1,48 +0,0 @@
|
||||
# Copyright (c) 2013 Mirantis Inc.
|
||||
#
|
||||
# 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 os.path
|
||||
|
||||
import yaml
|
||||
|
||||
import murano.dsl.class_loader as class_loader
|
||||
import murano.dsl.yaql_expression as yaql_expression
|
||||
import murano.engine.system.yaql_functions as yaql_functions
|
||||
|
||||
|
||||
def yaql_constructor(loader, node):
|
||||
value = loader.construct_scalar(node)
|
||||
return yaql_expression.YaqlExpression(value)
|
||||
|
||||
yaml.add_constructor(u'!yaql', yaql_constructor)
|
||||
yaml.add_implicit_resolver(u'!yaql', yaql_expression.YaqlExpression)
|
||||
|
||||
|
||||
class SimpleClassLoader(class_loader.MuranoClassLoader):
|
||||
def __init__(self, base_path):
|
||||
self._base_path = base_path
|
||||
super(SimpleClassLoader, self).__init__()
|
||||
|
||||
def load_definition(self, name):
|
||||
path = os.path.join(self._base_path, name, 'manifest.yaml')
|
||||
if not os.path.exists(path):
|
||||
return None
|
||||
with open(path) as stream:
|
||||
return yaml.load(stream)
|
||||
|
||||
def create_root_context(self):
|
||||
context = super(SimpleClassLoader, self).create_root_context()
|
||||
yaql_functions.register(context)
|
||||
return context
|
@ -38,6 +38,7 @@ class DslTestCase(base.MuranoTestCase):
|
||||
self.register_function(
|
||||
lambda data: self._traces.append(data()), 'trace')
|
||||
self._traces = []
|
||||
test_class_loader.TestClassLoader.clear_configs()
|
||||
eventlet.debug.hub_exceptions(False)
|
||||
|
||||
def new_runner(self, model):
|
||||
|
@ -22,10 +22,12 @@ from murano.dsl import murano_package
|
||||
from murano.dsl import namespace_resolver
|
||||
from murano.engine.system import yaql_functions
|
||||
from murano.engine import yaql_yaml_loader
|
||||
from murano.tests.unit.dsl.foundation import object_model
|
||||
|
||||
|
||||
class TestClassLoader(class_loader.MuranoClassLoader):
|
||||
_classes_cache = {}
|
||||
_configs = {}
|
||||
|
||||
def __init__(self, directory, package_name, parent_loader=None):
|
||||
self._package = murano_package.MuranoPackage()
|
||||
@ -90,3 +92,16 @@ class TestClassLoader(class_loader.MuranoClassLoader):
|
||||
|
||||
def register_function(self, func, name):
|
||||
self._functions[name] = func
|
||||
|
||||
def get_class_config(self, name):
|
||||
return TestClassLoader._configs.get(name, {})
|
||||
|
||||
def set_config_value(self, class_name, property_name, value):
|
||||
if isinstance(class_name, object_model.Object):
|
||||
class_name = class_name.type_name
|
||||
TestClassLoader._configs.setdefault(class_name, {})[
|
||||
property_name] = value
|
||||
|
||||
@staticmethod
|
||||
def clear_configs():
|
||||
TestClassLoader._configs = {}
|
||||
|
17
murano/tests/unit/dsl/meta/ConfigProperties.yaml
Normal file
17
murano/tests/unit/dsl/meta/ConfigProperties.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
Name: ConfigProperties
|
||||
|
||||
Properties:
|
||||
cfgProperty:
|
||||
Usage: Config
|
||||
Contract: $.int().notNull()
|
||||
Default: 123
|
||||
|
||||
normalProperty:
|
||||
Contract: $.string().notNull()
|
||||
Default: DEFAULT
|
||||
|
||||
Methods:
|
||||
testPropertyValues:
|
||||
Body:
|
||||
- trace($.cfgProperty)
|
||||
- trace($.normalProperty)
|
@ -24,6 +24,9 @@ Properties:
|
||||
usageTestProperty6:
|
||||
Contract: $.int()
|
||||
Usage: Const
|
||||
usageTestProperty7:
|
||||
Contract: $.int()
|
||||
Usage: Config
|
||||
|
||||
|
||||
Methods:
|
||||
@ -79,6 +82,12 @@ Methods:
|
||||
- $.usageTestProperty6: 66
|
||||
- Return: $.usageTestProperty6
|
||||
|
||||
testModifyUsageTestProperty7:
|
||||
Body:
|
||||
- $.usageTestProperty7: 77
|
||||
- Return: $.usageTestProperty7
|
||||
|
||||
|
||||
testMixinOverride:
|
||||
Body:
|
||||
- $.virtualMethod()
|
||||
|
57
murano/tests/unit/dsl/test_config_properties.py
Normal file
57
murano/tests/unit/dsl/test_config_properties.py
Normal file
@ -0,0 +1,57 @@
|
||||
# Copyright (c) 2014 Mirantis, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from murano.tests.unit.dsl.foundation import object_model as om
|
||||
from murano.tests.unit.dsl.foundation import test_case
|
||||
|
||||
|
||||
class TestConfigProperties(test_case.DslTestCase):
|
||||
def test_config_property(self):
|
||||
obj = om.Object('ConfigProperties')
|
||||
self.class_loader.set_config_value(obj, 'cfgProperty', '987')
|
||||
runner = self.new_runner(obj)
|
||||
runner.testPropertyValues()
|
||||
self.assertEqual(
|
||||
[987, 'DEFAULT'],
|
||||
self.traces
|
||||
)
|
||||
|
||||
def test_config_property_exclusion_from_obect_model(self):
|
||||
obj = om.Object('ConfigProperties', cfgProperty=555)
|
||||
runner = self.new_runner(obj)
|
||||
runner.testPropertyValues()
|
||||
self.assertEqual(
|
||||
[123, 'DEFAULT'],
|
||||
self.traces
|
||||
)
|
||||
|
||||
def test_config_affects_default(self):
|
||||
obj = om.Object('ConfigProperties')
|
||||
self.class_loader.set_config_value(obj, 'normalProperty', 'custom')
|
||||
runner = self.new_runner(obj)
|
||||
runner.testPropertyValues()
|
||||
self.assertEqual(
|
||||
[123, 'custom'],
|
||||
self.traces
|
||||
)
|
||||
|
||||
def test_config_not_affects_in_properties(self):
|
||||
obj = om.Object('ConfigProperties', normalProperty='qq')
|
||||
self.class_loader.set_config_value(obj, 'normalProperty', 'custom')
|
||||
runner = self.new_runner(obj)
|
||||
runner.testPropertyValues()
|
||||
self.assertEqual(
|
||||
[123, 'qq'],
|
||||
self.traces
|
||||
)
|
@ -113,3 +113,7 @@ class TestPropertyAccess(test_case.DslTestCase):
|
||||
exceptions.NoWriteAccessError,
|
||||
self._runner.on(self._multi_derived).
|
||||
testModifyUsageTestProperty6)
|
||||
self.assertRaises(
|
||||
exceptions.NoWriteAccessError,
|
||||
self._runner.on(self._multi_derived).
|
||||
testModifyUsageTestProperty7)
|
||||
|
@ -16,7 +16,9 @@
|
||||
from heatclient.v1 import stacks
|
||||
import mock
|
||||
|
||||
from murano.dsl import murano_object
|
||||
from murano.dsl import class_loader
|
||||
from murano.dsl import murano_class
|
||||
from murano.dsl import object_store
|
||||
from murano.engine import client_manager
|
||||
from murano.engine.system import heat_stack
|
||||
from murano.tests.unit import base
|
||||
@ -28,11 +30,14 @@ MOD_NAME = 'murano.engine.system.heat_stack'
|
||||
class TestHeatStack(base.MuranoTestCase):
|
||||
def setUp(self):
|
||||
super(TestHeatStack, self).setUp()
|
||||
self.mock_murano_obj = mock.Mock(spec=murano_object.MuranoObject)
|
||||
self.mock_murano_obj.name = 'TestObj'
|
||||
self.mock_murano_obj.parents = []
|
||||
self.mock_murano_class = mock.Mock(spec=murano_class.MuranoClass)
|
||||
self.mock_murano_class.name = 'io.murano.system.HeatStack'
|
||||
self.mock_murano_class.parents = []
|
||||
self.heat_client_mock = mock.MagicMock()
|
||||
self.heat_client_mock.stacks = mock.MagicMock(spec=stacks.StackManager)
|
||||
self.mock_object_store = mock.Mock(spec=object_store.ObjectStore)
|
||||
self.mock_object_store.class_loader = mock.Mock(
|
||||
spec=class_loader.MuranoClassLoader)
|
||||
self.client_manager_mock = mock.Mock(
|
||||
spec=client_manager.ClientManager)
|
||||
|
||||
@ -49,8 +54,8 @@ class TestHeatStack(base.MuranoTestCase):
|
||||
status_get.return_value = 'NOT_FOUND'
|
||||
wait_st.return_value = {}
|
||||
|
||||
hs = heat_stack.HeatStack(self.mock_murano_obj,
|
||||
None, None, None)
|
||||
hs = heat_stack.HeatStack(self.mock_murano_class,
|
||||
None, self.mock_object_store, None)
|
||||
hs._name = 'test-stack'
|
||||
hs._description = 'Generated by TestHeatStack'
|
||||
hs._template = {'resources': {'test': 1}}
|
||||
@ -82,8 +87,8 @@ class TestHeatStack(base.MuranoTestCase):
|
||||
status_get.return_value = 'NOT_FOUND'
|
||||
wait_st.return_value = {}
|
||||
|
||||
hs = heat_stack.HeatStack(self.mock_murano_obj,
|
||||
None, None, None)
|
||||
hs = heat_stack.HeatStack(self.mock_murano_class,
|
||||
None, self.mock_object_store, None)
|
||||
hs._clients = self.client_manager_mock
|
||||
hs._name = 'test-stack'
|
||||
hs._description = None
|
||||
@ -107,8 +112,8 @@ class TestHeatStack(base.MuranoTestCase):
|
||||
def test_update_wrong_template_version(self):
|
||||
"""Template version other than expected should cause error."""
|
||||
|
||||
hs = heat_stack.HeatStack(self.mock_murano_obj,
|
||||
None, None, None)
|
||||
hs = heat_stack.HeatStack(self.mock_murano_class,
|
||||
None, self.mock_object_store, None)
|
||||
hs._name = 'test-stack'
|
||||
hs._description = 'Generated by TestHeatStack'
|
||||
hs._template = {'resources': {'test': 1}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user