
- Convert the various functions that take a task detail into ones that take atom details (since this is now the generic type they should take). - Don't expose the detail type strings as part of the atom detail api, leave those as private hidden strings and provide conversion functions from string<->class instead. - Have the logbook objects contain the following new methods to reduce the dependence on persistence_utils to do the same. - to_dict() which converts the current object into a dict - from_dict() which converts the provided dict into a object - merge() which merges a incoming objects data with the current objects - Have the persistence backends + storage + action engine use these new methods instead of there current usage. - Don't compare to logbook.RETRY_DETAIL or logbook.TASK_DETAIL since python has the isinstance function just use it (ideally we should fix the code so that this isn't even needed, usage of isinstance means something is not designed/structured right). - In storage tests we can't assume that failures will be non-lossy since under certain backends when a failure is stored information about the internally held exc_info is lost, so take this into account when testing by using matches() where applicable. Change-Id: Ie8a274cfd4cb4e64e87c355dc99d466d74a4e82c
162 lines
6.0 KiB
Python
162 lines
6.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright (C) 2012 Yahoo! Inc. All Rights Reserved.
|
|
#
|
|
# 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 contextlib
|
|
import logging
|
|
|
|
from taskflow.openstack.common import timeutils
|
|
from taskflow.openstack.common import uuidutils
|
|
from taskflow.persistence import logbook
|
|
from taskflow.utils import misc
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def temporary_log_book(backend=None):
|
|
"""Creates a temporary logbook for temporary usage in the given backend.
|
|
|
|
Mainly useful for tests and other use cases where a temporary logbook
|
|
is needed for a short-period of time.
|
|
"""
|
|
book = logbook.LogBook('tmp')
|
|
if backend is not None:
|
|
with contextlib.closing(backend.get_connection()) as conn:
|
|
conn.save_logbook(book)
|
|
return book
|
|
|
|
|
|
def temporary_flow_detail(backend=None):
|
|
"""Creates a temporary flow detail and logbook for temporary usage in
|
|
the given backend.
|
|
|
|
Mainly useful for tests and other use cases where a temporary flow detail
|
|
is needed for a short-period of time.
|
|
"""
|
|
flow_id = uuidutils.generate_uuid()
|
|
book = temporary_log_book(backend)
|
|
book.add(logbook.FlowDetail(name='tmp-flow-detail', uuid=flow_id))
|
|
if backend is not None:
|
|
with contextlib.closing(backend.get_connection()) as conn:
|
|
conn.save_logbook(book)
|
|
# Return the one from the saved logbook instead of the local one so
|
|
# that the freshest version is given back.
|
|
return book, book.find(flow_id)
|
|
|
|
|
|
def create_flow_detail(flow, book=None, backend=None, meta=None):
|
|
"""Creates a flow detail for the given flow and adds it to the provided
|
|
logbook (if provided) and then uses the given backend (if provided) to
|
|
save the logbook then returns the created flow detail.
|
|
"""
|
|
flow_id = uuidutils.generate_uuid()
|
|
flow_name = getattr(flow, 'name', None)
|
|
if flow_name is None:
|
|
LOG.warn("No name provided for flow %s (id %s)", flow, flow_id)
|
|
flow_name = flow_id
|
|
|
|
flow_detail = logbook.FlowDetail(name=flow_name, uuid=flow_id)
|
|
if meta is not None:
|
|
if flow_detail.meta is None:
|
|
flow_detail.meta = {}
|
|
flow_detail.meta.update(meta)
|
|
|
|
if backend is not None and book is None:
|
|
LOG.warn("No logbook provided for flow %s, creating one.", flow)
|
|
book = temporary_log_book(backend)
|
|
|
|
if book is not None:
|
|
book.add(flow_detail)
|
|
if backend is not None:
|
|
with contextlib.closing(backend.get_connection()) as conn:
|
|
conn.save_logbook(book)
|
|
# Return the one from the saved logbook instead of the local one so
|
|
# that the freshest version is given back.
|
|
return book.find(flow_id)
|
|
else:
|
|
return flow_detail
|
|
|
|
|
|
def _format_meta(metadata, indent):
|
|
"""Format the common metadata dictionary in the same manner."""
|
|
if not metadata:
|
|
return []
|
|
lines = [
|
|
'%s- metadata:' % (" " * indent),
|
|
]
|
|
for (k, v) in metadata.items():
|
|
# Progress for now is a special snowflake and will be formatted
|
|
# in percent format.
|
|
if k == 'progress' and isinstance(v, misc.NUMERIC_TYPES):
|
|
v = "%0.2f%%" % (v * 100.0)
|
|
lines.append("%s+ %s = %s" % (" " * (indent + 2), k, v))
|
|
return lines
|
|
|
|
|
|
def _format_shared(obj, indent):
|
|
"""Format the common shared attributes in the same manner."""
|
|
if obj is None:
|
|
return []
|
|
lines = []
|
|
for attr_name in ("uuid", "state"):
|
|
if not hasattr(obj, attr_name):
|
|
continue
|
|
lines.append("%s- %s = %s" % (" " * indent, attr_name,
|
|
getattr(obj, attr_name)))
|
|
return lines
|
|
|
|
|
|
def pformat_atom_detail(atom_detail, indent=0):
|
|
"""Pretty formats a atom detail."""
|
|
detail_type = logbook.atom_detail_type(atom_detail)
|
|
lines = ["%s%s: '%s'" % (" " * (indent), detail_type, atom_detail.name)]
|
|
lines.extend(_format_shared(atom_detail, indent=indent + 1))
|
|
lines.append("%s- version = %s"
|
|
% (" " * (indent + 1), misc.get_version_string(atom_detail)))
|
|
lines.append("%s- results = %s"
|
|
% (" " * (indent + 1), atom_detail.results))
|
|
lines.append("%s- failure = %s" % (" " * (indent + 1),
|
|
bool(atom_detail.failure)))
|
|
lines.extend(_format_meta(atom_detail.meta, indent=indent + 1))
|
|
return "\n".join(lines)
|
|
|
|
|
|
def pformat_flow_detail(flow_detail, indent=0):
|
|
"""Pretty formats a flow detail."""
|
|
lines = ["%sFlow: '%s'" % (" " * indent, flow_detail.name)]
|
|
lines.extend(_format_shared(flow_detail, indent=indent + 1))
|
|
lines.extend(_format_meta(flow_detail.meta, indent=indent + 1))
|
|
for task_detail in flow_detail:
|
|
lines.append(pformat_atom_detail(task_detail, indent=indent + 1))
|
|
return "\n".join(lines)
|
|
|
|
|
|
def pformat(book, indent=0):
|
|
"""Pretty formats a logbook."""
|
|
lines = ["%sLogbook: '%s'" % (" " * indent, book.name)]
|
|
lines.extend(_format_shared(book, indent=indent + 1))
|
|
lines.extend(_format_meta(book.meta, indent=indent + 1))
|
|
if book.created_at is not None:
|
|
lines.append("%s- created_at = %s"
|
|
% (" " * (indent + 1),
|
|
timeutils.isotime(book.created_at)))
|
|
if book.updated_at is not None:
|
|
lines.append("%s- updated_at = %s"
|
|
% (" " * (indent + 1),
|
|
timeutils.isotime(book.updated_at)))
|
|
for flow_detail in book:
|
|
lines.append(pformat_flow_detail(flow_detail, indent=indent + 1))
|
|
return "\n".join(lines)
|