diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f4fb4f44f..59c737a6b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -44,6 +44,7 @@ repos: hooks: - id: ansible-lint additional_dependencies: + - ansible-core==2.15.13 - netaddr - jmespath diff --git a/hooks/playbooks/skmo/recreate-edpm-deployment-if-stale-target.yaml b/hooks/playbooks/skmo/recreate-edpm-deployment-if-stale-target.yaml new file mode 100644 index 000000000..8dbffe5c7 --- /dev/null +++ b/hooks/playbooks/skmo/recreate-edpm-deployment-if-stale-target.yaml @@ -0,0 +1,24 @@ +--- +- name: Read NodeSet status + kubernetes.core.k8s_info: + api_version: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneNodeSet + name: "{{ skmo_edpm_target.nodeset }}" + namespace: "{{ skmo_edpm_target.namespace }}" + register: _osdpns + +- name: Delete stale OpenStackDataPlaneDeployment + kubernetes.core.k8s: + state: absent + api_version: dataplane.openstack.org/v1beta1 + kind: OpenStackDataPlaneDeployment + name: "{{ skmo_edpm_target.deployment }}" + namespace: "{{ skmo_edpm_target.namespace }}" + wait: true + wait_timeout: 600 + when: + - _osdpns.resources | length > 0 + - _osdpns.resources[0].status.configHash is defined + - _osdpns.resources[0].status.configHash | length > 0 + - _osdpns.resources[0].status.deployedConfigHash is not defined or + _osdpns.resources[0].status.configHash != _osdpns.resources[0].status.deployedConfigHash diff --git a/hooks/playbooks/skmo/recreate-edpm-deployment-if-stale.yaml b/hooks/playbooks/skmo/recreate-edpm-deployment-if-stale.yaml new file mode 100644 index 000000000..bbd7b14ab --- /dev/null +++ b/hooks/playbooks/skmo/recreate-edpm-deployment-if-stale.yaml @@ -0,0 +1,23 @@ +--- +# Delete OpenStackDataPlaneDeployment when NodeSet config drifted. +# After status.deployed=true the deployment controller does not re-run ansible +# until the CR is recreated, even if osdpns configHash changed. +- name: Recreate stale EDPM deployments when NodeSet config changed + hosts: "{{ cifmw_target_hook_host | default('localhost') }}" + gather_facts: false + vars: + skmo_edpm_targets: + - namespace: openstack + nodeset: openstack-edpm + deployment: edpm-deployment + - namespace: openstack2 + nodeset: openstack-edpm + deployment: edpm-deployment + tasks: + - name: Recreate stale EDPM deployment in {{ skmo_edpm_target.namespace }} + ansible.builtin.include_tasks: recreate-edpm-deployment-if-stale-target.yaml + loop: "{{ skmo_edpm_targets }}" + loop_control: + label: "{{ item.namespace }}" + vars: + skmo_edpm_target: "{{ item }}" diff --git a/roles/ci_gen_kustomize_values/templates/multi-namespace/network-values/values.yaml.j2 b/roles/ci_gen_kustomize_values/templates/multi-namespace/network-values/values.yaml.j2 index fc50d6f04..f69c07d81 100644 --- a/roles/ci_gen_kustomize_values/templates/multi-namespace/network-values/values.yaml.j2 +++ b/roles/ci_gen_kustomize_values/templates/multi-namespace/network-values/values.yaml.j2 @@ -73,7 +73,31 @@ data: iface: {{ ns.interfaces[network.network_name] }} {% endif %} {% endif %} -{% if network.tools.multus is defined %} +{% if network.tools.multus is defined and network.network_name == "octavia" and cifmw_architecture_scenario == 'multi-namespace-skmo' %} + net-attach-def: | + { + "cniVersion": "0.3.1", + "name": "octavia", + "type": "bridge", + "bridge": "octbr", + "ipam": { + "type": "whereabouts", + "range": "{{ network[_ipv.network_vX] }}", +{% if _ipv.ipvX_routes in network.tools.multus and network.tools.multus[_ipv.ipvX_routes] | length > 0 %} + "routes": [ +{% for route in network.tools.multus[_ipv.ipvX_routes] %} + { + "dst": "{{ route.destination }}", + "gw": "{{ route.gateway }}" + }{% if not loop.last %},{% endif %} +{% endfor %} + ], +{% endif %} + "range_start": "{{ network.tools.multus[_ipv.ipvX_ranges].0.start }}", + "range_end": "{{ network.tools.multus[_ipv.ipvX_ranges].0.end }}" + } + } +{% elif network.tools.multus is defined %} net-attach-def: | { "cniVersion": "0.3.1", @@ -124,3 +148,6 @@ data: lbServiceType: LoadBalancer storageClass: {{ cifmw_ci_gen_kustomize_values_storage_class }} + + # Required when SKMO enables octavia NNCP bridge on OCP masters + bridgeName: ospbr diff --git a/roles/ci_gen_kustomize_values/templates/multi-namespace/network-values2/values.yaml.j2 b/roles/ci_gen_kustomize_values/templates/multi-namespace/network-values2/values.yaml.j2 index b31bfedcd..38357bee8 100644 --- a/roles/ci_gen_kustomize_values/templates/multi-namespace/network-values2/values.yaml.j2 +++ b/roles/ci_gen_kustomize_values/templates/multi-namespace/network-values2/values.yaml.j2 @@ -32,7 +32,7 @@ data: {% endif %} {% endfor %} -{% for network in cifmw_networking_env_definition.networks.values() if "2" in network %} +{% for network in cifmw_networking_env_definition.networks.values() if "2" in network.network_name or (cifmw_architecture_scenario == 'multi-namespace-skmo' and network.network_name == 'octavia') %} {% set ns.lb_tools = {} %} {{ network.network_name | replace("2", "") }}: dnsDomain: {{ network.search_domain }} @@ -53,7 +53,7 @@ data: gateway: {{ network[_ipv.gw_vX] }} {% endif %} name: subnet1 -{% if network.vlan_id is defined %} +{% if network.vlan_id is defined and network.network_name != 'external2' %} vlan: {{ network.vlan_id }} {% endif %} {% if ns.lb_tools | length > 0 %} @@ -83,7 +83,31 @@ data: iface: {{ ns.interfaces[network.network_name] }} {% endif %} {% endif %} -{% if network.tools.multus is defined %} +{% if network.tools.multus is defined and network.network_name == "octavia" and cifmw_architecture_scenario == 'multi-namespace-skmo' %} + net-attach-def: | + { + "cniVersion": "0.3.1", + "name": "octavia", + "type": "bridge", + "bridge": "octbr", + "ipam": { + "type": "whereabouts", + "range": "{{ network[_ipv.network_vX] }}", +{% if _ipv.ipvX_routes in network.tools.multus and network.tools.multus[_ipv.ipvX_routes] | length > 0 %} + "routes": [ +{% for route in network.tools.multus[_ipv.ipvX_routes] %} + { + "dst": "{{ route.destination }}", + "gw": "{{ route.gateway }}" + }{% if not loop.last %},{% endif %} +{% endfor %} + ], +{% endif %} + "range_start": "{{ network.tools.multus[_ipv.ipvX_ranges].0.start }}", + "range_end": "{{ network.tools.multus[_ipv.ipvX_ranges].0.end }}" + } + } +{% elif network.tools.multus is defined %} net-attach-def: | { "cniVersion": "0.3.1", @@ -134,3 +158,5 @@ data: lbServiceType: LoadBalancer storageClass: {{ cifmw_ci_gen_kustomize_values_storage_class }} + + bridgeName: ospbr diff --git a/roles/federation/tasks/run_keycloak_setup.yml b/roles/federation/tasks/run_keycloak_setup.yml index a759269ac..b10c05a7b 100644 --- a/roles/federation/tasks/run_keycloak_setup.yml +++ b/roles/federation/tasks/run_keycloak_setup.yml @@ -48,49 +48,86 @@ ansible.builtin.command: cmd: "oc apply -f {{ [ ansible_user_dir, 'ci-framework-data', 'tmp', 'rhsso-operator-olm.yaml' ] | path_join }}" -- name: Wait for the rhsso install plan to be present +- name: Check rhsso operator CSV status kubernetes.core.k8s_info: - api_version: operators.coreos.com/v1alpha1 - kind: InstallPlan - register: ip_list - until: >- - ip_list.resources | - selectattr('metadata.labels', 'defined') | - map(attribute='metadata.labels') | - map('dict2items') | - flatten | - selectattr('key', 'match', '.*rhsso-operator.*') | - list | length > 0 - retries: 30 - delay: 40 - -- name: Get rhsso install plan name - ansible.builtin.set_fact: - _rhsso_ip_name: "{{ item.metadata.name }}" - _rhsso_ip_namespace: "{{ item.metadata.namespace }}" - loop: >- - {{ - ip_list.resources | - selectattr('metadata.labels', 'defined') | - list - }} - when: >- - item.metadata.labels | dict2items | - selectattr('key', 'match', '.*rhsso-operator.*') | - list | length > 0 - loop_control: - label: "{{ item.metadata.name }}" - -- name: Approve rhsso operator install plan - kubernetes.core.k8s: kubeconfig: "{{ cifmw_openshift_kubeconfig }}" api_version: operators.coreos.com/v1alpha1 - kind: InstallPlan - name: "{{ _rhsso_ip_name }}" - namespace: "{{ _rhsso_ip_namespace }}" - definition: - spec: - approved: true + kind: ClusterServiceVersion + namespace: "{{ item }}" + label_selectors: + - operators.coreos.com/rhsso-operator.default=true + register: _rhsso_csv_checks + loop: + - default + - "{{ cifmw_federation_keycloak_namespace }}" + failed_when: false + +- name: Set rhsso operator already installed fact + ansible.builtin.set_fact: + _rhsso_operator_installed: >- + {{ + ( + _rhsso_csv_checks.results | + default([]) | + map(attribute='resources') | + flatten | + selectattr('status.phase', 'defined') | + selectattr('status.phase', 'equalto', 'Succeeded') | + list | length + ) > 0 + }} + +- name: Install and approve rhsso operator when not already installed + when: not _rhsso_operator_installed + block: + - name: Wait for the rhsso install plan to be present + kubernetes.core.k8s_info: + kubeconfig: "{{ cifmw_openshift_kubeconfig }}" + api_version: operators.coreos.com/v1alpha1 + kind: InstallPlan + namespace: default + register: ip_list + until: >- + ip_list.resources | + selectattr('metadata.labels', 'defined') | + map(attribute='metadata.labels') | + map('dict2items') | + flatten | + selectattr('key', 'match', '.*rhsso-operator.*') | + list | length > 0 + retries: 30 + delay: 40 + + - name: Get unapproved rhsso install plan name + ansible.builtin.set_fact: + _rhsso_ip_name: "{{ item.metadata.name }}" + _rhsso_ip_namespace: "{{ item.metadata.namespace }}" + loop: >- + {{ + ip_list.resources | + selectattr('metadata.labels', 'defined') | + list + }} + when: + - >- + item.metadata.labels | dict2items | + selectattr('key', 'match', '.*rhsso-operator.*') | + list | length > 0 + - not (item.spec.approved | default(false) | bool) + loop_control: + label: "{{ item.metadata.name }}" + + - name: Approve rhsso operator install plan + when: _rhsso_ip_name is defined + kubernetes.core.k8s: + kubeconfig: "{{ cifmw_openshift_kubeconfig }}" + api_version: operators.coreos.com/v1alpha1 + kind: InstallPlan + name: "{{ _rhsso_ip_name }}" + namespace: "{{ _rhsso_ip_namespace }}" + definition: + spec: + approved: true - name: Add sso admin user secret kubernetes.core.k8s: diff --git a/roles/federation/tasks/run_openstack_setup.yml b/roles/federation/tasks/run_openstack_setup.yml index 5752d102c..ad123f8f0 100644 --- a/roles/federation/tasks/run_openstack_setup.yml +++ b/roles/federation/tasks/run_openstack_setup.yml @@ -73,26 +73,6 @@ remote_path: "/home/cloud-admin/{{ cifmw_federation_rules_file }}" local_path: "{{ [ ansible_user_dir, 'ci-framework-data', 'tmp', cifmw_federation_rules_file ] | path_join }}" -- name: Check if federation mapping already exists - environment: - KUBECONFIG: "{{ cifmw_openshift_kubeconfig }}" - PATH: "{{ cifmw_path }}" - ansible.builtin.command: - cmd: >- - oc exec -n {{ cifmw_federation_run_osp_cmd_namespace }} -t openstackclient -- - openstack mapping show {{ cifmw_federation_mapping_name }} -f value -c id - register: _federation_mapping_check - failed_when: false - changed_when: false - -- name: Run federation mapping create - when: _federation_mapping_check.rc != 0 - vars: - _osp_cmd: "openstack mapping create - --rules {{ cifmw_federation_rules_file }} - {{ cifmw_federation_mapping_name }}" - ansible.builtin.include_tasks: run_osp_cmd.yml - - name: Check if federation group already exists environment: KUBECONFIG: "{{ cifmw_openshift_kubeconfig }}" @@ -135,6 +115,26 @@ {{ cifmw_federation_project_name }}" ansible.builtin.include_tasks: run_osp_cmd.yml +- name: Check if federation mapping already exists + environment: + KUBECONFIG: "{{ cifmw_openshift_kubeconfig }}" + PATH: "{{ cifmw_path }}" + ansible.builtin.command: + cmd: >- + oc exec -n {{ cifmw_federation_run_osp_cmd_namespace }} -t openstackclient -- + openstack mapping show {{ cifmw_federation_mapping_name }} -f value -c id + register: _federation_mapping_check + failed_when: false + changed_when: false + +- name: Run federation mapping create + when: _federation_mapping_check.rc != 0 + vars: + _osp_cmd: "openstack mapping create + --rules {{ cifmw_federation_rules_file }} + {{ cifmw_federation_mapping_name }}" + ansible.builtin.include_tasks: run_osp_cmd.yml + - name: Run federation role add (safe to repeat - role add is idempotent) environment: KUBECONFIG: "{{ cifmw_openshift_kubeconfig }}" diff --git a/scenarios/reproducers/va-multi-skmo.yml b/scenarios/reproducers/va-multi-skmo.yml index 6b19e135c..00cbad6f2 100644 --- a/scenarios/reproducers/va-multi-skmo.yml +++ b/scenarios/reproducers/va-multi-skmo.yml @@ -319,8 +319,25 @@ cifmw_networking_definition: ranges: - start: 100 end: 250 - vlan: 32 + # No vlan: existing leaf NetConfig external subnet was created without VLAN; + # NetConfig webhook rejects adding vlan to an established subnet. + mtu: 1500 + octavia: + network: "172.23.0.0/24" + vlan: 23 mtu: 1500 + tools: + multus: + ranges: + - start: 30 + end: 70 + routes: + - destination: "172.24.0.0/16" + gateway: "172.23.0.150" + netconfig: + ranges: + - start: 100 + end: 250 group-templates: ocps: @@ -362,6 +379,8 @@ cifmw_networking_definition: trunk-parent: ctlplane storage: trunk-parent: ctlplane + octavia: + trunk-parent: ctlplane compute2s: network-template: range: @@ -375,6 +394,8 @@ cifmw_networking_definition: trunk-parent: ctlplane2 storage2: trunk-parent: ctlplane2 + octavia: + trunk-parent: ctlplane2 instances: controller-0: networks: @@ -403,10 +424,7 @@ pre_admin_setup: cifmw_os_net_setup_public_end: "192.168.133.250" cifmw_os_net_setup_public_gateway: "192.168.133.1" -post_tests: - - name: Run tempest against openstack2 namespace - type: playbook - source: "{{ ansible_user_dir }}/src/github.com/openstack-k8s-operators/ci-framework/playbooks/multi-namespace/ns2_validation.yaml" - extra_vars: - cifmw_test_operator_tempest_name: tempest-tests2 - cifmw_test_operator_namespace: openstack2 +# Tempest runs via ci-framework-jobs scenario vars (05-tests.yaml), not post_tests hooks. +# Zuul: integration-uni-base variable_files_dirs loads scenarios/uni/va-multi-namespace-skmo/. +# Local reproducer: pass -e @.../ci-framework-jobs/scenarios/uni/va-multi-namespace-skmo/05-tests.yaml +post_tests: []