From 04f027193131072757bbcc1558de19367147c45d Mon Sep 17 00:00:00 2001 From: Ethan Lynn Date: Mon, 13 Oct 2014 17:58:57 +0800 Subject: [PATCH] Add unicode support for resource name When using template which has resource name in unicode to create stack will failed with encode/decode error. This patch override __str__ & __unicode__ function in related classes to make them compatible with unicode. blueprint: template-unicode-support Change-Id: Ie677d180d2131abd052101996900414e2b2ef4ad --- heat/engine/dependencies.py | 30 ++++++++++++++++++++++++---- heat/engine/resource.py | 39 ++++++++++++++++++++++++++----------- heat/engine/scheduler.py | 35 ++++++++++++++++++++------------- heat/engine/stack.py | 12 +++++++++--- 4 files changed, 85 insertions(+), 31 deletions(-) diff --git a/heat/engine/dependencies.py b/heat/engine/dependencies.py index f9f3971c0..28c47f08b 100644 --- a/heat/engine/dependencies.py +++ b/heat/engine/dependencies.py @@ -14,6 +14,7 @@ import collections import itertools +from oslo.utils import encodeutils import six from six.moves import xrange @@ -84,7 +85,13 @@ class Node(object): def __str__(self): '''Return a human-readable string representation of the node.''' - return '{%s}' % ', '.join(str(n) for n in self) + text = '{%s}' % ', '.join(str(n) for n in self) + return encodeutils.safe_encode(text) + + def __unicode__(self): + '''Return a human-readable string representation of the node.''' + text = '{%s}' % ', '.join(unicode(n) for n in self) + return encodeutils.safe_decode(text) def __repr__(self): '''Return a string representation of the node.''' @@ -137,7 +144,15 @@ class Graph(collections.defaultdict): def __str__(self): '''Convert the graph to a human-readable string.''' pairs = ('%s: %s' % (str(k), str(v)) for k, v in six.iteritems(self)) - return '{%s}' % ', '.join(pairs) + text = '{%s}' % ', '.join(pairs) + return encodeutils.safe_encode(text) + + def __unicode__(self): + '''Convert the graph to a human-readable string.''' + pairs = ('%s: %s' % (unicode(k), unicode(v)) + for k, v in six.iteritems(self)) + text = '{%s}' % ', '.join(pairs) + return encodeutils.safe_decode(text) @staticmethod def toposort(graph): @@ -155,7 +170,7 @@ class Graph(collections.defaultdict): else: # There are nodes remaining, but none without # dependencies: a cycle - raise CircularDependencyException(cycle=str(graph)) + raise CircularDependencyException(cycle=six.text_type(graph)) class Dependencies(object): @@ -227,10 +242,17 @@ class Dependencies(object): ''' return str(self._graph) + def __unicode__(self): + ''' + Return a human-readable string representation of the dependency graph + ''' + return unicode(self._graph) + def __repr__(self): '''Return a string representation of the object.''' edge_reprs = (repr(e) for e in self._graph.edges()) - return 'Dependencies([%s])' % ', '.join(edge_reprs) + text = 'Dependencies([%s])' % ', '.join(edge_reprs) + return encodeutils.safe_encode(text) def graph(self, reverse=False): '''Return a copy of the underlying dependency graph.''' diff --git a/heat/engine/resource.py b/heat/engine/resource.py index 722a265d0..673a43faf 100644 --- a/heat/engine/resource.py +++ b/heat/engine/resource.py @@ -15,6 +15,7 @@ import base64 import contextlib from datetime import datetime from oslo.config import cfg +from oslo.utils import encodeutils from oslo.utils import excutils import six import warnings @@ -365,11 +366,27 @@ class Resource(object): def __str__(self): if self.stack.id: if self.resource_id: - return '%s "%s" [%s] %s' % (self.__class__.__name__, self.name, + text = '%s "%s" [%s] %s' % (self.__class__.__name__, self.name, self.resource_id, str(self.stack)) - return '%s "%s" %s' % (self.__class__.__name__, self.name, - str(self.stack)) - return '%s "%s"' % (self.__class__.__name__, self.name) + else: + text = '%s "%s" %s' % (self.__class__.__name__, self.name, + str(self.stack)) + else: + text = '%s "%s"' % (self.__class__.__name__, self.name) + return encodeutils.safe_encode(text) + + def __unicode__(self): + if self.stack.id: + if self.resource_id: + text = '%s "%s" [%s] %s' % (self.__class__.__name__, self.name, + self.resource_id, + unicode(self.stack)) + else: + text = '%s "%s" %s' % (self.__class__.__name__, self.name, + unicode(self.stack)) + else: + text = '%s "%s"' % (self.__class__.__name__, self.name) + return encodeutils.safe_decode(text) def add_dependencies(self, deps): for dep in self.t.dependencies(self.stack): @@ -441,7 +458,7 @@ class Resource(object): LOG.debug('%s', six.text_type(ex)) except Exception as ex: LOG.info('%(action)s: %(info)s', {"action": action, - "info": str(self)}, + "info": six.text_type(self)}, exc_info=True) failure = exception.ResourceFailure(ex, self, action) self.state_set(action, self.FAILED, six.text_type(failure)) @@ -528,10 +545,10 @@ class Resource(object): action = self.CREATE if (self.action, self.status) != (self.INIT, self.COMPLETE): exc = exception.Error(_('State %s invalid for create') - % str(self.state)) + % six.text_type(self.state)) raise exception.ResourceFailure(exc, self, action) - LOG.info(_LI('creating %s'), str(self)) + LOG.info(_LI('creating %s'), six.text_type(self)) # Re-resolve the template, since if the resource Ref's # the StackId pseudo parameter, it will change after @@ -723,7 +740,7 @@ class Resource(object): # Don't try to suspend the resource unless it's in a stable state if (self.action == self.DELETE or self.status != self.COMPLETE): exc = exception.Error(_('State %s invalid for suspend') - % str(self.state)) + % six.text_type(self.state)) raise exception.ResourceFailure(exc, self, action) LOG.info(_LI('suspending %s'), six.text_type(self)) @@ -739,7 +756,7 @@ class Resource(object): # Can't resume a resource unless it's SUSPEND_COMPLETE if self.state != (self.SUSPEND, self.COMPLETE): exc = exception.Error(_('State %s invalid for resume') - % str(self.state)) + % six.text_type(self.state)) raise exception.ResourceFailure(exc, self, action) LOG.info(_LI('resuming %s'), six.text_type(self)) @@ -1039,8 +1056,8 @@ class Resource(object): reason_string = get_string_details() self._add_event('signal', self.status, reason_string) except Exception as ex: - LOG.exception(_('signal %(name)s : %(msg)s') % {'name': str(self), - 'msg': ex}) + LOG.exception(_('signal %(name)s : %(msg)s') + % {'name': six.text_type(self), 'msg': ex}) failure = exception.ResourceFailure(ex, self) raise failure diff --git a/heat/engine/scheduler.py b/heat/engine/scheduler.py index d4779f06f..22f3b9d55 100644 --- a/heat/engine/scheduler.py +++ b/heat/engine/scheduler.py @@ -18,6 +18,7 @@ from time import time as wallclock import types import eventlet +from oslo.utils import encodeutils from oslo.utils import excutils import six @@ -43,7 +44,7 @@ def task_description(task): return '%s from %s' % (name, task.__self__) elif isinstance(task, types.FunctionType): if name is not None: - return str(name) + return six.text_type(name) return repr(task) @@ -62,7 +63,7 @@ class Timeout(BaseException): """ Initialise with the TaskRunner and a timeout period in seconds. """ - message = _('%s Timed out') % str(task_runner) + message = _('%s Timed out') % six.text_type(task_runner) super(Timeout, self).__init__(message) # Note that we don't attempt to handle leap seconds or large clock @@ -148,12 +149,18 @@ class TaskRunner(object): def __str__(self): """Return a human-readable string representation of the task.""" - return 'Task %s' % self.name + text = 'Task %s' % self.name + return encodeutils.safe_encode(text) + + def __unicode__(self): + """Return a human-readable string representation of the task.""" + text = 'Task %s' % self.name + return encodeutils.safe_decode(text) def _sleep(self, wait_time): """Sleep for the specified number of seconds.""" if ENABLE_SLEEP and wait_time is not None: - LOG.debug('%s sleeping' % str(self)) + LOG.debug('%s sleeping' % six.text_type(self)) eventlet.sleep(wait_time) def __call__(self, wait_time=1, timeout=None): @@ -180,7 +187,7 @@ class TaskRunner(object): assert self._runner is None, "Task already started" assert not self._done, "Task already cancelled" - LOG.debug('%s starting' % str(self)) + LOG.debug('%s starting' % six.text_type(self)) if timeout is not None: self._timeout = Timeout(self, timeout) @@ -192,7 +199,7 @@ class TaskRunner(object): else: self._runner = False self._done = True - LOG.debug('%s done (not resumable)' % str(self)) + LOG.debug('%s done (not resumable)' % six.text_type(self)) def step(self): """ @@ -203,18 +210,18 @@ class TaskRunner(object): assert self._runner is not None, "Task not started" if self._timeout is not None and self._timeout.expired(): - LOG.info(_LI('%s timed out'), str(self)) + LOG.info(_LI('%s timed out'), six.text_type(self)) self._done = True self._timeout.trigger(self._runner) else: - LOG.debug('%s running' % str(self)) + LOG.debug('%s running' % six.text_type(self)) try: next(self._runner) except StopIteration: self._done = True - LOG.debug('%s complete' % str(self)) + LOG.debug('%s complete' % six.text_type(self)) return self._done @@ -234,7 +241,7 @@ class TaskRunner(object): return if not self.started() or grace_period is None: - LOG.debug('%s cancelled' % str(self)) + LOG.debug('%s cancelled' % six.text_type(self)) self._done = True if self.started(): self._runner.close() @@ -351,12 +358,13 @@ class DependencyTaskGroup(object): if name is None: name = '(%s) %s' % (getattr(task, '__name__', task_description(task)), - str(dependencies)) + six.text_type(dependencies)) self.name = name def __repr__(self): """Return a string representation of the task.""" - return '%s(%s)' % (type(self).__name__, self.name) + text = '%s(%s)' % (type(self).__name__, self.name) + return encodeutils.safe_encode(text) def __call__(self): """Return a co-routine which runs the task group.""" @@ -497,7 +505,8 @@ class PollingTaskGroup(object): def __repr__(self): """Return a string representation of the task group.""" - return '%s(%s)' % (type(self).__name__, self.name) + text = '%s(%s)' % (type(self).__name__, self.name) + return encodeutils.safe_encode(text) def __call__(self): """Return a co-routine which runs the task group.""" diff --git a/heat/engine/stack.py b/heat/engine/stack.py index 2deb458f9..dba1db7f3 100644 --- a/heat/engine/stack.py +++ b/heat/engine/stack.py @@ -391,7 +391,13 @@ class Stack(collections.Mapping): def __str__(self): '''Return a human-readable string representation of the stack.''' - return 'Stack "%s" [%s]' % (self.name, self.id) + text = 'Stack "%s" [%s]' % (self.name, self.id) + return encodeutils.safe_encode(text) + + def __unicode__(self): + '''Return a human-readable string representation of the stack.''' + text = 'Stack "%s" [%s]' % (self.name, self.id) + return encodeutils.safe_encode(text) def resource_by_refid(self, refid): ''' @@ -991,7 +997,7 @@ class Stack(collections.Mapping): ''' # No need to suspend if the stack has been suspended if self.state == (self.SUSPEND, self.COMPLETE): - LOG.info(_LI('%s is already suspended'), str(self)) + LOG.info(_LI('%s is already suspended'), six.text_type(self)) return sus_task = scheduler.TaskRunner(self.stack_task, @@ -1011,7 +1017,7 @@ class Stack(collections.Mapping): ''' # No need to resume if the stack has been resumed if self.state == (self.RESUME, self.COMPLETE): - LOG.info(_LI('%s is already resumed'), str(self)) + LOG.info(_LI('%s is already resumed'), six.text_type(self)) return sus_task = scheduler.TaskRunner(self.stack_task,