{%- set primary_role_name = roles[0].name -%}
{%- for role in roles if ('primary' in role.tags and 'controller' in role.tags) -%}
  {%- if loop.first -%}
    {%- set primary_role_name = role.name -%}
  {%- endif -%}
{%- endfor -%}
# primary role is: {{primary_role_name}}
heat_template_version: wallaby

description: >
  Deploy an OpenStack environment, consisting of several node types (roles),
  Controller, Compute, BlockStorage, SwiftStorage and CephStorage. The Storage
  roles enable independent scaling of the storage components, but the minimal
  deployment is one Controller and one Compute node.


# TODO(shadower): we should probably use the parameter groups to put
# some order in here.
parameters:
  ExtraAnsibleHostVars:
    default: {}
    description: Mapping of Ansible host variable overrides.
    type: json

  # Common parameters (not specific to
{%- for network in networks if network.vip|default(false) and network.enabled|default(true) %}
  {%- if network.name == 'External' %}
  # Special case the External hostname param, which is CloudName
  CloudName:
    default: overcloud.localdomain
    description: The DNS name of this cloud. E.g. ci-overcloud.tripleo.org
    type: string
  # TODO (dsneddon) Legacy name, eventually refactor to match network name
  PublicVirtualFixedIPs:
    default: []
    description: >
        Control the IP allocation for the PublicVirtualInterface port. E.g.
        [{'ip_address':'1.2.3.4'}]
    type: json
  {%- elif network.name == 'InternalApi' %}
  # Special case the Internal API hostname param, which is CloudNameInternal
  CloudNameInternal:
    default: overcloud.{{network.name.lower()}}.localdomain
    description: >
      The DNS name of this cloud's {{network.name_lower}} endpoint. E.g.
      'ci-overcloud.{{network.name.lower()}}.tripleo.org'.
    type: string
  {%- elif network.name == 'StorageMgmt' %}
  # Special case StorageMgmt hostname param, which is CloudNameStorageManagement
  CloudNameStorageManagement:
    default: overcloud.{{network.name.lower()}}.localdomain
    description: >
      The DNS name of this cloud's {{network.name_lower}} endpoint. E.g.
      'ci-overcloud.{{network.name.lower()}}.tripleo.org'.
    type: string
  {%- else %}
  CloudName{{network.name}}:
    default: overcloud.{{network.name.lower()}}.localdomain
    description: >
      The DNS name of this cloud's {{network.name_lower}} endpoint. E.g.
      'ci-overcloud.{{network.name.lower()}}.tripleo.org'.
    type: string
  {%- endif %}
  {{network.name}}VirtualFixedIPs:
    default: []
    description: >
        Control the IP allocation for the {{network.name}}VirtualInterface port. E.g.
        [{'ip_address':'1.2.3.4'}]
    type: json
{%- endfor %}
  CloudNameCtlplane:
    default: overcloud.ctlplane.localdomain
    description: >
      The DNS name of this cloud's provisioning network endpoint. E.g.
      'ci-overcloud.ctlplane.tripleo.org'.
    type: string
  ExtraHostFileEntries:
    default: []
    description: List of extra hosts entries to be appended to /etc/hosts
    type: comma_delimited_list
  UndercloudHostsEntries:
    default: []
    description: >
      List of undercloud hosts entries to be appended to /etc/hosts. The
      value is populated with the HEAT_HOSTS entries on the undercloud by
      tripleoclient when running deploy.
    type: comma_delimited_list
  EndpointMapOverride:
    default: {}
    description: Can be used to override the calcluated EndpointMap
    type: json
  ExtraConfig:
    default: {}
    description: |
      Additional hiera configuration to inject into the cluster.
    type: json
  DeployedServerPortMap:
    default: {}
    type: json
  NeutronControlPlaneID:
    default: 'ctlplane'
    type: string
    description: Neutron ID or name for ctlplane network.
  NeutronPhysicalBridge:
    default: 'br-ex'
    description: An OVS bridge to create for accessing external networks.
    type: string
  NeutronPublicInterface:
    default: nic1
    description: Which interface to add to the NeutronPhysicalBridge.
    type: string
  ControlPlaneSubnet:
    description: The name of the undercloud Neutron control plane subnet
    default: ctlplane-subnet
    type: string
  ControlPlaneSubnetCidr:
    default: ''
    description: >
      The subnet CIDR of the control plane network. (The parameter is
      automatically resolved from the ctlplane subnet's cidr attribute.)
    type: string
  DnsSearchDomains: # Override this via parameter_defaults
    default: []
    description: A list of DNS search domains to be added (in order) to resolv.conf.
    type: comma_delimited_list
  ControlFixedIPs:
    default: []
    description: >
        Control the IP allocation for the ControlVirtualIP port. E.g.
        [{'ip_address':'1.2.3.4'}]
    type: json
  RabbitCookieSalt:
    type: string
    default: unset
    description: Salt for the rabbit cookie, change this to force the randomly generated rabbit cookie to change.
  CloudDomain:
    default: 'localdomain'
    type: string
    description: >
      The DNS domain used for the hosts. This must match the
      overcloud_domain_name configured on the undercloud.
  ServerMetadata:
    default: {}
    description: >
      Extra properties or metadata passed to Nova for the created nodes in
      the overcloud. It's accessible via the Nova metadata API.
    type: json
# Compute-specific params
# FIXME(shardy) handle these deprecated names as they don't match compute.yaml
  HypervisorNeutronPhysicalBridge:
    default: 'br-ex'
    description: >
      An OVS bridge to create on each hypervisor. This defaults to br-ex the
      same as the control plane nodes, as we have a uniform configuration of
      the openvswitch agent. Typically should not need to be changed.
    type: string
  HypervisorNeutronPublicInterface:
    default: nic1
    description: What interface to add to the HypervisorNeutronPhysicalBridge.
    type: string

  NodeCreateBatchSize:
    default: 30
    description: Maxiumum batch size for creating nodes
    type: number

  NovaAdditionalCell:
    default: false
    description: Whether this is an cell additional to the default cell.
    type: boolean

  NovaLocalMetadataPerCell:
    default: false
    description: >
      Indicates that the nova-metadata API service has been deployed
      per-cell, so that we can have better performance and data isolation in a
      multi-cell deployment. Users should consider the use of this configuration
      depending on how neutron is setup. If networks span cells, you might need
      to run nova-metadata API service globally. If your networks are segmented
      along cell boundaries, then you can run nova-metadata API service per cell.
      When running nova-metadata API service per cell, you should also configure
      each Neutron metadata-agent to point to the corresponding nova-metadata API
      service.
    type: boolean

  BondInterfaceOvsOptions:
    default: ''
    description: The ovs_options or bonding_options string for the bond
      interface. Set things like lacp=active and/or bond_mode=balance-slb
      for OVS bonds or like mode=4 for Linux bonds using this option.
    type: string
    constraints:
    - allowed_pattern: ^((?!balance.tcp).)*$
      description: The balance-tcp bond mode is known to cause packet loss and
        should not be used in BondInterfaceOvsOptions.

  NetworkConfigWithAnsible:
    description: NetworkConfig with ansible flag
    type: boolean
    default: True

  # Jinja loop for Role in role_data.yaml
{% for role in roles %}

{%- if role.name == 'ComputeOvsDpdk' %}
  NumDpdkInterfaceRxQueues:
    description: Number of Rx Queues required for DPDK bond or DPDK ports
    default: 1
    type: number
{%- endif %}

  {{role.name}}LocalMtu: # Override this via parameter_defaults
    default: 1500
    description: MTU to use for the Undercloud local_interface.
    type: number
    constraints:
      - range: { min: 1000, max: 65536 }
  {{role.name}}NetworkConfigTemplate:
    description: {{role.name}} NetworkConfig Template
    type: string
{%- if 'external_bridge' in role.tags|default([]) %}
    default: 'templates/net_config_bridge.j2'
{%- else %}
    default: ''
{%- endif %}
  {{role.name}}ExtraConfig:
    default: {}
    description: |
      Role specific additional hiera configuration to inject into the cluster.
    type: json
  {%- if role.deprecated_param_extraconfig is defined %}
  {{role.deprecated_param_extraconfig}}:
    default: {}
    description: |
      DEPRECATED use {{role.name}}ExtraConfig instead
    type: json
  {%- endif %}
  # Parameters generated for {{role.name}} Role
  {{role.name}}Services:
    description: A list of service resources (configured in the Heat
                 resource_registry) which represent nested stacks
                 for each service that should get installed on the {{role.name}} role.
    type: comma_delimited_list
  {{role.name}}NetworkConfigUpdate:
    type: boolean
    description: >
      When set to "True", existing networks will be updated on the overcloud.
      This parameter replaces the functionality previously provided by
      NetworkDeploymentActions. Defaults to "False" so that only new nodes will
      have their networks configured. This is a role based parameter.
    default: False
  {{role.name}}AnyErrorsFatal:
    default: true
    type: string
  {{role.name}}MaxFailPercentage:
    default: 0
    type: number
  {{role.name}}Count:
    description: Number of {{role.name}} nodes to deploy
    type: number
    default: {{role.CountDefault|default(0)}}

  {{role.name}}HostnameFormat:
    type: string
    description: >
      Format for {{role.name}} node hostnames
      Note %index% is translated into the index of the node, e.g 0/1/2 etc
      and %stackname% is replaced with the stack name e.g overcloud
  {% if role.HostnameFormatDefault %}
    default: "{{role.HostnameFormatDefault}}"
  {% else %}
    default: "%stackname%-{{role.name.lower()}}-%index%"
  {% endif %}
  {{role.name}}RemovalPolicies:
    default: []
    type: json
    description: >
      List of resources to be removed from {{role.name}} ResourceGroup when
      doing an update which requires removal of specific resources.
      Example format ComputeRemovalPolicies: [{'resource_list': ['0']}]

  {{role.name}}RemovalPoliciesMode:
    default: append
    type: string
    description: >
      How to handle change to RemovalPolicies for {{role.name}}
      ResourceGroup when doing an update. Default mode 'append' will
      append to the existing blacklist and 'update' would replace
      the blacklist.

  {{role.name}}SchedulerHints:
    type: json
    description: Optional scheduler hints to pass to nova
    default: {}
{%- if role.deprecated_param_scheduler_hints is defined %}
  {{role.deprecated_param_scheduler_hints}}:
    type: json
    description: DEPRECATED - use {{role.name}}SchedulerHints instead
    default: {}
{%- endif %}

  {{role.name}}Parameters:
    type: json
    description: Optional Role Specific parameters to be provided to service
    default: {}

  {{role.name}}ExtraGroupVars:
    type: json
    description: Optional extra Ansible group vars
    default: {}

  {{role.name}}ControlPlaneSubnet:
    default: ctlplane-subnet
    description: |
      Name of the subnet on ctlplane network for this role.
    type: string

  {{role.name}}ServiceNetMap:
    default: {}
    description: |
      Role specific ServiceNetMap overrides, the map provided will be merged
      with the global ServiceNetMap when passing the ServiceNetMap to the
      {{role.name}}ServiceChain resource and the {{role.name}} resource group.
      For example:
      {{role.name}}ServiceNetMap:
        NovaLibvirtNetwork: internal_api_leaf2
    type: json

  {{role.name}}NetConfigOverride:
    default: {}
    description: |
      Custom JSON data to be used to override the os-net-config config.
      This is meant to be used by net_config_override parameter in tripleoclient
      to provide an easy means to pass in custom net configs for the Undercloud.
    type: json

{% endfor %}

  # Identifiers to trigger tasks on nodes
  UpdateIdentifier:
    default: ''
    type: string
    description: >
      Setting to a previously unused value during stack-update will trigger
      package update on all nodes
  DeployIdentifier:
    default: ''
    type: string
    description: >
      Setting this to a unique value will re-run any deployment tasks which
      perform configuration on a Heat stack-update.
  AddVipsToEtcHosts:
    default: True
    type: boolean
    description: >
      Set to true to append per network Vips to /etc/hosts on each node.

  DeploymentServerBlacklist:
    default: []
    type: comma_delimited_list
    description: >
      List of server hostnames to blacklist from any triggered deployments.

  GlobalConfigExtraMapData:
    type: json
    default: {}
    description: Map of extra global_config_settings data to set on each node.

  NetConfigDataLookup:
    type: json
    default: {}
    description: >
      Configure os-net-config mappings for specific nodes
      Your environment file needs to look like:
        parameter_defaults:
          NetConfigDataLookup:
            node1:
              nic1: "00:c8:7c:e6:f0:2e"
            node2:
              nic1: "00:18:7d:99:0c:b6"
            node3:
              dmiString: 'system-uuid'
              id: 'A8C85861-1B16-4803-8689-AFC62984F8F6'
              nic1: em3
            # Dell PowerEdge
            nodegroup1:
              dmiString: "system-product-name"
              id: "PowerEdge R630"
              nic1: em3
              nic2: em1
              nic3: em2
            # Cisco UCS B200-M4"
            nodegroup2:
              dmiString: "system-product-name"
              id: "UCSB-B200-M4"
              nic1: enp7s0
              nic2: enp6s0

      This will result in the first node* entry where either a mac matches a
      local device or a DMI String matches the specified id being written as a
      mapping file for os-net-config. (/etc/os-net-config/mapping.yaml)

  DnsServers: # Override this via parameter_defaults
    default: []
    description: >
      DNS servers to use for the Overcloud (2 max for some implementations).
      If not set the nameservers configured in the ctlplane subnet's
      dns_nameservers attribute will be used.
    type: comma_delimited_list

  RootStackName:
    description: The name of the stack/plan.
    type: string

{% for role in roles %}
{%- if role.deprecated_param_scheduler_hints is defined or role.deprecated_param_extraconfig is defined %}
{%- if not parameter_groups_defined|default(false) %}
parameter_groups:
- label: deprecated
  description: Do not use deprecated params, they will be removed.
  parameters:
{%- set parameter_groups_defined = true %}
{%- endif %}
{%- endif %}
{%- if role.deprecated_param_scheduler_hints is defined %}
    - {{role.deprecated_param_scheduler_hints}}
{%- endif %}
{%- if role.deprecated_param_extraconfig is defined %}
    - {{role.deprecated_param_extraconfig}}
{%- endif %}
{%- endfor %}

conditions:
  add_vips_to_etc_hosts: {equals : [{get_param: AddVipsToEtcHosts}, True]}
  control_fixed_ip_not_set: {equals : [{get_param: ControlFixedIPs}, []]}
{%- for network in networks if network.name != 'External' %}
  {{network.name_lower}}_virtual_fixed_ip_set:
    not:
      equals:
        - get_param: {{network.name}}VirtualFixedIPs
        - []
{%- endfor %}
  public_virtual_fixed_ip_set:
    not:
      equals:
        - get_param: PublicVirtualFixedIPs
        - []
  set_default_mysql_cell_internal:
    or:
      - equals:
        - get_param: NovaAdditionalCell
        - true
      - and:
        - equals:
          - get_param: NovaAdditionalCell
          - false
        - equals:
          - get_param: [EndpointMapOverride, MysqlCellInternal]
          - ''
  set_default_nova_vnc_proxy_cell_public:
    or:
      - equals:
        - get_param: NovaAdditionalCell
        - true
      - and:
        - equals:
          - get_param: NovaAdditionalCell
          - false
        - equals:
          - get_param: [EndpointMapOverride, NovaVNCProxyCellPublic]
          - ''
  set_default_nova_metadata_cell_internal:
    or:
      - equals:
        - get_param: NovaLocalMetadataPerCell
        - true
      - and:
        - equals:
          - get_param: NovaLocalMetadataPerCell
          - false
        - equals:
          - get_param: [EndpointMapOverride, NovaMetadataCellInternal]
          - ''
  dnsservers_set:
    not:
      equals: [{get_param: DnsServers}, []]

resources:

  VipHosts:
    type: OS::Heat::Value
    properties:
      type: comma_delimited_list
      value:
        - str_replace:
            template: IP  HOST
            params:
              IP: {get_attr: [VipMap, net_ip_map, ctlplane]}
              HOST: {get_param: CloudNameCtlplane}
{%- for network in networks if network.vip|default(false) and network.enabled|default(true) %}
  {%- if network.name == 'External' %}
  # Special case the External hostname param, which is CloudName
        - str_replace:
            template: IP  HOST
            params:
              IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
              HOST: {get_param: CloudName}
  {%- elif network.name == 'InternalApi' %}
  # Special case the Internal API hostname param, which is CloudNameInternal
        - str_replace:
            template: IP  HOST
            params:
              IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
              HOST: {get_param: CloudNameInternal}
  {%- elif network.name == 'StorageMgmt' %}
  # Special case StorageMgmt hostname param, which is CloudNameStorageManagement
        - str_replace:
            template: IP  HOST
            params:
              IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
              HOST: {get_param: CloudNameStorageManagement}
  {%- else %}
        - str_replace:
            template: IP  HOST
            params:
              IP: {get_attr: [VipMap, net_ip_map, {{network.name_lower}}]}
              HOST: {get_param: CloudName{{network.name}}}
  {%- endif %}
{%- endfor %}

  NetCidrMapValue:
    type: OS::Heat::Value
    properties:
      type: json
      value:
        map_replace:
        - map_merge:
          - {get_attr: [Networks, net_cidr_map]}
          # NOTE(hjensas): When ctlplane network and subnets are created by the
          # undercloud installer, the subnet cidrs are added as tags.
          - ctlplane: {get_attr: [ControlVirtualIP, network, tags]}
        - keys:
            ctlplane: {get_param: NeutronControlPlaneID}

  NetIpVersionMapValue:
    type: OS::Heat::Value
    properties:
      type: json
      value:
        map_replace:
        - map_merge:
          - {get_attr: [Networks, net_ip_version_map]}
          - ctlplane: {get_attr: [ControlVirtualIP, subnets, 0, ip_version]}
        - keys:
            ctlplane: {get_param: NeutronControlPlaneID}

  ServiceNetMap:
    type: OS::TripleO::ServiceNetMap

  EndpointMap:
    type: OS::TripleO::EndpointMap
    properties:
      CloudEndpoints:
        ctlplane: {get_param: CloudNameCtlplane}
{%- for network in networks if network.vip|default(false) and network.enabled|default(true) %}
  {%- if network.name == 'External' %}
  # Special case the External hostname param, which is CloudName
        {{network.name_lower}}: {get_param: CloudName}
  {%- elif network.name == 'InternalApi' %}
  # Special case the Internal API hostname param, which is CloudNameInternal
        {{network.name_lower}}: {get_param: CloudNameInternal}
  {%- elif network.name == 'StorageMgmt' %}
  # Special case StorageMgmt hostname param, which is CloudNameStorageManagement
        {{network.name_lower}}: {get_param: CloudNameStorageManagement}
  {%- else %}
        {{network.name_lower}}: {get_param: CloudName{{network.name}}}
  {%- endif %}
{%- endfor %}
      NetIpMap: {get_attr: [VipMap, net_ip_map]}
      ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map]}

  EndpointMapData:
    type: OS::Heat::Value
    properties:
      type: json
      value:
        map_merge:
          - {get_attr: [EndpointMap, endpoint_map]}
          - {get_param: EndpointMapOverride}
            # For parent stack we must set these to the local endpoints
            # For split-controlplane stacks that are nova cells we must set
            # these to the local endpoints
            # For split-controlplane stacks that are not nova cells we should
            # take these from EndpointMapOverride (i.e the parent stack)
          - if:
            - set_default_mysql_cell_internal
            - MysqlCellInternal: {get_attr: [EndpointMap, endpoint_map, MysqlInternal]}
            - {}
          - if:
            - set_default_nova_vnc_proxy_cell_public
            - NovaVNCProxyCellPublic: {get_attr: [EndpointMap, endpoint_map, NovaVNCProxyPublic]}
            - {}
          - if:
            - set_default_nova_metadata_cell_internal
            - NovaMetadataCellInternal: {get_attr: [EndpointMap, endpoint_map, NovaMetadataInternal]}
            - {}

  # Creates the "heat-admin" user if configured via the environment
  # Should return a OS::Heat::MultipartMime reference via OS::stack_id
  NodeAdminUserData:
    type: OS::TripleO::NodeAdminUserData

  # Bootstraps an ntp configuration and includes a hardware clock sync to
  # for containers.
  # Should return a OS::Heat::MultipartMime reference via OS::stack_id
  NodeTimesyncUserData:
    type: OS::TripleO::NodeTimesyncUserData

  # For optional operator additional userdata
  # Should return a OS::Heat::MultipartMime reference via OS::stack_id
  NodeUserData:
    type: OS::TripleO::NodeUserData

  # Jinja loop for Role in roles_data.yaml
{% for role in roles %}
  # Resources generated for {{role.name}} Role
  {{role.name}}ServiceChain:
    type: OS::TripleO::{{role.name}}Services
    properties:
      Services:
        get_param: {{role.name}}Services
      ServiceNetMap:
        map_merge:
          - {get_attr: [ServiceNetMap, service_net_map]}
          - {get_param: {{role.name}}ServiceNetMap}
      ServiceData:
        net_cidr_map: {get_attr: [NetCidrMapValue, value]}
        net_vip_map: {get_attr: [VipMap, net_ip_map]}
        net_ip_version_map: {get_attr: [NetIpVersionMapValue, value]}
        vip_subnet_map: {get_attr: [ServiceNetMap, vip_subnet_map]}
      EndpointMap: {get_attr: [EndpointMapData, value]}
      RoleName: {{role.name}}
      RoleParameters:
        map_merge:
          - {{role.RoleParametersDefault|default({})}}
          - get_param: {{role.name}}Parameters

  # Lookup of role_data via heat outputs is slow, so workaround this by caching
  # the value in an OS::Heat::Value resource
  {{role.name}}ServiceChainRoleData:
    type: OS::Heat::Value
    properties:
      type: json
      value: {get_attr: [{{role.name}}ServiceChain, role_data]}

  {{role.name}}ConfigData:
    type: OS::Heat::Value
    properties:
      type: json
      value:
        service_configs: {get_attr: [{{role.name}}ServiceConfigSettings, value]}
        service_names: {get_attr: [{{role.name}}ServiceNames, value]}
        role_extraconfig:
          map_merge:
            - tripleo::profile::base::metrics::collectd::sensubility::subscriptions: {get_attr: [{{role.name}}ServiceChainRoleData, value, monitoring_subscriptions]}
            - tripleo_collectd_sensubility_subscriptions: {get_attr: [{{role.name}}ServiceChainRoleData, value, monitoring_subscriptions]}
{%- if role.deprecated_param_extraconfig is defined %}
            - {get_param: {{role.deprecated_param_extraconfig}}}
{%- endif %}
            - {get_param: {{role.name}}ExtraConfig}
        extraconfig: {get_param: ExtraConfig}
        hieradata_files:
          - '%{::uuid}'
          - fqdn
          - docker_puppet # Optionally provided by container-puppet.sh
          - ansible_managed
          - heat_config_%{::deploy_config_name}
          - config_step
          - role_extraconfig
          - extraconfig
          - pci_passthrough_whitelist
          - service_names
          - service_configs
          - cloud_domain
          - bootstrap_node # provided by tripleo_hieradata
          - all_nodes # provided by tripleo_hieradata
          - vip_data # provided by tripleo_hieradata
          - net_ip_map
          - ovn_chassis_mac_map # provided by tripleo_hieradata
          - '%{::osfamily}'
          # The following are required for compatibility with the Controller role
          # where some vendor integrations added hieradata via ExtraConfigPre
          - neutron_bigswitch_data # Optionally provided by Controller/ComputeExtraConfigPre
          # Special variable for upgrade
          - upgrade

  {{role.name}}ServiceConfigSettings:
    type: OS::Heat::Value
    properties:
      type: json
      value:
        map_merge:
          - get_param: GlobalConfigExtraMapData
          - get_attr: [{{role.name}}ServiceChainRoleData, value, config_settings]
          {% for r in roles %}
          - get_attr: [{{r.name}}ServiceChainRoleData, value, global_config_settings]
          {% endfor %}
          # This next step combines two yaql passes:
          # - The inner one does a deep merge on the service_config_settings for all roles
          # - The outer one filters the map based on the services enabled for the role
          #   then merges the result into one map.
          - yaql:
              expression: let(root => $) -> $.data.map.items().where($[0] in coalesce($root.data.services, [])).select($[1]).reduce($1.mergeWith($2), {})
              data:
                map:
                  yaql:
                    expression: $.data.where($ != null).reduce($1.mergeWith($2), {})
                    data:
                    {% for r in roles %}
                      - get_attr: [{{r.name}}ServiceChainRoleData, value, service_config_settings]
                    {% endfor %}
                services: {get_attr: [{{role.name}}ServiceNames, value]}

  # Filter any null/None service_names which may be present due to mapping
  # of services to OS::Heat::None
  {{role.name}}ServiceNames:
    type: OS::Heat::Value
    depends_on: {{role.name}}ServiceChain
    properties:
      type: comma_delimited_list
      value:
        yaql:
          expression: let(root => $) -> distinct($.data.extra_services.items().where($[0] in coalesce($root.data.enabled_services, [])).select($[1]).flatten() + coalesce($root.data.enabled_services, []))
          data:
            enabled_services: {get_attr: [{{role.name}}ServiceChainRoleData, value, service_names]}
            extra_services:
              # If anything other than keystone needs this
              # then we should add an extra_networks interface
              # to the service templates role_data but for
              # now we hard-code the keystone special case
              keystone:
                - keystone_admin_api
                - keystone_public_api

  {{role.name}}IpListMap:
    type: OS::TripleO::Network::Ports::NetIpListMap
    properties:
      ControlPlaneIpList: {get_attr: [{{role.name}}, ip_address]}
  {%- for network in networks if network.enabled|default(true) and network.name in role.networks|default([]) %}
      {{network.name}}IpList: {get_attr: [{{role.name}}, {{network.name_lower}}_ip_address]}
  {%- endfor %}
      RoleNetworks:
        - ctlplane
  {%- for network in networks if network.enabled|default(true) and network.name in role.networks|default([]) %}
        - {{network.name_lower}}
  {%- endfor %}
      EnabledServices: {get_attr: [{{role.name}}ServiceNames, value]}
      ServiceNetMap: {get_attr: [ServiceNetMap, service_net_map_lower]}
      ServiceHostnameList: {get_attr: [{{role.name}}, hostname]}
      NetworkHostnameMap: {get_attr: [{{role.name}}NetworkHostnameMap, value]}

  {{role.name}}NetworkHostnameMap:
    type: OS::Heat::Value
    properties:
      type: json
      value:
        # Note (shardy) this somewhat complex yaql may be replaced
        # with a map_deep_merge function in ocata.  It merges the
        # list of maps, but appends to colliding lists so we can
        # create a map of lists for all nodes for each network
        yaql:
          expression: dict($.data.where($ != null).flatten().selectMany($.items()).groupBy($[0], $[1]).select([$[0], $[1].flatten()]))
          data:
            - {get_attr: [{{role.name}}, hostname_map]}

  # Combine the NodeAdminUserData and NodeUserData mime archives
  {{role.name}}UserData:
    type: OS::Heat::MultipartMime
    properties:
      parts:
      - config: {get_resource: NodeAdminUserData}
        type: multipart
      - config: {get_resource: NodeTimesyncUserData}
        type: multipart
      - config: {get_resource: NodeUserData}
        type: multipart
      - config: {get_resource: {{role.name}}RoleUserData}
        type: multipart

  # For optional operator role-specific userdata
  # Should return a OS::Heat::MultipartMime reference via OS::stack_id
  {{role.name}}RoleUserData:
    type: OS::TripleO::{{role.name}}::NodeUserData

  {{role.name}}:
    type: OS::Heat::ResourceGroup
    depends_on: Networks
    update_policy:
      batch_create:
        max_batch_size: {get_param: NodeCreateBatchSize}
    properties:
      count: {get_param: {{role.name}}Count}
      removal_policies: {get_param: {{role.name}}RemovalPolicies}
      removal_policies_mode: {get_param: {{role.name}}RemovalPoliciesMode}
      resource_def:
        type: OS::TripleO::{{role.name}}
        properties:
          CloudDomain: {get_param: CloudDomain}
          ServiceNetMap:
            map_merge:
              - {get_attr: [ServiceNetMap, service_net_map]}
              - {get_param: {{role.name}}ServiceNetMap}
          EndpointMap: {get_attr: [EndpointMapData, value]}
          Hostname:
            str_replace:
              template: {get_param: {{role.name}}HostnameFormat}
              params:
                '%stackname%': {get_param: 'OS::stack_name'}
          NodeIndex: '%index%'
          # Note, SchedulerHints must be defined here, not only in the
          # nested template, as it can contain %index%
          {{role.name}}SchedulerHints:
            map_merge:
  {%- if role.deprecated_param_scheduler_hints is defined %}
              - {get_param: {{role.deprecated_param_scheduler_hints}}}
  {%- endif %}
              - {get_param: {{role.name}}SchedulerHints}
          ServiceNames: {get_attr: [{{role.name}}ServiceNames, value]}
          ServiceMetadataSettings: {get_attr: [{{role.name}}ServiceChainRoleData, value, service_metadata_settings]}
          OVNBridgeMappings: {get_attr: [{{role.name}}ServiceChainRoleData, value, config_settings, 'ovn::controller::ovn_bridge_mappings']}
          DeploymentServerBlacklistDict: {get_attr: [DeploymentServerBlacklistDict, value]}
          RoleParameters:
            map_merge:
              - {{role.RoleParametersDefault|default({})}}
              - get_param: {{role.name}}Parameters
          UserData: {get_resource: {{role.name}}UserData}
{%- endfor %}

{%- for role in roles %}
  {{role.name}}Servers:
    type: OS::Heat::Value
    depends_on: {{role.name}}
    properties:
      type: json
      value:
        yaql:
          expression: let(servers=>switch(isDict($.data.servers) => $.data.servers, true => {})) -> $servers.deleteAll($servers.keys().where($servers[$] = null))
          data:
            servers: {get_attr: [{{role.name}}, attributes, nova_server_resource]}
{%- endfor %}

  # This is a different format to *Servers, as it creates a map of lists
  # whereas *Servers creates a map of maps with keys of the nested resource names
  ServerIdMap:
    type: OS::Heat::Value
    properties:
      value:
        server_ids:
{%- for role in roles %}
          {{role.name}}: {get_attr: [{{role.name}}, nova_server_resource]}
{%- endfor %}
        bootstrap_server_id:
          yaql:
            expression: coalesce($.data, []).first(null)
            data: {get_attr: [{{primary_role_name}}, nova_server_resource]}

  # This resource just creates a dict out of the DeploymentServerBlacklist,
  # which is a list. The dict is used in the role templates to set a condition
  # on whether to create the deployment resources. We can't use the list
  # directly because there is no way to ask Heat if a list contains a specific
  # value.
  DeploymentServerBlacklistDict:
    type: OS::Heat::Value
    properties:
      type: json
      value:
        map_merge:
          repeat:
            template:
              hostname: 1
            for_each:
              hostname: {get_param: DeploymentServerBlacklist}

  HostsEntryValue:
    type: OS::Heat::Value
    properties:
      type: comma_delimited_list
      value:
        list_concat_unique:
          list_concat:
            - - {get_param: UndercloudHostsEntries}
            - - if:
                - add_vips_to_etc_hosts
                - {get_attr: [VipHosts, value]}
                - []
{%- for role in roles %}
            - {get_attr: [{{role.name}}, hosts_entry]}
{%- endfor %}
            - - {get_param: ExtraHostFileEntries}

  CloudNames:
    type: OS::Heat::Value
    properties:
      value:
{%- for network in networks if network.vip|default(false) and network.enabled|default(true) %}
  {%- if network.name == 'External' %}
        # Special case the External hostname param, which is CloudName
        cloud_name_{{network.name_lower}}: {get_param: CloudName}
  {%- elif network.name == 'InternalApi' %}
        # Special case the Internal API hostname param, which is CloudNameInternal
        cloud_name_{{network.name_lower}}: {get_param: CloudNameInternal}
  {%- elif network.name == 'StorageMgmt' %}
        # Special case StorageMgmt hostname param, which is CloudNameStorageManagement
        cloud_name_{{network.name_lower}}: {get_param: CloudNameStorageManagement}
  {%- else %}
        cloud_name_{{network.name_lower}}: {get_param: CloudName{{network.name}}}
  {%- endif %}
{%- endfor %}
        cloud_name_ctlplane: {get_param: CloudNameCtlplane}

  GlobalConfig:
    type: OS::Heat::Value
    properties:
      type: json
      value:
        map_merge:
{% for role in roles %}
          - get_attr: [{{role.name}}ServiceChainRoleData, value, global_config_settings]
{% endfor %}

  # creates the network architecture
  Networks:
    type: OS::TripleO::Network
    properties:
      CtlplaneNetworkCidrs: {get_attr: [ControlVirtualIP, network, tags]}

{%- for role in roles %}
  {{role.name}}GroupVars:
    type: OS::Heat::Value
    properties:
      value:
        ctlplane_mtu: {get_attr: [Networks, net_attributes_map, ctlplane, network, mtu]}
        ctlplane_gateway_ip: {get_attr: [Networks, net_attributes_map, ctlplane, subnets, {get_param: {{role.name}}ControlPlaneSubnet}, gateway_ip]}
        ctlplane_dns_nameservers:
          if:
            - dnsservers_set
            - {get_param: DnsServers}
            - {get_attr: [Networks, net_attributes_map, ctlplane, subnets, {get_param: {{role.name}}ControlPlaneSubnet}, dns_nameservers]}
        ctlplane_subnet_cidr: {str_split: ['/', {get_attr: [Networks, net_attributes_map, ctlplane, subnets, {get_param: {{role.name}}ControlPlaneSubnet}, cidr]}, 1]}
        ctlplane_host_routes:
          list_concat_unique:
            - {get_attr: [Networks, net_attributes_map, ctlplane, subnets, {get_param: {{role.name}}ControlPlaneSubnet}, host_routes]}
          {%- if role.default_route_networks is not defined or 'ControlPlane' in role.default_route_networks %}
            - - default: true
                next_hop: {get_attr: [Networks, net_attributes_map, ctlplane, subnets, {get_param: {{role.name}}ControlPlaneSubnet}, gateway_ip]}
          {%- endif %}
        # MTU is not filtered on role.networks, for DVR we need the External MTU on the exteranl_bridge
    {% for network in networks if network.enabled|default(true) %}
        {{network.name_lower}}_mtu: {get_attr: [Networks, net_attributes_map, {{network.name_lower}}, network, mtu]}
    {%- endfor %}
    {%- for network in networks if network.enabled|default(true) and network.name in role.networks|default([]) %}
      {%- if role.networks is mapping %}
        {%- set _role_net_subnet = role.networks[network.name]['subnet'] %}
      {%- else %}
        {%- set _role_net_subnet = network.name_lower + '_subnet' %}
      {%- endif %}
        {{network.name_lower}}_gateway_ip: {get_attr: [Networks, net_attributes_map, {{network.name_lower}}, subnets, {{_role_net_subnet}}, gateway_ip]}
        {{network.name_lower}}_host_routes:
          list_concat_unique:
            - {get_attr: [Networks, net_attributes_map, {{network.name_lower}}, subnets, {{_role_net_subnet}}, host_routes]}
          {%- if role.default_route_networks is defined and network.name in role.default_route_networks %}
            - - default: true
                next_hop: {get_attr: [Networks, net_attributes_map, {{network.name_lower}}, subnets, {{_role_net_subnet}}, gateway_ip]}
          {%- endif %}
        {{network.name_lower}}_cidr: {str_split: ['/', {get_attr: [Networks, net_attributes_map, {{network.name_lower}}, subnets, {{_role_net_subnet}}, cidr]}, 1]}
        {{network.name_lower}}_vlan_id:
          yaql:
            expression: >
              switch(not isList($.data) => 1,
                     not $.data.where($.startsWith('tripleo_vlan_id')).len() => 1,
                     true => int($.data.where($.startsWith('tripleo_vlan_id')).first().split('=').last()))
            data: {get_attr: [Networks, net_attributes_map, {{network.name_lower}}, subnets, {{_role_net_subnet}}, tags]}
    {%- endfor %}
        network_cidrs:
      {%- for network in networks if network.enabled|default(true) and network.name in role.networks|default([]) %}
      {%- if role.networks is mapping %}
        {%- set _role_net_subnet = role.networks[network.name]['subnet'] %}
      {%- else %}
        {%- set _role_net_subnet = network.name_lower + '_subnet' %}
      {%- endif %}
          {{network.name}}_cidr: {str_split: ['/', {get_attr: [Networks, net_attributes_map, {{network.name_lower}}, subnets, {{_role_net_subnet}}, cidr]}, 1]}
      {%- endfor %}
        dns_search_domains: {get_param: DnsSearchDomains}
        bond_interface_ovs_options: {get_param: BondInterfaceOvsOptions}
        local_mtu: {get_param: {{role.name}}LocalMtu}
        role_networks:
        {%- for network in networks if network.enabled|default(true) and network.name in role.networks|default([]) %}
          - {{network.name}}
        {%- endfor %}
        networks_lower:
        {%- for network in networks if network.enabled|default(true) %}
          {{network.name}}: {{network.name_lower}}
        {%- endfor %}
        networks_all:
        {%- for network in networks if network.enabled|default(true) %}
          - {{network.name}}
        {%- endfor %}
        service_metadata_settings: {get_attr: [{{role.name}}ServiceChainRoleData, value, service_metadata_settings]}
        tripleo_network_config_template: {get_param: {{role.name}}NetworkConfigTemplate}
        tripleo_network_config_with_ansible: {get_param: NetworkConfigWithAnsible}
        default_route_networks: {{role.default_route_networks|default(['ControlPlane'])}}
        networks_skip_config: {{ role.networks_skip_config|default([]) }}
        role_tags: {{role.tags}}

  {{role.name}}NetworkConfig:
    type: OS::TripleO::{{role.name}}::Net::SoftwareConfig
    properties:
      ControlPlaneIp: "{{ '{{' }} ctlplane_ip {{ '}}' }}"
      ControlPlaneSubnetCidr: {str_split: ['/', {get_attr: [Networks, net_attributes_map, ctlplane, subnets, {get_param: {{role.name}}ControlPlaneSubnet}, cidr]}, 1]}
      ControlPlaneDefaultRoute: {get_attr: [Networks, net_attributes_map, ctlplane, subnets, {get_param: {{role.name}}ControlPlaneSubnet}, gateway_ip]}
      ControlPlaneStaticRoutes:
        list_concat_unique:
          - {get_attr: [Networks, net_attributes_map, ctlplane, subnets, {get_param: {{role.name}}ControlPlaneSubnet}, host_routes]}
        {%- if role.default_route_networks is not defined or 'ControlPlane' in role.default_route_networks %}
          - - default: true
              next_hop: {get_attr: [Networks, net_attributes_map, ctlplane, subnets, {get_param: {{role.name}}ControlPlaneSubnet}, gateway_ip]}
        {%- endif %}
      ControlPlaneMtu: {get_attr: [Networks, net_attributes_map, ctlplane, network, mtu]}
      DnsServers:
        if:
          - dnsservers_set
          - {get_param: DnsServers}
          - {get_attr: [Networks, net_attributes_map, ctlplane, subnets, {get_param: {{role.name}}ControlPlaneSubnet}, dns_nameservers]}
  {%- for network in networks if network.enabled|default(true) and network.name in role.networks|default([]) %}
      {%- if role.networks is mapping %}
        {%- set _role_net_subnet = role.networks[network.name]['subnet'] %}
      {%- else %}
        {%- set _role_net_subnet = network.name_lower + '_subnet' %}
      {%- endif %}
      {{network.name}}IpSubnet: "{{ '{{' }} {{network.name_lower}}_ip ~ '/' ~ {{network.name_lower}}_cidr {{ '}}' }}"
      {{network.name}}InterfaceRoutes:
        list_concat_unique:
          - {get_attr: [Networks, net_attributes_map, {{network.name_lower}}, subnets, {{_role_net_subnet}}, host_routes]}
        {%- if role.default_route_networks is defined and network.name in role.default_route_networks %}
          - - default: true
              next_hop: {get_attr: [Networks, net_attributes_map, {{network.name_lower}}, subnets, {{_role_net_subnet}}, gateway_ip]}
        {%- endif %}
      {{network.name}}Mtu: {get_attr: [Networks, net_attributes_map, {{network.name_lower}}, network, mtu]}
      {{network.name}}NetworkVlanID:
        yaql:
          expression: >
            switch(not isList($.data) => 1,
                   not $.data.where($.startsWith('tripleo_vlan_id')).len() => 1,
                   true => int($.data.where($.startsWith('tripleo_vlan_id')).first().split('=').last()))
          data: {get_attr: [Networks, net_attributes_map, {{network.name_lower}}, subnets, {{_role_net_subnet}}, tags]}
    {%- if role.default_route_networks is defined and network.name in role.default_route_networks %}
      {{network.name}}InterfaceDefaultRoute: {get_attr: [Networks, net_attributes_map, {{network.name_lower}}, subnets, {{_role_net_subnet}}, gateway_ip]}
    {%- endif %}
  {%- endfor %}
{%- endfor %}

  ControlVirtualIP:
    depends_on: ServiceNetMap
    type: OS::TripleO::Network::Ports::ControlPlaneVipPort
    properties:
      name: control_virtual_ip
      dns_name: {str_split: ['.', {get_param: CloudNameCtlplane}, 0]}
      network: {get_param: NeutronControlPlaneID}
      fixed_ips:
        if:
        - control_fixed_ip_not_set
        - [{subnet: {get_attr: [ServiceNetMap, vip_subnet_map, ctlplane]}}]
        - get_param: ControlFixedIPs
      replacement_policy: AUTO
      tags:
        - tripleo_vip_net=ctlplane
        - str_replace:
            template: tripleo_stack_name=$STACK_NAME
            params:
              $STACK_NAME: {get_param: 'OS::stack_name'}

{%- for network in networks if network.vip|default(false) and network.enabled|default(true) %}
  {%- if network.name == 'External' %}
  # The public VIP is on the External net, falls back to ctlplane
  PublicVirtualIP:
  {%- else %}
  {{network.name}}VirtualIP:
  {%- endif %}
    depends_on: [Networks, ServiceNetMap]
    type: OS::TripleO::Network::Ports::{{network.name}}VipPort
    properties:
      ControlPlaneIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
      ControlPlaneSubnetCidr: {str_split: ['/', {get_attr: [Networks, net_attributes_map, ctlplane, subnets, {get_attr: [ServiceNetMap, vip_subnet_map, ctlplane]}, cidr]}, 1]}
      PortName: {{network.name_lower}}_virtual_ip
      {%- if network.name == 'External' %}
      DnsName: {str_split: ['.', {get_param: CloudName}, 0]}
      {%- elif network.name == 'InternalApi' %}
      DnsName: {str_split: ['.', {get_param: CloudNameInternal}, 0]}
      {%- elif network.name == 'StorageMgmt' %}
      DnsName: {str_split: ['.', {get_param: CloudNameStorageManagement}, 0]}
      {%- elif network.name not in ['External', 'InternalApi', 'StorageMgmt'] %}
      DnsName: {str_split: ['.', {get_param: CloudName{{network.name}}}, 0]}
      {%- endif %}
      FixedIPs:
        if:
        {%- if network.name == 'External' %}
        - public_virtual_fixed_ip_set
        - {get_param: PublicVirtualFixedIPs}
        {%- else %}
        - {{network.name_lower}}_virtual_fixed_ip_set
        - {get_param: {{network.name}}VirtualFixedIPs}
        {%- endif %}
        - [{subnet: {get_attr: [ServiceNetMap, vip_subnet_map, {{network.name}}]}}]
      IsVirtualIP: true
{%- endfor %}

  VipMap:
    type: OS::TripleO::Network::Ports::NetVipMap
    properties:
      ControlPlaneIp: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
      ControlPlaneSubnetCidr: {str_split: ['/', {get_attr: [Networks, net_attributes_map, ctlplane, subnets, {get_attr: [ServiceNetMap, vip_subnet_map, ctlplane]}, cidr]}, 1]}
{%- for network in networks if network.vip|default(false) and network.enabled|default(true) %}
  {%- if network.name == 'External' %}
      ExternalIp: {get_attr: [PublicVirtualIP, ip_address]}
      ExternalIpUri: {get_attr: [PublicVirtualIP, ip_address_uri]}
  {%- else %}
      {{network.name}}Ip: {get_attr: [{{network.name}}VirtualIP, ip_address]}
      {{network.name}}IpUri: {get_attr: [{{network.name}}VirtualIP, ip_address_uri]}
  {%- endif %}
{%- endfor %}
      # No tenant or management VIP required
    # Because of nested get_attr functions in the KeystoneAdminVip output, we
    # can't determine which attributes of VipMap are used until after
    # ServiceNetMap's attribute values are available.
    depends_on: ServiceNetMap

  # Optional ExtraConfig for all nodes - all roles are passed in here, but
  # the nested template may configure each role differently (or not at all)
  AllNodesExtraConfig:
    type: OS::TripleO::AllNodesExtraConfig
    properties:
      servers:
{%- for role in roles %}
        {{role.name}}: {get_attr: [{{role.name}}Servers, value]}
{%- endfor %}

  BlacklistedIpAddresses:
    type: OS::Heat::Value
    properties:
      value:
        list_concat:
{%- for role in roles %}
          - {get_attr: [{{role.name}}, blacklist_ip_address]}
{%- endfor %}

  AnsibleHostVars:
    type: OS::Heat::Value
    properties:
      type: json
      value:
{%- for role in roles %}
        {{role.name}}:
          map_merge:
            list_concat:
            - {get_attr: [{{role.name}}, ansible_host_vars_map]}
{%- endfor %}

  BlacklistedHostnames:
    type: OS::Heat::Value
    properties:
      value:
        list_concat:
{%- for role in roles %}
          - {get_attr: [{{role.name}}, blacklist_hostname]}
{%- endfor %}

  # Post deployment steps for all roles
  AllNodesDeploySteps:
    type: OS::TripleO::PostDeploySteps
    depends_on:
      - AllNodesExtraConfig
    properties:
      servers:
{%- for role in roles %}
        {{role.name}}: {get_attr: [{{role.name}}Servers, value]}
{%- endfor %}
      EndpointMap: {get_attr: [EndpointMapData, value]}
      role_data:
{%- for role in roles %}
        {{role.name}}: {get_attr: [{{role.name}}ServiceChainRoleData, value]}
{%- endfor %}
{%- for role in roles %}
      {{role.name}}Count: {get_param: {{role.name}}Count}
{%- endfor %}
      ServiceNetMapLower: {get_attr: [ServiceNetMap, service_net_map_lower]}
      PingTestGatewayIPsMap:
{%- for role in roles %}
        {{role.name}}:
          yaql:
            expression: list($.data.where($ != null)).flatten()
            data:
              - {get_attr: [Networks, net_attributes_map, ctlplane, subnets, {get_param: {{role.name}}ControlPlaneSubnet}, gateway_ip]}
    {%- for network in networks %}
      {%- if network.enabled|default(true) and network.name in role.networks|default([]) %}
        {%- if role.networks is mapping %}
          {%- set _role_net_subnet = role.networks[network.name]['subnet'] %}
        {%- else %}
          {%- set _role_net_subnet = network.name_lower + '_subnet' %}
        {%- endif %}
              - {get_attr: [Networks, net_attributes_map, {{network.name_lower}}, subnets, {{ _role_net_subnet }}, gateway_ip]}
      {%- endif %}
    {%- endfor %}
{%- endfor %}
      PingTestIpsMap:
{%- for role in roles %}
        {{role.name}}:
          list_join:
          - ' '
          - - yaql:
                expression: coalesce($.data, []).first(null)
                data: {get_attr: [{{role.name}}, ip_address]}
    {%- for network in networks %}
      {%- if network.enabled|default(true) and network.name in role.networks|default([]) %}
            - yaql:
                expression: coalesce($.data, []).first(null)
                data: {get_attr: [{{role.name}}, {{network.name_lower}}_ip_address]}
      {%- endif %}
    {%- endfor %}
{%- endfor %}
      HostsEntry: {get_attr: [HostsEntryValue, value]}
      EnabledServices:
        list_concat:
{%- for role in roles %}
          - {get_attr: [{{role.name}}ServiceNames, value]}
{%- endfor %}
      ControlVirtualIP: {get_attr: [ControlVirtualIP, fixed_ips, 0, ip_address]}
      EnabledNetworks:
{%- for network in networks if network.enabled|default(true) %}
        - {{ network.name }}
{%- endfor %}
      NetVipMap: {get_attr: [VipMap, net_ip_map]}
      CloudNames: {get_attr: [CloudNames, value]}
      UndercloudHostsEntries: {get_param: UndercloudHostsEntries}
      ExtraHostsEntries: {get_param: ExtraHostFileEntries}
      VipHostsEntries:
        if:
          - add_vips_to_etc_hosts
          - {get_attr: [VipHosts, value]}
          - []
      KeystoneResourcesConfigs:
        map_merge:
{% for role in roles %}
          - get_attr: [{{role.name}}ServiceChainRoleData, value, keystone_resources]
{% endfor %}
      NetCidrMap: {get_attr: [NetCidrMapValue, value]}

outputs:
  ManagedEndpoints:
    description: Asserts that the keystone endpoints have been provisioned.
    value: true
  KeystoneURL:
    description: URL for the Overcloud Keystone service
    value: {get_attr: [EndpointMapData, value, KeystonePublic, uri_no_suffix]}
  KeystoneAdminVip:
    description: Keystone Admin VIP endpoint
    # Note that these nested get_attr functions require a dependency
    # relationship between VipMap and ServiceNetMap, since we can't determine
    # which attributes of VipMap are used until after ServiceNetMap's attribute
    # values are available. If this is ever reworked to not use nested
    # get_attr, that dependency can be removed.
    value: {get_attr: [VipMap, net_ip_map, {get_attr: [ServiceNetMap, service_net_map, KeystoneAdminApiNetwork]}]}
  EndpointMap:
    description: |
      Mapping of the resources with the needed info for their endpoints.
      This includes the protocol used, the IP, port and also a full
      representation of the URI.
    value: {get_attr: [EndpointMapData, value]}
  HostsEntry:
    description: |
      The content that should be appended to your /etc/hosts if you want to get
      hostname-based access to the deployed nodes (useful for testing without
      setting up a DNS).
    value:
      list_concat_unique:
        - {get_attr: [HostsEntryValue, value]}
        - {get_attr: [VipHosts, value]}
  EnabledServices:
    description: The services enabled on each role
    value:
{%- for role in roles %}
      {{role.name}}: {get_attr: [{{role.name}}ServiceNames, value]}
{%- endfor %}
  RoleData:
    description: The configuration data associated with each role
    value:
{%- for role in roles %}
      {{role.name}}: {get_attr: [{{role.name}}ServiceChainRoleData, value]}
{%- endfor %}
  RoleConfig:
    description: The configuration workflows associated with each role
    value: {get_attr: [AllNodesDeploySteps, RoleConfig]}
  RoleNetIpMap:
    description: Mapping of each network to a list of IPs for each role
    value:
{%- for role in roles %}
      {{role.name}}: {get_attr: [{{role.name}}IpListMap, net_ip_map]}
{%- endfor %}
  RoleGroupVars:
    description: Mapping of roles to ansible group_vars to be applied config in those roles
    value:
{%- for role in roles %}
      {{role.name}}:
        map_merge:
        - {get_attr: [{{role.name}}ServiceChainRoleData, value, ansible_group_vars]}
        - {get_attr: [{{role.name}}GroupVars, value]}
        - {get_attr: [{{role.name}}ConfigData, value]}
        - any_errors_fatal: {get_param: {{role.name}}AnyErrorsFatal}
          max_fail_percentage: {get_param: {{role.name}}MaxFailPercentage}
          neutron_physical_bridge_name: {get_param: NeutronPhysicalBridge}
          neutron_public_interface_name: {get_param: NeutronPublicInterface}
          network_config_update: {get_param: {{role.name}}NetworkConfigUpdate}
          tripleo_network_config_os_net_config_mappings: {get_param: NetConfigDataLookup}
          deployed_server_port_map: {get_param: DeployedServerPortMap}
          tripleo_network_config_override: {get_param: {{role.name}}NetConfigOverride}
          tripleo_stack_name: {get_param: RootStackName}
{%- if role.name == 'ComputeOvsDpdk' %}
          num_dpdk_interface_rx_queues: {get_param: NumDpdkInterfaceRxQueues}
{%- endif %}
        - {get_param: {{role.name}}ExtraGroupVars}
{%- endfor %}
  RoleNetHostnameMap:
    description: Mapping of each network to a list of hostnames for each role
    value:
{%- for role in roles %}
      {{role.name}}: {get_attr: [{{role.name}}NetworkHostnameMap, value]}
{%- endfor %}
  RoleTags:
    description: Tags for each role, as defined in roles_data.yaml
    value:
{%- for role in roles %}
      {{role.name}}: {{role.tags|default([])}}
{%- endfor %}
  VipMap:
    description: Mapping of each network to VIP addresses. Also includes the Redis and OVN DBs VIPs.
    value: {get_attr: [VipMap, net_ip_map]}
  ServerIdData:
    description: Mapping of each role to a list of nova server IDs and the bootstrap ID
    value: {get_attr: [ServerIdMap, value]}
  BlacklistedHostnames:
    description: List of blacklisted hostnames
    value: {get_attr: [BlacklistedHostnames, value]}
  BlacklistedIpAddresses:
    description: List of blacklisted ctlplane IP addresses
    value: {get_attr: [BlacklistedIpAddresses, value]}
  GlobalConfig:
    description: The global_config (hieradata).
    value: {get_attr: [GlobalConfig, value]}
  RoleNetworkConfigMap:
    description: Mapping of roles to network config
    value:
{%- for role in roles %}
      {{role.name}}: {get_attr: [{{role.name}}NetworkConfig, config]}
{%- endfor %}
  AnsibleHostVarsMap:
    description: Map of Ansible Host variables per role
    value:
      map_merge:
      - {get_attr: [AnsibleHostVars, value]}
      - {get_param: ExtraAnsibleHostVars}
  TripleoHeatTemplatesJinja2RenderingDataSources:
    description: The role_data and the network_data used when rendering the THT Jinja2 templates
    value:
      roles_data: {{ roles }}
      networks_data: {{ networks }}