heat_template_version: rocky
{%- set octavia_standalone=[] -%}
{%- for role in roles if 'standalone' in role.tags -%}
  {% if octavia_standalone.append('1') %}{% endif %}
{%- endfor %}

description: >
  Configuration of Octavia as-a-service resources in the overcloud.

parameters:
  ServiceData:
    default: {}
    description: Dictionary packing service data
    type: json
  ServiceNetMap:
    default: {}
    description: Mapping of service_name -> network name. Typically set
                 via parameter_defaults in the resource registry.  This
                 mapping overrides those in ServiceNetMapDefaults.
    type: json
  DefaultPasswords:
    default: {}
    type: json
  RoleName:
    default: ''
    description: Role name on which the service is applied
    type: string
  RoleParameters:
    default: {}
    description: Parameters specific to the role
    type: json
  EndpointMap:
    default: {}
    description: Mapping of service endpoint -> protocol. Typically set
                 via parameter_defaults in the resource registry.
    type: json
  StackAction:
    type: string
    description: >
      Heat action on performed top-level stack.  Note StackUpdateType is
      set to UPGRADE when a major-version upgrade is in progress.
    constraints:
    - allowed_values: ['CREATE', 'UPDATE']
  OctaviaPostWorkflowName:
    description: Mistral workflow name for octavia configuration steps
                 once the overcloud is ready.
    type: string
    default: 'tripleo.octavia_post.v1.octavia_post_deploy'
  OctaviaAmphoraImageName:
    description: The glance image name used when spawning amphorae. Default
                 is an empty string which will use the file name as the image
                 name.
    type: string
    default: ''
  OctaviaAmphoraImageFilename:
    description: Filename for the amphora image. Image files are expected to be
                 located in directory /usr/share/openstack-octavia-amphora-images.
                 Using the default of an empty string will cause a distro
                 specific default to be used. (e.g.
                 /usr/share/openstack-octavia-amphora-images/amphora-x64-haproxy.qcow2
                 on CentOS and /usr/share/openstack-octavia-amphora-images/octavia-amphora.qcow2
                 on Red Hat Enterprise Linux).
    type: string
    default: ''
  OctaviaAmphoraImageFormat:
    default: 'qcow2'
    description: Image format ('qcow2' or 'raw') of the amphora image.
    type: string
    constraints:
    - allowed_values: ['qcow2', 'raw']
  OctaviaAmphoraImageTag:
    default: 'amphora-image'
    description: Glance image tag for identifying the amphora image.
    type: string
  OctaviaAmphoraSshKeyName:
    type: string
    default: 'octavia-ssh-key'
    description: SSH key name.
  OctaviaAmphoraSshKeyFile:
    type: string
{% if not octavia_standalone %}
    default: ''
{% endif %}
    description: Public key file path. User will be able to SSH into amphorae
      with the provided key. User may, in most cases, also elevate to root
      from user 'centos' (CentOS), 'ubuntu' (Ubuntu) or 'cloud-user' (RHEL)
      (depends on how amphora image was created). Logging in to amphorae
      provides a convenient way to e.g. debug load balancing services.
  NovaEnableRbdBackend:
    default: false
    description: Whether to enable the Rbd backend for Nova ephemeral storage.
    type: boolean
    tags:
      - role_specific
  OctaviaControlNetwork:
    description: The name for the neutron network used for the amphora
                 control network
    type: string
    default: 'lb-mgmt-net'
  OctaviaControlSubnet:
    description: The name for the neutron subnet used for the amphora
      control network
    type: string
    default: 'lb-mgmt-subnet'
  OctaviaControlSecurityGroup:
    description: The name for the neutron security group used to
      control access on the amphora control network
    type: string
    default: 'lb-mgmt-sec-group'
  OctaviaControlSubnetCidr:
    description: Subnet for amphora control subnet in CIDR form.
    type: string
    default: '172.24.0.0/16'
  OctaviaControlSubnetGateway:
    description: IP address for control network gateway
    type: string
    default: '172.24.0.1'
  OctaviaControlSubnetPoolStart:
    description: First address in amphora control subnet address
                 pool.
    type: string
    default: '172.24.0.2'
  OctaviaControlSubnetPoolEnd:
    description: First address in amphora control subnet address
                 pool.
    type: string
    default: '172.24.255.254'
  OctaviaCaCertFile:
    type: string
    default: '/etc/octavia/certs/ca_01.pem'
    description: Octavia CA certificate file path.
  OctaviaCaKeyFile:
    type: string
    default: '/etc/octavia/certs/private/cakey.pem'
    description: Octavia CA private key file path.
  OctaviaServerCertsKeyPassphrase:
    constraints:
      - length: { min: 32, max: 32}
    description: Passphrase for encrypting Amphora Certificates and
                 Private Keys. Must be exactly 32 characters.
    type: string
    hidden: true
  OctaviaCaKeyPassphrase:
    description: CA private key passphrase.
    type: string
    hidden: true
  OctaviaClientCertFile:
    default: '/etc/octavia/certs/client.pem'
    description: client certificate for amphoras
    type: string
  OctaviaGenerateCerts:
    type: boolean
    default: false
    description: Enable internal generation of certificates for secure
                 communication with amphorae for isolated private clouds or
                 systems where security is not a concern. Otherwise, use
                 OctaviaCaCert, OctaviaCaKey, OctaviaCaKeyPassphrase,
                 OctaviaClientCert and OctaviaServerCertsKeyPassphrase
                 to configure Octavia.
  OctaviaMgmtPortDevName:
    type: string
    default: "o-hm0"
    description: Name of the octavia management network interface using
                 for communication between octavia worker/health-manager
                 with the amphora machine.
  AdminPassword:
    description: The password for the keystone admin account, used for monitoring, querying neutron etc.
    type: string
    hidden: true
  OctaviaUserName:
    description: The username for the Octavia database and keystone accounts.
    type: string
    default: 'octavia'
  OctaviaPassword:
    description: The password for the Octavia database and keystone accounts.
    type: string
    hidden: true
  OctaviaProjectName:
    description: The project name for the keystone Octavia account.
    type: string
    default: 'service'
  ContainerCli:
    type: string
    default: 'podman'
    description: CLI tool used to manage containers.
    constraints:
      - allowed_values: ['docker', 'podman']

conditions:
  octavia_raw_image_check:
    or:
    - equals:
      - get_param: OctaviaAmphoraImageFormat
      - raw
    - get_param: NovaEnableRbdBackend

  generate_certs:
      and:
      - get_param: OctaviaGenerateCerts
      - equals:
        - get_param: StackAction
        - CREATE

resources:
{% if not octavia_standalone %}
  default_key_pair:
    type: OS::Nova::KeyPair
    external_id: default
{% endif %}
  OctaviaVars:
    type: OS::Heat::Value
    properties:
      type: json
      value:
        vars:
          os_auth_type: "password"
          os_identity_api_version: "3"
          amp_image_name: { get_param: OctaviaAmphoraImageName }
          amp_image_filename: {get_param: OctaviaAmphoraImageFilename }
          amp_image_tag: { get_param: OctaviaAmphoraImageTag }
          amp_ssh_key_name: { get_param: OctaviaAmphoraSshKeyName }
          amp_ssh_key_path: { get_param: OctaviaAmphoraSshKeyFile }
{% if not octavia_standalone %}
          amp_ssh_key_data: { get_attr: [default_key_pair, public_key] }
{% endif %}
{% raw %}
          amp_to_raw: {if: [octavia_raw_image_check, true, false]}
          auth_username: { get_param: OctaviaUserName }
          auth_password: { get_param: OctaviaPassword }
          auth_project_name: { get_param: OctaviaProjectName }
          lb_mgmt_net_name: { get_param: OctaviaControlNetwork }
          lb_mgmt_subnet_name: { get_param: OctaviaControlSubnet }
          lb_sec_group_name: { get_param: OctaviaControlSubnet }
          lb_mgmt_subnet_cidr: { get_param: OctaviaControlSubnetCidr }
          lb_mgmt_subnet_gateway: { get_param: OctaviaControlSubnetGateway }
          lb_mgmt_subnet_pool_start: { get_param: OctaviaControlSubnetPoolStart }
          lb_mgmt_subnet_pool_end: { get_param: OctaviaControlSubnetPoolEnd }
          ca_cert_path: { get_param: OctaviaCaCertFile }
          ca_private_key_path: { get_param: OctaviaCaKeyFile }
          server_certs_key_passphrase: {get_param: OctaviaServerCertsKeyPassphrase}
          ca_passphrase: { get_param: OctaviaCaKeyPassphrase }
          client_cert_path: { get_param: OctaviaClientCertFile }
          generate_certs: {if: [generate_certs, true, false]}
          mgmt_port_dev: { get_param: OctaviaMgmtPortDevName }
          os_password: { get_param: AdminPassword }
          os_project_name: 'admin'
          os_username: 'admin'
          octavia_ansible_playbook: '/usr/share/ansible/tripleo-playbooks/octavia-files.yaml'
          os_auth_url: { get_param: [EndpointMap, KeystoneV3Public, uri] }
          os_int_auth_url: { get_param: [EndpointMap, KeystoneInternal, uri] }
          octavia_local_tmpdir: "{{playbook_dir}}/octavia-ansible/local_dir"
          octavia_group_vars_dir: "{{playbook_dir}}/octavia-ansible/group_vars"
          container_cli: { get_param: ContainerCli }

outputs:
  role_data:
    description: Role data for the Octavia configuration service
    value:
      service_name: octavia_deployment_config
      upgrade_tasks: []
      puppet_config:
        config_image: ''
        config_volume: ''
        step_config: ''
      docker_config: {}
      config_settings: {}
      external_deploy_tasks:
        - name: octavia_post_deploy
          when: step|int == 5
          block:
            - name: Set up group_vars
              set_fact:
                octavia_ansible_group_vars: { get_attr: [OctaviaVars, value, vars] }
            - name: Make needed directories on the undercloud
              become: true
              file:
                path: "{{item}}"
                state: directory
                owner: "{{ ansible_user }}"
              with_items:
                - "{{playbook_dir}}/octavia-ansible"
                - "{{ octavia_ansible_group_vars.octavia_local_tmpdir }}"
                - "{{ octavia_ansible_group_vars.octavia_group_vars_dir }}"
            - name: Write group_vars file
              copy:
                dest: "{{ octavia_ansible_group_vars.octavia_group_vars_dir }}/octavia_vars.yaml"
                content: "{{ octavia_ansible_group_vars|to_nice_yaml }}"
            - name: Write octavia inventory
              copy:
                dest: "{{playbook_dir}}/octavia-ansible/inventory.yaml"
                content: |
                  octavia_nodes:
                    hosts:

                  {%- set octavia_groups = ['worker'] -%}
                  {%- for octavia_group in octavia_groups -%}
                  {%- if 'octavia_' ~ octavia_groups %}

                      {% for host in groups['octavia_' ~ octavia_group] -%}
                      {{ hostvars.raw_get(host)['ansible_hostname'] }}:
                        ansible_user: {{ hostvars.raw_get(host)['ansible_ssh_user'] | default('heat-admin') }}
                        ansible_host: {{ hostvars.raw_get(host)['ansible_host'] | default(host) }}
                        canonical_hostname: {{ hostvars.raw_get(host)['canonical_hostname'] | default(host) }}
                        ansible_become: true
                      {% endfor %}

                  {%- endif -%}
                  {%- endfor %}

                  Undercloud:
                    hosts:
                      {% for host in groups['Undercloud'] -%}
                      {{ hostvars.raw_get(host)['ansible_hostname'] }}:
                        ansible_host: {{ hostvars.raw_get(host)['ansible_host'] | default(host) }}
                        ansible_become: false
                        ansible_connection: local

                      {%- endfor -%}
            - name: Check for ssh_private_key in working directory
              stat:
                path: "{{playbook_dir}}/ssh_private_key"
              register: detect_private_key_file
            - name: Set private key location
              set_fact:
                octavia_ansible_ssh_key: "{{ playbook_dir }}/ssh_private_key"
              when: octavia_ansible_ssh_key is not defined and detect_private_key_file.stat.exists
            - name: Configure octavia command
              set_fact:
                config_octavia_cmd:
                  list_join:
                    - ' '
                    - - ANSIBLE_CONFIG="{{playbook_dir}}/ansible.cfg" ansible-playbook -i "{{playbook_dir}}/octavia-ansible/inventory.yaml"
                      - '--extra-vars @{{ octavia_ansible_group_vars.octavia_group_vars_dir }}/octavia_vars.yaml'
                      - '{% if octavia_ansible_ssh_key is defined %}--private-key {{octavia_ansible_ssh_key}}{% endif %}'
                      - '{{ octavia_ansible_group_vars.octavia_ansible_playbook }}'
            - set_fact:
                octavia_log_dir: "{{playbook_dir}}/octavia-ansible/"
            - debug:
                msg: "Configure Octavia command is: {{ config_octavia_cmd }}"
            - name: Configure octavia on overcloud
              become: true
              environment:
                ANSIBLE_HOST_KEY_CHECKING: False
                ANSIBLE_SSH_RETRIES: 3
                ANSIBLE_RETRY_FILES_ENABLED: false
                ANSIBLE_LOCAL_TEMP: "{{ octavia_ansible_group_vars.octavia_local_tmpdir }}"
                ANSIBLE_LOG_PATH:  "{{ octavia_log_dir }}/octavia-ansible.log"
              shell:  "{{ config_octavia_cmd }}"
            - name: Purge temp dirs
              file:
                state: absent
                path: "{{ item }}"
              with_items:
              - "{{ octavia_ansible_group_vars.octavia_local_tmpdir }}"
{% endraw %}
