Skip to content

Commit

Permalink
[networking_mapper] Complete the Move to best effort approach and all…
Browse files Browse the repository at this point in the history
…ow multiple base nets
  • Loading branch information
pablintino committed May 21, 2024
1 parent b892366 commit 0e7c1f8
Show file tree
Hide file tree
Showing 20 changed files with 275 additions and 503 deletions.
50 changes: 4 additions & 46 deletions plugins/action/ci_net_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,9 @@
- 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.
- It's required for full maps.
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.
Expand Down Expand Up @@ -247,8 +240,8 @@
type: bool
"""

from ansible.plugins.action import ActionBase
from ansible.errors import AnsibleActionFail
from ansible.plugins.action import ActionBase

from ansible_collections.cifmw.general.plugins.module_utils.encoding import (
ansible_encoding,
Expand All @@ -260,26 +253,6 @@


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()
Expand All @@ -292,33 +265,18 @@ def run(self, tmp=None, task_vars=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)
mapping_result = mapper.map(
networking_definition, interfaces_info=task_args.get("interfaces_info", None)
)
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_raw()), "failed": True}

Expand Down
160 changes: 75 additions & 85 deletions plugins/module_utils/net_map/networking_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class NetworkingMapperOptions:
"""

search_domain_base: str = None
interfaces_info_translations: typing.Dict[str, typing.List[str]] = None

@classmethod
def from_dict(cls, data: typing.Dict[str, typing.Any]) -> "NetworkingMapperOptions":
Expand Down Expand Up @@ -120,12 +121,13 @@ def __init__(
self,
instance_name,
pools_manager: ip_pools.IPPoolsManager,
options: NetworkingMapperOptions = None,
host_vars: typing.Dict[str, typing.Any] = None,
group_templates: typing.Dict[
str, networking_definition.GroupTemplateDefinition
] = None,
instance_definition: networking_definition.InstanceDefinition = None,
interface_info: typing.Dict[str, typing.Any] = None,
interfaces_info: typing.List[typing.Dict[str, typing.Any]] = None,
):
"""Initializes a NetworkingInstanceMapper
Expand All @@ -138,22 +140,21 @@ def __init__(
to the instance. Optional.
instance_definition: The networking_definition.InstanceDefinition
of the instance. Optional.
interface_info: Dict of interface-related information. If given,
this dict must contain a `mac` field with the network interface
interfaces_info: A list of dicts of interface-related information. If given,
each element must contain a `mac` field with the network interface
MAC address of the instance.
"""
self.__instance_name = instance_name
self.__pools_manager = pools_manager
self.__host_vars = host_vars
self.__options = options
self.__instance_definition: typing.Union[
networking_definition.InstanceDefinition, None
] = instance_definition
self.__group_templates: typing.Dict[
str, networking_definition.GroupTemplateDefinition
] = (group_templates or {})
self.__interface_info: typing.Optional[typing.Dict[str, typing.Any]] = (
interface_info
)
self.__interfaces_info = interfaces_info or []
self.__add_instance_reservation(instance_definition, pools_manager)

@property
Expand Down Expand Up @@ -195,7 +196,7 @@ def __map_instance_network(
ipv4, ipv6 = self.__map_instance_network_ips(
net_def.name, group_net_def, instance_net_definition
)
iface_data = self.__map_instance_network_interface_data()
iface_data = self.__map_instance_network_interface_data(net_def)
device_name = iface_data.get("device", None)
interface_name = (
f"{device_name}.{net_def.vlan}"
Expand All @@ -219,7 +220,7 @@ def __map_instance_network(
parent_interface=device_name if net_def.vlan else None,
interface_name=interface_name,
mac_addr=self.__map_instance_network_interface_mac(
iface_data.get("macaddress", None), net_def.vlan
iface_data.get("macaddress", None), net_def
),
is_trunk_parent=self.__map_instance_net_is_trunk_parent(
group_net_def, instance_net_definition
Expand Down Expand Up @@ -302,46 +303,62 @@ def __map_instance_net_trunk_parent(

@staticmethod
def __map_instance_network_interface_mac(
main_iface_mac: typing.Optional[str], vlan_id: typing.Optional[int]
interface_mac: typing.Optional[str],
net_def: networking_definition.NetworkDefinition,
) -> typing.Optional[str]:
if not vlan_id:
return main_iface_mac
if main_iface_mac:
random_inst = random.Random()
random_inst.seed(a=f"{main_iface_mac}{vlan_id}")
mac_bytes = [
0x52,
0x54,
0x00,
random_inst.randint(0x00, 0x7F),
random_inst.randint(0x00, 0xFF),
random_inst.randint(0x00, 0xFF),
]

return ":".join(map(lambda x: "%02x" % x, mac_bytes))
return None
if interface_mac:
return interface_mac
random_inst = random.Random()
random_inst.seed(a=net_def.name)
mac_bytes = [
0x52,
0x54,
0x00,
random_inst.randint(0x00, 0x7F),
random_inst.randint(0x00, 0xFF),
random_inst.randint(0x00, 0xFF),
]

return ":".join(map(lambda x: "%02x" % x, mac_bytes))

def __map_instance_network_interface_data(
self,
self, net_def: networking_definition.NetworkDefinition
) -> typing.Optional[typing.Dict[str, typing.Any]]:
if self.__interface_info is None:
return {}
interfaces_info_mac = self.__interface_info.get("mac", None)
if not interfaces_info_mac:
raise exceptions.NetworkMappingError(
f"interface information for {self.__instance_name} instance "
"does not contain mac address"
interfaces_info_mac = None
for iface_info in self.__interfaces_info:
interfaces_info_net_name = iface_info.get("network", None)
if not interfaces_info_net_name:
raise exceptions.NetworkMappingError(
f"interface information for {self.__instance_name} instance "
"does not contain `network` field"
)
if interfaces_info_net_name == net_def.name or (
net_def.name
in (self.__options.interfaces_info_translations or {}).get(
interfaces_info_net_name, []
)
):
if "mac" not in iface_info:
raise exceptions.NetworkMappingError(
f"interface information for {self.__instance_name} instance and {interfaces_info_net_name} "
"network does not contain `mac` field"
)
interfaces_info_mac = iface_info["mac"]
break

interface_data = {}
if interfaces_info_mac:
interface_data = (
self.__map_instance_network_interface_ansible_data(interfaces_info_mac)
or {}
)
interface_data = self.__map_instance_network_interface_ansible_data(
interfaces_info_mac
)
if interfaces_info_mac and "macaddress" not in interface_data:
interface_data["macaddress"] = interfaces_info_mac.lower()
if "macaddress" not in interface_data:
interface_data["macaddress"] = interfaces_info_mac.lower()
return interface_data

def __map_instance_network_interface_ansible_data(
self, interfaces_info_mac: typing.Optional[str]
) -> typing.Dict[str, typing.Any]:
) -> typing.Optional[typing.Dict[str, typing.Any]]:
ansible_interfaces = self.__host_vars.get("ansible_interfaces", [])
for iface_name in ansible_interfaces:
# Ansible internally replaces `-` and `:` by _ to create safe facts names
Expand All @@ -357,7 +374,7 @@ def __map_instance_network_interface_ansible_data(
mac = ansible_iface_data.get("macaddress", None)
if mac and mac.lower() == interfaces_info_mac.lower():
return ansible_iface_data
return {}
return None

def __map_instance_network_ips(
self,
Expand Down Expand Up @@ -704,7 +721,7 @@ def map_routers(self, network_definition_raw: typing.Dict[str, typing.Any]):

return self.__safe_encode_to_primitives(routers)

def map_partial(
def map(
self,
network_definition_raw: typing.Dict[str, typing.Any],
interfaces_info: typing.Dict[str, typing.Any] = None,
Expand All @@ -728,48 +745,12 @@ def map_partial(
exceptions.NetworkMappingError: If any inconsistency is found
during the mapping process.
"""
net_definition = self.__parse_validate_net_definition(network_definition_raw)
return self.__safe_encode_to_primitives(
self.__map(net_definition, interfaces_info=interfaces_info)
)

def map_complete(
self,
network_definition_raw: typing.Dict[str, typing.Any],
interfaces_info: typing.Dict[str, typing.Any],
) -> typing.Dict[str, typing.Any]:
"""
Parses, validates and maps a Networking Definition into a complete
networking_env_definitions.NetworkingEnvironmentDefinition.
The resulting mapping is a dictionary that only contains primitive types.
Args:
network_definition_raw: The Networking Definition to map.
interfaces_info: Dict containing the MAC addresses of each instance.
Returns: The Networking Environment Definition as a dictionary.
Raises:
exceptions.NetworkMappingValidationError:
If network_definition_raw or interfaces_info are not provided,
or they are not a dictionary.
exceptions.NetworkMappingError: If any inconsistency is found
during the mapping process.
"""
if not isinstance(interfaces_info, dict):
if interfaces_info is not None and not isinstance(interfaces_info, dict):
raise exceptions.NetworkMappingValidationError(
"interfaces_info is a mandatory dict"
"interfaces_info must be a list of dicts"
)
net_definition = self.__parse_validate_net_definition(network_definition_raw)
return self.__safe_encode_to_primitives(
self.__map(net_definition, interfaces_info=interfaces_info)
)

def __map(
self,
net_definition: networking_definition.NetworkingDefinition,
interfaces_info: typing.Optional[typing.Dict[str, typing.Any]] = None,
) -> networking_env_definitions.NetworkingEnvironmentDefinition:
net_definition = self.__parse_validate_net_definition(network_definition_raw)
inst_groups_of_interest = self.__create_instances_dict(net_definition)
pools_manager = ip_pools.IPPoolsManager(net_definition.group_templates)
instance_mappers = self.__build_instances_net_mappers(
Expand All @@ -786,9 +767,10 @@ def __map(

routers = self.__routers_mapper.map_routers(net_definition.routers)

return networking_env_definitions.NetworkingEnvironmentDefinition(
net_def_env = networking_env_definitions.NetworkingEnvironmentDefinition(
networks, instances, routers
)
return self.__safe_encode_to_primitives(net_def_env)

@staticmethod
def __parse_validate_net_definition(
Expand Down Expand Up @@ -825,7 +807,9 @@ def __build_instances_net_mappers(
instance_groups: typing.Dict[str, typing.List[str]],
net_definition: networking_definition.NetworkingDefinition,
pools_manager: ip_pools.IPPoolsManager,
interfaces_info: typing.Optional[typing.Dict[str, typing.Any]] = None,
interfaces_info: typing.Optional[
typing.Dict[str, typing.List[typing.Dict[str, typing.Any]]]
] = None,
) -> typing.List[NetworkingInstanceMapper]:
instance_nets_mappers: typing.List[NetworkingInstanceMapper] = []
for instance_name, instance_groups in instance_groups.items():
Expand All @@ -846,18 +830,24 @@ def __build_instances_net_mappers(
f"{instance_name} instance is not part of the Ansible inventory"
)

instance_interface_info = (
instance_interfaces_info = (
interfaces_info[instance_name] if interfaces_info else None
)

if instance_interfaces_info is not None and not isinstance(
instance_interfaces_info, list
):
raise exceptions.NetworkMappingError(
f"interfaces_info for {instance_name} instance must be a list of dicts"
)
instance_nets_mappers.append(
NetworkingInstanceMapper(
instance_name,
pools_manager,
self.__options,
self.__host_vars.get(instance_name, {}),
instance_definition=instance_definition,
group_templates=groups_template_definitions,
interface_info=instance_interface_info,
interfaces_info=instance_interfaces_info,
)
)

Expand Down
1 change: 0 additions & 1 deletion roles/ci_nmstate/molecule/default/converge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@
instance:
- mac: "{{ ansible_default_ipv4.macaddress }}"
network: "testing-net"
cifmw_networking_mapper_network_name: "testing-net"
ansible.builtin.include_role:
name: "networking_mapper"

Expand Down
Loading

0 comments on commit 0e7c1f8

Please sign in to comment.