Adds ability to throw/catch/rethrow exceptions in MuranoPL
The syntax is Try: - Throw: ns:name #can be list of names to simulate type hierarchy Message: message #optional Cause: $sourceException #optional Extra: { 'someExtra': 'data' } #optional Catch: - With: ns:name #can be list of names - As: exception #optional - Do: - Rethrow: Else: #optional - else block Finally: #optional - finally block Improves stack traces to contain information about Python native stack frames and macro blocks Change-Id: I2e2bcc5e1a0da5f9489d73525f8b3fa99cc0220c Implements: blueprint muranopl-exception-handling
This commit is contained in:
29
meta/io.murano/Classes/Exception.yaml
Normal file
29
meta/io.murano/Classes/Exception.yaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
Namespaces:
|
||||||
|
=: io.murano
|
||||||
|
|
||||||
|
Name: Exception
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
name:
|
||||||
|
Contract: $.string()
|
||||||
|
Usage: Runtime
|
||||||
|
|
||||||
|
message:
|
||||||
|
Contract: $.string()
|
||||||
|
Usage: Runtime
|
||||||
|
|
||||||
|
stackTrace:
|
||||||
|
Contract: $
|
||||||
|
Usage: Runtime
|
||||||
|
|
||||||
|
extra:
|
||||||
|
Contract: {}
|
||||||
|
Usage: Runtime
|
||||||
|
|
||||||
|
nativeException:
|
||||||
|
Contract: $
|
||||||
|
Usage: Runtime
|
||||||
|
|
||||||
|
cause:
|
||||||
|
Contract: $.class(Exception)
|
||||||
|
Usage: Runtime
|
12
meta/io.murano/Classes/StackTrace.yaml
Normal file
12
meta/io.murano/Classes/StackTrace.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Namespaces:
|
||||||
|
=: io.murano
|
||||||
|
|
||||||
|
Name: StackTrace
|
||||||
|
|
||||||
|
Properties:
|
||||||
|
frames:
|
||||||
|
Contract:
|
||||||
|
- instruction: $.string()
|
||||||
|
location: $
|
||||||
|
method: $
|
||||||
|
class: $
|
@@ -17,6 +17,8 @@ Classes:
|
|||||||
io.murano.Object: Object.yaml
|
io.murano.Object: Object.yaml
|
||||||
io.murano.Environment: Environment.yaml
|
io.murano.Environment: Environment.yaml
|
||||||
io.murano.Application: Application.yaml
|
io.murano.Application: Application.yaml
|
||||||
|
io.murano.Exception: Exception.yaml
|
||||||
|
io.murano.StackTrace: StackTrace.yaml
|
||||||
io.murano.SharedIp: SharedIp.yaml
|
io.murano.SharedIp: SharedIp.yaml
|
||||||
|
|
||||||
io.murano.system.SecurityGroupManager: SecurityGroupManager.yaml
|
io.murano.system.SecurityGroupManager: SecurityGroupManager.yaml
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import anyjson
|
import anyjson
|
||||||
|
import eventlet.debug
|
||||||
from oslo import messaging
|
from oslo import messaging
|
||||||
from oslo.messaging import target
|
from oslo.messaging import target
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ from murano.common.helpers import token_sanitizer
|
|||||||
from murano.common import rpc
|
from murano.common import rpc
|
||||||
from murano.dsl import executor
|
from murano.dsl import executor
|
||||||
from murano.dsl import results_serializer
|
from murano.dsl import results_serializer
|
||||||
|
from murano.dsl import virtual_exceptions
|
||||||
from murano.engine import environment
|
from murano.engine import environment
|
||||||
from murano.engine import package_class_loader
|
from murano.engine import package_class_loader
|
||||||
from murano.engine import package_loader
|
from murano.engine import package_loader
|
||||||
@@ -36,6 +38,8 @@ RPC_SERVICE = None
|
|||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
eventlet.debug.hub_exceptions(False)
|
||||||
|
|
||||||
|
|
||||||
class TaskProcessingEndpoint(object):
|
class TaskProcessingEndpoint(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -118,7 +122,10 @@ class TaskExecutor(object):
|
|||||||
if self.action:
|
if self.action:
|
||||||
self._invoke(exc)
|
self._invoke(exc)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.warn(e, exc_info=1)
|
if isinstance(e, virtual_exceptions.MuranoPlException):
|
||||||
|
LOG.error(e.format())
|
||||||
|
else:
|
||||||
|
LOG.exception(e)
|
||||||
reporter = status_reporter.StatusReporter()
|
reporter = status_reporter.StatusReporter()
|
||||||
reporter.initialize(obj)
|
reporter.initialize(obj)
|
||||||
reporter.report_error(obj, str(e))
|
reporter.report_error(obj, str(e))
|
||||||
|
76
murano/dsl/dsl_exception.py
Normal file
76
murano/dsl/dsl_exception.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import murano.dsl.yaql_functions as yaql_functions
|
||||||
|
|
||||||
|
|
||||||
|
class MuranoPlException(Exception):
|
||||||
|
def __init__(self, names, message, stacktrace, extra=None, cause=None):
|
||||||
|
super(MuranoPlException, self).__init__(
|
||||||
|
'{0}: {1}'.format(names, message))
|
||||||
|
if not isinstance(names, list):
|
||||||
|
names = [names]
|
||||||
|
self._names = names
|
||||||
|
self._message = message
|
||||||
|
self._stacktrace = stacktrace
|
||||||
|
self._extra = extra or {}
|
||||||
|
self._cause = cause
|
||||||
|
|
||||||
|
@property
|
||||||
|
def names(self):
|
||||||
|
return self._names
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self):
|
||||||
|
return self._message
|
||||||
|
|
||||||
|
@property
|
||||||
|
def stacktrace(self):
|
||||||
|
return self._stacktrace
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extra(self):
|
||||||
|
return self._extra
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cause(self):
|
||||||
|
return self._cause
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_python_exception(exception, context):
|
||||||
|
stacktrace = yaql_functions.new('io.murano.StackTrace', context)
|
||||||
|
exception_type = type(exception)
|
||||||
|
names = ['{0}.{1}'.format(exception_type.__module__,
|
||||||
|
exception_type.__name__)]
|
||||||
|
|
||||||
|
return MuranoPlException(
|
||||||
|
names, exception.message, stacktrace)
|
||||||
|
|
||||||
|
def _format_name(self):
|
||||||
|
if not self._names:
|
||||||
|
return ''
|
||||||
|
elif len(self._names) == 1:
|
||||||
|
return self._names[0]
|
||||||
|
else:
|
||||||
|
return self._names
|
||||||
|
|
||||||
|
def format(self, prefix=' '):
|
||||||
|
text = '\n{3}{0}: {1}\n' \
|
||||||
|
'{3}Traceback (most recent call last):\n' \
|
||||||
|
'{2}'.format(self._format_name(), self.message,
|
||||||
|
self.stacktrace.toString(prefix + ' '), prefix)
|
||||||
|
if self._cause is not None:
|
||||||
|
text += '\n\n{0} Caused by {1}'.format(
|
||||||
|
prefix, self._cause.format(prefix + ' ').lstrip())
|
||||||
|
return text
|
@@ -21,7 +21,9 @@ import eventlet
|
|||||||
import eventlet.event
|
import eventlet.event
|
||||||
import yaql.context
|
import yaql.context
|
||||||
|
|
||||||
|
|
||||||
import murano.dsl.attribute_store as attribute_store
|
import murano.dsl.attribute_store as attribute_store
|
||||||
|
import murano.dsl.dsl_exception as dsl_exception
|
||||||
import murano.dsl.expressions as expressions
|
import murano.dsl.expressions as expressions
|
||||||
import murano.dsl.helpers as helpers
|
import murano.dsl.helpers as helpers
|
||||||
import murano.dsl.murano_method as murano_method
|
import murano.dsl.murano_method as murano_method
|
||||||
@@ -150,12 +152,18 @@ class MuranoDslExecutor(object):
|
|||||||
if '_context' in inspect.getargspec(body).args:
|
if '_context' in inspect.getargspec(body).args:
|
||||||
params['_context'] = self._create_context(
|
params['_context'] = self._create_context(
|
||||||
this, murano_class, context, **params)
|
this, murano_class, context, **params)
|
||||||
if inspect.ismethod(body) and not body.__self__:
|
try:
|
||||||
return body(this, **params)
|
if inspect.ismethod(body) and not body.__self__:
|
||||||
else:
|
return body(this, **params)
|
||||||
return body(**params)
|
else:
|
||||||
|
return body(**params)
|
||||||
|
except Exception as e:
|
||||||
|
raise dsl_exception.MuranoPlException.from_python_exception(
|
||||||
|
e, context)
|
||||||
elif isinstance(body, expressions.DslExpression):
|
elif isinstance(body, expressions.DslExpression):
|
||||||
return self.execute(body, murano_class, this, context, **params)
|
return self.execute(
|
||||||
|
body, murano_class, this, context, **params)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
|
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
import types
|
import types
|
||||||
|
|
||||||
|
import murano.dsl.dsl_exception as dsl_exception
|
||||||
import murano.dsl.helpers as helpers
|
import murano.dsl.helpers as helpers
|
||||||
import murano.dsl.lhs_expression as lhs_expression
|
import murano.dsl.lhs_expression as lhs_expression
|
||||||
import murano.dsl.yaql_expression as yaql_expression
|
import murano.dsl.yaql_expression as yaql_expression
|
||||||
@@ -21,6 +22,15 @@ import murano.dsl.yaql_expression as yaql_expression
|
|||||||
_macros = []
|
_macros = []
|
||||||
|
|
||||||
|
|
||||||
|
class InstructionStub(object):
|
||||||
|
def __init__(self, title, position):
|
||||||
|
self._title = title
|
||||||
|
self.source_file_position = position
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self._title
|
||||||
|
|
||||||
|
|
||||||
def register_macro(cls):
|
def register_macro(cls):
|
||||||
_macros.append(cls)
|
_macros.append(cls)
|
||||||
|
|
||||||
@@ -55,11 +65,16 @@ class Statement(DslExpression):
|
|||||||
return self._expression
|
return self._expression
|
||||||
|
|
||||||
def execute(self, context, murano_class):
|
def execute(self, context, murano_class):
|
||||||
result = helpers.evaluate(self.expression, context)
|
try:
|
||||||
if self.destination:
|
result = helpers.evaluate(self.expression, context)
|
||||||
self.destination(result, context, murano_class)
|
if self.destination:
|
||||||
|
self.destination(result, context, murano_class)
|
||||||
return result
|
return result
|
||||||
|
except dsl_exception.MuranoPlException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise dsl_exception.MuranoPlException.from_python_exception(
|
||||||
|
e, context)
|
||||||
|
|
||||||
|
|
||||||
def parse_expression(expr):
|
def parse_expression(expr):
|
||||||
@@ -79,7 +94,16 @@ def parse_expression(expr):
|
|||||||
if result is None:
|
if result is None:
|
||||||
for cls in _macros:
|
for cls in _macros:
|
||||||
try:
|
try:
|
||||||
return cls(**kwds)
|
macro = cls(**kwds)
|
||||||
|
position = None
|
||||||
|
title = 'block construct'
|
||||||
|
if hasattr(expr, 'source_file_position'):
|
||||||
|
position = expr.source_file_position
|
||||||
|
if '__str__' in cls.__dict__:
|
||||||
|
title = str(macro)
|
||||||
|
macro.virtual_instruction = InstructionStub(
|
||||||
|
title, position)
|
||||||
|
return macro
|
||||||
except TypeError:
|
except TypeError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@@ -46,6 +46,14 @@ def serialize(value, memo=None):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def execute_instruction(instruction, action, context):
|
||||||
|
old_instruction = context.get_data('$?currentInstruction')
|
||||||
|
context.set_data(instruction, '?currentInstruction')
|
||||||
|
result = action()
|
||||||
|
context.set_data(old_instruction, '?currentInstruction')
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def evaluate(value, context, max_depth=sys.maxint):
|
def evaluate(value, context, max_depth=sys.maxint):
|
||||||
if isinstance(value, yaql.expressions.Expression):
|
if isinstance(value, yaql.expressions.Expression):
|
||||||
value = yaql_expression.YaqlExpression(value)
|
value = yaql_expression.YaqlExpression(value)
|
||||||
@@ -55,11 +63,7 @@ def evaluate(value, context, max_depth=sys.maxint):
|
|||||||
if max_depth <= 0:
|
if max_depth <= 0:
|
||||||
return func
|
return func
|
||||||
else:
|
else:
|
||||||
try:
|
return execute_instruction(value, func, context)
|
||||||
context.set_data(value, '?currentInstruction')
|
|
||||||
return func()
|
|
||||||
finally:
|
|
||||||
context.set_data(None, '?currentInstruction')
|
|
||||||
|
|
||||||
elif isinstance(value, types.DictionaryType):
|
elif isinstance(value, types.DictionaryType):
|
||||||
result = {}
|
result = {}
|
||||||
@@ -192,3 +196,7 @@ def get_current_instruction(context):
|
|||||||
|
|
||||||
def get_current_method(context):
|
def get_current_method(context):
|
||||||
return context.get_data('$?currentMethod')
|
return context.get_data('$?currentMethod')
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_exception(context):
|
||||||
|
return context.get_data('$?currentException')
|
||||||
|
@@ -17,6 +17,7 @@ import types
|
|||||||
import eventlet.greenpool as greenpool
|
import eventlet.greenpool as greenpool
|
||||||
import yaql.context
|
import yaql.context
|
||||||
|
|
||||||
|
import murano.dsl.dsl_exception as dsl_exception
|
||||||
import murano.dsl.exceptions as exceptions
|
import murano.dsl.exceptions as exceptions
|
||||||
import murano.dsl.expressions as expressions
|
import murano.dsl.expressions as expressions
|
||||||
import murano.dsl.helpers as helpers
|
import murano.dsl.helpers as helpers
|
||||||
@@ -33,7 +34,20 @@ class CodeBlock(expressions.DslExpression):
|
|||||||
def execute(self, context, murano_class):
|
def execute(self, context, murano_class):
|
||||||
try:
|
try:
|
||||||
for expr in self.code_block:
|
for expr in self.code_block:
|
||||||
expr.execute(context, murano_class)
|
def action():
|
||||||
|
try:
|
||||||
|
expr.execute(context, murano_class)
|
||||||
|
except dsl_exception.MuranoPlException:
|
||||||
|
raise
|
||||||
|
except Exception as ex:
|
||||||
|
raise dsl_exception.MuranoPlException.\
|
||||||
|
from_python_exception(ex, context)
|
||||||
|
|
||||||
|
if hasattr(expr, 'virtual_instruction'):
|
||||||
|
instruction = expr.virtual_instruction
|
||||||
|
helpers.execute_instruction(instruction, action, context)
|
||||||
|
else:
|
||||||
|
action()
|
||||||
except exceptions.BreakException as e:
|
except exceptions.BreakException as e:
|
||||||
if self._breakable:
|
if self._breakable:
|
||||||
raise e
|
raise e
|
||||||
@@ -47,16 +61,14 @@ class MethodBlock(CodeBlock):
|
|||||||
self._name = name
|
self._name = name
|
||||||
|
|
||||||
def execute(self, context, murano_class):
|
def execute(self, context, murano_class):
|
||||||
|
new_context = yaql.context.Context(context)
|
||||||
|
new_context.set_data(self._name, '?currentMethod')
|
||||||
try:
|
try:
|
||||||
context.set_data(self._name, '?currentMethod')
|
super(MethodBlock, self).execute(new_context, murano_class)
|
||||||
super(MethodBlock, self).execute(
|
|
||||||
context, murano_class)
|
|
||||||
except exceptions.ReturnException as e:
|
except exceptions.ReturnException as e:
|
||||||
return e.value
|
return e.value
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
finally:
|
|
||||||
context.set_data(None, '?currentMethod')
|
|
||||||
|
|
||||||
|
|
||||||
class ReturnMacro(expressions.DslExpression):
|
class ReturnMacro(expressions.DslExpression):
|
||||||
@@ -220,13 +232,14 @@ class DoMacro(expressions.DslExpression):
|
|||||||
self._code.execute(child_context, murano_class)
|
self._code.execute(child_context, murano_class)
|
||||||
|
|
||||||
|
|
||||||
expressions.register_macro(DoMacro)
|
def register():
|
||||||
expressions.register_macro(ReturnMacro)
|
expressions.register_macro(DoMacro)
|
||||||
expressions.register_macro(BreakMacro)
|
expressions.register_macro(ReturnMacro)
|
||||||
expressions.register_macro(ParallelMacro)
|
expressions.register_macro(BreakMacro)
|
||||||
expressions.register_macro(IfMacro)
|
expressions.register_macro(ParallelMacro)
|
||||||
expressions.register_macro(WhileDoMacro)
|
expressions.register_macro(IfMacro)
|
||||||
expressions.register_macro(ForMacro)
|
expressions.register_macro(WhileDoMacro)
|
||||||
expressions.register_macro(RepeatMacro)
|
expressions.register_macro(ForMacro)
|
||||||
expressions.register_macro(MatchMacro)
|
expressions.register_macro(RepeatMacro)
|
||||||
expressions.register_macro(SwitchMacro)
|
expressions.register_macro(MatchMacro)
|
||||||
|
expressions.register_macro(SwitchMacro)
|
||||||
|
@@ -17,6 +17,7 @@ import types
|
|||||||
|
|
||||||
import murano.dsl.macros as macros
|
import murano.dsl.macros as macros
|
||||||
import murano.dsl.typespec as typespec
|
import murano.dsl.typespec as typespec
|
||||||
|
import murano.dsl.virtual_exceptions as virtual_exceptions
|
||||||
import murano.dsl.yaql_expression as yaql_expression
|
import murano.dsl.yaql_expression as yaql_expression
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -25,6 +26,10 @@ except ImportError: # python2.6
|
|||||||
from ordereddict import OrderedDict # noqa
|
from ordereddict import OrderedDict # noqa
|
||||||
|
|
||||||
|
|
||||||
|
macros.register()
|
||||||
|
virtual_exceptions.register()
|
||||||
|
|
||||||
|
|
||||||
class MethodUsages(object):
|
class MethodUsages(object):
|
||||||
Action = 'Action'
|
Action = 'Action'
|
||||||
Runtime = 'Runtime'
|
Runtime = 'Runtime'
|
||||||
|
@@ -1,6 +1,28 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import murano.dsl.principal_objects.exception
|
||||||
|
import murano.dsl.principal_objects.stack_trace
|
||||||
import murano.dsl.principal_objects.sys_object
|
import murano.dsl.principal_objects.sys_object
|
||||||
|
|
||||||
|
|
||||||
def register(class_loader):
|
def register(class_loader):
|
||||||
sys_object = murano.dsl.principal_objects.sys_object
|
sys_object = murano.dsl.principal_objects.sys_object
|
||||||
class_loader.import_class(sys_object.SysObject)
|
class_loader.import_class(sys_object.SysObject)
|
||||||
|
|
||||||
|
stack_trace = murano.dsl.principal_objects.stack_trace
|
||||||
|
class_loader.import_class(stack_trace.StackTrace)
|
||||||
|
|
||||||
|
exception = murano.dsl.principal_objects.exception
|
||||||
|
class_loader.import_class(exception.DslException)
|
||||||
|
22
murano/dsl/principal_objects/exception.py
Normal file
22
murano/dsl/principal_objects/exception.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# 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.dsl import murano_class
|
||||||
|
from murano.dsl import murano_object
|
||||||
|
|
||||||
|
|
||||||
|
@murano_class.classname('io.murano.Exception')
|
||||||
|
class DslException(murano_object.MuranoObject):
|
||||||
|
def toString(self):
|
||||||
|
return self.get_property('nativeException').format()
|
99
murano/dsl/principal_objects/stack_trace.py
Normal file
99
murano/dsl/principal_objects/stack_trace.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from murano.dsl import helpers
|
||||||
|
from murano.dsl import murano_class
|
||||||
|
from murano.dsl import murano_object
|
||||||
|
from murano.dsl import yaql_expression
|
||||||
|
|
||||||
|
|
||||||
|
@murano_class.classname('io.murano.StackTrace')
|
||||||
|
class StackTrace(murano_object.MuranoObject):
|
||||||
|
def initialize(self, _context, includeNativeFrames=True):
|
||||||
|
frames = []
|
||||||
|
context = _context
|
||||||
|
while True:
|
||||||
|
if not context:
|
||||||
|
break
|
||||||
|
instruction = helpers.get_current_instruction(context)
|
||||||
|
frames.append({
|
||||||
|
'instruction': None if instruction is None
|
||||||
|
else str(instruction),
|
||||||
|
|
||||||
|
'location': None if instruction is None
|
||||||
|
else instruction.source_file_position,
|
||||||
|
|
||||||
|
'method': helpers.get_current_method(context),
|
||||||
|
'class': helpers.get_type(context)
|
||||||
|
})
|
||||||
|
context = helpers.get_caller_context(context)
|
||||||
|
frames.pop()
|
||||||
|
frames.reverse()
|
||||||
|
|
||||||
|
if includeNativeFrames:
|
||||||
|
class InstructionStub(object):
|
||||||
|
def __init__(self, title, position):
|
||||||
|
self._title = title
|
||||||
|
self.source_file_position = position
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self._title
|
||||||
|
|
||||||
|
native_frames = []
|
||||||
|
for frame in inspect.trace()[1:]:
|
||||||
|
info = inspect.getframeinfo(frame[0])
|
||||||
|
position = yaql_expression.YaqlExpressionFilePosition(
|
||||||
|
os.path.abspath(info.filename), info.lineno,
|
||||||
|
-1, -1, -1, -1, -1)
|
||||||
|
instruction = InstructionStub(
|
||||||
|
info.code_context[0].strip(), position)
|
||||||
|
method = info.function
|
||||||
|
native_frames.append({
|
||||||
|
'instruction': instruction,
|
||||||
|
'method': method,
|
||||||
|
'class': None
|
||||||
|
})
|
||||||
|
frames.extend(native_frames)
|
||||||
|
|
||||||
|
self.set_property('frames', frames)
|
||||||
|
|
||||||
|
def toString(self, prefix=''):
|
||||||
|
def format_frame(frame):
|
||||||
|
instruction = frame['instruction']
|
||||||
|
method = frame['method']
|
||||||
|
murano_class = frame['class']
|
||||||
|
location = frame['location']
|
||||||
|
|
||||||
|
if location:
|
||||||
|
args = (
|
||||||
|
os.path.abspath(location.file_path),
|
||||||
|
location.start_line,
|
||||||
|
':' + str(location.start_column)
|
||||||
|
if location.start_column >= 0 else '',
|
||||||
|
method,
|
||||||
|
instruction,
|
||||||
|
prefix,
|
||||||
|
'' if not murano_class else murano_class.name + '::'
|
||||||
|
)
|
||||||
|
return '{5}File "{0}", line {1}{2} in method {6}{3}\n' \
|
||||||
|
'{5} {4}'.format(*args)
|
||||||
|
else:
|
||||||
|
return '{2}File <unknown> in method {3}{0}\n{2} {1}'.format(
|
||||||
|
method, instruction, prefix,
|
||||||
|
'' if not murano_class else murano_class.name + '::')
|
||||||
|
|
||||||
|
return '\n'.join([format_frame(t)for t in self.get_property('frames')])
|
151
murano/dsl/virtual_exceptions.py
Normal file
151
murano/dsl/virtual_exceptions.py
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import murano.dsl.dsl_exception as dsl_exception
|
||||||
|
import murano.dsl.expressions as expressions
|
||||||
|
import murano.dsl.helpers as helpers
|
||||||
|
import murano.dsl.macros as macros
|
||||||
|
import murano.dsl.yaql_functions as yaql_functions
|
||||||
|
|
||||||
|
|
||||||
|
class ThrowMacro(expressions.DslExpression):
|
||||||
|
def __init__(self, Throw, Message=None, Extra=None, Cause=None):
|
||||||
|
if not Throw:
|
||||||
|
raise ValueError()
|
||||||
|
if not isinstance(Throw, list):
|
||||||
|
Throw = [Throw]
|
||||||
|
|
||||||
|
self._names = Throw
|
||||||
|
self._message = Message
|
||||||
|
self._extra = Extra or {}
|
||||||
|
self._cause = Cause
|
||||||
|
|
||||||
|
def _resolve_names(self, names, context):
|
||||||
|
murano_class = helpers.get_type(context)
|
||||||
|
for name in names:
|
||||||
|
yield murano_class.namespace_resolver.resolve_name(name)
|
||||||
|
|
||||||
|
def execute(self, context, murano_class):
|
||||||
|
stacktrace = yaql_functions.new('io.murano.StackTrace', context,
|
||||||
|
includeNativeFrames=False)
|
||||||
|
cause = None
|
||||||
|
if self._cause:
|
||||||
|
cause = helpers.evaluate(self._cause, context).get_property(
|
||||||
|
'nativeException')
|
||||||
|
raise dsl_exception.MuranoPlException(
|
||||||
|
list(self._resolve_names(helpers.evaluate(self._names, context),
|
||||||
|
context)),
|
||||||
|
helpers.evaluate(self._message, context),
|
||||||
|
stacktrace, self._extra, cause)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self._message:
|
||||||
|
return 'Throw {0}: {1}'.format(self._names, self._message)
|
||||||
|
return 'Throw ' + self._names
|
||||||
|
|
||||||
|
|
||||||
|
class CatchBlock(expressions.DslExpression):
|
||||||
|
def __init__(self, With=None, As=None, Do=None):
|
||||||
|
if With is not None and not isinstance(With, list):
|
||||||
|
With = [With]
|
||||||
|
self._with = With
|
||||||
|
self._as = As
|
||||||
|
self._code_block = None if Do is None else macros.CodeBlock(Do)
|
||||||
|
|
||||||
|
def _resolve_names(self, names, context):
|
||||||
|
murano_class = helpers.get_type(context)
|
||||||
|
for name in names:
|
||||||
|
yield murano_class.namespace_resolver.resolve_name(name)
|
||||||
|
|
||||||
|
def execute(self, context, murano_class):
|
||||||
|
exception = helpers.get_current_exception(context)
|
||||||
|
names = None if self._with is None else \
|
||||||
|
list(self._resolve_names(self._with, context))
|
||||||
|
|
||||||
|
for name in exception.names:
|
||||||
|
if self._with is None or name in names:
|
||||||
|
if self._code_block:
|
||||||
|
if self._as:
|
||||||
|
wrapped = self._wrap_internal_exception(
|
||||||
|
exception, context, name)
|
||||||
|
context.set_data(wrapped, self._as)
|
||||||
|
self._code_block.execute(context, murano_class)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _wrap_internal_exception(self, exception, context, name):
|
||||||
|
obj = yaql_functions.new('io.murano.Exception', context)
|
||||||
|
obj.set_property('name', name)
|
||||||
|
obj.set_property('message', exception.message)
|
||||||
|
obj.set_property('stackTrace', exception.stacktrace)
|
||||||
|
obj.set_property('extra', exception.extra)
|
||||||
|
obj.set_property('nativeException', exception)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
class TryBlockMacro(expressions.DslExpression):
|
||||||
|
def __init__(self, Try, Catch=None, Finally=None, Else=None):
|
||||||
|
self._try_block = macros.CodeBlock(Try)
|
||||||
|
self._catch_block = None
|
||||||
|
if Catch is not None:
|
||||||
|
if not isinstance(Catch, list):
|
||||||
|
Catch = [Catch]
|
||||||
|
self._catch_block = [CatchBlock(**c) for c in Catch]
|
||||||
|
self._finally_block = None if Finally is None \
|
||||||
|
else macros.CodeBlock(Finally)
|
||||||
|
self._else_block = None if Else is None \
|
||||||
|
else macros.CodeBlock(Else)
|
||||||
|
|
||||||
|
def execute(self, context, murano_class):
|
||||||
|
try:
|
||||||
|
self._try_block.execute(context, murano_class)
|
||||||
|
except dsl_exception.MuranoPlException as e:
|
||||||
|
caught = False
|
||||||
|
if self._catch_block:
|
||||||
|
try:
|
||||||
|
context.set_data(e, '?currentException')
|
||||||
|
for cb in self._catch_block:
|
||||||
|
if cb.execute(context, murano_class):
|
||||||
|
caught = True
|
||||||
|
break
|
||||||
|
if not caught:
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
context.set_data(None, '?currentException')
|
||||||
|
else:
|
||||||
|
if self._else_block:
|
||||||
|
self._else_block.execute(context, murano_class)
|
||||||
|
finally:
|
||||||
|
if self._finally_block:
|
||||||
|
self._finally_block.execute(context, murano_class)
|
||||||
|
|
||||||
|
|
||||||
|
class RethrowMacro(expressions.DslExpression):
|
||||||
|
def __init__(self, Rethrow):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def execute(self, context, murano_class):
|
||||||
|
exception = context.get_data('$?currentException')
|
||||||
|
if not exception:
|
||||||
|
raise TypeError('Rethrow must be inside Catch')
|
||||||
|
raise exception
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return 'Rethrow'
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
expressions.register_macro(ThrowMacro)
|
||||||
|
expressions.register_macro(TryBlockMacro)
|
||||||
|
expressions.register_macro(RethrowMacro)
|
@@ -1,66 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
import os.path
|
|
||||||
|
|
||||||
import yaql.context
|
|
||||||
|
|
||||||
import murano.dsl.helpers as helpers
|
|
||||||
|
|
||||||
|
|
||||||
@yaql.context.ContextAware()
|
|
||||||
def stack_trace(context):
|
|
||||||
frames = []
|
|
||||||
while True:
|
|
||||||
context = helpers.get_caller_context(context)
|
|
||||||
if not context:
|
|
||||||
break
|
|
||||||
instruction = helpers.get_current_instruction(context)
|
|
||||||
frames.append({
|
|
||||||
'instruction': None if instruction is None else str(instruction),
|
|
||||||
'location': None if instruction is None
|
|
||||||
else instruction.file_position,
|
|
||||||
'method': helpers.get_current_method(context),
|
|
||||||
'class': helpers.get_type(context)
|
|
||||||
})
|
|
||||||
frames.pop()
|
|
||||||
return frames
|
|
||||||
|
|
||||||
|
|
||||||
def format_stack_trace_yaql(trace):
|
|
||||||
return format_stack_trace(trace, '')
|
|
||||||
|
|
||||||
|
|
||||||
def format_stack_trace(trace, prefix=''):
|
|
||||||
def format_frame(frame):
|
|
||||||
instruction = frame['instruction']
|
|
||||||
method = frame['method']
|
|
||||||
murano_class = frame['class']
|
|
||||||
location = frame['location']
|
|
||||||
|
|
||||||
if location:
|
|
||||||
return '{5}File "{0}" at {1}:{2} in method {3} of class {6}\n' \
|
|
||||||
'{5} {4}'.format(
|
|
||||||
os.path.abspath(location.file_path),
|
|
||||||
location.start_line,
|
|
||||||
location.start_column,
|
|
||||||
method,
|
|
||||||
instruction,
|
|
||||||
prefix,
|
|
||||||
murano_class.name)
|
|
||||||
else:
|
|
||||||
return '{2}File <unknown> in method {0}\n{2} {1}'.format(
|
|
||||||
method, instruction, prefix)
|
|
||||||
|
|
||||||
return '\n'.join([format_frame(t)for t in trace()])
|
|
@@ -42,11 +42,11 @@ class YaqlExpression(object):
|
|||||||
return self._expression
|
return self._expression
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def file_position(self):
|
def source_file_position(self):
|
||||||
return self._file_position
|
return self._file_position
|
||||||
|
|
||||||
@file_position.setter
|
@source_file_position.setter
|
||||||
def file_position(self, value):
|
def source_file_position(self, value):
|
||||||
self._file_position = value
|
self._file_position = value
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
@@ -53,7 +53,7 @@ def _id(value):
|
|||||||
@yaql.context.EvalArg('type', str)
|
@yaql.context.EvalArg('type', str)
|
||||||
@yaql.context.ContextAware()
|
@yaql.context.ContextAware()
|
||||||
def _cast(context, value, type):
|
def _cast(context, value, type):
|
||||||
if not '.' in type:
|
if '.' not in type:
|
||||||
murano_class = helpers.get_type(context)
|
murano_class = helpers.get_type(context)
|
||||||
type = murano_class.namespace_resolver.resolve_name(type)
|
type = murano_class.namespace_resolver.resolve_name(type)
|
||||||
class_loader = helpers.get_class_loader(context)
|
class_loader = helpers.get_class_loader(context)
|
||||||
@@ -86,6 +86,10 @@ def _new(context, name, *args):
|
|||||||
None, object_store, new_context, parameters=parameters)
|
None, object_store, new_context, parameters=parameters)
|
||||||
|
|
||||||
|
|
||||||
|
def new(name, context, **kwargs):
|
||||||
|
return _new(context, name, lambda: kwargs)
|
||||||
|
|
||||||
|
|
||||||
@yaql.context.EvalArg('value', murano_object.MuranoObject)
|
@yaql.context.EvalArg('value', murano_object.MuranoObject)
|
||||||
@yaql.context.ContextAware()
|
@yaql.context.ContextAware()
|
||||||
def _super(context, value):
|
def _super(context, value):
|
||||||
|
@@ -14,13 +14,33 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
import yaml.composer
|
||||||
|
import yaml.constructor
|
||||||
|
|
||||||
from murano.dsl import yaql_expression
|
from murano.dsl import yaql_expression
|
||||||
|
|
||||||
|
|
||||||
class YaqlYamlLoader(yaml.Loader):
|
class MuranoPlDict(dict):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class MuranoPlYamlConstructor(yaml.constructor.Constructor):
|
||||||
|
def construct_yaml_map(self, node):
|
||||||
|
data = MuranoPlDict()
|
||||||
|
data.source_file_position = build_position(node)
|
||||||
|
yield data
|
||||||
|
value = self.construct_mapping(node)
|
||||||
|
data.update(value)
|
||||||
|
|
||||||
|
|
||||||
|
class YaqlYamlLoader(yaml.Loader, MuranoPlYamlConstructor):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
YaqlYamlLoader.add_constructor(u'tag:yaml.org,2002:map',
|
||||||
|
MuranoPlYamlConstructor.construct_yaml_map)
|
||||||
|
|
||||||
|
|
||||||
# workaround for PyYAML bug: http://pyyaml.org/ticket/221
|
# workaround for PyYAML bug: http://pyyaml.org/ticket/221
|
||||||
resolvers = {}
|
resolvers = {}
|
||||||
for k, v in yaml.Loader.yaml_implicit_resolvers.items():
|
for k, v in yaml.Loader.yaml_implicit_resolvers.items():
|
||||||
@@ -28,10 +48,8 @@ for k, v in yaml.Loader.yaml_implicit_resolvers.items():
|
|||||||
YaqlYamlLoader.yaml_implicit_resolvers = resolvers
|
YaqlYamlLoader.yaml_implicit_resolvers = resolvers
|
||||||
|
|
||||||
|
|
||||||
def yaql_constructor(loader, node):
|
def build_position(node):
|
||||||
value = loader.construct_scalar(node)
|
return yaql_expression.YaqlExpressionFilePosition(
|
||||||
result = yaql_expression.YaqlExpression(value)
|
|
||||||
position = yaql_expression.YaqlExpressionFilePosition(
|
|
||||||
node.start_mark.name,
|
node.start_mark.name,
|
||||||
node.start_mark.line + 1,
|
node.start_mark.line + 1,
|
||||||
node.start_mark.column + 1,
|
node.start_mark.column + 1,
|
||||||
@@ -39,7 +57,12 @@ def yaql_constructor(loader, node):
|
|||||||
node.end_mark.line + 1,
|
node.end_mark.line + 1,
|
||||||
node.end_mark.column + 1,
|
node.end_mark.column + 1,
|
||||||
node.end_mark.index - node.start_mark.index)
|
node.end_mark.index - node.start_mark.index)
|
||||||
result.file_position = position
|
|
||||||
|
|
||||||
|
def yaql_constructor(loader, node):
|
||||||
|
value = loader.construct_scalar(node)
|
||||||
|
result = yaql_expression.YaqlExpression(value)
|
||||||
|
result.source_file_position = build_position(node)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
yaml.add_constructor(u'!yaql', yaql_constructor, YaqlYamlLoader)
|
yaml.add_constructor(u'!yaql', yaql_constructor, YaqlYamlLoader)
|
||||||
|
@@ -18,12 +18,8 @@ import re
|
|||||||
import mock
|
import mock
|
||||||
import yaql
|
import yaql
|
||||||
|
|
||||||
import murano.dsl.exceptions as exceptions
|
|
||||||
import murano.dsl.helpers as helpers
|
import murano.dsl.helpers as helpers
|
||||||
import murano.dsl.murano_class as murano_class
|
|
||||||
import murano.dsl.murano_object as murano_object
|
|
||||||
import murano.dsl.namespace_resolver as ns_resolver
|
import murano.dsl.namespace_resolver as ns_resolver
|
||||||
import murano.dsl.typespec as typespec
|
|
||||||
import murano.dsl.yaql_expression as yaql_expression
|
import murano.dsl.yaql_expression as yaql_expression
|
||||||
from murano.tests import base
|
from murano.tests import base
|
||||||
|
|
||||||
@@ -114,325 +110,6 @@ class Bunch(object):
|
|||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
|
||||||
class TestClassesManipulation(base.MuranoTestCase):
|
|
||||||
resolver = mock.Mock(resolve_name=lambda name: name)
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestClassesManipulation, self).setUp()
|
|
||||||
|
|
||||||
def test_class_name(self):
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
|
||||||
|
|
||||||
self.assertEqual(ROOT_CLASS, cls.name)
|
|
||||||
|
|
||||||
def test_class_namespace_resolver(self):
|
|
||||||
resolver = ns_resolver.NamespaceResolver({})
|
|
||||||
cls = murano_class.MuranoClass(None, resolver, ROOT_CLASS, None)
|
|
||||||
|
|
||||||
self.assertEqual(resolver, cls.namespace_resolver)
|
|
||||||
|
|
||||||
def test_root_class_has_no_parents(self):
|
|
||||||
root_class = murano_class.MuranoClass(
|
|
||||||
None, self.resolver, ROOT_CLASS, ['You should not see me!'])
|
|
||||||
|
|
||||||
self.assertEqual([], root_class.parents)
|
|
||||||
|
|
||||||
def test_non_root_class_resolves_parents(self):
|
|
||||||
root_cls = murano_class.MuranoClass(None, self.resolver,
|
|
||||||
ROOT_CLASS, None)
|
|
||||||
class_loader = mock.Mock(get_class=lambda name: root_cls)
|
|
||||||
desc_cl1 = murano_class.MuranoClass(class_loader, self.resolver,
|
|
||||||
'Obj', None)
|
|
||||||
desc_cl2 = murano_class.MuranoClass(
|
|
||||||
class_loader, self.resolver, 'Obj', None, [root_cls])
|
|
||||||
|
|
||||||
self.assertEqual([root_cls], desc_cl1.parents)
|
|
||||||
self.assertEqual([root_cls], desc_cl2.parents)
|
|
||||||
|
|
||||||
def test_class_initial_properties(self):
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
|
||||||
self.assertEqual([], cls.properties)
|
|
||||||
|
|
||||||
def test_fails_add_incompatible_property_to_class(self):
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
|
||||||
kwargs = {'name': 'sampleProperty', 'property_typespec': {}}
|
|
||||||
|
|
||||||
self.assertRaises(TypeError, cls.add_property, **kwargs)
|
|
||||||
|
|
||||||
def test_add_property_to_class(self):
|
|
||||||
self.skipTest("FIXME!")
|
|
||||||
|
|
||||||
prop = typespec.PropertySpec({'Default': 1}, self.resolver)
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
|
||||||
cls.add_property('firstPrime', prop)
|
|
||||||
|
|
||||||
class_properties = cls.properties
|
|
||||||
class_property = cls.get_property('firstPrime')
|
|
||||||
|
|
||||||
self.assertEqual(['firstPrime'], class_properties)
|
|
||||||
self.assertEqual(prop, class_property)
|
|
||||||
|
|
||||||
def test_class_property_search(self):
|
|
||||||
self.skipTest("FIXME!")
|
|
||||||
|
|
||||||
void_prop = typespec.PropertySpec({'Default': 'Void'}, self.resolver)
|
|
||||||
mother_prop = typespec.PropertySpec({'Default': 'Mother'},
|
|
||||||
self.resolver)
|
|
||||||
father_prop = typespec.PropertySpec({'Default': 'Father'},
|
|
||||||
self.resolver)
|
|
||||||
child_prop = typespec.PropertySpec({'Default': 'Child'},
|
|
||||||
self.resolver)
|
|
||||||
root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
|
||||||
mother = murano_class.MuranoClass(None, self.resolver,
|
|
||||||
'Mother', [root])
|
|
||||||
father = murano_class.MuranoClass(None, self.resolver,
|
|
||||||
'Father', [root])
|
|
||||||
child = murano_class.MuranoClass(
|
|
||||||
None, self.resolver, 'Child', [mother, father])
|
|
||||||
|
|
||||||
root.add_property('Void', void_prop)
|
|
||||||
mother.add_property('Mother', mother_prop)
|
|
||||||
father.add_property('Father', father_prop)
|
|
||||||
child.add_property('Child', child_prop)
|
|
||||||
|
|
||||||
self.assertEqual(child_prop, child.find_property('Child'))
|
|
||||||
self.assertEqual(father_prop, child.find_property('Father'))
|
|
||||||
self.assertEqual(mother_prop, child.find_property('Mother'))
|
|
||||||
self.assertEqual(void_prop, child.find_property('Void'))
|
|
||||||
|
|
||||||
def test_class_is_compatible(self):
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
|
||||||
descendant_cls = murano_class.MuranoClass(
|
|
||||||
None, self.resolver, 'DescendantCls', None, [cls])
|
|
||||||
obj = mock.Mock(spec=murano_object.MuranoObject)
|
|
||||||
descendant_obj = mock.Mock(spec=murano_object.MuranoObject)
|
|
||||||
obj.type = cls
|
|
||||||
descendant_obj.type = descendant_cls
|
|
||||||
descendant_obj.parents = [obj]
|
|
||||||
|
|
||||||
self.assertTrue(cls.is_compatible(obj))
|
|
||||||
self.assertTrue(cls.is_compatible(descendant_obj))
|
|
||||||
self.assertFalse(descendant_cls.is_compatible(obj))
|
|
||||||
|
|
||||||
def test_new_method_calls_initialize(self):
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
|
||||||
cls.object_class = mock.Mock()
|
|
||||||
|
|
||||||
with mock.patch('inspect.getargspec') as spec_mock:
|
|
||||||
spec_mock.return_value = Bunch(args=())
|
|
||||||
obj = cls.new(None, None, None, {})
|
|
||||||
|
|
||||||
self.assertTrue(obj.initialize.called)
|
|
||||||
|
|
||||||
def test_new_method_not_calls_initialize(self):
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
|
||||||
cls.object_class = mock.Mock()
|
|
||||||
|
|
||||||
obj = cls.new(None, None, None)
|
|
||||||
|
|
||||||
self.assertFalse(obj.initialize.called)
|
|
||||||
|
|
||||||
|
|
||||||
class TestObjectsManipulation(base.MuranoTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestObjectsManipulation, self).setUp()
|
|
||||||
|
|
||||||
self.resolver = mock.Mock(resolve_name=lambda name: name)
|
|
||||||
self.cls = mock.Mock()
|
|
||||||
self.cls.name = ROOT_CLASS
|
|
||||||
self.cls.parents = []
|
|
||||||
|
|
||||||
def test_object_valid_type_instantiation(self):
|
|
||||||
obj = murano_object.MuranoObject(self.cls, None, None, None)
|
|
||||||
|
|
||||||
self.assertEqual(self.cls, obj.type)
|
|
||||||
|
|
||||||
def test_object_own_properties_initialization(self):
|
|
||||||
# TODO: there should be test for initializing first non-dependent
|
|
||||||
# object properties, then the dependent ones (given as
|
|
||||||
# YAQL-expressions)
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_object_parent_properties_initialization(self):
|
|
||||||
root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver,
|
|
||||||
'SomeClass', None, [root])
|
|
||||||
root.new = mock.Mock()
|
|
||||||
init_kwargs = {'theArg': 0}
|
|
||||||
obj = murano_object.MuranoObject(cls, None, None, None)
|
|
||||||
expected_calls = [mock.call().initialize(**init_kwargs)]
|
|
||||||
|
|
||||||
obj.initialize(**init_kwargs)
|
|
||||||
|
|
||||||
# each object should also initialize his parent objects
|
|
||||||
self.assertEqual(expected_calls, root.new.mock_calls[1:])
|
|
||||||
|
|
||||||
def test_object_id(self):
|
|
||||||
_id = 'some_id'
|
|
||||||
patch_at = 'murano.dsl.helpers.generate_id'
|
|
||||||
|
|
||||||
obj = murano_object.MuranoObject(self.cls, None, None, None,
|
|
||||||
object_id=_id)
|
|
||||||
with mock.patch(patch_at) as gen_id_mock:
|
|
||||||
gen_id_mock.return_value = _id
|
|
||||||
obj1 = murano_object.MuranoObject(self.cls, None, None, None)
|
|
||||||
|
|
||||||
self.assertEqual(_id, obj.object_id)
|
|
||||||
self.assertEqual(_id, obj1.object_id)
|
|
||||||
|
|
||||||
def test_parent_obj(self):
|
|
||||||
parent = mock.Mock()
|
|
||||||
obj = murano_object.MuranoObject(self.cls, parent, None, None)
|
|
||||||
|
|
||||||
self.assertEqual(parent, obj.parent)
|
|
||||||
|
|
||||||
def test_fails_internal_property_access(self):
|
|
||||||
self.skipTest("FIXME!")
|
|
||||||
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
|
||||||
|
|
||||||
cls.add_property('__hidden',
|
|
||||||
typespec.PropertySpec({'Default': 10}, self.resolver))
|
|
||||||
obj = murano_object.MuranoObject(cls, None, None, None)
|
|
||||||
|
|
||||||
self.assertRaises(AttributeError, lambda: obj.__hidden)
|
|
||||||
|
|
||||||
def test_proper_property_access(self):
|
|
||||||
self.skipTest("FIXME!")
|
|
||||||
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
|
||||||
|
|
||||||
cls.add_property('someProperty',
|
|
||||||
typespec.PropertySpec({'Default': 0}, self.resolver))
|
|
||||||
obj = cls.new(None, None, None, {})
|
|
||||||
|
|
||||||
self.assertEqual(0, obj.someProperty)
|
|
||||||
|
|
||||||
def test_parent_class_property_access(self):
|
|
||||||
self.skipTest("FIXME!")
|
|
||||||
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
|
||||||
child_cls = murano_class.MuranoClass(None, self.resolver,
|
|
||||||
'Child', [cls])
|
|
||||||
|
|
||||||
cls.add_property('anotherProperty',
|
|
||||||
typespec.PropertySpec({'Default': 0}, self.resolver))
|
|
||||||
obj = child_cls.new(None, None, None, {})
|
|
||||||
|
|
||||||
self.assertEqual(0, obj.anotherProperty)
|
|
||||||
|
|
||||||
def test_fails_on_parents_property_collision(self):
|
|
||||||
self.skipTest("FIXME!")
|
|
||||||
|
|
||||||
root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
|
||||||
mother = murano_class.MuranoClass(None, self.resolver,
|
|
||||||
'Mother', [root])
|
|
||||||
father = murano_class.MuranoClass(None, self.resolver,
|
|
||||||
'Father', [root])
|
|
||||||
child = murano_class.MuranoClass(
|
|
||||||
None, self.resolver, 'Child', [mother, father])
|
|
||||||
|
|
||||||
mother.add_property(
|
|
||||||
'conflictProp',
|
|
||||||
typespec.PropertySpec({'Default': 0}, self.resolver))
|
|
||||||
father.add_property(
|
|
||||||
'conflictProp',
|
|
||||||
typespec.PropertySpec({'Default': 0}, self.resolver))
|
|
||||||
obj = child.new(None, None, None, {})
|
|
||||||
|
|
||||||
self.assertRaises(LookupError, lambda: obj.conflictProp)
|
|
||||||
|
|
||||||
def test_fails_setting_undeclared_property(self):
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
|
||||||
obj = cls.new(None, None, None, {})
|
|
||||||
|
|
||||||
self.assertRaises(AttributeError, obj.set_property, 'newOne', 10)
|
|
||||||
|
|
||||||
def test_set_undeclared_property_as_internal(self):
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
|
||||||
obj = cls.new(None, None, None, {})
|
|
||||||
obj.cast = mock.Mock(return_value=obj)
|
|
||||||
prop_value = 10
|
|
||||||
|
|
||||||
obj.set_property('internalProp', prop_value, caller_class=cls)
|
|
||||||
resolved_value = obj.get_property('internalProp', caller_class=cls)
|
|
||||||
|
|
||||||
self.assertEqual(prop_value, resolved_value)
|
|
||||||
|
|
||||||
def test_fails_forbidden_set_property(self):
|
|
||||||
self.skipTest("FIXME!")
|
|
||||||
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
|
||||||
cls.add_property('someProperty',
|
|
||||||
typespec.PropertySpec({'Default': 0}, self.resolver))
|
|
||||||
cls.is_compatible = mock.Mock(return_value=False)
|
|
||||||
obj = cls.new(None, None, None, {})
|
|
||||||
|
|
||||||
self.assertRaises(exceptions.NoWriteAccess, obj.set_property,
|
|
||||||
'someProperty', 10, caller_class=cls)
|
|
||||||
|
|
||||||
def test_set_property(self):
|
|
||||||
self.skipTest("FIXME!")
|
|
||||||
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
|
||||||
cls.add_property('someProperty',
|
|
||||||
typespec.PropertySpec({'Default': 0}, self.resolver))
|
|
||||||
obj = cls.new(None, None, None, {})
|
|
||||||
|
|
||||||
with mock.patch('yaql.context.Context'):
|
|
||||||
with mock.patch('murano.engine.helpers') as helpers_mock:
|
|
||||||
helpers_mock.evaluate = lambda val, ctx, _: val
|
|
||||||
obj.set_property('someProperty', 10)
|
|
||||||
|
|
||||||
self.assertEqual(10, obj.someProperty)
|
|
||||||
|
|
||||||
def test_set_parent_property(self):
|
|
||||||
self.skipTest("FIXME!")
|
|
||||||
|
|
||||||
root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
|
||||||
cls = murano_class.MuranoClass(None, self.resolver,
|
|
||||||
'SomeClass', [root])
|
|
||||||
root.add_property('rootProperty',
|
|
||||||
typespec.PropertySpec({'Default': 0}, self.resolver))
|
|
||||||
obj = cls.new(None, None, None, {})
|
|
||||||
|
|
||||||
with mock.patch('murano.engine.helpers') as helpers_mock:
|
|
||||||
with mock.patch('yaql.context.Context'):
|
|
||||||
helpers_mock.evaluate = lambda val, ctx, _: val
|
|
||||||
obj.set_property('rootProperty', 20)
|
|
||||||
|
|
||||||
self.assertEqual(20, obj.rootProperty)
|
|
||||||
|
|
||||||
def test_object_up_cast(self):
|
|
||||||
self.skipTest("FIXME!")
|
|
||||||
|
|
||||||
root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS)
|
|
||||||
root_alt = murano_class.MuranoClass(None, self.resolver, 'RootAlt', [])
|
|
||||||
cls = murano_class.MuranoClass(
|
|
||||||
None, self.resolver, 'SomeClass', [root, root_alt])
|
|
||||||
root_obj = root.new(None, None, None)
|
|
||||||
cls_obj = cls.new(None, None, None)
|
|
||||||
|
|
||||||
root_obj_casted2root = root_obj.cast(root)
|
|
||||||
cls_obj_casted2root = cls_obj.cast(root)
|
|
||||||
cls_obj_casted2root_alt = cls_obj.cast(root_alt)
|
|
||||||
|
|
||||||
self.assertEqual(root_obj, root_obj_casted2root)
|
|
||||||
# each object creates an _internal_ parent objects hierarchy,
|
|
||||||
# so direct comparison of objects is not possible
|
|
||||||
self.assertEqual(root, cls_obj_casted2root.type)
|
|
||||||
self.assertEqual(root_alt, cls_obj_casted2root_alt.type)
|
|
||||||
|
|
||||||
def test_fails_object_down_cast(self):
|
|
||||||
root = murano_class.MuranoClass(None, self.resolver, ROOT_CLASS, None)
|
|
||||||
cls = murano_class.MuranoClass(
|
|
||||||
None, self.resolver, 'SomeClass', None, [root])
|
|
||||||
root_obj = root.new(None, None, None)
|
|
||||||
|
|
||||||
self.assertRaises(TypeError, root_obj.cast, cls)
|
|
||||||
|
|
||||||
|
|
||||||
class TestHelperFunctions(base.MuranoTestCase):
|
class TestHelperFunctions(base.MuranoTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
Reference in New Issue
Block a user