heat_template_version: rocky

description: >
  OpenStack containerized Ironic Inspector service (EXPERIMENTAL)

parameters:
  DockerIronicInspectorImage:
    description: image
    type: string
  DockerIronicInspectorConfigImage:
    description: The container image to use for the ironic_inspector config_volume
    type: string
  EndpointMap:
    default: {}
    description: Mapping of service endpoint -> protocol. Typically set
                 via parameter_defaults in the resource registry.
    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
  ServiceData:
    default: {}
    description: Dictionary packing service data
    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
  IPAImageURLs:
    default: []
    description: IPA image URLs, the format should be ["http://path/to/kernel", "http://path/to/ramdisk"]
    type: json
  UpgradeRemoveUnusedPackages:
    default: false
    description: Remove package if the service is being disabled during upgrade
    type: boolean
  MonitoringSubscriptionIronicInspector:
    default: 'overcloud-ironic-inspector'
    type: string
  KeystoneRegion:
    type: string
    default: 'regionOne'
    description: Keystone region for endpoint
  Debug:
    default: false
    description: Set to True to enable debugging on all services.
    type: boolean
  IronicInspectorDiscoveryDefaultDriver:
    default: 'ipmi'
    description: |
      The default driver to use for newly discovered nodes
      (requires IronicInspectorEnableNodeDiscovery set to True). This
      driver is automatically added to enabled_drivers.
    type: string
  IronicInspectorEnableNodeDiscovery:
    default: false
    description: |
      Makes ironic-inspector enroll any unknown node that PXE-boots
      introspection ramdisk in Ironic. The default driver to use for new nodes
      is specified by the IronicInspectorDiscoveryDefaultDriver parameter.
      Introspection rules can also be used to specify it.
    type: boolean
  IronicInspectorCollectors:
    default: default,logs
    description: Comma-separated list of IPA inspection collectors
    type: string
  IronicInspectorExtraProcessingHooks:
    default: extra_hardware,lldp_basic,local_link_connection
    description: |
        Comma-separated list of processing hooks to append to the default list.
    type: string
  IronicInspectorInterface:
    default: br-ex
    description: |
      Network interface on which inspection dnsmasq will listen. Should allow
      access to untagged traffic from nodes booted for inspection. The default
      value only makes sense if you don't modify any networking configuration.
    type: string
  IronicInspectorIPXEEnabled:
    default: true
    description: Whether to use iPXE for inspection.
    type: boolean
  IronicInspectorKernelArgs:
    default: 'ipa-inspection-dhcp-all-interfaces=1 ipa-collect-lldp=1 ipa-debug=1'
    description: Kernel args for the Ironic inspector.
    type: string
  IronicInspectorIpRange:
    description: |
        DEPRECATED: Use IronicInspectorSubnets instead.
        Temporary IP range that will be given to nodes during the inspection
        process. This should not overlap with any range that Neutron's DHCP
        gives away, but it has to be routeable back to ironic-inspector API.
        This option has no meaningful defaults, and thus is required.
    type: string
    default: ''
  IronicInspectorSubnets:
    description: |
        Temporary IP ranges that will be given to nodes during the inspection
        process. This should not overlap with any range that Neutron's DHCP
        gives away, but they need to be routeable back to ironic-inspector API.
        This option has no meaningful defaults, and thus is required.

        List of dictionaries with keys: 'tag', 'ip_range', 'netmask', and
        'gateway'. 'ip_range' is the only required key. Assigning multiple
        tagged subnets allow dnsmasq to serve dhcp request that came in via
        dhcp relay/helper.

        Example: - ip_range: 192.168.0.100,192.168.0.120
                 - ip_range: 192.168.1.100,192.168.1.200
                   netmask: 255.255.255.0
                   gateway: 192.168.1.254
                   tag: subnet1

        NOTE: For HA deployments use disjoint address pools to avoid potential
              address conflict. Use the hostname (short form) of each instance
              that will run Ironic Inspector and define a dictionary with the
              disjoint ip ranges.

        Example HA deployment using disjoint address pools:
                 overcloud-ironic-0:
                   - ip_range: 192.168.24.100,192.168.24.119
                   - ip_range: 192.168.25.100,192.168.25.119
                     netmask: 255.255.255.0
                     gateway: 192.168.25.254
                     tag: subnet1
                 overcloud-ironic-1:
                   - ip_range: 192.168.24.120,192.168.24.139
                   - ip_range: 192.168.25.120,192.168.25.139
                     netmask: 255.255.255.0
                     gateway: 192.168.25.254
                     tag: subnet1
    type: json
    default: {}
  IronicInspectorUseSwift:
    default: true
    description: Whether to use Swift for storing introspection data.
    type: boolean
  IronicIPXEPort:
    default: 8088
    description: Port to use for serving images when iPXE is used.
    type: string
  IronicPassword:
    description: The password for the Ironic service and db account, used by the Ironic services
    type: string
    hidden: true
  AdditionalArchitectures:
    default: []
    description: List of additional architectures to enable.
    type: comma_delimited_list

conditions:
  enable_ipxe: {equals : [{get_param: IronicInspectorIPXEEnabled}, true]}
  use_swift: {equals : [{get_param: IronicInspectorUseSwift}, true]}
  enable_node_discovery: {equals : [{get_param: IronicInspectorEnableNodeDiscovery}, true]}
  ironic_inspection_subnets_not_set: {equals : [{get_param: IronicInspectorSubnets}, {}]}
  enable_architecture_ppc64le: {contains: ['ppc64le', {get_param: AdditionalArchitectures}]}
  ipa_images: {not: {equals: [{get_param: IPAImageURLs}, []]}}

resources:

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

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

outputs:
  role_data:
    description: Role data for the Ironic Inspector role.
    value:
      service_name: ironic_inspector
      monitoring_subscription: {get_param: MonitoringSubscriptionIronicInspector}
      config_settings:
        map_merge:
          - ironic::inspector::listen_address:
              str_replace:
                 template:
                   "%{hiera('$NETWORK')}"
                 params:
                   $NETWORK: {get_param: [ServiceNetMap, IronicInspectorNetwork]}
            ironic::inspector::dnsmasq_local_ip:
              str_replace:
                 template:
                   "%{hiera('$NETWORK')}"
                 params:
                   $NETWORK: {get_param: [ServiceNetMap, IronicInspectorNetwork]}
            tripleo::profile::base::ironic_inspector::inspection_subnets:
              if:
              - ironic_inspection_subnets_not_set
              - [{ip_range: {get_param: IronicInspectorIpRange}}]
              - get_param: IronicInspectorSubnets
            ironic::inspector::dnsmasq_interface: {get_param: IronicInspectorInterface}
            ironic::inspector::dnsmasq_dhcp_hostsdir: /var/lib/ironic-inspector/dhcp-hostsdir
            ironic::inspector::pxe_filter::dnsmasq::purge_dhcp_hostsdir: false
            ironic::inspector::pxe_filter::driver: dnsmasq
            ironic::inspector::logging::debug: {get_param: Debug}
            ironic::inspector::always_store_ramdisk_logs: {get_param: Debug}
            ironic::inspector::authtoken::www_authenticate_uri: {get_param: [EndpointMap, KeystoneInternal, uri] }
            ironic::inspector::authtoken::auth_url: {get_param: [EndpointMap, KeystoneInternal, uri_no_suffix]}
            ironic::inspector::authtoken::username: 'ironic'
            ironic::inspector::authtoken::password: {get_param: IronicPassword}
            ironic::inspector::authtoken::project_name: 'service'
            ironic::inspector::authtoken::user_domain_name: 'Default'
            ironic::inspector::authtoken::project_domain_name: 'Default'
            ironic::inspector::cors::allowed_origin: '*'
            ironic::inspector::cors::max_age: 3600
            ironic::inspector::cors::allow_methods: 'GET,POST,PUT,DELETE,OPTIONS,PATCH'
            ironic::inspector::cors::allow_headers: 'Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma,X-Auth-Token'
            ironic::inspector::cors::expose_headers: 'Content-Type,Cache-Control,Content-Language,Expires,Last-Modified,Pragma'
            tripleo::ironic_inspector::firewall_rules:
              '137 ironic-inspector':
                dport:
                  - 5050
              '137 ironic-inspector dhcp input':
                iniface: {get_param: IronicInspectorInterface}
                proto: 'udp'
                chain: 'INPUT'
                dport: 67
              '137 ironic-inspector dhcp output':
                proto: 'udp'
                chain: 'OUTPUT'
                dport: 68
            ironic::inspector::ironic_username: 'ironic'
            ironic::inspector::ironic_password: {get_param: IronicPassword}
            ironic::inspector::ironic_tenant_name: 'service'
            ironic::inspector::ironic_auth_url: {get_param: [EndpointMap, KeystoneInternal, uri_no_suffix]}
            ironic::inspector::ipxe_timeout: 60
            ironic::inspector::ironic_max_retries: 6
            ironic::inspector::ironic_retry_interval: 10
            ironic::inspector::ironic_user_domain_name: 'Default'
            ironic::inspector::ironic_project_domain_name: 'Default'
            ironic::inspector::http_port: {get_param: IronicIPXEPort}
            ironic::inspector::additional_processing_hooks: {get_param: IronicInspectorExtraProcessingHooks}
            ironic::inspector::ramdisk_collectors: {get_param: IronicInspectorCollectors}
            ironic::inspector::ramdisk_kernel_args: {get_param: IronicInspectorKernelArgs}
            ironic::inspector::db::database_connection:
              list_join:
                - ''
                - - {get_param: [EndpointMap, MysqlInternal, protocol]}
                  - '://ironic-inspector:'
                  - {get_param: IronicPassword}
                  - '@'
                  - {get_param: [EndpointMap, MysqlInternal, host]}
                  - '/ironic-inspector'
                  - '?read_default_file=/etc/my.cnf.d/tripleo.cnf&read_default_group=tripleo'
          -
            if:
            - enable_architecture_ppc64le
            - ironic::inspector::enable_ppc64le: true
            - {}
          -
            if:
            - enable_ipxe
            - ironic::inspector::pxe_transfer_protocol: 'http'
            - {}
          -
            if:
            - use_swift
            - ironic::inspector::store_data: 'swift'
              ironic::inspector::swift_username: 'ironic'
              ironic::inspector::swift_password: {get_param: IronicPassword}
              ironic::inspector::swift_tenant_name: 'service'
              ironic::inspector::swift_auth_url: {get_param: [EndpointMap, KeystoneInternal, uri_no_suffix]}
              ironic::inspector::swift_user_domain_name: 'Default'
              ironic::inspector::swift_project_domain_name: 'Default'
            - {}
          -
            if:
            - enable_node_discovery
            - ironic::inspector::node_not_found_hook: 'enroll'
              ironic::inspector::discovery_default_driver: {get_param: IronicInspectorDiscoveryDefaultDriver}
            - {}
          # Match what we do for Ironic containers
          - ironic::inspector::tftp_root: /var/lib/ironic/tftpboot
          - ironic::inspector::http_root: /var/lib/ironic/httpboot
      service_config_settings:
        keystone:
          ironic::keystone::auth_inspector::tenant: 'service'
          ironic::keystone::auth_inspector::public_url: {get_param: [EndpointMap, IronicInspectorPublic, uri]}
          ironic::keystone::auth_inspector::internal_url: {get_param: [EndpointMap, IronicInspectorInternal, uri]}
          ironic::keystone::auth_inspector::admin_url: {get_param: [EndpointMap, IronicInspectorAdmin, uri]}
          ironic::keystone::auth_inspector::password: {get_param: IronicPassword}
          ironic::keystone::auth_inspector::region: {get_param: KeystoneRegion}
        mysql:
          ironic::inspector::db::mysql::password: {get_param: IronicPassword}
          ironic::inspector::db::mysql::user: ironic-inspector
          ironic::inspector::db::mysql::host: {get_param: [EndpointMap, MysqlInternal, host_nobrackets]}
          ironic::inspector::db::mysql::dbname: ironic-inspector
          ironic::inspector::db::mysql::allowed_hosts:
            - '%'
            - "%{hiera('mysql_bind_host')}"
      # BEGIN DOCKER SETTINGS
      puppet_config:
        config_volume: ironic_inspector
        puppet_tags: ironic_inspector_config
        step_config:
          list_join:
            - "\n"
            - - include ::tripleo::profile::base::ironic_inspector
              - {get_attr: [MySQLClient, role_data, step_config]}
        config_image: {get_param: DockerIronicInspectorConfigImage}
        volumes:
          - /var/lib/ironic:/var/lib/ironic:z
          - /var/lib/ironic-inspector/dhcp-hostsdir:/var/lib/ironic-inspector/dhcp-hostsdir:z
      kolla_config:
        /var/lib/kolla/config_files/ironic_inspector.json:
          command: /usr/bin/ironic-inspector --config-file /etc/ironic-inspector/inspector-dist.conf --config-file /etc/ironic-inspector/inspector.conf
          config_files:
            - source: "/var/lib/kolla/config_files/src/*"
              dest: "/"
              merge: true
              preserve_properties: true
          permissions:
            - path: /var/log/ironic-inspector
              owner: ironic-inspector:ironic-inspector
              recurse: true
            - path: /var/lib/ironic
              owner: ironic:ironic
              recurse: true
            - path: /var/lib/ironic-inspector/dhcp-hostsdir
              owner: ironic-inspector:ironic-inspector
              recurse: true
        /var/lib/kolla/config_files/ironic_inspector_dnsmasq.json:
          config_files:
            - source: "/var/lib/kolla/config_files/src/*"
              dest: "/"
              merge: true
              preserve_properties: true
          permissions:
            - path: /var/lib/ironic-inspector/dhcp-hostsdir
              owner: ironic-inspector:ironic-inspector
              recurse: true
          command: /sbin/dnsmasq --conf-file=/etc/ironic-inspector/dnsmasq.conf -k --log-facility=/var/log/ironic-inspector/dnsmasq.log
      docker_config:
        step_3:
          ironic_inspector_init_log:
            start_order: 0
            image: &ironic_inspector_image
              get_param: DockerIronicInspectorImage
            net: none
            user: root
            volumes:
              - /var/log/containers/ironic-inspector:/var/log/ironic-inspector:z
            command: ['/bin/bash', '-c', 'chown -R ironic-inspector:ironic-inspector /var/log/ironic-inspector']

          ironic_inspector_init_dnsmasq_dhcp_hostsdir:
            start_order: 1
            image: *ironic_inspector_image
            net: none
            user: root
            volumes:
            - /var/lib/ironic-inspector/dhcp-hostsdir:/var/lib/ironic-inspector/dhcp-hostsdir:shared,z
            command: ['/bin/bash', '-c', 'chown -R ironic-inspector:ironic-inspector /var/lib/ironic-inspector/dhcp-hostsdir']
          ironic_inspector_db_sync:
            start_order: 2
            image: *ironic_inspector_image
            net: host
            user: root
            privileged: false
            detach: false
            volumes:
              list_concat:
                - {get_attr: [ContainersCommon, volumes]}
                -
                  - /var/lib/kolla/config_files/ironic_inspector.json:/var/lib/kolla/config_files/config.json:ro
                  - /var/lib/config-data/ironic_inspector/etc/ironic-inspector:/etc/ironic-inspector:ro
                  - /var/log/containers/ironic-inspector:/var/log/ironic-inspector:z
            environment:
              - KOLLA_CONFIG_STRATEGY=COPY_ALWAYS
            command: "/usr/bin/bootstrap_host_exec ironic_inspector su ironic-inspector -s /bin/bash -c 'ironic-inspector-dbsync --config-file /etc/ironic-inspector/inspector.conf upgrade'"
          ironic_inspector_get_ipa:
            start_order: 2
            image: *ironic_inspector_image
            net: host
            user: root
            privileged: false
            detach: false
            volumes:
              list_concat:
                - {get_attr: [ContainersCommon, volumes]}
                -
                  - /var/lib/kolla/config_files/ironic_inspector.json:/var/lib/kolla/config_files/config.json:ro
                  - /var/lib/ironic:/var/lib/ironic:shared,z
            environment:
              - KOLLA_CONFIG_STRATEGY=COPY_ALWAYS
            command:
              if:
               - ipa_images
               - list_join:
                   - " "
                   - - "curl -g -o /var/lib/ironic/httpboot/agent.kernel"
                     - {get_param: [IPAImageURLs, 0]}
                     - "-o /var/lib/ironic/httpboot/agent.ramdisk"
                     - {get_param: [IPAImageURLs, 1]}
               - 'true'
        step_4:
          ironic_inspector:
            start_order: 92
            image: *ironic_inspector_image
            privileged: true
            net: host
            restart: always
            healthcheck:
              test: /openstack/healthcheck
            volumes:
              list_concat:
                - {get_attr: [ContainersCommon, volumes]}
                -
                  - /var/lib/kolla/config_files/ironic_inspector.json:/var/lib/kolla/config_files/config.json:ro
                  - /var/lib/config-data/puppet-generated/ironic_inspector/:/var/lib/kolla/config_files/src:ro
                  - /var/lib/ironic:/var/lib/ironic:shared,z
                  - /var/log/containers/ironic-inspector:/var/log/ironic-inspector:z
                  - /var/lib/ironic-inspector/dhcp-hostsdir:/var/lib/ironic-inspector/dhcp-hostsdir:shared,z
            environment:
              - KOLLA_CONFIG_STRATEGY=COPY_ALWAYS
          ironic_inspector_dnsmasq:
            start_order: 93
            image: *ironic_inspector_image
            privileged: true
            net: host
            restart: always
            user: root
            healthcheck:
              test: /openstack/healthcheck
            volumes:
              list_concat:
                - {get_attr: [ContainersCommon, volumes]}
                -
                  - /var/lib/kolla/config_files/ironic_inspector_dnsmasq.json:/var/lib/kolla/config_files/config.json:ro
                  - /var/lib/config-data/puppet-generated/ironic_inspector/:/var/lib/kolla/config_files/src:ro
                  - /var/log/containers/ironic-inspector:/var/log/ironic-inspector:z
                  - /var/lib/ironic-inspector/dhcp-hostsdir:/var/lib/ironic-inspector/dhcp-hostsdir:shared,z
            environment:
              - KOLLA_CONFIG_STRATEGY=COPY_ALWAYS
      host_prep_tasks:
        - name: create persistent directories
          file:
            path: "{{ item.path }}"
            state: directory
            setype: "{{ item.setype }}"
          with_items:
            - { 'path': /var/log/containers/ironic-inspector, 'setype': svirt_sandbox_file_t }
            - { 'path': /var/log/ironic-inspector, 'setype': svirt_sandbox_file_t }
        - name: ironic-inspector logs readme
          copy:
            dest: /var/log/ironic-inspector/readme.txt
            content: |
              Log files from ironic-inspector container can be found under
              /var/log/containers/ironic-inspector.
          ignore_errors: true
        - name: create persistent ironic-inspector dnsmasq dhcp hostsdir
          file:
            path: /var/lib/ironic-inspector/dhcp-hostsdir
            state: directory
            setype: svirt_sandbox_file_t
      upgrade_tasks:
        - when: step|int == 3
          block:
            - name: Set fact for removal of openstack-ironic-inspector package
              set_fact:
                remove_ironic_inspector_package: {get_param: UpgradeRemoveUnusedPackages}
            - name: Remove openstack-ironic-inspector package if operator requests it
              package: name=openstack-ironic-inspector state=removed
              ignore_errors: True
              when: remove_ironic_inspector_package|bool
      post_upgrade_tasks:
        - when: step|int == 1
          import_role:
            name: tripleo-docker-rm
          vars:
            containers_to_rm:
              - ironic_inspector
              - ironic_inspector_dnsmasq