From d60b86483a6913cd93ed804aca35001f66978b22 Mon Sep 17 00:00:00 2001 From: Sergio Gimenez Date: Tue, 12 May 2026 11:23:28 +0200 Subject: [PATCH 1/4] docs(i2cat): track site-specific inventory Keep the downstream i2CAT repository tied to the public-safe automation while restoring local repository metadata and tracked host inventory for the OpenOP lab. --- .gitignore | 1 - CONTRIBUTING.md | 4 +- README.md | 3 ++ ansible/inventory/hosts.local.yml | 63 +++++++++++++++++++++++++++++++ mkdocs.yml | 6 +-- 5 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 ansible/inventory/hosts.local.yml diff --git a/.gitignore b/.gitignore index 3ba3d3a..04e0e49 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ ansible/.ansible/ *.retry ansible_facts/ ansible/secrets.yml -ansible/inventory/hosts.local.yml # Automation folder - contains kubeconfig files generated by ansible automation/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 47ea210..424cde0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,8 +45,8 @@ To contribute code, follow these steps: 1. **Clone the Repository**: ```bash - git clone https://labs.etsi.org/rep/oop/code/integration.git - cd integration + git clone git@gitlab.i2cat.net:areas/software-networks/operator-platform/oop/op-automation.git + cd i2CAT_OP_AUTOMATION ``` 2. **Create a Feature Branch**: diff --git a/README.md b/README.md index 6cd10e4..4af249b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ Repository scope: - deploy two-host federation setups - fetch kubeconfigs back to local machine +This downstream repository keeps i2CAT-specific inventory and local defaults on +top of the public-safe upstream automation. + ## Start here - `docs/getting-started.md`: environment, inventory, secrets, first deployment diff --git a/ansible/inventory/hosts.local.yml b/ansible/inventory/hosts.local.yml new file mode 100644 index 0000000..147b20b --- /dev/null +++ b/ansible/inventory/hosts.local.yml @@ -0,0 +1,63 @@ +--- +all: + hosts: + openop_1: + ansible_host: 192.168.123.188 + ansible_connection: ssh + ansible_user: ubuntu + ansible_ssh_private_key_file: "~/.ssh/keys/openop-dev-vm.key" + ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o ConnectTimeout=300 -o ServerAliveInterval=30 -o IdentitiesOnly=yes' + ansible_python_interpreter: /usr/bin/python3 + deployment_mode: remote + host_ip: 192.168.123.188 + kubeconfig_output_dir: "/home/ubuntu/kind-cluster-config" + kubeconfig_filename: operator-platform-external-kubeconfig.yaml + cluster_name: dev-all-in-one + + openop_2: + ansible_host: 192.168.123.178 + ansible_connection: ssh + ansible_user: ubuntu + ansible_ssh_private_key_file: "~/.ssh/keys/openop-dev-vm.key" + ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o ConnectTimeout=300 -o ServerAliveInterval=30 -o IdentitiesOnly=yes' + ansible_python_interpreter: /usr/bin/python3 + deployment_mode: remote + host_ip: 192.168.123.178 + kubeconfig_output_dir: "/home/ubuntu/kind-cluster-config" + kubeconfig_filename: op2-kubeconfig.yaml + cluster_name: node-2 + api_server_port: 6444 + oeg_partner_api_root: "http://192.168.123.155:30989" + oeg_avail_zone_notif_link: "http://192.168.123.178:32263/oeg/1.0.0/availability-zones/notifications" + + openop_3: + ansible_host: 192.168.123.155 + ansible_connection: ssh + ansible_user: ubuntu + ansible_ssh_private_key_file: "~/.ssh/keys/openop-dev-vm.key" + ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o ConnectTimeout=300 -o ServerAliveInterval=30 -o IdentitiesOnly=yes' + ansible_python_interpreter: /usr/bin/python3 + deployment_mode: remote + host_ip: 192.168.123.155 + kubeconfig_output_dir: "/home/ubuntu/kind-cluster-config" + kubeconfig_filename: op1-kubeconfig.yaml + cluster_name: node-1 + api_server_port: 6443 + oeg_partner_api_root: "http://192.168.123.178:30989" + oeg_avail_zone_notif_link: "http://192.168.123.155:32263/oeg/1.0.0/availability-zones/notifications" + + children: + openop_dev: + hosts: + openop_1: + op1_nodes: + hosts: + openop_3: + op2_nodes: + hosts: + openop_2: + k8s_clusters: + children: + openop_dev: + op1_nodes: + op2_nodes: diff --git a/mkdocs.yml b/mkdocs.yml index 6308ca4..cd4aee7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,8 +1,8 @@ site_name: Operator Platform Automation -site_url: https://labs.etsi.org/rep/oop/code/integration +site_url: https://op-automation-b0b7e6.pages.i2cat.net site_dir: public -repo_url: https://labs.etsi.org/rep/oop/code/integration -repo_name: oop/code/integration +repo_url: https://gitlab.i2cat.net/areas/software-networks/operator-platform/oop/op-automation +repo_name: areas/software-networks/operator-platform/oop/op-automation copyright: Copyright © 2025 Software Networks theme: -- GitLab From 82795ad2d899514e114bb440e9f1a4d0701f93bf Mon Sep 17 00:00:00 2001 From: Sergio Gimenez Date: Mon, 18 May 2026 11:51:38 +0200 Subject: [PATCH 2/4] Add full-oop but with existing cluster, avoiding kind cluster creation --- ansible/inventory/hosts.local.yml | 63 ++++++++ ansible/inventory/hosts.yml | 25 ++- .../prerequisites/setup-docker.yml | 2 +- .../prerequisites/setup-kubectl.yml | 2 +- .../prerequisites/setup-python-libs.yml | 2 +- ansible/playbooks/scenarios/README.md | 11 ++ .../full_oop_existing_cluster/deploy.yml | 142 ++++++++++++++++++ .../graceful_undeploy.yml | 97 ++++++++++++ .../quick_undeploy.yml | 6 + 9 files changed, 344 insertions(+), 6 deletions(-) create mode 100644 ansible/inventory/hosts.local.yml create mode 100644 ansible/playbooks/scenarios/full_oop_existing_cluster/deploy.yml create mode 100644 ansible/playbooks/scenarios/full_oop_existing_cluster/graceful_undeploy.yml create mode 100644 ansible/playbooks/scenarios/full_oop_existing_cluster/quick_undeploy.yml diff --git a/ansible/inventory/hosts.local.yml b/ansible/inventory/hosts.local.yml new file mode 100644 index 0000000..147b20b --- /dev/null +++ b/ansible/inventory/hosts.local.yml @@ -0,0 +1,63 @@ +--- +all: + hosts: + openop_1: + ansible_host: 192.168.123.188 + ansible_connection: ssh + ansible_user: ubuntu + ansible_ssh_private_key_file: "~/.ssh/keys/openop-dev-vm.key" + ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o ConnectTimeout=300 -o ServerAliveInterval=30 -o IdentitiesOnly=yes' + ansible_python_interpreter: /usr/bin/python3 + deployment_mode: remote + host_ip: 192.168.123.188 + kubeconfig_output_dir: "/home/ubuntu/kind-cluster-config" + kubeconfig_filename: operator-platform-external-kubeconfig.yaml + cluster_name: dev-all-in-one + + openop_2: + ansible_host: 192.168.123.178 + ansible_connection: ssh + ansible_user: ubuntu + ansible_ssh_private_key_file: "~/.ssh/keys/openop-dev-vm.key" + ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o ConnectTimeout=300 -o ServerAliveInterval=30 -o IdentitiesOnly=yes' + ansible_python_interpreter: /usr/bin/python3 + deployment_mode: remote + host_ip: 192.168.123.178 + kubeconfig_output_dir: "/home/ubuntu/kind-cluster-config" + kubeconfig_filename: op2-kubeconfig.yaml + cluster_name: node-2 + api_server_port: 6444 + oeg_partner_api_root: "http://192.168.123.155:30989" + oeg_avail_zone_notif_link: "http://192.168.123.178:32263/oeg/1.0.0/availability-zones/notifications" + + openop_3: + ansible_host: 192.168.123.155 + ansible_connection: ssh + ansible_user: ubuntu + ansible_ssh_private_key_file: "~/.ssh/keys/openop-dev-vm.key" + ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o ConnectTimeout=300 -o ServerAliveInterval=30 -o IdentitiesOnly=yes' + ansible_python_interpreter: /usr/bin/python3 + deployment_mode: remote + host_ip: 192.168.123.155 + kubeconfig_output_dir: "/home/ubuntu/kind-cluster-config" + kubeconfig_filename: op1-kubeconfig.yaml + cluster_name: node-1 + api_server_port: 6443 + oeg_partner_api_root: "http://192.168.123.178:30989" + oeg_avail_zone_notif_link: "http://192.168.123.155:32263/oeg/1.0.0/availability-zones/notifications" + + children: + openop_dev: + hosts: + openop_1: + op1_nodes: + hosts: + openop_3: + op2_nodes: + hosts: + openop_2: + k8s_clusters: + children: + openop_dev: + op1_nodes: + op2_nodes: diff --git a/ansible/inventory/hosts.yml b/ansible/inventory/hosts.yml index 8d14c28..4d7126f 100644 --- a/ansible/inventory/hosts.yml +++ b/ansible/inventory/hosts.yml @@ -17,9 +17,24 @@ all: ansible_python_interpreter: /usr/bin/python3 deployment_mode: remote host_ip: 192.168.123.214 - # kubeconfig_output_dir: "/home/ubuntu/kind-cluster-config" - # kubeconfig_filename: operator-platform-external-kubeconfig.yaml - # cluster_name: dev-all-in-one + cluster_name: sunrise-remote + api_server_port: 16443 + kubeconfig_output_dir: "/home/ubuntu/.kube" + kubeconfig_filename: "config" + + openop_kul: + ansible_host: 193.190.168.50 + ansible_connection: ssh + ansible_user: i2cat + ansible_ssh_private_key_file: "~/.ssh/keys/sn-keypair.pem" + ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o ConnectTimeout=300 -o ServerAliveInterval=30 -o IdentitiesOnly=yes' + ansible_python_interpreter: /usr/bin/python3 + deployment_mode: remote + host_ip: 193.190.168.50 + cluster_name: op2-remote + api_server_port: 16443 + kubeconfig_output_dir: /home/i2cat/.kube + kubeconfig_filename: "config" openop_1: @@ -71,6 +86,10 @@ all: openop_dev: hosts: openop_1: + existing_cluster_nodes: + hosts: + openop_sunrise: + example_op_2: op1_nodes: hosts: openop_3: diff --git a/ansible/playbooks/infrastructure/prerequisites/setup-docker.yml b/ansible/playbooks/infrastructure/prerequisites/setup-docker.yml index 626edf7..cf2ba4f 100644 --- a/ansible/playbooks/infrastructure/prerequisites/setup-docker.yml +++ b/ansible/playbooks/infrastructure/prerequisites/setup-docker.yml @@ -4,7 +4,7 @@ # Usage: ansible-playbook playbooks/infrastructure/prerequisites/setup-docker.yml - name: Install Docker - hosts: k8s_clusters + hosts: "{{ target_hosts | default('k8s_clusters') }}" gather_facts: true become: true diff --git a/ansible/playbooks/infrastructure/prerequisites/setup-kubectl.yml b/ansible/playbooks/infrastructure/prerequisites/setup-kubectl.yml index 606693c..b02f843 100644 --- a/ansible/playbooks/infrastructure/prerequisites/setup-kubectl.yml +++ b/ansible/playbooks/infrastructure/prerequisites/setup-kubectl.yml @@ -4,7 +4,7 @@ # Usage: ansible-playbook playbooks/infrastructure/prerequisites/setup-kubectl.yml - name: Install kubectl - hosts: k8s_clusters + hosts: "{{ target_hosts | default('k8s_clusters') }}" gather_facts: true become: true diff --git a/ansible/playbooks/infrastructure/prerequisites/setup-python-libs.yml b/ansible/playbooks/infrastructure/prerequisites/setup-python-libs.yml index 0cf2d85..fbc23ac 100644 --- a/ansible/playbooks/infrastructure/prerequisites/setup-python-libs.yml +++ b/ansible/playbooks/infrastructure/prerequisites/setup-python-libs.yml @@ -4,7 +4,7 @@ # Usage: ansible-playbook playbooks/infrastructure/prerequisites/setup-python-libs.yml - name: Install Python Libraries - hosts: k8s_clusters + hosts: "{{ target_hosts | default('k8s_clusters') }}" gather_facts: true become: true diff --git a/ansible/playbooks/scenarios/README.md b/ansible/playbooks/scenarios/README.md index db28050..dc5c71b 100644 --- a/ansible/playbooks/scenarios/README.md +++ b/ansible/playbooks/scenarios/README.md @@ -27,6 +27,15 @@ ansible-playbook playbooks/scenarios/dual_oop/deploy.yml --limit openop_3 ansible-playbook playbooks/scenarios/dual_oop/deploy.yml --limit openop_2 ``` +### `full_oop_existing_cluster` + +One complete Operator Platform on a host that already has a running cluster. + +```bash +ansible-playbook playbooks/scenarios/full_oop_existing_cluster/deploy.yml --limit openop_sunrise +ansible-playbook playbooks/scenarios/full_oop_existing_cluster/deploy.yml --limit example_op_2 +``` + ## Undeploy Each scenario provides: @@ -49,6 +58,7 @@ ansible-playbook playbooks/scenarios/dual_oop/quick_undeploy.yml --limit openop_ ## Inventory expectations - `k8s_clusters`: shared host group for common prerequisite playbooks +- `existing_cluster_nodes`: hosts with an already running Kubernetes cluster - `op1_nodes`: first host for `dual_oop` - `op2_nodes`: second host for `dual_oop` @@ -57,3 +67,4 @@ ansible-playbook playbooks/scenarios/dual_oop/quick_undeploy.yml --limit openop_ - Run from the `ansible/` directory. - `secrets.yml` is loaded by `dual_oop` and `full_oop`. - For remote deployments, kubeconfigs are fetched to `~/kind-cluster-configs/`. +- `full_oop_existing_cluster` expects `cluster_name`, `api_server_port`, `kubeconfig_output_dir`, and `kubeconfig_filename` in inventory. diff --git a/ansible/playbooks/scenarios/full_oop_existing_cluster/deploy.yml b/ansible/playbooks/scenarios/full_oop_existing_cluster/deploy.yml new file mode 100644 index 0000000..5caf112 --- /dev/null +++ b/ansible/playbooks/scenarios/full_oop_existing_cluster/deploy.yml @@ -0,0 +1,142 @@ +--- +# Playbook: Full OOP Existing Cluster Deployment +# Description: Deploys complete Operator Platform with a single Federation Manager on an already running Kubernetes cluster +# Usage: ansible-playbook playbooks/scenarios/full_oop_existing_cluster/deploy.yml + +# ========================================== +# Prerequisites Installation +# ========================================== +- import_playbook: ../../infrastructure/prerequisites/install-python-deps.yml + vars: + target_hosts: existing_cluster_nodes +- import_playbook: ../../infrastructure/prerequisites/setup-docker.yml + vars: + target_hosts: existing_cluster_nodes +- import_playbook: ../../infrastructure/prerequisites/setup-kubectl.yml + vars: + target_hosts: existing_cluster_nodes +- import_playbook: ../../infrastructure/prerequisites/setup-python-libs.yml + vars: + target_hosts: existing_cluster_nodes + +# ========================================== +# Full OOP Deployment On Existing Cluster +# ========================================== +- name: Deploy Full OOP on Existing Cluster + hosts: existing_cluster_nodes + gather_facts: true + vars_files: + - ../../../secrets.yml + + vars: + prepare_oop_storage: true + k8s_distribution: existing + k8s_platform: existing + federation_manager_ecp_port: "{{ lite2edge_nodeport }}" + federation_manager_ecp_client_name: "lite2edge" + + pre_tasks: + - name: Display deployment information + ansible.builtin.debug: + msg: | + ========================================== + Full OOP Existing Cluster Deployment + ========================================== + Scenario: Single operator platform on running cluster + Federation Managers: 1 (local only) + Target Host: {{ inventory_hostname }} + + Cluster Access: + Cluster Name: {{ cluster_name }} + Kubeconfig: {{ kubeconfig_output_dir }}/{{ kubeconfig_filename }} + API Server Port: {{ api_server_port }} + ========================================== + + - name: Set kubeconfig facts from inventory + ansible.builtin.set_fact: + kubeconfig_dir: "{{ kubeconfig_output_dir }}" + kind_config_dir: "{{ kubeconfig_output_dir }}" + kubeconfig_path: "{{ kubeconfig_output_dir }}/{{ kubeconfig_filename }}" + + - name: Get control-plane InternalIPs for Prometheus endpoint discovery + ansible.builtin.shell: >- + kubectl --kubeconfig {{ kubeconfig_path }} get nodes + --selector='node-role.kubernetes.io/control-plane' + -o jsonpath='{range .items[*]}{range .status.addresses[?(@.type=="InternalIP")]}{.address}{"\n"}{end}{end}' + register: prometheus_control_plane_ips_result + changed_when: false + + - name: Get worker InternalIPs for Prometheus endpoint discovery + ansible.builtin.shell: >- + kubectl --kubeconfig {{ kubeconfig_path }} get nodes + --selector='!node-role.kubernetes.io/control-plane' + -o jsonpath='{range .items[*]}{range .status.addresses[?(@.type=="InternalIP")]}{.address}{"\n"}{end}{end}' + register: prometheus_worker_ips_result + changed_when: false + + - name: Set Prometheus node IPs from existing cluster + ansible.builtin.set_fact: + prometheus_control_plane_ips: "{{ prometheus_control_plane_ips_result.stdout_lines | reject('equalto', '') | list }}" + prometheus_worker_ips: "{{ prometheus_worker_ips_result.stdout_lines | reject('equalto', '') | list }}" + + roles: + - role: helm + tags: [infrastructure, helm] + + - role: prometheus + tags: [infrastructure, monitoring, prometheus] + + - role: node-feature-discovery + tags: [infrastructure, nfd] + + - role: zot + tags: [oop, zot, registry] + + - role: artefact-manager + tags: [oop, artefact-manager] + + - role: srm + tags: [oop, srm] + + - role: oeg + tags: [oop, oeg] + + - role: federation-manager + tags: [oop, federation-manager] + vars: + federation_manager_remote: false + + - role: homer + tags: [oop, homer, dashboard] + + - role: lite2edge + tags: [edge, lite2edge] + + post_tasks: + - name: Display deployment summary + ansible.builtin.debug: + msg: | + ========================================== + Full OOP Existing Cluster Deployment Complete + ========================================== + + Components Deployed: + ✓ Existing K8s Cluster ({{ cluster_name }}) + ✓ Prometheus Monitoring + ✓ Node Feature Discovery + ✓ Zot Registry + ✓ Artefact Manager + ✓ SRM Controller + ✓ OEG Controller + ✓ Federation Manager (1 instance) + ✓ Homer Dashboard + ✓ Lite2Edge Platform + + Access Points: + Kubeconfig: {{ kubeconfig_path }} + Prometheus: http://{{ host_ip | default('localhost') }}:30090 + Grafana: http://{{ host_ip | default('localhost') }}:30091 + Homer: http://{{ host_ip | default('localhost') }}:{{ homer_nodeport }} + + Existing cluster preserved. + ========================================== diff --git a/ansible/playbooks/scenarios/full_oop_existing_cluster/graceful_undeploy.yml b/ansible/playbooks/scenarios/full_oop_existing_cluster/graceful_undeploy.yml new file mode 100644 index 0000000..80cff4c --- /dev/null +++ b/ansible/playbooks/scenarios/full_oop_existing_cluster/graceful_undeploy.yml @@ -0,0 +1,97 @@ +--- +# Playbook: Graceful Full OOP Existing Cluster Undeployment Scenario +# Description: Removes Full OOP components from an existing cluster without deleting the cluster +# Usage: ansible-playbook playbooks/scenarios/full_oop_existing_cluster/graceful_undeploy.yml + +- name: Graceful Undeploy Full OOP from Existing Cluster + hosts: existing_cluster_nodes + gather_facts: true + + vars: + lite2edge_state: absent + homer_state: absent + federation_manager_state: absent + oeg_state: absent + srm_state: absent + artefact_manager_state: absent + i2edge_state: absent + zot_state: absent + node_feature_discovery_state: absent + prometheus_state: absent + helm_state: absent + k8s_distribution: existing + k8s_platform: existing + + vars_prompt: + - name: confirm_deletion + prompt: "Are you sure you want to undeploy the Full OOP components from the existing cluster? The cluster itself will be kept. (yes/no)" + default: "no" + private: false + + pre_tasks: + - name: Display undeployment information + ansible.builtin.debug: + msg: | + ========================================== + Graceful Full OOP Existing Cluster Undeployment + ========================================== + Target Host: {{ inventory_hostname }} + Cluster Name: {{ cluster_name }} + + This removes platform components only. + Existing Kubernetes cluster stays running. + ========================================== + + - name: Exit if deletion not confirmed + ansible.builtin.meta: end_play + when: confirm_deletion | lower not in ['yes', 'y'] + + - name: Set kubeconfig facts from inventory + ansible.builtin.set_fact: + kind_config_dir: "{{ kubeconfig_output_dir }}" + kubeconfig_path: "{{ kubeconfig_output_dir }}/{{ kubeconfig_filename }}" + + roles: + - role: lite2edge + tags: [edge, lite2edge] + + - role: homer + tags: [oop, homer, dashboard] + + - role: federation-manager + tags: [oop, federation-manager] + + - role: oeg + tags: [oop, oeg] + + - role: srm + tags: [oop, srm] + + - role: artefact-manager + tags: [oop, artefact-manager] + + - role: i2edge + tags: [oop, i2edge] + + - role: zot + tags: [oop, zot, registry] + + - role: node-feature-discovery + tags: [infrastructure, nfd] + + - role: prometheus + tags: [infrastructure, monitoring, prometheus] + + - role: helm + tags: [infrastructure, helm] + + post_tasks: + - name: Display undeployment summary + ansible.builtin.debug: + msg: | + ========================================== + Full OOP Existing Cluster Undeployment Complete + ========================================== + Removed platform components from {{ cluster_name }}. + Existing Kubernetes cluster preserved. + ========================================== diff --git a/ansible/playbooks/scenarios/full_oop_existing_cluster/quick_undeploy.yml b/ansible/playbooks/scenarios/full_oop_existing_cluster/quick_undeploy.yml new file mode 100644 index 0000000..55bd312 --- /dev/null +++ b/ansible/playbooks/scenarios/full_oop_existing_cluster/quick_undeploy.yml @@ -0,0 +1,6 @@ +--- +# Playbook: Quick Full OOP Existing Cluster Undeployment Scenario +# Description: Alias for graceful removal because the underlying cluster must be preserved +# Usage: ansible-playbook playbooks/scenarios/full_oop_existing_cluster/quick_undeploy.yml + +- import_playbook: ./graceful_undeploy.yml -- GitLab From 5771b2430431ff321389245f4ca865f954aee8b4 Mon Sep 17 00:00:00 2001 From: Sergio Gimenez Date: Mon, 18 May 2026 12:24:44 +0200 Subject: [PATCH 3/4] Works full oop with existing cluster --- .../federation-manager/defaults/main.yml | 12 +- .../roles/federation-manager/tasks/deploy.yml | 194 ++++++++++++++ .../templates/mongo-db.yaml.j2 | 39 ++- ansible/roles/lite2edge/defaults/main.yml | 2 +- ansible/roles/srm/defaults/main.yml | 2 +- ansible/roles/zot/defaults/main.yml | 10 +- ansible/roles/zot/tasks/install.yml | 241 ++++++++++++++++++ .../roles/zot/templates/zot-values.yaml.j2 | 14 +- 8 files changed, 500 insertions(+), 14 deletions(-) diff --git a/ansible/roles/federation-manager/defaults/main.yml b/ansible/roles/federation-manager/defaults/main.yml index e6e6d95..f0d12ba 100644 --- a/ansible/roles/federation-manager/defaults/main.yml +++ b/ansible/roles/federation-manager/defaults/main.yml @@ -57,8 +57,18 @@ federation_manager_keycloak_realm: "federation" federation_manager_mongodb_image: "mongo" federation_manager_mongodb_tag: "6.0" federation_manager_mongodb_nodeport: 30017 -federation_manager_mongodb_data_dir: "/tmp/db" +federation_manager_mongodb_persistence_enabled: true +federation_manager_mongodb_persistence_size: 1Gi +federation_manager_mongodb_persistence_access_mode: ReadWriteOnce +federation_manager_mongodb_persistence_storage_class: manual +federation_manager_mongodb_persistence_create_pv: true +federation_manager_mongodb_persistence_use_default_storage_class: true +federation_manager_mongodb_persistence_node_name: "" +federation_manager_mongodb_persistence_auto_configure: true +federation_manager_mongodb_preserve_data: true +federation_manager_mongodb_data_dir: "/mnt/data/federation-manager-mongodb-{{ federation_manager_variant }}" federation_manager_mongodb_cleanup_data: true +federation_manager_mongodb_init_cleanup_lock: true # Federation Manager MongoDB settings federation_manager_mongodb_host: "mongodb" diff --git a/ansible/roles/federation-manager/tasks/deploy.yml b/ansible/roles/federation-manager/tasks/deploy.yml index b4484f6..18b106c 100644 --- a/ansible/roles/federation-manager/tasks/deploy.yml +++ b/ansible/roles/federation-manager/tasks/deploy.yml @@ -40,6 +40,200 @@ kubeconfig: "{{ federation_manager_kubeconfig }}" when: gitlab_token is defined +- name: Get available storage classes + kubernetes.core.k8s_info: + api_version: storage.k8s.io/v1 + kind: StorageClass + kubeconfig: "{{ federation_manager_kubeconfig }}" + register: federation_manager_storage_classes + when: + - federation_manager_mongodb_persistence_enabled + - federation_manager_mongodb_persistence_auto_configure + +- name: Detect dynamic storage classes + ansible.builtin.set_fact: + federation_manager_dynamic_storage_classes: >- + {{ (federation_manager_storage_classes.resources | default([])) + | rejectattr('provisioner', 'equalto', 'kubernetes.io/no-provisioner') + | map(attribute='metadata.name') + | list }} + when: + - federation_manager_mongodb_persistence_enabled + - federation_manager_mongodb_persistence_auto_configure + +- name: Detect local storage classes + ansible.builtin.set_fact: + federation_manager_local_storage_classes: >- + {{ (federation_manager_storage_classes.resources | default([])) + | selectattr('provisioner', 'equalto', 'kubernetes.io/no-provisioner') + | map(attribute='metadata.name') + | list }} + when: + - federation_manager_mongodb_persistence_enabled + - federation_manager_mongodb_persistence_auto_configure + +- name: Detect default storage class + ansible.builtin.shell: | + kubectl get storageclass -o jsonpath='{range .items[?(@.metadata.annotations.storageclass\.kubernetes\.io/is-default-class=="true")]}{.metadata.name}{"\n"}{end}' | head -n 1 + args: + executable: /bin/bash + register: federation_manager_default_storage_class_result + changed_when: false + failed_when: false + environment: + KUBECONFIG: "{{ federation_manager_kubeconfig }}" + when: + - federation_manager_mongodb_persistence_enabled + - federation_manager_mongodb_persistence_auto_configure + +- name: Set default storage class fact + ansible.builtin.set_fact: + federation_manager_default_storage_class: "{{ federation_manager_default_storage_class_result.stdout | trim }}" + when: + - federation_manager_mongodb_persistence_enabled + - federation_manager_mongodb_persistence_auto_configure + +- name: Select default dynamic storage class + ansible.builtin.set_fact: + federation_manager_mongodb_persistence_use_default_storage_class: true + federation_manager_mongodb_persistence_storage_class: "{{ federation_manager_default_storage_class }}" + when: + - federation_manager_mongodb_persistence_enabled + - federation_manager_mongodb_persistence_auto_configure + - federation_manager_dynamic_storage_classes | length > 0 + - federation_manager_default_storage_class | length > 0 + +- name: Select non-default dynamic storage class + ansible.builtin.set_fact: + federation_manager_mongodb_persistence_use_default_storage_class: false + federation_manager_mongodb_persistence_storage_class: "{{ federation_manager_dynamic_storage_classes[0] }}" + when: + - federation_manager_mongodb_persistence_enabled + - federation_manager_mongodb_persistence_auto_configure + - federation_manager_dynamic_storage_classes | length > 0 + - federation_manager_default_storage_class | length == 0 + +- name: Get schedulable worker node name + ansible.builtin.command: > + kubectl get nodes + -l '!node-role.kubernetes.io/control-plane' + --field-selector spec.unschedulable!=true + -o jsonpath='{.items[0].metadata.name}' + register: federation_manager_worker_node_name_result + changed_when: false + failed_when: false + environment: + KUBECONFIG: "{{ federation_manager_kubeconfig }}" + when: + - federation_manager_mongodb_persistence_enabled + - federation_manager_mongodb_persistence_auto_configure + - federation_manager_dynamic_storage_classes | length == 0 + +- name: Get schedulable node name fallback + ansible.builtin.command: > + kubectl get nodes + --field-selector spec.unschedulable!=true + -o jsonpath='{.items[0].metadata.name}' + register: federation_manager_node_name_result + changed_when: false + failed_when: false + environment: + KUBECONFIG: "{{ federation_manager_kubeconfig }}" + when: + - federation_manager_mongodb_persistence_enabled + - federation_manager_mongodb_persistence_auto_configure + - federation_manager_dynamic_storage_classes | length == 0 + - federation_manager_worker_node_name_result.stdout | default('') | length == 0 + +- name: Select node for local storage + ansible.builtin.set_fact: + federation_manager_mongodb_persistence_node_name: >- + {{ (federation_manager_worker_node_name_result.stdout | default('') | length > 0) + | ternary(federation_manager_worker_node_name_result.stdout, federation_manager_node_name_result.stdout | default('')) }} + when: + - federation_manager_mongodb_persistence_enabled + - federation_manager_mongodb_persistence_auto_configure + - federation_manager_dynamic_storage_classes | length == 0 + +- name: Select local storage class + ansible.builtin.set_fact: + federation_manager_mongodb_persistence_use_default_storage_class: false + federation_manager_mongodb_persistence_create_pv: true + federation_manager_mongodb_persistence_storage_class: >- + {{ (federation_manager_local_storage_classes | length > 0) | ternary(federation_manager_local_storage_classes[0], 'local-storage') }} + when: + - federation_manager_mongodb_persistence_enabled + - federation_manager_mongodb_persistence_auto_configure + - federation_manager_dynamic_storage_classes | length == 0 + - federation_manager_mongodb_persistence_node_name | length > 0 + +- name: Get existing MongoDB deployment availability + ansible.builtin.command: > + kubectl get deployment mongodb + --namespace {{ federation_manager_namespace }} + -o jsonpath='{.status.availableReplicas}' + register: federation_manager_mongodb_available_result + changed_when: false + failed_when: false + environment: + KUBECONFIG: "{{ federation_manager_kubeconfig }}" + +- name: Get existing MongoDB PV details + ansible.builtin.command: > + kubectl get pv mongodb-{{ federation_manager_variant }} + -o jsonpath='{.spec.hostPath.path}|{.spec.storageClassName}|{range .spec.nodeAffinity.required.nodeSelectorTerms[0].matchExpressions[0].values[*]}{.}{end}|{.status.phase}' + register: federation_manager_mongodb_pv_details_result + changed_when: false + failed_when: false + environment: + KUBECONFIG: "{{ federation_manager_kubeconfig }}" + when: + - federation_manager_mongodb_persistence_enabled + - federation_manager_mongodb_persistence_create_pv + - not federation_manager_mongodb_persistence_use_default_storage_class + +- name: Determine if MongoDB storage reset is required + ansible.builtin.set_fact: + federation_manager_mongodb_reset_required: >- + {{ + federation_manager_mongodb_pv_details_result.rc == 0 and ( + (federation_manager_mongodb_pv_details_result.stdout.split('|') | last) in ['Released', 'Failed'] or + (federation_manager_mongodb_pv_details_result.stdout.split('|') | first) != federation_manager_mongodb_data_dir or + (federation_manager_mongodb_pv_details_result.stdout.split('|'))[1] != federation_manager_mongodb_persistence_storage_class or + ( + federation_manager_mongodb_persistence_node_name | length > 0 and + (federation_manager_mongodb_pv_details_result.stdout.split('|'))[2] != federation_manager_mongodb_persistence_node_name + ) + ) and (federation_manager_mongodb_available_result.stdout | default('0') | trim) != '1' + }} + when: + - federation_manager_mongodb_persistence_enabled + - federation_manager_mongodb_persistence_create_pv + - not federation_manager_mongodb_persistence_use_default_storage_class + +- name: Reset broken MongoDB storage objects before redeploy + kubernetes.core.k8s: + state: absent + api_version: "{{ item.api_version }}" + kind: "{{ item.kind }}" + namespace: "{{ item.namespace | default(omit) }}" + name: "{{ item.name }}" + kubeconfig: "{{ federation_manager_kubeconfig }}" + loop: + - api_version: apps/v1 + kind: Deployment + namespace: "{{ federation_manager_namespace }}" + name: mongodb + - api_version: v1 + kind: PersistentVolumeClaim + namespace: "{{ federation_manager_namespace }}" + name: mongodb + - api_version: v1 + kind: PersistentVolume + name: "mongodb-{{ federation_manager_variant }}" + when: + - federation_manager_mongodb_reset_required | default(false) + # ========================================== # MongoDB Deployment # ========================================== diff --git a/ansible/roles/federation-manager/templates/mongo-db.yaml.j2 b/ansible/roles/federation-manager/templates/mongo-db.yaml.j2 index 7e0ca28..a0cdd56 100644 --- a/ansible/roles/federation-manager/templates/mongo-db.yaml.j2 +++ b/ansible/roles/federation-manager/templates/mongo-db.yaml.j2 @@ -1,18 +1,31 @@ --- +{% if federation_manager_mongodb_persistence_enabled and federation_manager_mongodb_persistence_create_pv and not federation_manager_mongodb_persistence_use_default_storage_class %} kind: PersistentVolume apiVersion: v1 metadata: name: mongodb-{{ federation_manager_variant }} spec: capacity: - storage: 1Gi + storage: {{ federation_manager_mongodb_persistence_size }} hostPath: path: {{ federation_manager_mongodb_data_dir }} + type: DirectoryOrCreate accessModes: - - ReadWriteOnce + - {{ federation_manager_mongodb_persistence_access_mode }} persistentVolumeReclaimPolicy: Retain - storageClassName: manual + storageClassName: {{ federation_manager_mongodb_persistence_storage_class }} +{% if federation_manager_mongodb_persistence_node_name | length > 0 %} + nodeAffinity: + required: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/hostname + operator: In + values: + - {{ federation_manager_mongodb_persistence_node_name }} +{% endif %} --- +{% endif %} kind: PersistentVolumeClaim apiVersion: v1 metadata: @@ -20,12 +33,14 @@ metadata: namespace: {{ federation_manager_namespace }} spec: accessModes: - - ReadWriteOnce + - {{ federation_manager_mongodb_persistence_access_mode }} resources: requests: - storage: 1Gi + storage: {{ federation_manager_mongodb_persistence_size }} +{% if not federation_manager_mongodb_persistence_use_default_storage_class %} volumeName: mongodb-{{ federation_manager_variant }} - storageClassName: manual + storageClassName: {{ federation_manager_mongodb_persistence_storage_class }} +{% endif %} --- kind: Deployment apiVersion: apps/v1 @@ -46,6 +61,18 @@ spec: - name: storage persistentVolumeClaim: claimName: mongodb +{% if federation_manager_mongodb_init_cleanup_lock %} + initContainers: + - name: mongo-clean-lock + image: '{{ federation_manager_mongodb_image }}:{{ federation_manager_mongodb_tag }}' + command: + - /bin/sh + - -c + - rm -f /data/db/mongod.lock /data/db/WiredTiger.lock + volumeMounts: + - name: storage + mountPath: /data/db +{% endif %} containers: - name: mongodb image: '{{ federation_manager_mongodb_image }}:{{ federation_manager_mongodb_tag }}' diff --git a/ansible/roles/lite2edge/defaults/main.yml b/ansible/roles/lite2edge/defaults/main.yml index d9e8e7e..0d671d8 100644 --- a/ansible/roles/lite2edge/defaults/main.yml +++ b/ansible/roles/lite2edge/defaults/main.yml @@ -13,7 +13,7 @@ lite2edge_image_pull_secret: gitlab-registry-secret lite2edge_registry_email: "" # Image Configuration -lite2edge_image: "gitlab.i2cat.net:5050/areas/software-networks/operator-platform/lite2edge:fix-deploy-path-appid-cdab2ef" +lite2edge_image: "gitlab.i2cat.net:5050/areas/software-networks/operator-platform/lite2edge:latest" lite2edge_image_pull_policy: IfNotPresent # State (present/absent) diff --git a/ansible/roles/srm/defaults/main.yml b/ansible/roles/srm/defaults/main.yml index fa0bae7..6a247c0 100644 --- a/ansible/roles/srm/defaults/main.yml +++ b/ansible/roles/srm/defaults/main.yml @@ -10,7 +10,7 @@ srm_controller_name: srmcontroller srm_controller_replicas: 1 # srm_controller_image_repository: "labs.etsi.org:5050/oop/code/service-resource-manager" srm_controller_image_repository: "gitlab.i2cat.net:5050/areas/software-networks/operator-platform/oop/service-resource-manager" -srm_controller_image_tag: "feature-srm-fm-integration-and-spec-compliance-d1f8bbd-tfsrc" +srm_controller_image_tag: "1.1" srm_controller_image_pull_policy: Always srm_image_pull_secret_enabled: true srm_image_pull_secret_name: "gitlab-registry-secret" diff --git a/ansible/roles/zot/defaults/main.yml b/ansible/roles/zot/defaults/main.yml index 145027f..78476db 100644 --- a/ansible/roles/zot/defaults/main.yml +++ b/ansible/roles/zot/defaults/main.yml @@ -26,5 +26,13 @@ zot_install_timeout: 300 zot_service_type: NodePort zot_http_nodeport: 30050 zot_persistence_enabled: true -zot_storage_class: "standard" # defaults to standard/local-path in kind +zot_persistence_access_mode: ReadWriteOnce +zot_storage_class: "standard" # defaults to standard/local-path on clusters with dynamic provisioning zot_persistence_size: 10Gi +zot_persistence_host_path: /mnt/data/zot +zot_persistence_create_pv: true +zot_persistence_use_default_storage_class: true +zot_persistence_node_name: "" +zot_persistence_auto_configure: true +zot_pvc_create: true +zot_existing_pvc_name: "{{ zot_release_name }}-pvc-{{ zot_release_name }}-0" diff --git a/ansible/roles/zot/tasks/install.yml b/ansible/roles/zot/tasks/install.yml index 1079614..d17f9b4 100644 --- a/ansible/roles/zot/tasks/install.yml +++ b/ansible/roles/zot/tasks/install.yml @@ -25,6 +25,247 @@ name: "{{ zot_namespace }}" kubeconfig: "{{ zot_kubeconfig }}" +- name: Get available storage classes + kubernetes.core.k8s_info: + api_version: storage.k8s.io/v1 + kind: StorageClass + kubeconfig: "{{ zot_kubeconfig }}" + register: zot_storage_classes + when: + - zot_persistence_enabled + - zot_persistence_auto_configure + +- name: Detect dynamic storage classes + ansible.builtin.set_fact: + zot_dynamic_storage_classes: >- + {{ (zot_storage_classes.resources | default([])) + | rejectattr('provisioner', 'equalto', 'kubernetes.io/no-provisioner') + | map(attribute='metadata.name') + | list }} + when: + - zot_persistence_enabled + - zot_persistence_auto_configure + +- name: Detect local storage classes + ansible.builtin.set_fact: + zot_local_storage_classes: >- + {{ (zot_storage_classes.resources | default([])) + | selectattr('provisioner', 'equalto', 'kubernetes.io/no-provisioner') + | map(attribute='metadata.name') + | list }} + when: + - zot_persistence_enabled + - zot_persistence_auto_configure + +- name: Detect default storage class + ansible.builtin.shell: | + kubectl get storageclass -o jsonpath='{range .items[?(@.metadata.annotations.storageclass\.kubernetes\.io/is-default-class=="true")]}{.metadata.name}{"\n"}{end}' | head -n 1 + args: + executable: /bin/bash + register: zot_default_storage_class_result + changed_when: false + failed_when: false + environment: + KUBECONFIG: "{{ zot_kubeconfig }}" + when: + - zot_persistence_enabled + - zot_persistence_auto_configure + +- name: Set default storage class fact + ansible.builtin.set_fact: + zot_default_storage_class: "{{ zot_default_storage_class_result.stdout | trim }}" + when: + - zot_persistence_enabled + - zot_persistence_auto_configure + +- name: Select default dynamic storage class + ansible.builtin.set_fact: + zot_persistence_use_default_storage_class: true + zot_pvc_create: true + zot_storage_class: "{{ zot_default_storage_class }}" + when: + - zot_persistence_enabled + - zot_persistence_auto_configure + - zot_dynamic_storage_classes | length > 0 + - zot_default_storage_class | length > 0 + +- name: Select non-default dynamic storage class + ansible.builtin.set_fact: + zot_persistence_use_default_storage_class: false + zot_pvc_create: true + zot_storage_class: "{{ zot_dynamic_storage_classes[0] }}" + when: + - zot_persistence_enabled + - zot_persistence_auto_configure + - zot_dynamic_storage_classes | length > 0 + - zot_default_storage_class | length == 0 + +- name: Get schedulable worker node name + ansible.builtin.command: > + kubectl get nodes + -l '!node-role.kubernetes.io/control-plane' + --field-selector spec.unschedulable!=true + -o jsonpath='{.items[0].metadata.name}' + register: zot_worker_node_name_result + changed_when: false + failed_when: false + environment: + KUBECONFIG: "{{ zot_kubeconfig }}" + when: + - zot_persistence_enabled + - zot_persistence_auto_configure + - zot_dynamic_storage_classes | length == 0 + +- name: Get schedulable node name fallback + ansible.builtin.command: > + kubectl get nodes + --field-selector spec.unschedulable!=true + -o jsonpath='{.items[0].metadata.name}' + register: zot_node_name_result + changed_when: false + failed_when: false + environment: + KUBECONFIG: "{{ zot_kubeconfig }}" + when: + - zot_persistence_enabled + - zot_persistence_auto_configure + - zot_dynamic_storage_classes | length == 0 + - zot_worker_node_name_result.stdout | default('') | length == 0 + +- name: Select node for local storage + ansible.builtin.set_fact: + zot_persistence_node_name: >- + {{ (zot_worker_node_name_result.stdout | default('') | length > 0) + | ternary(zot_worker_node_name_result.stdout, zot_node_name_result.stdout | default('')) }} + when: + - zot_persistence_enabled + - zot_persistence_auto_configure + - zot_dynamic_storage_classes | length == 0 + +- name: Select local storage class + ansible.builtin.set_fact: + zot_persistence_use_default_storage_class: false + zot_persistence_create_pv: true + zot_pvc_create: false + zot_storage_class: >- + {{ (zot_local_storage_classes | length > 0) | ternary(zot_local_storage_classes[0], 'local-storage') }} + when: + - zot_persistence_enabled + - zot_persistence_auto_configure + - zot_dynamic_storage_classes | length == 0 + - zot_persistence_node_name | length > 0 + +- name: Get existing Zot PV + kubernetes.core.k8s_info: + api_version: v1 + kind: PersistentVolume + name: zot-pv + kubeconfig: "{{ zot_kubeconfig }}" + register: zot_pv_info + failed_when: false + when: + - zot_persistence_enabled + - zot_persistence_create_pv + - not zot_pvc_create + +- name: Set desired Zot PVC storage class + ansible.builtin.set_fact: + zot_pvc_storage_class_desired: >- + {{ (zot_persistence_use_default_storage_class | bool) | ternary('', zot_storage_class) }} + when: zot_persistence_enabled + +- name: Get existing Zot PVC + kubernetes.core.k8s_info: + api_version: v1 + kind: PersistentVolumeClaim + namespace: "{{ zot_namespace }}" + name: "{{ zot_existing_pvc_name }}" + kubeconfig: "{{ zot_kubeconfig }}" + register: zot_pvc_info + failed_when: false + when: zot_persistence_enabled + +- name: Force delete Zot PVC when storage class changes + ansible.builtin.shell: | + current_sc="$(kubectl -n {{ zot_namespace }} get pvc {{ zot_existing_pvc_name }} -o jsonpath='{.spec.storageClassName}' 2>/dev/null || true)" + if [ "$current_sc" != "{{ zot_pvc_storage_class_desired }}" ]; then + kubectl -n {{ zot_namespace }} patch pvc {{ zot_existing_pvc_name }} --type=json -p='[{"op":"remove","path":"/metadata/finalizers"}]' || true + kubectl -n {{ zot_namespace }} delete pvc {{ zot_existing_pvc_name }} --ignore-not-found + fi + register: zot_force_delete_pvc + changed_when: zot_force_delete_pvc.rc == 0 and (zot_pvc_info.resources | default([]) | length > 0) + failed_when: false + environment: + KUBECONFIG: "{{ zot_kubeconfig }}" + when: zot_persistence_enabled + +- name: Delete released Zot PV + kubernetes.core.k8s: + state: absent + api_version: v1 + kind: PersistentVolume + name: zot-pv + kubeconfig: "{{ zot_kubeconfig }}" + when: + - zot_persistence_enabled + - zot_persistence_create_pv + - not zot_pvc_create + - zot_pv_info.resources is defined + - zot_pv_info.resources | length > 0 + - zot_pv_info.resources[0].status.phase in ['Released', 'Failed'] + +- name: Create Zot PersistentVolume + kubernetes.core.k8s: + state: present + definition: + apiVersion: v1 + kind: PersistentVolume + metadata: + name: zot-pv + spec: + capacity: + storage: "{{ zot_persistence_size }}" + accessModes: + - "{{ zot_persistence_access_mode }}" + storageClassName: "{{ zot_storage_class }}" + nodeAffinity: + required: + nodeSelectorTerms: + - matchExpressions: + - key: kubernetes.io/hostname + operator: In + values: + - "{{ zot_persistence_node_name }}" + hostPath: + path: "{{ zot_persistence_host_path }}" + kubeconfig: "{{ zot_kubeconfig }}" + when: + - zot_persistence_enabled + - zot_persistence_create_pv + - not zot_pvc_create + - zot_persistence_node_name | length > 0 + +- name: Create Zot PersistentVolumeClaim + kubernetes.core.k8s: + state: present + definition: + apiVersion: v1 + kind: PersistentVolumeClaim + metadata: + name: "{{ zot_existing_pvc_name }}" + namespace: "{{ zot_namespace }}" + spec: + accessModes: + - "{{ zot_persistence_access_mode }}" + resources: + requests: + storage: "{{ zot_persistence_size }}" + storageClassName: "{{ zot_storage_class }}" + kubeconfig: "{{ zot_kubeconfig }}" + when: + - zot_persistence_enabled + - not zot_pvc_create + - name: Create Zot values file from template ansible.builtin.template: src: zot-values.yaml.j2 diff --git a/ansible/roles/zot/templates/zot-values.yaml.j2 b/ansible/roles/zot/templates/zot-values.yaml.j2 index 0f12ef7..93a650c 100644 --- a/ansible/roles/zot/templates/zot-values.yaml.j2 +++ b/ansible/roles/zot/templates/zot-values.yaml.j2 @@ -15,10 +15,16 @@ service: port: 5000 nodePort: {{ zot_http_nodeport }} -persistence: - enabled: {{ zot_persistence_enabled | lower }} - storageClass: {{ zot_storage_class }} - size: {{ zot_persistence_size }} +persistence: {{ zot_persistence_enabled | lower }} +pvc: + create: {{ zot_pvc_create | lower }} + name: {{ 'null' if zot_pvc_create else (zot_existing_pvc_name | to_json) }} + accessModes: ["{{ zot_persistence_access_mode }}"] + storage: {{ zot_persistence_size }} + storageClassName: {{ 'null' if zot_persistence_use_default_storage_class else (zot_storage_class | to_json) }} + +strategy: + type: {% if zot_persistence_enabled and not zot_pvc_create %}Recreate{% else %}RollingUpdate{% endif %} mountConfig: true configFiles: -- GitLab From 1a9b7814eae7c92822fd4b751354b321c564a8c6 Mon Sep 17 00:00:00 2001 From: Sergio Gimenez Date: Mon, 18 May 2026 13:43:05 +0200 Subject: [PATCH 4/4] Deployment works in existing cluster + merged OOP core services in a single namespace --- ansible/inventory/group_vars/all/srm.yml | 4 +- ansible/inventory/hosts.yml | 8 ++- ansible/playbooks/scenarios/README.md | 2 +- .../playbooks/scenarios/dual_oop/deploy.yml | 10 +++ .../scenarios/dual_oop/graceful_undeploy.yml | 10 +++ .../playbooks/scenarios/full_oop/deploy.yml | 20 ++++-- .../scenarios/full_oop/graceful_undeploy.yml | 5 ++ .../full_oop_existing_cluster/deploy.yml | 72 +++++++++++++------ .../graceful_undeploy.yml | 25 +++++++ .../quick_undeploy.yml | 6 -- .../roles/artefact-manager/defaults/main.yml | 2 +- .../templates/artefact-manager.yaml.j2 | 4 +- .../federation-manager/defaults/main.yml | 8 +-- .../roles/federation-manager/tasks/deploy.yml | 39 +++------- .../federation-manager/tasks/undeploy.yml | 30 +++++--- ansible/roles/homer/defaults/main.yml | 2 +- ansible/roles/lite2edge/tasks/undeploy.yml | 8 +-- ansible/roles/oeg/defaults/main.yml | 6 +- ansible/roles/oeg/tasks/deploy.yml | 56 ++++++++------- ansible/roles/srm/defaults/main.yml | 5 +- ansible/roles/zot/defaults/main.yml | 2 +- ansible/roles/zot/tasks/install.yml | 22 +++--- ansible/roles/zot/tasks/undeploy.yml | 38 ++++++++++ 23 files changed, 255 insertions(+), 129 deletions(-) delete mode 100644 ansible/playbooks/scenarios/full_oop_existing_cluster/quick_undeploy.yml diff --git a/ansible/inventory/group_vars/all/srm.yml b/ansible/inventory/group_vars/all/srm.yml index 8558fda..2d78cbc 100644 --- a/ansible/inventory/group_vars/all/srm.yml +++ b/ansible/inventory/group_vars/all/srm.yml @@ -12,8 +12,8 @@ oop_namespace: oop # Service NodePort srm_nodeport: 32415 -# SRM image (i2cat registry) -srm_controller_image_repository: "gitlab.i2cat.net:5050/areas/software-networks/operator-platform/oop/service-resource-manager" +# SRM image (ETSI registry) +srm_controller_image_repository: "labs.etsi.org:5050/oop/code/service-resource-manager" srm_controller_image_tag: "1.1" # SRM EdgeCloud adapter configuration diff --git a/ansible/inventory/hosts.yml b/ansible/inventory/hosts.yml index 4d7126f..af5953b 100644 --- a/ansible/inventory/hosts.yml +++ b/ansible/inventory/hosts.yml @@ -21,12 +21,13 @@ all: api_server_port: 16443 kubeconfig_output_dir: "/home/ubuntu/.kube" kubeconfig_filename: "config" + oop_deployment_profile: lite2edge openop_kul: ansible_host: 193.190.168.50 ansible_connection: ssh ansible_user: i2cat - ansible_ssh_private_key_file: "~/.ssh/keys/sn-keypair.pem" + ansible_ssh_private_key_file: "~/.ssh/keys/sn-keypair" ansible_ssh_common_args: '-o StrictHostKeyChecking=no -o ConnectTimeout=300 -o ServerAliveInterval=30 -o IdentitiesOnly=yes' ansible_python_interpreter: /usr/bin/python3 deployment_mode: remote @@ -35,6 +36,9 @@ all: api_server_port: 16443 kubeconfig_output_dir: /home/i2cat/.kube kubeconfig_filename: "config" + oop_deployment_profile: kubernetes + deploy_prometheus: false + skip_prerequisites: true openop_1: @@ -89,7 +93,7 @@ all: existing_cluster_nodes: hosts: openop_sunrise: - example_op_2: + openop_kul: op1_nodes: hosts: openop_3: diff --git a/ansible/playbooks/scenarios/README.md b/ansible/playbooks/scenarios/README.md index dc5c71b..e4d3607 100644 --- a/ansible/playbooks/scenarios/README.md +++ b/ansible/playbooks/scenarios/README.md @@ -33,7 +33,7 @@ One complete Operator Platform on a host that already has a running cluster. ```bash ansible-playbook playbooks/scenarios/full_oop_existing_cluster/deploy.yml --limit openop_sunrise -ansible-playbook playbooks/scenarios/full_oop_existing_cluster/deploy.yml --limit example_op_2 +ansible-playbook playbooks/scenarios/full_oop_existing_cluster/deploy.yml --limit openop_kul ``` ## Undeploy diff --git a/ansible/playbooks/scenarios/dual_oop/deploy.yml b/ansible/playbooks/scenarios/dual_oop/deploy.yml index 56f2537..246947a 100644 --- a/ansible/playbooks/scenarios/dual_oop/deploy.yml +++ b/ansible/playbooks/scenarios/dual_oop/deploy.yml @@ -24,6 +24,11 @@ vars: # OP1-specific configuration + oop_core_namespace: oop + oop_federation_manager_namespace: "{{ oop_core_namespace }}" + oop_artefact_manager_namespace: "{{ oop_core_namespace }}" + oop_zot_namespace: "{{ oop_core_namespace }}" + oop_homer_namespace: "{{ oop_core_namespace }}" kind_cluster_name: "op1" cluster_name: "{{ kind_cluster_name }}" worker_nodes: 0 @@ -155,6 +160,11 @@ vars: # OP2-specific configuration + oop_core_namespace: oop + oop_federation_manager_namespace: "{{ oop_core_namespace }}" + oop_artefact_manager_namespace: "{{ oop_core_namespace }}" + oop_zot_namespace: "{{ oop_core_namespace }}" + oop_homer_namespace: "{{ oop_core_namespace }}" kind_cluster_name: "op2" cluster_name: "{{ kind_cluster_name }}" worker_nodes: 0 diff --git a/ansible/playbooks/scenarios/dual_oop/graceful_undeploy.yml b/ansible/playbooks/scenarios/dual_oop/graceful_undeploy.yml index 1d55576..aab8c16 100644 --- a/ansible/playbooks/scenarios/dual_oop/graceful_undeploy.yml +++ b/ansible/playbooks/scenarios/dual_oop/graceful_undeploy.yml @@ -13,6 +13,11 @@ vars: # OP1-specific configuration + oop_core_namespace: oop + oop_federation_manager_namespace: "{{ oop_core_namespace }}" + oop_artefact_manager_namespace: "{{ oop_core_namespace }}" + oop_zot_namespace: "{{ oop_core_namespace }}" + oop_homer_namespace: "{{ oop_core_namespace }}" kind_cluster_name: "op1" cluster_name: "{{ kind_cluster_name }}" kubeconfig_filename: "op1-kubeconfig.yaml" @@ -131,6 +136,11 @@ vars: # OP2-specific configuration + oop_core_namespace: oop + oop_federation_manager_namespace: "{{ oop_core_namespace }}" + oop_artefact_manager_namespace: "{{ oop_core_namespace }}" + oop_zot_namespace: "{{ oop_core_namespace }}" + oop_homer_namespace: "{{ oop_core_namespace }}" kind_cluster_name: "op2" cluster_name: "{{ kind_cluster_name }}" kubeconfig_filename: "op2-kubeconfig.yaml" diff --git a/ansible/playbooks/scenarios/full_oop/deploy.yml b/ansible/playbooks/scenarios/full_oop/deploy.yml index 680f2c5..8df6ef3 100644 --- a/ansible/playbooks/scenarios/full_oop/deploy.yml +++ b/ansible/playbooks/scenarios/full_oop/deploy.yml @@ -19,12 +19,22 @@ gather_facts: true vars_files: - ../../../secrets.yml + - ../../../inventory/profiles/{{ oop_deployment_profile | default('lite2edge') }}.yml vars: # Scenario-specific configuration + oop_core_namespace: oop + oop_federation_manager_namespace: "{{ oop_core_namespace }}" + oop_artefact_manager_namespace: "{{ oop_core_namespace }}" + oop_zot_namespace: "{{ oop_core_namespace }}" + oop_homer_namespace: "{{ oop_core_namespace }}" prepare_oop_storage: true - federation_manager_ecp_port: "{{ lite2edge_nodeport }}" - federation_manager_ecp_client_name: "lite2edge" + edgecloud_platform: "{{ oop_profile_edgecloud_platform }}" + srm_edge_cloud_adapter_name: "{{ oop_profile_srm_edge_cloud_adapter_name }}" + srm_adapter_base_url: "{{ oop_profile_srm_adapter_base_url }}" + federation_manager_ecp_host: "{{ oop_profile_federation_manager_ecp_host }}" + federation_manager_ecp_port: "{{ oop_profile_federation_manager_ecp_port }}" + federation_manager_ecp_client_name: "{{ oop_profile_federation_manager_ecp_client_name }}" pre_tasks: - name: Display deployment information @@ -34,6 +44,7 @@ Full OOP Deployment ========================================== Scenario: Single operator platform + Deployment Profile: {{ oop_profile_name }} Federation Managers: 1 (local only) Target Host: {{ inventory_hostname }} ========================================== @@ -88,6 +99,7 @@ # Edge Platform - role: lite2edge + when: edgecloud_platform == 'lite2edge' tags: [edge, lite2edge] post_tasks: @@ -108,7 +120,7 @@ ✓ OEG Controller ✓ Federation Manager (1 instance) ✓ Homer Dashboard - ✓ Lite2Edge Platform + {% if edgecloud_platform == 'lite2edge' %}✓ Lite2Edge Platform{% else %}⊘ Lite2Edge Platform (skipped){% endif %} Access Points: Kubeconfig: {{ kind_config_dir }}/{{ kubeconfig_filename }} @@ -119,5 +131,5 @@ Next Steps: 1. Verify all pods: kubectl get pods -A 2. Access Homer dashboard for service overview - 3. Check Federation Manager: kubectl get pods -n federation-manager + 3. Check Federation Manager: kubectl get pods -n {{ oop_federation_manager_namespace }} ========================================== diff --git a/ansible/playbooks/scenarios/full_oop/graceful_undeploy.yml b/ansible/playbooks/scenarios/full_oop/graceful_undeploy.yml index 924a65f..f363bb3 100644 --- a/ansible/playbooks/scenarios/full_oop/graceful_undeploy.yml +++ b/ansible/playbooks/scenarios/full_oop/graceful_undeploy.yml @@ -8,6 +8,11 @@ gather_facts: true vars: + oop_core_namespace: oop + oop_federation_manager_namespace: "{{ oop_core_namespace }}" + oop_artefact_manager_namespace: "{{ oop_core_namespace }}" + oop_zot_namespace: "{{ oop_core_namespace }}" + oop_homer_namespace: "{{ oop_core_namespace }}" # Set all components to absent state lite2edge_state: absent homer_state: absent diff --git a/ansible/playbooks/scenarios/full_oop_existing_cluster/deploy.yml b/ansible/playbooks/scenarios/full_oop_existing_cluster/deploy.yml index 5caf112..69f9ff5 100644 --- a/ansible/playbooks/scenarios/full_oop_existing_cluster/deploy.yml +++ b/ansible/playbooks/scenarios/full_oop_existing_cluster/deploy.yml @@ -4,20 +4,24 @@ # Usage: ansible-playbook playbooks/scenarios/full_oop_existing_cluster/deploy.yml # ========================================== -# Prerequisites Installation +# User-Space Python Bootstrap # ========================================== -- import_playbook: ../../infrastructure/prerequisites/install-python-deps.yml - vars: - target_hosts: existing_cluster_nodes -- import_playbook: ../../infrastructure/prerequisites/setup-docker.yml - vars: - target_hosts: existing_cluster_nodes -- import_playbook: ../../infrastructure/prerequisites/setup-kubectl.yml - vars: - target_hosts: existing_cluster_nodes -- import_playbook: ../../infrastructure/prerequisites/setup-python-libs.yml +- name: Bootstrap user-space Ansible Python for existing clusters + hosts: existing_cluster_nodes + gather_facts: false + vars: - target_hosts: existing_cluster_nodes + ansible_user_python_venv: "/home/{{ ansible_user }}/.ansible-venv" + + tasks: + - name: Create user-space Python venv and install Ansible dependencies + ansible.builtin.raw: >- + python3 -m venv {{ ansible_user_python_venv }} && + {{ ansible_user_python_venv }}/bin/pip install --upgrade pip && + {{ ansible_user_python_venv }}/bin/pip install kubernetes PyYAML requests + + - name: Reset SSH connection so later tasks can use bootstrapped interpreter + ansible.builtin.meta: reset_connection # ========================================== # Full OOP Deployment On Existing Cluster @@ -27,13 +31,24 @@ gather_facts: true vars_files: - ../../../secrets.yml + - ../../../inventory/profiles/{{ oop_deployment_profile | default('lite2edge') }}.yml vars: + ansible_python_interpreter: "/home/{{ ansible_user }}/.ansible-venv/bin/python" + oop_core_namespace: oop + oop_federation_manager_namespace: "{{ oop_core_namespace }}" + oop_artefact_manager_namespace: "{{ oop_core_namespace }}" + oop_zot_namespace: "{{ oop_core_namespace }}" + oop_homer_namespace: "{{ oop_core_namespace }}" prepare_oop_storage: true k8s_distribution: existing k8s_platform: existing - federation_manager_ecp_port: "{{ lite2edge_nodeport }}" - federation_manager_ecp_client_name: "lite2edge" + edgecloud_platform: "{{ oop_profile_edgecloud_platform }}" + srm_edge_cloud_adapter_name: "{{ oop_profile_srm_edge_cloud_adapter_name }}" + srm_adapter_base_url: "{{ oop_profile_srm_adapter_base_url }}" + federation_manager_ecp_host: "{{ oop_profile_federation_manager_ecp_host }}" + federation_manager_ecp_port: "{{ oop_profile_federation_manager_ecp_port }}" + federation_manager_ecp_client_name: "{{ oop_profile_federation_manager_ecp_client_name }}" pre_tasks: - name: Display deployment information @@ -43,8 +58,10 @@ Full OOP Existing Cluster Deployment ========================================== Scenario: Single operator platform on running cluster + Deployment Profile: {{ oop_profile_name }} Federation Managers: 1 (local only) Target Host: {{ inventory_hostname }} + Ansible Python: {{ ansible_python_interpreter }} Cluster Access: Cluster Name: {{ cluster_name }} @@ -66,17 +83,30 @@ register: prometheus_control_plane_ips_result changed_when: false + - name: Get master-labeled InternalIPs for Prometheus endpoint discovery fallback + ansible.builtin.shell: >- + kubectl --kubeconfig {{ kubeconfig_path }} get nodes + --selector='node-role.kubernetes.io/master' + -o jsonpath='{range .items[*]}{range .status.addresses[?(@.type=="InternalIP")]}{.address}{"\n"}{end}{end}' + register: prometheus_master_ips_result + changed_when: false + - name: Get worker InternalIPs for Prometheus endpoint discovery ansible.builtin.shell: >- kubectl --kubeconfig {{ kubeconfig_path }} get nodes - --selector='!node-role.kubernetes.io/control-plane' + --selector='!node-role.kubernetes.io/control-plane,!node-role.kubernetes.io/master' -o jsonpath='{range .items[*]}{range .status.addresses[?(@.type=="InternalIP")]}{.address}{"\n"}{end}{end}' register: prometheus_worker_ips_result changed_when: false - name: Set Prometheus node IPs from existing cluster ansible.builtin.set_fact: - prometheus_control_plane_ips: "{{ prometheus_control_plane_ips_result.stdout_lines | reject('equalto', '') | list }}" + prometheus_control_plane_ips: >- + {{ + (prometheus_control_plane_ips_result.stdout_lines | reject('equalto', '') | list) + if (prometheus_control_plane_ips_result.stdout_lines | reject('equalto', '') | list | length > 0) + else (prometheus_master_ips_result.stdout_lines | reject('equalto', '') | list) + }} prometheus_worker_ips: "{{ prometheus_worker_ips_result.stdout_lines | reject('equalto', '') | list }}" roles: @@ -84,6 +114,7 @@ tags: [infrastructure, helm] - role: prometheus + when: deploy_prometheus | default(true) | bool tags: [infrastructure, monitoring, prometheus] - role: node-feature-discovery @@ -110,6 +141,7 @@ tags: [oop, homer, dashboard] - role: lite2edge + when: edgecloud_platform == 'lite2edge' tags: [edge, lite2edge] post_tasks: @@ -122,7 +154,7 @@ Components Deployed: ✓ Existing K8s Cluster ({{ cluster_name }}) - ✓ Prometheus Monitoring + {% if deploy_prometheus | default(true) | bool %}✓ Prometheus Monitoring{% else %}⊘ Prometheus Monitoring (skipped){% endif %} ✓ Node Feature Discovery ✓ Zot Registry ✓ Artefact Manager @@ -130,13 +162,13 @@ ✓ OEG Controller ✓ Federation Manager (1 instance) ✓ Homer Dashboard - ✓ Lite2Edge Platform + {% if edgecloud_platform == 'lite2edge' %}✓ Lite2Edge Platform{% else %}⊘ Lite2Edge Platform (skipped){% endif %} Access Points: Kubeconfig: {{ kubeconfig_path }} - Prometheus: http://{{ host_ip | default('localhost') }}:30090 + {% if deploy_prometheus | default(true) | bool %}Prometheus: http://{{ host_ip | default('localhost') }}:30090 Grafana: http://{{ host_ip | default('localhost') }}:30091 - Homer: http://{{ host_ip | default('localhost') }}:{{ homer_nodeport }} + {% endif %}Homer: http://{{ host_ip | default('localhost') }}:{{ homer_nodeport }} Existing cluster preserved. ========================================== diff --git a/ansible/playbooks/scenarios/full_oop_existing_cluster/graceful_undeploy.yml b/ansible/playbooks/scenarios/full_oop_existing_cluster/graceful_undeploy.yml index 80cff4c..dc4758f 100644 --- a/ansible/playbooks/scenarios/full_oop_existing_cluster/graceful_undeploy.yml +++ b/ansible/playbooks/scenarios/full_oop_existing_cluster/graceful_undeploy.yml @@ -3,11 +3,34 @@ # Description: Removes Full OOP components from an existing cluster without deleting the cluster # Usage: ansible-playbook playbooks/scenarios/full_oop_existing_cluster/graceful_undeploy.yml +- name: Bootstrap user-space Ansible Python for existing clusters + hosts: existing_cluster_nodes + gather_facts: false + + vars: + ansible_user_python_venv: "/home/{{ ansible_user }}/.ansible-venv" + + tasks: + - name: Create user-space Python venv and install Ansible dependencies + ansible.builtin.raw: >- + python3 -m venv {{ ansible_user_python_venv }} && + {{ ansible_user_python_venv }}/bin/pip install --upgrade pip && + {{ ansible_user_python_venv }}/bin/pip install kubernetes PyYAML requests + + - name: Reset SSH connection so later tasks can use bootstrapped interpreter + ansible.builtin.meta: reset_connection + - name: Graceful Undeploy Full OOP from Existing Cluster hosts: existing_cluster_nodes gather_facts: true vars: + ansible_python_interpreter: "/home/{{ ansible_user }}/.ansible-venv/bin/python" + oop_core_namespace: oop + oop_federation_manager_namespace: "{{ oop_core_namespace }}" + oop_artefact_manager_namespace: "{{ oop_core_namespace }}" + oop_zot_namespace: "{{ oop_core_namespace }}" + oop_homer_namespace: "{{ oop_core_namespace }}" lite2edge_state: absent homer_state: absent federation_manager_state: absent @@ -48,6 +71,7 @@ - name: Set kubeconfig facts from inventory ansible.builtin.set_fact: + kubeconfig_dir: "{{ kubeconfig_output_dir }}" kind_config_dir: "{{ kubeconfig_output_dir }}" kubeconfig_path: "{{ kubeconfig_output_dir }}/{{ kubeconfig_filename }}" @@ -80,6 +104,7 @@ tags: [infrastructure, nfd] - role: prometheus + when: deploy_prometheus | default(true) | bool tags: [infrastructure, monitoring, prometheus] - role: helm diff --git a/ansible/playbooks/scenarios/full_oop_existing_cluster/quick_undeploy.yml b/ansible/playbooks/scenarios/full_oop_existing_cluster/quick_undeploy.yml deleted file mode 100644 index 55bd312..0000000 --- a/ansible/playbooks/scenarios/full_oop_existing_cluster/quick_undeploy.yml +++ /dev/null @@ -1,6 +0,0 @@ ---- -# Playbook: Quick Full OOP Existing Cluster Undeployment Scenario -# Description: Alias for graceful removal because the underlying cluster must be preserved -# Usage: ansible-playbook playbooks/scenarios/full_oop_existing_cluster/quick_undeploy.yml - -- import_playbook: ./graceful_undeploy.yml diff --git a/ansible/roles/artefact-manager/defaults/main.yml b/ansible/roles/artefact-manager/defaults/main.yml index f2c2a0e..b410fe9 100644 --- a/ansible/roles/artefact-manager/defaults/main.yml +++ b/ansible/roles/artefact-manager/defaults/main.yml @@ -23,4 +23,4 @@ artefact_manager_image_pull_policy: "Always" # ========================================== # Namespace # ========================================== -artefact_manager_namespace: "artefact-manager" +artefact_manager_namespace: "{{ oop_artefact_manager_namespace | default('artefact-manager') }}" diff --git a/ansible/roles/artefact-manager/templates/artefact-manager.yaml.j2 b/ansible/roles/artefact-manager/templates/artefact-manager.yaml.j2 index 05083b3..6f19e19 100644 --- a/ansible/roles/artefact-manager/templates/artefact-manager.yaml.j2 +++ b/ansible/roles/artefact-manager/templates/artefact-manager.yaml.j2 @@ -15,11 +15,11 @@ data: insecure = true [[registry]] - location = "zot.zot.svc.cluster.local:5000" + location = "zot.{{ oop_zot_namespace | default('zot') }}.svc.cluster.local:5000" insecure = true [[registry]] - location = "zot.zot.svc.cluster.local:5000" + location = "zot.{{ oop_zot_namespace | default('zot') }}.svc.cluster.local:5000" insecure = true --- apiVersion: apps/v1 diff --git a/ansible/roles/federation-manager/defaults/main.yml b/ansible/roles/federation-manager/defaults/main.yml index f0d12ba..2ee34de 100644 --- a/ansible/roles/federation-manager/defaults/main.yml +++ b/ansible/roles/federation-manager/defaults/main.yml @@ -15,7 +15,7 @@ federation_manager_variant: "local" federation_manager_state: present # Namespace (override per deployment) -federation_manager_namespace: "federation-manager" +federation_manager_namespace: "{{ oop_federation_manager_namespace | default('federation-manager') }}" # Kubeconfig # K8s-distro-agnostic kubeconfig path @@ -24,7 +24,7 @@ federation_manager_kubeconfig: "{{ kubeconfig_path | default(kubeconfig_output_d # ========================================== # Federation Manager Image # ========================================== -federation_manager_image: "gitlab.i2cat.net:5050/areas/software-networks/operator-platform/federation-manager/federation-manager-i2cat" #federation_manager_image: "labs.etsi.org:5050/oop/code/federation-manager/federation-manager" +federation_manager_image: "labs.etsi.org:5050/oop/code/federation-manager" federation_manager_tag: "1.1" federation_manager_replicas: 1 federation_manager_container_port: 8989 @@ -87,7 +87,7 @@ federation_manager_op_platform_caps: "homeRouting" # ========================================== # Service Resource Manager Integration # ========================================== -federation_manager_srm_host: "srm.oop.svc.cluster.local" +federation_manager_srm_host: "srm.{{ oop_srm_namespace | default(oop_namespace | default('oop')) }}.svc.cluster.local" federation_manager_srm_port: 8080 # ========================================== @@ -101,7 +101,7 @@ federation_manager_ecp_flavour_id: "default" # ========================================== # Artefact Manager Integration # ========================================== -federation_manager_am_host: "artefact-manager.artefact-manager.svc.cluster.local" +federation_manager_am_host: "artefact-manager.{{ oop_artefact_manager_namespace | default('artefact-manager') }}.svc.cluster.local" federation_manager_am_port: 8000 federation_manager_am_enabled: "false" # Destination registry for artefact-manager copies. diff --git a/ansible/roles/federation-manager/tasks/deploy.yml b/ansible/roles/federation-manager/tasks/deploy.yml index 18b106c..1a4844f 100644 --- a/ansible/roles/federation-manager/tasks/deploy.yml +++ b/ansible/roles/federation-manager/tasks/deploy.yml @@ -2,16 +2,6 @@ # Federation Manager deployment tasks # Deploys MongoDB, Keycloak, and Federation Manager -# ========================================== -# Registry Authentication -# ========================================== -- name: Log in to private GitLab registry (if token is provided) - community.docker.docker_login: - registry_url: "{{ docker_registry_host }}" - username: "{{ docker_registry_username }}" - password: "{{ gitlab_token }}" - when: gitlab_token is defined - # ========================================== # Namespace Setup # ========================================== @@ -211,26 +201,15 @@ - federation_manager_mongodb_persistence_create_pv - not federation_manager_mongodb_persistence_use_default_storage_class -- name: Reset broken MongoDB storage objects before redeploy - kubernetes.core.k8s: - state: absent - api_version: "{{ item.api_version }}" - kind: "{{ item.kind }}" - namespace: "{{ item.namespace | default(omit) }}" - name: "{{ item.name }}" - kubeconfig: "{{ federation_manager_kubeconfig }}" - loop: - - api_version: apps/v1 - kind: Deployment - namespace: "{{ federation_manager_namespace }}" - name: mongodb - - api_version: v1 - kind: PersistentVolumeClaim - namespace: "{{ federation_manager_namespace }}" - name: mongodb - - api_version: v1 - kind: PersistentVolume - name: "mongodb-{{ federation_manager_variant }}" +- name: Fail when existing Federation Manager MongoDB storage conflicts with desired config + ansible.builtin.fail: + msg: >- + Existing MongoDB storage for Federation Manager conflicts with desired configuration. + Current PV details={{ federation_manager_mongodb_pv_details_result.stdout | default('not found') | quote }}. + Desired hostPath={{ federation_manager_mongodb_data_dir | quote }}, + desired storageClass={{ federation_manager_mongodb_persistence_storage_class | quote }}, + desired node={{ federation_manager_mongodb_persistence_node_name | default('') | quote }}. + Run the graceful undeploy/cleanup flow before redeploying. when: - federation_manager_mongodb_reset_required | default(false) diff --git a/ansible/roles/federation-manager/tasks/undeploy.yml b/ansible/roles/federation-manager/tasks/undeploy.yml index 30ccea8..732ef47 100644 --- a/ansible/roles/federation-manager/tasks/undeploy.yml +++ b/ansible/roles/federation-manager/tasks/undeploy.yml @@ -84,6 +84,15 @@ name: mongodb kubeconfig: "{{ federation_manager_kubeconfig }}" +- name: Get Federation Manager namespace state + kubernetes.core.k8s_info: + api_version: v1 + kind: Namespace + name: "{{ federation_manager_namespace }}" + kubeconfig: "{{ federation_manager_kubeconfig }}" + register: federation_manager_namespace_info + when: federation_manager_mongodb_cleanup_data + - name: Create MongoDB hostPath cleanup DaemonSet kubernetes.core.k8s: state: present @@ -127,7 +136,10 @@ path: "{{ federation_manager_mongodb_data_dir }}" type: DirectoryOrCreate kubeconfig: "{{ federation_manager_kubeconfig }}" - when: federation_manager_mongodb_cleanup_data + when: + - federation_manager_mongodb_cleanup_data + - federation_manager_namespace_info.resources | length > 0 + - federation_manager_namespace_info.resources[0].status.phase != 'Terminating' - name: Wait for MongoDB cleanup DaemonSet ansible.builtin.command: > @@ -137,7 +149,10 @@ changed_when: false environment: KUBECONFIG: "{{ federation_manager_kubeconfig }}" - when: federation_manager_mongodb_cleanup_data + when: + - federation_manager_mongodb_cleanup_data + - federation_manager_namespace_info.resources | length > 0 + - federation_manager_namespace_info.resources[0].status.phase != 'Terminating' - name: Delete MongoDB cleanup DaemonSet kubernetes.core.k8s: @@ -147,7 +162,10 @@ namespace: "{{ federation_manager_namespace }}" name: fm-mongodb-cleanup kubeconfig: "{{ federation_manager_kubeconfig }}" - when: federation_manager_mongodb_cleanup_data + when: + - federation_manager_mongodb_cleanup_data + - federation_manager_namespace_info.resources | length > 0 + - federation_manager_namespace_info.resources[0].status.phase != 'Terminating' - name: Delete MongoDB PVC kubernetes.core.k8s: @@ -166,12 +184,6 @@ name: "mongodb-{{ federation_manager_variant }}" kubeconfig: "{{ federation_manager_kubeconfig }}" -- name: Remove MongoDB data directory on host - ansible.builtin.file: - path: "{{ federation_manager_mongodb_data_dir }}" - state: absent - when: federation_manager_mongodb_cleanup_data - - name: Delete image pull secret kubernetes.core.k8s: state: absent diff --git a/ansible/roles/homer/defaults/main.yml b/ansible/roles/homer/defaults/main.yml index a6ccaa6..e5b4cef 100644 --- a/ansible/roles/homer/defaults/main.yml +++ b/ansible/roles/homer/defaults/main.yml @@ -22,7 +22,7 @@ homer_nodeport: 30088 # ========================================== # Namespace # ========================================== -homer_namespace: "homer" +homer_namespace: "{{ oop_homer_namespace | default('homer') }}" # ========================================== # Dashboard Content diff --git a/ansible/roles/lite2edge/tasks/undeploy.yml b/ansible/roles/lite2edge/tasks/undeploy.yml index deb0361..c9ce93d 100644 --- a/ansible/roles/lite2edge/tasks/undeploy.yml +++ b/ansible/roles/lite2edge/tasks/undeploy.yml @@ -8,10 +8,6 @@ kubeconfig: "{{ lite2edge_kubeconfig }}" register: namespace_check -- name: End play if lite2edge not deployed - ansible.builtin.meta: end_host - when: namespace_check.resources | length == 0 - - name: Display undeploy information ansible.builtin.debug: msg: | @@ -22,6 +18,7 @@ - lite2edge application and resources - Namespace: {{ lite2edge_namespace }} ========================================== + when: namespace_check.resources | length > 0 - name: Delete lite2edge resources kubernetes.core.k8s: @@ -31,6 +28,7 @@ namespace: "{{ lite2edge_namespace }}" wait: true wait_timeout: 60 + when: namespace_check.resources | length > 0 - name: Delete lite2edge namespace kubernetes.core.k8s: @@ -41,7 +39,9 @@ kubeconfig: "{{ lite2edge_kubeconfig }}" wait: true wait_timeout: 60 + when: namespace_check.resources | length > 0 - name: Display undeploy result ansible.builtin.debug: msg: "✓ lite2edge resources deleted" + when: namespace_check.resources | length > 0 diff --git a/ansible/roles/oeg/defaults/main.yml b/ansible/roles/oeg/defaults/main.yml index 5921bf4..62e0e76 100644 --- a/ansible/roles/oeg/defaults/main.yml +++ b/ansible/roles/oeg/defaults/main.yml @@ -8,7 +8,7 @@ oeg_kubeconfig: "{{ kubeconfig_path | default(kubeconfig_output_dir ~ '/' ~ kube oeg_controller_enabled: true oeg_controller_name: oegcontroller oeg_controller_replicas: 1 -oeg_controller_image_repository: "gitlab.i2cat.net:5050/areas/software-networks/operator-platform/oop/open-exposure-gateway" +oeg_controller_image_repository: "labs.etsi.org:5050/oop/code/open-exposure-gateway" oeg_controller_image_tag: "1.1" oeg_controller_image_pull_policy: Always oeg_controller_image_pull_secret: "gitlab-registry-secret" @@ -50,9 +50,9 @@ oeg_mongodb_wipe_ssh_user: ubuntu # OEG Environment Variables oeg_mongo_uri: "mongodb://{{ oeg_mongodb_name }}:{{ oeg_mongodb_service_port }}" oeg_srm_host: "http://srm:8080/srm/1.0.0" -oeg_federation_manager_host: "http://federation-manager.federation-manager.svc.cluster.local:8989/operatorplatform/federation/v1" +oeg_federation_manager_host: "http://federation-manager.{{ oop_federation_manager_namespace | default('federation-manager') }}.svc.cluster.local:8989/operatorplatform/federation/v1" oeg_partner_api_root: "http://federation-manager.federation-manager-remote.svc.cluster.local:8989" -oeg_token_endpoint: "http://keycloak.federation-manager.svc.cluster.local:8080/realms/federation/protocol/openid-connect/token" +oeg_token_endpoint: "http://keycloak.{{ oop_federation_manager_namespace | default('federation-manager') }}.svc.cluster.local:8080/realms/federation/protocol/openid-connect/token" oeg_avail_zone_notif_link: "http://oeg.{{ oeg_namespace }}.svc.cluster.local/oeg/1.0.0/availability-zones/notifications" # Resource limits diff --git a/ansible/roles/oeg/tasks/deploy.yml b/ansible/roles/oeg/tasks/deploy.yml index 8c955aa..280d5a4 100644 --- a/ansible/roles/oeg/tasks/deploy.yml +++ b/ansible/roles/oeg/tasks/deploy.yml @@ -301,7 +301,16 @@ - name: Set desired MongoDB PVC storage class ansible.builtin.set_fact: oeg_mongodb_pvc_storage_class_desired: >- - {{ (oeg_mongodb_persistence_use_default_storage_class | bool) | ternary('', oeg_mongodb_persistence_storage_class) }} + {{ + (oeg_mongodb_persistence_create_pv | bool) + | ternary( + oeg_mongodb_persistence_storage_class, + ( + (oeg_mongodb_persistence_use_default_storage_class | bool) + | ternary(oeg_default_storage_class | default(''), oeg_mongodb_persistence_storage_class) + ) + ) + }} when: oeg_mongodb_persistence_enabled - name: Get existing MongoDB PVC @@ -315,33 +324,32 @@ failed_when: false when: oeg_mongodb_persistence_enabled -- name: Force delete MongoDB PVC when storage class changes - ansible.builtin.shell: | - current_sc="$(kubectl -n {{ oeg_namespace }} get pvc mongodb-oeg-pvc -o jsonpath='{.spec.storageClassName}' 2>/dev/null || true)" - if [ -n "$current_sc" ] && [ "$current_sc" != "{{ oeg_mongodb_pvc_storage_class_desired }}" ]; then - kubectl -n {{ oeg_namespace }} patch pvc mongodb-oeg-pvc --type=json -p='[{"op":"remove","path":"/metadata/finalizers"}]' || true - kubectl -n {{ oeg_namespace }} delete pvc mongodb-oeg-pvc --ignore-not-found - fi - register: oeg_force_delete_pvc - changed_when: oeg_force_delete_pvc.rc == 0 - failed_when: false +- name: Fail when existing OEG MongoDB PVC conflicts with desired storage config + ansible.builtin.fail: + msg: >- + Existing PVC {{ oeg_namespace }}/mongodb-oeg-pvc conflicts with desired storage configuration. + Current storageClass={{ oeg_mongodb_pvc_info.resources[0].spec.storageClassName | default('') | quote }}, + current volumeName={{ oeg_mongodb_pvc_info.resources[0].spec.volumeName | default('') | quote }}, + desired storageClass={{ oeg_mongodb_pvc_storage_class_desired | quote }}. + Run the graceful undeploy/cleanup flow before redeploying. when: - oeg_mongodb_persistence_enabled + - oeg_mongodb_pvc_info.resources is defined + - oeg_mongodb_pvc_info.resources | length > 0 + - (oeg_mongodb_pvc_info.resources[0].spec.storageClassName | default('')) != oeg_mongodb_pvc_storage_class_desired -- name: Delete MongoDB PVC if storage class changed - kubernetes.core.k8s: - state: absent - api_version: v1 - kind: PersistentVolumeClaim - namespace: "{{ oeg_namespace }}" - name: mongodb-oeg-pvc - kubeconfig: "{{ oeg_kubeconfig }}" +- name: Fail when OEG MongoDB PVC is pending with stale static binding + ansible.builtin.fail: + msg: >- + PVC {{ oeg_namespace }}/mongodb-oeg-pvc is pending with stale volumeName={{ oeg_mongodb_pvc_info.resources[0].spec.volumeName | default('') | quote }} + under storageClass={{ oeg_mongodb_pvc_info.resources[0].spec.storageClassName | default('') | quote }}. + This requires graceful undeploy cleanup before redeploy. when: - oeg_mongodb_persistence_enabled - - not oeg_mongodb_preserve_data | default(true) - oeg_mongodb_pvc_info.resources is defined - oeg_mongodb_pvc_info.resources | length > 0 - - (oeg_mongodb_pvc_info.resources[0].spec.storageClassName | default('')) != oeg_mongodb_pvc_storage_class_desired + - (oeg_mongodb_pvc_info.resources[0].status.phase | default('')) == 'Pending' + - (oeg_mongodb_pvc_info.resources[0].spec.volumeName | default('')) | length > 0 - name: Create MongoDB PersistentVolume for OEG kubernetes.core.k8s: @@ -371,7 +379,6 @@ when: - oeg_mongodb_persistence_enabled - oeg_mongodb_persistence_create_pv - - not oeg_mongodb_persistence_use_default_storage_class - oeg_mongodb_persistence_node_name | length > 0 - name: Create MongoDB PersistentVolumeClaim for OEG @@ -389,11 +396,12 @@ resources: requests: storage: "{{ oeg_mongodb_persistence_size }}" + volumeName: mongodb-oeg-pv storageClassName: "{{ oeg_mongodb_persistence_storage_class }}" kubeconfig: "{{ oeg_kubeconfig }}" when: - oeg_mongodb_persistence_enabled - - not oeg_mongodb_persistence_use_default_storage_class + - oeg_mongodb_persistence_create_pv - name: Create MongoDB PersistentVolumeClaim for OEG (default storage class) kubernetes.core.k8s: @@ -413,7 +421,7 @@ kubeconfig: "{{ oeg_kubeconfig }}" when: - oeg_mongodb_persistence_enabled - - oeg_mongodb_persistence_use_default_storage_class + - not oeg_mongodb_persistence_create_pv - name: Deploy MongoDB for OEG kubernetes.core.k8s: diff --git a/ansible/roles/srm/defaults/main.yml b/ansible/roles/srm/defaults/main.yml index 6a247c0..1c0cdbd 100644 --- a/ansible/roles/srm/defaults/main.yml +++ b/ansible/roles/srm/defaults/main.yml @@ -8,8 +8,7 @@ srm_kubeconfig: "{{ kubeconfig_path | default(kubeconfig_output_dir ~ '/' ~ kube srm_controller_enabled: true srm_controller_name: srmcontroller srm_controller_replicas: 1 -# srm_controller_image_repository: "labs.etsi.org:5050/oop/code/service-resource-manager" -srm_controller_image_repository: "gitlab.i2cat.net:5050/areas/software-networks/operator-platform/oop/service-resource-manager" +srm_controller_image_repository: "labs.etsi.org:5050/oop/code/service-resource-manager" srm_controller_image_tag: "1.1" srm_controller_image_pull_policy: Always srm_image_pull_secret_enabled: true @@ -63,7 +62,7 @@ srm_mongodb_preserve_data: true # Preserve data on undeploy (do not delete # SRM Environment Variables srm_emp_storage_uri: "mongodb://{{ srm_mongodb_name }}:{{ srm_mongodb_service_port }}" -srm_artifact_manager_address: "http://artefact-manager.artefact-manager.svc.cluster.local:8000" +srm_artifact_manager_address: "http://artefact-manager.{{ oop_artefact_manager_namespace | default('artefact-manager') }}.svc.cluster.local:8000" srm_kubernetes_master_ip: "kubernetes.default.svc.cluster.local" srm_kubernetes_master_port: "443" srm_kubernetes_username: admin diff --git a/ansible/roles/zot/defaults/main.yml b/ansible/roles/zot/defaults/main.yml index 78476db..da98c96 100644 --- a/ansible/roles/zot/defaults/main.yml +++ b/ansible/roles/zot/defaults/main.yml @@ -17,7 +17,7 @@ zot_helm_repo_name: project-zot zot_helm_repo_url: http://zotregistry.dev/helm-charts zot_chart: project-zot/zot zot_release_name: zot -zot_namespace: zot +zot_namespace: "{{ oop_zot_namespace | default('zot') }}" zot_install_timeout: 300 # ========================================== diff --git a/ansible/roles/zot/tasks/install.yml b/ansible/roles/zot/tasks/install.yml index d17f9b4..6385083 100644 --- a/ansible/roles/zot/tasks/install.yml +++ b/ansible/roles/zot/tasks/install.yml @@ -185,19 +185,17 @@ failed_when: false when: zot_persistence_enabled -- name: Force delete Zot PVC when storage class changes - ansible.builtin.shell: | - current_sc="$(kubectl -n {{ zot_namespace }} get pvc {{ zot_existing_pvc_name }} -o jsonpath='{.spec.storageClassName}' 2>/dev/null || true)" - if [ "$current_sc" != "{{ zot_pvc_storage_class_desired }}" ]; then - kubectl -n {{ zot_namespace }} patch pvc {{ zot_existing_pvc_name }} --type=json -p='[{"op":"remove","path":"/metadata/finalizers"}]' || true - kubectl -n {{ zot_namespace }} delete pvc {{ zot_existing_pvc_name }} --ignore-not-found - fi - register: zot_force_delete_pvc - changed_when: zot_force_delete_pvc.rc == 0 and (zot_pvc_info.resources | default([]) | length > 0) - failed_when: false - environment: - KUBECONFIG: "{{ zot_kubeconfig }}" +- name: Fail when existing Zot PVC conflicts with desired storage config + ansible.builtin.fail: + msg: >- + Existing Zot PVC conflicts with desired storage configuration. + Current storageClass={{ zot_pvc_info.resources[0].spec.storageClassName | default('') | quote }}. + Desired storageClass={{ zot_pvc_storage_class_desired | quote }}. + Run the graceful undeploy/cleanup flow before redeploying. when: zot_persistence_enabled + and zot_pvc_info.resources is defined + and zot_pvc_info.resources | length > 0 + and (zot_pvc_info.resources[0].spec.storageClassName | default('')) != zot_pvc_storage_class_desired - name: Delete released Zot PV kubernetes.core.k8s: diff --git a/ansible/roles/zot/tasks/undeploy.yml b/ansible/roles/zot/tasks/undeploy.yml index 44dd2fd..495e3c7 100644 --- a/ansible/roles/zot/tasks/undeploy.yml +++ b/ansible/roles/zot/tasks/undeploy.yml @@ -26,6 +26,44 @@ msg: "✓ Zot uninstalled successfully" when: zot_uninstall is changed +- name: Get Zot PVC info + kubernetes.core.k8s_info: + api_version: v1 + kind: PersistentVolumeClaim + namespace: "{{ zot_namespace }}" + name: "{{ zot_existing_pvc_name }}" + kubeconfig: "{{ zot_kubeconfig }}" + register: zot_pvc_info + failed_when: false + +- name: Remove finalizers from Zot PVC during cleanup + ansible.builtin.command: > + kubectl -n {{ zot_namespace }} patch pvc {{ zot_existing_pvc_name }} + --type=json + -p=[{"op":"remove","path":"/metadata/finalizers"}] + changed_when: true + failed_when: false + environment: + KUBECONFIG: "{{ zot_kubeconfig }}" + when: + - zot_pvc_info.resources is defined + - zot_pvc_info.resources | length > 0 + +- name: Delete Zot PVC during cleanup + kubernetes.core.k8s: + state: absent + api_version: v1 + kind: PersistentVolumeClaim + namespace: "{{ zot_namespace }}" + name: "{{ zot_existing_pvc_name }}" + kubeconfig: "{{ zot_kubeconfig }}" + wait: yes + wait_timeout: 60 + failed_when: false + when: + - zot_pvc_info.resources is defined + - zot_pvc_info.resources | length > 0 + # ========================================== # Remove Namespace and PVCs # ========================================== -- GitLab