def test_checkpoints(): checkpoint(actor=_ACTOR_NAME, phase=_PHASE_NAME, context=_CONTEXT_NAME, hostname=_HOSTNAME) result = get_checkpoints(_CONTEXT_NAME) assert result and len(result) == 1 assert result[0]['id'] assert result[0]['actor'] == _ACTOR_NAME assert result[0]['phase'] == _PHASE_NAME assert result[0]['stamp'].endswith('Z')
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
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