class Tag(with_metaclass(TagMeta)): """ Tag is the base class for all Tags. Tags are used as filtering mechanism for actors to be loaded during workflow executions. Phases do use tags to filter actors according to their tags. Special Tag class attributes: Tag here refers to the derived class of Tag Tag.Common: Dynamically created class type that designates actors to be executed in the `main` stage during workflow phases. Using common includes this actor in any workflow, which means the that any workflow tag filter will be ignored if this tag matches. Tag.Before: Dynamically created class type that designates actors to be executed in the `before` stage during workflow phases. Tag.Before.Common: Dynamically created class type that designates actors to be executed in the `before` stage during workflow phases. Using common includes this actor in any workflow, which means the that any workflow tag filter will be ignored if this tag matches. Tag.After: Dynamically created class type that designates actors to be executed in the `after` stage during workflow phases. Tag.After.Common: Dynamically created class type that designates actors to be executed in the `after` stage during workflow phases. Using common includes this actor in any workflow, which means the that any workflow tag filter will be ignored if this tag matches. """ actors = () """
class Model(with_metaclass(ModelMeta)): """ Model is a base class for all models. Models are defining the data structure of the payload of messages and the metadata required, such as a name and topic. """ def __init__(self, init_method='from_initialization', **kwargs): super(Model, self).__init__() defined_fields = type(self).fields for key in kwargs.keys(): if key not in defined_fields: raise ModelMisuseError( 'Trying to initialize model {} with value for undefined field {}' .format(type(self).__name__, key)) for field in defined_fields.keys(): getattr(defined_fields[field], init_method)(kwargs, field, self) topic = None """ `topic` has to be set to a subclass of :py:class:`leapp.topics.Topic` It defines the categorization of this model. """ fields = None """ `fields` contains a dictionary with all attributes of the py:class:`leapp.models.fields.Field` type in the class. Note: Dynamically added fields are ignored by the framework. """ @classmethod def create(cls, data): """ Create an instance of this class and use the data to initialize the fields within. :param data: Data to initialize the Model from deserialized data :type data: dict :return: Instance of this class """ return cls(init_method='to_model', **data) def dump(self): """ Dumps the data in the dictionary form that is safe to serialize to JSON. :return: dict with a builtin representation of the data that can be safely serialized to JSON """ result = {} for field in type(self).fields.keys(): type(self).fields[field].to_builtin(self, field, result) return result def __eq__(self, other): """ Implementation for equality comparison of Model instances """ return isinstance(other, type(self)) and \ all(getattr(self, name) == getattr(other, name) for name in sorted(type(self).fields.keys()))
class Topic(with_metaclass(TopicMeta)): """ Base class for all :ref:`topics <terminology:topic>`""" name = None """ Name of the topic """ messages = () """
class Model(with_metaclass(ModelMeta)): """ Model is the base class for all models Models are defining the data structure of the payload of messages and the meta data required around that. Such as name and topic """ def __init__(self, init_method='from_initialization', **kwargs): super(Model, self).__init__() for field in type(self).fields.keys(): getattr(type(self).fields[field], init_method)(kwargs, field, self) topic = None """ `topic` has to be set to a subclass of leapp.topics.Topic It defines the categorization of this model. """ fields = None """ `fields` contains a dictionary with all attributes of type `Field` in the class Note: Dynamically added fields are ignored by the framework """ @classmethod def create(cls, data): """ Create an instance of this class and use data to initialize the fields within :param data: Data to initialize the Model from deserialized data :type data: dict :return: Instance of this class """ return cls(init_method='to_model', **data) def dump(self): """ Dumps the data in dictionary form that is safe to serialize to JSON :return: dict with builtin representation of the data that can be safely serialized to JSON """ result = {} for field in type(self).fields.keys(): type(self).fields[field].to_builtin(self, field, result) return result def __eq__(self, other): """ Implementation for equality comparison of Model instances """ return isinstance(other, type(self)) and \ all(getattr(self, name) == getattr(other, name) for name in sorted(type(self).fields.keys()))
class Tag(with_metaclass(TagMeta)): """ Tag is the base class for all Tags. Tags are used as filtering mechanism for actors to be loaded during workflow executions. Phases do use tags to filter actors according to their tags. Special Tag class attributes: Tag here refers to the derived class of Tag Tag.Common: Dynamically created class type that designates actors to be executed in the `main` stage during workflow phases. Using common includes this actor in any workflow, which means the that any workflow tag filter will be ignored if this tag matches. Tag.Before: Dynamically created class type that designates actors to be executed in the `before` stage during workflow phases. Tag.Before.Common: Dynamically created class type that designates actors to be executed in the `before` stage during workflow phases. Using common includes this actor in any workflow, which means the that any workflow tag filter will be ignored if this tag matches. Tag.After: Dynamically created class type that designates actors to be executed in the `after` stage during workflow phases. Tag.After.Common: Dynamically created class type that designates actors to be executed in the `after` stage during workflow phases. Using common includes this actor in any workflow, which means the that any workflow tag filter will be ignored if this tag matches. """ actors = () """ Tuple of all registered actors using this tag """ name = None """ Name of the Tag in snake case """ @classmethod def serialize(cls): return { 'class_name': cls.__name__, 'name': cls.name, 'actors': [actor.class_name for actor in cls.actors] }
class Topic(with_metaclass(TopicMeta)): """ Base class for all :ref:`topics <terminology:topic>`""" name = None """ Name of the topic in snake case """ messages = () """ Tuple of :py:class:`leapp.models.Model` derived classes that are using this topic are automatically added to this variable. """ @classmethod def serialize(cls): return { 'class_name': cls.__name__, 'name': cls.name, 'messages': [m.__name__ for m in cls.messages] }
class Model(with_metaclass(ModelMeta)): def __init__(self, *args, **kwargs): init_method = kwargs.pop('init_method', 'from_initialization') super(Model, self).__init__() for field in type(self).fields.keys(): getattr(type(self).fields[field], init_method)(kwargs, field, self) @classmethod def create(cls, data): return cls(init_method='to_model', **data) def dump(self): result = {} for field in type(self).fields.keys(): type(self).fields[field].to_builtin(self, field, result) return result def __eq__(self, other): return isinstance(other, type(self)) and \ all(getattr(self, name) == getattr(other, name) for name in sorted(type(self).fields.keys()))
class Phase(with_metaclass(PhaseMeta)): name = None filter = None policies = Policies(Policies.Errors.FailPhase, Policies.Retry.Phase) flags = Flags() @classmethod def get_index(cls): return PhaseMeta.classes.index(cls) @classmethod def serialize(cls): """ :return: Dictionary with the serialized representation of the phase """ return { 'name': cls.name, 'index': cls.get_index(), 'filter': cls.filter.serialize() if cls.filter else None, 'policies': cls.policies.serialize(), 'flags': cls.flags.serialize(), }
class Workflow(with_metaclass(WorkflowMeta)): """ Workflow is the base class for all :ref:`workflow <terminology:workflow>` definitions. """ name = None """Name of the workflow""" short_name = None """ Short name of the workflow """ tag = None """ Workflow Tag """ description = '' """ Documentation for the workflow """ configuration = None """ Model to be used as workflow configuration """ @property def errors(self): """ :return: All reported errors """ return self._errors @property def failure(self): return self._errors or self._unhandled_exception or self._stop_after_phase_requested @property def answer_store(self): """ : return: AnswerStore instance used for messaging """ return self._answer_store def save_answers(self, answerfile_path, userchoices_path): """ Generates an answer file for the dialogs of the workflow and saves it to `answerfile_path`. Updates a .userchoices file at `userchoices_path` with new answers encountered in answerfile. :param answerfile_path: The path where to store the answer file. :param userchoices_path: The path where to store the .userchoices file. :return: None """ # answerfile is generated only for the dialogs actually encountered in the worflow self._answer_store.generate(self._dialogs, answerfile_path) # userchoices is updated with any new data retrieved from answerfile self._answer_store.update(userchoices_path, allow_missing=True) def _load_from_file(self, filepath): if os.path.isfile(filepath): # XXX FIXME load_and_translate doesn't help here as somehow dialog.component.value # in Dialog.request_answers is not respectfully updated (set to None so storage # values are not taken into consideration). # Patching in 2 places - load here and direct call to translate in request_answers self._answer_store.load(filepath) else: self.log.warning("Previous file %s not found", filepath) def load_answers(self, answerfile_path, userchoices_path): self._load_from_file(userchoices_path) self._load_from_file(answerfile_path) def __init__(self, logger=None, auto_reboot=False): """ :param logger: Optional logger to be used instead of leapp.workflow :type logger: Instance of :py:class:`logging.Logger` """ self.log = (logger or logging.getLogger('leapp')).getChild('workflow') self._errors = [] self._all_consumed = set() self._all_produced = set() self._initial = set() self._phase_actors = [] self._experimental_whitelist = set() self._auto_reboot = auto_reboot self._unhandled_exception = False self._answer_store = AnswerStore() self._dialogs = [] self._stop_after_phase_requested = False if self.configuration: config_actors = [ actor for actor in self.tag.actors if self.configuration in actor.produces ] if config_actors: if len(config_actors) == 1: self._phase_actors.append( (_ConfigPhase, PhaseActors((), 'Before'), PhaseActors(tuple(config_actors), 'Main'), PhaseActors((), 'After'))) else: config_actor_names = [a.name for a in config_actors] raise MultipleConfigActorsError(config_actor_names) self.description = self.description or type(self).__doc__ for phase in self.phases: phase.filter.tags += (self.tag, ) self._phase_actors.append(( phase, # filters all actors with the give tags # phasetag .Before self._apply_phase(phase.filter.get_before(), 'Before'), # phasetag self._apply_phase(phase.filter.get(), 'Main'), # phasetag .After self._apply_phase(phase.filter.get_after(), 'After'))) def _apply_phase(self, actors, stage): phase_actors = PhaseActors(actors, stage) self._initial.update(set(phase_actors.initial) - self._all_produced) self._all_consumed.update(phase_actors.consumes) self._all_produced.update(phase_actors.produces) return phase_actors @property def experimental_whitelist(self): """ Whitelist of actors that may be executed even that they are marked experimental """ return self._experimental_whitelist def whitelist_experimental_actor(self, actor): """ Adds an actor to the experimental whitelist and allows them to be executed. :param actor: Actor to be whitelisted :type actor: class derived from py:class:`leapp.actors.Actor` :return: None """ if actor: self._experimental_whitelist.add(actor) @property def phase_actors(self): """ Return all actors for the phase """ return self._phase_actors @property def initial(self): """ Initial messages required """ return self._initial @property def consumes(self): """ All consumed messages """ return self._all_consumed @property def produces(self): """ All produced messages """ return self._all_produced @property def dialogs(self): """ All encountered dialogs """ return self._dialogs @classmethod def serialize(cls): """ :return: Serialized form of the workflow """ return { 'name': cls.name, 'short_name': cls.short_name, 'tag': cls.tag.__name__, 'description': cls.description, 'phases': [phase.serialize() for phase in cls.phases], } def is_valid_phase(self, phase=None): if phase: return phase in [ name for phs in self._phase_actors for name in phase_names(phs) ] def run(self, context=None, until_phase=None, until_actor=None, skip_phases_until=None, skip_dialogs=False, only_with_tags=None): """ Executes the workflow :param context: Custom execution ID to be used instead of a randomly generated UUIDv4 :type context: str :param until_phase: Specify until including which phase the execution should run - phase.stage can be used to control it even more granularly. `phase` is any phase name where `stage` refers to `main`, `before` or `after`. If no stage is defined, `after` is assumed to be the default value. The execution ends when this phase (and stage, if specified) has been executed. :type until_phase: str :param until_actor: The execution finishes when this actor has been executed. :type until_actor: str :param skip_phases_until: Skips all phases until including the phase specified, and then continues the execution. :type skip_phases_until: str or None :param skip_dialogs: Inform actors about the mode of dialogs processing. If skip_dialogs is set to True it means that dialogs can't be processed in the current workflow run interactively and every attempted call of get_answers api method will be non-blocking, returning an empty dict if no user choice was found in answerfile or a selected option otherwise. If skip_dialogs is set to False then in case of absent recorded answer the dialog will be rendered in a blocking user input requiring way. The value of skip_dialogs will be passed to the actors that can theoretically use it for their purposes. :type skip_dialogs: bool :param only_with_tags: Executes only actors with the given tag, any other actor is going to get skipped. :type only_with_tags: List[str] """ context = context or str(uuid.uuid4()) os.environ['LEAPP_EXECUTION_ID'] = context if not os.environ.get('LEAPP_HOSTNAME', None): os.environ['LEAPP_HOSTNAME'] = socket.getfqdn() self.log.info('Starting workflow execution: {name} - ID: {id}'.format( name=self.name, id=os.environ['LEAPP_EXECUTION_ID'])) skip_phases_until = (skip_phases_until or '').lower() needle_phase = until_phase or '' needle_stage = None if '.' in needle_phase: needle_phase, needle_stage = needle_phase.split('.', 1) needle_phase = needle_phase.lower() needle_stage = (needle_stage or '').lower() needle_actor = (until_actor or '').lower() self._errors = get_errors(context) config_model = type(self).configuration for phase in skip_phases_until, needle_phase: if phase and not self.is_valid_phase(phase): raise CommandError( 'Phase {phase} does not exist in the workflow'.format( phase=phase)) self._stop_after_phase_requested = False for phase in self._phase_actors: os.environ['LEAPP_CURRENT_PHASE'] = phase[0].name if skip_phases_until: if skip_phases_until in phase_names(phase): skip_phases_until = '' self.log.info( 'Skipping phase {name}'.format(name=phase[0].name)) continue display_status_current_phase(phase) self.log.info('Starting phase {name}'.format(name=phase[0].name)) current_logger = self.log.getChild(phase[0].name) early_finish = False for stage in phase[1:]: if early_finish: return current_logger.info( "Starting stage {stage} of phase {phase}".format( phase=phase[0].name, stage=stage.stage)) for actor in stage.actors: if early_finish: return designation = '' if ExperimentalTag in actor.tags: designation = '[EXPERIMENTAL]' if actor not in self.experimental_whitelist: current_logger.info( "Skipping experimental actor {actor}".format( actor=actor.name)) continue if only_with_tags and not contains_tag( only_with_tags, actor.tags): current_logger.info( "Actor {actor} does not contain any required tag. Skipping." .format(actor=actor.name)) continue display_status_current_actor(actor, designation=designation) current_logger.info( "Executing actor {actor} {designation}".format( designation=designation, actor=actor.name)) messaging = InProcessMessaging( config_model=config_model, answer_store=self._answer_store) messaging.load(actor.consumes) instance = actor(logger=current_logger, messaging=messaging, config_model=config_model, skip_dialogs=skip_dialogs) try: instance.run() except BaseException: self._unhandled_exception = True raise self._stop_after_phase_requested = messaging.stop_after_phase or self._stop_after_phase_requested # Collect dialogs self._dialogs.extend(messaging.dialogs()) # Collect errors if messaging.errors(): self._errors.extend(messaging.errors()) if phase[ 0].policies.error is Policies.Errors.FailImmediately: self.log.info( 'Workflow interrupted due to FailImmediately error policy' ) early_finish = True break for command in messaging.commands: if command[ 'command'] == SkipPhasesUntilCommand.COMMAND: skip_phases_until = command['arguments'][ 'until_phase'] self.log.info( 'SkipPhasesUntilCommand received. Skipping phases until {}' .format(skip_phases_until)) checkpoint(actor=actor.name, phase=phase[0].name, context=context, hostname=os.environ['LEAPP_HOSTNAME']) if needle_actor in actor_names(actor): self.log.info( 'Workflow finished due to the until-actor flag') early_finish = True break if not stage.actors: checkpoint(actor='', phase=phase[0].name + '.' + stage.stage, context=context, hostname=os.environ['LEAPP_HOSTNAME']) if needle_phase in phase_names( phase) and needle_stage == stage.stage.lower(): self.log.info( 'Workflow finished due to the until-phase flag') early_finish = True break checkpoint(actor='', phase=phase[0].name, context=context, hostname=os.environ['LEAPP_HOSTNAME']) if self._errors and phase[ 0].policies.error is Policies.Errors.FailPhase: self.log.info( 'Workflow interrupted due to the FailPhase error policy') early_finish = True elif needle_phase in phase_names(phase): self.log.info('Workflow finished due to the until-phase flag') early_finish = True elif self._stop_after_phase_requested: self.log.info('Workflow received request to stop after phase.') early_finish = True elif phase[0].flags.request_restart_after_phase or phase[ 0].flags.restart_after_phase: reboot = True if phase[ 0].flags.request_restart_after_phase and not self._auto_reboot: reboot = False messaging.request_answers( RawMessageDialog( message= 'A reboot is required to continue. Please reboot your system.' )) if reboot: self.log.info( 'Initiating system reboot due to the restart_after_reboot flag' ) reboot_system() early_finish = True elif phase[0].flags.is_checkpoint: self.log.info( 'Stopping the workflow execution due to the is_checkpoint flag' ) early_finish = True if early_finish: return
class Workflow(with_metaclass(WorkflowMeta)): """ Workflow is the base class for all :ref:`workflow <terminology:workflow>` definitions. """ name = None """Name of the workflow""" short_name = None """ Short name of the workflow """ tag = None """ Workflow Tag """ description = '' """ Documentation for the workflow """ @property def errors(self): """ :return: All reported errors """ return self._errors def __init__(self, logger=None, auto_reboot=False): """ :param logger: Optional logger to be used instead of leapp.workflow :type logger: Instance of :py:class:`logging.Logger` """ self.log = (logger or logging.getLogger('leapp')).getChild('workflow') self._errors = [] self._all_consumed = set() self._all_produced = set() self._initial = set() self._phase_actors = [] self._experimental_whitelist = set() self._auto_reboot = auto_reboot for phase in self.phases: phase.filter.tags += (self.tag,) self._phase_actors.append(( phase, self._apply_phase(phase.filter.get_before(), 'Before'), self._apply_phase(phase.filter.get(), 'Main'), self._apply_phase(phase.filter.get_after(), 'After'))) def _apply_phase(self, actors, stage): phase_actors = PhaseActors(actors, stage) self._initial.update(set(phase_actors.initial) - self._all_produced) self._all_consumed.update(phase_actors.consumes) self._all_produced.update(phase_actors.produces) return phase_actors @property def experimental_whitelist(self): """ Whitelist of actors that may be executed even that they are marked experimental """ return self._experimental_whitelist def whitelist_experimental_actor(self, actor): """ Adds an actor to the experimental whitelist and allows them to be executed. :param actor: Actor to be whitelisted :type actor: class derived from py:class:`leapp.actors.Actor` :return: None """ if actor: self._experimental_whitelist.add(actor) @property def phase_actors(self): """ Return all actors for the phase """ return self._phase_actors @property def initial(self): """ Initial messages required """ return self._initial @property def consumes(self): """ All consumed messages """ return self._all_consumed @property def produces(self): """ All produced messages """ return self._all_produced @classmethod def serialize(cls): """ :return: Serialized form of the workflow """ return { 'name': cls.name, 'short_name': cls.short_name, 'tag': cls.tag.__name__, 'description': cls.description, 'phases': [phase.serialize() for phase in cls.phases], } def run(self, context=None, until_phase=None, until_actor=None, skip_phases_until=None): """ Executes the workflow :param context: Custom execution ID to be used instead of a randomly generated UUIDv4 :type context: str :param until_phase: Specify until including which phase the execution should run - phase.stage can be used to control it even more granularly. `phase` is any phase name where `stage` refers to `main`, `before` or `after`. If no stage is defined, `after` is assumed to be the default value. The execution ends when this phase (and stage, if specified) has been executed. :type until_phase: str :param until_actor: The execution finishes when this actor has been executed. :type until_actor: str :param skip_phases_until: Skips all phases until including the phase specified, and then continues the execution. :type skip_phases_until: str or None """ context = context or str(uuid.uuid4()) os.environ['LEAPP_EXECUTION_ID'] = context if not os.environ.get('LEAPP_HOSTNAME', None): os.environ['LEAPP_HOSTNAME'] = socket.getfqdn() self.log.info('Starting workflow execution: {name} - ID: {id}'.format( name=self.name, id=os.environ['LEAPP_EXECUTION_ID'])) skip_phases_until = (skip_phases_until or '').lower() needle_phase = until_phase or '' needle_stage = None if '.' in needle_phase: needle_phase, needle_stage = needle_phase.split('.', 1) needle_phase = needle_phase.lower() needle_stage = (needle_stage or '').lower() needle_actor = (until_actor or '').lower() self._errors = get_errors(context) for phase in self._phase_actors: os.environ['LEAPP_CURRENT_PHASE'] = phase[0].name if skip_phases_until: if skip_phases_until in (phase[0].__name__.lower(), phase[0].name.lower()): skip_phases_until = '' self.log.info('Skipping phase {name}'.format(name=phase[0].name)) continue self.log.info('Starting phase {name}'.format(name=phase[0].name)) current_logger = self.log.getChild(phase[0].name) for stage in phase[1:]: current_logger.info("Starting stage {stage} of phase {phase}".format( phase=phase[0].name, stage=stage.stage)) for actor in stage.actors: designation = '' if ExperimentalTag in actor.tags: designation = '[EXPERIMENTAL]' if actor not in self.experimental_whitelist: current_logger.info("Skipping experimental actor {actor}".format(actor=actor.name)) continue current_logger.info("Executing actor {actor} {designation}".format(designation=designation, actor=actor.name)) messaging = InProcessMessaging() messaging.load(actor.consumes) actor(logger=current_logger, messaging=messaging).run() # Collect errors if messaging.errors(): self._errors.extend(messaging.errors()) if phase[0].policies.error is Policies.Errors.FailImmediately: self.log.info('Workflow interrupted due to FailImmediately error policy') return checkpoint(actor=actor.name, phase=phase[0].name, context=context, hostname=os.environ['LEAPP_HOSTNAME']) if needle_actor in (actor.name.lower(), actor.class_name.lower()): self.log.info('Workflow finished due to the until-actor flag') return if not stage.actors: checkpoint(actor='', phase=phase[0].name + '.' + stage.stage, context=context, hostname=os.environ['LEAPP_HOSTNAME']) if needle_phase in (phase[0].__name__.lower(), phase[0].name.lower()) and \ needle_stage == stage.stage.lower(): self.log.info('Workflow finished due to the until-phase flag') return checkpoint(actor='', phase=phase[0].name, context=context, hostname=os.environ['LEAPP_HOSTNAME']) if self._errors and phase[0].policies.error is Policies.Errors.FailPhase: self.log.info('Workflow interrupted due to the FailPhase error policy') return if needle_phase in (phase[0].__name__.lower(), phase[0].name.lower()): self.log.info('Workflow finished due to the until-phase flag') return if phase[0].flags.request_restart_after_phase or phase[0].flags.restart_after_phase: reboot = True if phase[0].flags.request_restart_after_phase and not self._auto_reboot: reboot = False messaging.request_answers( RawMessageDialog(message='A reboot is required to continue. Please reboot your system.') ) if reboot: self.log.info('Initiating system reboot due to the restart_after_reboot flag') reboot_system() return
class Workflow(with_metaclass(WorkflowMeta)): def __init__(self, logger=None): self.log = (logger or logging.getLogger('leapp')).getChild('workflow') self._all_consumed = set() self._all_produced = set() self._initial = set() self._phase_actors = [] for phase in self.phases: self._phase_actors.append( (phase, self._apply_phase(phase.filter.get_before(), 'Before'), self._apply_phase(phase.filter.get(), 'Main'), self._apply_phase(phase.filter.get_after(), 'After'))) def _apply_phase(self, actors, stage): phase_actors = PhaseActors(actors, stage) self._initial.update(set(phase_actors.initial) - self._all_produced) self._all_consumed.update(phase_actors.consumes) self._all_produced.update(phase_actors.produces) return phase_actors @property def phase_actors(self): return self._phase_actors @property def initial(self): return self._initial @property def consumes(self): return self._all_consumed @property def produces(self): return self._all_produced def run(self, *args, **kwargs): os.environ['LEAPP_EXECUTION_ID'] = kwargs.get('execution_id', str(uuid.uuid4())) self.log.info('Starting workflow execution: {name} - ID: {id}'.format( name=self.name, id=os.environ['LEAPP_EXECUTION_ID'])) needle_phase = kwargs.pop('until_phase', None) or '' needle_stage = None if '.' in needle_phase: needle_phase, needle_stage = needle_phase.split('.', 1) needle_phase = needle_phase.lower() needle_stage = (needle_stage or '').lower() needle_actor = (kwargs.pop('until_actor', None) or '').lower() for phase in self._phase_actors: os.environ['LEAPP_CURRENT_PHASE'] = phase[0].name self.log.info('Starting phase {name}'.format(name=phase[0].name)) current_logger = self.log.getChild(phase[0].name) for stage in phase[1:]: current_logger.info( "Starting stage {stage} of phase {phase}".format( phase=phase[0].name, stage=stage.stage)) for actor in stage.actors: current_logger.info( "Executing actor {actor}".format(actor=actor.name)) messaging = RemoteMessaging() messaging.load(actor.consumes) actor(logger=current_logger, messaging=messaging).run(*args, **kwargs) if needle_actor in (actor.name.lower(), actor.class_name.lower()): self.log.info( 'Workflow finished due to until-actor flag') return if phase[0].name.lower( ) == needle_phase and needle_stage == stage.stage.lower(): self.log.info('Workflow finished due to until-phase flag') return if phase[0].name == needle_phase: self.log.info('Workflow finished due to until-phase flag') return
class Tag(with_metaclass(TagMeta)): pass
class MetaClassUser(with_metaclass(TestMetaClass)): pass
class Topic(with_metaclass(TopicMeta)): pass
class Phase(with_metaclass(PhaseMeta)): @classmethod def get_index(cls): return PhaseMeta.classes.index(cls)