Merge "Adds per-class configs"

This commit is contained in:
Jenkins 2014-12-18 09:19:40 +00:00 committed by Gerrit Code Review
commit fc0a050c51
15 changed files with 166 additions and 74 deletions

View File

@ -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"))

View File

@ -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)

View File

@ -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

View File

@ -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']

View File

@ -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)

View File

@ -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(

View File

@ -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 {}

View File

@ -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

View File

@ -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):

View File

@ -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 = {}

View 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)

View File

@ -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()

View 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
)

View File

@ -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)

View File

@ -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}}