---
version: '2.0'
name: tripleo.derive_params.v1
description: TripleO Workflows to derive deployment parameters from the introspected data

workflows:

  derive_parameters:
    description: The main workflow for deriving parameters from the introspected data

    input:
      - plan: overcloud
      - queue_name: tripleo
      - persist_params_in_plan: true
      - user_inputs: {}

    tags:
      - tripleo-common-managed

    tasks:
      get_flattened_parameters:
        action: tripleo.parameters.get_flatten container=<% $.plan %>
        publish:
          environment_parameters: <% task().result.environment_parameters %>
          heat_resource_tree: <% task().result.heat_resource_tree %>
        on-success:
          - get_roles: <% $.environment_parameters and $.heat_resource_tree %>
          - set_status_failed_get_flattened_parameters: <% (not $.environment_parameters) or (not $.heat_resource_tree) %>
        on-error: set_status_failed_get_flattened_parameters

      get_roles:
        action: tripleo.role.list container=<% $.plan %>
        publish:
          role_name_list: <% task().result %>
        on-success:
          - get_valid_roles: <% $.role_name_list %>
          - set_status_failed_get_roles: <% not $.role_name_list %>
        on-error: set_status_failed_on_error_get_roles

      # Obtain only the roles which has count > 0, by checking <RoleName>Count parameter, like ComputeCount
      get_valid_roles:
        publish:
          valid_role_name_list: <% let(hr => $.heat_resource_tree.parameters) -> $.role_name_list.where(int($hr.get(concat($, 'Count'), {}).get('default', 0)) > 0) %>
        on-success:
          - for_each_role: <% $.valid_role_name_list %>
          - set_status_failed_get_valid_roles: <% not $.valid_role_name_list %>

      # Execute the basic preparation workflow for each role to get introspection data
      for_each_role:
        with-items: role_name in <% $.valid_role_name_list %>
        concurrency: 1
        workflow: _derive_parameters_per_role
        input:
          plan: <% $.plan %>
          role_name: <% $.role_name %>
          environment_parameters: <% $.environment_parameters %>
          heat_resource_tree: <% $.heat_resource_tree %>
          user_inputs: <% $.user_inputs %>
        publish:
          # Gets all the roles derived parameters as dictionary
          result: <% task().result.select($.get('derived_parameters', {})).sum() %>
        on-success:
          - send_message: <% not $.persist_params_in_plan %>
          - reset_derive_parameters_in_plan: <% $.persist_params_in_plan %>
        on-error: set_status_failed_for_each_role

      reset_derive_parameters_in_plan:
        action: tripleo.parameters.reset
        input:
          container: <% $.plan %>
          key: 'derived_parameters'
        on-success:
          # Add the derived parameters to the deployment plan only when $.result
          # (the derived parameters) is non-empty. Otherwise, we're done.
          - update_derive_parameters_in_plan: <% $.result %>
          - send_message: <% not $.result %>
        on-error: set_status_failed_reset_derive_parameters_in_plan

      update_derive_parameters_in_plan:
        action: tripleo.parameters.update
        input:
          container: <% $.plan %>
          key: 'derived_parameters'
          parameters: <% $.get('result', {}) %>
        on-success: send_message
        on-error: set_status_failed_update_derive_parameters_in_plan

      set_status_failed_get_flattened_parameters:
        on-success: send_message
        publish:
          status: FAILED
          message: <% task(get_flattened_parameters).result %>

      set_status_failed_get_roles:
        on-success: send_message
        publish:
          status: FAILED
          message: "Unable to determine the list of roles in the deployment plan"

      set_status_failed_on_error_get_roles:
        on-success: send_message
        publish:
          status: FAILED
          message: <% task(get_roles).result %>

      set_status_failed_get_valid_roles:
        on-success: send_message
        publish:
          status: FAILED
          message: 'Unable to determine the list of valid roles in the deployment plan.'

      set_status_failed_for_each_role:
        on-success: update_message_format
        publish:
          status: FAILED
          # gets the status and message for all roles from task result.
          message: <% task(for_each_role).result.select(dict('role_name' => $.role_name, 'status' => $.get('status', 'SUCCESS'), 'message' => $.get('message', ''))) %>

      update_message_format:
        on-success: send_message
        publish:
          # updates the message format(Role 'role name': message) for each roles which are failed and joins the message list as string with ', ' separator.
          message: <% $.message.where($.get('status', 'SUCCESS') != 'SUCCESS').select(concat("Role '{}':".format($.role_name), " ", $.get('message', '(error unknown)'))).join(', ') %>

      set_status_failed_reset_derive_parameters_in_plan:
        on-success: send_message
        publish:
          status: FAILED
          message: <% task(reset_derive_parameters_in_plan).result %>

      set_status_failed_update_derive_parameters_in_plan:
        on-success: send_message
        publish:
          status: FAILED
          message: <% task(update_derive_parameters_in_plan).result %>

      send_message:
        workflow: tripleo.messaging.v1.send
        input:
          queue_name: <% $.queue_name %>
          type: <% execution().name %>
          status: <% $.get('status', 'SUCCESS') %>
          execution: <% execution() %>
          message: <% $.get('message', '') %>
          plan_name: <% $.plan %>
          payload:
            result: <% $.get('result', '') %>

  get_flattened_parameters:
    description: Workflow to retreive a list of flattened parameters

    input:
      - plan: overcloud
      - queue_name: tripleo

    output:
      flattened_parameters: <% $.flattened_parameters %>

    tags:
      - tripleo-common-managed

    tasks:
      get_flattened_parameters:
        action: tripleo.parameters.get_flatten container=<% $.plan %>
        on-success: send_message
        publish:
          status: SUCCESS
          message: <% task().result %>
          flattened_parameters: <% task().result %>
        publish-on-error:
          status: FAILED
          message: <% task().result %>

      send_message:
        workflow: tripleo.messaging.v1.send
        input:
          queue_name: <% $.queue_name %>
          type: <% execution().name %>
          status: <% $.get('status', 'SUCCESS')
          execution: <% execution() %>
          messages: <% $.get('message', '') %>
          payload:
            flattened_parameters: <% $.get('flattened_parameters', []) %>

  _derive_parameters_per_role:
    description: >
      Workflow which runs per role to get the introspection data on the first matching node assigned to role.
      Once introspection data is fetched, this worklow will trigger the actual derive parameters workflow
    input:
      - plan
      - role_name
      - environment_parameters
      - heat_resource_tree
      - user_inputs

    output:
      derived_parameters: <% $.get('derived_parameters', {}) %>
      # Need role_name in output parameter to display the status for all roles in main workflow when any role fails here.
      role_name: <% $.role_name %>

    tags:
      - tripleo-common-managed

    tasks:
      get_role_info:
        workflow: _get_role_info
        input:
          role_name: <% $.role_name %>
          heat_resource_tree: <% $.heat_resource_tree %>
        publish:
          role_features: <% task().result.get('role_features', []) %>
          role_services: <% task().result.get('role_services', []) %>
        on-success:
          # Continue only if there are features associated with this role. Otherwise, we're done.
          - get_scheduler_hints: <% $.role_features %>
        on-error: set_status_failed_get_role_info

      # Find a node associated with this role. Look for nodes matching any scheduler hints
      # associated with the role, and if there are no scheduler hints then locate nodes
      # with a profile matching the role's flavor.
      get_scheduler_hints:
        publish:
          scheduler_hints: <% let(param_name => concat($.role_name, 'SchedulerHints')) -> $.heat_resource_tree.parameters.get($param_name, {}).get('default', {}) %>
        on-success:
          - get_hint_regex: <% $.scheduler_hints %>
          # If there are no scheduler hints then move on to use the flavor
          - get_flavor_name: <% not $.scheduler_hints %>

      get_hint_regex:
        publish:
          hint_regex: <% $.scheduler_hints.get('capabilities:node', '').replace('%index%', '(\d+)') %>
        on-success:
          - get_node_with_hint: <% $.hint_regex %>
          # If there is no 'capabilities:node' hint then move on to use the flavor
          - get_flavor_name: <% not $.hint_regex %>

      get_node_with_hint:
        workflow: tripleo.baremetal.v1.nodes_with_hint
        input:
          hint_regex: <% concat('^', $.hint_regex, '$') %>
        publish:
          role_node_uuid: <% task().result.matching_nodes.first('') %>
        on-success:
          - get_introspection_data: <% $.role_node_uuid %>
          # If no nodes match the scheduler hint then move on to use the flavor
          - get_flavor_name: <% not $.role_node_uuid %>
        on-error: set_status_failed_on_error_get_node_with_hint

      get_flavor_name:
        publish:
          flavor_name: <% let(param_name => concat('Overcloud', $.role_name, 'Flavor').replace('OvercloudControllerFlavor', 'OvercloudControlFlavor')) -> $.heat_resource_tree.parameters.get($param_name, {}).get('default', '') %>
        on-success:
          - get_profile_name: <% $.flavor_name %>
          - set_status_failed_get_flavor_name: <% not $.flavor_name %>

      get_profile_name:
        action: tripleo.parameters.get_profile_of_flavor flavor_name=<% $.flavor_name %>
        publish:
          profile_name: <% task().result %>
        on-success: get_profile_node
        on-error: set_status_failed_get_profile_name

      get_profile_node:
        workflow: tripleo.baremetal.v1.nodes_with_profile
        input:
          profile: <% $.profile_name %>
        publish:
          role_node_uuid: <% task().result.matching_nodes.first('') %>
        on-success:
          - get_introspection_data: <% $.role_node_uuid %>
          - set_status_failed_no_matching_node_get_profile_node: <% not $.role_node_uuid %>
        on-error: set_status_failed_on_error_get_profile_node

      get_introspection_data:
        action: baremetal_introspection.get_data uuid=<% $.role_node_uuid %>
        publish:
          hw_data: <% task().result %>
          # Establish an empty dictionary of derived_parameters prior to
          # invoking the individual "feature" algorithms
          derived_parameters: <% dict() %>
        on-success: handle_dpdk_feature
        on-error: set_status_failed_get_introspection_data

      handle_dpdk_feature:
        on-success:
          - get_dpdk_derive_params: <% $.role_features.contains('DPDK') %>
          - handle_sriov_feature: <% not $.role_features.contains('DPDK') %>

      get_dpdk_derive_params:
        workflow: tripleo.derive_params_formulas.v1.dpdk_derive_params
        input:
          plan: <% $.plan %>
          role_name: <% $.role_name %>
          hw_data: <% $.hw_data %>
          user_inputs: <% $.user_inputs %>
        publish:
          derived_parameters: <% task().result.get('derived_parameters', {}) %>
        on-success: handle_sriov_feature
        on-error: set_status_failed_get_dpdk_derive_params

      handle_sriov_feature:
        on-success:
          - get_sriov_derive_params: <% $.role_features.contains('SRIOV') %>
          - handle_host_feature: <% not $.role_features.contains('SRIOV') %>

      get_sriov_derive_params:
        workflow: tripleo.derive_params_formulas.v1.sriov_derive_params
        input:
          role_name: <% $.role_name %>
          hw_data: <% $.hw_data %>
          derived_parameters: <% $.derived_parameters %>
        publish:
          derived_parameters: <% task().result.get('derived_parameters', {}) %>
        on-success: handle_host_feature
        on-error: set_status_failed_get_sriov_derive_params

      handle_host_feature:
        on-success:
          - get_host_derive_params: <% $.role_features.contains('HOST') %>
          - handle_hci_feature: <% not $.role_features.contains('HOST') %>

      get_host_derive_params:
        workflow: tripleo.derive_params_formulas.v1.host_derive_params
        input:
          role_name: <% $.role_name %>
          hw_data: <% $.hw_data %>
          user_inputs: <% $.user_inputs %>
          derived_parameters: <% $.derived_parameters %>
        publish:
          derived_parameters: <% task().result.get('derived_parameters', {}) %>
        on-success: handle_hci_feature
        on-error: set_status_failed_get_host_derive_params

      handle_hci_feature:
        on-success:
          - get_hci_derive_params: <% $.role_features.contains('HCI') %>

      get_hci_derive_params:
        workflow: tripleo.derive_params_formulas.v1.hci_derive_params
        input:
          role_name: <% $.role_name %>
          environment_parameters: <% $.environment_parameters %>
          heat_resource_tree: <% $.heat_resource_tree %>
          introspection_data: <% $.hw_data %>
          user_inputs: <% $.user_inputs %>
          derived_parameters: <% $.derived_parameters %>
        publish:
          derived_parameters: <% task().result.get('derived_parameters', {}) %>
        on-error: set_status_failed_get_hci_derive_params
        # Done (no more derived parameter features)

      set_status_failed_get_role_info:
        publish:
          role_name: <% $.role_name %>
          status: FAILED
          message: <% task(get_role_info).result.get('message', '') %>
        on-success: fail

      set_status_failed_get_flavor_name:
        publish:
          role_name: <% $.role_name %>
          status: FAILED
          message: <% "Unable to determine flavor for role '{0}'".format($.role_name) %>
        on-success: fail

      set_status_failed_get_profile_name:
        publish:
          role_name: <% $.role_name %>
          status: FAILED
          message: <% task(get_profile_name).result %>
        on-success: fail

      set_status_failed_no_matching_node_get_profile_node:
        publish:
          role_name: <% $.role_name %>
          status: FAILED
          message: <% "Unable to determine matching node for profile '{0}'".format($.profile_name) %>
        on-success: fail

      set_status_failed_on_error_get_profile_node:
        publish:
          role_name: <% $.role_name %>
          status: FAILED
          message: <% task(get_profile_node).result %>
        on-success: fail

      set_status_failed_on_error_get_node_with_hint:
        publish:
          role_name: <% $.role_name %>
          status: FAILED
          message: <% task(get_node_with_hint).result %>
        on-success: fail

      set_status_failed_get_introspection_data:
        publish:
          role_name: <% $.role_name %>
          status: FAILED
          message: <% task(get_introspection_data).result %>
        on-success: fail

      set_status_failed_get_dpdk_derive_params:
        publish:
          role_name: <% $.role_name %>
          status: FAILED
          message: <% task(get_dpdk_derive_params).result.message %>
        on-success: fail

      set_status_failed_get_sriov_derive_params:
        publish:
          role_name: <% $.role_name %>
          status: FAILED
          message: <% task(get_sriov_derive_params).result.message %>
        on-success: fail

      set_status_failed_get_host_derive_params:
        publish:
          role_name: <% $.role_name %>
          status: FAILED
          message: <% task(get_host_derive_params).result.message %>
        on-success: fail

      set_status_failed_get_hci_derive_params:
        publish:
          role_name: <% $.role_name %>
          status: FAILED
          message: <% task(get_hci_derive_params).result.message %>
        on-success: fail


  _get_role_info:
    description: >
      Workflow that determines the list of derived parameter features (DPDK,
      HCI, etc.) for a role based on the services assigned to the role.

    input:
      - role_name
      - heat_resource_tree

    tags:
      - tripleo-common-managed

    tasks:
      get_resource_chains:
        publish:
          resource_chains: <% $.heat_resource_tree.resources.values().where($.get('type', '') = 'OS::Heat::ResourceChain') %>
        on-success:
          - get_role_chain: <% $.resource_chains %>
          - set_status_failed_get_resource_chains: <% not $.resource_chains %>

      get_role_chain:
        publish:
          role_chain: <% let(chain_name => concat($.role_name, 'ServiceChain'))-> $.heat_resource_tree.resources.values().where($.name = $chain_name).first({}) %>
        on-success:
          - get_service_chain: <% $.role_chain %>
          - set_status_failed_get_role_chain: <% not $.role_chain %>

      get_service_chain:
        publish:
          service_chain: <% let(resources => $.role_chain.resources)-> $.resource_chains.where($resources.contains($.id)).first('') %>
        on-success:
          - get_role_services: <% $.service_chain %>
          - set_status_failed_get_service_chain: <% not $.service_chain %>

      get_role_services:
        publish:
          role_services: <% let(resources => $.heat_resource_tree.resources)-> $.service_chain.resources.select($resources.get($)) %>
        on-success:
          - check_features: <% $.role_services %>
          - set_status_failed_get_role_services: <% not $.role_services %>

      check_features:
        on-success: build_feature_dict
        publish:
          # The role supports the DPDK feature if the NeutronDatapathType parameter is present
          dpdk: <% let(resources => $.heat_resource_tree.resources) -> $.role_services.any($.get('parameters', []).contains('NeutronDatapathType') or $.get('resources', []).select($resources.get($)).any($.get('parameters', []).contains('NeutronDatapathType'))) %>

          # The role supports the DPDK feature in ODL if the OvsEnableDpdk parameter value is true in role parameters.
          odl_dpdk: <% let(role => $.role_name) -> $.heat_resource_tree.parameters.get(concat($role, 'Parameters'), {}).get('default', {}).get('OvsEnableDpdk', false) %>

          # The role supports the SRIOV feature if it includes NeutronSriovAgent services.
          sriov: <% $.role_services.any($.get('type', '').endsWith('::NeutronSriovAgent')) %>

          # The role supports the HCI feature if it includes both NovaCompute and CephOSD services.
          hci: <% $.role_services.any($.get('type', '').endsWith('::NovaCompute')) and $.role_services.any($.get('type', '').endsWith('::CephOSD')) %>

      build_feature_dict:
        on-success: filter_features
        publish:
          feature_dict: <% dict(DPDK => ($.dpdk or $.odl_dpdk), SRIOV => $.sriov, HOST => ($.dpdk or $.odl_dpdk or $.sriov), HCI => $.hci) %>

      filter_features:
        publish:
          # The list of features that are enabled (i.e. are true in the feature_dict).
          role_features: <% let(feature_dict => $.feature_dict)-> $feature_dict.keys().where($feature_dict[$]) %>

      set_status_failed_get_resource_chains:
        publish:
          message: <% 'Unable to locate any resource chains in the heat resource tree' %>
        on-success: fail

      set_status_failed_get_role_chain:
        publish:
          message: <% "Unable to determine the service chain resource for role '{0}'".format($.role_name) %>
        on-success: fail

      set_status_failed_get_service_chain:
        publish:
          message: <% "Unable to determine the service chain for role '{0}'".format($.role_name) %>
        on-success: fail

      set_status_failed_get_role_services:
        publish:
          message: <% "Unable to determine list of services for role '{0}'".format($.role_name) %>
        on-success: fail
