From 696635ea9ecc4fd0dbcb1468e5950ad3167384c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Jens=C3=A5s?= Date: Tue, 22 Jul 2025 12:48:44 +0200 Subject: [PATCH 1/2] Add cifmw_ocp_agent_installer role MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a port of the OCP Agent Installer role used in hotstack. I used Claude 4 Sonnet to rename all the variables, and removed some hotstack specific snapshot features. Follow up changes to integrate this with other ci-framework would follow, and that will most likely also mean edits to this role. Assisted-By: Claude Code/claude-sonnet-4 Signed-off-by: Harald Jensås --- roles/ocp_agent_installer/README.md | 12 ++ roles/ocp_agent_installer/defaults/main.yml | 74 ++++++++ .../additional-trusted-ca-config-image.yaml | 8 + .../files/etcd-config.yaml | 8 + roles/ocp_agent_installer/meta/main.yml | 15 ++ .../tasks/additional_ca.yml | 53 ++++++ .../tasks/config_assets.yml | 94 ++++++++++ .../tasks/ingress_cert.yml | 39 +++++ .../tasks/install_client.yml | 47 +++++ .../tasks/install_installer.yml | 40 +++++ .../ocp_agent_installer/tasks/iso_assets.yml | 19 ++ .../tasks/machine_configs.yml | 90 ++++++++++ roles/ocp_agent_installer/tasks/main.yml | 162 ++++++++++++++++++ .../ocp_agent_installer/tasks/pxe_assets.yml | 66 +++++++ .../additional-trusted-ca-config-map.yaml.j2 | 11 ++ .../templates/disable-netifnames.bu.j2 | 10 ++ .../templates/enable-iscsi.bu.j2 | 32 ++++ .../templates/enable-multipath.bu.j2 | 30 ++++ .../image-content-source-policy.yaml.j2 | 8 + .../templates/lv-cinder-volumes.bu.j2 | 31 ++++ .../templates/ovn-k8s-config.j2 | 13 ++ 21 files changed, 862 insertions(+) create mode 100644 roles/ocp_agent_installer/README.md create mode 100644 roles/ocp_agent_installer/defaults/main.yml create mode 100644 roles/ocp_agent_installer/files/additional-trusted-ca-config-image.yaml create mode 100644 roles/ocp_agent_installer/files/etcd-config.yaml create mode 100644 roles/ocp_agent_installer/meta/main.yml create mode 100644 roles/ocp_agent_installer/tasks/additional_ca.yml create mode 100644 roles/ocp_agent_installer/tasks/config_assets.yml create mode 100644 roles/ocp_agent_installer/tasks/ingress_cert.yml create mode 100644 roles/ocp_agent_installer/tasks/install_client.yml create mode 100644 roles/ocp_agent_installer/tasks/install_installer.yml create mode 100644 roles/ocp_agent_installer/tasks/iso_assets.yml create mode 100644 roles/ocp_agent_installer/tasks/machine_configs.yml create mode 100644 roles/ocp_agent_installer/tasks/main.yml create mode 100644 roles/ocp_agent_installer/tasks/pxe_assets.yml create mode 100644 roles/ocp_agent_installer/templates/additional-trusted-ca-config-map.yaml.j2 create mode 100644 roles/ocp_agent_installer/templates/disable-netifnames.bu.j2 create mode 100644 roles/ocp_agent_installer/templates/enable-iscsi.bu.j2 create mode 100644 roles/ocp_agent_installer/templates/enable-multipath.bu.j2 create mode 100644 roles/ocp_agent_installer/templates/image-content-source-policy.yaml.j2 create mode 100644 roles/ocp_agent_installer/templates/lv-cinder-volumes.bu.j2 create mode 100644 roles/ocp_agent_installer/templates/ovn-k8s-config.j2 diff --git a/roles/ocp_agent_installer/README.md b/roles/ocp_agent_installer/README.md new file mode 100644 index 0000000000..d6bf988a65 --- /dev/null +++ b/roles/ocp_agent_installer/README.md @@ -0,0 +1,12 @@ +# cifmw_ocp_agent_installer + +> **NOTE**: This Work-in-Progress ... + + +* Create PXE bootstrap-artifacts using the OCP Agent Installer +* Customizations: + * Enable iscsi + * Enable multipath + * Stand up cinder-volumes LVM + * Extra config for OVN-Kubernetes + * etcd hardware speed (Slower) diff --git a/roles/ocp_agent_installer/defaults/main.yml b/roles/ocp_agent_installer/defaults/main.yml new file mode 100644 index 0000000000..3918a02eed --- /dev/null +++ b/roles/ocp_agent_installer/defaults/main.yml @@ -0,0 +1,74 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# 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. + + +# Can be one of ['pxe', 'iso'] +cifmw_ocp_agent_installer_bootstrap_assets: pxe +cifmw_ocp_agent_installer_add_ingress_cert_to_ca_trust: true + +cifmw_ocp_agent_installer_openshift_version: stable-4.18 +cifmw_ocp_agent_installer_mirror_url: https://mirror.openshift.com/pub/openshift-v4/x86_64/clients/ocp +cifmw_ocp_agent_installer_client_url: "{{ cifmw_ocp_agent_installer_mirror_url }}/{{ cifmw_ocp_agent_installer_openshift_version }}/openshift-client-linux.tar.gz" +cifmw_ocp_agent_installer_installer_url: "{{ cifmw_ocp_agent_installer_mirror_url }}/{{ cifmw_ocp_agent_installer_openshift_version }}/openshift-install-linux.tar.gz" + +cifmw_ocp_agent_installer_base_dir: "{{ cifmw_basedir | default(ansible_user_dir ~ '/ci-framework-data') }}/ocp-agent-installer" +cifmw_ocp_agent_installer_bin_dir: "{{ ansible_user_dir }}/bin" +cifmw_ocp_agent_installer_kube_config_dir: "{{ ansible_user_dir }}/.kube" +cifmw_ocp_agent_installer_cluster_dir: "{{ cifmw_ocp_agent_installer_base_dir }}/ocp-cluster" +cifmw_ocp_agent_installer_manifests_dir: "{{ cifmw_ocp_agent_installer_cluster_dir }}/openshift" +cifmw_ocp_agent_installer_agent_installer_dir: "{{ cifmw_ocp_agent_installer_base_dir }}/agent-installer" +cifmw_ocp_agent_installer_cluster_custom_config_dir: "{{ cifmw_ocp_agent_installer_base_dir }}/cluster-custom-config/" +cifmw_ocp_agent_installer_butane_dir: "{{ cifmw_ocp_agent_installer_cluster_custom_config_dir }}/butane" +cifmw_ocp_agent_installer_machine_configs_dir: "{{ cifmw_ocp_agent_installer_cluster_custom_config_dir }}/machine-configs" +cifmw_ocp_agent_installer_config_assets_dir: "{{ cifmw_ocp_agent_installer_cluster_custom_config_dir }}/config-assets" + +cifmw_ocp_agent_installer_boot_artifacts_dir: /var/www/html/boot-artifacts + +cifmw_ocp_agent_installer_install_config: +cifmw_ocp_agent_installer_agent_config: +cifmw_ocp_agent_installer_pull_secret: + +cifmw_ocp_agent_installer_cinder_volume_pvs: [] +cifmw_ocp_agent_installer_cinder_volume_roles: + - master +cifmw_ocp_agent_installer_enable_multipath: false +cifmw_ocp_agent_installer_multipath_roles: + - master +cifmw_ocp_agent_installer_enable_iscsi: false +cifmw_ocp_agent_installer_iscsi_roles: + - master +cifmw_ocp_agent_installer_disable_net_ifnames: true +cifmw_ocp_agent_installer_net_ifnames_roles: + - master + +# OVN Configuration +cifmw_ocp_agent_installer_enable_ovn_k8s_overrides: true +cifmw_ocp_agent_installer_ovn_k8s_gateway_config_ip_forwarding: true +cifmw_ocp_agent_installer_ovn_k8s_gateway_config_host_routing: false + +# Etcd - set to true for controlPlaneHardwareSpeed = Slower +# https://www.redhat.com/en/blog/introducing-selectable-profiles-for-etcd +cifmw_ocp_agent_installer_enable_etcd_hardware_speed_slow: true + +cifmw_ocp_agent_installer_enable_image_content_source_policy: false +# To enable ImageContentSourcePolicy (ICSP), set this variable to contain the value +# for the ICSP spec's repositoryDigestMirrors field. +cifmw_ocp_agent_installer_image_content_source_policy_mirrors: [] + +cifmw_ocp_agent_installer_enable_additional_trusted_ca: false +cifmw_ocp_agent_installer_ocp_additional_trusted_ca: + - name: registry-proxy.engineering.redhat.com + url: https://url.corp.redhat.com/hotstack-ca diff --git a/roles/ocp_agent_installer/files/additional-trusted-ca-config-image.yaml b/roles/ocp_agent_installer/files/additional-trusted-ca-config-image.yaml new file mode 100644 index 0000000000..bced9aa42e --- /dev/null +++ b/roles/ocp_agent_installer/files/additional-trusted-ca-config-image.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: config.openshift.io/v1 +kind: Image +metadata: + name: cluster +spec: + additionalTrustedCA: + name: hotstack-additional-trusted-ca diff --git a/roles/ocp_agent_installer/files/etcd-config.yaml b/roles/ocp_agent_installer/files/etcd-config.yaml new file mode 100644 index 0000000000..30447e9b52 --- /dev/null +++ b/roles/ocp_agent_installer/files/etcd-config.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: operator.openshift.io/v1 +kind: Etcd +metadata: + name: cluster +spec: + controlPlaneHardwareSpeed: Slower + managementState: Managed diff --git a/roles/ocp_agent_installer/meta/main.yml b/roles/ocp_agent_installer/meta/main.yml new file mode 100644 index 0000000000..ae5261d4ab --- /dev/null +++ b/roles/ocp_agent_installer/meta/main.yml @@ -0,0 +1,15 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# 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. diff --git a/roles/ocp_agent_installer/tasks/additional_ca.yml b/roles/ocp_agent_installer/tasks/additional_ca.yml new file mode 100644 index 0000000000..614c8d5910 --- /dev/null +++ b/roles/ocp_agent_installer/tasks/additional_ca.yml @@ -0,0 +1,53 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# 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: Assert + ansible.builtin.assert: + that: + - ca.name is defined + - ca.name | length > 0 + - ca.url is defined or ca.data is defined + - ( + ca.url | length > 0 or + ca.data | length > 0 + ) + +- name: Download the CA bundle if url + when: ca.url is defined + ansible.builtin.uri: + url: "{{ ca.url }}" + method: get + return_content: true + validate_certs: false + register: __get_ca_from_url_result + ignore_errors: true + +- name: Append to _ocp_additional_trusted_ca_map + when: ( + ca.data is defined or + ( + ca.url is defined and + not __get_ca_from_url_result.failed + ) + ) + ansible.builtin.set_fact: + _ocp_additional_trusted_ca_map: >- + {{ + _ocp_additional_trusted_ca_map | + combine( + {ca.name: ca.data | default(__get_ca_from_url_result.content)} + ) + }} diff --git a/roles/ocp_agent_installer/tasks/config_assets.yml b/roles/ocp_agent_installer/tasks/config_assets.yml new file mode 100644 index 0000000000..f53b246845 --- /dev/null +++ b/roles/ocp_agent_installer/tasks/config_assets.yml @@ -0,0 +1,94 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# 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: Template ovn-k8s customization + when: cifmw_ocp_agent_installer_enable_ovn_k8s_overrides | bool + ansible.builtin.template: + src: ovn-k8s-config.j2 + dest: >- + {{ + [ + cifmw_ocp_agent_installer_config_assets_dir, + 'ovn_k8s_config.yaml' + ] | ansible.builtin.path_join + }} + mode: '0644' + +- name: Copy Etcd customization + when: cifmw_ocp_agent_installer_enable_etcd_hardware_speed_slow | bool + ansible.builtin.copy: + src: etcd-config.yaml + dest: >- + {{ + [ + cifmw_ocp_agent_installer_config_assets_dir, + '95-etcd_config.yaml' + ] | ansible.builtin.path_join + }} + mode: '0644' + +- name: Template ImageContentSourcePolicy customization + when: cifmw_ocp_agent_installer_enable_image_content_source_policy | bool + ansible.builtin.template: + src: image-content-source-policy.yaml.j2 + dest: >- + {{ + [ + cifmw_ocp_agent_installer_config_assets_dir, + '95-image-content-source-policy.yaml' + ] | ansible.builtin.path_join + }} + mode: '0644' + +- name: Additional trusted CA + when: + - cifmw_ocp_agent_installer_enable_additional_trusted_ca | bool + - cifmw_ocp_agent_installer_ocp_additional_trusted_ca is defined + - cifmw_ocp_agent_installer_ocp_additional_trusted_ca | length > 0 + block: + - name: Initialize _ocp_additional_trusted_ca_map fact + ansible.builtin.set_fact: + _ocp_additional_trusted_ca_map: {} + + - name: Append to _ocp_additional_trusted_ca_map fact + ansible.builtin.include_tasks: additional_ca.yml + loop: "{{ cifmw_ocp_agent_installer_ocp_additional_trusted_ca }}" + loop_control: + loop_var: ca + + - name: Template additional CA config map + ansible.builtin.template: + src: additional-trusted-ca-config-map.yaml.j2 + dest: >- + {{ + [ + cifmw_ocp_agent_installer_config_assets_dir, + '93-additional-ca-config-map.yaml' + ] | ansible.builtin.path_join + }} + mode: '0644' + + - name: Copy additional CA config image + ansible.builtin.copy: + src: additional-trusted-ca-config-image.yaml + dest: >- + {{ + [ + cifmw_ocp_agent_installer_config_assets_dir, + '94-additional-ca-config-image.yaml' + ] | ansible.builtin.path_join + }} + mode: '0644' diff --git a/roles/ocp_agent_installer/tasks/ingress_cert.yml b/roles/ocp_agent_installer/tasks/ingress_cert.yml new file mode 100644 index 0000000000..0d7f081693 --- /dev/null +++ b/roles/ocp_agent_installer/tasks/ingress_cert.yml @@ -0,0 +1,39 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# 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: Extract ingress CA cert + register: _ingress_cert + ansible.builtin.shell: | + POD_NAME=$({{ cifmw_ocp_agent_installer_bin_dir }}/oc get pods -n openshift-authentication -o jsonpath='{.items[0].metadata.name}') + {{ cifmw_ocp_agent_installer_bin_dir }}/oc rsh -n openshift-authentication $POD_NAME \ + cat /run/secrets/kubernetes.io/serviceaccount/ca.crt + retries: 10 + delay: 10 + until: _ingress_cert.rc == 0 + +- name: Write ingress cert to ca-trust + become: true + ansible.builtin.copy: + content: "{{ _ingress_cert.stdout }}" + dest: /etc/pki/ca-trust/source/anchors/ingress-ca.crt + owner: root + group: root + mode: '0644' + +- name: Update CA trust + become: true + ansible.builtin.command: + cmd: update-ca-trust diff --git a/roles/ocp_agent_installer/tasks/install_client.yml b/roles/ocp_agent_installer/tasks/install_client.yml new file mode 100644 index 0000000000..1cf3c7f5fc --- /dev/null +++ b/roles/ocp_agent_installer/tasks/install_client.yml @@ -0,0 +1,47 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# 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: Download the client + ansible.builtin.get_url: + url: "{{ cifmw_ocp_agent_installer_client_url }}" + dest: >- + {{ + [ + cifmw_ocp_agent_installer_agent_installer_dir, + 'openshift-client-linux.tar.gz' + ] | ansible.builtin.path_join + }} + mode: '0644' + +- name: Extract client to /home/zuul/bin + ansible.builtin.unarchive: + src: >- + {{ + [ + cifmw_ocp_agent_installer_agent_installer_dir, + 'openshift-client-linux.tar.gz' + ] | ansible.builtin.path_join + }} + dest: "{{ cifmw_ocp_agent_installer_bin_dir }}" + remote_src: true + creates: "{{ cifmw_ocp_agent_installer_bin_dir }}/oc" + +- name: Configure bash completion + become: true + ansible.builtin.shell: | + {{ cifmw_ocp_agent_installer_bin_dir }}/oc completion bash > /etc/bash_completion.d/oc_bash_completion + args: + creates: /etc/bash_completion.d/oc_bash_completion diff --git a/roles/ocp_agent_installer/tasks/install_installer.yml b/roles/ocp_agent_installer/tasks/install_installer.yml new file mode 100644 index 0000000000..63b30f32df --- /dev/null +++ b/roles/ocp_agent_installer/tasks/install_installer.yml @@ -0,0 +1,40 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# 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: Download the installer + ansible.builtin.get_url: + url: "{{ cifmw_ocp_agent_installer_installer_url }}" + dest: >- + {{ + [ + cifmw_ocp_agent_installer_agent_installer_dir, + 'openshift-install-linux.tar.gz' + ] | ansible.builtin.path_join + }} + mode: '0644' + +- name: Extract installer to /home/zuul/bin + ansible.builtin.unarchive: + src: >- + {{ + [ + cifmw_ocp_agent_installer_agent_installer_dir, + 'openshift-install-linux.tar.gz' + ] | ansible.builtin.path_join + }} + dest: "{{ cifmw_ocp_agent_installer_bin_dir }}" + remote_src: true + creates: "{{ cifmw_ocp_agent_installer_bin_dir }}/openshift-install" diff --git a/roles/ocp_agent_installer/tasks/iso_assets.yml b/roles/ocp_agent_installer/tasks/iso_assets.yml new file mode 100644 index 0000000000..99e39a342c --- /dev/null +++ b/roles/ocp_agent_installer/tasks/iso_assets.yml @@ -0,0 +1,19 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# 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: Fail - not implemented + ansible.builtin.fail: + msg: "Assisted Installer with ISO assets has not been implemented in ocp_agent_installer" diff --git a/roles/ocp_agent_installer/tasks/machine_configs.yml b/roles/ocp_agent_installer/tasks/machine_configs.yml new file mode 100644 index 0000000000..decfaf926f --- /dev/null +++ b/roles/ocp_agent_installer/tasks/machine_configs.yml @@ -0,0 +1,90 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# 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: Disable net.ifnames + when: cifmw_ocp_agent_installer_disable_net_ifnames | bool + block: + - name: Template butane config for net.ifnames + vars: + role: "{{ item }}" + ansible.builtin.template: + src: disable-netifnames.bu.j2 + dest: "{{ cifmw_ocp_agent_installer_butane_dir }}/90-{{ item }}-disable-netifnames.bu" + mode: '0644' + loop: "{{ cifmw_ocp_agent_installer_net_ifnames_roles }}" + - name: Generate MachineConfig for iscsi + ansible.builtin.command: + cmd: >- + butane {{ cifmw_ocp_agent_installer_butane_dir }}/90-{{ item }}-disable-netifnames.bu + -o {{ cifmw_ocp_agent_installer_machine_configs_dir }}/90-{{ item }}-disable-netifnames.yaml + loop: "{{ cifmw_ocp_agent_installer_net_ifnames_roles }}" + +- name: Enable iscsi + when: cifmw_ocp_agent_installer_enable_iscsi | bool + block: + - name: Template butane config for iscsi + vars: + role: "{{ item }}" + ansible.builtin.template: + src: enable-iscsi.bu.j2 + dest: "{{ cifmw_ocp_agent_installer_butane_dir }}/90-{{ item }}-enable-iscsi.bu" + mode: '0644' + loop: "{{ cifmw_ocp_agent_installer_iscsi_roles }}" + + - name: Generate MachineConfig for iscsi + ansible.builtin.command: + cmd: >- + butane {{ cifmw_ocp_agent_installer_butane_dir }}/90-{{ item }}-enable-iscsi.bu + -o {{ cifmw_ocp_agent_installer_machine_configs_dir }}/90-{{ item }}-enable-iscsi.yaml + loop: "{{ cifmw_ocp_agent_installer_iscsi_roles }}" + +- name: Enable Multipath + when: cifmw_ocp_agent_installer_enable_multipath | bool + block: + - name: Template butane config for multipath + vars: + role: "{{ item }}" + ansible.builtin.template: + src: enable-multipath.bu.j2 + dest: "{{ cifmw_ocp_agent_installer_butane_dir }}/91-{{ item }}-enable-multipath.bu" + mode: '0644' + loop: "{{ cifmw_ocp_agent_installer_multipath_roles }}" + + - name: Generate MachineConfig for multipath + ansible.builtin.command: + cmd: >- + butane {{ cifmw_ocp_agent_installer_butane_dir }}/91-{{ item }}-enable-multipath.bu + -o {{ cifmw_ocp_agent_installer_machine_configs_dir }}/91-{{ item }}-enable-multipath.yaml + loop: "{{ cifmw_ocp_agent_installer_multipath_roles }}" + +- name: LVM cinder-volumes + when: cifmw_ocp_agent_installer_cinder_volume_pvs | length > 0 + block: + - name: Template butane config for LVM cinder-volumes + vars: + role: "{{ item }}" + ansible.builtin.template: + src: lv-cinder-volumes.bu.j2 + dest: "{{ cifmw_ocp_agent_installer_butane_dir }}/92-{{ item }}-lv-cinder-volumes.bu" + mode: '0644' + loop: "{{ cifmw_ocp_agent_installer_cinder_volume_roles }}" + + - name: Generate MachineConfig for multipath + ansible.builtin.command: + cmd: >- + butane {{ cifmw_ocp_agent_installer_butane_dir }}/92-{{ item }}-lv-cinder-volumes.bu + -o {{ cifmw_ocp_agent_installer_machine_configs_dir }}/92-{{ item }}-lv-cinder-volumes.yaml + loop: "{{ cifmw_ocp_agent_installer_cinder_volume_roles }}" diff --git a/roles/ocp_agent_installer/tasks/main.yml b/roles/ocp_agent_installer/tasks/main.yml new file mode 100644 index 0000000000..26787dd761 --- /dev/null +++ b/roles/ocp_agent_installer/tasks/main.yml @@ -0,0 +1,162 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# 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: Assert config is defined + ansible.builtin.assert: + that: + - cifmw_ocp_agent_installer_pull_secret is defined + - cifmw_ocp_agent_installer_pull_secret | length > 0 + - cifmw_ocp_agent_installer_install_config is defined + - cifmw_ocp_agent_installer_install_config | length > 0 + - cifmw_ocp_agent_installer_agent_config is defined + - cifmw_ocp_agent_installer_agent_config | length > 0 + +- name: Include client install tasks + ansible.builtin.include_tasks: install_client.yml + +- name: Include installer install tasks + ansible.builtin.include_tasks: install_installer.yml + +- name: Include config asset tasks + ansible.builtin.include_tasks: config_assets.yml + +- name: Include machine config tasks + ansible.builtin.include_tasks: machine_configs.yml + +- name: Include additional ca tasks + ansible.builtin.include_tasks: additional_ca.yml + +- name: Install package requirements for agent installer + become: true + ansible.builtin.dnf: + name: + - nmstate + - butane + state: present + +- name: Create the cluster directories + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: "{{ ansible_user | default(ansible_user_id) }}" + group: "{{ ansible_user | default(ansible_user_id) }}" + mode: '0755' + loop: + - "{{ cifmw_ocp_agent_installer_bin_dir }}" + - "{{ cifmw_ocp_agent_installer_kube_config_dir }}" + - "{{ cifmw_ocp_agent_installer_cluster_dir }}" + - "{{ cifmw_ocp_agent_installer_manifests_dir }}" + - "{{ cifmw_ocp_agent_installer_agent_installer_dir }}" + - "{{ cifmw_ocp_agent_installer_cluster_custom_config_dir }}" + - "{{ cifmw_ocp_agent_installer_butane_dir }}" + - "{{ cifmw_ocp_agent_installer_machine_configs_dir }}" + - "{{ cifmw_ocp_agent_installer_config_assets_dir }}" + +- name: Generate install-config.yaml file + ansible.builtin.copy: + content: | + apiVersion: v1 + baseDomain: {{ cifmw_ocp_agent_installer_install_config.baseDomain }} + compute: + {{ cifmw_ocp_agent_installer_install_config.compute | to_nice_yaml(indent=2) | indent(width=2) }} + controlPlane: + {{ cifmw_ocp_agent_installer_install_config.controlPlane | to_nice_yaml(indent=2) | indent(width=2) }} + metadata: + name: {{ cifmw_ocp_agent_installer_install_config.metadata.name }} + networking: + {{ cifmw_ocp_agent_installer_install_config.networking | to_nice_yaml(indent=2) | indent(width=2) }} + platform: + {{ cifmw_ocp_agent_installer_install_config.platform | to_nice_yaml(indent=2) | indent(width=2) }} + pullSecret: '{{ cifmw_ocp_agent_installer_pull_secret | b64decode }}' + sshKey: '{{ cifmw_ocp_agent_installer_install_config.sshKey }}' + dest: "{{ cifmw_ocp_agent_installer_cluster_dir }}/install-config.yaml" + owner: "{{ ansible_user | default(ansible_user_id) }}" + group: "{{ ansible_user | default(ansible_user_id) }}" + mode: '0600' + +- name: Generate agent-config.yaml file + ansible.builtin.copy: + content: | + apiVersion: v1alpha1 + kind: AgentConfig + metadata: + name: {{ cifmw_ocp_agent_installer_agent_config.metadata.name }} + {{ cifmw_ocp_agent_installer_agent_config | to_nice_yaml(indent=2) | indent(width=2) }} + dest: "{{ cifmw_ocp_agent_installer_cluster_dir }}/agent-config.yaml" + owner: "{{ ansible_user | default(ansible_user_id) }}" + group: "{{ ansible_user | default(ansible_user_id) }}" + mode: '0600' + +- name: Generate custom manifests + ansible.builtin.shell: | + cd {{ cifmw_ocp_agent_installer_cluster_dir }} + {{ cifmw_ocp_agent_installer_bin_dir }}/openshift-install create manifests + +- name: Copy machine_configs to manifests dir + ansible.builtin.copy: + remote_src: true + src: "{{ cifmw_ocp_agent_installer_machine_configs_dir }}/" + dest: "{{ cifmw_ocp_agent_installer_manifests_dir }}/" + mode: '0644' + +- name: Copy config assets to manifests dir + ansible.builtin.copy: + remote_src: true + src: "{{ cifmw_ocp_agent_installer_config_assets_dir }}/" + dest: "{{ cifmw_ocp_agent_installer_manifests_dir }}/" + mode: '0644' + +- name: Include pxe assets tasks + ansible.builtin.include_tasks: pxe_assets.yml + when: cifmw_ocp_agent_installer_bootstrap_assets == 'pxe' + +- name: Include iso assets tasks + ansible.builtin.include_tasks: iso_assets.yml + when: cifmw_ocp_agent_installer_bootstrap_assets == 'iso' + +- name: Copy auth/kubeconfig to ~/.kube/config + ansible.builtin.copy: + remote_src: true + src: >- + {{ + [ + cifmw_ocp_agent_installer_cluster_dir, + 'auth', + 'kubeconfig' + ] | ansible.builtin.path_join + }} + dest: >- + {{ + [ + cifmw_ocp_agent_installer_kube_config_dir, + 'config' + ] | ansible.builtin.path_join + }} + mode: '0600' + +- name: Wait for bootstrap-complete + ansible.builtin.command: + cmd: > + {{ cifmw_ocp_agent_installer_bin_dir }}/openshift-install --dir {{ cifmw_ocp_agent_installer_cluster_dir }} agent wait-for bootstrap-complete --log-level=info + +- name: Wait for install-complete + ansible.builtin.command: + cmd: > + {{ cifmw_ocp_agent_installer_bin_dir }}/openshift-install --dir {{ cifmw_ocp_agent_installer_cluster_dir }} agent wait-for install-complete + +- name: Add ingress certificate to CA trust + when: cifmw_ocp_agent_installer_add_ingress_cert_to_ca_trust | bool + ansible.builtin.include_tasks: ingress_cert.yml diff --git a/roles/ocp_agent_installer/tasks/pxe_assets.yml b/roles/ocp_agent_installer/tasks/pxe_assets.yml new file mode 100644 index 0000000000..0e65c7c751 --- /dev/null +++ b/roles/ocp_agent_installer/tasks/pxe_assets.yml @@ -0,0 +1,66 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# 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 the boot-artifacts directory + become: true + ansible.builtin.file: + path: "{{ cifmw_ocp_agent_installer_boot_artifacts_dir }}" + state: directory + mode: '0755' + +- name: Create PXE assets + ansible.builtin.command: + chdir: "{{ cifmw_ocp_agent_installer_cluster_dir }}" + cmd: > + {{ cifmw_ocp_agent_installer_bin_dir }}/openshift-install agent create pxe-files + +- name: Set serial console in ipxe + ansible.builtin.lineinfile: + path: "{{ cifmw_ocp_agent_installer_cluster_dir }}/boot-artifacts/agent.x86_64.ipxe" + backrefs: true + regexp: "^(kernel.*)$" + line: '\1 console=ttyS0' + +- name: Disable net.ifnames + when: cifmw_ocp_agent_installer_disable_net_ifnames | bool + ansible.builtin.lineinfile: + path: "{{ cifmw_ocp_agent_installer_cluster_dir }}/boot-artifacts/agent.x86_64.ipxe" + backrefs: true + regexp: "^(kernel.*)$" + line: '\1 net.ifnames=0' + +- name: Copy boot-artifacts to the web server - (cifmw_ocp_agent_installer_boot_artifacts_dir) + become: true + ansible.builtin.copy: + remote_src: true + src: >- + {{ + [ + cifmw_ocp_agent_installer_cluster_dir, + 'boot-artifacts', + item + ] | ansible.builtin.path_join + }} + dest: "{{ cifmw_ocp_agent_installer_boot_artifacts_dir }}" + owner: root + group: root + mode: '0644' + loop: + - agent.x86_64-vmlinuz + - agent.x86_64-initrd.img + - agent.x86_64-rootfs.img + # Copy the iPXE last, the other files should be ready before iPXE try to load them. + - agent.x86_64.ipxe diff --git a/roles/ocp_agent_installer/templates/additional-trusted-ca-config-map.yaml.j2 b/roles/ocp_agent_installer/templates/additional-trusted-ca-config-map.yaml.j2 new file mode 100644 index 0000000000..3f249a8738 --- /dev/null +++ b/roles/ocp_agent_installer/templates/additional-trusted-ca-config-map.yaml.j2 @@ -0,0 +1,11 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: openshift-config + name: hotstack-additional-trusted-ca +data: +{% for ca in cifmw_ocp_agent_installer_ocp_additional_trusted_ca %} + {{ ca.name }}: | + {{ _ocp_additional_trusted_ca_map[ca.name] | indent(width=4) }} +{% endfor %} diff --git a/roles/ocp_agent_installer/templates/disable-netifnames.bu.j2 b/roles/ocp_agent_installer/templates/disable-netifnames.bu.j2 new file mode 100644 index 0000000000..9a220be44d --- /dev/null +++ b/roles/ocp_agent_installer/templates/disable-netifnames.bu.j2 @@ -0,0 +1,10 @@ +--- +variant: openshift +version: 4.17.0 +metadata: + name: 90-{{ role | default('master') }}-netifnames-conf + labels: + machineconfiguration.openshift.io/role: {{ role | default('master') }} +openshift: + kernel_arguments: + - net.ifnames=0 diff --git a/roles/ocp_agent_installer/templates/enable-iscsi.bu.j2 b/roles/ocp_agent_installer/templates/enable-iscsi.bu.j2 new file mode 100644 index 0000000000..2dd0c993f5 --- /dev/null +++ b/roles/ocp_agent_installer/templates/enable-iscsi.bu.j2 @@ -0,0 +1,32 @@ +--- +variant: openshift +version: 4.17.0 +metadata: + name: 90-{{ role | default('master') }}-nable-iscsi + labels: + machineconfiguration.openshift.io/role: {{ role | default('master') }} +storage: + files: + - path: /etc/iscsi/iscsid.conf + overwrite: true + mode: 384 + user: + name: root + group: + name: root + contents: + inline: | + # Default to 3 retries and 5 seconds each (15 seconds in total), + # which is convenient for testing, as any healthy deployment and + # backend should be able to login to the backend in that amount of + # time, and if there is a broken path it will not take 2 minutes to + # give up, just around 15 seconds. + node.session.initial_login_retry_max = 3 + node.conn[0].timeo.login_timeout = 5 + # The default CHAP algorithms include MD5 will does not work under + # FIPS. Set this parameter to exclude MD5 and SHA-1. + node.session.auth.chap_algs = SHA3-256,SHA256 +systemd: + units: + - enabled: true + name: iscsid.service diff --git a/roles/ocp_agent_installer/templates/enable-multipath.bu.j2 b/roles/ocp_agent_installer/templates/enable-multipath.bu.j2 new file mode 100644 index 0000000000..374370f1f5 --- /dev/null +++ b/roles/ocp_agent_installer/templates/enable-multipath.bu.j2 @@ -0,0 +1,30 @@ +--- +variant: openshift +version: 4.17.0 +metadata: + name: 91-{{ role | default('master') }}-multipath-conf + labels: + machineconfiguration.openshift.io/role: {{ role | default('master') }} +storage: + files: + - path: /etc/multipath.conf + overwrite: false + mode: 344 + user: + name: root + group: + name: root + contents: + inline: | + defaults { + user_friendly_names no + recheck_wwid yes + skip_kpartx yes + find_multipaths yes + } + blacklist { + } +systemd: + units: + - enabled: true + name: multipathd.service diff --git a/roles/ocp_agent_installer/templates/image-content-source-policy.yaml.j2 b/roles/ocp_agent_installer/templates/image-content-source-policy.yaml.j2 new file mode 100644 index 0000000000..642bab9cad --- /dev/null +++ b/roles/ocp_agent_installer/templates/image-content-source-policy.yaml.j2 @@ -0,0 +1,8 @@ +--- +apiVersion: operator.openshift.io/v1alpha1 +kind: ImageContentSourcePolicy +metadata: + name: brew-registry +spec: + repositoryDigestMirrors: + {{ cifmw_ocp_agent_installer_image_content_source_policy_mirrors | to_nice_yaml(indent=2) | indent(width=4) }} diff --git a/roles/ocp_agent_installer/templates/lv-cinder-volumes.bu.j2 b/roles/ocp_agent_installer/templates/lv-cinder-volumes.bu.j2 new file mode 100644 index 0000000000..66380dae82 --- /dev/null +++ b/roles/ocp_agent_installer/templates/lv-cinder-volumes.bu.j2 @@ -0,0 +1,31 @@ +--- +variant: openshift +version: 4.17.0 +metadata: + name: 91-{{ role | default('master') }}-lv-cinder-volumes + labels: + machineconfiguration.openshift.io/role: {{ role | default('master') }} +storage: + disks: +{% for _disk in cifmw_ocp_agent_installer_cinder_volume_pvs %} + - device: {{ _disk }} + wipe_table: true +{% endfor %} +systemd: + units: + - name: lv-cinder-volumes.service + enabled: true + contents: | + [Unit] + Description=Create logical volume with name cinder-volumes. + After=var.mount systemd-udev-settle.service + + [Service] + Type=oneshot + ExecCondition=/bin/bash -c '! /usr/sbin/vgdisplay cinder-volumes' + ExecStartPre=/usr/sbin/pvcreate {{ cifmw_ocp_agent_installer_cinder_volume_pvs | join(' ') }} +ExecStart=/usr/sbin/vgcreate cinder-volumes {{ cifmw_ocp_agent_installer_cinder_volume_pvs | join(' ') }} + RemainAfterExit=yes + + [Install] + WantedBy=multi-user.target diff --git a/roles/ocp_agent_installer/templates/ovn-k8s-config.j2 b/roles/ocp_agent_installer/templates/ovn-k8s-config.j2 new file mode 100644 index 0000000000..8070d1b6aa --- /dev/null +++ b/roles/ocp_agent_installer/templates/ovn-k8s-config.j2 @@ -0,0 +1,13 @@ +--- +apiVersion: operator.openshift.io/v1 +kind: Network +metadata: + name: cluster +spec: + defaultNetwork: + ovnKubernetesConfig: + gatewayConfig: +{% if cifmw_ocp_agent_installer_ovn_k8s_gateway_config_ip_forwarding %} + ipForwarding: Global +{% endif %} + routingViaHost: {{ cifmw_ocp_agent_installer_ovn_k8s_gateway_config_host_routing }} From 1523de93f6e6e12624b0fe44fecdf609c06df81a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Jens=C3=A5s?= Date: Mon, 13 Oct 2025 22:54:08 +0200 Subject: [PATCH 2/2] Add DHCP options support for VM types Enable per-VM-type DHCP options in libvirt_manager for PXE boot scenarios. VMs are tagged by type and dnsmasq applies corresponding options to each group via dhcp_options field in VM definitions. Assisted-By: Claude Code/claude-sonnet-4 --- docs/dictionary/en-custom.txt | 1 + roles/dnsmasq/README.md | 14 ++ roles/dnsmasq/molecule/default/converge.yml | 119 +++++++++++++ roles/dnsmasq/tasks/manage_host.yml | 6 +- roles/libvirt_manager/DHCP_OPTIONS_EXAMPLE.md | 161 ++++++++++++++++++ roles/libvirt_manager/README.md | 1 + .../generate_network_data/tasks/test.yml | 72 ++++++++ .../generate_network_data/vars/scenarios.yml | 27 +++ .../tasks/create_dhcp_options.yml | 46 +++++ .../tasks/generate_networking_data.yml | 3 + .../tasks/reserve_dnsmasq_ips.yml | 2 + .../templates/vm-types-dhcp-options.conf.j2 | 8 + zuul.d/projects.yaml | 109 ------------ 13 files changed, 459 insertions(+), 110 deletions(-) create mode 100644 roles/libvirt_manager/DHCP_OPTIONS_EXAMPLE.md create mode 100644 roles/libvirt_manager/tasks/create_dhcp_options.yml create mode 100644 roles/libvirt_manager/templates/vm-types-dhcp-options.conf.j2 diff --git a/docs/dictionary/en-custom.txt b/docs/dictionary/en-custom.txt index 547e6b3346..4706600ccf 100644 --- a/docs/dictionary/en-custom.txt +++ b/docs/dictionary/en-custom.txt @@ -178,6 +178,7 @@ env envfile epel epyc +etcd eth extraimages extraRPMs diff --git a/roles/dnsmasq/README.md b/roles/dnsmasq/README.md index 972e243550..602baf2d2a 100644 --- a/roles/dnsmasq/README.md +++ b/roles/dnsmasq/README.md @@ -168,6 +168,7 @@ supported in libvirt). * `mac`: (String) Entry MAC address. Mandatory. * `ips`: (List[string]) List of IP addresses associated to the MAC (v4, v6). Mandatory. * `name`: (String) Host name. Optional. +* `tag`: (String) Tag to assign to this host. Tags can be used to apply specific DHCP options to groups of hosts. Optional. #### Examples @@ -182,7 +183,20 @@ supported in libvirt). - "2345:0425:2CA1::0567:5673:cafe" - "192.168.254.11" name: r2d2 + tag: droid # Optional: assign tag for DHCP options ansible.builtin.include_role: name: dnsmasq tasks_from: manage_host.yml ``` + +#### Using tags for DHCP options + +When you assign a `tag` to DHCP entries, you can then configure DHCP options for that tag: + +``` +# In /etc/cifmw-dnsmasq.d/custom-options.conf +dhcp-option=tag:droid,60,HTTPClient +dhcp-option=tag:droid,67,http://192.168.254.1/boot.ipxe +``` + +All hosts with the `droid` tag will receive these DHCP options. diff --git a/roles/dnsmasq/molecule/default/converge.yml b/roles/dnsmasq/molecule/default/converge.yml index 2b5e24cecd..914af886e8 100644 --- a/roles/dnsmasq/molecule/default/converge.yml +++ b/roles/dnsmasq/molecule/default/converge.yml @@ -145,6 +145,125 @@ name: dnsmasq tasks_from: manage_host.yml + - name: Inject nodes with tags for DHCP options + vars: + cifmw_dnsmasq_dhcp_entries: + - network: starwars + state: present + mac: "0a:19:02:f8:4c:b1" + ips: + - "192.168.254.21" + - "2345:0425:2CA1::0567:5673:0021" + name: "r2d2" + tag: "droid" + - network: starwars + state: present + mac: "0a:19:02:f8:4c:b2" + ips: + - "192.168.254.22" + name: "c3po" + tag: "droid" + - network: startrek + state: present + mac: "0a:19:02:f8:4c:b3" + ips: + - "192.168.253.31" + name: "data" + tag: "android" + ansible.builtin.include_role: + name: dnsmasq + tasks_from: manage_host.yml + + - name: Verify DHCP host entries with tags + block: + - name: Read r2d2 DHCP host entry + become: true + ansible.builtin.slurp: + path: "/etc/cifmw-dnsmasq.d/dhcp-hosts.d/starwars_r2d2_0a:19:02:f8:4c:b1" + register: _r2d2_entry + + - name: Read c3po DHCP host entry + become: true + ansible.builtin.slurp: + path: "/etc/cifmw-dnsmasq.d/dhcp-hosts.d/starwars_c3po_0a:19:02:f8:4c:b2" + register: _c3po_entry + + - name: Read data DHCP host entry + become: true + ansible.builtin.slurp: + path: "/etc/cifmw-dnsmasq.d/dhcp-hosts.d/startrek_data_0a:19:02:f8:4c:b3" + register: _data_entry + + - name: Decode entries + ansible.builtin.set_fact: + _r2d2_content: "{{ _r2d2_entry.content | b64decode | trim }}" + _c3po_content: "{{ _c3po_entry.content | b64decode | trim }}" + _data_content: "{{ _data_entry.content | b64decode | trim }}" + + - name: Assert r2d2 entry has droid tag + ansible.builtin.assert: + that: + - "'set:droid' in _r2d2_content" + - "'0a:19:02:f8:4c:b1' in _r2d2_content" + - "'192.168.254.21' in _r2d2_content" + - "'r2d2' in _r2d2_content" + msg: "r2d2 DHCP entry should contain tag 'droid': {{ _r2d2_content }}" + + - name: Assert c3po entry has droid tag + ansible.builtin.assert: + that: + - "'set:droid' in _c3po_content" + - "'0a:19:02:f8:4c:b2' in _c3po_content" + - "'192.168.254.22' in _c3po_content" + - "'c3po' in _c3po_content" + msg: "c3po DHCP entry should contain tag 'droid': {{ _c3po_content }}" + + - name: Assert data entry has android tag + ansible.builtin.assert: + that: + - "'set:android' in _data_content" + - "'0a:19:02:f8:4c:b3' in _data_content" + - "'192.168.253.31' in _data_content" + - "'data' in _data_content" + msg: "data DHCP entry should contain tag 'android': {{ _data_content }}" + + - name: "Verify entry without tag has no set: prefix" + become: true + ansible.builtin.slurp: + path: "/etc/cifmw-dnsmasq.d/dhcp-hosts.d/starwars_solo_0a:19:02:f8:4c:a8" + register: _solo_entry + + - name: "Assert solo entry does not have a tag" + vars: + _solo_content: "{{ _solo_entry.content | b64decode | trim }}" + ansible.builtin.assert: + that: + - "'set:' not in _solo_content" + - "'0a:19:02:f8:4c:a8' in _solo_content" + - "'solo' in _solo_content" + msg: "solo DHCP entry should not contain any tag: {{ _solo_content }}" + + - name: "Create DHCP options configuration for tagged hosts" + become: true + ansible.builtin.copy: + dest: "/etc/cifmw-dnsmasq.d/test-dhcp-options.conf" + content: | + # Test DHCP options for droids + dhcp-option=tag:droid,60,HTTPClient + dhcp-option=tag:droid,67,http://192.168.254.1/droid-boot.ipxe + # Test DHCP options for androids + dhcp-option=tag:android,60,HTTPClient + dhcp-option=tag:android,67,http://192.168.253.1/android-boot.ipxe + mode: '0644' + validate: "/usr/sbin/dnsmasq -C %s --test" + notify: Restart dnsmasq + + - name: Verify dnsmasq configuration is valid + become: true + ansible.builtin.command: + cmd: /usr/sbin/dnsmasq -C /etc/cifmw-dnsmasq.conf --test + changed_when: false + - name: Add a domain specific forwarder vars: cifmw_dnsmasq_forwarder: diff --git a/roles/dnsmasq/tasks/manage_host.yml b/roles/dnsmasq/tasks/manage_host.yml index 30666e5678..73a5778853 100644 --- a/roles/dnsmasq/tasks/manage_host.yml +++ b/roles/dnsmasq/tasks/manage_host.yml @@ -62,7 +62,11 @@ {%- set _ = data.append(entry.mac) -%} {{ data | join('_') }} _entry: >- - {% set data = [entry.mac] -%} + {% set data = [] -%} + {% if entry.tag is defined and entry.tag | length > 0 -%} + {% set _ = data.append('set:' + entry.tag) -%} + {% endif -%} + {% set _ = data.append(entry.mac) -%} {% for ip in entry.ips if ip is not none and ip | length > 0 -%} {% set _ = data.append(ip | ansible.utils.ipwrap) -%} {% endfor -%} diff --git a/roles/libvirt_manager/DHCP_OPTIONS_EXAMPLE.md b/roles/libvirt_manager/DHCP_OPTIONS_EXAMPLE.md new file mode 100644 index 0000000000..3737dda578 --- /dev/null +++ b/roles/libvirt_manager/DHCP_OPTIONS_EXAMPLE.md @@ -0,0 +1,161 @@ +# DHCP Options Support in libvirt_manager + +This document explains how to add DHCP options to VM groups in the libvirt_manager role. + +## Overview + +The libvirt_manager role now supports assigning DHCP options to groups of VMs based on their type. This is useful for scenarios like PXE booting where you need to provide specific boot parameters to certain VM types. + +## How It Works + +1. **VM Type Tagging**: Each VM is automatically tagged with its type (e.g., `compute`, `controller`, `baremetal_instance`) +2. **DHCP Options**: You can specify DHCP options in the VM type definition +3. **dnsmasq Configuration**: The role automatically generates dnsmasq configuration that applies these options to all VMs of that type + +## Configuration Example + +### Basic Example + +Here's how to add DHCP options for PXE booting to baremetal instances: + +```yaml +cifmw_libvirt_manager_configuration: + vms: + baremetal_instance: + amount: 3 + disk_file_name: "blank" + disksize: 50 + memory: 8 + cpus: 4 + bootmenu_enable: "yes" + nets: + - public + - provisioning + dhcp_options: + - "60,HTTPClient" # Vendor class identifier + - "67,http://192.168.122.1:8081/boot.ipxe" # Boot filename (iPXE script) +``` + +### Advanced Example with Multiple VM Types + +```yaml +cifmw_libvirt_manager_configuration: + vms: + controller: + amount: 1 + image_url: "{{ cifmw_discovered_image_url }}" + sha256_image_name: "{{ cifmw_discovered_hash }}" + disk_file_name: "centos-stream-9.qcow2" + disksize: 50 + memory: 4 + cpus: 2 + nets: + - public + - osp_trunk + # No DHCP options for controllers - they'll use defaults + + compute: + amount: 3 + disk_file_name: blank + disksize: 40 + memory: 8 + cpus: 4 + nets: + - public + - osp_trunk + dhcp_options: + - "60,HTTPClient" + - "67,http://192.168.122.1:8081/boot-artifacts/compute-boot.ipxe" + + baremetal_instance: + amount: 2 + disk_file_name: "blank" + disksize: 50 + memory: 8 + cpus: 4 + bootmenu_enable: "yes" + nets: + - public + dhcp_options: + - "60,HTTPClient" + - "67,http://192.168.122.1:8081/boot-artifacts/agent.x86_64.ipxe" +``` + +## Common DHCP Options + +Here are some commonly used DHCP options for PXE/network booting: + +| Option | Name | Purpose | Example | +|--------|------|---------|---------| +| 60 | vendor-class-identifier | Identifies the vendor/client type | `60,HTTPClient` | +| 67 | bootfile-name | Path to boot file | `67,http://server/boot.ipxe` | +| 66 | tftp-server-name | TFTP server address | `66,192.168.1.10` | +| 150 | tftp-server-address | TFTP server IP (Cisco) | `150,192.168.1.10` | +| 210 | path-prefix | Path prefix for boot files | `210,/tftpboot/` | + + +## Technical Details + +### Under the Hood + +1. **Tag Assignment**: When VMs are created, each is assigned a tag matching its type in the dnsmasq DHCP host entry: + ``` + set:baremetal_instance,52:54:00:xx:xx:xx,192.168.122.10,hostname + ``` + +2. **DHCP Options Configuration**: A configuration file is generated at `/etc/cifmw-dnsmasq.d/vm-types-dhcp-options.conf`: + ``` + # Options for baremetal_instance VMs + dhcp-option=tag:baremetal_instance,60,HTTPClient + dhcp-option=tag:baremetal_instance,67,http://192.168.122.1:8081/boot.ipxe + ``` + +3. **dnsmasq Processing**: When a VM with the `baremetal_instance` tag requests DHCP, it receives both the standard network options AND the VM-type-specific options. + +### Files Modified + +- `roles/libvirt_manager/tasks/reserve_dnsmasq_ips.yml`: Adds VM type tags to DHCP entries +- `roles/libvirt_manager/tasks/create_dhcp_options.yml`: New file that generates DHCP options configuration +- `roles/libvirt_manager/tasks/generate_networking_data.yml`: Includes the new task +- `roles/dnsmasq/tasks/manage_host.yml`: Updated to support tags in DHCP entries + +## Troubleshooting + +### Verify DHCP Options Are Applied + +1. Check the generated configuration: + ```bash + cat /etc/cifmw-dnsmasq.d/vm-types-dhcp-options.conf + ``` + +2. Check DHCP host entries: + ```bash + ls -la /etc/cifmw-dnsmasq.d/dhcp-hosts.d/ + cat /etc/cifmw-dnsmasq.d/dhcp-hosts.d/public_* + ``` + +3. Verify dnsmasq configuration is valid: + ```bash + dnsmasq -C /etc/cifmw-dnsmasq.conf --test + ``` + +4. Monitor DHCP requests: + ```bash + journalctl -u cifmw-dnsmasq -f + ``` + +### Common Issues + +**Issue**: DHCP options not being sent to VMs +- **Solution**: Ensure dnsmasq service is restarted after making changes +- **Check**: Verify the VM type tag matches between the DHCP host entry and the options configuration + +**Issue**: VMs not PXE booting correctly +- **Solution**: Verify the boot file URL is accessible from the VM's network +- **Check**: Ensure option 67 contains the full URL including protocol (http://) + +## References + +- [dnsmasq manual](http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html) +- [DHCP Options RFC](https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml) +- [iPXE documentation](https://ipxe.org/howto/dhcpd) diff --git a/roles/libvirt_manager/README.md b/roles/libvirt_manager/README.md index a8beb7c61f..6e6e86a7fe 100644 --- a/roles/libvirt_manager/README.md +++ b/roles/libvirt_manager/README.md @@ -97,6 +97,7 @@ cifmw_libvirt_manager_configuration: bootmenu_enable: (string, toggle bootmenu. Optional, defaults to "no") networkconfig: (dict or list[dict], [network-config](https://cloudinit.readthedocs.io/en/latest/reference/network-config-format-v2.html#network-config-v2) v2 config, needed if a static ip address should be defined at boot time in absence of a dhcp server in special scenarios. Optional) devices: (dict, optional, defaults to {}. The keys are the VMs of that type that needs devices to be attached, and the values are lists of strings, where each string must contain a valid libvirt XML element that will be passed to virsh attach-device) + dhcp_options: (list, optional, defaults to []. List of DHCP options to apply to all VMs of this type. Format: ["option_number,value", ...]) networks: net_name: ``` diff --git a/roles/libvirt_manager/molecule/generate_network_data/tasks/test.yml b/roles/libvirt_manager/molecule/generate_network_data/tasks/test.yml index 04f360b08f..a867d77fe3 100644 --- a/roles/libvirt_manager/molecule/generate_network_data/tasks/test.yml +++ b/roles/libvirt_manager/molecule/generate_network_data/tasks/test.yml @@ -94,6 +94,78 @@ _run_fail: true _failure: true + - name: Validate DHCP options + when: + - not _run_fail | bool + - scenario.check_dhcp_options is defined + - scenario.check_dhcp_options | bool + block: + - name: Check DHCP options configuration file exists + become: true + ansible.builtin.stat: + path: "/etc/cifmw-dnsmasq.d/vm-types-dhcp-options.conf" + register: _dhcp_options_file + + - name: Assert DHCP options file exists + ansible.builtin.assert: + quiet: true + that: + - _dhcp_options_file.stat.exists + msg: "DHCP options file should exist" + + - name: Read DHCP options file + become: true + ansible.builtin.slurp: + path: "/etc/cifmw-dnsmasq.d/vm-types-dhcp-options.conf" + register: _dhcp_options_content + + - name: Decode DHCP options content + ansible.builtin.set_fact: + _dhcp_opts: "{{ _dhcp_options_content.content | b64decode }}" + + - name: Verify DHCP options content for compute VMs + ansible.builtin.assert: + quiet: true + that: + - "'dhcp-option=tag:compute,60,HTTPClient' in _dhcp_opts" + - "'dhcp-option=tag:compute,67,http://192.168.140.1:8081/boot-artifacts/compute.ipxe' in _dhcp_opts" + msg: "DHCP options should contain correct entries for compute VMs" + + - name: Verify DHCP host entry has tag + become: true + ansible.builtin.shell: + cmd: "grep -l 'set:compute' /etc/cifmw-dnsmasq.d/dhcp-hosts.d/osp_trunk_compute-0*" + register: _tagged_entry + changed_when: false + failed_when: _tagged_entry.rc != 0 + + - name: Read tagged DHCP host entry + become: true + ansible.builtin.slurp: + path: "{{ _tagged_entry.stdout }}" + register: _dhcp_host_entry + + - name: Verify tag format in DHCP host entry + vars: + _entry_content: "{{ _dhcp_host_entry.content | b64decode | trim }}" + ansible.builtin.assert: + quiet: true + that: + - "'set:compute' in _entry_content" + - "_entry_content.startswith('set:compute,')" + msg: "DHCP host entry should start with 'set:compute,': {{ _entry_content }}" + + rescue: + - name: Debug DHCP options content + when: _dhcp_opts is defined + ansible.builtin.debug: + var: _dhcp_opts + + - name: Mark run as failed + ansible.builtin.set_fact: + _run_fail: true + _failure: true + - name: Assert we have expected facts set block: - name: Ensure it failed at the right place diff --git a/roles/libvirt_manager/molecule/generate_network_data/vars/scenarios.yml b/roles/libvirt_manager/molecule/generate_network_data/vars/scenarios.yml index b70b46176e..cbd4fdc314 100644 --- a/roles/libvirt_manager/molecule/generate_network_data/vars/scenarios.yml +++ b/roles/libvirt_manager/molecule/generate_network_data/vars/scenarios.yml @@ -38,6 +38,33 @@ scenarios: + - name: DHCP options for VM types + check_dns: + - rec: "compute-0.utility" + ip: "192.168.140.10" + - rec: "compute-0.ctlplane.local" + ip: "192.168.140.10" + - rec: "compute-0.public.local" + ip: "192.168.110.10" + check_dhcp: + - osp_trunk_compute-0 + - public_compute-0 + check_dhcp_options: true + lm_config_patch: + vms: + compute: + dhcp_options: + - "60,HTTPClient" + - "67,http://192.168.140.1:8081/boot-artifacts/compute.ipxe" + networks: + osp_trunk: | + + osp_trunk + + + + + - name: Baremetal integration check_dns: - rec: "compute-0.utility" diff --git a/roles/libvirt_manager/tasks/create_dhcp_options.yml b/roles/libvirt_manager/tasks/create_dhcp_options.yml new file mode 100644 index 0000000000..d31c5d1bae --- /dev/null +++ b/roles/libvirt_manager/tasks/create_dhcp_options.yml @@ -0,0 +1,46 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# 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: Initialize empty _lm_dhcp_options fact + ansible.builtin.set_fact: + _lm_dhcp_options: {} + +- name: Collect DHCP options from VM definitions + when: + - item.value.dhcp_options is defined + - item.value.dhcp_options | length > 0 + vars: + _vm_type: "{{ item.key }}" + _options: "{{ item.value.dhcp_options }}" + ansible.builtin.set_fact: + _lm_dhcp_options: >- + {{ + _lm_dhcp_options | combine({_vm_type: _options}) + }} + loop: "{{ _cifmw_libvirt_manager_layout.vms | dict2items }}" + loop_control: + label: "{{ item.key }}" + +- name: Generate DHCP option configuration for VM types + when: + - _lm_dhcp_options | length > 0 + become: true + notify: "Restart dnsmasq" + ansible.builtin.template: + src: "vm-types-dhcp-options.conf.j2" + dest: "/etc/cifmw-dnsmasq.d/vm-types-dhcp-options.conf" + mode: '0644' + validate: "/usr/sbin/dnsmasq -C %s --test" diff --git a/roles/libvirt_manager/tasks/generate_networking_data.yml b/roles/libvirt_manager/tasks/generate_networking_data.yml index 1bf06d309a..c464a0867e 100644 --- a/roles/libvirt_manager/tasks/generate_networking_data.yml +++ b/roles/libvirt_manager/tasks/generate_networking_data.yml @@ -303,6 +303,9 @@ - name: Reserve IPs in DHCP and create DNS entries ansible.builtin.import_tasks: create_dns_records.yml +- name: Create DHCP options for VM types + ansible.builtin.import_tasks: create_dhcp_options.yml + # This task might also be done via the reproducer/prepare_networking.yml # but, depending on how we call the libvirt_manager, we might not have it. # Using the same filename/permissions/content, we can ensure it's there diff --git a/roles/libvirt_manager/tasks/reserve_dnsmasq_ips.yml b/roles/libvirt_manager/tasks/reserve_dnsmasq_ips.yml index b56ebde9ef..5fe2d3ff53 100644 --- a/roles/libvirt_manager/tasks/reserve_dnsmasq_ips.yml +++ b/roles/libvirt_manager/tasks/reserve_dnsmasq_ips.yml @@ -37,6 +37,7 @@ (host_data.key is match('^ocp.*')) | ternary(_ocp_name, host_data.key) }} + _vm_type: "{{ hostvars[host_data.key].vm_type | default('') }}" _host: network: "{{ _translated_name }}" name: "{{ _hostname }}" @@ -49,6 +50,7 @@ _net_data.ip_v6 | default('') ] }} + tag: "{{ _vm_type }}" ansible.builtin.set_fact: _lm_dhcp_entries: "{{ _lm_dhcp_entries + [_host] }}" loop: "{{ cifmw_networking_env_definition.instances | dict2items }}" diff --git a/roles/libvirt_manager/templates/vm-types-dhcp-options.conf.j2 b/roles/libvirt_manager/templates/vm-types-dhcp-options.conf.j2 new file mode 100644 index 0000000000..905467f77a --- /dev/null +++ b/roles/libvirt_manager/templates/vm-types-dhcp-options.conf.j2 @@ -0,0 +1,8 @@ +# Managed by ci-framework/libvirt_manager +# DHCP options for VM types +{% for vm_type, options in _lm_dhcp_options.items() %} +# Options for {{ vm_type }} VMs +{% for option in options %} +dhcp-option=tag:{{ vm_type }},{{ option }} +{% endfor %} +{% endfor %} diff --git a/zuul.d/projects.yaml b/zuul.d/projects.yaml index 182be6866a..f30e3d1956 100644 --- a/zuul.d/projects.yaml +++ b/zuul.d/projects.yaml @@ -2,114 +2,5 @@ github-check: jobs: - noop - - cifmw-pod-ansible-test - - cifmw-pod-k8s-snippets-source - - cifmw-pod-pre-commit - - cifmw-pod-zuul-files - - cifmw-content-provider-build-images - - cifmw-edpm-build-images - - cifmw-multinode-kuttl - - cifmw-tcib - - cifmw-architecture-validate-hci - - ci-framework-openstack-meta-content-provider - - build-push-container-cifmw-client - - cifmw-molecule-adoption_osp_deploy - - cifmw-molecule-artifacts - - cifmw-molecule-build_containers - - cifmw-molecule-build_openstack_packages - - cifmw-molecule-build_push_container - - cifmw-molecule-cert_manager - - cifmw-molecule-ci_dcn_site - - cifmw-molecule-ci_gen_kustomize_values - - cifmw-molecule-ci_local_storage - - cifmw-molecule-ci_lvms_storage - - cifmw-molecule-ci_multus - - cifmw-molecule-ci_network - - cifmw-molecule-ci_nmstate - - cifmw-molecule-ci_setup - - cifmw-molecule-cifmw_block_device - - cifmw-molecule-cifmw_ceph_client - - cifmw-molecule-cifmw_ceph_spec - - cifmw-molecule-cifmw_cephadm - - cifmw-molecule-cifmw_create_admin - - cifmw-molecule-cifmw_external_dns - - cifmw-molecule-cifmw_helpers - - cifmw-molecule-cifmw_nfs - - cifmw-molecule-cifmw_ntp - - cifmw-molecule-cifmw_setup - - cifmw-molecule-cifmw_snr_nhc - - cifmw-molecule-cifmw_test_role - - cifmw-molecule-cleanup_openstack - - cifmw-molecule-compliance - - cifmw-molecule-config_drive - - cifmw-molecule-copy_container - - cifmw-molecule-deploy_bmh - - cifmw-molecule-devscripts - - cifmw-molecule-discover_latest_image - - cifmw-molecule-dlrn_promote - - cifmw-molecule-dlrn_report - cifmw-molecule-dnsmasq - - cifmw-molecule-edpm_build_images - - cifmw-molecule-edpm_deploy - - cifmw-molecule-edpm_deploy_baremetal - - cifmw-molecule-edpm_kustomize - - cifmw-molecule-edpm_prepare - - cifmw-molecule-env_op_images - - cifmw-molecule-federation - - cifmw-molecule-fix_python_encodings - - cifmw-molecule-hci_prepare - - cifmw-molecule-hive - - cifmw-molecule-idrac_configuration - - cifmw-molecule-install_ca - - cifmw-molecule-install_openstack_ca - - cifmw-molecule-install_yamls - - cifmw-molecule-ipa - - cifmw-molecule-krb_request - - cifmw-molecule-kustomize_deploy - cifmw-molecule-libvirt_manager - - cifmw-molecule-manage_secrets - - cifmw-molecule-mirror_registry - - cifmw-molecule-nat64_appliance - - cifmw-molecule-networking_mapper - - cifmw-molecule-openshift_adm - - cifmw-molecule-openshift_login - - cifmw-molecule-openshift_obs - - cifmw-molecule-openshift_provisioner_node - - cifmw-molecule-openshift_setup - - cifmw-molecule-operator_build - - cifmw-molecule-operator_deploy - - cifmw-molecule-os_must_gather - - cifmw-molecule-os_net_setup - - cifmw-molecule-ovirt - - cifmw-molecule-pkg_build - - cifmw-molecule-podman - - cifmw-molecule-polarion - - cifmw-molecule-recognize_ssh_keypair - - cifmw-molecule-registry_deploy - - cifmw-molecule-repo_setup - - cifmw-molecule-reportportal - - cifmw-molecule-reproducer - - cifmw-molecule-rhol_crc - - cifmw-molecule-run_hook - - cifmw-molecule-set_openstack_containers - - cifmw-molecule-shiftstack - - cifmw-molecule-ssh_jumper - - cifmw-molecule-sushy_emulator - - cifmw-molecule-switch_config - - cifmw-molecule-tempest - - cifmw-molecule-test_deps - - cifmw-molecule-test_operator - - cifmw-molecule-tofu - - cifmw-molecule-update - - cifmw-molecule-update_containers - - cifmw-molecule-validations - - cifmw-molecule-virtualbmc - - edpm-ansible-molecule-edpm_podman - - edpm-ansible-molecule-edpm_ovs - github-post: - jobs: - - build-push-container-cifmw-client-post - name: openstack-k8s-operators/ci-framework - templates: - - podified-multinode-edpm-ci-framework-pipeline - - data-plane-adoption-ci-framework-pipeline