Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions plugins/filter/match_portgroup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from __future__ import absolute_import, division, print_function

__metaclass__ = type

DOCUMENTATION = """
name: match_portgroup
short_description: Filters NADs matching a network name in the source-portgroup annotation
version_added: 1.0.0
author: ""
description:
- Filters a list of NetworkAttachmentDefinitions to find those whose
source-portgroup annotation contains the given network name.
- Supports comma-separated values in the annotation for multiple portgroups.
- The annotation is always split on commas, and each resulting value is
stripped of surrounding whitespace before being compared. A source
portgroup whose name contains a literal comma therefore cannot be
expressed in this annotation and will never match. Such portgroups must
be mapped explicitly with the C(network_attachment_definition) override
in C(mtv_management_network_map_overrides).
options:
_input:
description: List of NAD objects from Forklift inventory.
type: list
required: true
network_name:
description: The network name to match against the annotation.
type: string
required: true
annotation_key:
description: The annotation key to check.
type: string
required: true
namespace:
description: Optional namespace to filter by.
type: string
required: false
"""

EXAMPLES = """
- name: Find NADs matching a portgroup name
ansible.builtin.set_fact:
matched: >-
{{ nads | infra.openshift_virtualization_migration.match_portgroup(
'VLAN100', 'infra.openshift-virtualization-migration/source-portgroup') }}

- name: Find NADs matching a portgroup name in a specific namespace
ansible.builtin.set_fact:
matched: >-
{{ nads | infra.openshift_virtualization_migration.match_portgroup(
'VLAN100', 'infra.openshift-virtualization-migration/source-portgroup', 'my-namespace') }}
"""

RETURN = """
_value:
description: List of NAD objects whose annotation matches the network name.
type: list
"""


def match_portgroup(nads, network_name, annotation_key, namespace=""):
"""Filter NADs whose source-portgroup annotation contains the network name.

Supports both single-value and comma-separated annotations. Portgroup names
containing a literal comma cannot be represented in the annotation.
"""
results = []
for nad in nads:
try:
annotations = (
nad.get("object", {}).get("metadata", {}).get("annotations", {})
)
annotation_value = annotations.get(annotation_key, "")
portgroups = [
pg.strip() for pg in annotation_value.split(",") if pg.strip()
]

if network_name not in portgroups:
continue

if namespace and namespace.strip():
nad_namespace = (
nad.get("object", {}).get("metadata", {}).get("namespace", "")
)
if nad_namespace != namespace.strip():
continue

results.append(nad)
except (AttributeError, TypeError):
continue
return results


class FilterModule(object):
"""Filter to match portgroup annotations"""

def filters(self):
return {
"match_portgroup": match_portgroup,
}
12 changes: 10 additions & 2 deletions roles/mtv_management/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,17 @@ mtv_management_multiple_storage_maps_overrides: {}
# description: Overrides for the network map
mtv_management_network_map_overrides: [
# - id: <network_id>
# network_destination_type: pod # Explicitly specify destination type
# network_attachment_definition: nad # Namespaced name (<namespace>/<name>) or just <name> if in default namespace
# network_destination_type: pod # Explicitly specify destination type (pod, ignored, or multus)
# network_attachment_definition: <namespace>/<name> # Namespaced name or just <name> if in default namespace
# exclude: true
# include: true
#
# Multiple port groups mapping to the same NAD:
# - id: <first_portgroup_id>
# network_attachment_definition: my-namespace/my-nad
# include: true
# - id: <second_portgroup_id>
# network_attachment_definition: my-namespace/my-nad # Same NAD as above
# include: true
]
...
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,18 @@

- name: _mtv_network_map_process_network | Locate VMware NetworkAttachmentDefinition
ansible.builtin.set_fact:
mtv_management_mtv_network_networkattachmentdefinition: >-
{{ mtv_networkattachmentdefinitions | json_query(__mtv_management_query) }}
mtv_management_mtv_network_networkattachmentdefinition: "{{ __mtv_management_matched_nads }}"
vars:
__mtv_management_nad_override_ns: "{{ (mtv_management_mtv_vmware_network_overrides['network_attachment_definition'] | ansible.builtin.split('/'))[0] if 'network_attachment_definition' in mtv_management_mtv_vmware_network_overrides and (mtv_management_mtv_vmware_network_overrides['network_attachment_definition'] | ansible.builtin.split('/') | length) == 2 else '' }}" # noqa: yaml[line-length]
__mtv_management_query: "{{ '[?object.metadata.annotations.\"' + mtv_management_nad_source_portgroup_annotation + '\"==`' + mtv_vmware_network.name + '`' + (' && object.metadata.namespace==`' + (__mtv_management_nad_override_ns | trim) + '`' if (__mtv_management_nad_override_ns | default('') | trim) | length > 0 else '') + ']' }}" # noqa: yaml[line-length]
__mtv_management_matched_nads: "{{ mtv_networkattachmentdefinitions | infra.openshift_virtualization_migration.match_portgroup(mtv_vmware_network.name, mtv_management_nad_source_portgroup_annotation, __mtv_management_nad_override_ns) }}" # noqa: yaml[line-length]
when: mtv_vmware_network is defined

- name: _mtv_network_map_process_network | Locate Ovirt NetworkAttachmentDefinition
ansible.builtin.set_fact:
mtv_management_mtv_network_networkattachmentdefinition: >-
{{ mtv_networkattachmentdefinitions | json_query(__mtv_management_query) }}
mtv_management_mtv_network_networkattachmentdefinition: "{{ __mtv_management_matched_nads }}"
vars:
__mtv_management_nad_override_ns: "{{ (mtv_management_mtv_ovirt_network_overrides['network_attachment_definition'] | ansible.builtin.split('/'))[0] if 'network_attachment_definition' in mtv_management_mtv_ovirt_network_overrides and (mtv_management_mtv_ovirt_network_overrides['network_attachment_definition'] | ansible.builtin.split('/') | length) == 2 else '' }}" # noqa: yaml[line-length]
__mtv_management_query: "{{ '[?object.metadata.annotations.\"' + mtv_management_nad_source_portgroup_annotation + '\"==`' + mtv_ovirt_network.name + '`' + (' && object.metadata.namespace==`' + (__mtv_management_nad_override_ns | trim) + '`' if (__mtv_management_nad_override_ns | default('') | trim) | length > 0 else '') + ']' }}" # noqa: yaml[line-length]
__mtv_management_matched_nads: "{{ mtv_networkattachmentdefinitions | infra.openshift_virtualization_migration.match_portgroup(mtv_ovirt_network.name, mtv_management_nad_source_portgroup_annotation, __mtv_management_nad_override_ns) }}" # noqa: yaml[line-length]
when: mtv_ovirt_network is defined

- name: _mtv_network_map_process_network | Validate Found VMware NetworkAttachmentDefinitions
Expand Down
46 changes: 20 additions & 26 deletions roles/mtv_management/templates/networkmap_map.yml.j2
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
destination:
{% if provider in ['vsphere', 'ovirt'] %}
{% if 'vsphere' == provider %}
{% if 'network_destination_type' in mtv_management_mtv_vmware_network_overrides and mtv_management_mtv_vmware_network_overrides['network_destination_type'] == "ignored" %}
type: ignored
{% elif ('network_destination_type' in mtv_management_mtv_vmware_network_overrides and mtv_management_mtv_vmware_network_overrides['network_destination_type'] == "pod") or mtv_management_mtv_network_networkattachmentdefinition | length == 0 %}
type: pod
{% else %}
{% if 'network_attachment_definition' in mtv_management_mtv_vmware_network_overrides and mtv_management_mtv_vmware_network_overrides['network_attachment_definition'] | default('', true) | trim | length > 0 %}
{% set multus_nad_name = (mtv_management_mtv_vmware_network_overrides['network_attachment_definition'] | ansible.builtin.split('/'))[1] if mtv_management_mtv_vmware_network_overrides['network_attachment_definition'] | ansible.builtin.split('/') | length == 2 else mtv_management_mtv_vmware_network_overrides['network_attachment_definition'] %}
{% set multus_nad_namespace = (mtv_management_mtv_vmware_network_overrides['network_attachment_definition'] | ansible.builtin.split('/'))[0] if mtv_management_mtv_vmware_network_overrides['network_attachment_definition'] | ansible.builtin.split('/') | length == 2 else "default" %}
{% set __network_overrides = mtv_management_mtv_vmware_network_overrides %}
{% set __source_network = mtv_vmware_network %}
{% else %}
{% set multus_nad_name = (mtv_management_mtv_network_networkattachmentdefinition | first).object.metadata.name %}
{% set multus_nad_namespace = (mtv_management_mtv_network_networkattachmentdefinition | first).object.metadata.namespace %}
{% set __network_overrides = mtv_management_mtv_ovirt_network_overrides %}
{% set __source_network = mtv_ovirt_network %}
{% endif %}
type: multus
name: {{ multus_nad_name }}
namespace: {{ multus_nad_namespace }}
{% endif %}
source:
id: "{{ mtv_vmware_network.id }}"
name: "{{ mtv_vmware_network.name }}"
{% elif 'ovirt' == provider %}
{% if 'network_destination_type' in mtv_management_mtv_ovirt_network_overrides and mtv_management_mtv_ovirt_network_overrides['network_destination_type'] == "ignored" %}
{# Destination type explicitly requested through the network map overrides, if any. #}
{% set __destination_type = (__network_overrides['network_destination_type'] | default('', true) | trim) if 'network_destination_type' in __network_overrides else '' %}
{# NAD explicitly requested through the network map overrides, as '<namespace>/<name>' or '<name>'. #}
{% set __override_nad = (__network_overrides['network_attachment_definition'] | default('', true) | trim) if 'network_attachment_definition' in __network_overrides else '' %}
{# Fall back to the pod network only when no NAD was discovered *and* none was explicitly requested. #}
{% set __should_use_pod = __destination_type == "pod" or (mtv_management_mtv_network_networkattachmentdefinition | length == 0 and __override_nad | length == 0) %}
{% if __destination_type == "ignored" %}
type: ignored
{% elif ('network_destination_type' in mtv_management_mtv_ovirt_network_overrides and mtv_management_mtv_ovirt_network_overrides['network_destination_type'] == "pod") or mtv_management_mtv_network_networkattachmentdefinition | length == 0 %}
{% elif __should_use_pod %}
type: pod
{% else %}
{% if 'network_attachment_definition' in mtv_management_mtv_ovirt_network_overrides and mtv_management_mtv_ovirt_network_overrides['network_attachment_definition'] | default('', true) | trim | length > 0 %}
{% set multus_nad_name = (mtv_management_mtv_ovirt_network_overrides['network_attachment_definition'] | ansible.builtin.split('/'))[1] if mtv_management_mtv_ovirt_network_overrides['network_attachment_definition'] | ansible.builtin.split('/') | length == 2 else mtv_management_mtv_ovirt_network_overrides['network_attachment_definition'] %}
{% set multus_nad_namespace = (mtv_management_mtv_ovirt_network_overrides['network_attachment_definition'] | ansible.builtin.split('/'))[0] if mtv_management_mtv_ovirt_network_overrides['network_attachment_definition'] | ansible.builtin.split('/') | length == 2 else "default" %}
{% if __override_nad | length > 0 %}
{% set __override_nad_parts = __override_nad | ansible.builtin.split('/') %}
{% set multus_nad_name = __override_nad_parts[1] if __override_nad_parts | length == 2 else __override_nad %}
{% set multus_nad_namespace = __override_nad_parts[0] if __override_nad_parts | length == 2 else "default" %}
{% else %}
{% set multus_nad_name = (mtv_management_mtv_network_networkattachmentdefinition | first).object.metadata.name %}
{% set multus_nad_namespace = (mtv_management_mtv_network_networkattachmentdefinition | first).object.metadata.namespace %}
Expand All @@ -37,6 +31,6 @@ source:
namespace: {{ multus_nad_namespace }}
{% endif %}
source:
id: "{{ mtv_ovirt_network.id }}"
name: "{{ mtv_ovirt_network.name }}"
{% endif %}
id: "{{ __source_network.id }}"
name: "{{ __source_network.name }}"
{% endif %}
14 changes: 9 additions & 5 deletions roles/network_mgmt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ Description: Management of network related components.
| manual ¦ Validate linux-bridge | `ansible.builtin.assert` | True |
| manual ¦ Validate ovn-layer2 NAD entries | `ansible.builtin.assert` | True |
| manual ¦ Validate per-NAD MTU range | `ansible.builtin.assert` | True |
| manual ¦ Validate mutually exclusive portgroup and portgroups | `ansible.builtin.assert` | False |
| manual ¦ Validate portgroups entries | `ansible.builtin.assert` | True |
| manual ¦ Apply NodeNetworkConfigurationPolicy | `redhat.openshift.k8s` | True |
| manual ¦ Validate access port | `ansible.builtin.assert` | True |
| manual ¦ Validate trunk ports | `ansible.builtin.assert` | True |
Expand Down Expand Up @@ -400,11 +402,13 @@ classDef rescue stroke:#665352,stroke-width:2px;
manual___Validate_ovs_bridge_mode2-->|Task| manual___Validate_linux_bridge3[manual validate linux bridge<br>When: **network mgmt openshift network bridge mode <br>linux bridge**]:::task
manual___Validate_linux_bridge3-->|Task| manual___Validate_ovn_layer2_NAD_entries4[manual validate ovn layer2 nad entries<br>When: **network mgmt openshift network bridge mode ovn<br>layer2**]:::task
manual___Validate_ovn_layer2_NAD_entries4-->|Task| manual___Validate_per_NAD_MTU_range5[manual validate per nad mtu range<br>When: **network mgmt openshift network bridge mode ovn<br>layer2 and nad mtu is defined**]:::task
manual___Validate_per_NAD_MTU_range5-->|Task| manual___Apply_NodeNetworkConfigurationPolicy6[manual apply nodenetworkconfigurationpolicy<br>When: **network mgmt manual bridge name default <br>length 0 and network mgmt manual bond name <br>default length 0 and network mgmt<br>openshift network bridge mode linux bridge**]:::task
manual___Apply_NodeNetworkConfigurationPolicy6-->|Task| manual___Validate_access_port7[manual validate access port<br>When: **network mgmt openshift network bridge mode ovn<br>layer2 and trunk not in nad or not nad trunk<br>**]:::task
manual___Validate_access_port7-->|Task| manual___Validate_trunk_ports8[manual validate trunk ports<br>When: **network mgmt openshift network bridge mode ovn<br>layer2 and trunk in nad and nad trunk**]:::task
manual___Validate_trunk_ports8-->|Task| manual___Apply_NetworkAttachmentDefinitions9[manual apply networkattachmentdefinitions]:::task
manual___Apply_NetworkAttachmentDefinitions9-->End
manual___Validate_per_NAD_MTU_range5-->|Task| manual___Validate_mutually_exclusive_portgroup_and_portgroups6[manual validate mutually exclusive portgroup and<br>portgroups]:::task
manual___Validate_mutually_exclusive_portgroup_and_portgroups6-->|Task| manual___Validate_portgroups_entries7[manual validate portgroups entries<br>When: **portgroups in nad**]:::task
manual___Validate_portgroups_entries7-->|Task| manual___Apply_NodeNetworkConfigurationPolicy8[manual apply nodenetworkconfigurationpolicy<br>When: **network mgmt manual bridge name default <br>length 0 and network mgmt manual bond name <br>default length 0 and network mgmt<br>openshift network bridge mode linux bridge**]:::task
manual___Apply_NodeNetworkConfigurationPolicy8-->|Task| manual___Validate_access_port9[manual validate access port<br>When: **network mgmt openshift network bridge mode ovn<br>layer2 and trunk not in nad or not nad trunk<br>**]:::task
manual___Validate_access_port9-->|Task| manual___Validate_trunk_ports10[manual validate trunk ports<br>When: **network mgmt openshift network bridge mode ovn<br>layer2 and trunk in nad and nad trunk**]:::task
manual___Validate_trunk_ports10-->|Task| manual___Apply_NetworkAttachmentDefinitions11[manual apply networkattachmentdefinitions]:::task
manual___Apply_NetworkAttachmentDefinitions11-->End
```

## Playbook
Expand Down
16 changes: 16 additions & 0 deletions roles/network_mgmt/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,20 @@ network_mgmt_manual_localnet_name: ''
# required: True
# description: NAD List in Manual Mode
network_mgmt_manual_nad_list: []
# - name: my-nad
# namespace: my-namespace
# portgroup: my-portgroup # Single source portgroup (string)
# mtu: 1500 # Optional MTU override (ovn-layer2 only)
# - name: my-shared-nad
# namespace: my-namespace
# portgroups: # Multiple source portgroups (list)
# - portgroup-site-a # Joined with comma in the source-portgroup annotation
# - portgroup-site-b
#
# 'portgroup' and 'portgroups' are mutually exclusive: defining both on the same entry
# fails validation. Because the names are joined with commas into the
# infra.openshift-virtualization-migration/source-portgroup annotation, and that annotation
# is split on commas when the network map is built, a portgroup name containing a literal
# comma is rejected. Map such a portgroup explicitly with the network_attachment_definition
# override in mtv_management_network_map_overrides instead.
...
30 changes: 30 additions & 0 deletions roles/network_mgmt/tasks/manual.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,36 @@
loop_control:
loop_var: nad

- name: manual | Validate mutually exclusive portgroup and portgroups
ansible.builtin.assert:
that:
- not (('portgroup' in nad and nad.portgroup | default('', true) | trim | length > 0) and
('portgroups' in nad and nad.portgroups | default([], true) | length > 0))
fail_msg: >-
NAD '{{ nad.name | default('<unnamed>') }}' defines both 'portgroup' and 'portgroups'.
Use 'portgroup' for a single source portgroup or 'portgroups' for several, not both.
loop: "{{ network_mgmt_manual_nad_list }}"
loop_control:
loop_var: nad

- name: manual | Validate portgroups entries
ansible.builtin.assert:
that:
- nad.portgroups is iterable
- nad.portgroups is not string
- nad.portgroups is not mapping
- nad.portgroups | map('string') | select('search', ',') | list | length == 0
fail_msg: >-
'portgroups' for NAD '{{ nad.name | default('<unnamed>') }}' must be a list of portgroup
names, and no name may contain a comma: the names are joined with commas into the
infra.openshift-virtualization-migration/source-portgroup annotation, which uses the
comma as its separator.
loop: "{{ network_mgmt_manual_nad_list }}"
when:
- "'portgroups' in nad"
loop_control:
loop_var: nad

## Only create the NNCP if the bridge and the bond is defined, the template looks for the ports to put in the bond.
- name: manual | Apply NodeNetworkConfigurationPolicy
redhat.openshift.k8s:
Expand Down
4 changes: 3 additions & 1 deletion roles/network_mgmt/templates/manual-lb/nad.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ metadata:
namespace: {{ nad.namespace | default(network_mgmt_nad_namespace) }}
annotations:
infra.openshift-virtualization-migration/provider-type: manual
{% if 'portgroup' in nad and nad['portgroup'] | default("", true) | length > 0 %}
{% if 'portgroups' in nad and nad['portgroups'] | default([], true) | length > 0 %}
infra.openshift-virtualization-migration/source-portgroup: {{ nad.portgroups | join(',') }}
{% elif 'portgroup' in nad and nad['portgroup'] | default("", true) | length > 0 %}
infra.openshift-virtualization-migration/source-portgroup: {{ nad.portgroup }}
{% endif %}
{% if network_mgmt_openshift_network_bridge_mode == 'linux-bridge' %}
Expand Down
Empty file.
Loading
Loading