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
11 changes: 11 additions & 0 deletions ansible/inventory/group_vars/all/network
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@ network_patch_suffix_phy: '-phy'
# OVS bridge.
network_patch_suffix_ovs: '-ovs'

###############################################################################
# OpenStack Network configuration

# Whether to register networks in OpenStack. The following networks are
# registered:
# - Ironic provisioning network (defined by provision_wl_net_name)
# - Ironic inspection network (defined by inspection_net_name)
# - Ironic cleaning network (defined by cleaning_net_name)
# Default is true.
openstack_network_registration_enabled: true

###############################################################################
# Network routing table configuration.

Expand Down
2 changes: 1 addition & 1 deletion ansible/network-connectivity.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
default(100) }}
vars:
# Skip external connectivity check when behind a proxy.
nc_skip_external_net: "{{ http_proxy | truthy }}"
nc_skip_external_net: "{{ http_proxy is truthy }}"
# Set this to an external IP address to check.
nc_external_ip: 8.8.8.8
# Set this to an external hostname to check.
Expand Down
52 changes: 27 additions & 25 deletions ansible/provision-net.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,29 +68,31 @@
- cleaning-net
- inspection-net
tasks:
- name: Validate OpenStack password authentication parameters
fail:
msg: >
Required OpenStack authentication parameter {{ item }} is
{% if item in openstack_auth %}empty{% else %}not present{% endif %}
in openstack_auth. Have you sourced the environment file?
when:
- openstack_auth_type == 'password'
- item not in openstack_auth or not openstack_auth[item]
with_items: "{{ openstack_auth_password_required_params }}"
tags:
- config-validation
- block:
- name: Validate OpenStack password authentication parameters
fail:
msg: >
Required OpenStack authentication parameter {{ item }} is
{% if item in openstack_auth %}empty{% else %}not present{% endif %}
in openstack_auth. Have you sourced the environment file?
when:
- openstack_auth_type == 'password'
- item not in openstack_auth or not openstack_auth[item]
with_items: "{{ openstack_auth_password_required_params }}"
tags:
- config-validation

- import_role:
name: stackhpc.openstack.os_networks
vars:
os_openstacksdk_install_epel: "{{ dnf_install_epel }}"
os_openstacksdk_state: "latest"
os_networks_upper_constraints_file: "{{ openstacksdk_upper_constraints_file }}"
os_networks_venv: "{{ venv }}"
os_networks_auth_type: "{{ openstack_auth_type }}"
os_networks_auth: "{{ openstack_auth }}"
os_networks_cacert: "{{ openstack_cacert | default(omit, true) }}"
os_networks_interface: "{{ openstack_interface | default(omit, true) }}"
# Network configuration.
os_networks: "{{ network_registrations }}"
- import_role:
name: stackhpc.openstack.os_networks
vars:
os_openstacksdk_install_epel: "{{ dnf_install_epel }}"
os_openstacksdk_state: "latest"
os_networks_upper_constraints_file: "{{ openstacksdk_upper_constraints_file }}"
os_networks_venv: "{{ venv }}"
os_networks_auth_type: "{{ openstack_auth_type }}"
os_networks_auth: "{{ openstack_auth }}"
os_networks_cacert: "{{ openstack_cacert | default(omit, true) }}"
os_networks_interface: "{{ openstack_interface | default(omit, true) }}"
# Network configuration.
os_networks: "{{ network_registrations }}"
when: openstack_network_registration_enabled | bool
70 changes: 70 additions & 0 deletions doc/source/configuration/reference/network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,26 @@ The following attributes are supported:
bond and bridge interfaces, settings apply to underlying interfaces. This
should be a string of arguments passed to the ``ethtool`` utility, for
example ``"-G ${DEVICE} rx 8192 tx 8192"``.
``ingress_qos_map``
.. note::

``ingress_qos_map`` is only supported with
``network_engine: nmstate`` on VLAN interfaces.

VLAN ingress QoS map configuration. This maps VLAN header Priority Code
Point (PCP) to Linux internal packet priority for incoming packets.

- Structured list: ``[{from: 7, to: 254}, {from: 3, to: 12}]``
``egress_qos_map``
.. note::

``egress_qos_map`` is only supported with
``network_engine: nmstate`` on VLAN interfaces.

VLAN egress QoS map configuration. This maps Linux internal packet
priority to VLAN header Priority Code Point (PCP) for outgoing packets.

- Structured list: ``[{from: 129, to: 7}, {from: 130, to: 6}]``
``zone``
.. note:: ``zone`` is not currently supported on Ubuntu.

Expand Down Expand Up @@ -765,6 +785,29 @@ this case, a ``parent`` attribute must specify the underlying interface:
Ethernet interfaces, bridges, and bond master interfaces may all be parents to
a VLAN interface.

VLAN QoS Mapping (nmstate)
^^^^^^^^^^^^^^^^^^^^^^^^^^

When using ``network_engine: nmstate``, VLAN interfaces can define native
nmstate QoS maps via ``ingress_qos_map`` and ``egress_qos_map``. These map
between Linux internal packet priority and VLAN header Priority Code Point
(PCP). Use the structured list-of-maps form.

.. code-block:: yaml
:caption: ``inventory/group_vars/<group>/network-interfaces``

example_interface: "eth2.{{ example_vlan }}"
example_ingress_qos_map:
- from: 7
to: 254
example_egress_qos_map:
- from: 129
to: 7

Undefined QoS map values do not render an nmstate QoS map key. Use an
explicit empty list (``[]``) to render an empty map through nmstate, for
example when clearing an existing map.

Bridges and VLANs
^^^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -926,6 +969,27 @@ consideration:
lookup the interface for the cloud-init network configuration that occurs
during bifrost provisioning of the overcloud.


Registration of networks in OpenStack
--------------------------------------

By default, Kayobe will register the following networks:

* :ref:`workload-cleaning-network`
* :ref:`workload-provisioning-network`
* :ref:`workload-inspection-network`

as part of ``kayobe overcloud post configure``. You can change this behaviour
for all networks with the ``openstack_network_registration_enabled`` variable:

.. code-block:: yaml
:caption: ``networks.yml``

# Disabling registration of OpenStack networks
openstack_network_registration_enabled: false

This can be useful if you define you networks by some other means e.g OpenTofu.

Overcloud Provisioning Network
------------------------------

Expand All @@ -951,6 +1015,8 @@ To configure a network called ``example`` with an inspection allocation pool:
This pool should not overlap with a kayobe allocation pool on the same
network.

.. _workload-cleaning-network:

Workload Cleaning Network
-------------------------

Expand Down Expand Up @@ -980,6 +1046,8 @@ allocation pool:
This pool should not overlap with a kayobe or inspection allocation pool on
the same network.

.. _workload-provisioning-network:

Workload Provisioning Network
-----------------------------

Expand Down Expand Up @@ -1007,6 +1075,8 @@ allocation pool:
This pool should not overlap with a kayobe or inspection allocation pool on
the same network.

.. _workload-inspection-network:

Workload Inspection Network
---------------------------

Expand Down
11 changes: 11 additions & 0 deletions etc/kayobe/networks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,17 @@
# OVS bridge.
#network_patch_suffix_ovs:

###############################################################################
# OpenStack Network configuration

# Whether to register networks in OpenStack. The following networks are
# registered:
# - Ironic provisioning network (defined by provision_wl_net_name)
# - Ironic inspection network (defined by inspection_net_name)
# - Ironic cleaning network (defined by cleaning_net_name)
# Default is true.
#openstack_network_registration_enabled:

###############################################################################
# Network routing table configuration.

Expand Down
28 changes: 27 additions & 1 deletion kayobe/cli/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,20 @@ def take_action(self, parsed_args):
ignore_limit=True, check=False)


class ControlHostServiceDeploy(KayobeAnsibleMixin, VaultMixin, Command):
"""Deploy the Ansible control host services."""

def take_action(self, parsed_args):
self.app.LOG.debug("Running no-op control host service deploy")


class ControlHostServiceDestroy(KayobeAnsibleMixin, VaultMixin, Command):
"""Destroy the Ansible control host services."""

def take_action(self, parsed_args):
self.app.LOG.debug("Running no-op control host service destroy")


class ConfigurationDump(KayobeAnsibleMixin, VaultMixin, Command):
"""Dump Kayobe configuration.

Expand Down Expand Up @@ -1202,12 +1216,24 @@ def take_action(self, parsed_args):

class InfraVMServiceDeploy(KayobeAnsibleMixin, VaultMixin,
Command):
"""Run hooks for infra structure services."""
"""Run hooks for infra VM services."""

def take_action(self, parsed_args):
self.app.LOG.debug("Running no-op Infra VM service deploy")


class InfraVMServiceDestroy(KayobeAnsibleMixin, VaultMixin,
Command):
"""Destroy the infra VM services.

Permanently destroy the infra VM containers, container images, and
container volumes.
"""

def take_action(self, parsed_args):
self.app.LOG.debug("Running no-op Infra VM service destroy")


class OvercloudInventoryDiscover(KayobeAnsibleMixin, VaultMixin, Command):
"""Discover the overcloud inventory from the seed's Ironic service.

Expand Down
87 changes: 87 additions & 0 deletions kayobe/plugins/filter/nmstate.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ def _get_ip_config(context, name, inventory_hostname, defroute=None):
'rx', 'tx', 'rx-max', 'tx-max', 'rx-jumbo', 'rx-mini'
}

MAX_U32 = 2**32 - 1
MAX_VLAN_PRIORITY = 7


def _resolve_ethtool_feature_aliases(features):
"""Convert ethtool feature aliases to canonical names.
Expand Down Expand Up @@ -290,6 +293,79 @@ def _get_bond_options(context, name, inventory_hostname):
return bond_options


def _validate_vlan_qos_map_int(value, network_name, direction, field):
if isinstance(value, bool) or not isinstance(value, int):
raise ValueError(
f"Network '{network_name}' has invalid {direction} QoS map "
f"'{field}' value '{value}'. Expected an integer.")

if value < 0 or value > MAX_U32:
raise ValueError(
f"Network '{network_name}' has invalid {direction} QoS map "
f"'{field}' value '{value}'. Expected a value in range "
f"0..{MAX_U32}.")

if direction == "ingress" and field == "from" \
and value > MAX_VLAN_PRIORITY:
raise ValueError(
f"Network '{network_name}' has invalid ingress QoS map "
f"'from' value '{value}'. Maximum VLAN priority is "
f"{MAX_VLAN_PRIORITY}.")

if direction == "egress" and field == "to" \
and value > MAX_VLAN_PRIORITY:
raise ValueError(
f"Network '{network_name}' has invalid egress QoS map "
f"'to' value '{value}'. Maximum VLAN priority is "
f"{MAX_VLAN_PRIORITY}.")

return value


def _validate_vlan_qos_map_entry(entry, network_name, direction):
if not isinstance(entry, dict):
raise ValueError(
f"Network '{network_name}' has invalid {direction} QoS map "
"entry format. Expected a dict with keys 'from' and 'to'.")

if "from" not in entry or "to" not in entry:
raise ValueError(
f"Network '{network_name}' has invalid {direction} QoS map "
"entry. Required keys are 'from' and 'to'.")

return {
"from": _validate_vlan_qos_map_int(
entry["from"], network_name, direction, "from"),
"to": _validate_vlan_qos_map_int(
entry["to"], network_name, direction, "to")
}


def _validate_and_sort_vlan_qos_map(raw_map, network_name, direction):
if raw_map is None:
return None

if not isinstance(raw_map, list):
raise ValueError(
f"Network '{network_name}' has invalid {direction} QoS map "
f"format '{type(raw_map).__name__}'. Expected a list of dicts "
"with keys 'from' and 'to'.")

normalized = []
for entry in raw_map:
normalized.append(_validate_vlan_qos_map_entry(
entry, network_name, direction))

normalized.sort(key=lambda item: (item["from"], item["to"]))
return normalized


def _get_vlan_qos_map(context, name, inventory_hostname, direction):
qos_map = networks.net_attr(
context, name, f"{direction}_qos_map", inventory_hostname)
return _validate_and_sort_vlan_qos_map(qos_map, name, direction)


@jinja2.pass_context
def nmstate_config(context, names, inventory_hostname=None):
interfaces = {}
Expand Down Expand Up @@ -514,6 +590,17 @@ def get_iface(name):
"base-iface": parent,
"id": int(vlan_id)
}

ingress_qos_map = _get_vlan_qos_map(
context, name, inventory_hostname, "ingress")
if ingress_qos_map is not None:
iface["vlan"]["ingress-qos-map"] = ingress_qos_map

egress_qos_map = _get_vlan_qos_map(
context, name, inventory_hostname, "egress")
if egress_qos_map is not None:
iface["vlan"]["egress-qos-map"] = egress_qos_map

# Ensure parent is initialized
get_iface(parent)

Expand Down
Loading
Loading