[ATMOSPHERE-308] Add Ironic (#1697) (#1725)

Manual cherry-pick for stable/2023.1

Reviewed-by: Mohammed Naser <mnaser@vexxhost.com>
diff --git a/roles/ironic/README.md b/roles/ironic/README.md
new file mode 100644
index 0000000..03dfba0
--- /dev/null
+++ b/roles/ironic/README.md
@@ -0,0 +1 @@
+# `ironic`
diff --git a/roles/ironic/defaults/main.yml b/roles/ironic/defaults/main.yml
new file mode 100644
index 0000000..158e06b
--- /dev/null
+++ b/roles/ironic/defaults/main.yml
@@ -0,0 +1,38 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+ironic_helm_release_name: ironic
+ironic_helm_chart_path: "../../charts/ironic/"
+ironic_helm_chart_ref: /usr/local/src/ironic
+
+ironic_helm_release_namespace: openstack
+ironic_helm_values: {}
+
+# List of annotations to apply to the Ingress
+ironic_ingress_annotations: {}
+
+# Ironic bare metal network used for PXE
+ironic_bare_metal_network_manage: true
+ironic_bare_metal_network_name: baremetal
+ironic_bare_metal_network_provider_physical_network: external
+ironic_bare_metal_network_provider_network_type: flat # vlan
+# ironic_bare_metal_network_provider_segmentation_id:
+ironic_bare_metal_subnet_name: baremetal
+ironic_bare_metal_subnet_cidr: 172.24.6.0/24
+
+# Ironic Python Agent images
+ironic_python_agent_deploy_kernel_name: ipa-centos9-zed.kernel
+ironic_python_agent_deploy_kernel_url: https://tarballs.opendev.org/openstack/ironic-python-agent/dib/files/ipa-centos9-stable-zed.kernel
+ironic_python_agent_deploy_ramdisk_name: ipa-centos9-zed.initramfs
+ironic_python_agent_deploy_ramdisk_url: https://tarballs.opendev.org/openstack/ironic-python-agent/dib/files/ipa-centos9-stable-zed.initramfs
diff --git a/roles/ironic/meta/main.yml b/roles/ironic/meta/main.yml
new file mode 100644
index 0000000..4140469
--- /dev/null
+++ b/roles/ironic/meta/main.yml
@@ -0,0 +1,34 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+galaxy_info:
+  author: VEXXHOST, Inc.
+  description: Ansible role for OpenStack Ironic
+  license: Apache-2.0
+  min_ansible_version: 5.5.0
+  platforms:
+    - name: Ubuntu
+      versions:
+        - focal
+
+dependencies:
+  - role: defaults
+  - role: openstack_helm_endpoints
+    vars:
+      openstack_helm_endpoints_chart: ironic
+  - role: openstacksdk
+  - role: vexxhost.kubernetes.upload_helm_chart
+    vars:
+      upload_helm_chart_src: "{{ ironic_helm_chart_path }}"
+      upload_helm_chart_dest: "{{ ironic_helm_chart_ref }}"
diff --git a/roles/ironic/tasks/main.yml b/roles/ironic/tasks/main.yml
new file mode 100644
index 0000000..09cc9c6
--- /dev/null
+++ b/roles/ironic/tasks/main.yml
@@ -0,0 +1,96 @@
+# Copyright (c) 2022 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Create bare metal network
+  when: ironic_bare_metal_network_manage | bool
+  ansible.builtin.import_tasks: network/create.yml
+
+- name: Lookup existing bare metal network
+  when: not (ironic_bare_metal_network_manage | bool)
+  ansible.builtin.import_tasks: network/lookup.yml
+
+# - name: Create bare metal network ports
+#   # noqa: args[module]
+#   openstack.cloud.port:
+#     cloud: atmosphere
+#     name: "ironic-{{ inventory_hostname_short }}"
+#     device_owner: ironic:conductor
+#     network: "{{ ironic_bare_metal_network.id }}"
+#     fixed_ips: >-
+#       {{
+#         [
+#           {
+#             "ip_address": ironic_bare_metal_network_ip
+#           }
+#         ]
+#         if ironic_bare_metal_network_ip is defined else omit
+#       }}
+
+# - name: Set binding for ports
+#   changed_when: false
+#   ansible.builtin.shell: |
+#     openstack port set \
+#       --host {{ ansible_fqdn }} \
+#       ironic-{{ inventory_hostname_short }}
+#   environment:
+#     OS_CLOUD: atmosphere
+
+- name: Upload images
+  ansible.builtin.include_role:
+    name: glance_image
+  loop:
+    - name: "{{ ironic_python_agent_deploy_kernel_name }}"
+      url: "{{ ironic_python_agent_deploy_kernel_url }}"
+      format: aki
+    - name: "{{ ironic_python_agent_deploy_ramdisk_name }}"
+      url: "{{ ironic_python_agent_deploy_ramdisk_url }}"
+      format: ari
+  vars:
+    glance_image_name: "{{ item.name }}"
+    glance_image_url: "{{ item.url }}"
+    glance_image_container_format: "{{ item.format }}"
+    glance_image_disk_format: "{{ item.format }}"
+
+- name: Get details on the kernel image
+  run_once: true
+  openstack.cloud.image_info:
+    cloud: atmosphere
+    image: "{{ ironic_python_agent_deploy_kernel_name }}"
+  register: ironic_python_agent_deploy_kernel
+
+- name: Get details on the ramdisk image
+  run_once: true
+  openstack.cloud.image_info:
+    cloud: atmosphere
+    image: "{{ ironic_python_agent_deploy_ramdisk_name }}"
+  register: ironic_python_agent_deploy_ramdisk
+
+- name: Deploy Helm chart
+  run_once: true
+  kubernetes.core.helm:
+    name: "{{ ironic_helm_release_name }}"
+    chart_ref: "{{ ironic_helm_chart_ref }}"
+    release_namespace: "{{ ironic_helm_release_namespace }}"
+    create_namespace: true
+    kubeconfig: /etc/kubernetes/admin.conf
+    values: "{{ _ironic_helm_values | combine(ironic_helm_values, recursive=True) }}"
+
+- name: Create Ingress
+  ansible.builtin.include_role:
+    name: openstack_helm_ingress
+  vars:
+    openstack_helm_ingress_endpoint: baremetal
+    openstack_helm_ingress_service_name: ironic-api
+    openstack_helm_ingress_service_port: 6385
+    openstack_helm_ingress_annotations: "{{ ironic_ingress_annotations }}"
diff --git a/roles/ironic/tasks/network/create.yml b/roles/ironic/tasks/network/create.yml
new file mode 100644
index 0000000..8032983
--- /dev/null
+++ b/roles/ironic/tasks/network/create.yml
@@ -0,0 +1,35 @@
+# Copyright (c) 2022 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Create bare metal network
+  run_once: true
+  openstack.cloud.network:
+    cloud: atmosphere
+    # Network settings
+    name: "{{ ironic_bare_metal_network_name }}"
+    provider_physical_network: "{{ ironic_bare_metal_network_provider_physical_network }}"
+    provider_network_type: "{{ ironic_bare_metal_network_provider_network_type }}"
+    provider_segmentation_id: "{{ ironic_bare_metal_network_provider_segmentation_id }}"
+  register: ironic_bare_metal_network
+
+- name: Create bare metal network subnet
+  run_once: true
+  openstack.cloud.subnet:
+    cloud: atmosphere
+    # Subnet settings
+    network_name: "{{ ironic_bare_metal_subnet_name }}"
+    name: "{{ ironic_bare_metal_subnet_name }}"
+    cidr: "{{ ironic_bare_metal_subnet_cidr }}"
+    allocation_pool_start: "{{ ironic_bare_metal_subnet_allocation_pool_start | default(omit) }}"
+    allocation_pool_end: "{{ ironic_bare_metal_subnet_allocation_pool_end | default(omit) }}"
diff --git a/roles/ironic/tasks/network/lookup.yml b/roles/ironic/tasks/network/lookup.yml
new file mode 100644
index 0000000..8838ac9
--- /dev/null
+++ b/roles/ironic/tasks/network/lookup.yml
@@ -0,0 +1,33 @@
+# Copyright (c) 2022 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+- name: Gather information about a bare metal network
+  run_once: true
+  openstack.cloud.networks_info:
+    cloud: atmosphere
+    # Network settings
+    name: "{{ ironic_bare_metal_network_name }}"
+  register: ironic_bare_metal_networks_info
+
+- name: Assert that we match a single network only
+  ansible.builtin.assert:
+    that:
+      - ironic_bare_metal_networks_info.openstack_networks | length == 1
+    fail_msg: "Expected exactly one network, but found {{ ironic_bare_metal_networks_info.openstack_networks | length }}"
+    success_msg: "Successfully matched a single network"
+  run_once: true
+
+- name: Set fact with bare metal network information
+  ansible.builtin.set_fact:
+    ironic_bare_metal_network: "{{ ironic_bare_metal_networks_info.openstack_networks[0] }}"
diff --git a/roles/ironic/vars/main.yml b/roles/ironic/vars/main.yml
new file mode 100644
index 0000000..a965b1b
--- /dev/null
+++ b/roles/ironic/vars/main.yml
@@ -0,0 +1,78 @@
+# Copyright (c) 2023 VEXXHOST, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+_ironic_helm_values:
+  endpoints: "{{ openstack_helm_endpoints }}"
+  images:
+    tags: "{{ atmosphere_images | vexxhost.atmosphere.openstack_helm_image_tags('ironic') }}"
+  bootstrap:
+    image:
+      enabled: false
+    network:
+      enabled: false
+    object_store:
+      enabled: false
+  dependencies:
+    static:
+      api:
+        jobs:
+          - ironic-db-sync
+          - ironic-ks-user
+          - ironic-ks-endpoints
+          - ironic-rabbit-init
+          # NOTE(mnaser): We're managing all the networks via Ansible.
+          # - ironic-manage-cleaning-network
+      conductor:
+        jobs:
+          - ironic-db-sync
+          - ironic-ks-user
+          - ironic-ks-endpoints
+          - ironic-rabbit-init
+          # NOTE(mnaser): We're managing all the networks via Ansible.
+          # - ironic-manage-cleaning-network
+  conf:
+    ironic:
+      DEFAULT:
+        log_config_append: null
+        enabled_network_interfaces: flat,neutron
+        default_network_interface: flat
+      conductor:
+        clean_step_priority_override: deploy.erase_devices_express:5
+        deploy_kernel: "{{ ironic_python_agent_deploy_kernel.openstack_image.id }}"
+        deploy_ramdisk: "{{ ironic_python_agent_deploy_ramdisk.openstack_image.id }}"
+      deploy:
+        erase_devices_priority: 0
+        erase_devices_metadata_priority: 0
+      neutron:
+        cleaning_network: "{{ ironic_bare_metal_network_name }}"
+        inspection_network: "{{ ironic_bare_metal_network_name }}"
+        provisioning_network: "{{ ironic_bare_metal_network_name }}"
+        rescuing_network: "{{ ironic_bare_metal_network_name }}"
+      pxe:
+        kernel_append_params: "ipa-insecure=true systemd.journald.forward_to_console=yes"
+      service_catalog:
+        valid_interfaces: public
+  pod:
+    affinity:
+      anti:
+        type:
+          conductor: requiredDuringSchedulingIgnoredDuringExecution
+    replicas:
+      api: 3
+      conductor: 3
+  manifests:
+    ingress_api: false
+    service_ingress_api: false
+    # NOTE(mnaser): We're managing all the networks via Ansible.
+    job_manage_cleaning_network: false