Support for Unicode strings in MuranoPL was fixed
There were several issues with Unicode support in MuranoPL after switch to yaql 1.0. Each of those issues caused any deploy to fail if it used to do anything with non-ASCII strings Fixed issues are: * Unicode strings should not be encoded to str types anymore. yaql 1.0 has native support for the Unicode and fails when non- ASCII chars encounter in str expressions. Also tests for Unicode support are now part of the yaql * Traces of execution were logged not as Unicode strings. Because those traces contain parameter values ant function return value logging failed when any of above contained non-ASCII chars * Stack trace logging failed when frame expression contained non-ASCII chars * Exception messages could not contain non-ASCII chars Also Logging API was not Unicode ready Change-Id: Ief0b45f15669c5f8ee74fd6ff41fa5bc39c9500b Closes-Bug: #1494275
This commit is contained in:
parent
a8e91b16e7
commit
9e8cec8910
@ -20,7 +20,7 @@ from murano.dsl.principal_objects import stack_trace
|
|||||||
class MuranoPlException(Exception):
|
class MuranoPlException(Exception):
|
||||||
def __init__(self, names, message, stacktrace, extra=None, cause=None):
|
def __init__(self, names, message, stacktrace, extra=None, cause=None):
|
||||||
super(MuranoPlException, self).__init__(
|
super(MuranoPlException, self).__init__(
|
||||||
'[{0}]: {1}'.format(', '.join(names), message))
|
u'[{0}]: {1}'.format(', '.join(names), message))
|
||||||
if not isinstance(names, list):
|
if not isinstance(names, list):
|
||||||
names = [names]
|
names = [names]
|
||||||
self._names = names
|
self._names = names
|
||||||
|
@ -145,10 +145,10 @@ class MuranoDslExecutor(object):
|
|||||||
def _log_method(self, context, args, kwargs):
|
def _log_method(self, context, args, kwargs):
|
||||||
method = helpers.get_current_method(context)
|
method = helpers.get_current_method(context)
|
||||||
param_gen = itertools.chain(
|
param_gen = itertools.chain(
|
||||||
(str(arg) for arg in args),
|
(unicode(arg) for arg in args),
|
||||||
('{0} => {1}'.format(name, value)
|
(u'{0} => {1}'.format(name, value)
|
||||||
for name, value in kwargs.iteritems()))
|
for name, value in kwargs.iteritems()))
|
||||||
params_str = ', '.join(param_gen)
|
params_str = u', '.join(param_gen)
|
||||||
method_name = '{0}::{1}'.format(method.murano_class.name, method.name)
|
method_name = '{0}::{1}'.format(method.murano_class.name, method.name)
|
||||||
thread_id = helpers.get_current_thread_id()
|
thread_id = helpers.get_current_thread_id()
|
||||||
caller_str = ''
|
caller_str = ''
|
||||||
@ -158,18 +158,20 @@ class MuranoDslExecutor(object):
|
|||||||
if frame['location']:
|
if frame['location']:
|
||||||
caller_str = ' called from ' + stack_trace.format_frame(frame)
|
caller_str = ' called from ' + stack_trace.format_frame(frame)
|
||||||
|
|
||||||
LOG.trace('{thread}: Begin execution {method}({params}){caller}'
|
LOG.trace(u'{thread}: Begin execution {method}({params}){caller}'
|
||||||
.format(thread=thread_id, method=method_name,
|
.format(thread=thread_id, method=method_name,
|
||||||
params=params_str, caller=caller_str))
|
params=params_str, caller=caller_str))
|
||||||
try:
|
try:
|
||||||
def log_result(result):
|
def log_result(result):
|
||||||
LOG.trace('{thread}: End execution {method} with result '
|
LOG.trace(
|
||||||
'{result}'.format(thread=thread_id,
|
u'{thread}: End execution {method} with result '
|
||||||
method=method_name, result=result))
|
u'{result}'.format(
|
||||||
|
thread=thread_id, method=method_name, result=result))
|
||||||
yield log_result
|
yield log_result
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.trace('{thread}: End execution {method} with exception {exc}'
|
LOG.trace(
|
||||||
.format(thread=thread_id, method=method_name, exc=e))
|
u'{thread}: End execution {method} with exception '
|
||||||
|
u'{exc}'.format(thread=thread_id, method=method_name, exc=e))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -66,7 +66,7 @@ def compose_stack_frame(context):
|
|||||||
method = helpers.get_current_method(context)
|
method = helpers.get_current_method(context)
|
||||||
return {
|
return {
|
||||||
'instruction': None if instruction is None
|
'instruction': None if instruction is None
|
||||||
else str(instruction),
|
else unicode(instruction),
|
||||||
|
|
||||||
'location': None if instruction is None
|
'location': None if instruction is None
|
||||||
else instruction.source_file_position,
|
else instruction.source_file_position,
|
||||||
@ -94,10 +94,10 @@ def format_frame(frame, prefix=''):
|
|||||||
instruction,
|
instruction,
|
||||||
prefix
|
prefix
|
||||||
)
|
)
|
||||||
return ('{5}File "{0}", line {1}{2} in method {3}\n'
|
return (u'{5}File "{0}", line {1}{2} in method {3}\n'
|
||||||
'{5} {4}').format(*args)
|
u'{5} {4}').format(*args)
|
||||||
else:
|
else:
|
||||||
return '{2}File <unknown> in method {0}\n{2} {1}'.format(
|
return u'{2}File <unknown> in method {0}\n{2} {1}'.format(
|
||||||
method, instruction, prefix)
|
method, instruction, prefix)
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,10 +50,10 @@ class ThrowMacro(expressions.DslExpression):
|
|||||||
helpers.evaluate(self._message, context),
|
helpers.evaluate(self._message, context),
|
||||||
stacktrace, self._extra, cause)
|
stacktrace, self._extra, cause)
|
||||||
|
|
||||||
def __str__(self):
|
def __unicode__(self):
|
||||||
if self._message:
|
if self._message:
|
||||||
return 'Throw {0}: {1}'.format(self._names, self._message)
|
return u'Throw {0}: {1}'.format(self._names, self._message)
|
||||||
return 'Throw ' + str(self._names)
|
return u'Throw ' + unicode(self._names)
|
||||||
|
|
||||||
|
|
||||||
class CatchBlock(expressions.DslExpression):
|
class CatchBlock(expressions.DslExpression):
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
import re
|
import re
|
||||||
import types
|
import types
|
||||||
|
|
||||||
from oslo_utils import encodeutils
|
|
||||||
from yaql.language import exceptions as yaql_exceptions
|
from yaql.language import exceptions as yaql_exceptions
|
||||||
from yaql.language import expressions
|
from yaql.language import expressions
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ class YaqlExpression(dsl_types.YaqlExpression):
|
|||||||
def __init__(self, expression, version):
|
def __init__(self, expression, version):
|
||||||
self._version = version
|
self._version = version
|
||||||
if isinstance(expression, types.StringTypes):
|
if isinstance(expression, types.StringTypes):
|
||||||
self._expression = encodeutils.safe_encode(expression)
|
self._expression = unicode(expression)
|
||||||
self._parsed_expression = yaql_integration.parse(
|
self._parsed_expression = yaql_integration.parse(
|
||||||
self._expression, version)
|
self._expression, version)
|
||||||
self._file_position = None
|
self._file_position = None
|
||||||
|
@ -21,7 +21,7 @@ from yaql.language import yaqltypes
|
|||||||
|
|
||||||
from murano.dsl import dsl
|
from murano.dsl import dsl
|
||||||
|
|
||||||
NAME_TEMPLATE = 'applications.{0}'
|
NAME_TEMPLATE = u'applications.{0}'
|
||||||
|
|
||||||
inject_format = specs.inject(
|
inject_format = specs.inject(
|
||||||
'_Logger__yaql_format_function',
|
'_Logger__yaql_format_function',
|
||||||
@ -39,42 +39,49 @@ class Logger(object):
|
|||||||
self._underlying_logger = logging.getLogger(
|
self._underlying_logger = logging.getLogger(
|
||||||
NAME_TEMPLATE.format(logger_name))
|
NAME_TEMPLATE.format(logger_name))
|
||||||
|
|
||||||
|
@specs.parameter('_Logger__message', yaqltypes.String())
|
||||||
@inject_format
|
@inject_format
|
||||||
def trace(__self, __yaql_format_function, __message, *args, **kwargs):
|
def trace(__self, __yaql_format_function, __message, *args, **kwargs):
|
||||||
__self._log(__self._underlying_logger.trace,
|
__self._log(__self._underlying_logger.trace,
|
||||||
__yaql_format_function, __message, args, kwargs)
|
__yaql_format_function, __message, args, kwargs)
|
||||||
|
|
||||||
|
@specs.parameter('_Logger__message', yaqltypes.String())
|
||||||
@inject_format
|
@inject_format
|
||||||
def debug(__self, __yaql_format_function, __message, *args, **kwargs):
|
def debug(__self, __yaql_format_function, __message, *args, **kwargs):
|
||||||
__self._log(__self._underlying_logger.debug,
|
__self._log(__self._underlying_logger.debug,
|
||||||
__yaql_format_function, __message, args, kwargs)
|
__yaql_format_function, __message, args, kwargs)
|
||||||
|
|
||||||
|
@specs.parameter('_Logger__message', yaqltypes.String())
|
||||||
@inject_format
|
@inject_format
|
||||||
def info(__self, __yaql_format_function, __message, *args, **kwargs):
|
def info(__self, __yaql_format_function, __message, *args, **kwargs):
|
||||||
__self._log(__self._underlying_logger.info,
|
__self._log(__self._underlying_logger.info,
|
||||||
__yaql_format_function, __message, args, kwargs)
|
__yaql_format_function, __message, args, kwargs)
|
||||||
|
|
||||||
|
@specs.parameter('_Logger__message', yaqltypes.String())
|
||||||
@inject_format
|
@inject_format
|
||||||
def warning(__self, __yaql_format_function, __message, *args, **kwargs):
|
def warning(__self, __yaql_format_function, __message, *args, **kwargs):
|
||||||
__self._log(__self._underlying_logger.warning,
|
__self._log(__self._underlying_logger.warning,
|
||||||
__yaql_format_function, __message, args, kwargs)
|
__yaql_format_function, __message, args, kwargs)
|
||||||
|
|
||||||
|
@specs.parameter('_Logger__message', yaqltypes.String())
|
||||||
@inject_format
|
@inject_format
|
||||||
def error(__self, __yaql_format_function, __message, *args, **kwargs):
|
def error(__self, __yaql_format_function, __message, *args, **kwargs):
|
||||||
__self._log(__self._underlying_logger.error,
|
__self._log(__self._underlying_logger.error,
|
||||||
__yaql_format_function, __message, args, kwargs)
|
__yaql_format_function, __message, args, kwargs)
|
||||||
|
|
||||||
|
@specs.parameter('_Logger__message', yaqltypes.String())
|
||||||
@inject_format
|
@inject_format
|
||||||
def critical(__self, __yaql_format_function,
|
def critical(__self, __yaql_format_function,
|
||||||
__message, *args, **kwargs):
|
__message, *args, **kwargs):
|
||||||
__self._log(__self._underlying_logger.critical,
|
__self._log(__self._underlying_logger.critical,
|
||||||
__yaql_format_function, __message, args, kwargs)
|
__yaql_format_function, __message, args, kwargs)
|
||||||
|
|
||||||
|
@specs.parameter('_Logger__message', yaqltypes.String())
|
||||||
@inject_format
|
@inject_format
|
||||||
def exception(__self, __yaql_format_function,
|
def exception(__self, __yaql_format_function,
|
||||||
__exc, __message, *args, **kwargs):
|
__exc, __message, *args, **kwargs):
|
||||||
"""Print error message and stacktrace"""
|
"""Print error message and stacktrace"""
|
||||||
stack_trace_message = '\n'.join([
|
stack_trace_message = u'\n'.join([
|
||||||
__self._format_without_exceptions(
|
__self._format_without_exceptions(
|
||||||
__yaql_format_function, __message, args, kwargs),
|
__yaql_format_function, __message, args, kwargs),
|
||||||
__exc['stackTrace']().toString()
|
__exc['stackTrace']().toString()
|
||||||
@ -95,7 +102,7 @@ class Logger(object):
|
|||||||
# NOTE(akhivin): we do not want break program workflow
|
# NOTE(akhivin): we do not want break program workflow
|
||||||
# even formatting parameters are incorrect
|
# even formatting parameters are incorrect
|
||||||
self._underlying_logger.warning(
|
self._underlying_logger.warning(
|
||||||
'Can not format string: {0}'.format(message))
|
u'Can not format string: {0}'.format(message))
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def _log(self, log_function, yaql_format_function, message, args, kwargs):
|
def _log(self, log_function, yaql_format_function, message, args, kwargs):
|
||||||
|
@ -15,6 +15,7 @@ Methods:
|
|||||||
Contract: $.class(sys:Logger).notNull()
|
Contract: $.class(sys:Logger).notNull()
|
||||||
Body:
|
Body:
|
||||||
- $log.debug('str')
|
- $log.debug('str')
|
||||||
|
- $log.debug('тест')
|
||||||
- $log.debug('str', 1)
|
- $log.debug('str', 1)
|
||||||
- $log.debug('str {0}', message)
|
- $log.debug('str {0}', message)
|
||||||
- $log.debug('str {message}', message=>message)
|
- $log.debug('str {message}', message=>message)
|
||||||
@ -26,6 +27,7 @@ Methods:
|
|||||||
Contract: $.class(sys:Logger).notNull()
|
Contract: $.class(sys:Logger).notNull()
|
||||||
Body:
|
Body:
|
||||||
- $log.trace('str')
|
- $log.trace('str')
|
||||||
|
- $log.trace('тест')
|
||||||
- $log.trace('str', 1)
|
- $log.trace('str', 1)
|
||||||
- $log.trace('str {0}', message)
|
- $log.trace('str {0}', message)
|
||||||
- $log.trace('str {message}', message=>message)
|
- $log.trace('str {message}', message=>message)
|
||||||
@ -37,6 +39,7 @@ Methods:
|
|||||||
Contract: $.class(sys:Logger).notNull()
|
Contract: $.class(sys:Logger).notNull()
|
||||||
Body:
|
Body:
|
||||||
- $log.info('str')
|
- $log.info('str')
|
||||||
|
- $log.info('тест')
|
||||||
- $log.info('str', 1)
|
- $log.info('str', 1)
|
||||||
- $log.info('str {0}', message)
|
- $log.info('str {0}', message)
|
||||||
- $log.info('str {message}', message=>message)
|
- $log.info('str {message}', message=>message)
|
||||||
@ -48,6 +51,7 @@ Methods:
|
|||||||
Contract: $.class(sys:Logger).notNull()
|
Contract: $.class(sys:Logger).notNull()
|
||||||
Body:
|
Body:
|
||||||
- $log.warning('str')
|
- $log.warning('str')
|
||||||
|
- $log.warning('тест')
|
||||||
- $log.warning('str', 1)
|
- $log.warning('str', 1)
|
||||||
- $log.warning('str {0}', message)
|
- $log.warning('str {0}', message)
|
||||||
- $log.warning('str {message}', message=>message)
|
- $log.warning('str {message}', message=>message)
|
||||||
@ -59,6 +63,7 @@ Methods:
|
|||||||
Contract: $.class(sys:Logger).notNull()
|
Contract: $.class(sys:Logger).notNull()
|
||||||
Body:
|
Body:
|
||||||
- $log.error('str')
|
- $log.error('str')
|
||||||
|
- $log.error('тест')
|
||||||
- $log.error('str', 1)
|
- $log.error('str', 1)
|
||||||
- $log.error('str {0}', message)
|
- $log.error('str {0}', message)
|
||||||
- $log.error('str {message}', message=>message)
|
- $log.error('str {message}', message=>message)
|
||||||
@ -70,6 +75,7 @@ Methods:
|
|||||||
Contract: $.class(sys:Logger).notNull()
|
Contract: $.class(sys:Logger).notNull()
|
||||||
Body:
|
Body:
|
||||||
- $log.critical('str')
|
- $log.critical('str')
|
||||||
|
- $log.critical('тест')
|
||||||
- $log.critical('str', 1)
|
- $log.critical('str', 1)
|
||||||
- $log.critical('str {0}', message)
|
- $log.critical('str {0}', message)
|
||||||
- $log.critical('str {message}', message=>message)
|
- $log.critical('str {message}', message=>message)
|
||||||
@ -87,6 +93,7 @@ Methods:
|
|||||||
As: e
|
As: e
|
||||||
Do:
|
Do:
|
||||||
- $log.exception($e, 'str')
|
- $log.exception($e, 'str')
|
||||||
|
- $log.exception($e, 'тест')
|
||||||
- $log.exception($e, 'str', 1)
|
- $log.exception($e, 'str', 1)
|
||||||
- $log.exception($e, 'str {0}', message)
|
- $log.exception($e, 'str {0}', message)
|
||||||
- $log.exception($e, 'str {message}', message=>message)
|
- $log.exception($e, 'str {message}', message=>message)
|
||||||
@ -98,4 +105,3 @@ Methods:
|
|||||||
Body:
|
Body:
|
||||||
- Throw: exceptionName
|
- Throw: exceptionName
|
||||||
Message: exception message
|
Message: exception message
|
||||||
|
|
||||||
|
29
murano/tests/unit/dsl/meta/TestUnicode.yaml
Normal file
29
murano/tests/unit/dsl/meta/TestUnicode.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
Name: TestUnicode
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
testLiteral:
|
||||||
|
Body:
|
||||||
|
Return: солнце ♥ φεγγάρι
|
||||||
|
|
||||||
|
testExpression:
|
||||||
|
Body:
|
||||||
|
- Return: ('солнце ♥' + ' φεγγάρι').toUpper()
|
||||||
|
|
||||||
|
|
||||||
|
testParameter:
|
||||||
|
Body:
|
||||||
|
- Return: $.foo('солнце ♥ φεγγάρι')
|
||||||
|
|
||||||
|
|
||||||
|
testException:
|
||||||
|
Body:
|
||||||
|
- Throw: Exception
|
||||||
|
Message: солнце ♥ φεγγάρι
|
||||||
|
|
||||||
|
|
||||||
|
foo:
|
||||||
|
Arguments:
|
||||||
|
arg:
|
||||||
|
Contract: $.string().notNull()
|
||||||
|
Body:
|
||||||
|
Return: $arg.toUpper()
|
@ -1,3 +1,4 @@
|
|||||||
|
# coding: utf-8
|
||||||
# Copyright (c) 2015 Mirantis, Inc.
|
# Copyright (c) 2015 Mirantis, Inc.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
@ -26,6 +27,7 @@ class TestLogger(test_case.DslTestCase):
|
|||||||
|
|
||||||
FORMAT_CALLS = [
|
FORMAT_CALLS = [
|
||||||
call(ANY, 'str', (), {}),
|
call(ANY, 'str', (), {}),
|
||||||
|
call(ANY, u'тест', (), {}),
|
||||||
call(ANY, 'str', (1,), {}),
|
call(ANY, 'str', (1,), {}),
|
||||||
call(ANY, 'str {0}', ('message',), {}),
|
call(ANY, 'str {0}', ('message',), {}),
|
||||||
call(ANY, 'str {message}', (), {'message': 'message'}),
|
call(ANY, 'str {message}', (), {'message': 'message'}),
|
||||||
|
45
murano/tests/unit/dsl/test_unicode.py
Normal file
45
murano/tests/unit/dsl/test_unicode.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# coding: utf-8
|
||||||
|
# Copyright (c) 2015 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 dsl_exception
|
||||||
|
from murano.tests.unit.dsl.foundation import object_model as om
|
||||||
|
from murano.tests.unit.dsl.foundation import test_case
|
||||||
|
|
||||||
|
|
||||||
|
class TestUnicode(test_case.DslTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestUnicode, self).setUp()
|
||||||
|
self._runner = self.new_runner(om.Object('TestUnicode'))
|
||||||
|
|
||||||
|
def test_literal(self):
|
||||||
|
self.assertEqual(
|
||||||
|
u"солнце ♥ φεγγάρι",
|
||||||
|
self._runner.testLiteral())
|
||||||
|
|
||||||
|
def test_expression(self):
|
||||||
|
self.assertEqual(
|
||||||
|
u"СОЛНЦЕ ♥ ΦΕΓΓΆΡΙ",
|
||||||
|
self._runner.testExpression())
|
||||||
|
|
||||||
|
def test_parameter(self):
|
||||||
|
self.assertEqual(
|
||||||
|
u"СОЛНЦЕ ♥ ΦΕΓΓΆΡΙ",
|
||||||
|
self._runner.testParameter())
|
||||||
|
|
||||||
|
def test_exception(self):
|
||||||
|
x = self.assertRaises(
|
||||||
|
dsl_exception.MuranoPlException,
|
||||||
|
self._runner.testException)
|
||||||
|
self.assertEqual(u"солнце ♥ φεγγάρι", x.message)
|
@ -149,21 +149,6 @@ class TestYaqlExpression(base.MuranoTestCase):
|
|||||||
|
|
||||||
self.assertEqual('string', yaql_expr.expression)
|
self.assertEqual('string', yaql_expr.expression)
|
||||||
|
|
||||||
def test_unicode_expression(self):
|
|
||||||
yaql_expr = yaql_expression.YaqlExpression(u"'yaql ♥ unicode'",
|
|
||||||
self._version)
|
|
||||||
|
|
||||||
self.assertEqual(u"'yaql ♥ unicode'".encode('utf-8'),
|
|
||||||
yaql_expr.expression)
|
|
||||||
|
|
||||||
def test_unicode_expression_expression(self):
|
|
||||||
yaql_expr = yaql_expression.YaqlExpression(u"'yaql ♥ unicode'",
|
|
||||||
self._version)
|
|
||||||
yaql_expr2 = yaql_expression.YaqlExpression(yaql_expr, self._version)
|
|
||||||
|
|
||||||
self.assertEqual(u"'yaql ♥ unicode'".encode('utf-8'),
|
|
||||||
yaql_expr2.expression)
|
|
||||||
|
|
||||||
def test_evaluate_calls(self):
|
def test_evaluate_calls(self):
|
||||||
string = 'string'
|
string = 'string'
|
||||||
expected_calls = [mock.call(string, self._version),
|
expected_calls = [mock.call(string, self._version),
|
||||||
|
Loading…
Reference in New Issue
Block a user