diff --git a/docs/dictionary/en-custom.txt b/docs/dictionary/en-custom.txt index 7c816e0703..ca52d0542a 100644 --- a/docs/dictionary/en-custom.txt +++ b/docs/dictionary/en-custom.txt @@ -257,6 +257,7 @@ ipam ipi ipmi ips +iptables ipv iscsi isdir @@ -415,6 +416,7 @@ openstack openstackclient openstackcontrolplane openstackdataplane +openstackdataplanedeployment openstackdataplanenodeset openstackdataplanenodesets openstackprovisioner diff --git a/post-deployment.yml b/post-deployment.yml index 869ae9b853..27983a1ffa 100644 --- a/post-deployment.yml +++ b/post-deployment.yml @@ -54,6 +54,12 @@ cifmw_fdp_update_container_images_target_package: "{{ cifmw_fdp_update_target_package }}" cifmw_fdp_update_container_images_repo_baseurl: "{{ cifmw_fdp_update_repo_baseurl }}" + - name: Update EDPM (containers and host packages) + ansible.builtin.import_role: + name: fdp_update_edpm + vars: + cifmw_fdp_update_edpm_repo_baseurl: "{{ cifmw_fdp_update_repo_baseurl }}" + - name: Run compliance scan for computes hosts: "{{ groups['computes'] | default ([]) }}" gather_facts: true diff --git a/roles/fdp_update_edpm/README.md b/roles/fdp_update_edpm/README.md new file mode 100644 index 0000000000..59a11ee666 --- /dev/null +++ b/roles/fdp_update_edpm/README.md @@ -0,0 +1,184 @@ +# fdp_update_edpm + +Role for updating OpenStack EDPM (Edge Data Plane Management) nodes with custom container images and host packages. + +## Description + +This role provides a declarative approach to update EDPM nodes with: + +1. **Updates container images** by patching OpenStackDataPlaneNodeSet CRs with new image references +2. **Updates host packages** by configuring `edpm_bootstrap_packages` and `edpm_bootstrap_repos` in the nodeset +3. **Configures registry authentication** with OpenShift service account tokens +4. **Installs CA certificates** for secure registry access +5. **Optionally creates deployments** to apply the changes to EDPM nodes + +### Key Features + +- **Declarative approach**: Only modifies Kubernetes CRs, doesn't execute commands directly on EDPM nodes +- **Uses native EDPM capabilities**: Leverages `edpm_bootstrap` and `edpm_podman` roles from edpm-ansible +- **Secure by default**: Installs OpenShift CA certificates instead of using insecure registries +- **Flexible**: Supports updating containers, packages, or both +- **Idempotent**: Can be run multiple times safely + +## Requirements + +- OpenShift cluster with OpenStack operators installed +- Access to `oc` command +- OpenStackVersion CR with custom container images +- Custom repository with updated packages (if updating host packages) + +## Role Variables + +### General Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `cifmw_fdp_update_edpm_namespace` | `"openstack"` | OpenShift namespace | +| `cifmw_fdp_update_edpm_nodeset_name` | `"all"` | NodeSet to update (`"all"` or specific name) | +| `cifmw_fdp_update_edpm_dry_run` | `false` | Show changes without applying | + +### Container Image Updates + +| Variable | Default | Description | +|----------|---------|-------------| +| `cifmw_fdp_update_edpm_containers_enabled` | `true` | Enable container image updates | +| `cifmw_fdp_update_edpm_image_registry` | `""` | External registry URL (auto-detected if empty) | +| `cifmw_fdp_update_edpm_image_variable_mapping` | See defaults | Mapping of image keys to EDPM variables | + +### Host Package Updates + +| Variable | Default | Description | +|----------|---------|-------------| +| `cifmw_fdp_update_edpm_packages_enabled` | `true` | Enable host package updates | +| `cifmw_fdp_update_edpm_repo_baseurl` | `""` | **REQUIRED** Repository base URL | +| `cifmw_fdp_update_edpm_repo_name` | `"fdp-update"` | Repository name | +| `cifmw_fdp_update_edpm_packages` | See defaults | List of packages to install/update | + +### Hypervisor Firewall Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `cifmw_fdp_update_edpm_setup_hypervisor_firewall` | `true` | Enable/disable hypervisor firewall setup for registry access | +| `cifmw_fdp_update_compute_interface` | `"osp_trunk"` | Network interface on hypervisor connected to compute nodes (EDPM) | +| `cifmw_fdp_update_registry_interface` | `"ocpbm"` | Network interface on hypervisor connected to OpenShift/registry | +| `cifmw_fdp_update_compute_network` | `"192.168.122.0/24"` | Compute nodes network CIDR (source for NAT) | +| `cifmw_fdp_update_registry_network` | `"192.168.201.0/24"` | OpenShift/registry network CIDR (destination for NAT) | + +### Registry Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `cifmw_fdp_update_edpm_configure_registry_ca` | `true` | Install OpenShift CA certificate via bootstrap command | +| `cifmw_fdp_update_edpm_configure_registry_auth` | `true` | Configure registry authentication | + +### Deployment Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `cifmw_fdp_update_edpm_auto_deploy` | `true` | Automatically create deployment | +| `cifmw_fdp_update_edpm_deployment_per_nodeset` | `true` | Create separate deployment per nodeset | +| `cifmw_fdp_update_edpm_wait_for_deployment` | `true` | Wait for deployment to complete | +| `cifmw_fdp_update_edpm_deployment_timeout` | `3600` | Deployment timeout (seconds) | +| `cifmw_fdp_update_edpm_deployment_services` | See defaults | Services to run in deployment | + +## Dependencies + +None (uses native OpenStack Data Plane operators and edpm-ansible roles) + +## Example Playbook + +### Update both containers and packages + +```yaml +- hosts: localhost + roles: + - role: fdp_update_edpm + vars: + cifmw_fdp_update_edpm_namespace: openstack + cifmw_fdp_update_edpm_nodeset_name: openstack-edpm + cifmw_fdp_update_edpm_repo_baseurl: "http://example.com/repos/fdp-updates" + cifmw_fdp_update_edpm_packages: + - openvswitch3.5 + - openvswitch-selinux-extra-policy +``` + +### Update only containers + +```yaml +- hosts: localhost + roles: + - role: fdp_update_edpm + vars: + cifmw_fdp_update_edpm_packages_enabled: false + cifmw_fdp_update_edpm_containers_enabled: true +``` + +### Update only packages + +```yaml +- hosts: localhost + roles: + - role: fdp_update_edpm + vars: + cifmw_fdp_update_edpm_containers_enabled: false + cifmw_fdp_update_edpm_packages_enabled: true + cifmw_fdp_update_edpm_repo_baseurl: "http://example.com/repos/updates" +``` + +### Dry run (show changes without applying) + +```yaml +- hosts: localhost + roles: + - role: fdp_update_edpm + vars: + cifmw_fdp_update_edpm_dry_run: true +``` + +### Custom network configuration + +```yaml +- hosts: localhost + roles: + - role: fdp_update_edpm + vars: + cifmw_fdp_update_compute_interface: "br-ex" + cifmw_fdp_update_registry_interface: "br-ocp" + cifmw_fdp_update_compute_network: "10.0.0.0/24" + cifmw_fdp_update_registry_network: "172.16.0.0/24" +``` + +## How It Works + +1. **Validates parameters**: Ensures required variables are set +2. **Configures hypervisor firewall** (if enabled): Sets up iptables rules to allow EDPM nodes to access the OpenShift registry +3. **Fetches NodeSets**: Gets OpenStackDataPlaneNodeSet CRs from the cluster +4. **Fetches container images** (if enabled): Gets custom images from OpenStackVersion CR +5. **For each NodeSet**: + - Patches container image variables (e.g., `edpm_ovn_controller_agent_image`) + - Patches `edpm_bootstrap_packages` with packages to install + - Patches `edpm_bootstrap_repos` with custom repository configuration + - Configures registry authentication (`edpm_container_registry_logins`) + - Installs CA certificate via `edpm_bootstrap_command` (if enabled) +6. **Creates deployment** (if enabled): Creates OpenStackDataPlaneDeployment CR +7. **Waits for completion** (if enabled): Monitors deployment until Ready + +## Architecture: Declarative vs Imperative + +This role follows the **declarative** approach of Kubernetes/OpenStack: + +- ❌ **Does NOT** SSH to nodes and run `dnf install` directly +- ❌ **Does NOT** SSH to nodes and run `systemctl restart` directly +- ✅ **Does** patch NodeSet CRs with desired state +- ✅ **Does** let OpenStack Data Plane Operator apply the changes +- ✅ **Does** use native `edpm_bootstrap` role for package installation +- ✅ **Does** use native `edpm_podman` role for container management +- ✅ **Does** use `edpm_bootstrap_command` for CA certificate installation + +## License + +Apache 2.0 + +## Author Information + +Red Hat OpenStack CI Framework Team diff --git a/roles/fdp_update_edpm/defaults/main.yml b/roles/fdp_update_edpm/defaults/main.yml new file mode 100644 index 0000000000..cc87b399c6 --- /dev/null +++ b/roles/fdp_update_edpm/defaults/main.yml @@ -0,0 +1,132 @@ +--- +# 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. + +# ============================================ +# General Configuration +# ============================================ + +# OpenShift namespace where EDPM resources are deployed +cifmw_fdp_update_edpm_namespace: "openstack" + +# NodeSet selector - can be a specific name or 'all' for all nodesets +cifmw_fdp_update_edpm_nodeset_name: "all" + +# Dry run - show changes without applying +cifmw_fdp_update_edpm_dry_run: false + +# ============================================ +# Container Image Updates +# ============================================ + +# Enable/disable container image updates +cifmw_fdp_update_edpm_containers_enabled: true + +# Image registry URL (auto-detected from OpenShift if empty) +cifmw_fdp_update_edpm_image_registry: "" + +# Mapping of control plane image keys to EDPM ansible variables +# Only ovnControllerImage is used on EDPM compute nodes +cifmw_fdp_update_edpm_image_variable_mapping: + ovnControllerImage: edpm_ovn_controller_agent_image + +# ============================================ +# Host Package Updates +# ============================================ + +# Enable/disable host package updates +cifmw_fdp_update_edpm_packages_enabled: true + +# Repository configuration for host package updates +cifmw_fdp_update_edpm_repo_name: "fdp-update" +cifmw_fdp_update_edpm_repo_baseurl: "" # REQUIRED if packages_enabled is true +cifmw_fdp_update_edpm_repo_enabled: true +cifmw_fdp_update_edpm_repo_gpgcheck: false +cifmw_fdp_update_edpm_repo_priority: 1 + +# Packages to update on the host +# These will be added to edpm_bootstrap_packages in the nodeset +cifmw_fdp_update_edpm_packages: + - openvswitch3.5 + - openvswitch-selinux-extra-policy + +# ============================================ +# Hypervisor Firewall Configuration +# ============================================ + +# Enable/disable hypervisor firewall setup for registry access +cifmw_fdp_update_edpm_setup_hypervisor_firewall: true + +# Network interface on hypervisor connected to compute nodes (EDPM) +cifmw_fdp_update_compute_interface: "osp_trunk" + +# Network interface on hypervisor connected to OpenShift/registry +cifmw_fdp_update_registry_interface: "ocpbm" + +# Compute nodes network CIDR (source for NAT) +cifmw_fdp_update_compute_network: "192.168.122.0/24" + +# OpenShift/registry network CIDR (destination for NAT) +cifmw_fdp_update_registry_network: "192.168.201.0/24" + +# ============================================ +# Registry Configuration +# ============================================ + +# Configure OpenShift registry CA certificate on EDPM nodes +cifmw_fdp_update_edpm_configure_registry_ca: true + +# Configure registry authentication automatically +# Uses 'oc create token' or 'oc whoami -t' to get a service account token +cifmw_fdp_update_edpm_configure_registry_auth: true + +# ============================================ +# Deployment Configuration +# ============================================ + +# Automatically create OpenStackDataPlaneDeployment after updating NodeSets +# Creates a single deployment for all updated NodeSets +cifmw_fdp_update_edpm_auto_deploy: true + +# Wait for deployment to complete before continuing +cifmw_fdp_update_edpm_wait_for_deployment: true + +# Timeout for deployment completion (in seconds) +# Default: 3600 seconds (60 minutes / 1 hour) +cifmw_fdp_update_edpm_deployment_timeout: 3600 + +# Polling interval when waiting for deployment (in seconds) +cifmw_fdp_update_edpm_deployment_poll_interval: 30 + +# Services to run in the deployment +# For updates, we need to: +# 1. bootstrap - Install host packages and configure repos +# 2. configure-os - Configure registry authentication +# 3. configure-network - Ensure network is configured +# 4. Service-specific services (ovn, nova, etc) - Pull updated images and restart +cifmw_fdp_update_edpm_deployment_services: + - bootstrap # MUST be first to install packages and configure repos + - configure-os # MUST be second to authenticate before pulling images + - configure-network + - ovn + +# ============================================ +# Internal Variables (do not override) +# ============================================ + +_cifmw_fdp_update_edpm_updated_images: {} +_cifmw_fdp_update_edpm_nodesets: [] +_cifmw_fdp_update_edpm_updated_nodesets: [] +_cifmw_fdp_update_edpm_external_registry: "" diff --git a/roles/fdp_update_edpm/meta/main.yml b/roles/fdp_update_edpm/meta/main.yml new file mode 100644 index 0000000000..3bfc3f70f4 --- /dev/null +++ b/roles/fdp_update_edpm/meta/main.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. + +galaxy_info: + author: Red Hat + description: Update OpenStack EDPM container images and host packages with FDP updates + company: Red Hat + license: Apache-2.0 + min_ansible_version: "2.15" + platforms: + - name: Fedora + versions: + - all + - name: EL + versions: + - "9" + galaxy_tags: + - openstack + - edpm + - dataplane + - kubernetes + - openshift + - rpm + - containers + +dependencies: [] diff --git a/roles/fdp_update_edpm/tasks/configure_ca_cert.yml b/roles/fdp_update_edpm/tasks/configure_ca_cert.yml new file mode 100644 index 0000000000..759a96b0fd --- /dev/null +++ b/roles/fdp_update_edpm/tasks/configure_ca_cert.yml @@ -0,0 +1,65 @@ +--- +# 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. + +# This file configures OpenShift registry CA certificate on EDPM nodes +# Embeds CA certificate installation in edpm_bootstrap_command + +- name: Get OpenShift ingress CA certificate + kubernetes.core.k8s_info: + api_version: v1 + kind: Secret + name: router-ca + namespace: openshift-ingress-operator + register: _cifmw_fdp_update_edpm_ca_cert_result + +- name: Decode CA certificate + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_ca_cert: "{{ _cifmw_fdp_update_edpm_ca_cert_result.resources[0].data['tls.crt'] | b64decode }}" + +- name: Get current edpm_bootstrap_command from nodeset + kubernetes.core.k8s_info: + api_version: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: "{{ _cifmw_fdp_update_edpm_current_nodeset_name }}" + namespace: "{{ cifmw_fdp_update_edpm_namespace }}" + register: _cifmw_fdp_update_edpm_current_bootstrap_result + failed_when: false + +- name: Build bootstrap command with CA certificate installation + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_bootstrap_with_ca: | + # Install OpenShift registry CA certificate + cat > /etc/pki/ca-trust/source/anchors/openshift-registry-ca.crt <<'EOF' + {{ _cifmw_fdp_update_edpm_ca_cert }} + EOF + update-ca-trust extract + + {{ _cifmw_fdp_update_edpm_current_bootstrap_result.resources[0].spec.nodeTemplate.ansible.ansibleVars.edpm_bootstrap_command | default('') if (_cifmw_fdp_update_edpm_current_bootstrap_result.resources | length > 0) else '' }} + +- name: Patch NodeSet with updated bootstrap command + kubernetes.core.k8s: + state: patched + api_version: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: "{{ _cifmw_fdp_update_edpm_current_nodeset_name }}" + namespace: "{{ cifmw_fdp_update_edpm_namespace }}" + definition: + spec: + nodeTemplate: + ansible: + ansibleVars: + edpm_bootstrap_command: "{{ _cifmw_fdp_update_edpm_bootstrap_with_ca }}" + when: not cifmw_fdp_update_edpm_dry_run diff --git a/roles/fdp_update_edpm/tasks/create_deployment.yml b/roles/fdp_update_edpm/tasks/create_deployment.yml new file mode 100644 index 0000000000..ed17139a32 --- /dev/null +++ b/roles/fdp_update_edpm/tasks/create_deployment.yml @@ -0,0 +1,61 @@ +--- +# 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: Set deployment name + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_deployment_name: "edpm-fdp-update-{{ ansible_date_time.epoch }}" + +- name: Create OpenStackDataPlaneDeployment + kubernetes.core.k8s: + state: present + api_version: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneDeployment + definition: + metadata: + name: "{{ _cifmw_fdp_update_edpm_deployment_name }}" + namespace: "{{ cifmw_fdp_update_edpm_namespace }}" + spec: + nodeSets: "{{ _cifmw_fdp_update_edpm_updated_nodesets }}" + servicesOverride: "{{ cifmw_fdp_update_edpm_deployment_services }}" + +- name: Display deployment information + ansible.builtin.debug: + msg: + - "Created deployment: {{ _cifmw_fdp_update_edpm_deployment_name }}" + - "NodeSets: {{ _cifmw_fdp_update_edpm_updated_nodesets }}" + - "Services: {{ cifmw_fdp_update_edpm_deployment_services }}" + +- name: Wait for deployment to complete + when: cifmw_fdp_update_edpm_wait_for_deployment | bool + block: + - name: Wait for deployment Ready condition + kubernetes.core.k8s_info: + api_version: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneDeployment + name: "{{ _cifmw_fdp_update_edpm_deployment_name }}" + namespace: "{{ cifmw_fdp_update_edpm_namespace }}" + register: _cifmw_fdp_update_edpm_deployment_result + until: >- + _cifmw_fdp_update_edpm_deployment_result.resources | length > 0 and + _cifmw_fdp_update_edpm_deployment_result.resources[0].status.conditions | + selectattr('type', 'equalto', 'Ready') | + map(attribute='status') | first | default('False') == 'True' + retries: "{{ (cifmw_fdp_update_edpm_deployment_timeout / cifmw_fdp_update_edpm_deployment_poll_interval) | int }}" + delay: "{{ cifmw_fdp_update_edpm_deployment_poll_interval }}" + + - name: Display deployment completion + ansible.builtin.debug: + msg: "Deployment {{ _cifmw_fdp_update_edpm_deployment_name }} completed successfully" diff --git a/roles/fdp_update_edpm/tasks/fetch_nodesets.yml b/roles/fdp_update_edpm/tasks/fetch_nodesets.yml new file mode 100644 index 0000000000..7c2f4fb086 --- /dev/null +++ b/roles/fdp_update_edpm/tasks/fetch_nodesets.yml @@ -0,0 +1,32 @@ +--- +# 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: Get OpenStackDataPlaneNodeSets + kubernetes.core.k8s_info: + api_version: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: "{{ cifmw_fdp_update_edpm_nodeset_name if cifmw_fdp_update_edpm_nodeset_name != 'all' else omit }}" + namespace: "{{ cifmw_fdp_update_edpm_namespace }}" + register: _cifmw_fdp_update_edpm_nodesets_result + +- name: Parse NodeSets + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_nodesets: "{{ _cifmw_fdp_update_edpm_nodesets_result.resources }}" + +- name: Fail if no NodeSets found + ansible.builtin.fail: + msg: "No OpenStackDataPlaneNodeSets found in namespace {{ cifmw_fdp_update_edpm_namespace }}" + when: _cifmw_fdp_update_edpm_nodesets | length == 0 diff --git a/roles/fdp_update_edpm/tasks/main.yml b/roles/fdp_update_edpm/tasks/main.yml new file mode 100644 index 0000000000..9c5f29eb0f --- /dev/null +++ b/roles/fdp_update_edpm/tasks/main.yml @@ -0,0 +1,83 @@ +--- +# 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. + +# ============================================ +# Validate and Initialize +# ============================================ + +- name: Validate parameters and initialize + ansible.builtin.include_tasks: validate.yml + +# ============================================ +# Setup Hypervisor Firewall +# ============================================ + +- name: Setup hypervisor firewall for registry access + ansible.builtin.include_tasks: setup_hypervisor_firewall.yml + when: cifmw_fdp_update_edpm_setup_hypervisor_firewall | default(true) | bool + +# ============================================ +# Fetch NodeSets +# ============================================ + +- name: Fetch EDPM NodeSets + ansible.builtin.include_tasks: fetch_nodesets.yml + +# ============================================ +# Update Container Images (Optional) +# ============================================ + +- name: Update container images + when: cifmw_fdp_update_edpm_containers_enabled | bool + ansible.builtin.include_tasks: update_container_images.yml + +# ============================================ +# Process Each NodeSet +# ============================================ + +- name: Process each NodeSet + ansible.builtin.include_tasks: process_nodeset.yml + loop: "{{ _cifmw_fdp_update_edpm_nodesets }}" + loop_control: + loop_var: nodeset + label: "{{ nodeset.metadata.name }}" + +# ============================================ +# Deploy Updates to EDPM Nodes (Optional) +# ============================================ + +- name: Deploy updates to EDPM nodes + when: + - cifmw_fdp_update_edpm_auto_deploy | bool + - not cifmw_fdp_update_edpm_dry_run | bool + - _cifmw_fdp_update_edpm_updated_nodesets | default([]) | length > 0 + ansible.builtin.include_tasks: create_deployment.yml + +# ============================================ +# Summary +# ============================================ + +- name: Display update summary + ansible.builtin.debug: + msg: + - "==============================================" + - "EDPM Update Summary" + - "==============================================" + - "Updated {{ _cifmw_fdp_update_edpm_updated_nodesets | length }} NodeSet(s): {{ _cifmw_fdp_update_edpm_updated_nodesets }}" + - "Container images updated: {{ cifmw_fdp_update_edpm_containers_enabled }}" + - "Host packages updated: {{ cifmw_fdp_update_edpm_packages_enabled }}" + - "==============================================" + when: not cifmw_fdp_update_edpm_dry_run diff --git a/roles/fdp_update_edpm/tasks/process_nodeset.yml b/roles/fdp_update_edpm/tasks/process_nodeset.yml new file mode 100644 index 0000000000..43d9cadd96 --- /dev/null +++ b/roles/fdp_update_edpm/tasks/process_nodeset.yml @@ -0,0 +1,159 @@ +--- +# 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: Set NodeSet name + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_current_nodeset_name: "{{ nodeset.metadata.name }}" + +# ============================================ +# Patch Container Images +# ============================================ + +- name: Patch container images in nodeset + when: + - cifmw_fdp_update_edpm_containers_enabled | bool + - _cifmw_fdp_update_edpm_updated_images is defined + - _cifmw_fdp_update_edpm_updated_images | length > 0 + block: + - name: Build EDPM image variables patch + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_image_vars_patch: {} + + - name: Add each EDPM variable to patch + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_image_vars_patch: >- + {{ + _cifmw_fdp_update_edpm_image_vars_patch | combine({ + cifmw_fdp_update_edpm_image_variable_mapping[item.key]: item.value + }) + }} + loop: "{{ _cifmw_fdp_update_edpm_updated_images | dict2items }}" + loop_control: + label: "{{ item.key }}" + + - name: Apply container images using k8s patch + kubernetes.core.k8s: + state: patched + api_version: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: "{{ _cifmw_fdp_update_edpm_current_nodeset_name }}" + namespace: "{{ cifmw_fdp_update_edpm_namespace }}" + definition: + spec: + nodeTemplate: + ansible: + ansibleVars: "{{ _cifmw_fdp_update_edpm_image_vars_patch }}" + when: + - not cifmw_fdp_update_edpm_dry_run + - _cifmw_fdp_update_edpm_image_vars_patch | length > 0 + +# ============================================ +# Patch Host Packages and Repositories +# ============================================ + +- name: Patch host packages and repositories in nodeset + when: cifmw_fdp_update_edpm_packages_enabled | bool + ansible.builtin.include_tasks: update_host_packages.yml + +# ============================================ +# Configure Registry Authentication +# ============================================ + +- name: Configure registry authentication + when: + - not cifmw_fdp_update_edpm_dry_run + - cifmw_fdp_update_edpm_configure_registry_auth | bool + - _cifmw_fdp_update_edpm_external_registry is defined + - _cifmw_fdp_update_edpm_external_registry | length > 0 + block: + - name: Get authentication token for OpenShift registry + ansible.builtin.command: oc whoami -t + register: _cifmw_fdp_update_edpm_oc_token_result + changed_when: false + failed_when: false + + - name: Get existing registry logins + kubernetes.core.k8s_info: + api_version: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: "{{ _cifmw_fdp_update_edpm_current_nodeset_name }}" + namespace: "{{ cifmw_fdp_update_edpm_namespace }}" + register: _cifmw_fdp_update_edpm_current_logins_result + failed_when: false + + - name: Parse existing registry logins + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_existing_logins: >- + {{ + _cifmw_fdp_update_edpm_current_logins_result.resources[0].spec.nodeTemplate.ansible.ansibleVars.edpm_container_registry_logins | default({}) + if (_cifmw_fdp_update_edpm_current_logins_result.resources | length > 0) + else {} + }} + + - name: Merge with new registry login + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_merged_logins: >- + {{ + _cifmw_fdp_update_edpm_existing_logins | combine({ + _cifmw_fdp_update_edpm_external_registry: { + 'serviceaccount': _cifmw_fdp_update_edpm_oc_token_result.stdout + } + }) + }} + when: + - _cifmw_fdp_update_edpm_oc_token_result.rc == 0 + - _cifmw_fdp_update_edpm_oc_token_result.stdout | length > 0 + + - name: Build registry authentication patch + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_registry_auth_patch: + spec: + nodeTemplate: + ansible: + ansibleVars: + edpm_container_registry_logins: "{{ _cifmw_fdp_update_edpm_merged_logins }}" + when: + - _cifmw_fdp_update_edpm_oc_token_result.rc == 0 + - _cifmw_fdp_update_edpm_oc_token_result.stdout | length > 0 + + - name: Apply registry authentication configuration + kubernetes.core.k8s: + state: patched + api_version: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: "{{ _cifmw_fdp_update_edpm_current_nodeset_name }}" + namespace: "{{ cifmw_fdp_update_edpm_namespace }}" + definition: "{{ _cifmw_fdp_update_edpm_registry_auth_patch }}" + when: + - _cifmw_fdp_update_edpm_oc_token_result.rc == 0 + - _cifmw_fdp_update_edpm_oc_token_result.stdout | length > 0 + +# ============================================ +# Configure Registry CA Certificate +# ============================================ + +- name: Configure registry CA certificate + when: cifmw_fdp_update_edpm_configure_registry_ca | bool + ansible.builtin.include_tasks: configure_ca_cert.yml + +# ============================================ +# Record NodeSet as Processed +# ============================================ + +- name: Record NodeSet as processed + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_updated_nodesets: "{{ _cifmw_fdp_update_edpm_updated_nodesets + [_cifmw_fdp_update_edpm_current_nodeset_name] }}" + when: not cifmw_fdp_update_edpm_dry_run diff --git a/roles/fdp_update_edpm/tasks/setup_hypervisor_firewall.yml b/roles/fdp_update_edpm/tasks/setup_hypervisor_firewall.yml new file mode 100644 index 0000000000..127572dc46 --- /dev/null +++ b/roles/fdp_update_edpm/tasks/setup_hypervisor_firewall.yml @@ -0,0 +1,59 @@ +--- +# 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. + +# ============================================ +# Setup Hypervisor Firewall for Registry Access +# ============================================ +# This task configures iptables rules on the hypervisor to allow +# compute nodes (EDPM) to access the OpenShift registry for pulling +# container images during FDP updates. + +- name: Allow traffic from compute to registry interface + become: true + ansible.builtin.iptables: + chain: FORWARD + in_interface: "{{ cifmw_fdp_update_compute_interface }}" + out_interface: "{{ cifmw_fdp_update_registry_interface }}" + jump: ACCEPT + action: insert + rule_num: '1' + +- name: Allow return traffic from registry to compute interface + become: true + ansible.builtin.iptables: + chain: FORWARD + in_interface: "{{ cifmw_fdp_update_registry_interface }}" + out_interface: "{{ cifmw_fdp_update_compute_interface }}" + ctstate: RELATED,ESTABLISHED + jump: ACCEPT + action: insert + rule_num: '1' + +- name: Enable NAT for compute nodes to access registry + become: true + ansible.builtin.iptables: + table: nat + chain: POSTROUTING + source: "{{ cifmw_fdp_update_compute_network }}" + destination: "{{ cifmw_fdp_update_registry_network }}" + out_interface: "{{ cifmw_fdp_update_registry_interface }}" + jump: MASQUERADE + +- name: Persist firewall rules + become: true + community.general.iptables_state: + state: saved + path: /etc/sysconfig/iptables diff --git a/roles/fdp_update_edpm/tasks/update_container_images.yml b/roles/fdp_update_edpm/tasks/update_container_images.yml new file mode 100644 index 0000000000..dc58cda5f5 --- /dev/null +++ b/roles/fdp_update_edpm/tasks/update_container_images.yml @@ -0,0 +1,89 @@ +--- +# 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. + +# ============================================ +# Fetch Updated Container Images +# ============================================ + +- name: Get OpenStackVersion CR list + kubernetes.core.k8s_info: + api_version: core.openstack.org/v1beta1 + kind: OpenStackVersion + namespace: "{{ cifmw_fdp_update_edpm_namespace }}" + register: _cifmw_fdp_update_edpm_osv_result + +- name: Fail if no OpenStackVersion CR found + ansible.builtin.fail: + msg: "No OpenStackVersion CR found in namespace {{ cifmw_fdp_update_edpm_namespace }}" + when: _cifmw_fdp_update_edpm_osv_result.resources | length == 0 + +- name: Parse and filter EDPM-relevant custom images + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_updated_images: >- + {{ + (_cifmw_fdp_update_edpm_osv_result.resources[0].spec.customContainerImages | default({})) | dict2items | + selectattr('key', 'in', cifmw_fdp_update_edpm_image_variable_mapping.keys()) | + items2dict + }} + +# ============================================ +# Convert Internal Registry URLs to External +# ============================================ + +- name: Convert internal registry URLs to external for EDPM compute nodes + when: _cifmw_fdp_update_edpm_updated_images | length > 0 + block: + - name: Determine external registry URL + block: + - name: Try to auto-detect registry route if not configured + kubernetes.core.k8s_info: + api_version: route.openshift.io/v1 + kind: Route + name: default-route + namespace: openshift-image-registry + register: _cifmw_fdp_update_edpm_registry_route_result + failed_when: false + when: cifmw_fdp_update_edpm_image_registry is not defined or cifmw_fdp_update_edpm_image_registry | length == 0 + + - name: Set external registry URL + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_external_registry: >- + {%- if cifmw_fdp_update_edpm_image_registry is defined and cifmw_fdp_update_edpm_image_registry | length > 0 -%} + {{ cifmw_fdp_update_edpm_image_registry }} + {%- elif _cifmw_fdp_update_edpm_registry_route_result.resources | default([]) | length > 0 -%} + {{ _cifmw_fdp_update_edpm_registry_route_result.resources[0].spec.host }} + {%- else -%} + UNDEFINED + {%- endif -%} + + - name: Fail if no external registry URL could be determined + ansible.builtin.fail: + msg: | + Cannot determine external registry URL! + EDPM compute nodes require an external registry URL to pull images. + Please set: cifmw_fdp_update_edpm_image_registry + when: _cifmw_fdp_update_edpm_external_registry == 'UNDEFINED' + + - name: Convert URLs (handle all internal registry variations) + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_updated_images: "{{ dict(_cifmw_fdp_update_edpm_updated_images.keys() | zip(_cifmw_fdp_update_edpm_updated_images.values() | map('regex_replace', 'image-registry\\.openshift-image-registry\\.svc\\.cluster\\.local:5000', _cifmw_fdp_update_edpm_external_registry) | map('regex_replace', 'image-registry\\.openshift-image-registry\\.svc:5000', _cifmw_fdp_update_edpm_external_registry) | list)) }}" + +- name: Display container images to update + ansible.builtin.debug: + msg: + - "Container images found in OpenStackVersion CR:" + - "{{ _cifmw_fdp_update_edpm_updated_images }}" + when: _cifmw_fdp_update_edpm_updated_images | length > 0 diff --git a/roles/fdp_update_edpm/tasks/update_host_packages.yml b/roles/fdp_update_edpm/tasks/update_host_packages.yml new file mode 100644 index 0000000000..6e8d62d243 --- /dev/null +++ b/roles/fdp_update_edpm/tasks/update_host_packages.yml @@ -0,0 +1,109 @@ +--- +# 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. + +# This file patches the nodeset with edpm_bootstrap_* variables +# The actual package installation happens during deployment via edpm_bootstrap role + +# ============================================ +# Get Current Bootstrap Configuration +# ============================================ + +- name: Get current bootstrap configuration from nodeset + kubernetes.core.k8s_info: + api_version: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: "{{ _cifmw_fdp_update_edpm_current_nodeset_name }}" + namespace: "{{ cifmw_fdp_update_edpm_namespace }}" + register: _cifmw_fdp_update_edpm_current_bootstrap_result + failed_when: false + +# ============================================ +# Merge New Packages with Existing +# ============================================ + +- name: Parse current bootstrap packages + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_bootstrap_packages_list: >- + {{ + _cifmw_fdp_update_edpm_current_bootstrap_result.resources[0].spec.nodeTemplate.ansible.ansibleVars.edpm_bootstrap_packages | default([]) + if (_cifmw_fdp_update_edpm_current_bootstrap_result.resources | length > 0) + else [] + }} + +- name: Merge new packages with existing packages + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_merged_packages: "{{ (_cifmw_fdp_update_edpm_bootstrap_packages_list + cifmw_fdp_update_edpm_packages) | unique }}" + +# ============================================ +# Build Repository Configuration +# ============================================ + +- name: Parse current bootstrap repos + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_bootstrap_repos_list: >- + {{ + _cifmw_fdp_update_edpm_current_bootstrap_result.resources[0].spec.nodeTemplate.ansible.ansibleVars.edpm_bootstrap_repos | default([]) + if (_cifmw_fdp_update_edpm_current_bootstrap_result.resources | length > 0) + else [] + }} + +- name: Build new repository configuration + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_new_repo: + name: "{{ cifmw_fdp_update_edpm_repo_name }}" + baseurl: "{{ cifmw_fdp_update_edpm_repo_baseurl }}" + enabled: "{{ cifmw_fdp_update_edpm_repo_enabled | int }}" + gpgcheck: "{{ cifmw_fdp_update_edpm_repo_gpgcheck | int }}" + priority: "{{ cifmw_fdp_update_edpm_repo_priority }}" + +- name: Check if repo already exists in nodeset + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_repo_exists: "{{ _cifmw_fdp_update_edpm_bootstrap_repos_list | selectattr('name', 'equalto', cifmw_fdp_update_edpm_repo_name) | list | length > 0 }}" + +- name: Remove existing repo if it exists + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_bootstrap_repos_list: "{{ _cifmw_fdp_update_edpm_bootstrap_repos_list | rejectattr('name', 'equalto', cifmw_fdp_update_edpm_repo_name) | list }}" + when: _cifmw_fdp_update_edpm_repo_exists | bool + +- name: Add new repo to repos list + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_merged_repos: "{{ _cifmw_fdp_update_edpm_bootstrap_repos_list + [_cifmw_fdp_update_edpm_new_repo] }}" + +# ============================================ +# Patch NodeSet with Bootstrap Configuration +# ============================================ + +- name: Patch nodeset with bootstrap packages and repos + kubernetes.core.k8s: + state: patched + api_version: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: "{{ _cifmw_fdp_update_edpm_current_nodeset_name }}" + namespace: "{{ cifmw_fdp_update_edpm_namespace }}" + definition: + spec: + nodeTemplate: + ansible: + ansibleVars: + edpm_bootstrap_packages: "{{ _cifmw_fdp_update_edpm_merged_packages }}" + edpm_bootstrap_repos: "{{ _cifmw_fdp_update_edpm_merged_repos }}" + when: not cifmw_fdp_update_edpm_dry_run + +- name: Display packages and repos configuration + ansible.builtin.debug: + msg: + - "Packages to install on host: {{ _cifmw_fdp_update_edpm_merged_packages }}" + - "Repositories configured: {{ _cifmw_fdp_update_edpm_merged_repos | map(attribute='name') | list }}" diff --git a/roles/fdp_update_edpm/tasks/validate.yml b/roles/fdp_update_edpm/tasks/validate.yml new file mode 100644 index 0000000000..2036e66ab6 --- /dev/null +++ b/roles/fdp_update_edpm/tasks/validate.yml @@ -0,0 +1,33 @@ +--- +# 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: Validate package update configuration + ansible.builtin.assert: + that: + - cifmw_fdp_update_edpm_repo_baseurl is defined + - cifmw_fdp_update_edpm_repo_baseurl | length > 0 + - cifmw_fdp_update_edpm_packages is defined + - cifmw_fdp_update_edpm_packages | length > 0 + fail_msg: "Package updates require: cifmw_fdp_update_edpm_repo_baseurl and cifmw_fdp_update_edpm_packages" + when: cifmw_fdp_update_edpm_packages_enabled | bool + +- name: Verify oc command is available + ansible.builtin.command: oc version --client + changed_when: false + +- name: Initialize updated nodesets tracking list + ansible.builtin.set_fact: + _cifmw_fdp_update_edpm_updated_nodesets: [] diff --git a/zuul.d/molecule.yaml b/zuul.d/molecule.yaml index e1d4e77efc..2ed0cc6a41 100644 --- a/zuul.d/molecule.yaml +++ b/zuul.d/molecule.yaml @@ -936,6 +936,15 @@ - ^.config/molecule/.* name: cifmw-molecule-fdp_update_container_images parent: cifmw-molecule-noop +- job: + files: + - ^common-requirements.txt + - ^test-requirements.txt + - ^roles/fdp_update_edpm/.* + - ^ci/playbooks/molecule.* + - ^.config/molecule/.* + name: cifmw-molecule-fdp_update_edpm + parent: cifmw-molecule-noop - job: files: - ^common-requirements.txt diff --git a/zuul.d/projects.yaml b/zuul.d/projects.yaml index 8ca3c251c6..b213366beb 100644 --- a/zuul.d/projects.yaml +++ b/zuul.d/projects.yaml @@ -56,6 +56,7 @@ - cifmw-molecule-edpm_prepare - cifmw-molecule-env_op_images - cifmw-molecule-fdp_update_container_images + - cifmw-molecule-fdp_update_edpm - cifmw-molecule-federation - cifmw-molecule-fix_python_encodings - cifmw-molecule-hci_prepare