From 4090cdaddc79499f40658d54dd6e5646bd16b17c Mon Sep 17 00:00:00 2001 From: Pablo Rodriguez Nava Date: Wed, 3 Jan 2024 13:00:02 +0100 Subject: [PATCH] [networking_mapping] Add the Ansible wrapper role --- ci/config/molecule.yaml | 17 + plugins/action/ci_net_map.py | 325 ++++++++++++++++++ plugins/module_utils/net_map/exceptions.py | 10 + .../module_utils/net_map/networking_mapper.py | 18 + roles/networking_mapper/README.md | 164 +++++++++ roles/networking_mapper/defaults/main.yml | 30 ++ roles/networking_mapper/meta/main.yml | 41 +++ .../molecule/default/converge.yml | 216 ++++++++++++ .../molecule/default/molecule.yml | 11 + .../molecule/default/prepare.yml | 21 ++ roles/networking_mapper/tasks/main.yml | 107 ++++++ zuul.d/molecule.yaml | 25 ++ zuul.d/projects.yaml | 1 + 13 files changed, 986 insertions(+) create mode 100644 plugins/action/ci_net_map.py create mode 100644 roles/networking_mapper/README.md create mode 100644 roles/networking_mapper/defaults/main.yml create mode 100644 roles/networking_mapper/meta/main.yml create mode 100644 roles/networking_mapper/molecule/default/converge.yml create mode 100644 roles/networking_mapper/molecule/default/molecule.yml create mode 100644 roles/networking_mapper/molecule/default/prepare.yml create mode 100644 roles/networking_mapper/tasks/main.yml diff --git a/ci/config/molecule.yaml b/ci/config/molecule.yaml index feddc4be08..4478c8a114 100644 --- a/ci/config/molecule.yaml +++ b/ci/config/molecule.yaml @@ -48,3 +48,20 @@ - job: name: cifmw-molecule-ci_local_storage nodeset: centos-9-crc-xl +- job: + name: cifmw-molecule-networking_mapper + nodeset: + nodes: + - name: controller + label: cloud-centos-9-stream-tripleo-vexxhost-medium + - name: compute-0 + label: cloud-centos-9-stream-tripleo-vexxhost-medium + - name: compute-1 + label: cloud-centos-9-stream-tripleo-vexxhost-medium + - name: crc + label: cloud-centos-9-stream-tripleo-vexxhost-medium + groups: + - name: computes + nodes: + - compute-0 + - compute-1 diff --git a/plugins/action/ci_net_map.py b/plugins/action/ci_net_map.py new file mode 100644 index 0000000000..714b49f8c8 --- /dev/null +++ b/plugins/action/ci_net_map.py @@ -0,0 +1,325 @@ +#!/usr/bin/env python3 + +# Copyright Red Hat, Inc. +# Apache License Version 2.0 (see LICENSE) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import typing + +DOCUMENTATION = r""" +--- +action: ci_net_map + +short_description: Maps the Networking Definition to a Networking Env. Definition + +description: +- Takes the Networking Definition, and based on Ansible facts and a set of + rules, translates that info into a valid Networking Env. Definition. +- This task plugin requires networking facts to be present for every + instance referenced in the Networking Definition. +- Partial mappings are allowed to allow infrastructure bootstrapping based on the + mapping result even without already deployed instances. + +options: + networking_definition: + description: + - The Networking Definition as a dictionary. + type: dict + required: true + interfaces_info: + description: + - Dict that contains the list of MAC addresses of each instance to be mapped. + - Each dict entry is a list of dicts with at least a `mac` field. + - Each item in the list is an interface in a given infrastructure network. + - If not passed a partial map is done. + type: iterable + required: false + network_name: + description: + - Required if interfaces_info is given. + - Represents the name of the physical network that accommodates the OSP networks. + - Filters out the interfaces_info interfaces by selecting only the proper one. + type: str + required: false + search_domain_base: + description: + - Domain name used for generating networks search domains. + type: str + default: example.com + +""" + +EXAMPLES = r""" +- name: Perform a partial map of the given Networking Definition + cimf.general.ci_net_map: + networking_definition: + networks: + ctlplane: + network: "192.168.122.0/24" + gateway: "192.168.122.1" + dns: + - "192.168.122.253" + - "192.168.122.254" + search-domain: "ctlplane.example.local" + mtu: 1500 + tenant: + network: "172.19.0.0/24" + gateway-v4: "172.19.0.1" + search-domain: "tenant.example.local" + dns-v4: + - "8.8.8.8" + - "172.19.0.1" + vlan: 22 + mtu: 1496 + group-templates: + group-1: + networks: + ctlplane: + range: "192.168.122.10-192.168.122.14" + tenant: + skip-nm-configuration: true + range: + start: 10 + length: 5 + instances: + instance-1: + networks: + ctlplane: + ip: "192.168.122.100" + storage: + skip-nm-configuration: true + ip: "172.18.0.100" + +- name: Perform a full map of the given Networking Definition + cimf.general.ci_net_map: + networking_definition: + networks: + ctlplane: + network: "192.168.122.0/24" + gateway: "192.168.122.1" + dns: + - "192.168.122.253" + - "192.168.122.254" + search-domain: "ctlplane.example.local" + mtu: 1500 + tenant: + network: "172.19.0.0/24" + gateway-v4: "172.19.0.1" + search-domain: "tenant.example.local" + dns-v4: + - "8.8.8.8" + - "172.19.0.1" + vlan: 22 + mtu: 1496 + group-templates: + group-1: + networks: + ctlplane: + range: "192.168.122.10-192.168.122.14" + tenant: + skip-nm-configuration: true + range: + start: 10 + length: 5 + instances: + instance-1: + networks: + ctlplane: + ip: "192.168.122.100" + storage: + skip-nm-configuration: true + ip: "172.18.0.100" + network_name: test-network + interfaces_info: + controller-0: + - mac: fa:16:3e:12:4f:d1 + network: test-network + - mac: aa:45:3e:ab:ff:e4 + network: test-network-2 + instance-0: + - mac: fa:16:3e:c1:b2:e9 + network: test-network + instance-1: + - mac: fa:16:3e:3d:e5:cc + network: test-network + - mac: aa:45:3e:f9:00:c0 + network: test-network-2 +""" + +RETURN = r""" +networking_env_definition: + description: The output Networking Environment Definition + returned: success + type: dict + sample: + networks: + network-1: + network_name: network-1 + search_domain: net-1.example.local + tools: + metallb: + ipv4_ranges: [] + ipv6_ranges: + - start: fdc0:8b54:108a:c949:0000:0000:0000:0011 + start_host: 17 + end: fdc0:8b54:108a:c949:0000:0000:0000:001a + end_host: 26 + length: 10 + multus: + ipv4_ranges: + - start: 192.168.122.30 + start_host: 30 + end: 192.168.122.39 + end_host: 39 + length: 10 + ipv6_ranges: + - start: fdc0:8b54:108a:c949:0000:0000:0000:001e + start_host: 30 + end: fdc0:8b54:108a:c949:0000:0000:0000:0027 + end_host: 39 + length: 10 + dns_v4: + - 192.168.122.253 + dns_v6: + - fdc0:8b54:108a:c949:ffff:ffff:ffff:fffe + network_v4: 192.168.122.0/24 + network_v6: fdc0:8b54:108a:c949::/64 + gw_v4: 192.168.122.1 + gw_v6: fdc0:8b54:108a:c949:0000:0000:0000:0001 + mtu: 1500 + network-2: + network_name: network-2 + search_domain: net-2.example.local + tools: + metallb: + ipv4_ranges: + - start: 172.18.0.60 + start_host: 60 + end: 172.18.0.69 + end_host: 69 + length: 10 + ipv6_ranges: [] + netconfig: + ipv4_ranges: + - start: 172.18.0.40 + start_host: 40 + end: 172.18.0.49 + end_host: 49 + length: 10 + dns_v4: + - 1.1.1.1 + dns_v6: [] + network_v4: 172.18.0.0/24 + gw_v4: 172.18.0.1 + vlan_id: 21 + mtu: 1496 + instances: + instance-1: + name: instance-1 + networks: + network-1: + network_name: network-1 + skip_nm: false + mac_addr: 27:b9:47:74:b3:02 + interface_name: eth1 + ip_v4: 192.168.122.10 + ip_v6: fdc0:8b54:108a:c949:0000:0000:0000:000a + mtu: 1500 + hostname: instance-1-hostname + instance-2: + name: instance-2 + networks: + network-1: + network_name: network-1 + skip_nm: false + mac_addr: a1:69:da:21:aa:03 + interface_name: eth2 + ip_v4: 192.168.122.11 + ip_v6: fdc0:8b54:108a:c949:0000:0000:0000:000b + mtu: 1500 + hostname: instance-2-hostname +complete_map: + description: Tells if the output is the result of a full map (with instances_info) + returned: success + type: bool +""" + +from ansible.plugins.action import ActionBase +from ansible.errors import AnsibleActionFail + +from ansible_collections.cifmw.general.plugins.module_utils.encoding import ( + ansible_encoding, +) +from ansible_collections.cifmw.general.plugins.module_utils.net_map import ( + exceptions, + networking_mapper, +) + + +class ActionModule(ActionBase): + @staticmethod + def __build_interfaces_info_dict( + ifaces_info_raw: typing.Dict[str, typing.Any], net_name: str + ) -> typing.Dict[str, typing.Any]: + ifaces_info_dict = {} + for instance_name, ifaces_list in ifaces_info_raw.items(): + iface_data = next( + ( + iface_content + for iface_content in ifaces_list + if "network" in iface_content + and iface_content["network"] == net_name + ), + None, + ) + if instance_name not in ifaces_info_dict and iface_data: + ifaces_info_dict[instance_name] = iface_data + + return ifaces_info_dict + + def run(self, tmp=None, task_vars=None): + if task_vars is None: + task_vars = dict() + + result = super(ActionModule, self).run(tmp, task_vars) + del tmp + + task_args = ansible_encoding.decode_ansible_raw(self._task.args) + networking_definition = task_args.get("networking_definition", None) + if not networking_definition: + raise AnsibleActionFail("networking_definition is a mandatory argument") + + interfaces_info_dict = None + is_complete_map = "interfaces_info" in task_args + network_name = task_args.get("network_name", None) + if is_complete_map and not network_name: + raise AnsibleActionFail( + "network_name is a mandatory " "argument if interfaces_info is given" + ) + elif is_complete_map: + interfaces_info_dict = self.__build_interfaces_info_dict( + task_args["interfaces_info"], network_name + ) + + mapper = networking_mapper.NetworkingDefinitionMapper( + dict(task_vars["hostvars"]), + task_vars["groups"], + options=networking_mapper.NetworkingMapperOptions.from_dict(task_args), + ) + + try: + mapping_result = ( + mapper.map_complete(networking_definition, interfaces_info_dict) + if is_complete_map + else mapper.map_partial(networking_definition) + ) + result["failed"] = False + result["networking_env_definition"] = mapping_result + result["complete_map"] = is_complete_map + except exceptions.NetworkMappingError as run_exception: + result = {**result, **(run_exception.to_dict()), "failed": True} + + return result diff --git a/plugins/module_utils/net_map/exceptions.py b/plugins/module_utils/net_map/exceptions.py index f6673521d7..a8100cfadf 100644 --- a/plugins/module_utils/net_map/exceptions.py +++ b/plugins/module_utils/net_map/exceptions.py @@ -1,8 +1,18 @@ +import typing + +from ansible_collections.cifmw.general.plugins.module_utils.encoding import ( + ansible_encoding, +) + + class NetworkMappingError(Exception): def __init__(self, message) -> None: super().__init__(message) self.message = message + def to_dict(self) -> typing.Dict[str, typing.Any]: + return ansible_encoding.decode_ansible_raw(vars(self)) + class NetworkMappingValidationError(NetworkMappingError): def __init__( diff --git a/plugins/module_utils/net_map/networking_mapper.py b/plugins/module_utils/net_map/networking_mapper.py index 66e0c0930d..a48585403b 100644 --- a/plugins/module_utils/net_map/networking_mapper.py +++ b/plugins/module_utils/net_map/networking_mapper.py @@ -27,6 +27,24 @@ class NetworkingMapperOptions: search_domain_base: str = None + @classmethod + def from_dict(cls, data: typing.Dict[str, typing.Any]) -> "NetworkingMapperOptions": + """Creates a NetworkingMapperOptions from a dict. + + Unknown keys are ignored. + + Returns: The new NetworkingMapperOptions instance. + + """ + field_names = {field.name for field in dataclasses.fields(cls)} + return cls( + **{ + field_name: value + for field_name, value in data.items() + if field_name in field_names + } + ) + class NetMapperJsonEncoder(json.JSONEncoder): """ diff --git a/roles/networking_mapper/README.md b/roles/networking_mapper/README.md new file mode 100644 index 0000000000..826748b1ff --- /dev/null +++ b/roles/networking_mapper/README.md @@ -0,0 +1,164 @@ +# networking_mapper +Handles validation and mapping of the Networking Definition, the generic, CI-framework specific, way of +defining CI networks into the Networking Environment Definition, the dictionary that holds all the +networking details of a given environment. + + +## Parameters +* `cifmw_networking_definition`: The Networking Definition as a dictionary. +* `cifmw_networking_mapper_ifaces_info`: The interface information dictionary that holds low-level info for full maps. +* `cifmw_networking_mapper_network_name`: The network name to filter `cifmw_networking_mapper_ifaces_info` interfaces. + +## Important definitions +- Networking Definition: The input to the CI-framework that defines all the networking-needed data. +- Networking Environment Definition: The output of the mapper that the CI-framework consumes to configure + all components that depend on the environment networking. +- Partial mapping: The mapper is expected to be run in two main situations. + - Before instances are deployed: At that point low-level information such as mac addresses, interface names or MTU may + not be yet available, but important information such as IPs to configure, desired MTU, DNS or other data + is available by looking at the Networking Information. + A partial map is just that, a run of the mapper with an incomplete output that lacks of instances-specific pieces. + - When instances are deployed: At that point, all the low-level information is available, and the mapper is able + to create the entire output with all the details for all the involved networks and instances based on the input + Networking Definition and the Interfaces Information dict, that contains all the details to wire up instances + interfaces to networks. +- Interfaces Information: An input to the mapper that enables full maps by providing the mac address of each instance + interface for each infrastructure network. The mapper will filter the interface list of each instance based + on the `cifmw_networking_mapper_network_name`parameter. + +## Examples +``` +- name: Call the networking mapper partial map (limited instances information) + vars: + cifmw_networking_definition: + networks: + ctlplane: + network: "192.168.122.0/24" + gateway: "192.168.122.1" + dns: + - "192.168.122.253" + - "192.168.122.254" + search-domain: "ctlplane.example.local" + mtu: 1500 + internal-api: + network: "172.17.0.0/24" + gateway: "172.17.0.1" + vlan: 20 + mtu: 1496 + storage: + network: "172.18.0.0/24" + vlan: 21 + mtu: 1496 + tenant: + network: "172.19.0.0/24" + gateway-v4: "172.19.0.1" + search-domain: "tenant.example.local" + dns-v4: + - "8.8.8.8" + - "172.19.0.1" + vlan: 22 + mtu: 1496 + group-templates: + computes: + networks: + ctlplane: + range: "192.168.122.10-192.168.122.14" + internal-api: + range: "10-14" + tenant: + range: + start: 10 + length: 5 + storage: + range: + start: 10 + length: 5 + instances: + crc: + networks: + ctlplane: + ip: "192.168.122.10" + storage: + ip: "172.18.0.10" + controller: + networks: + ctlplane: + ip: "192.168.122.5" + ansible.builtin.include_role: + name: networking_mapper + + +- name: Call the networking mapper full map + vars: + cifmw_networking_definition: + networks: + ctlplane: + network: "192.168.122.0/24" + gateway: "192.168.122.1" + dns: + - "192.168.122.253" + - "192.168.122.254" + search-domain: "ctlplane.example.local" + mtu: 1500 + internal-api: + network: "172.17.0.0/24" + gateway: "172.17.0.1" + vlan: 20 + mtu: 1496 + storage: + network: "172.18.0.0/24" + vlan: 21 + mtu: 1496 + tenant: + network: "172.19.0.0/24" + gateway-v4: "172.19.0.1" + search-domain: "tenant.example.local" + dns-v4: + - "8.8.8.8" + - "172.19.0.1" + vlan: 22 + mtu: 1496 + group-templates: + computes: + networks: + ctlplane: + range: "192.168.122.10-192.168.122.14" + internal-api: + range: "10-14" + tenant: + range: + start: 10 + length: 5 + storage: + range: + start: 10 + length: 5 + instances: + crc: + networks: + ctlplane: + ip: "192.168.122.10" + storage: + ip: "172.18.0.10" + controller: + networks: + ctlplane: + ip: "192.168.122.5" + cifmw_networking_mapper_ifaces_info: + controller-0: + - mac: fa:16:3e:12:4f:d1 + network: test-network + - mac: aa:45:3e:ab:ff:e4 + network: test-network-2 + instance-0: + - mac: fa:16:3e:c1:b2:e9 + network: test-network + instance-1: + - mac: fa:16:3e:3d:e5:cc + network: test-network + - mac: aa:45:3e:f9:00:c0 + network: test-network-2 + cifmw_networking_mapper_network_name: "test-network" + ansible.builtin.include_role: + name: networking_mapper +``` diff --git a/roles/networking_mapper/defaults/main.yml b/roles/networking_mapper/defaults/main.yml new file mode 100644 index 0000000000..f0977e6eca --- /dev/null +++ b/roles/networking_mapper/defaults/main.yml @@ -0,0 +1,30 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +# All variables intended for modification should be placed in this file. +# All variables within this role should have a prefix of "cifmw_networking_mapper" +cifmw_networking_mapper_basedir: >- + {{ + cifmw_basedir | default(ansible_user_dir ~ '/ci-framework-data') + }} +cifmw_networking_mapper_infra_dir: "/etc/ci/env" +cifmw_networking_mapper_networking_def_path: "{{ cifmw_networking_mapper_infra_dir }}/networking-definition.yml" +cifmw_networking_mapper_networking_env_def_path: >- + {{ (cifmw_networking_mapper_infra_dir, + 'networking-environment-definition.yml') | path_join + }} +cifmw_networking_mapper_ifaces_info_path: "{{ cifmw_networking_mapper_basedir }}/parameters/interfaces-info.yml" diff --git a/roles/networking_mapper/meta/main.yml b/roles/networking_mapper/meta/main.yml new file mode 100644 index 0000000000..0d7ee526fb --- /dev/null +++ b/roles/networking_mapper/meta/main.yml @@ -0,0 +1,41 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +galaxy_info: + author: CI Framework + description: CI Framework Role -- networking_mapper + company: Red Hat + license: Apache-2.0 + min_ansible_version: 2.14 + namespace: cifmw + # + # Provide a list of supported platforms, and for each platform a list of versions. + # If you don't wish to enumerate all versions for a particular platform, use 'all'. + # To view available platforms and versions (or releases), visit: + # https://galaxy.ansible.com/api/v1/platforms/ + # + platforms: + - name: CentOS + versions: + - 9 + + galaxy_tags: + - cifmw + +# List your role dependencies here, one per line. Be sure to remove the '[]' above, +# if you add dependencies to this list. +dependencies: [] diff --git a/roles/networking_mapper/molecule/default/converge.yml b/roles/networking_mapper/molecule/default/converge.yml new file mode 100644 index 0000000000..938f44bd0a --- /dev/null +++ b/roles/networking_mapper/molecule/default/converge.yml @@ -0,0 +1,216 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +- name: Converge + hosts: all + tasks: + - name: Set testing facts + ansible.builtin.set_fact: + _testing_net_def: + networks: + ctlplane: + network: "192.168.122.0/24" + gateway: "192.168.122.1" + dns: + - "192.168.122.253" + - "192.168.122.254" + search-domain: "ctlplane.example.local" + mtu: 1500 + internal-api: + network: "172.17.0.0/24" + gateway: "172.17.0.1" + vlan: 20 + mtu: 1496 + storage: + network: "172.18.0.0/24" + vlan: 21 + mtu: 1496 + tenant: + network: "172.19.0.0/24" + gateway-v4: "172.19.0.1" + search-domain: "tenant.example.local" + dns-v4: + - "8.8.8.8" + - "172.19.0.1" + vlan: 22 + mtu: 1496 + group-templates: + computes: + networks: + ctlplane: + range: "192.168.122.10-192.168.122.14" + internal-api: + range: "10-14" + tenant: + range: + start: 10 + length: 5 + storage: + range: + start: 10 + length: 5 + instances: + crc: + networks: + ctlplane: + ip: "192.168.122.10" + storage: + ip: "172.18.0.10" + + controller: + networks: + ctlplane: + ip: "192.168.122.5" + + - name: Call the networking mapper partial map + vars: + cifmw_networking_definition: "{{ _testing_net_def }}" + ansible.builtin.include_role: + name: networking_mapper + + - name: Slurp the Networking Definition content + ansible.builtin.slurp: + path: "/etc/ci/env/networking-definition.yml" + register: _net_def_slurp + + - name: Assert the Networking Definition file content + vars: + _content: >- + {{ + _net_def_slurp.content | + b64decode | + from_yaml + }} + ansible.builtin.assert: + that: _content == _testing_net_def + + - name: Slurp Zuul inventory + ansible.builtin.slurp: + path: "{{ ansible_user_dir }}/ci-framework-data/artifacts/zuul_inventory.yml" + register: _zuul_inventory_slurp + + - name: Set common facts + vars: + _zuul_inventory_content: >- + {{ + _zuul_inventory_slurp.content | + b64decode | + from_yaml + }} + ansible.builtin.set_fact: + _inventory_content: "{{ _zuul_inventory_content }}" + _inventory_host_items: >- + {{ + _zuul_inventory_content.all.hosts | + dict2items | + selectattr("value.ansible_connection", "defined") | + selectattr("value.ansible_connection", "equalto", "ssh") + }} + _ifaces_info_net: "test-network" + + - name: Add Zuul hosts + vars: + hosts_groups: >- + {% set _groups = {} -%} + {% for name, content in (_inventory_content.all.children | default([])).items() -%} + {% for host_name in content.hosts | default([]) -%} + {% set _ = _groups.update({host_name: (_groups[host_name] | default([])) + [name] }) -%} + {% endfor -%} + {% endfor -%} + {{ _groups }} + ansible.builtin.add_host: + name: '{{ item.key }}' + ansible_ssh_host: "{{ item.value.ansible_host }}" + ansible_ssh_port: "{{ item.value.ansible_port }}" + ansible_ssh_user: "{{ item.value.ansible_user }}" + ansible_ssh_private_key_file: "{{ ansible_user_dir }}/.ssh/id_rsa" + groups: "{{ hosts_groups[item.key] | default([]) }}" + loop: "{{ _inventory_host_items }}" + + - name: Fetch net facts to further build the testing interfaces_info + ansible.builtin.setup: + gather_subset: + - network + delegate_to: "{{ item.key }}" + delegate_facts: true + loop: "{{ _inventory_host_items }}" + + - name: Build interfaces_info + vars: + _host_default_ipv4: "{{ hostvars[item.key]['ansible_default_ipv4'] }}" + _host_iface_content: + ip: "{{ _host_default_ipv4.address }}" + mac: "{{ _host_default_ipv4.macaddress }}" + network: "{{ _ifaces_info_net }}" + ansible.builtin.set_fact: + _interfaces_info_content: >- + {{ + _interfaces_info_content | + default({}) | + combine( + { + item.key: [ + _host_iface_content + ] + } + ) + }} + loop: "{{ _inventory_host_items }}" + + - name: Call the networking mapper full map + vars: + cifmw_networking_definition: "{{ _testing_net_def }}" + cifmw_networking_mapper_ifaces_info: "{{ _interfaces_info_content }}" + cifmw_networking_mapper_network_name: "{{ _ifaces_info_net }}" + ansible.builtin.include_role: + name: networking_mapper + + - name: Stat to check for Networking Env Definition file existence + ansible.builtin.slurp: + path: "/etc/ci/env/networking-environment-definition.yml" + register: _net_env_def_slurp + + - name: Assert the Networking Env Definition file content + vars: + _content: >- + {{ + _net_env_def_slurp.content | + b64decode | + from_yaml + }} + ansible.builtin.assert: + that: + - "_content.instances is defined" + - "_content.networks is defined" + - >- + " + ( + content.networks.keys() | list | sort + ) == + ( + _testing_net_def.keys() | list | sort + ) + " + - >- + " + ( + _content.instances.keys() | sort + ) == + ( + _inventory_host_items | map(attribute='key') | list | sort + ) + " diff --git a/roles/networking_mapper/molecule/default/molecule.yml b/roles/networking_mapper/molecule/default/molecule.yml new file mode 100644 index 0000000000..fda947cafe --- /dev/null +++ b/roles/networking_mapper/molecule/default/molecule.yml @@ -0,0 +1,11 @@ +--- +# Mainly used to override the defaults set in .config/molecule/ +# By default, it uses the "config_podman.yml" - in CI, it will use +# "config_local.yml". +log: true + +provisioner: + name: ansible + log: true + env: + ANSIBLE_STDOUT_CALLBACK: yaml diff --git a/roles/networking_mapper/molecule/default/prepare.yml b/roles/networking_mapper/molecule/default/prepare.yml new file mode 100644 index 0000000000..d3594acc41 --- /dev/null +++ b/roles/networking_mapper/molecule/default/prepare.yml @@ -0,0 +1,21 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +- name: Prepare + hosts: all + roles: + - role: test_deps diff --git a/roles/networking_mapper/tasks/main.yml b/roles/networking_mapper/tasks/main.yml new file mode 100644 index 0000000000..7197475fad --- /dev/null +++ b/roles/networking_mapper/tasks/main.yml @@ -0,0 +1,107 @@ +--- +# Copyright Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +- name: Ensure that networking and hostname facts are in place + ansible.builtin.setup: + gather_subset: + - network + - platform + delegate_to: "{{ item }}" + delegate_facts: true + loop: "{{ hostvars.keys() | list }}" + +- name: Load the Network Definition from file if not given + when: "cifmw_networking_definition is not defined" + block: + - name: Check for Networking Definition file existence + ansible.builtin.stat: + path: "{{ cifmw_networking_mapper_networking_def_path }}" + register: _net_def_stat + + - name: Load the Networking Definition from file + when: "_net_def_stat.stat.exists" + ansible.builtin.include_vars: + file: "{{ cifmw_networking_mapper_networking_def_path }}" + name: cifmw_networking_definition + +- name: Load the interfaces info from file if not given + when: "cifmw_networking_mapper_ifaces_info is not defined" + block: + - name: Check for interfaces info file existence + ansible.builtin.stat: + path: "{{ cifmw_networking_mapper_ifaces_info_path }}" + register: _ifaces_info_stat + + - name: Load the interfaces info from file + when: "_ifaces_info_stat.stat.exists" + ansible.builtin.include_vars: + file: "{{ cifmw_networking_mapper_ifaces_info_path }}" + name: cifmw_networking_mapper_ifaces_info + +- name: Call the networking mapper + cifmw.general.ci_net_map: + networking_definition: "{{ cifmw_networking_definition }}" + interfaces_info: >- + {{ + cifmw_networking_mapper_ifaces_info | + default(omit) + }} + search_domain_base: >- + {{ + cifmw_networking_mapper_search_domain_base | + default(omit) + }} + network_name: >- + {{ + cifmw_networking_mapper_network_name | default(omit) + }} + register: _networking_mapper_out + +- name: Set networking mapper facts + ansible.builtin.set_fact: + # Set is as a fact in case it was not + cifmw_networking_definition: "{{ cifmw_networking_definition }}" + cifmw_networking_env_definition: >- + {{ + _networking_mapper_out.networking_env_definition + if _networking_mapper_out.complete_map else omit + }} + cifmw_networking_mapper_output: >- + {{ + _networking_mapper_out.networking_env_definition + }} + +- name: Write mapping outputs to file + become: true + block: + - name: Ensure CI infrastructure dir exists + ansible.builtin.file: + path: "{{ cifmw_networking_mapper_infra_dir }}" + state: directory + mode: '0755' + + - name: Write the Networking Definition to file + ansible.builtin.copy: + dest: "{{ cifmw_networking_mapper_networking_def_path }}" + content: "{{ cifmw_networking_definition | to_nice_yaml }}" + mode: '0644' + + - name: Write the Networking Environment Definition to file + when: _networking_mapper_out.complete_map + ansible.builtin.copy: + dest: "{{ cifmw_networking_mapper_networking_env_def_path }}" + content: "{{ _networking_mapper_out.networking_env_definition | to_nice_yaml }}" + mode: '0644' diff --git a/zuul.d/molecule.yaml b/zuul.d/molecule.yaml index 5e768eeb4c..a54b496796 100644 --- a/zuul.d/molecule.yaml +++ b/zuul.d/molecule.yaml @@ -342,6 +342,31 @@ parent: cifmw-molecule-base vars: TEST_RUN: manage_secrets +- job: + files: + - ^common-requirements.txt + - ^test-requirements.txt + - ^roles/networking_mapper/(?!meta|README).* + - ^ci/playbooks/molecule.* + name: cifmw-molecule-networking_mapper + nodeset: + groups: + - name: computes + nodes: + - compute-0 + - compute-1 + nodes: + - label: cloud-centos-9-stream-tripleo-vexxhost-medium + name: controller + - label: cloud-centos-9-stream-tripleo-vexxhost-medium + name: compute-0 + - label: cloud-centos-9-stream-tripleo-vexxhost-medium + name: compute-1 + - label: cloud-centos-9-stream-tripleo-vexxhost-medium + name: crc + parent: cifmw-molecule-base + vars: + TEST_RUN: networking_mapper - job: files: - ^common-requirements.txt diff --git a/zuul.d/projects.yaml b/zuul.d/projects.yaml index 516f56a256..122c373817 100644 --- a/zuul.d/projects.yaml +++ b/zuul.d/projects.yaml @@ -41,6 +41,7 @@ - cifmw-molecule-install_yamls - cifmw-molecule-libvirt_manager - cifmw-molecule-manage_secrets + - cifmw-molecule-networking_mapper - cifmw-molecule-openshift_login - cifmw-molecule-openshift_provisioner_node - cifmw-molecule-openshift_setup