Exemple #1
0
    def run(self):
        # make a temp directory to contain the repo
        local_dir_path = tempfile.mkdtemp(
            suffix="_%s_import" % self.container)
        url_bits = self.url.rsplit('@')
        err_msg = None
        try:
            # create a bare repo
            repo = Repo.clone_from(url_bits[0], local_dir_path)
        except Exception:
            err_msg = ("Error cloning remote repository: %s " % url_bits[0])
            LOG.exception(err_msg)
            return mistral_workflow_utils.Result(error=err_msg)

        # if a tag value was given, checkout that tag
        if len(url_bits) > 1:
            try:
                self._checkout_reference(repo, url_bits[-1])
            except IndexError:
                err_msg = ("Error finding %s reference "
                           "from remote repository" % url_bits[-1])
                LOG.exception(err_msg)
            except Exception:
                err_msg = ("Error checking out %s reference from remote "
                           "repository %s" % (url_bits[-1], url_bits[0]))
                LOG.exception(err_msg)

        if err_msg:
            return mistral_workflow_utils.Result(error=err_msg)

        return local_dir_path
Exemple #2
0
def _send_result_to_parent_workflow(wf_ex_id):
    with db_api.transaction():
        wf_ex = db_api.get_workflow_execution(wf_ex_id)

        wf_output = wf_ex.output

    if wf_ex.state == states.SUCCESS:
        result = wf_utils.Result(data=wf_output)
    elif wf_ex.state == states.ERROR:
        err_msg = (wf_ex.state_info
                   or 'Failed subworkflow [execution_id=%s]' % wf_ex.id)

        result = wf_utils.Result(error=err_msg)
    elif wf_ex.state == states.CANCELLED:
        err_msg = (wf_ex.state_info
                   or 'Cancelled subworkflow [execution_id=%s]' % wf_ex.id)

        result = wf_utils.Result(error=err_msg, cancel=True)
    else:
        raise RuntimeError(
            "Method _send_result_to_parent_workflow() must never be called"
            " if a workflow is not in SUCCESS, ERROR or CNCELLED state.")

    rpc.get_engine_client().on_action_complete(wf_ex.id,
                                               result,
                                               wf_action=True)
Exemple #3
0
    def run(self):
        swift = self.get_object_client()
        mistral = self.get_workflow_client()
        tmp_dir = tempfile.mkdtemp()

        try:
            self._download_templates(swift, tmp_dir)
            self._generate_plan_env_file(mistral, tmp_dir)
            self._create_and_upload_tarball(swift, tmp_dir)
        except swiftexceptions.ClientException as err:
            msg = "Error attempting an operation on container: %s" % err
            return mistral_workflow_utils.Result(error=msg)
        except mistralclient_base.APIException:
            msg = ("The Mistral environment %s could not be found." %
                   self.plan)
            return mistral_workflow_utils.Result(error=msg)
        except (OSError, IOError) as err:
            msg = "Error while writing file: %s" % err
            return mistral_workflow_utils.Result(error=msg)
        except processutils.ProcessExecutionError as err:
            msg = "Error while creating a tarball: %s" % err
            return mistral_workflow_utils.Result(error=msg)
        except Exception as err:
            msg = "Error exporting plan: %s" % err
            return mistral_workflow_utils.Result(error=msg)
        finally:
            shutil.rmtree(tmp_dir)
    def run(self):
        environments = {}
        try:
            map_file = self._get_object_client().get_object(
                self.container, 'capabilities-map.yaml')
            capabilities = yaml.safe_load(map_file[1])
        except Exception:
            err_msg = ("Error parsing capabilities-map.yaml.")
            LOG.exception(err_msg)
            return mistral_workflow_utils.Result(None, err_msg)
        # identify all environments
        for topic in capabilities['topics']:
            for eg in topic['environment_groups']:
                for env in eg['environments']:
                    environments[env['file']] = {'enabled': False}
        try:
            mistral_client = self._get_workflow_client()
            mistral_env = mistral_client.environments.get(self.container)
        except Exception as mistral_err:
            err_msg = ("Error retrieving mistral "
                       "environment. %s" % mistral_err)
            LOG.exception(err_msg)
            return mistral_workflow_utils.Result(None, err_msg)

        selected_envs = [
            item['path'] for item in mistral_env.variables['environments']
            if 'path' in item
        ]
        for item in selected_envs:
            if item in environments:
                environments[item]['enabled'] = True
            else:
                environments[item] = {'enabled': False}

        return environments
    def put(self, id, action_ex):
        """Update the specified action_execution."""
        acl.enforce('action_executions:update', context.ctx())

        LOG.info(
            "Update action_execution [id=%s, action_execution=%s]"
            % (id, action_ex)
        )

        output = action_ex.output

        if action_ex.state == states.SUCCESS:
            result = wf_utils.Result(data=output)
        elif action_ex.state == states.ERROR:
            if not output:
                output = 'Unknown error'
            result = wf_utils.Result(error=output)
        else:
            raise exc.InvalidResultException(
                "Error. Expected on of %s, actual: %s" %
                ([states.SUCCESS, states.ERROR], action_ex.state)
            )

        values = rpc.get_engine_client().on_action_complete(id, result)

        return resources.ActionExecution.from_dict(values)
Exemple #6
0
    def run(self):
        swift = self.get_object_client()
        mistral = self.get_workflow_client()

        # Get plan environment from Swift
        try:
            plan_env_dict, plan_env_missing = self.get_plan_env_dict(
                swift, self.container)
        except exception.PlanOperationError as err:
            return mistral_workflow_utils.Result(error=six.text_type(err))

        # Update mistral environment with contents from plan environment file
        variables = json.dumps(plan_env_dict, sort_keys=True)
        self.cache_delete(self.container, "tripleo.parameters.get")
        try:
            mistral.environments.update(name=self.container,
                                        variables=variables)
        except mistralclient_base.APIException:
            message = "Error updating mistral environment: %s" % self.container
            return mistral_workflow_utils.Result(error=message)

        # Delete the plan environment file from Swift, as it is no long needed.
        # (If we were to leave the environment file behind, we would have to
        # take care to keep it in sync with the actual contents of the Mistral
        # environment. To avoid that, we simply delete it.)
        # TODO(akrivoka): Once the 'Deployment plan management changes' spec
        # (https://review.openstack.org/#/c/438918/) is implemented, we will no
        # longer use Mistral environments for holding the plan data, so this
        # code can go away.
        if not plan_env_missing:
            try:
                swift.delete_object(self.container, constants.PLAN_ENVIRONMENT)
            except swiftexceptions.ClientException as err:
                message = "Error deleting file from container: %s" % err
                return mistral_workflow_utils.Result(error=message)
Exemple #7
0
    def put(self, id, action_execution):
        """Update the specified action_execution."""
        LOG.info("Update action_execution [id=%s, action_execution=%s]" %
                 (id, action_execution))

        # Client must provide a valid json. It shouldn't  necessarily be an
        # object but it should be json complaint so strings have to be escaped.
        output = None

        if action_execution.output:
            try:
                output = json.loads(action_execution.output)
            except (ValueError, TypeError) as e:
                raise exc.InvalidResultException(str(e))

        if action_execution.state == states.SUCCESS:
            result = wf_utils.Result(data=output)
        elif action_execution.state == states.ERROR:
            result = wf_utils.Result(error=output)
        else:
            raise exc.InvalidResultException(
                "Error. Expected on of %s, actual: %s" %
                ([states.SUCCESS, states.ERROR], action_execution.state))

        values = rpc.get_engine_client().on_action_complete(id, result)

        return ActionExecution.from_dict(values)
    def run(self):
        mistral_client = self._get_workflow_client()
        mistral_env = None
        try:
            mistral_env = mistral_client.environments.get(self.container)
        except Exception as mistral_err:
            err_msg = ("Error retrieving mistral "
                       "environment. %s" % mistral_err)
            LOG.exception(err_msg)
            return mistral_workflow_utils.Result(None, err_msg)

        for k, v in self.environments.items():
            found = False
            if {'path': k} in mistral_env.variables['environments']:
                found = True
            if v:
                if not found:
                    mistral_env.variables['environments'].append({'path': k})
            else:
                if found:
                    mistral_env.variables['environments'].remove({'path': k})

        env_kwargs = {
            'name': mistral_env.name,
            'variables': mistral_env.variables
        }
        try:
            mistral_client.environments.update(**env_kwargs)
        except Exception as mistral_err:
            err_msg = ("Error retrieving mistral environment. %s" %
                       mistral_err)
            LOG.exception(err_msg)
            return mistral_workflow_utils.Result(None, err_msg)
        return mistral_env.variables
    def test_with_items_action_context(self):
        wb_service.create_workbook_v2(WB_ACTION_CONTEXT)

        # Start workflow.
        wf_ex = self.engine.start_workflow('wb.wf', WF_INPUT_URLS)

        with db_api.transaction():
            wf_ex = db_api.get_workflow_execution(wf_ex.id)

            task_ex = wf_ex.task_executions[0]

            act_exs = task_ex.executions

        self.engine.on_action_complete(act_exs[0].id, wf_utils.Result("Ivan"))
        self.engine.on_action_complete(act_exs[1].id, wf_utils.Result("John"))
        self.engine.on_action_complete(act_exs[2].id,
                                       wf_utils.Result("Mistral"))

        self.await_workflow_success(wf_ex.id)

        with db_api.transaction():
            task_ex = db_api.get_task_execution(task_ex.id)

            result = data_flow.get_task_execution_result(task_ex)

        self.assertIsInstance(result, list)

        self.assertIn('John', result)
        self.assertIn('Ivan', result)
        self.assertIn('Mistral', result)

        self.assertEqual(states.SUCCESS, task_ex.state)
Exemple #10
0
    def test_with_items_action_context(self):
        wb_service.create_workbook_v2(WORKBOOK_ACTION_CONTEXT)

        # Start workflow.
        wf_ex = self.engine.start_workflow('wb1.wf1_with_items', WF_INPUT_URLS)

        wf_ex = db_api.get_workflow_execution(wf_ex.id)
        task_ex = wf_ex.task_executions[0]

        act_exs = task_ex.executions
        self.engine.on_action_complete(act_exs[0].id, wf_utils.Result("Ivan"))
        self.engine.on_action_complete(act_exs[1].id, wf_utils.Result("John"))
        self.engine.on_action_complete(act_exs[2].id,
                                       wf_utils.Result("Mistral"))

        self._await(lambda: self.is_execution_success(wf_ex.id), )

        # Note: We need to reread execution to access related tasks.
        wf_ex = db_api.get_workflow_execution(wf_ex.id)

        task_ex = db_api.get_task_execution(task_ex.id)
        result = data_flow.get_task_execution_result(task_ex)

        self.assertTrue(isinstance(result, list))

        self.assertIn('John', result)
        self.assertIn('Ivan', result)
        self.assertIn('Mistral', result)

        self.assertEqual(states.SUCCESS, task_ex.state)
    def run(self):
        mistral_client = self._get_workflow_client()
        mistral_env = None
        try:
            mistral_env = mistral_client.environments.get(self.container)
        except Exception as mistral_err:
            err_msg = ("Error retrieving mistral "
                       "environment. %s" % mistral_err)
            LOG.exception(err_msg)
            return mistral_workflow_utils.Result(None, err_msg)

        for k, v in self.environments.items():
            if v.get('enabled', False):
                mistral_env.variables['environments'].append({'path': k})
            else:
                # see if it resides in mistral env and if so, remove it
                if {'path': k} in mistral_env.variables['environments']:
                    mistral_env.variables['environments'].pop({'path': k})

        env_kwargs = {
            'name': mistral_env.name,
            'variables': mistral_env.variables
        }
        try:
            mistral_client.environments.update(**env_kwargs)
        except Exception as mistral_err:
            err_msg = ("Error retrieving mistral "
                       "environment. %s" % mistral_err)
            LOG.exception(err_msg)
            return mistral_workflow_utils.Result(None, err_msg)
        return mistral_env
Exemple #12
0
 def run(self):
     try:
         nodes.validate_nodes(self.nodes_json)
     except exception.InvalidNode as err:
         LOG.error("Validation of nodes failed: %s", err)
         return mistral_workflow_utils.Result(error=str(err))
     except Exception as err:
         LOG.exception("Unexpected exception during node validation")
         return mistral_workflow_utils.Result(error=str(err))
Exemple #13
0
def send_result_to_parent_workflow(wf_ex_id):
    wf_ex = db_api.get_workflow_execution(wf_ex_id)

    if wf_ex.state == states.SUCCESS:
        rpc.get_engine_client().on_action_complete(
            wf_ex.id, wf_utils.Result(data=wf_ex.output))
    elif wf_ex.state == states.ERROR:
        err_msg = 'Failed subworkflow [execution_id=%s]' % wf_ex.id

        rpc.get_engine_client().on_action_complete(
            wf_ex.id, wf_utils.Result(error=err_msg))
Exemple #14
0
 def run(self):
     try:
         temp_dir = tempfile.gettempdir()
         target_path = '%s/*_%s_import' % (temp_dir, self.container)
         path = glob.glob(target_path)[0]
         shutil.rmtree(path)
     except IndexError as idx_err:
         LOG.exception("Directory not found: %s" % target_path)
         return mistral_workflow_utils.Result(error=six.text_type(idx_err))
     except OSError as os_err:
         LOG.exception("Error removing directory: %s" % target_path)
         return mistral_workflow_utils.Result(error=six.text_type(os_err))
Exemple #15
0
    def run(self):
        swift = self.get_object_client()
        mistral = self.get_workflow_client()
        env_data = {
            'name': self.container,
        }

        if not pattern_validator(constants.PLAN_NAME_PATTERN, self.container):
            message = ("Unable to create plan. The plan name must "
                       "only contain letters, numbers or dashes")
            return mistral_workflow_utils.Result(error=message)

        # Check to see if an environment with that name already exists
        try:
            mistral.environments.get(self.container)
        except mistralclient_base.APIException:
            # The environment doesn't exist, as expected. Proceed.
            pass
        else:
            message = ("Unable to create plan. The Mistral environment "
                       "already exists")
            return mistral_workflow_utils.Result(error=message)

        # Get plan environment from Swift
        try:
            plan_env_dict, plan_env_missing = self.get_plan_env_dict(
                swift, self.container)
        except exception.PlanOperationError as err:
            return mistral_workflow_utils.Result(error=six.text_type(err))

        # Create mistral environment
        env_data['variables'] = json.dumps(plan_env_dict, sort_keys=True)
        try:
            mistral.environments.create(**env_data)
        except Exception as err:
            message = "Error occurred creating plan: %s" % err
            return mistral_workflow_utils.Result(error=message)

        # Delete the plan environment file from Swift, as it is no long needed.
        # (If we were to leave the environment file behind, we would have to
        # take care to keep it in sync with the actual contents of the Mistral
        # environment. To avoid that, we simply delete it.)
        # TODO(akrivoka): Once the 'Deployment plan management changes' spec
        # (https://review.openstack.org/#/c/438918/) is implemented, we will no
        # longer use Mistral environments for holding the plan data, so this
        # code can go away.
        if not plan_env_missing:
            try:
                swift.delete_object(self.container, constants.PLAN_ENVIRONMENT)
            except swiftexceptions.ClientException as err:
                message = "Error deleting file from container: %s" % err
                return mistral_workflow_utils.Result(error=message)
    def test_no_nodes(self):
        # One error per each flavor
        expected = mistral_workflow_utils.Result(
            error={
                'errors': [
                    'Error: only 0 of 1 requested ironic nodes are '
                    'tagged to profile compute (for flavor '
                    'compute)\n'
                    'Recommendation: tag more nodes using openstack '
                    'baremetal node set --property  '
                    '"capabilities=profile:compute,'
                    'boot_option:local" <NODE ID>',
                    'Error: only 0 of 1 requested ironic nodes are '
                    'tagged to profile control (for flavor '
                    'control).\n'
                    'Recommendation: tag more nodes using openstack '
                    'baremetal node set --property '
                    '"capabilities=profile:control,'
                    'boot_option:local" <NODE ID>'
                ],
                'warnings': []
            })

        action = validations.VerifyProfilesAction(self.nodes, self.flavors)
        result = action.run()
        self.assertEqual(expected.error['errors'].sort(),
                         result.error['errors'].sort())
        self.assertEqual(expected.error['warnings'], result.error['warnings'])
        self.assertEqual(None, result.data)
    def test_run_check_hypervisor_stats_not_met(self):
        statistics = {'count': 0, 'memory_mb': 0, 'vcpus': 0}

        action_args = self.action_args.copy()
        action_args.update({'statistics': statistics})

        action = validations.CheckNodesCountAction(**action_args)
        result = action.run()

        expected = mistral_workflow_utils.Result(
            error={
                'errors': [
                    'Only 0 nodes are exposed to Nova of 3 requests. Check '
                    'that enough nodes are in "available" state with '
                    'maintenance mode off.'
                ],
                'warnings': [],
                'result': {
                    'statistics': statistics,
                    'enough_nodes': False,
                    'requested_count': 2,
                    'available_count': 3,
                }
            })
        self.assertEqual(expected, result)
    def test_check_default_param_not_in_stack(self):
        missing_param = 'CephStorageCount'
        action_args = self.action_args.copy()
        action_args['parameters'] = {'ControllerCount': 3}
        action_args['stack'] = {'parameters': self.defaults.copy()}
        del action_args['stack']['parameters'][missing_param]

        action = validations.CheckNodesCountAction(**action_args)
        result = action.run()

        expected = mistral_workflow_utils.Result(
            error={
                'errors':
                ['Not enough baremetal nodes - available: 3, requested: 4'],
                'warnings': [],
                'result': {
                    'enough_nodes': False,
                    'requested_count': 4,
                    'available_count': 3,
                    'statistics': {
                        'count': 3,
                        'memory_mb': 1,
                        'vcpus': 1
                    }
                }
            })
        self.assertEqual(expected, result)
    def test_run(self, wait_for_data_mock, build_sc_params_mock,
                 extract_from_swift_url_mock, create_temp_url_mock,
                 get_heat_mock, get_obj_client_mock):
        extract_from_swift_url_mock.return_value = ('container', 'object')
        build_sc_params_mock.return_value = {'foo': 'bar'}
        config = mock.MagicMock()
        sd = mock.MagicMock()
        get_heat_mock().software_configs.create.return_value = config
        get_heat_mock().software_deployments.create.return_value = sd
        wait_for_data_mock.return_value = '{"deploy_status_code": 0}'

        action = deployment.OrchestrationDeployAction(self.server_id,
                                                      self.config, self.name)
        expected = mistral_workflow_utils.Result(
            data={"deploy_status_code": 0}, error=None)
        self.assertEqual(expected, action.run())
        create_temp_url_mock.assert_called_once()
        extract_from_swift_url_mock.assert_called_once()
        build_sc_params_mock.assert_called_once()
        get_obj_client_mock.assert_called_once()
        wait_for_data_mock.assert_called_once()

        sd.delete.assert_called_once()
        config.delete.assert_called_once()
        get_obj_client_mock.delete_object.called_once_with(
            'container', 'object')
        get_obj_client_mock.delete_container.called_once_with('container')
    def test_run_boot_option_is_netboot(self):
        roles_info = {
            'role2': ('flavor2', 1),
            'role3': ('flavor3', 1),
        }

        expected = mistral_workflow_utils.Result(
            data={
                'flavors': {
                    'flavor2': ({
                        'name': 'flavor2',
                        'keys': {
                            'capabilities:boot_option': 'netboot'
                        }
                    }, 1),
                    'flavor3': ({
                        'name': 'flavor3',
                        'keys': None
                    }, 1),
                },
                'warnings': [(
                    'Flavor %s "capabilities:boot_option" is set to '
                    '"netboot". Nodes will PXE boot from the ironic '
                    'conductor instead of using a local bootloader. Make '
                    'sure that enough nodes are marked with the '
                    '"boot_option" capability set to "netboot".' % 'flavor2')],
                'errors': []
            })

        action_args = {'roles_info': roles_info}
        action = validations.CheckFlavorsAction(**action_args)
        result = action.run()
        self.assertEqual(expected, result)
    def test_assign_profiles_wrong_state(self):
        # active nodes are not considered for assigning profiles
        self.nodes[:] = [
            self._get_fake_node(possible_profiles=['compute'],
                                provision_state='active'),
            self._get_fake_node(possible_profiles=['control'],
                                provision_state='cleaning'),
            self._get_fake_node(profile='compute', provision_state='error')
        ]
        expected = mistral_workflow_utils.Result(
            error={
                'warnings': [
                    'There are 1 ironic nodes with no profile that will not '
                    'be used: %s' % self.nodes[0].get('uuid')
                ],
                'errors': [
                    'Error: only 0 of 1 requested ironic nodes are tagged to '
                    'profile control (for flavor control).\n'
                    'Recommendation: tag more nodes using openstack baremetal '
                    'node set --property "capabilities=profile:control,'
                    'boot_option:local" <NODE ID>',
                    'Error: only 0 of 1 requested ironic nodes are tagged to '
                    'profile compute (for flavor compute).\n'
                    'Recommendation: tag more nodes using openstack baremetal '
                    'node set --property "capabilities=profile:compute,'
                    'boot_option:local" <NODE ID>'
                ]
            })

        action = validations.VerifyProfilesAction(self.nodes, self.flavors)
        result = action.run()
        self.assertEqual(expected.error['errors'].sort(),
                         result.error['errors'].sort())
        self.assertEqual(expected.error['warnings'], result.error['warnings'])
        self.assertEqual(None, result.data)
Exemple #22
0
    def _fail_workflow(wf_ex_id, err, action_ex_id=None):
        """Private helper to fail workflow on exceptions."""
        err_msg = str(err)

        with db_api.transaction():
            wf_ex = db_api.load_workflow_execution(wf_ex_id)

            if wf_ex is None:
                LOG.error(
                    "Cant fail workflow execution with id='%s': not found.",
                    wf_ex_id)
                return

            wf_handler.set_execution_state(wf_ex, states.ERROR, err_msg)

            if action_ex_id:
                # Note(dzimine): Don't call self.engine_client:
                # 1) to avoid computing and triggering next tasks
                # 2) to avoid a loop in case of error in transport
                action_ex = db_api.get_action_execution(action_ex_id)

                task_handler.on_action_complete(action_ex,
                                                wf_utils.Result(error=err_msg))

            return wf_ex
Exemple #23
0
    def test_run_action_with_kwargs_input(self, run_mock, class_mock,
                                          validate_mock, def_mock):
        action_def = models.ActionDefinition()
        action_def.update({
            'name': 'fake_action',
            'action_class': '',
            'attributes': {},
            'description': '',
            'input': '**kwargs',
            'is_system': True,
            'scope': 'public'
        })
        def_mock.return_value = action_def
        run_mock.return_value = wf_utils.Result(data='Hello')

        class_ret = mock.MagicMock()
        class_mock.return_value = class_ret

        self.engine.start_action('fake_action', {'input': 'Hello'})

        self.assertEqual(1, def_mock.call_count)
        def_mock.assert_called_with('fake_action')

        self.assertEqual(0, validate_mock.call_count)

        class_ret.assert_called_once_with(input='Hello')

        run_mock.assert_called_once_with({'input': 'Hello'}, None, save=False)
Exemple #24
0
    def run(self):
        error_text = None
        # heat throws HTTPNotFound if the stack is not found
        try:
            stack = self.get_orchestration_client().stacks.get(self.container)
        except heatexceptions.HTTPNotFound:
            pass
        else:
            if stack is not None:
                raise exception.StackInUseError(name=self.container)

        try:
            swift = self.get_object_client()
            swiftutils.delete_container(swift, self.container)

            # if mistral environment exists, delete it too
            mistral = self.get_workflow_client()
            if self.container in [
                    env.name for env in mistral.environments.list()
            ]:
                # deletes environment
                mistral.environments.delete(self.container)
        except swiftexceptions.ClientException as ce:
            LOG.exception("Swift error deleting plan.")
            error_text = ce.msg
        except Exception as err:
            LOG.exception("Error deleting plan.")
            error_text = six.text_type(err)

        if error_text:
            return mistral_workflow_utils.Result(error=error_text)
    def test_async_task_on_clause_has_yaql_error(self):
        wf_text = """
        version: '2.0'

        wf:
          type: direct

          tasks:
            task1:
              action: std.async_noop
              on-complete:
                - task2: <% wrong(yaql) %>

            task2:
              action: std.noop
        """

        # Invoke workflow and assert workflow, task,
        # and async action execution are RUNNING.
        wf_ex = self._run_workflow(wf_text, states.RUNNING)

        self.assertEqual(states.RUNNING, wf_ex.state)
        self.assertEqual(1, len(wf_ex.task_executions))

        task_1_ex = self._assert_single_item(wf_ex.task_executions,
                                             name='task1')

        self.assertEqual(states.RUNNING, task_1_ex.state)

        task_1_action_exs = db_api.get_action_executions(
            task_execution_id=task_1_ex.id)

        self.assertEqual(1, len(task_1_action_exs))
        self.assertEqual(states.RUNNING, task_1_action_exs[0].state)

        # Update async action execution result.
        result = wf_utils.Result(data='foobar')

        self.assertRaises(exc.YaqlEvaluationException,
                          self.engine.on_action_complete,
                          task_1_action_exs[0].id, result)

        # Assert that task1 is SUCCESS and workflow is ERROR.
        wf_ex = db_api.get_workflow_execution(wf_ex.id)

        self.assertEqual(states.ERROR, wf_ex.state)
        self.assertIn('Can not evaluate YAQL expression', wf_ex.state_info)
        self.assertEqual(1, len(wf_ex.task_executions))

        task_1_ex = self._assert_single_item(wf_ex.task_executions,
                                             name='task1')

        self.assertEqual(states.SUCCESS, task_1_ex.state)

        task_1_action_exs = db_api.get_action_executions(
            task_execution_id=task_1_ex.id)

        self.assertEqual(1, len(task_1_action_exs))
        self.assertEqual(states.SUCCESS, task_1_action_exs[0].state)
Exemple #26
0
        def send_error_back(error_msg):
            error_result = wf_utils.Result(error=error_msg)

            if action_ex_id:
                self._engine_client.on_action_complete(action_ex_id,
                                                       error_result)
            else:
                return error_result
Exemple #27
0
    def run(self):
        oc = self.get_object_client()

        # checks to see if a container has a valid name
        if not pattern_validator(constants.PLAN_NAME_PATTERN, self.container):
            message = ("Unable to create plan. The plan name must "
                       "only contain letters, numbers or dashes")
            return mistral_workflow_utils.Result(error=message)

        # checks to see if a container with that name exists
        if self.container in [
                container["name"] for container in oc.get_account()[1]
        ]:
            result_string = ("A container with the name %s already"
                             " exists.") % self.container
            return mistral_workflow_utils.Result(error=result_string)
        oc.put_container(self.container, headers=default_container_headers)
Exemple #28
0
class ExecutorClient(base.Executor):
    """RPC Executor client."""

    def __init__(self, transport):
        """Constructs an RPC client for the Executor.

        :param transport: Messaging transport.
        :type transport: Transport.
        """
        self.topic = cfg.CONF.executor.topic

        serializer = auth_ctx.RpcContextSerializer(
            auth_ctx.JsonPayloadSerializer()
        )

        self._client = messaging.RPCClient(
            transport,
            messaging.Target(),
            serializer=serializer
        )

    def run_action(self, action_ex_id, action_class_str, attributes,
                   action_params, target=None, async=True):
        """Sends a request to run action to executor.

        :param action_ex_id: Action execution id.
        :param action_class_str: Action class name.
        :param attributes: Action class attributes.
        :param action_params: Action input parameters.
        :param target: Target (group of action executors).
        :param async: If True, run action in asynchronous mode (w/o waiting
            for completion).
        :return: Action result.
        """

        kwargs = {
            'action_ex_id': action_ex_id,
            'action_class_str': action_class_str,
            'attributes': attributes,
            'params': action_params
        }

        call_ctx = self._client.prepare(topic=self.topic, server=target)

        rpc_client_method = call_ctx.cast if async else call_ctx.call

        res = rpc_client_method(auth_ctx.ctx(), 'run_action', **kwargs)

        # TODO(rakhmerov): It doesn't seem a good approach since we have
        # a serializer for Result class. A better solution would be to
        # use a composite serializer that dispatches serialization and
        # deserialization to concrete serializers depending on object
        # type.

        return (
            wf_utils.Result(data=res['data'], error=res['error'])
            if res else None
        )
Exemple #29
0
def on_action_complete(action_ex, wf_spec, result):
    """Handles event of action result arrival.

    Given action result this method changes corresponding task execution
    object. This method must never be called for the case of individual
    action which is not associated with any tasks.

    :param action_ex: Action execution objects the result belongs to.
    :param wf_spec: Workflow specification.
    :param result: Task action/workflow output wrapped into
        mistral.workflow.utils.Result instance.
    :return Task execution object.
    """

    task_ex = action_ex.task_execution

    # Ignore if action already completed.
    if (states.is_completed(action_ex.state)
            and not isinstance(action_ex, models.WorkflowExecution)):
        return task_ex

    task_spec = wf_spec.get_tasks()[task_ex.name]

    try:
        result = action_handler.transform_result(result, task_ex, task_spec)
    except exc.YaqlEvaluationException as e:
        err_msg = str(e)

        LOG.error(
            'YAQL error while transforming action result'
            ' [action_execution_id=%s, result=%s]: %s', action_ex.id, result,
            err_msg)

        result = wf_utils.Result(error=err_msg)

    # Ignore workflow executions because they're handled during
    # workflow completion.
    if not isinstance(action_ex, models.WorkflowExecution):
        action_handler.store_action_result(action_ex, result)

    if result.is_success():
        task_state = states.SUCCESS
        task_state_info = None
    else:
        task_state = states.ERROR
        task_state_info = result.error

    if not task_spec.get_with_items():
        _complete_task(task_ex, task_spec, task_state, task_state_info)
    else:
        with_items.increase_capacity(task_ex)

        if with_items.is_completed(task_ex):
            _complete_task(task_ex, task_spec,
                           with_items.get_final_state(task_ex),
                           task_state_info)

    return task_ex
Exemple #30
0
 def run(self):
     return_value = {'stderr': ''}
     if self._validations_enabled():
         return_value['stdout'] = 'Validations are enabled'
         mistral_result = {"data": return_value}
     else:
         return_value['stdout'] = 'Validations are disabled'
         mistral_result = {"error": return_value}
     return mistral_workflow_utils.Result(**mistral_result)