heat_template_version: wallaby

description: >
  OpenStack containerized Nova API service

parameters:
  ContainerNovaApiImage:
    description: image
    type: string
  ContainerNovaConfigImage:
    description: The container image to use for the nova config_volume
    type: string
  NovaApiLoggingSource:
    type: json
    default:
      tag: openstack.nova.api
      file: /var/log/containers/nova/nova-api.log
  EndpointMap:
    default: {}
    description: Mapping of service endpoint -> protocol. Typically set
                 via parameter_defaults in the resource registry.
    type: json
  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
  RoleName:
    default: ''
    description: Role name on which the service is applied
    type: string
  RoleParameters:
    default: {}
    description: Parameters specific to the role
    type: json
  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.
  EnableInternalTLS:
    type: boolean
    default: false
  NovaPassword:
    description: The password for the nova service and db account
    type: string
    hidden: true
  MysqlIPv6:
    default: false
    description: Enable IPv6 in MySQL
    type: boolean
  NovaWorkers:
    default: 0
    description: Number of workers for Nova services.
    type: number
  KeystoneRegion:
    type: string
    default: 'regionOne'
    description: Keystone region for endpoint
  InstanceNameTemplate:
    default: 'instance-%08x'
    description: Template string to be used to generate instance names
    type: string
  NovaEnableDBPurge:
    default: true
    description: |
        Whether to create cron job for purging soft deleted rows in Nova database.
    type: boolean
  NovaEnableDBArchive:
    default: true
    description: |
        Whether to create cron job for archiving soft deleted rows in Nova database.
    type: boolean
  MonitoringSubscriptionNovaApi:
    default: 'overcloud-nova-api'
    type: string
  NovaDefaultFloatingPool:
    default: 'public'
    description: Default pool for floating IP addresses
    type: string
  NovaApiPolicies:
    description: |
      A hash of policies to configure for Nova API.
      e.g. { nova-context_is_admin: { key: context_is_admin, value: 'role:admin' } }
    default: {}
    type: json
  NovaAllowResizeToSameHost:
    default: false
    description: Allow destination machine to match source for resize.
    type: boolean
  NovaApiMaxLimit:
    default: 1000
    description: Max number of objects returned per API query
    type: number

parameter_groups:
- label: deprecated
  description: |
   The following parameters are deprecated and will be removed. They should not
   be relied on for new deployments. If you have concerns regarding deprecated
   parameters, please contact the TripleO development team on IRC or the
   OpenStack mailing list.
  parameters:
  - MysqlIPv6

conditions:

  internal_tls_enabled: {equals: [{get_param: EnableInternalTLS}, true]}

  mysql_ipv6_use_ip_address:
    and:
    - {equals: [{get_param: [ServiceData, net_ip_version_map, {get_param: [ServiceNetMap, MysqlNetwork]}]}, 6]}
    - {equals: [{get_param: EnableInternalTLS}, false]}

  nova_workers_zero: {equals : [{get_param: NovaWorkers}, 0]}


resources:

  ContainersCommon:
    type: ../containers-common.yaml

  MySQLClient:
    type: ../../deployment/database/mysql-client.yaml

  NovaApiLogging:
    type: OS::TripleO::Services::Logging::NovaApi

  ApacheServiceBase:
    type: ../../deployment/apache/apache-baremetal-puppet.yaml
    properties:
      ServiceData: {get_param: ServiceData}
      ServiceNetMap: {get_param: ServiceNetMap}
      EndpointMap: {get_param: EndpointMap}
      RoleName: {get_param: RoleName}
      RoleParameters: {get_param: RoleParameters}
      EnableInternalTLS: {get_param: EnableInternalTLS}

  NovaBase:
    type: ./nova-base-puppet.yaml
    properties:
      ServiceData: {get_param: ServiceData}
      ServiceNetMap: {get_param: ServiceNetMap}
      EndpointMap: {get_param: EndpointMap}
      RoleName: {get_param: RoleName}
      RoleParameters: {get_param: RoleParameters}

  NovaApiDBClient:
    type: ./nova-apidb-client-puppet.yaml
    properties:
      ServiceData: {get_param: ServiceData}
      ServiceNetMap: {get_param: ServiceNetMap}
      EndpointMap: {get_param: EndpointMap}
      RoleName: {get_param: RoleName}
      RoleParameters: {get_param: RoleParameters}

  NovaDBClient:
    type: ./nova-db-client-puppet.yaml
    properties:
      ServiceData: {get_param: ServiceData}
      ServiceNetMap: {get_param: ServiceNetMap}
      EndpointMap: {get_param: EndpointMap}
      RoleName: {get_param: RoleName}
      RoleParameters: {get_param: RoleParameters}


outputs:
  role_data:
    description: Role data for the Nova API role.
    value:
      service_name: nova_api
      firewall_rules:
        '113 nova_api':
          dport:
            - 8774
            - 13774
      keystone_resources:
        nova:
          endpoints:
            public: {get_param: [EndpointMap, NovaPublic, uri]}
            internal: {get_param: [EndpointMap, NovaInternal, uri]}
            admin: {get_param: [EndpointMap, NovaAdmin, uri]}
          users:
            nova:
              roles:
                - admin
                - service
              password: {get_param: NovaPassword}
          region: {get_param: KeystoneRegion}
          service: 'compute'
      monitoring_subscription: {get_param: MonitoringSubscriptionNovaApi}
      config_settings:
        map_merge:
          - get_attr: [NovaBase, role_data, config_settings]
          - get_attr: [NovaApiDBClient, role_data, config_settings]
          - get_attr: [NovaDBClient, role_data, config_settings]
          - get_attr: [NovaApiLogging, config_settings]
          - apache::default_vhost: false
            nova::keystone::authtoken::project_name: 'service'
            nova::keystone::authtoken::user_domain_name: 'Default'
            nova::keystone::authtoken::project_domain_name: 'Default'
            nova::keystone::authtoken::password: {get_param: NovaPassword}
            nova::keystone::authtoken::www_authenticate_uri: {get_param: [EndpointMap, KeystoneInternal, uri_no_suffix] }
            nova::keystone::authtoken::auth_url: {get_param: [EndpointMap, KeystoneInternal, uri_no_suffix]}
            nova::keystone::authtoken::region_name: {get_param: KeystoneRegion}
            nova::keystone::authtoken::interface: 'internal'
            nova::api::max_limit: {get_param: NovaApiMaxLimit}
            nova::api::enabled: true
            nova::network::neutron::default_floating_pool: {get_param: NovaDefaultFloatingPool}
            nova::api::enable_proxy_headers_parsing: true
            nova::api::api_bind_address:
              str_replace:
                template:
                  "%{hiera('fqdn_$NETWORK')}"
                params:
                  $NETWORK: {get_param: [ServiceNetMap, NovaApiNetwork]}
            nova::api::service_name: 'httpd'
            nova::wsgi::apache_api::ssl: {get_param: EnableInternalTLS}
            # NOTE: bind IP is found in hiera replacing the network name with the local node IP
            # for the given network; replacement examples (eg. for internal_api):
            # internal_api -> IP
            # internal_api_uri -> [IP]
            # internal_api_subnet - > IP/CIDR
            nova::wsgi::apache_api::bind_host:
              str_replace:
                template:
                  "%{hiera('$NETWORK')}"
                params:
                  $NETWORK: {get_param: [ServiceNetMap, NovaApiNetwork]}
            nova::wsgi::apache_api::servername:
              str_replace:
                template:
                  "%{hiera('fqdn_$NETWORK')}"
                params:
                  $NETWORK: {get_param: [ServiceNetMap, NovaApiNetwork]}
            nova::api::instance_name_template: {get_param: InstanceNameTemplate}
            nova_enable_db_purge: {get_param: NovaEnableDBPurge}
            nova_enable_db_archive: {get_param: NovaEnableDBArchive}
            nova::policy::policies: {get_param: NovaApiPolicies}
            nova::api::allow_resize_to_same_host: {get_param: NovaAllowResizeToSameHost}
          -
            if:
            - nova_workers_zero
            - {}
            - nova::api::osapi_compute_workers: {get_param: NovaWorkers}
              nova::wsgi::apache_api::workers: {get_param: NovaWorkers}

      service_config_settings:
        rabbitmq: {get_attr: [NovaBase, role_data, service_config_settings], rabbitmq}
        mysql:
          map_merge:
            - get_attr: [NovaApiDBClient, role_data, service_config_settings, mysql]
            - get_attr: [NovaDBClient, role_data, service_config_settings, mysql]
        rsyslog:
          tripleo_logging_sources_nova_api:
            - {get_param: NovaApiLoggingSource}
      # BEGIN DOCKER SETTINGS
      puppet_config:
        config_volume: nova
        puppet_tags: nova_config
        step_config:
          list_join:
            - "\n"
            - - "['Nova_cell_v2'].each |String $val| { noop_resource($val) }"
              - include tripleo::profile::base::nova::api
              - {get_attr: [MySQLClient, role_data, step_config]}
        config_image: {get_param: ContainerNovaConfigImage}
      kolla_config:
        /var/lib/kolla/config_files/nova_api.json:
          command: /usr/sbin/httpd -DFOREGROUND
          config_files:
            - source: "/var/lib/kolla/config_files/src/etc/httpd/conf.d"
              dest: "/etc/httpd/conf.d"
              merge: false
              preserve_properties: true
            - source: "/var/lib/kolla/config_files/src/etc/httpd/conf.modules.d"
              dest: "/etc/httpd/conf.modules.d"
              # TODO(emilien) remove optional flag once we get a promotion
              # https://launchpad.net/bugs/1884115
              optional: true
              merge: false
              preserve_properties: true
            - source: "/var/lib/kolla/config_files/src/*"
              dest: "/"
              merge: true
              preserve_properties: true
          permissions:
            - path: /var/log/nova
              owner: nova:nova
              recurse: true
        /var/lib/kolla/config_files/nova_api_cron.json:
          command: /usr/sbin/crond -n
          config_files:
            - source: "/var/lib/kolla/config_files/src/*"
              dest: "/"
              merge: true
              preserve_properties: true
          permissions:
            - path: /var/log/nova
              owner: nova:nova
              recurse: true
      container_config_scripts:
        map_merge:
          - {get_attr: [ContainersCommon, container_config_scripts]}
          - nova_wait_for_api_service.py:
              mode: "0755"
              content: { get_file: ../../container_config_scripts/nova_wait_for_api_service.py }
            nova_api_ensure_default_cell.sh:
              mode: "0700"
              content:
                str_replace:
                  template: |
                    #!/bin/bash
                    DEFID=$(su nova -s /bin/bash -c "nova-manage cell_v2 list_cells" | sed -e '1,3d' -e '$d' | awk -F ' *| *' '$2 == "default" {print $4}')
                    if [ "$DEFID" ]; then
                      echo "(cellv2) Updating default cell_v2 cell $DEFID"
                      su nova -s /bin/bash -c "/usr/bin/nova-manage cell_v2 update_cell --cell_uuid $DEFID --name=default --database_connection='CELLDB' --transport-url='TRANSPORTURL'"
                    else
                      echo "(cellv2) Creating default cell_v2 cell"
                      su nova -s /bin/bash -c "/usr/bin/nova-manage cell_v2 create_cell --name=default --database_connection='CELLDB' --transport-url='TRANSPORTURL'"
                    fi
                  params:
                    CELLDB:
                      list_join:
                      - ''
                      - - '{scheme}'
                        - '://'
                        - '{username}'
                        - ':'
                        - '{password}'
                        - '@'
                        -
                          if:
                            - mysql_ipv6_use_ip_address
                            - '[{hostname}]'
                            - '{hostname}'
                        - '/'
                        - 'nova'
                        - '?'
                        - '{query}'
                    TRANSPORTURL:
                      list_join:
                      - ''
                      - - '{scheme}'
                        - '://'
                        - '{username}'
                        - ':'
                        - '{password}'
                        - '@'
                        - '{hostname}'
                        - ':'
                        - '{port}'
                        - '/'
                        - '?'
                        - '{query}'
      docker_config:
        step_2:
          get_attr: [NovaApiLogging, docker_config, step_2]
        step_3:
          nova_api_db_sync:
            start_order: 0 # Runs before nova-conductor dbsync
            image: &nova_api_image {get_param: ContainerNovaApiImage}
            net: host
            detach: false
            user: root
            volumes: &nova_api_bootstrap_volumes
              list_concat:
                - {get_attr: [ContainersCommon, volumes]}
                - {get_attr: [NovaApiLogging, volumes]}
                -
                  - /var/lib/config-data/nova/etc/my.cnf.d/tripleo.cnf:/etc/my.cnf.d/tripleo.cnf:ro
                  - /var/lib/config-data/nova/etc/nova/:/etc/nova/:ro
            command: "/usr/bin/bootstrap_host_exec nova_api su nova -s /bin/bash -c '/usr/bin/nova-manage api_db sync'"
            environment:
              TRIPLEO_DEPLOY_IDENTIFIER: {get_param: DeployIdentifier}
          nova_api_map_cell0:
            start_order: 1 # Runs before nova-conductor dbsync
            image: *nova_api_image
            net: host
            detach: false
            user: root
            volumes: *nova_api_bootstrap_volumes
            command:
              str_replace:
                template: "/usr/bin/bootstrap_host_exec nova_api su nova -s /bin/bash -c '/usr/bin/nova-manage cell_v2 map_cell0 --database_connection=\"CELL0DB\"'"
                params:
                  CELL0DB:
                    list_join:
                    - ''
                    - - '{scheme}'
                      - '://'
                      - '{username}'
                      - ':'
                      - '{password}'
                      - '@'
                      -
                        if:
                          - mysql_ipv6_use_ip_address
                          - '[{hostname}]'
                          - '{hostname}'
                      - '/'
                      - 'nova_cell0'
                      - '?'
                      - '{query}'
          nova_api_ensure_default_cell:
            start_order: 2 # Runs before nova-conductor dbsync
            image: *nova_api_image
            net: host
            detach: false
            volumes:
              list_concat:
                - *nova_api_bootstrap_volumes
                -
                  - /var/lib/config-data/puppet-generated/nova:/var/lib/kolla/config_files/src:ro
                  - /var/lib/container-config-scripts/nova_api_ensure_default_cell.sh:/nova_api_ensure_default_cell.sh:ro
            user: root
            command: "/usr/bin/bootstrap_host_exec nova_api /nova_api_ensure_default_cell.sh"
        step_4:
          nova_api:
            start_order: 2
            image: *nova_api_image
            net: host
            user: root
            privileged: false
            restart: always
            healthcheck:
              test: /openstack/healthcheck
            volumes:
              list_concat:
                - {get_attr: [ContainersCommon, volumes]}
                - {get_attr: [NovaApiLogging, volumes]}
                -
                  - /var/lib/kolla/config_files/nova_api.json:/var/lib/kolla/config_files/config.json:ro
                  - /var/lib/config-data/puppet-generated/nova:/var/lib/kolla/config_files/src:ro
                - if:
                    - internal_tls_enabled
                    - - /etc/pki/tls/certs/httpd:/etc/pki/tls/certs/httpd:ro
                    - []
                - if:
                    - internal_tls_enabled
                    - - /etc/pki/tls/private/httpd:/etc/pki/tls/private/httpd:ro
                    - []
            environment:
              KOLLA_CONFIG_STRATEGY: COPY_ALWAYS
          nova_wait_for_api_service:
            start_order: 3
            image: *nova_api_image
            user: root
            net: host
            privileged: false
            detach: false
            volumes:
              list_concat:
                - {get_attr: [ContainersCommon, volumes]}
                -
                  - /var/lib/config-data/nova/etc/my.cnf.d/:/etc/my.cnf.d/:ro
                  - /var/lib/config-data/nova/etc/nova/:/etc/nova/:ro
                  - /var/log/containers/nova:/var/log/nova
                  - /var/lib/container-config-scripts/:/container-config-scripts/:z
            command: "/usr/bin/bootstrap_host_exec nova_api su nova -s /bin/bash -c '/container-config-scripts/pyshim.sh /container-config-scripts/nova_wait_for_api_service.py'"
            environment:
              __OS_DEBUG:
                yaql:
                  expression: str($.data.debug)
                  data:
                    debug: {get_attr: [NovaBase, role_data, config_settings, 'nova::logging::debug']}
          nova_api_cron:
            start_order: 4
            image: *nova_api_image
            net: host
            user: root
            privileged: false
            restart: always
            healthcheck:
              test: '/usr/share/openstack-tripleo-common/healthcheck/cron nova'
            volumes:
              list_concat:
                - {get_attr: [ContainersCommon, volumes]}
                - {get_attr: [NovaApiLogging, volumes]}
                -
                  - /var/lib/kolla/config_files/nova_api_cron.json:/var/lib/kolla/config_files/config.json:ro
                  - /var/lib/config-data/puppet-generated/nova:/var/lib/kolla/config_files/src:ro
            environment:
              KOLLA_CONFIG_STRATEGY: COPY_ALWAYS
      metadata_settings:
        get_attr: [ApacheServiceBase, role_data, metadata_settings]
      deploy_steps_tasks:
        list_concat:
          - get_attr: [ApacheServiceBase, role_data, deploy_steps_tasks]
          - - name: validate nova-api container state
              podman_container_info:
                name: nova_api
              register: nova_api_infos
              failed_when:
                - nova_api_infos.containers.0.Healthcheck.Status is defined
                - "'healthy' not in nova_api_infos.containers.0.Healthcheck.Status"
              retries: 10
              delay: 30
              tags:
                - opendev-validation
                - opendev-validation-nova
              when:
                - container_cli == 'podman'
                - not container_healthcheck_disabled
                - step|int == 4
      host_prep_tasks: {get_attr: [NovaApiLogging, host_prep_tasks]}
      external_upgrade_tasks:
        - when:
            - step|int == 1
          tags:
            - never
            - system_upgrade_transfer_data
            - system_upgrade_stop_services
          block:
            - name: Stop nova api container
              import_role:
                name: tripleo_container_stop
              vars:
                tripleo_containers_to_stop:
                  - nova_api
                  - nova_api_cron
                tripleo_delegate_to: "{{ groups['nova_api'] | default([]) }}"