Skip to content

Commit

Permalink
[networking_mapping] Add the Ansible wrapper role
Browse files Browse the repository at this point in the history
  • Loading branch information
pablintino authored and openshift-merge-bot[bot] committed Jan 11, 2024
1 parent 86d6845 commit 4090cda
Show file tree
Hide file tree
Showing 13 changed files with 986 additions and 0 deletions.
17 changes: 17 additions & 0 deletions ci/config/molecule.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
325 changes: 325 additions & 0 deletions plugins/action/ci_net_map.py
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions plugins/module_utils/net_map/exceptions.py
Original file line number Diff line number Diff line change
@@ -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__(
Expand Down
18 changes: 18 additions & 0 deletions plugins/module_utils/net_map/networking_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
Loading

0 comments on commit 4090cda

Please sign in to comment.