Ubuntu: support systemd-networkd
This change adds support for network configuration via systemd-networkd on Ubuntu systems. This is implemented via an Ansible Galaxy role, stackhpc.systemd_networkd which was forked from aruhier.systemd_networkd. Several improvements were made in https://github.com/stackhpc/ansible-role-systemd-networkd/pull/1, including: * Add support for removing unexpected config files * Use become where necessary * Refactor config generation into a single task to improve performance The systemd_networkd role does not add much abstraction on top of the systemd-networkd configuration file format, which provides a lot of flexibility at the expense of additional code in Kayobe. This code is implemented as filter plugins, similarly to the existing MichaelRigart.interfaces role. This patch includes support for: * Ethernet interfaces * bridges * bonds * VLANs * virtual Ethernet pairs (to connect Linux bridges and OVS bridges) * static IP addresses * static routes * MTU Some network attributes are currently not supported for systemd-networkd: * rules * route options * ethtool_opts * zone * allowed addresses Story: 2004960 Task: 41881 Change-Id: I248b5bb9ce5a80a07a2a311cb3aca6daca920720
This commit is contained in:
parent
ae2ed2215a
commit
3bbf736d8d
22
ansible/filter_plugins/networkd.py
Normal file
22
ansible/filter_plugins/networkd.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# Copyright (c) 2021 StackHPC Ltd.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
from kayobe.plugins.filter import networkd
|
||||||
|
|
||||||
|
|
||||||
|
class FilterModule(object):
|
||||||
|
"""Systemd-networkd filters."""
|
||||||
|
|
||||||
|
def filters(self):
|
||||||
|
return networkd.get_filters()
|
@ -86,3 +86,9 @@ network_patch_suffix_ovs: '-ovs'
|
|||||||
# List of IP routing tables. Each item should be a dict containing 'id' and
|
# List of IP routing tables. Each item should be a dict containing 'id' and
|
||||||
# 'name' items. These tables will be added to /etc/iproute2/rt_tables.
|
# 'name' items. These tables will be added to /etc/iproute2/rt_tables.
|
||||||
network_route_tables: []
|
network_route_tables: []
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Systemd-networkd configuration.
|
||||||
|
|
||||||
|
# Prefix for systemd-networkd configuration file names.
|
||||||
|
networkd_prefix: "50-kayobe-"
|
||||||
|
16
ansible/roles/network-debian/handlers/main.yml
Normal file
16
ansible/roles/network-debian/handlers/main.yml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
- name: Find netplan systemd-networkd configuration
|
||||||
|
become: true
|
||||||
|
find:
|
||||||
|
path: /run/systemd/network
|
||||||
|
register: netplan_systemd_networkd_config
|
||||||
|
listen: Remove netplan systemd-networkd configuration
|
||||||
|
|
||||||
|
- name: Remove netplan systemd-networkd configuration
|
||||||
|
become: true
|
||||||
|
file:
|
||||||
|
path: "{{ item.path }}"
|
||||||
|
state: absent
|
||||||
|
loop: "{{ netplan_systemd_networkd_config.files }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.path }}"
|
@ -1,51 +1,29 @@
|
|||||||
---
|
---
|
||||||
- name: Ensure NetworkManager is disabled
|
|
||||||
service:
|
|
||||||
name: NetworkManager
|
|
||||||
state: stopped
|
|
||||||
enabled: no
|
|
||||||
become: True
|
|
||||||
register: nm_result
|
|
||||||
failed_when:
|
|
||||||
- nm_result is failed
|
|
||||||
# Ugh, Ansible's service module doesn't handle uninstalled services.
|
|
||||||
- "'Could not find the requested service' not in nm_result.msg"
|
|
||||||
|
|
||||||
- import_role:
|
- import_role:
|
||||||
name: ahuffman.resolv
|
name: ahuffman.resolv
|
||||||
when: resolv_is_managed | bool
|
when: resolv_is_managed | bool
|
||||||
become: True
|
become: True
|
||||||
|
|
||||||
- name: Configure network interfaces (RedHat)
|
- name: Remove netplan.io packages
|
||||||
import_role:
|
become: true
|
||||||
name: MichaelRigart.interfaces
|
package:
|
||||||
vars:
|
name:
|
||||||
interfaces_route_tables: "{{ network_route_tables }}"
|
- libnetplan0
|
||||||
interfaces_ether_interfaces: >
|
- netplan.io
|
||||||
{{ network_interfaces |
|
state: absent
|
||||||
net_select_ethers |
|
notify:
|
||||||
map('net_interface_obj') |
|
- Remove netplan systemd-networkd configuration
|
||||||
list }}
|
|
||||||
interfaces_bridge_interfaces: >
|
|
||||||
{{ network_interfaces |
|
|
||||||
net_select_bridges |
|
|
||||||
map('net_bridge_obj') |
|
|
||||||
list }}
|
|
||||||
interfaces_bond_interfaces: >
|
|
||||||
{{ network_interfaces |
|
|
||||||
net_select_bonds |
|
|
||||||
map('net_bond_obj') |
|
|
||||||
list }}
|
|
||||||
|
|
||||||
# Ensure that interface bouncing is finished before veth pairs are added,
|
- name: Configure systemd-networkd
|
||||||
# since they are only ephemerally configured on Debian.
|
|
||||||
- name: Flush handlers
|
|
||||||
meta: flush_handlers
|
|
||||||
|
|
||||||
# Configure virtual ethernet patch links to connect the workload provision
|
|
||||||
# and external network bridges to the Neutron OVS bridge.
|
|
||||||
- name: Ensure OVS patch links exist
|
|
||||||
import_role:
|
import_role:
|
||||||
name: veth
|
name: stackhpc.systemd_networkd
|
||||||
vars:
|
vars:
|
||||||
veth_interfaces: "{{ network_interfaces | net_ovs_veths }}"
|
systemd_networkd_link: "{{ network_interfaces | networkd_links }}"
|
||||||
|
systemd_networkd_netdev: "{{ network_interfaces | networkd_netdevs }}"
|
||||||
|
systemd_networkd_network: "{{ network_interfaces | networkd_networks }}"
|
||||||
|
systemd_networkd_apply_config: true
|
||||||
|
systemd_networkd_enable_resolved: false
|
||||||
|
systemd_networkd_symlink_resolv_conf: false
|
||||||
|
systemd_networkd_cleanup: true
|
||||||
|
systemd_networkd_cleanup_patterns:
|
||||||
|
- "{{ networkd_prefix }}*"
|
||||||
|
@ -58,6 +58,8 @@ supported:
|
|||||||
|
|
||||||
Fully Qualified Domain Name (FQDN) used by API services on this network.
|
Fully Qualified Domain Name (FQDN) used by API services on this network.
|
||||||
``routes``
|
``routes``
|
||||||
|
.. note:: ``options`` is not currently supported on Ubuntu.
|
||||||
|
|
||||||
List of static IP routes. Each item should be a dict containing the
|
List of static IP routes. Each item should be a dict containing the
|
||||||
item ``cidr``, and optionally ``gateway``, ``table`` and ``options``.
|
item ``cidr``, and optionally ``gateway``, ``table`` and ``options``.
|
||||||
``cidr`` is the CIDR representation of the route's destination. ``gateway``
|
``cidr`` is the CIDR representation of the route's destination. ``gateway``
|
||||||
@ -334,11 +336,15 @@ The following attributes are supported:
|
|||||||
``bond_lacp_rate``
|
``bond_lacp_rate``
|
||||||
For bond interfaces, the lacp_rate to use for the bond.
|
For bond interfaces, the lacp_rate to use for the bond.
|
||||||
``ethtool_opts``
|
``ethtool_opts``
|
||||||
|
.. note:: ``ethtool_opts`` is not currently supported on Ubuntu.
|
||||||
|
|
||||||
Physical network interface options to apply with ``ethtool``. When used on
|
Physical network interface options to apply with ``ethtool``. When used on
|
||||||
bond and bridge interfaces, settings apply to underlying interfaces. This
|
bond and bridge interfaces, settings apply to underlying interfaces. This
|
||||||
should be a string of arguments passed to the ``ethtool`` utility, for
|
should be a string of arguments passed to the ``ethtool`` utility, for
|
||||||
example ``"-G ${DEVICE} rx 8192 tx 8192"``.
|
example ``"-G ${DEVICE} rx 8192 tx 8192"``.
|
||||||
``zone``
|
``zone``
|
||||||
|
.. note:: ``zone`` is not currently supported on Ubuntu.
|
||||||
|
|
||||||
The name of ``firewalld`` zone to be attached to network interface.
|
The name of ``firewalld`` zone to be attached to network interface.
|
||||||
|
|
||||||
IP Addresses
|
IP Addresses
|
||||||
|
@ -240,7 +240,7 @@ Alternatively, this can be added using the following commands::
|
|||||||
|
|
||||||
sudo ip l add breth1 type bridge
|
sudo ip l add breth1 type bridge
|
||||||
sudo ip l set breth1 up
|
sudo ip l set breth1 up
|
||||||
sudo ip a add 192.168.33.5/24 dev breth1
|
sudo ip a add 192.168.33.5/24 brd 192.168.33.255 dev breth1
|
||||||
sudo ip l add eth1 type dummy
|
sudo ip l add eth1 type dummy
|
||||||
sudo ip l set eth1 up
|
sudo ip l set eth1 up
|
||||||
sudo ip l set eth1 master breth1
|
sudo ip l set eth1 master breth1
|
||||||
|
571
kayobe/plugins/filter/networkd.py
Normal file
571
kayobe/plugins/filter/networkd.py
Normal file
@ -0,0 +1,571 @@
|
|||||||
|
# Copyright (c) 2021 StackHPC Ltd.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
This module provides Ansible filters that generate configuration for
|
||||||
|
systemd-networkd NetDevs, links and networks. The results are compatible with
|
||||||
|
the stackhpc.ansible_role_systemd_networkd role.
|
||||||
|
|
||||||
|
Systemd-networkd uses INI-style configuration files, with the provision for
|
||||||
|
multiple sections with the same name, and multiple options with the same name
|
||||||
|
in a given section. This results in a slightly unwieldy data format used by the
|
||||||
|
role. The top level is a list of dicts with section names as keys. The values
|
||||||
|
are lists of dicts mapping option names to values.
|
||||||
|
|
||||||
|
Example schema (YAML):
|
||||||
|
- section1:
|
||||||
|
- option1: value1
|
||||||
|
- option2: value2
|
||||||
|
- section2
|
||||||
|
- option3: value3
|
||||||
|
"""
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
|
|
||||||
|
from ansible import errors
|
||||||
|
import jinja2
|
||||||
|
|
||||||
|
from kayobe.plugins.filter import networks
|
||||||
|
from kayobe.plugins.filter import utils
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_options(config):
|
||||||
|
"""Filter out None values from a networkd config.
|
||||||
|
|
||||||
|
:param config: List of sections to filter.
|
||||||
|
:returns: a filtered list of sections without empty options.
|
||||||
|
"""
|
||||||
|
# Example schema (YAML):
|
||||||
|
# - section1:
|
||||||
|
# - option1: value1
|
||||||
|
# - option2:
|
||||||
|
# - section2
|
||||||
|
# - option3:
|
||||||
|
# We can filter this down to the following:
|
||||||
|
# - section1:
|
||||||
|
# - option1: value1
|
||||||
|
new_config = []
|
||||||
|
for section_dict in config:
|
||||||
|
new_section_dict = {}
|
||||||
|
for section_name, section in section_dict.items():
|
||||||
|
new_section = []
|
||||||
|
for option_dict in section:
|
||||||
|
new_option_dict = {}
|
||||||
|
for option_name, option in option_dict.items():
|
||||||
|
if option is not None:
|
||||||
|
new_option_dict[option_name] = option
|
||||||
|
if new_option_dict:
|
||||||
|
new_section.append(new_option_dict)
|
||||||
|
if new_section:
|
||||||
|
new_section_dict[section_name] = new_section
|
||||||
|
if new_section_dict:
|
||||||
|
new_config.append(new_section_dict)
|
||||||
|
return new_config
|
||||||
|
|
||||||
|
|
||||||
|
def _ms_to_s(n):
|
||||||
|
"""Convert from milliseconds to seconds."""
|
||||||
|
if n is not None:
|
||||||
|
n = float(n) / 1000
|
||||||
|
return n
|
||||||
|
|
||||||
|
|
||||||
|
def _vlan_netdev(context, name, inventory_hostname):
|
||||||
|
"""Return a networkd NetDev configuration for a VLAN interface.
|
||||||
|
|
||||||
|
:param context: a Jinja2 Context object.
|
||||||
|
:param name: name of the network.
|
||||||
|
:param inventory_hostname: Ansible inventory hostname.
|
||||||
|
"""
|
||||||
|
device = networks.net_interface(context, name, inventory_hostname)
|
||||||
|
mtu = networks.net_mtu(context, name, inventory_hostname)
|
||||||
|
vlan = networks.net_vlan(context, name, inventory_hostname)
|
||||||
|
config = [
|
||||||
|
{
|
||||||
|
'NetDev': [
|
||||||
|
{'Name': device},
|
||||||
|
{'Kind': 'vlan'},
|
||||||
|
{'MTUBytes': mtu},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'VLAN': [
|
||||||
|
{'Id': vlan},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return _filter_options(config)
|
||||||
|
|
||||||
|
|
||||||
|
def _bridge_netdev(context, name, inventory_hostname):
|
||||||
|
"""Return a networkd NetDev configuration for a bridge.
|
||||||
|
|
||||||
|
:param context: a Jinja2 Context object.
|
||||||
|
:param name: name of the network.
|
||||||
|
:param inventory_hostname: Ansible inventory hostname.
|
||||||
|
"""
|
||||||
|
device = networks.net_interface(context, name, inventory_hostname)
|
||||||
|
mtu = networks.net_mtu(context, name, inventory_hostname)
|
||||||
|
config = [
|
||||||
|
{
|
||||||
|
'NetDev': [
|
||||||
|
{'Name': device},
|
||||||
|
{'Kind': 'bridge'},
|
||||||
|
{'MTUBytes': mtu},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return _filter_options(config)
|
||||||
|
|
||||||
|
|
||||||
|
def _bond_netdev(context, name, inventory_hostname):
|
||||||
|
"""Return a networkd NetDev configuration for a bond.
|
||||||
|
|
||||||
|
:param context: a Jinja2 Context object.
|
||||||
|
:param name: name of the network.
|
||||||
|
:param inventory_hostname: Ansible inventory hostname.
|
||||||
|
"""
|
||||||
|
device = networks.net_interface(context, name, inventory_hostname)
|
||||||
|
mtu = networks.net_mtu(context, name, inventory_hostname)
|
||||||
|
mode = networks.net_bond_mode(context, name, inventory_hostname)
|
||||||
|
miimon = networks.net_bond_miimon(context, name, inventory_hostname)
|
||||||
|
updelay = networks.net_bond_updelay(context, name, inventory_hostname)
|
||||||
|
downdelay = networks.net_bond_downdelay(context, name, inventory_hostname)
|
||||||
|
xmit_hash_policy = networks.net_bond_xmit_hash_policy(context, name,
|
||||||
|
inventory_hostname)
|
||||||
|
lacp_rate = networks.net_bond_lacp_rate(context, name, inventory_hostname)
|
||||||
|
config = [
|
||||||
|
{
|
||||||
|
'NetDev': [
|
||||||
|
{'Name': device},
|
||||||
|
{'Kind': 'bond'},
|
||||||
|
{'MTUBytes': mtu},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Bond': [
|
||||||
|
{'Mode': mode},
|
||||||
|
{'TransmitHashPolicy': xmit_hash_policy},
|
||||||
|
{'LACPTransmitRate': lacp_rate},
|
||||||
|
{'MIIMonitorSec': _ms_to_s(miimon)},
|
||||||
|
{'UpDelaySec': _ms_to_s(updelay)},
|
||||||
|
{'DownDelaySec': _ms_to_s(downdelay)},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return _filter_options(config)
|
||||||
|
|
||||||
|
|
||||||
|
def _veth_netdev(context, veth, inventory_hostname):
|
||||||
|
"""Return a networkd NetDev configuration for a veth pair.
|
||||||
|
|
||||||
|
:param context: a Jinja2 Context object.
|
||||||
|
:param veth: a dict describing the virtual Ethernet pair.
|
||||||
|
:param inventory_hostname: Ansible inventory hostname.
|
||||||
|
"""
|
||||||
|
interface = veth['name']
|
||||||
|
peer = veth['peer']
|
||||||
|
mtu = veth['mtu']
|
||||||
|
config = [
|
||||||
|
{
|
||||||
|
'NetDev': [
|
||||||
|
{'Name': interface},
|
||||||
|
{'Kind': 'veth'},
|
||||||
|
{'MTUBytes': mtu},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Peer': [
|
||||||
|
{'Name': peer},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return _filter_options(config)
|
||||||
|
|
||||||
|
|
||||||
|
def _network(context, name, inventory_hostname, bridge, bond, vlan_interfaces):
|
||||||
|
"""Return a networkd network for an interface.
|
||||||
|
|
||||||
|
:param context: a Jinja2 Context object.
|
||||||
|
:param name: name of the network.
|
||||||
|
:param inventory_hostname: Ansible inventory hostname.
|
||||||
|
:param bridge: Name of a bridge into which the interface is plugged, or
|
||||||
|
None.
|
||||||
|
:param bond: Name of a bond of which the interface is a member, or None.
|
||||||
|
:param vlan_interfaces: List of VLAN subinterfaces of the interface.
|
||||||
|
"""
|
||||||
|
# FIXME(mgoddard): Currently does not support: rules, ethtool_opts, zone,
|
||||||
|
# allowed_addresses.
|
||||||
|
device = networks.net_interface(context, name, inventory_hostname)
|
||||||
|
ip = networks.net_ip(context, name, inventory_hostname)
|
||||||
|
cidr = networks.net_cidr(context, name, inventory_hostname)
|
||||||
|
gateway = networks.net_gateway(context, name, inventory_hostname)
|
||||||
|
if ip is None:
|
||||||
|
gateway = None
|
||||||
|
else:
|
||||||
|
if not cidr:
|
||||||
|
raise errors.AnsibleFilterError(
|
||||||
|
"No CIDR attribute configured for '%s' network but it has an "
|
||||||
|
"IP address" %
|
||||||
|
(name))
|
||||||
|
ip = "%s/%s" % (ip, ipaddress.ip_network(cidr).prefixlen)
|
||||||
|
|
||||||
|
mtu = networks.net_mtu(context, name, inventory_hostname)
|
||||||
|
routes = networks.net_routes(context, name, inventory_hostname)
|
||||||
|
bootproto = networks.net_bootproto(context, name, inventory_hostname)
|
||||||
|
defroute = networks.net_defroute(context, name, inventory_hostname)
|
||||||
|
if defroute is not None:
|
||||||
|
defroute = utils.call_bool_filter(context, defroute)
|
||||||
|
config = [
|
||||||
|
{
|
||||||
|
'Match': [
|
||||||
|
{'Name': device},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Network': [
|
||||||
|
{'Address': ip},
|
||||||
|
{'Broadcast': 'true' if ip else None},
|
||||||
|
{'Gateway': gateway},
|
||||||
|
{'DHCP': ('yes' if bootproto and bootproto.lower() == 'dhcp'
|
||||||
|
else None)},
|
||||||
|
{'UseGateway': ('false'
|
||||||
|
if defroute is not None and not defroute
|
||||||
|
else None)},
|
||||||
|
{'Bridge': bridge},
|
||||||
|
{'Bond': bond},
|
||||||
|
] + [
|
||||||
|
{'VLAN': vlan_interface}
|
||||||
|
for vlan_interface in vlan_interfaces
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Link': [
|
||||||
|
{'MTUBytes': mtu},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
if routes:
|
||||||
|
config += [
|
||||||
|
{
|
||||||
|
'Route': [
|
||||||
|
# FIXME(mgoddard): No support for 'options'.
|
||||||
|
{'Destination': route['cidr']},
|
||||||
|
{'Gateway': route.get('gateway')},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
for route in routes or []
|
||||||
|
]
|
||||||
|
return _filter_options(config)
|
||||||
|
|
||||||
|
|
||||||
|
def _bridge_port_network(context, name, port, inventory_hostname,
|
||||||
|
vlan_interfaces):
|
||||||
|
"""Return a networkd network configuration for a bridge port.
|
||||||
|
|
||||||
|
:param context: a Jinja2 Context object.
|
||||||
|
:param name: name of the network.
|
||||||
|
:param port: name of the bridge port interface.
|
||||||
|
:param inventory_hostname: Ansible inventory hostname.
|
||||||
|
:param vlan_interfaces: List of VLAN subinterfaces of the interface.
|
||||||
|
"""
|
||||||
|
bridge = networks.get_and_validate_interface(context, name,
|
||||||
|
inventory_hostname)
|
||||||
|
mtu = networks.net_mtu(context, name, inventory_hostname)
|
||||||
|
config = [
|
||||||
|
{
|
||||||
|
'Match': [
|
||||||
|
{'Name': port},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Network': [
|
||||||
|
{'Bridge': bridge},
|
||||||
|
] + [
|
||||||
|
{'VLAN': vlan_interface}
|
||||||
|
for vlan_interface in vlan_interfaces
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Link': [
|
||||||
|
{'MTUBytes': mtu},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return _filter_options(config)
|
||||||
|
|
||||||
|
|
||||||
|
def _bond_member_network(context, name, member, inventory_hostname,
|
||||||
|
vlan_interfaces):
|
||||||
|
"""Return a networkd network configuration for a bond member.
|
||||||
|
|
||||||
|
:param context: a Jinja2 Context object.
|
||||||
|
:param name: name of the network.
|
||||||
|
:param member: name of the bond member interface.
|
||||||
|
:param inventory_hostname: Ansible inventory hostname.
|
||||||
|
:param vlan_interfaces: List of VLAN subinterfaces of the interface.
|
||||||
|
"""
|
||||||
|
bond = networks.get_and_validate_interface(context, name,
|
||||||
|
inventory_hostname)
|
||||||
|
mtu = networks.net_mtu(context, name, inventory_hostname)
|
||||||
|
config = [
|
||||||
|
{
|
||||||
|
'Match': [
|
||||||
|
{'Name': member},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Network': [
|
||||||
|
{'Bond': bond},
|
||||||
|
] + [
|
||||||
|
{'VLAN': vlan_interface}
|
||||||
|
for vlan_interface in vlan_interfaces
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Link': [
|
||||||
|
{'MTUBytes': mtu},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return _filter_options(config)
|
||||||
|
|
||||||
|
|
||||||
|
def _veth_network(context, veth, inventory_hostname):
|
||||||
|
"""Return a networkd network configuration for a veth link.
|
||||||
|
|
||||||
|
:param context: a Jinja2 Context object.
|
||||||
|
:param veth: a dict describing the virtual Ethernet pair.
|
||||||
|
:param inventory_hostname: Ansible inventory hostname.
|
||||||
|
"""
|
||||||
|
interface = veth['name']
|
||||||
|
bridge = veth['bridge']
|
||||||
|
config = [
|
||||||
|
{
|
||||||
|
'Match': [
|
||||||
|
{'Name': interface},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Network': [
|
||||||
|
{'Bridge': bridge},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return _filter_options(config)
|
||||||
|
|
||||||
|
|
||||||
|
def _veth_peer_network(context, veth, inventory_hostname):
|
||||||
|
"""Return a networkd network configuration for a veth peer.
|
||||||
|
|
||||||
|
:param context: a Jinja2 Context object.
|
||||||
|
:param veth: a dict describing the virtual Ethernet pair.
|
||||||
|
:param inventory_hostname: Ansible inventory hostname.
|
||||||
|
"""
|
||||||
|
interface = veth['peer']
|
||||||
|
config = [
|
||||||
|
{
|
||||||
|
'Match': [
|
||||||
|
{'Name': interface},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'Network': [
|
||||||
|
# NOTE(mgoddard): bring the interface up, even without an IP.
|
||||||
|
{'ConfigureWithoutCarrier': 'true'},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return _filter_options(config)
|
||||||
|
|
||||||
|
|
||||||
|
@jinja2.contextfilter
|
||||||
|
def networkd_netdevs(context, names, inventory_hostname=None):
|
||||||
|
"""Return a dict representation of networkd NetDev configuration.
|
||||||
|
|
||||||
|
The format is compatible with the systemd_networkd_netdev variable in the
|
||||||
|
stackhpc.ansible_role_systemd_networkd role.
|
||||||
|
|
||||||
|
:param context: a Jinja2 Context object.
|
||||||
|
:param names: List of names of networks.
|
||||||
|
:param inventory_hostname: Ansible inventory hostname.
|
||||||
|
:returns: a dict representation of networkd NetDev configuration.
|
||||||
|
"""
|
||||||
|
# Prefix for configuration file names.
|
||||||
|
prefix = utils.get_hostvar(context, "networkd_prefix", inventory_hostname)
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
# VLANs.
|
||||||
|
for name in networks.net_select_vlans(context, names, inventory_hostname):
|
||||||
|
device = networks.get_and_validate_interface(context, name,
|
||||||
|
inventory_hostname)
|
||||||
|
netdev = _vlan_netdev(context, name, inventory_hostname)
|
||||||
|
result["%s%s" % (prefix, device)] = netdev
|
||||||
|
|
||||||
|
# Bridges.
|
||||||
|
for name in networks.net_select_bridges(context, names,
|
||||||
|
inventory_hostname):
|
||||||
|
device = networks.get_and_validate_interface(context, name,
|
||||||
|
inventory_hostname)
|
||||||
|
netdev = _bridge_netdev(context, name, inventory_hostname)
|
||||||
|
result["%s%s" % (prefix, device)] = netdev
|
||||||
|
|
||||||
|
# Bonds.
|
||||||
|
for name in networks.net_select_bonds(context, names, inventory_hostname):
|
||||||
|
device = networks.get_and_validate_interface(context, name,
|
||||||
|
inventory_hostname)
|
||||||
|
netdev = _bond_netdev(context, name, inventory_hostname)
|
||||||
|
result["%s%s" % (prefix, device)] = netdev
|
||||||
|
|
||||||
|
# Virtual Ethernet pairs.
|
||||||
|
veths = networks.get_ovs_veths(context, names, inventory_hostname)
|
||||||
|
for veth in veths:
|
||||||
|
netdev = _veth_netdev(context, veth, inventory_hostname)
|
||||||
|
device = veth['name']
|
||||||
|
result["%s%s" % (prefix, device)] = netdev
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@jinja2.contextfilter
|
||||||
|
def networkd_links(context, names, inventory_hostname=None):
|
||||||
|
"""Return a dict representation of networkd link configuration.
|
||||||
|
|
||||||
|
The format is compatible with the systemd_networkd_link variable in the
|
||||||
|
stackhpc.ansible_role_systemd_networkd role.
|
||||||
|
|
||||||
|
:param context: a Jinja2 Context object.
|
||||||
|
:param names: List of names of networks.
|
||||||
|
:param inventory_hostname: Ansible inventory hostname.
|
||||||
|
:returns: a dict representation of networkd link configuration.
|
||||||
|
"""
|
||||||
|
# NOTE(mgoddard): We do not currently support link configuration.
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
@jinja2.contextfilter
|
||||||
|
def networkd_networks(context, names, inventory_hostname=None):
|
||||||
|
"""Return a dict representation of networkd network configuration.
|
||||||
|
|
||||||
|
The format is compatible with the systemd_networkd_network variable in the
|
||||||
|
stackhpc.ansible_role_systemd_networkd role.
|
||||||
|
|
||||||
|
:param context: a Jinja2 Context object.
|
||||||
|
:param names: List of names of networks.
|
||||||
|
:param inventory_hostname: Ansible inventory hostname.
|
||||||
|
:returns: a dict representation of networkd network configuration.
|
||||||
|
"""
|
||||||
|
# TODO(mgoddard): some attributes are currently not supported for
|
||||||
|
# systemd-networkd: rules, route options, ethtool_opts, zone,
|
||||||
|
# allowed addresses
|
||||||
|
|
||||||
|
# Build up some useful mappings.
|
||||||
|
bridge_port_to_bridge = {}
|
||||||
|
bond_member_to_bond = {}
|
||||||
|
interface_to_vlans = {}
|
||||||
|
|
||||||
|
# List of all interfaces.
|
||||||
|
interfaces = [
|
||||||
|
networks.net_interface(context, name, inventory_hostname)
|
||||||
|
for name in names
|
||||||
|
]
|
||||||
|
|
||||||
|
# Map bridge ports to bridges.
|
||||||
|
for name in networks.net_select_bridges(context, names,
|
||||||
|
inventory_hostname):
|
||||||
|
device = networks.get_and_validate_interface(context, name,
|
||||||
|
inventory_hostname)
|
||||||
|
for port in networks.net_bridge_ports(context, name,
|
||||||
|
inventory_hostname):
|
||||||
|
bridge_port_to_bridge[port] = device
|
||||||
|
|
||||||
|
# Map bond members to bonds.
|
||||||
|
for name in networks.net_select_bonds(context, names, inventory_hostname):
|
||||||
|
device = networks.get_and_validate_interface(context, name,
|
||||||
|
inventory_hostname)
|
||||||
|
for member in networks.net_bond_slaves(context, name,
|
||||||
|
inventory_hostname):
|
||||||
|
bond_member_to_bond[member] = device
|
||||||
|
|
||||||
|
# Map interfaces to lists of VLAN subinterfaces.
|
||||||
|
for name in networks.net_select_vlans(context, names, inventory_hostname):
|
||||||
|
device = networks.get_and_validate_interface(context, name,
|
||||||
|
inventory_hostname)
|
||||||
|
vlan = networks.net_vlan(context, name, inventory_hostname)
|
||||||
|
parent = networks.get_vlan_parent(device, vlan)
|
||||||
|
vlan_interfaces = interface_to_vlans.setdefault(parent, [])
|
||||||
|
vlan_interfaces.append(device)
|
||||||
|
|
||||||
|
# Prefix for configuration file names.
|
||||||
|
prefix = utils.get_hostvar(context, "networkd_prefix", inventory_hostname)
|
||||||
|
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
# Configured networks.
|
||||||
|
for name in names:
|
||||||
|
device = networks.get_and_validate_interface(context, name,
|
||||||
|
inventory_hostname)
|
||||||
|
bridge = bridge_port_to_bridge.get(device)
|
||||||
|
bond = bond_member_to_bond.get(device)
|
||||||
|
vlan_interfaces = interface_to_vlans.get(device, [])
|
||||||
|
net = _network(context, name, inventory_hostname, bridge, bond,
|
||||||
|
vlan_interfaces)
|
||||||
|
result["%s%s" % (prefix, device)] = net
|
||||||
|
|
||||||
|
# Bridge ports that are not in configured networks.
|
||||||
|
for name in networks.net_select_bridges(context, names,
|
||||||
|
inventory_hostname):
|
||||||
|
device = networks.get_and_validate_interface(context, name,
|
||||||
|
inventory_hostname)
|
||||||
|
bridge_ports = networks.net_bridge_ports(context, name,
|
||||||
|
inventory_hostname)
|
||||||
|
for port in set(bridge_ports) - set(interfaces):
|
||||||
|
vlan_interfaces = interface_to_vlans.get(port, [])
|
||||||
|
netdev = _bridge_port_network(context, name, port,
|
||||||
|
inventory_hostname, vlan_interfaces)
|
||||||
|
result["%s%s" % (prefix, port)] = netdev
|
||||||
|
|
||||||
|
# Bond members that are not in configured networks.
|
||||||
|
for name in networks.net_select_bonds(context, names, inventory_hostname):
|
||||||
|
device = networks.get_and_validate_interface(context, name,
|
||||||
|
inventory_hostname)
|
||||||
|
bond_members = networks.net_bond_slaves(context, name,
|
||||||
|
inventory_hostname)
|
||||||
|
for member in set(bond_members) - set(interfaces):
|
||||||
|
vlan_interfaces = interface_to_vlans.get(member, [])
|
||||||
|
netdev = _bond_member_network(context, name, member,
|
||||||
|
inventory_hostname, vlan_interfaces)
|
||||||
|
result["%s%s" % (prefix, member)] = netdev
|
||||||
|
|
||||||
|
# Virtual Ethernet pairs for Open vSwitch.
|
||||||
|
veths = networks.get_ovs_veths(context, names, inventory_hostname)
|
||||||
|
for veth in veths:
|
||||||
|
net = _veth_network(context, veth, inventory_hostname)
|
||||||
|
device = veth['name']
|
||||||
|
result["%s%s" % (prefix, device)] = net
|
||||||
|
|
||||||
|
net = _veth_peer_network(context, veth, inventory_hostname)
|
||||||
|
device = veth['peer']
|
||||||
|
result["%s%s" % (prefix, device)] = net
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_filters():
|
||||||
|
return {
|
||||||
|
'networkd_netdevs': networkd_netdevs,
|
||||||
|
'networkd_links': networkd_links,
|
||||||
|
'networkd_networks': networkd_networks,
|
||||||
|
}
|
748
kayobe/tests/unit/plugins/filter/test_networkd.py
Normal file
748
kayobe/tests/unit/plugins/filter/test_networkd.py
Normal file
@ -0,0 +1,748 @@
|
|||||||
|
# Copyright (c) 2021 StackHPC Ltd.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
import copy
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from ansible import errors
|
||||||
|
from ansible.plugins.filter.core import to_bool
|
||||||
|
import jinja2
|
||||||
|
|
||||||
|
from kayobe.plugins.filter import networkd
|
||||||
|
|
||||||
|
|
||||||
|
class BaseNetworkdTest(unittest.TestCase):
|
||||||
|
|
||||||
|
maxDiff = 2000
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
# Inventory hostname, used to index IP list.
|
||||||
|
"inventory_hostname": "test-host",
|
||||||
|
# net1: Ethernet on eth0 with IP 1.2.3.4/24.
|
||||||
|
"net1_interface": "eth0",
|
||||||
|
"net1_cidr": "1.2.3.0/24",
|
||||||
|
"net1_ips": {"test-host": "1.2.3.4"},
|
||||||
|
# net2: VLAN on eth0.2 with VLAN 2 on interface eth0.
|
||||||
|
"net2_interface": "eth0.2",
|
||||||
|
"net2_vlan": 2,
|
||||||
|
# net3: bridge on br0 with ports eth0 and eth1.
|
||||||
|
"net3_interface": "br0",
|
||||||
|
"net3_bridge_ports": ["eth0", "eth1"],
|
||||||
|
# net4: bond on bond0 with members eth0 and eth1.
|
||||||
|
"net4_interface": "bond0",
|
||||||
|
"net4_bond_slaves": ["eth0", "eth1"],
|
||||||
|
# Prefix for networkd config file names.
|
||||||
|
"networkd_prefix": "50-kayobe-",
|
||||||
|
# Veth pair patch link prefix and suffix.
|
||||||
|
"network_patch_prefix": "p-",
|
||||||
|
"network_patch_suffix_ovs": "-ovs",
|
||||||
|
"network_patch_suffix_phy": "-phy",
|
||||||
|
}
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# Bandit complains about Jinja2 autoescaping without nosec.
|
||||||
|
self.env = jinja2.Environment() # nosec
|
||||||
|
self.env.filters['bool'] = to_bool
|
||||||
|
self.context = self._make_context(self.variables)
|
||||||
|
|
||||||
|
def _make_context(self, parent):
|
||||||
|
return self.env.context_class(
|
||||||
|
self.env, parent=parent, name='dummy', blocks={})
|
||||||
|
|
||||||
|
def _update_context(self, variables):
|
||||||
|
updated_vars = copy.deepcopy(self.variables)
|
||||||
|
updated_vars.update(variables)
|
||||||
|
self.context = self._make_context(updated_vars)
|
||||||
|
|
||||||
|
|
||||||
|
class TestNetworkdNetDevs(BaseNetworkdTest):
|
||||||
|
|
||||||
|
def test_empty(self):
|
||||||
|
devs = networkd.networkd_netdevs(self.context, [])
|
||||||
|
self.assertEqual({}, devs)
|
||||||
|
|
||||||
|
def test_vlan(self):
|
||||||
|
devs = networkd.networkd_netdevs(self.context, ["net2"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-eth0.2": [
|
||||||
|
{
|
||||||
|
"NetDev": [
|
||||||
|
{"Name": "eth0.2"},
|
||||||
|
{"Kind": "vlan"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"VLAN": [
|
||||||
|
{"Id": 2},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, devs)
|
||||||
|
|
||||||
|
def test_vlan_all_options(self):
|
||||||
|
self._update_context({"net2_mtu": 1400})
|
||||||
|
devs = networkd.networkd_netdevs(self.context, ["net2"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-eth0.2": [
|
||||||
|
{
|
||||||
|
"NetDev": [
|
||||||
|
{"Name": "eth0.2"},
|
||||||
|
{"Kind": "vlan"},
|
||||||
|
{"MTUBytes": 1400},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"VLAN": [
|
||||||
|
{"Id": 2},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, devs)
|
||||||
|
|
||||||
|
def test_vlan_no_interface(self):
|
||||||
|
self._update_context({"net2_interface": None})
|
||||||
|
self.assertRaises(errors.AnsibleFilterError,
|
||||||
|
networkd.networkd_netdevs, self.context, ["net2"])
|
||||||
|
|
||||||
|
def test_bridge(self):
|
||||||
|
devs = networkd.networkd_netdevs(self.context, ["net3"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-br0": [
|
||||||
|
{
|
||||||
|
"NetDev": [
|
||||||
|
{"Name": "br0"},
|
||||||
|
{"Kind": "bridge"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, devs)
|
||||||
|
|
||||||
|
def test_bridge_all_options(self):
|
||||||
|
self._update_context({"net3_mtu": 1400})
|
||||||
|
devs = networkd.networkd_netdevs(self.context, ["net3"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-br0": [
|
||||||
|
{
|
||||||
|
"NetDev": [
|
||||||
|
{"Name": "br0"},
|
||||||
|
{"Kind": "bridge"},
|
||||||
|
{"MTUBytes": 1400},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, devs)
|
||||||
|
|
||||||
|
def test_bridge_no_interface(self):
|
||||||
|
self._update_context({"net3_interface": None})
|
||||||
|
self.assertRaises(errors.AnsibleFilterError,
|
||||||
|
networkd.networkd_netdevs, self.context, ["net3"])
|
||||||
|
|
||||||
|
def test_bond(self):
|
||||||
|
devs = networkd.networkd_netdevs(self.context, ["net4"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-bond0": [
|
||||||
|
{
|
||||||
|
"NetDev": [
|
||||||
|
{"Name": "bond0"},
|
||||||
|
{"Kind": "bond"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, devs)
|
||||||
|
|
||||||
|
def test_bond_all_options(self):
|
||||||
|
self._update_context({
|
||||||
|
"net4_mtu": 1400,
|
||||||
|
"net4_bond_mode": "802.3ad",
|
||||||
|
"net4_bond_miimon": 100,
|
||||||
|
"net4_bond_updelay": 200,
|
||||||
|
"net4_bond_downdelay": 300,
|
||||||
|
"net4_bond_xmit_hash_policy": "layer3+4",
|
||||||
|
"net4_bond_lacp_rate": 60,
|
||||||
|
})
|
||||||
|
devs = networkd.networkd_netdevs(self.context, ["net4"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-bond0": [
|
||||||
|
{
|
||||||
|
"NetDev": [
|
||||||
|
{"Name": "bond0"},
|
||||||
|
{"Kind": "bond"},
|
||||||
|
{"MTUBytes": 1400},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Bond": [
|
||||||
|
{"Mode": "802.3ad"},
|
||||||
|
{"TransmitHashPolicy": "layer3+4"},
|
||||||
|
{"LACPTransmitRate": 60},
|
||||||
|
{"MIIMonitorSec": 0.1},
|
||||||
|
{"UpDelaySec": 0.2},
|
||||||
|
{"DownDelaySec": 0.3},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, devs)
|
||||||
|
|
||||||
|
def test_bond_no_interface(self):
|
||||||
|
self._update_context({"net4_interface": None})
|
||||||
|
self.assertRaises(errors.AnsibleFilterError,
|
||||||
|
networkd.networkd_netdevs, self.context, ["net4"])
|
||||||
|
|
||||||
|
def test_veth(self):
|
||||||
|
self._update_context({"external_net_names": ["net3"]})
|
||||||
|
devs = networkd.networkd_netdevs(self.context, ["net3"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-br0": [
|
||||||
|
{
|
||||||
|
"NetDev": [
|
||||||
|
{"Name": "br0"},
|
||||||
|
{"Kind": "bridge"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-p-br0-phy": [
|
||||||
|
{
|
||||||
|
"NetDev": [
|
||||||
|
{"Name": "p-br0-phy"},
|
||||||
|
{"Kind": "veth"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Peer": [
|
||||||
|
{"Name": "p-br0-ovs"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, devs)
|
||||||
|
|
||||||
|
def test_veth_with_mtu(self):
|
||||||
|
self._update_context({"external_net_names": ["net3"],
|
||||||
|
"net3_mtu": 1400})
|
||||||
|
devs = networkd.networkd_netdevs(self.context, ["net3"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-br0": [
|
||||||
|
{
|
||||||
|
"NetDev": [
|
||||||
|
{"Name": "br0"},
|
||||||
|
{"Kind": "bridge"},
|
||||||
|
{"MTUBytes": 1400},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-p-br0-phy": [
|
||||||
|
{
|
||||||
|
"NetDev": [
|
||||||
|
{"Name": "p-br0-phy"},
|
||||||
|
{"Kind": "veth"},
|
||||||
|
{"MTUBytes": 1400},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Peer": [
|
||||||
|
{"Name": "p-br0-ovs"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, devs)
|
||||||
|
|
||||||
|
def test_veth_no_interface(self):
|
||||||
|
self._update_context({"external_net_names": ["net3"],
|
||||||
|
"net3_interface": None})
|
||||||
|
self.assertRaises(errors.AnsibleFilterError,
|
||||||
|
networkd.networkd_netdevs, self.context, ["net3"])
|
||||||
|
|
||||||
|
|
||||||
|
class TestNetworkdLinks(BaseNetworkdTest):
|
||||||
|
|
||||||
|
def test_empty(self):
|
||||||
|
links = networkd.networkd_links(self.context, ['net1'])
|
||||||
|
self.assertEqual({}, links)
|
||||||
|
|
||||||
|
|
||||||
|
class TestNetworkdNetworks(BaseNetworkdTest):
|
||||||
|
|
||||||
|
def test_empty(self):
|
||||||
|
nets = networkd.networkd_networks(self.context, [])
|
||||||
|
self.assertEqual({}, nets)
|
||||||
|
|
||||||
|
def test_eth(self):
|
||||||
|
nets = networkd.networkd_networks(self.context, ["net1"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-eth0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth0"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"Address": "1.2.3.4/24"},
|
||||||
|
{"Broadcast": "true"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, nets)
|
||||||
|
|
||||||
|
def test_eth_all_options(self):
|
||||||
|
self._update_context({
|
||||||
|
"net1_gateway": "1.2.3.1",
|
||||||
|
"net1_mtu": 1400,
|
||||||
|
"net1_routes": [
|
||||||
|
{
|
||||||
|
"cidr": "1.2.4.0/24",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cidr": "1.2.5.0/24",
|
||||||
|
"gateway": "1.2.5.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cidr": "1.2.6.0/24",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"net1_bootproto": "dhcp",
|
||||||
|
"net1_defroute": 'no',
|
||||||
|
})
|
||||||
|
nets = networkd.networkd_networks(self.context, ["net1"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-eth0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth0"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"Address": "1.2.3.4/24"},
|
||||||
|
{"Broadcast": "true"},
|
||||||
|
{"Gateway": "1.2.3.1"},
|
||||||
|
{"DHCP": "yes"},
|
||||||
|
{'UseGateway': "false"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Link": [
|
||||||
|
{"MTUBytes": 1400},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Route": [
|
||||||
|
{"Destination": "1.2.4.0/24"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Route": [
|
||||||
|
{"Destination": "1.2.5.0/24"},
|
||||||
|
{"Gateway": "1.2.5.1"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Route": [
|
||||||
|
{"Destination": "1.2.6.0/24"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, nets)
|
||||||
|
|
||||||
|
def test_eth_no_interface(self):
|
||||||
|
self._update_context({"net1_interface": None})
|
||||||
|
self.assertRaises(errors.AnsibleFilterError,
|
||||||
|
networkd.networkd_networks, self.context, ["net1"])
|
||||||
|
|
||||||
|
def test_vlan(self):
|
||||||
|
nets = networkd.networkd_networks(self.context, ["net2"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-eth0.2": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth0.2"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, nets)
|
||||||
|
|
||||||
|
def test_vlan_with_parent(self):
|
||||||
|
nets = networkd.networkd_networks(self.context, ["net1", "net2"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-eth0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth0"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"Address": "1.2.3.4/24"},
|
||||||
|
{"Broadcast": "true"},
|
||||||
|
{"VLAN": "eth0.2"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-eth0.2": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth0.2"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, nets)
|
||||||
|
|
||||||
|
def test_vlan_no_interface(self):
|
||||||
|
self._update_context({"net2_interface": None})
|
||||||
|
self.assertRaises(errors.AnsibleFilterError,
|
||||||
|
networkd.networkd_networks, self.context, ["net2"])
|
||||||
|
|
||||||
|
def test_bridge(self):
|
||||||
|
nets = networkd.networkd_networks(self.context, ["net3"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-br0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "br0"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-eth0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth0"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"Bridge": "br0"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-eth1": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth1"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"Bridge": "br0"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, nets)
|
||||||
|
|
||||||
|
def test_bridge_with_bridge_port_net(self):
|
||||||
|
# Test the case where a bridge port interface is a Kayobe network
|
||||||
|
# (here, eth0 is net1).
|
||||||
|
self._update_context({
|
||||||
|
"net1_mtu": 1400,
|
||||||
|
"net1_ips": None,
|
||||||
|
})
|
||||||
|
nets = networkd.networkd_networks(self.context, ["net1", "net3"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-br0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "br0"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-eth0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth0"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"Bridge": "br0"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Link": [
|
||||||
|
{"MTUBytes": 1400},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-eth1": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth1"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"Bridge": "br0"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, nets)
|
||||||
|
|
||||||
|
def test_bridge_no_interface(self):
|
||||||
|
self._update_context({"net3_interface": None})
|
||||||
|
self.assertRaises(errors.AnsibleFilterError,
|
||||||
|
networkd.networkd_networks, self.context, ["net3"])
|
||||||
|
|
||||||
|
def test_bond(self):
|
||||||
|
nets = networkd.networkd_networks(self.context, ["net4"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-bond0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "bond0"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-eth0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth0"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"Bond": "bond0"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-eth1": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth1"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"Bond": "bond0"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, nets)
|
||||||
|
|
||||||
|
def test_bond_with_bond_member_net(self):
|
||||||
|
# Test the case where a bond member interface is a Kayobe network
|
||||||
|
# (here, eth0 is net1).
|
||||||
|
self._update_context({
|
||||||
|
"net1_mtu": 1400,
|
||||||
|
"net1_ips": None,
|
||||||
|
})
|
||||||
|
nets = networkd.networkd_networks(self.context, ["net1", "net4"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-bond0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "bond0"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-eth0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth0"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"Bond": "bond0"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Link": [
|
||||||
|
{"MTUBytes": 1400},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-eth1": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth1"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"Bond": "bond0"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, nets)
|
||||||
|
|
||||||
|
def test_bond_no_interface(self):
|
||||||
|
self._update_context({"net4_interface": None})
|
||||||
|
self.assertRaises(errors.AnsibleFilterError,
|
||||||
|
networkd.networkd_networks, self.context, ["net4"])
|
||||||
|
|
||||||
|
def test_veth(self):
|
||||||
|
self._update_context({"external_net_names": ["net3"],
|
||||||
|
"net3_bridge_ports": []})
|
||||||
|
nets = networkd.networkd_networks(self.context, ["net3"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-br0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "br0"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-p-br0-phy": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "p-br0-phy"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"Bridge": "br0"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-p-br0-ovs": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "p-br0-ovs"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"ConfigureWithoutCarrier": "true"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, nets)
|
||||||
|
|
||||||
|
def test_veth_on_vlan(self):
|
||||||
|
# Test the case where a VLAN interface is one of the networks that
|
||||||
|
# needs patching to OVS. The parent interface is a bridge, and the veth
|
||||||
|
# pair should be plugged into it.
|
||||||
|
self._update_context({
|
||||||
|
"provision_wl_net_name": "net5",
|
||||||
|
"net3_bridge_ports": [],
|
||||||
|
"net5_interface": "br0.42",
|
||||||
|
"net5_vlan": 42})
|
||||||
|
nets = networkd.networkd_networks(self.context, ["net3", "net5"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-br0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "br0"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"VLAN": "br0.42"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"50-kayobe-br0.42": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "br0.42"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-p-br0-phy": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "p-br0-phy"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"Bridge": "br0"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-p-br0-ovs": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "p-br0-ovs"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"ConfigureWithoutCarrier": "true"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, nets)
|
||||||
|
|
||||||
|
def test_veth_no_interface(self):
|
||||||
|
self._update_context({"external_net_names": ["net3"],
|
||||||
|
"net3_interface": None})
|
||||||
|
self.assertRaises(errors.AnsibleFilterError,
|
||||||
|
networkd.networkd_networks, self.context, ["net3"])
|
||||||
|
|
||||||
|
def test_no_veth_without_bridge(self):
|
||||||
|
self._update_context({"external_net_names": ["net1"]})
|
||||||
|
nets = networkd.networkd_networks(self.context, ["net1"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-eth0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth0"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"Address": "1.2.3.4/24"},
|
||||||
|
{"Broadcast": "true"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, nets)
|
||||||
|
|
||||||
|
def test_no_veth_on_vlan_without_bridge(self):
|
||||||
|
# Test the case where a VLAN interface is one of the networks that
|
||||||
|
# needs patching to OVS. The parent interface is a bridge, and the veth
|
||||||
|
# pair should be plugged into it.
|
||||||
|
self._update_context({"provision_wl_net": "net2"})
|
||||||
|
nets = networkd.networkd_networks(self.context, ["net1", "net2"])
|
||||||
|
expected = {
|
||||||
|
"50-kayobe-eth0": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth0"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Network": [
|
||||||
|
{"Address": "1.2.3.4/24"},
|
||||||
|
{"Broadcast": "true"},
|
||||||
|
{"VLAN": "eth0.2"},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"50-kayobe-eth0.2": [
|
||||||
|
{
|
||||||
|
"Match": [
|
||||||
|
{"Name": "eth0.2"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
self.assertEqual(expected, nets)
|
@ -16,11 +16,8 @@ controller_extra_network_interfaces:
|
|||||||
- test_net_eth_vlan
|
- test_net_eth_vlan
|
||||||
- test_net_bridge
|
- test_net_bridge
|
||||||
- test_net_bridge_vlan
|
- test_net_bridge_vlan
|
||||||
{# Bond configuration does not seem to work with dummy interfaces on Ubuntu #}
|
|
||||||
{% if ansible_os_family != 'Debian' %}
|
|
||||||
- test_net_bond
|
- test_net_bond
|
||||||
- test_net_bond_vlan
|
- test_net_bond_vlan
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
# dummy2: Ethernet interface.
|
# dummy2: Ethernet interface.
|
||||||
test_net_eth_cidr: 192.168.34.0/24
|
test_net_eth_cidr: 192.168.34.0/24
|
||||||
@ -44,7 +41,6 @@ test_net_bridge_vlan_cidr: 192.168.37.0/24
|
|||||||
test_net_bridge_vlan_interface: "{% raw %}{{ test_net_bridge_interface }}.{{ test_net_bridge_vlan_vlan }}{% endraw %}"
|
test_net_bridge_vlan_interface: "{% raw %}{{ test_net_bridge_interface }}.{{ test_net_bridge_vlan_vlan }}{% endraw %}"
|
||||||
test_net_bridge_vlan_vlan: 43
|
test_net_bridge_vlan_vlan: 43
|
||||||
|
|
||||||
{% if ansible_os_family != 'Debian' %}
|
|
||||||
# bond0: bond with slaves dummy5, dummy6.
|
# bond0: bond with slaves dummy5, dummy6.
|
||||||
test_net_bond_cidr: 192.168.38.0/24
|
test_net_bond_cidr: 192.168.38.0/24
|
||||||
test_net_bond_interface: bond0
|
test_net_bond_interface: bond0
|
||||||
@ -54,7 +50,6 @@ test_net_bond_bond_slaves: [dummy5, dummy6]
|
|||||||
test_net_bond_vlan_cidr: 192.168.39.0/24
|
test_net_bond_vlan_cidr: 192.168.39.0/24
|
||||||
test_net_bond_vlan_interface: "{% raw %}{{ test_net_bond_interface }}.{{ test_net_bond_vlan_vlan }}{% endraw %}"
|
test_net_bond_vlan_interface: "{% raw %}{{ test_net_bond_interface }}.{{ test_net_bond_vlan_vlan }}{% endraw %}"
|
||||||
test_net_bond_vlan_vlan: 44
|
test_net_bond_vlan_vlan: 44
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
# Define a software RAID device consisting of two loopback devices.
|
# Define a software RAID device consisting of two loopback devices.
|
||||||
controller_mdadm_arrays:
|
controller_mdadm_arrays:
|
||||||
|
@ -15,13 +15,6 @@ def _is_dnf():
|
|||||||
return info[0] == 'CentOS Linux' and info[1].startswith('8')
|
return info[0] == 'CentOS Linux' and info[1].startswith('8')
|
||||||
|
|
||||||
|
|
||||||
def _supports_bonds():
|
|
||||||
# Bond configuration does not currently work on Ubuntu when using dummy
|
|
||||||
# devices as slaves.
|
|
||||||
info = distro.linux_distribution()
|
|
||||||
return info[0] != 'Ubuntu'
|
|
||||||
|
|
||||||
|
|
||||||
def test_network_ethernet(host):
|
def test_network_ethernet(host):
|
||||||
interface = host.interface('dummy2')
|
interface = host.interface('dummy2')
|
||||||
assert interface.exists
|
assert interface.exists
|
||||||
@ -59,21 +52,21 @@ def test_network_bridge_vlan(host):
|
|||||||
assert host.file('/sys/class/net/br0.43/lower_br0').exists
|
assert host.file('/sys/class/net/br0.43/lower_br0').exists
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not _supports_bonds(), reason="Bonding no worky on Ubuntu")
|
|
||||||
def test_network_bond(host):
|
def test_network_bond(host):
|
||||||
interface = host.interface('bond0')
|
interface = host.interface('bond0')
|
||||||
assert interface.exists
|
assert interface.exists
|
||||||
assert '192.168.38.1' in interface.addresses
|
assert '192.168.38.1' in interface.addresses
|
||||||
sys_slaves = host.check_output('cat /sys/class/net/bond0/bonding/slaves')
|
sys_slaves = host.check_output('cat /sys/class/net/bond0/bonding/slaves')
|
||||||
slaves = ['dummy5', 'dummy6']
|
# Ordering is not guaranteed, so compare sets.
|
||||||
assert sys_slaves == " ".join(slaves)
|
sys_slaves = set(sys_slaves.split())
|
||||||
|
slaves = set(['dummy5', 'dummy6'])
|
||||||
|
assert sys_slaves == slaves
|
||||||
for slave in slaves:
|
for slave in slaves:
|
||||||
interface = host.interface(slave)
|
interface = host.interface(slave)
|
||||||
assert interface.exists
|
assert interface.exists
|
||||||
assert not interface.addresses
|
assert not interface.addresses
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not _supports_bonds(), reason="Bonding no worky on Ubuntu")
|
|
||||||
def test_network_bond_vlan(host):
|
def test_network_bond_vlan(host):
|
||||||
interface = host.interface('bond0.44')
|
interface = host.interface('bond0.44')
|
||||||
assert interface.exists
|
assert interface.exists
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
---
|
---
|
||||||
- src: ahuffman.resolv
|
- src: ahuffman.resolv
|
||||||
version: 1.3.1
|
version: 1.3.1
|
||||||
|
- src: stackhpc.systemd_networkd
|
||||||
|
version: v1.0.1
|
||||||
- src: jriguera.configdrive
|
- src: jriguera.configdrive
|
||||||
# There are no versioned releases of this role.
|
# There are no versioned releases of this role.
|
||||||
version: 8438592c84585c86e62ae07e526d3da53629b377
|
version: 8438592c84585c86e62ae07e526d3da53629b377
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Ensure interfaces.d directory exists
|
|
||||||
file:
|
|
||||||
path: /etc/network/interfaces.d
|
|
||||||
state: directory
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Ensure interfaces.d directory is sourced
|
|
||||||
lineinfile:
|
|
||||||
path: /etc/network/interfaces
|
|
||||||
line: source /etc/network/interfaces.d/*
|
|
||||||
become: true
|
|
||||||
|
|
||||||
- name: Ensure all-in-one network dummy interface exists
|
|
||||||
become: true
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
auto {{ bridge_port_interface }}
|
|
||||||
iface {{ bridge_port_interface }} inet manual
|
|
||||||
dest: /etc/network/interfaces.d/ifcfg-{{ bridge_port_interface }}
|
|
||||||
|
|
||||||
- name: Ensure all-in-one network bridge interface exists
|
|
||||||
become: true
|
|
||||||
copy:
|
|
||||||
content: |
|
|
||||||
auto {{ bridge_interface }}
|
|
||||||
iface {{ bridge_interface }} inet static
|
|
||||||
address {{ bridge_ip }}
|
|
||||||
netmask {{ (bridge_ip ~ '/' ~ bridge_prefix) | ipaddr('netmask') }}
|
|
||||||
bridge_ports {{ bridge_port_interface }}
|
|
||||||
dest: /etc/network/interfaces.d/ifcfg-{{ bridge_interface }}
|
|
||||||
|
|
||||||
- name: Ensure all-in-one network bridge interfaces are up
|
|
||||||
become: true
|
|
||||||
command: "{{ item }}"
|
|
||||||
with_items:
|
|
||||||
- "ifup {{ bridge_interface }}"
|
|
||||||
- "ifup {{ bridge_port_interface }}"
|
|
@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
- name: Ensure all-in-one network bridge interface exists (RedHat)
|
|
||||||
command: "{{ item }}"
|
|
||||||
become: true
|
|
||||||
with_items:
|
|
||||||
- "ip l set {{ bridge_interface }} up"
|
|
||||||
- "ip a add {{ bridge_ip }}/{{ bridge_prefix }} dev {{ bridge_interface }}"
|
|
||||||
# NOTE(mgoddard): CentOS 8 removes interfaces from their bridge during
|
|
||||||
# ifdown, and removes the bridge if there are no interfaces left. When
|
|
||||||
# Kayobe bounces veth links plugged into the bridge, it causes the
|
|
||||||
# bridge which has the IP we are using for SSH to be removed. Use a
|
|
||||||
# dummy interface.
|
|
||||||
- "ip l set {{ bridge_port_interface }} up"
|
|
||||||
- "ip l set {{ bridge_port_interface }} master {{ bridge_interface }}"
|
|
@ -6,4 +6,19 @@
|
|||||||
- "ip l add {{ bridge_interface }} type bridge"
|
- "ip l add {{ bridge_interface }} type bridge"
|
||||||
- "ip l add {{ bridge_port_interface }} type dummy"
|
- "ip l add {{ bridge_port_interface }} type dummy"
|
||||||
|
|
||||||
- include_tasks: "{{ ansible_os_family }}.yml"
|
- name: Ensure all-in-one network bridge interface exists
|
||||||
|
vars:
|
||||||
|
bridge_cidr: "{{ bridge_ip }}/{{ bridge_prefix }}"
|
||||||
|
bridge_broadcast: "{{ bridge_cidr | ipaddr('broadcast') }}"
|
||||||
|
command: "{{ item }}"
|
||||||
|
become: true
|
||||||
|
with_items:
|
||||||
|
- "ip l set {{ bridge_interface }} up"
|
||||||
|
- "ip a add {{ bridge_cidr }} brd {{ bridge_broadcast }} dev {{ bridge_interface }}"
|
||||||
|
# NOTE(mgoddard): CentOS 8 removes interfaces from their bridge during
|
||||||
|
# ifdown, and removes the bridge if there are no interfaces left. When
|
||||||
|
# Kayobe bounces veth links plugged into the bridge, it causes the
|
||||||
|
# bridge which has the IP we are using for SSH to be removed. Use a
|
||||||
|
# dummy interface.
|
||||||
|
- "ip l set {{ bridge_port_interface }} up"
|
||||||
|
- "ip l set {{ bridge_port_interface }} master {{ bridge_interface }}"
|
||||||
|
Loading…
Reference in New Issue
Block a user