Merge "Ubuntu: define implied VLAN parent interfaces in networkd"
This commit is contained in:
commit
c98cd6c520
@ -284,7 +284,6 @@ def _network(context, name, inventory_hostname, bridge, bond, vlan_interfaces):
|
||||
{
|
||||
'Network': [
|
||||
{'Address': ip},
|
||||
{'Broadcast': 'true' if ip else None},
|
||||
{'Gateway': gateway},
|
||||
{'DHCP': ('yes' if bootproto and bootproto.lower() == 'dhcp'
|
||||
else None)},
|
||||
@ -317,6 +316,34 @@ def _network(context, name, inventory_hostname, bridge, bond, vlan_interfaces):
|
||||
return _filter_options(config)
|
||||
|
||||
|
||||
def _vlan_parent_network(device, mtu, vlan_interfaces):
|
||||
"""Return a networkd network configuration for a VLAN parent interface.
|
||||
|
||||
:param device: name of the interface.
|
||||
:param mtu: Interface MTU.
|
||||
:param vlan_interfaces: List of VLAN subinterfaces of the interface.
|
||||
"""
|
||||
config = [
|
||||
{
|
||||
'Match': [
|
||||
{'Name': device},
|
||||
]
|
||||
},
|
||||
{
|
||||
'Network': [
|
||||
{'VLAN': vlan_interface}
|
||||
for vlan_interface in vlan_interfaces
|
||||
]
|
||||
},
|
||||
{
|
||||
'Link': [
|
||||
{'MTUBytes': mtu},
|
||||
]
|
||||
}
|
||||
]
|
||||
return _filter_options(config)
|
||||
|
||||
|
||||
def _bridge_port_network(context, name, port, inventory_hostname,
|
||||
vlan_interfaces):
|
||||
"""Return a networkd network configuration for a bridge port.
|
||||
@ -437,6 +464,25 @@ def _veth_peer_network(context, veth, inventory_hostname):
|
||||
return _filter_options(config)
|
||||
|
||||
|
||||
def _add_to_result(result, prefix, device, config):
|
||||
"""Add configuration for an interface to a filter result.
|
||||
|
||||
:param result: the result dict.
|
||||
:param prefix: the systemd-networkd configuration file prefix.
|
||||
:param device: the interface being configured.
|
||||
:param config: the configuration to add to the result.
|
||||
:raises: AnsibleFilterError if the interface already exists in the result.
|
||||
"""
|
||||
key = "%s%s" % (prefix, device)
|
||||
# Catch the case where interface configuration is added multiple times.
|
||||
# This should not happen.
|
||||
if key in result:
|
||||
raise errors.AnsibleFilterError(
|
||||
"Programming error: duplicate interface configuration for %s"
|
||||
% device)
|
||||
result[key] = config
|
||||
|
||||
|
||||
@jinja2.contextfilter
|
||||
def networkd_netdevs(context, names, inventory_hostname=None):
|
||||
"""Return a dict representation of networkd NetDev configuration.
|
||||
@ -459,7 +505,7 @@ def networkd_netdevs(context, names, inventory_hostname=None):
|
||||
device = networks.get_and_validate_interface(context, name,
|
||||
inventory_hostname)
|
||||
netdev = _vlan_netdev(context, name, inventory_hostname)
|
||||
result["%s%s" % (prefix, device)] = netdev
|
||||
_add_to_result(result, prefix, device, netdev)
|
||||
|
||||
# Bridges.
|
||||
for name in networks.net_select_bridges(context, names,
|
||||
@ -467,21 +513,21 @@ def networkd_netdevs(context, names, inventory_hostname=None):
|
||||
device = networks.get_and_validate_interface(context, name,
|
||||
inventory_hostname)
|
||||
netdev = _bridge_netdev(context, name, inventory_hostname)
|
||||
result["%s%s" % (prefix, device)] = netdev
|
||||
_add_to_result(result, 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
|
||||
_add_to_result(result, 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
|
||||
_add_to_result(result, prefix, device, netdev)
|
||||
|
||||
return result
|
||||
|
||||
@ -551,9 +597,10 @@ def networkd_networks(context, names, inventory_hostname=None):
|
||||
device = networks.get_and_validate_interface(context, name,
|
||||
inventory_hostname)
|
||||
vlan = networks.net_vlan(context, name, inventory_hostname)
|
||||
mtu = networks.net_mtu(context, name, inventory_hostname)
|
||||
parent = networks.get_vlan_parent(device, vlan)
|
||||
vlan_interfaces = interface_to_vlans.setdefault(parent, [])
|
||||
vlan_interfaces.append(device)
|
||||
vlan_interfaces.append({"device": device, "mtu": mtu})
|
||||
|
||||
# Prefix for configuration file names.
|
||||
prefix = utils.get_hostvar(context, "networkd_prefix", inventory_hostname)
|
||||
@ -568,8 +615,22 @@ def networkd_networks(context, names, inventory_hostname=None):
|
||||
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
|
||||
[vlan["device"] for vlan in vlan_interfaces])
|
||||
_add_to_result(result, prefix, device, net)
|
||||
|
||||
# VLAN parent interfaces that are not in configured networks, bridge ports
|
||||
# or bond members.
|
||||
implied_vlan_parents = (set(interface_to_vlans) -
|
||||
set(interfaces) -
|
||||
set(bridge_port_to_bridge) -
|
||||
set(bond_member_to_bond))
|
||||
for device in implied_vlan_parents:
|
||||
vlan_interfaces = interface_to_vlans[device]
|
||||
mtu = max([vlan["mtu"] for vlan in vlan_interfaces])
|
||||
net = _vlan_parent_network(device, mtu,
|
||||
[vlan["device"]
|
||||
for vlan in vlan_interfaces])
|
||||
_add_to_result(result, prefix, device, net)
|
||||
|
||||
# Bridge ports that are not in configured networks.
|
||||
for name in networks.net_select_bridges(context, names,
|
||||
@ -580,9 +641,10 @@ def networkd_networks(context, names, inventory_hostname=None):
|
||||
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
|
||||
net = _bridge_port_network(context, name, port, inventory_hostname,
|
||||
[vlan["device"]
|
||||
for vlan in vlan_interfaces])
|
||||
_add_to_result(result, prefix, port, net)
|
||||
|
||||
# Bond members that are not in configured networks.
|
||||
for name in networks.net_select_bonds(context, names, inventory_hostname):
|
||||
@ -592,20 +654,22 @@ def networkd_networks(context, names, inventory_hostname=None):
|
||||
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
|
||||
net = _bond_member_network(context, name, member,
|
||||
inventory_hostname,
|
||||
[vlan["device"]
|
||||
for vlan in vlan_interfaces])
|
||||
_add_to_result(result, prefix, member, net)
|
||||
|
||||
# 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
|
||||
_add_to_result(result, prefix, device, net)
|
||||
|
||||
net = _veth_peer_network(context, veth, inventory_hostname)
|
||||
device = veth['peer']
|
||||
result["%s%s" % (prefix, device)] = net
|
||||
_add_to_result(result, prefix, device, net)
|
||||
|
||||
return result
|
||||
|
||||
|
@ -298,7 +298,6 @@ class TestNetworkdNetworks(BaseNetworkdTest):
|
||||
{
|
||||
"Network": [
|
||||
{"Address": "1.2.3.4/24"},
|
||||
{"Broadcast": "true"},
|
||||
]
|
||||
},
|
||||
]
|
||||
@ -347,7 +346,6 @@ class TestNetworkdNetworks(BaseNetworkdTest):
|
||||
{
|
||||
"Network": [
|
||||
{"Address": "1.2.3.4/24"},
|
||||
{"Broadcast": "true"},
|
||||
{"Gateway": "1.2.3.1"},
|
||||
{"DHCP": "yes"},
|
||||
{'UseGateway': "false"},
|
||||
@ -400,6 +398,18 @@ class TestNetworkdNetworks(BaseNetworkdTest):
|
||||
def test_vlan(self):
|
||||
nets = networkd.networkd_networks(self.context, ["net2"])
|
||||
expected = {
|
||||
"50-kayobe-eth0": [
|
||||
{
|
||||
"Match": [
|
||||
{"Name": "eth0"}
|
||||
],
|
||||
},
|
||||
{
|
||||
"Network": [
|
||||
{"VLAN": "eth0.2"},
|
||||
]
|
||||
},
|
||||
],
|
||||
"50-kayobe-eth0.2": [
|
||||
{
|
||||
"Match": [
|
||||
@ -422,7 +432,6 @@ class TestNetworkdNetworks(BaseNetworkdTest):
|
||||
{
|
||||
"Network": [
|
||||
{"Address": "1.2.3.4/24"},
|
||||
{"Broadcast": "true"},
|
||||
{"VLAN": "eth0.2"},
|
||||
]
|
||||
},
|
||||
@ -527,6 +536,106 @@ class TestNetworkdNetworks(BaseNetworkdTest):
|
||||
}
|
||||
self.assertEqual(expected, nets)
|
||||
|
||||
def test_bridge_with_bridge_port_vlan(self):
|
||||
# Test the case where one of the bridge ports has a VLAN subinterface.
|
||||
self._update_context({
|
||||
"net2_interface": "eth1.2",
|
||||
})
|
||||
nets = networkd.networkd_networks(self.context, ["net2", "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"},
|
||||
{"VLAN": "eth1.2"}
|
||||
]
|
||||
},
|
||||
],
|
||||
"50-kayobe-eth1.2": [
|
||||
{
|
||||
"Match": [
|
||||
{"Name": "eth1.2"}
|
||||
]
|
||||
},
|
||||
],
|
||||
}
|
||||
self.assertEqual(expected, nets)
|
||||
|
||||
def test_bridge_with_bridge_port_vlan_net(self):
|
||||
# Test the case where one of the bridge ports has a VLAN subinterface,
|
||||
# and is also a Kayobe network (here eth0 is net1).
|
||||
self._update_context({
|
||||
"net1_ips": None,
|
||||
})
|
||||
nets = networkd.networkd_networks(self.context,
|
||||
["net1", "net2", "net3"])
|
||||
expected = {
|
||||
"50-kayobe-br0": [
|
||||
{
|
||||
"Match": [
|
||||
{"Name": "br0"}
|
||||
]
|
||||
},
|
||||
],
|
||||
"50-kayobe-eth0": [
|
||||
{
|
||||
"Match": [
|
||||
{"Name": "eth0"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Network": [
|
||||
{"Bridge": "br0"},
|
||||
{"VLAN": "eth0.2"}
|
||||
]
|
||||
},
|
||||
],
|
||||
"50-kayobe-eth0.2": [
|
||||
{
|
||||
"Match": [
|
||||
{"Name": "eth0.2"}
|
||||
]
|
||||
},
|
||||
],
|
||||
"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,
|
||||
@ -617,6 +726,106 @@ class TestNetworkdNetworks(BaseNetworkdTest):
|
||||
}
|
||||
self.assertEqual(expected, nets)
|
||||
|
||||
def test_bond_with_bond_member_vlan(self):
|
||||
# Test the case where one of the bond members has a VLAN subinterface.
|
||||
self._update_context({
|
||||
"net2_interface": "eth1.2",
|
||||
})
|
||||
nets = networkd.networkd_networks(self.context, ["net2", "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"},
|
||||
{"VLAN": "eth1.2"},
|
||||
]
|
||||
},
|
||||
],
|
||||
"50-kayobe-eth1.2": [
|
||||
{
|
||||
"Match": [
|
||||
{"Name": "eth1.2"}
|
||||
]
|
||||
},
|
||||
],
|
||||
}
|
||||
self.assertEqual(expected, nets)
|
||||
|
||||
def test_bond_with_bond_member_vlan_net(self):
|
||||
# Test the case where one of the bond members has a VLAN subinterface,
|
||||
# and is also a Kayobe network (here eth0 is net1).
|
||||
self._update_context({
|
||||
"net1_ips": None,
|
||||
})
|
||||
nets = networkd.networkd_networks(self.context,
|
||||
["net1", "net2", "net4"])
|
||||
expected = {
|
||||
"50-kayobe-bond0": [
|
||||
{
|
||||
"Match": [
|
||||
{"Name": "bond0"}
|
||||
]
|
||||
},
|
||||
],
|
||||
"50-kayobe-eth0": [
|
||||
{
|
||||
"Match": [
|
||||
{"Name": "eth0"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Network": [
|
||||
{"Bond": "bond0"},
|
||||
{"VLAN": "eth0.2"},
|
||||
]
|
||||
},
|
||||
],
|
||||
"50-kayobe-eth0.2": [
|
||||
{
|
||||
"Match": [
|
||||
{"Name": "eth0.2"}
|
||||
]
|
||||
},
|
||||
],
|
||||
"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,
|
||||
@ -737,7 +946,6 @@ class TestNetworkdNetworks(BaseNetworkdTest):
|
||||
{
|
||||
"Network": [
|
||||
{"Address": "1.2.3.4/24"},
|
||||
{"Broadcast": "true"},
|
||||
]
|
||||
},
|
||||
]
|
||||
@ -760,7 +968,6 @@ class TestNetworkdNetworks(BaseNetworkdTest):
|
||||
{
|
||||
"Network": [
|
||||
{"Address": "1.2.3.4/24"},
|
||||
{"Broadcast": "true"},
|
||||
{"VLAN": "eth0.2"},
|
||||
]
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user