# -*- coding: utf-8 -*- # Copyright (C) 2012 Yahoo! Inc. All Rights Reserved. # Copyright (C) 2013 Rackspace Hosting 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 logging import abc import six from taskflow.openstack.common import uuidutils from taskflow import states LOG = logging.getLogger(__name__) TASK_DETAIL = 'TASK_DETAIL' RETRY_DETAIL = 'RETRY_DETAIL' ATOM_TYPES = [TASK_DETAIL, RETRY_DETAIL] class LogBook(object): """This class that contains a dict of flow detail entries for a given *job* so that the job can track what 'work' has been completed for resumption/reverting and miscellaneous tracking purposes. The data contained within this class need *not* be backed by the backend storage in real time. The data in this class will only be guaranteed to be persisted when a save occurs via some backend connection. """ def __init__(self, name, uuid=None, updated_at=None, created_at=None): if uuid: self._uuid = uuid else: self._uuid = uuidutils.generate_uuid() self._name = name self._flowdetails_by_id = {} self._updated_at = updated_at self._created_at = created_at self.meta = None @property def created_at(self): return self._created_at @property def updated_at(self): return self._updated_at def add(self, fd): """Adds a new entry to the underlying logbook. Does not *guarantee* that the details will be immediately saved. """ self._flowdetails_by_id[fd.uuid] = fd def find(self, flow_uuid): return self._flowdetails_by_id.get(flow_uuid, None) @property def uuid(self): return self._uuid @property def name(self): return self._name def __iter__(self): for fd in six.itervalues(self._flowdetails_by_id): yield fd def __len__(self): return len(self._flowdetails_by_id) class FlowDetail(object): """This class contains a dict of task detail entries for a given flow along with any metadata associated with that flow. The data contained within this class need *not* be backed by the backend storage in real time. The data in this class will only be guaranteed to be persisted when a save/update occurs via some backend connection. """ def __init__(self, name, uuid): self._uuid = uuid self._name = name self._taskdetails_by_id = {} self.state = None # Any other metadata to include about this flow while storing. For # example timing information could be stored here, other misc. flow # related items (edge connections)... self.meta = None def update(self, fd): """Updates the objects state to be the same as the given one.""" if fd is self: return self._taskdetails_by_id = dict(fd._taskdetails_by_id) self.state = fd.state self.meta = fd.meta def add(self, td): self._taskdetails_by_id[td.uuid] = td def find(self, td_uuid): return self._taskdetails_by_id.get(td_uuid) @property def uuid(self): return self._uuid @property def name(self): return self._name def __iter__(self): for td in six.itervalues(self._taskdetails_by_id): yield td def __len__(self): return len(self._taskdetails_by_id) @six.add_metaclass(abc.ABCMeta) class AtomDetail(object): """This is a base class that contains an entry that contains the persistence of an atom after or before (or during) it is running including any results it may have produced, any state that it may be in (failed for example), any exception that occurred when running and any associated stacktrace that may have occurring during that exception being thrown and any other metadata that should be stored along-side the details about this atom. The data contained within this class need *not* backed by the backend storage in real time. The data in this class will only be guaranteed to be persisted when a save/update occurs via some backend connection. """ def __init__(self, name, uuid): self._uuid = uuid self._name = name # TODO(harlowja): decide if these should be passed in and therefore # immutable or let them be assigned? # # The state the atom was last in. self.state = None # The intention of action that would be applied to the atom. self.intention = states.EXECUTE # The results it may have produced (useful for reverting). self.results = None # An Failure object that holds exception the atom may have thrown # (or part of it), useful for knowing what failed. self.failure = None # Any other metadata to include about this atom while storing. For # example timing information could be stored here, other misc. atom # related items. self.meta = None # The version of the atom this atom details was associated with which # is quite useful for determining what versions of atoms this detail # information can be associated with. self.version = None def update(self, td): """Updates the objects state to be the same as the given one.""" if td is self: return self.state = td.state self.intention = td.intention self.meta = td.meta self.failure = td.failure self.results = td.results self.version = td.version @property def uuid(self): return self._uuid @property def name(self): return self._name @abc.abstractproperty def atom_type(self): """Identifies atom type represented by this detail.""" @abc.abstractmethod def reset(self, state): """Resets detail results ans failures.""" class TaskDetail(AtomDetail): """This class represents a task detail for flow task object.""" def __init__(self, name, uuid): super(TaskDetail, self).__init__(name, uuid) @property def atom_type(self): return TASK_DETAIL def reset(self, state): self.results = None self.failure = None self.state = state self.intention = states.EXECUTE class RetryDetail(AtomDetail): """This class represents a retry detail for retry controller object.""" def __init__(self, name, uuid): super(RetryDetail, self).__init__(name, uuid) self.results = [] @property def atom_type(self): return RETRY_DETAIL def reset(self, state): self.results = [] self.failure = None self.state = state self.intention = states.EXECUTE def get_atom_detail_class(atom_type): if atom_type == TASK_DETAIL: return TaskDetail elif atom_type == RETRY_DETAIL: return RetryDetail else: raise TypeError("Unknown atom type")