summaryrefslogtreecommitdiff
path: root/tests/unittests/test_net.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unittests/test_net.py')
-rw-r--r--tests/unittests/test_net.py1262
1 files changed, 1225 insertions, 37 deletions
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 167ed01e..e49abcc4 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -1,6 +1,7 @@
# This file is part of cloud-init. See LICENSE file for license information.
from cloudinit import net
+from cloudinit.net import _natural_sort_key
from cloudinit.net import cmdline
from cloudinit.net import eni
from cloudinit.net import netplan
@@ -149,20 +150,19 @@ ONBOOT=yes
TYPE=Ethernet
USERCTL=no
""".lstrip()),
- ('etc/sysconfig/network-scripts/route-eth0',
- """
-# Created by cloud-init on instance boot automatically, do not edit.
-#
-ADDRESS0=0.0.0.0
-GATEWAY0=172.19.3.254
-NETMASK0=0.0.0.0
-""".lstrip()),
('etc/resolv.conf',
"""
; Created by cloud-init on instance boot automatically, do not edit.
;
nameserver 172.19.0.12
""".lstrip()),
+ ('etc/NetworkManager/conf.d/99-cloud-init.conf',
+ """
+# Created by cloud-init on instance boot automatically, do not edit.
+#
+[main]
+dns = none
+""".lstrip()),
('etc/udev/rules.d/70-persistent-net.rules',
"".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',
'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))]
@@ -224,6 +224,13 @@ USERCTL=no
;
nameserver 172.19.0.12
""".lstrip()),
+ ('etc/NetworkManager/conf.d/99-cloud-init.conf',
+ """
+# Created by cloud-init on instance boot automatically, do not edit.
+#
+[main]
+dns = none
+""".lstrip()),
('etc/udev/rules.d/70-persistent-net.rules',
"".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',
'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))]
@@ -291,7 +298,7 @@ DEVICE=eth0
GATEWAY=172.19.3.254
HWADDR=fa:16:3e:ed:9a:59
IPADDR=172.19.1.34
-IPV6ADDR=2001:DB8::10
+IPV6ADDR=2001:DB8::10/64
IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64"
IPV6INIT=yes
IPV6_DEFAULTGW=2001:DB8::1
@@ -307,6 +314,13 @@ USERCTL=no
;
nameserver 172.19.0.12
""".lstrip()),
+ ('etc/NetworkManager/conf.d/99-cloud-init.conf',
+ """
+# Created by cloud-init on instance boot automatically, do not edit.
+#
+[main]
+dns = none
+""".lstrip()),
('etc/udev/rules.d/70-persistent-net.rules',
"".join(['SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ',
'ATTR{address}=="fa:16:3e:ed:9a:59", NAME="eth0"\n']))]
@@ -341,17 +355,15 @@ iface lo inet loopback
auto eth0
iface eth0 inet static
- address 1.2.3.12
+ address 1.2.3.12/29
broadcast 1.2.3.15
dns-nameservers 69.9.160.191 69.9.191.4
gateway 1.2.3.9
- netmask 255.255.255.248
auto eth1
iface eth1 inet static
- address 10.248.2.4
+ address 10.248.2.4/29
broadcast 10.248.2.7
- netmask 255.255.255.248
""".lstrip()
NETWORK_CONFIGS = {
@@ -410,6 +422,28 @@ NETWORK_CONFIGS = {
via: 65.61.151.37
set-name: eth99
""").rstrip(' '),
+ 'expected_sysconfig': {
+ 'ifcfg-eth1': textwrap.dedent("""\
+ BOOTPROTO=none
+ DEVICE=eth1
+ HWADDR=cf:d6:af:48:e8:80
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no"""),
+ 'ifcfg-eth99': textwrap.dedent("""\
+ BOOTPROTO=dhcp
+ DEFROUTE=yes
+ DEVICE=eth99
+ GATEWAY=65.61.151.37
+ HWADDR=c0:d6:9f:2c:e8:80
+ IPADDR=192.168.21.3
+ NETMASK=255.255.255.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no"""),
+ },
'yaml': textwrap.dedent("""
version: 1
config:
@@ -470,6 +504,62 @@ NETWORK_CONFIGS = {
- {'type': 'dhcp6'}
""").rstrip(' '),
},
+ 'v4_and_v6_static': {
+ 'expected_eni': textwrap.dedent("""\
+ auto lo
+ iface lo inet loopback
+
+ auto iface0
+ iface iface0 inet static
+ address 192.168.14.2/24
+ mtu 9000
+
+ # control-alias iface0
+ iface iface0 inet6 static
+ address 2001:1::1/64
+ mtu 1500
+ """).rstrip(' '),
+ 'expected_netplan': textwrap.dedent("""
+ network:
+ version: 2
+ ethernets:
+ iface0:
+ addresses:
+ - 192.168.14.2/24
+ - 2001:1::1/64
+ mtu: 9000
+ mtu6: 1500
+ """).rstrip(' '),
+ 'yaml': textwrap.dedent("""\
+ version: 1
+ config:
+ - type: 'physical'
+ name: 'iface0'
+ subnets:
+ - type: static
+ address: 192.168.14.2/24
+ mtu: 9000
+ - type: static
+ address: 2001:1::1/64
+ mtu: 1500
+ """).rstrip(' '),
+ 'expected_sysconfig': {
+ 'ifcfg-iface0': textwrap.dedent("""\
+ BOOTPROTO=none
+ DEVICE=iface0
+ IPADDR=192.168.14.2
+ IPV6ADDR=2001:1::1/64
+ IPV6INIT=yes
+ NETMASK=255.255.255.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no
+ MTU=9000
+ IPV6_MTU=1500
+ """),
+ },
+ },
'all': {
'expected_eni': ("""\
auto lo
@@ -511,12 +601,26 @@ iface bond0 inet6 dhcp
auto br0
iface br0 inet static
address 192.168.14.2/24
+ bridge_ageing 250
+ bridge_bridgeprio 22
+ bridge_fd 1
+ bridge_gcint 2
+ bridge_hello 1
+ bridge_maxage 10
+ bridge_pathcost eth3 50
+ bridge_pathcost eth4 75
+ bridge_portprio eth3 28
+ bridge_portprio eth4 14
bridge_ports eth3 eth4
bridge_stp off
+ bridge_waitport 1 eth3
+ bridge_waitport 2 eth4
# control-alias br0
iface br0 inet6 static
address 2001:1::1/64
+ post-up route add -A inet6 default gw 2001:4800:78ff:1b::1 || true
+ pre-down route del -A inet6 default gw 2001:4800:78ff:1b::1 || true
auto bond0.200
iface bond0.200 inet dhcp
@@ -642,6 +746,18 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true
interfaces:
- eth3
- eth4
+ parameters:
+ ageing-time: 250
+ forward-delay: 1
+ hello-time: 1
+ max-age: 10
+ path-cost:
+ eth3: 50
+ eth4: 75
+ priority: 22
+ routes:
+ - to: ::/0
+ via: 2001:4800:78ff:1b::1
vlans:
bond0.200:
dhcp4: true
@@ -664,6 +780,119 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true
- sacchromyces.maas
- brettanomyces.maas
""").rstrip(' '),
+ 'expected_sysconfig': {
+ 'ifcfg-bond0': textwrap.dedent("""\
+ BONDING_MASTER=yes
+ BONDING_OPTS="mode=active-backup """
+ """xmit_hash_policy=layer3+4 """
+ """miimon=100"
+ BONDING_SLAVE0=eth1
+ BONDING_SLAVE1=eth2
+ BOOTPROTO=dhcp
+ DEVICE=bond0
+ DHCPV6C=yes
+ IPV6INIT=yes
+ MACADDR=aa:bb:cc:dd:ee:ff
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Bond
+ USERCTL=no"""),
+ 'ifcfg-bond0.200': textwrap.dedent("""\
+ BOOTPROTO=dhcp
+ DEVICE=bond0.200
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ PHYSDEV=bond0
+ TYPE=Ethernet
+ USERCTL=no
+ VLAN=yes"""),
+ 'ifcfg-br0': textwrap.dedent("""\
+ AGEING=250
+ BOOTPROTO=none
+ DEFROUTE=yes
+ DEVICE=br0
+ IPADDR=192.168.14.2
+ IPV6ADDR=2001:1::1/64
+ IPV6INIT=yes
+ IPV6_DEFAULTGW=2001:4800:78ff:1b::1
+ NETMASK=255.255.255.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ PRIO=22
+ STP=off
+ TYPE=Bridge
+ USERCTL=no"""),
+ 'ifcfg-eth0': textwrap.dedent("""\
+ BOOTPROTO=none
+ DEVICE=eth0
+ HWADDR=c0:d6:9f:2c:e8:80
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no"""),
+ 'ifcfg-eth0.101': textwrap.dedent("""\
+ BOOTPROTO=none
+ DEFROUTE=yes
+ DEVICE=eth0.101
+ GATEWAY=192.168.0.1
+ IPADDR=192.168.0.2
+ IPADDR1=192.168.2.10
+ MTU=1500
+ NETMASK=255.255.255.0
+ NETMASK1=255.255.255.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ PHYSDEV=eth0
+ TYPE=Ethernet
+ USERCTL=no
+ VLAN=yes"""),
+ 'ifcfg-eth1': textwrap.dedent("""\
+ BOOTPROTO=none
+ DEVICE=eth1
+ HWADDR=aa:d6:9f:2c:e8:80
+ MASTER=bond0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ SLAVE=yes
+ TYPE=Ethernet
+ USERCTL=no"""),
+ 'ifcfg-eth2': textwrap.dedent("""\
+ BOOTPROTO=none
+ DEVICE=eth2
+ HWADDR=c0:bb:9f:2c:e8:80
+ MASTER=bond0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ SLAVE=yes
+ TYPE=Ethernet
+ USERCTL=no"""),
+ 'ifcfg-eth3': textwrap.dedent("""\
+ BOOTPROTO=none
+ BRIDGE=br0
+ DEVICE=eth3
+ HWADDR=66:bb:9f:2c:e8:80
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no"""),
+ 'ifcfg-eth4': textwrap.dedent("""\
+ BOOTPROTO=none
+ BRIDGE=br0
+ DEVICE=eth4
+ HWADDR=98:bb:9f:2c:e8:80
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no"""),
+ 'ifcfg-eth5': textwrap.dedent("""\
+ BOOTPROTO=dhcp
+ DEVICE=eth5
+ HWADDR=98:bb:9f:2c:e8:8a
+ NM_CONTROLLED=no
+ ONBOOT=no
+ TYPE=Ethernet
+ USERCTL=no""")
+ },
'yaml': textwrap.dedent("""
version: 1
config:
@@ -752,14 +981,32 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true
forwarding: 1
# basically anything in /proc/sys/net/ipv6/conf/.../
params:
- bridge_stp: 'off'
- bridge_fd: 0
+ bridge_ageing: 250
+ bridge_bridgeprio: 22
+ bridge_fd: 1
+ bridge_gcint: 2
+ bridge_hello: 1
+ bridge_maxage: 10
bridge_maxwait: 0
+ bridge_pathcost:
+ - eth3 50
+ - eth4 75
+ bridge_portprio:
+ - eth3 28
+ - eth4 14
+ bridge_stp: 'off'
+ bridge_waitport:
+ - 1 eth3
+ - 2 eth4
subnets:
- type: static
address: 192.168.14.2/24
- type: static
address: 2001:1::1/64 # default to /64
+ routes:
+ - gateway: 2001:4800:78ff:1b::1
+ netmask: '::'
+ network: '::'
# A global nameserver.
- type: nameserver
address: 8.8.8.8
@@ -778,9 +1025,308 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true
gateway: 11.0.0.1
metric: 3
""").lstrip(),
- }
+ },
+ 'bond': {
+ 'yaml': textwrap.dedent("""
+ version: 1
+ config:
+ - type: physical
+ name: bond0s0
+ mac_address: "aa:bb:cc:dd:e8:00"
+ - type: physical
+ name: bond0s1
+ mac_address: "aa:bb:cc:dd:e8:01"
+ - type: bond
+ name: bond0
+ mac_address: "aa:bb:cc:dd:e8:ff"
+ bond_interfaces:
+ - bond0s0
+ - bond0s1
+ params:
+ bond-mode: active-backup
+ bond_miimon: 100
+ bond-xmit-hash-policy: "layer3+4"
+ subnets:
+ - type: static
+ address: 192.168.0.2/24
+ gateway: 192.168.0.1
+ routes:
+ - gateway: 192.168.0.3
+ netmask: 255.255.255.0
+ network: 10.1.3.0
+ - type: static
+ address: 192.168.1.2/24
+ - type: static
+ address: 2001:1::1/92
+ """),
+ 'expected_sysconfig': {
+ 'ifcfg-bond0': textwrap.dedent("""\
+ BONDING_MASTER=yes
+ BONDING_OPTS="mode=active-backup xmit_hash_policy=layer3+4 miimon=100"
+ BONDING_SLAVE0=bond0s0
+ BONDING_SLAVE1=bond0s1
+ BOOTPROTO=none
+ DEFROUTE=yes
+ DEVICE=bond0
+ GATEWAY=192.168.0.1
+ MACADDR=aa:bb:cc:dd:e8:ff
+ IPADDR=192.168.0.2
+ IPADDR1=192.168.1.2
+ IPV6ADDR=2001:1::1/92
+ IPV6INIT=yes
+ NETMASK=255.255.255.0
+ NETMASK1=255.255.255.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Bond
+ USERCTL=no
+ """),
+ 'ifcfg-bond0s0': textwrap.dedent("""\
+ BOOTPROTO=none
+ DEVICE=bond0s0
+ HWADDR=aa:bb:cc:dd:e8:00
+ MASTER=bond0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ SLAVE=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """),
+ 'route6-bond0': textwrap.dedent("""\
+ """),
+ 'route-bond0': textwrap.dedent("""\
+ ADDRESS0=10.1.3.0
+ GATEWAY0=192.168.0.3
+ NETMASK0=255.255.255.0
+ """),
+ 'ifcfg-bond0s1': textwrap.dedent("""\
+ BOOTPROTO=none
+ DEVICE=bond0s1
+ HWADDR=aa:bb:cc:dd:e8:01
+ MASTER=bond0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ SLAVE=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """),
+ },
+ },
+ 'vlan': {
+ 'yaml': textwrap.dedent("""
+ version: 1
+ config:
+ - type: physical
+ name: en0
+ mac_address: "aa:bb:cc:dd:e8:00"
+ - type: vlan
+ name: en0.99
+ vlan_link: en0
+ vlan_id: 99
+ subnets:
+ - type: static
+ address: '192.168.2.2/24'
+ - type: static
+ address: '192.168.1.2/24'
+ gateway: 192.168.1.1
+ - type: static
+ address: 2001:1::bbbb/96
+ routes:
+ - gateway: 2001:1::1
+ netmask: '::'
+ network: '::'
+ """),
+ 'expected_sysconfig': {
+ 'ifcfg-en0': textwrap.dedent("""\
+ BOOTPROTO=none
+ DEVICE=en0
+ HWADDR=aa:bb:cc:dd:e8:00
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no"""),
+ 'ifcfg-en0.99': textwrap.dedent("""\
+ BOOTPROTO=none
+ DEFROUTE=yes
+ DEVICE=en0.99
+ GATEWAY=192.168.1.1
+ IPADDR=192.168.2.2
+ IPADDR1=192.168.1.2
+ IPV6ADDR=2001:1::bbbb/96
+ IPV6INIT=yes
+ IPV6_DEFAULTGW=2001:1::1
+ NETMASK=255.255.255.0
+ NETMASK1=255.255.255.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ PHYSDEV=en0
+ TYPE=Ethernet
+ USERCTL=no
+ VLAN=yes"""),
+ },
+ },
+ 'bridge': {
+ 'yaml': textwrap.dedent("""
+ version: 1
+ config:
+ - type: physical
+ name: eth0
+ mac_address: "52:54:00:12:34:00"
+ subnets:
+ - type: static
+ address: 2001:1::100/96
+ - type: physical
+ name: eth1
+ mac_address: "52:54:00:12:34:01"
+ subnets:
+ - type: static
+ address: 2001:1::101/96
+ - type: bridge
+ name: br0
+ bridge_interfaces:
+ - eth0
+ - eth1
+ params:
+ bridge_stp: 'off'
+ bridge_bridgeprio: 22
+ subnets:
+ - type: static
+ address: 192.168.2.2/24"""),
+ 'expected_sysconfig': {
+ 'ifcfg-br0': textwrap.dedent("""\
+ BOOTPROTO=none
+ DEVICE=br0
+ IPADDR=192.168.2.2
+ NETMASK=255.255.255.0
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ PRIO=22
+ STP=off
+ TYPE=Bridge
+ USERCTL=no
+ """),
+ 'ifcfg-eth0': textwrap.dedent("""\
+ BOOTPROTO=none
+ BRIDGE=br0
+ DEVICE=eth0
+ HWADDR=52:54:00:12:34:00
+ IPV6ADDR=2001:1::100/96
+ IPV6INIT=yes
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """),
+ 'ifcfg-eth1': textwrap.dedent("""\
+ BOOTPROTO=none
+ BRIDGE=br0
+ DEVICE=eth1
+ HWADDR=52:54:00:12:34:01
+ IPV6ADDR=2001:1::101/96
+ IPV6INIT=yes
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """),
+ },
+ },
+ 'manual': {
+ 'yaml': textwrap.dedent("""
+ version: 1
+ config:
+ - type: physical
+ name: eth0
+ mac_address: "52:54:00:12:34:00"
+ subnets:
+ - type: static
+ address: 192.168.1.2/24
+ control: manual
+ - type: physical
+ name: eth1
+ mtu: 1480
+ mac_address: "52:54:00:12:34:aa"
+ subnets:
+ - type: manual
+ - type: physical
+ name: eth2
+ mac_address: "52:54:00:12:34:ff"
+ subnets:
+ - type: manual
+ control: manual
+ """),
+ 'expected_eni': textwrap.dedent("""\
+ auto lo
+ iface lo inet loopback
+
+ # control-manual eth0
+ iface eth0 inet static
+ address 192.168.1.2/24
+
+ auto eth1
+ iface eth1 inet manual
+ mtu 1480
+
+ # control-manual eth2
+ iface eth2 inet manual
+ """),
+ 'expected_netplan': textwrap.dedent("""\
+
+ network:
+ version: 2
+ ethernets:
+ eth0:
+ addresses:
+ - 192.168.1.2/24
+ match:
+ macaddress: '52:54:00:12:34:00'
+ set-name: eth0
+ eth1:
+ match:
+ macaddress: 52:54:00:12:34:aa
+ mtu: 1480
+ set-name: eth1
+ eth2:
+ match:
+ macaddress: 52:54:00:12:34:ff
+ set-name: eth2
+ """),
+ 'expected_sysconfig': {
+ 'ifcfg-eth0': textwrap.dedent("""\
+ BOOTPROTO=none
+ DEVICE=eth0
+ HWADDR=52:54:00:12:34:00
+ IPADDR=192.168.1.2
+ NETMASK=255.255.255.0
+ NM_CONTROLLED=no
+ ONBOOT=no
+ TYPE=Ethernet
+ USERCTL=no
+ """),
+ 'ifcfg-eth1': textwrap.dedent("""\
+ BOOTPROTO=none
+ DEVICE=eth1
+ HWADDR=52:54:00:12:34:aa
+ MTU=1480
+ NM_CONTROLLED=no
+ ONBOOT=yes
+ TYPE=Ethernet
+ USERCTL=no
+ """),
+ 'ifcfg-eth2': textwrap.dedent("""\
+ BOOTPROTO=none
+ DEVICE=eth2
+ HWADDR=52:54:00:12:34:ff
+ NM_CONTROLLED=no
+ ONBOOT=no
+ TYPE=Ethernet
+ USERCTL=no
+ """),
+ },
+ },
}
+
CONFIG_V1_EXPLICIT_LOOPBACK = {
'version': 1,
'config': [{'name': 'eth0', 'type': 'physical',
@@ -790,39 +1336,231 @@ CONFIG_V1_EXPLICIT_LOOPBACK = {
]}
-def _setup_test(tmp_dir, mock_get_devicelist, mock_read_sys_net,
- mock_sys_dev_path):
- mock_get_devicelist.return_value = ['eth1000']
- dev_characteristics = {
- 'eth1000': {
- "bridge": False,
- "carrier": False,
- "dormant": False,
- "operstate": "down",
- "address": "07-1C-C6-75-A4-BE",
- }
+CONFIG_V1_SIMPLE_SUBNET = {
+ 'version': 1,
+ 'config': [{'mac_address': '52:54:00:12:34:00',
+ 'name': 'interface0',
+ 'subnets': [{'address': '10.0.2.15',
+ 'gateway': '10.0.2.2',
+ 'netmask': '255.255.255.0',
+ 'type': 'static'}],
+ 'type': 'physical'}]}
+
+
+DEFAULT_DEV_ATTRS = {
+ 'eth1000': {
+ "bridge": False,
+ "carrier": False,
+ "dormant": False,
+ "operstate": "down",
+ "address": "07-1C-C6-75-A4-BE",
+ "device/driver": None,
+ "device/device": None,
}
+}
+
+
+def _setup_test(tmp_dir, mock_get_devicelist, mock_read_sys_net,
+ mock_sys_dev_path, dev_attrs=None):
+ if not dev_attrs:
+ dev_attrs = DEFAULT_DEV_ATTRS
+
+ mock_get_devicelist.return_value = dev_attrs.keys()
def fake_read(devname, path, translate=None,
on_enoent=None, on_keyerror=None,
on_einval=None):
- return dev_characteristics[devname][path]
+ return dev_attrs[devname][path]
mock_read_sys_net.side_effect = fake_read
def sys_dev_path(devname, path=""):
- return tmp_dir + devname + "/" + path
+ return tmp_dir + "/" + devname + "/" + path
- for dev in dev_characteristics:
+ for dev in dev_attrs:
os.makedirs(os.path.join(tmp_dir, dev))
with open(os.path.join(tmp_dir, dev, 'operstate'), 'w') as fh:
- fh.write("down")
+ fh.write(dev_attrs[dev]['operstate'])
+ os.makedirs(os.path.join(tmp_dir, dev, "device"))
+ for key in ['device/driver']:
+ if key in dev_attrs[dev] and dev_attrs[dev][key]:
+ target = dev_attrs[dev][key]
+ link = os.path.join(tmp_dir, dev, key)
+ print('symlink %s -> %s' % (link, target))
+ os.symlink(target, link)
mock_sys_dev_path.side_effect = sys_dev_path
+class TestGenerateFallbackConfig(CiTestCase):
+
+ @mock.patch("cloudinit.net.sys_dev_path")
+ @mock.patch("cloudinit.net.read_sys_net")
+ @mock.patch("cloudinit.net.get_devicelist")
+ def test_device_driver(self, mock_get_devicelist, mock_read_sys_net,
+ mock_sys_dev_path):
+ devices = {
+ 'eth0': {
+ 'bridge': False, 'carrier': False, 'dormant': False,
+ 'operstate': 'down', 'address': '00:11:22:33:44:55',
+ 'device/driver': 'hv_netsvc', 'device/device': '0x3'},
+ 'eth1': {
+ 'bridge': False, 'carrier': False, 'dormant': False,
+ 'operstate': 'down', 'address': '00:11:22:33:44:55',
+ 'device/driver': 'mlx4_core', 'device/device': '0x7'},
+ }
+
+ tmp_dir = self.tmp_dir()
+ _setup_test(tmp_dir, mock_get_devicelist,
+ mock_read_sys_net, mock_sys_dev_path,
+ dev_attrs=devices)
+
+ network_cfg = net.generate_fallback_config(config_driver=True)
+ ns = network_state.parse_net_config_data(network_cfg,
+ skip_broken=False)
+
+ render_dir = os.path.join(tmp_dir, "render")
+ os.makedirs(render_dir)
+
+ # don't set rulepath so eni writes them
+ renderer = eni.Renderer(
+ {'eni_path': 'interfaces', 'netrules_path': 'netrules'})
+ renderer.render_network_state(ns, render_dir)
+
+ self.assertTrue(os.path.exists(os.path.join(render_dir,
+ 'interfaces')))
+ with open(os.path.join(render_dir, 'interfaces')) as fh:
+ contents = fh.read()
+ print(contents)
+ expected = """
+auto lo
+iface lo inet loopback
+
+auto eth0
+iface eth0 inet dhcp
+"""
+ self.assertEqual(expected.lstrip(), contents.lstrip())
+
+ self.assertTrue(os.path.exists(os.path.join(render_dir, 'netrules')))
+ with open(os.path.join(render_dir, 'netrules')) as fh:
+ contents = fh.read()
+ print(contents)
+ expected_rule = [
+ 'SUBSYSTEM=="net"',
+ 'ACTION=="add"',
+ 'DRIVERS=="hv_netsvc"',
+ 'ATTR{address}=="00:11:22:33:44:55"',
+ 'NAME="eth0"',
+ ]
+ self.assertEqual(", ".join(expected_rule) + '\n', contents.lstrip())
+
+ @mock.patch("cloudinit.net.sys_dev_path")
+ @mock.patch("cloudinit.net.read_sys_net")
+ @mock.patch("cloudinit.net.get_devicelist")
+ def test_device_driver_blacklist(self, mock_get_devicelist,
+ mock_read_sys_net, mock_sys_dev_path):
+ devices = {
+ 'eth1': {
+ 'bridge': False, 'carrier': False, 'dormant': False,
+ 'operstate': 'down', 'address': '00:11:22:33:44:55',
+ 'device/driver': 'hv_netsvc', 'device/device': '0x3'},
+ 'eth0': {
+ 'bridge': False, 'carrier': False, 'dormant': False,
+ 'operstate': 'down', 'address': '00:11:22:33:44:55',
+ 'device/driver': 'mlx4_core', 'device/device': '0x7'},
+ }
+
+ tmp_dir = self.tmp_dir()
+ _setup_test(tmp_dir, mock_get_devicelist,
+ mock_read_sys_net, mock_sys_dev_path,
+ dev_attrs=devices)
+
+ blacklist = ['mlx4_core']
+ network_cfg = net.generate_fallback_config(blacklist_drivers=blacklist,
+ config_driver=True)
+ ns = network_state.parse_net_config_data(network_cfg,
+ skip_broken=False)
+
+ render_dir = os.path.join(tmp_dir, "render")
+ os.makedirs(render_dir)
+
+ # don't set rulepath so eni writes them
+ renderer = eni.Renderer(
+ {'eni_path': 'interfaces', 'netrules_path': 'netrules'})
+ renderer.render_network_state(ns, render_dir)
+
+ self.assertTrue(os.path.exists(os.path.join(render_dir,
+ 'interfaces')))
+ with open(os.path.join(render_dir, 'interfaces')) as fh:
+ contents = fh.read()
+ print(contents)
+ expected = """
+auto lo
+iface lo inet loopback
+
+auto eth1
+iface eth1 inet dhcp
+"""
+ self.assertEqual(expected.lstrip(), contents.lstrip())
+
+ self.assertTrue(os.path.exists(os.path.join(render_dir, 'netrules')))
+ with open(os.path.join(render_dir, 'netrules')) as fh:
+ contents = fh.read()
+ print(contents)
+ expected_rule = [
+ 'SUBSYSTEM=="net"',
+ 'ACTION=="add"',
+ 'DRIVERS=="hv_netsvc"',
+ 'ATTR{address}=="00:11:22:33:44:55"',
+ 'NAME="eth1"',
+ ]
+ self.assertEqual(", ".join(expected_rule) + '\n', contents.lstrip())
+
+
class TestSysConfigRendering(CiTestCase):
+ scripts_dir = '/etc/sysconfig/network-scripts'
+ header = ('# Created by cloud-init on instance boot automatically, '
+ 'do not edit.\n#\n')
+
+ def _render_and_read(self, network_config=None, state=None, dir=None):
+ if dir is None:
+ dir = self.tmp_dir()
+
+ if network_config:
+ ns = network_state.parse_net_config_data(network_config)
+ elif state:
+ ns = state
+ else:
+ raise ValueError("Expected data or state, got neither")
+
+ renderer = sysconfig.Renderer()
+ renderer.render_network_state(ns, dir)
+ return dir2dict(dir)
+
+ def _compare_files_to_expected(self, expected, found):
+ orig_maxdiff = self.maxDiff
+ expected_d = dict(
+ (os.path.join(self.scripts_dir, k), util.load_shell_content(v))
+ for k, v in expected.items())
+
+ # only compare the files in scripts_dir
+ scripts_found = dict(
+ (k, util.load_shell_content(v)) for k, v in found.items()
+ if k.startswith(self.scripts_dir))
+ try:
+ self.maxDiff = None
+ self.assertEqual(expected_d, scripts_found)
+ finally:
+ self.maxDiff = orig_maxdiff
+
+ def _assert_headers(self, found):
+ missing = [f for f in found
+ if (f.startswith(self.scripts_dir) and
+ not found[f].startswith(self.header))]
+ if missing:
+ raise AssertionError("Missing headers in: %s" % missing)
+
@mock.patch("cloudinit.net.sys_dev_path")
@mock.patch("cloudinit.net.read_sys_net")
@mock.patch("cloudinit.net.get_devicelist")
@@ -950,6 +1688,32 @@ USERCTL=no
with open(os.path.join(render_dir, fn)) as fh:
self.assertEqual(expected_content, fh.read())
+ def test_network_config_v1_samples(self):
+ ns = network_state.parse_net_config_data(CONFIG_V1_SIMPLE_SUBNET)
+ render_dir = self.tmp_path("render")
+ os.makedirs(render_dir)
+ renderer = sysconfig.Renderer()
+ renderer.render_network_state(ns, render_dir)
+ found = dir2dict(render_dir)
+ nspath = '/etc/sysconfig/network-scripts/'
+ self.assertNotIn(nspath + 'ifcfg-lo', found.keys())
+ expected = """\
+# Created by cloud-init on instance boot automatically, do not edit.
+#
+BOOTPROTO=none
+DEFROUTE=yes
+DEVICE=interface0
+GATEWAY=10.0.2.2
+HWADDR=52:54:00:12:34:00
+IPADDR=10.0.2.15
+NETMASK=255.255.255.0
+NM_CONTROLLED=no
+ONBOOT=yes
+TYPE=Ethernet
+USERCTL=no
+"""
+ self.assertEqual(expected, found[nspath + 'ifcfg-interface0'])
+
def test_config_with_explicit_loopback(self):
ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK)
render_dir = self.tmp_path("render")
@@ -971,6 +1735,48 @@ USERCTL=no
"""
self.assertEqual(expected, found[nspath + 'ifcfg-eth0'])
+ def test_bond_config(self):
+ entry = NETWORK_CONFIGS['bond']
+ found = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ self._compare_files_to_expected(entry['expected_sysconfig'], found)
+ self._assert_headers(found)
+
+ def test_vlan_config(self):
+ entry = NETWORK_CONFIGS['vlan']
+ found = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ self._compare_files_to_expected(entry['expected_sysconfig'], found)
+ self._assert_headers(found)
+
+ def test_bridge_config(self):
+ entry = NETWORK_CONFIGS['bridge']
+ found = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ self._compare_files_to_expected(entry['expected_sysconfig'], found)
+ self._assert_headers(found)
+
+ def test_manual_config(self):
+ entry = NETWORK_CONFIGS['manual']
+ found = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ self._compare_files_to_expected(entry['expected_sysconfig'], found)
+ self._assert_headers(found)
+
+ def test_all_config(self):
+ entry = NETWORK_CONFIGS['all']
+ found = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ self._compare_files_to_expected(entry['expected_sysconfig'], found)
+ self._assert_headers(found)
+
+ def test_small_config(self):
+ entry = NETWORK_CONFIGS['small']
+ found = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ self._compare_files_to_expected(entry['expected_sysconfig'], found)
+ self._assert_headers(found)
+
+ def test_v4_and_v6_static_config(self):
+ entry = NETWORK_CONFIGS['v4_and_v6_static']
+ found = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ self._compare_files_to_expected(entry['expected_sysconfig'], found)
+ self._assert_headers(found)
+
class TestEniNetRendering(CiTestCase):
@@ -992,9 +1798,7 @@ class TestEniNetRendering(CiTestCase):
os.makedirs(render_dir)
renderer = eni.Renderer(
- {'links_path_prefix': None,
- 'eni_path': 'interfaces', 'netrules_path': None,
- })
+ {'eni_path': 'interfaces', 'netrules_path': None})
renderer.render_network_state(ns, render_dir)
self.assertTrue(os.path.exists(os.path.join(render_dir,
@@ -1366,6 +2170,13 @@ class TestNetplanRoundTrip(CiTestCase):
entry['expected_netplan'].splitlines(),
files['/etc/netplan/50-cloud-init.yaml'].splitlines())
+ def testsimple_render_v4_and_v6_static(self):
+ entry = NETWORK_CONFIGS['v4_and_v6_static']
+ files = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ self.assertEqual(
+ entry['expected_netplan'].splitlines(),
+ files['/etc/netplan/50-cloud-init.yaml'].splitlines())
+
def testsimple_render_all(self):
entry = NETWORK_CONFIGS['all']
files = self._render_and_read(network_config=yaml.load(entry['yaml']))
@@ -1373,10 +2184,17 @@ class TestNetplanRoundTrip(CiTestCase):
entry['expected_netplan'].splitlines(),
files['/etc/netplan/50-cloud-init.yaml'].splitlines())
+ def testsimple_render_manual(self):
+ entry = NETWORK_CONFIGS['manual']
+ files = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ self.assertEqual(
+ entry['expected_netplan'].splitlines(),
+ files['/etc/netplan/50-cloud-init.yaml'].splitlines())
+
class TestEniRoundTrip(CiTestCase):
def _render_and_read(self, network_config=None, state=None, eni_path=None,
- links_prefix=None, netrules_path=None, dir=None):
+ netrules_path=None, dir=None):
if dir is None:
dir = self.tmp_dir()
@@ -1391,8 +2209,7 @@ class TestEniRoundTrip(CiTestCase):
eni_path = 'etc/network/interfaces'
renderer = eni.Renderer(
- config={'eni_path': eni_path, 'links_path_prefix': links_prefix,
- 'netrules_path': netrules_path})
+ config={'eni_path': eni_path, 'netrules_path': netrules_path})
renderer.render_network_state(ns, dir)
return dir2dict(dir)
@@ -1425,6 +2242,27 @@ class TestEniRoundTrip(CiTestCase):
entry['expected_eni'].splitlines(),
files['/etc/network/interfaces'].splitlines())
+ def testsimple_render_v4_and_v6_static(self):
+ entry = NETWORK_CONFIGS['v4_and_v6_static']
+ files = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ self.assertEqual(
+ entry['expected_eni'].splitlines(),
+ files['/etc/network/interfaces'].splitlines())
+
+ def testsimple_render_manual(self):
+ """Test rendering of 'manual' for 'type' and 'control'.
+
+ 'type: manual' in a subnet is odd, but it is the way that was used
+ to declare that a network device should get a mtu set on it even
+ if there were no addresses to configure. Also strange is the fact
+ that in order to apply that MTU the ifupdown device must be set
+ to 'auto', or the MTU would not be set."""
+ entry = NETWORK_CONFIGS['manual']
+ files = self._render_and_read(network_config=yaml.load(entry['yaml']))
+ self.assertEqual(
+ entry['expected_eni'].splitlines(),
+ files['/etc/network/interfaces'].splitlines())
+
def test_routes_rendered(self):
# as reported in bug 1649652
conf = [
@@ -1516,6 +2354,118 @@ class TestNetRenderers(CiTestCase):
priority=['sysconfig', 'eni'])
+class TestGetInterfaces(CiTestCase):
+ _data = {'bonds': ['bond1'],
+ 'bridges': ['bridge1'],
+ 'vlans': ['bond1.101'],
+ 'own_macs': ['enp0s1', 'enp0s2', 'bridge1-nic', 'bridge1',
+ 'bond1.101', 'lo', 'eth1'],
+ 'macs': {'enp0s1': 'aa:aa:aa:aa:aa:01',
+ 'enp0s2': 'aa:aa:aa:aa:aa:02',
+ 'bond1': 'aa:aa:aa:aa:aa:01',
+ 'bond1.101': 'aa:aa:aa:aa:aa:01',
+ 'bridge1': 'aa:aa:aa:aa:aa:03',
+ 'bridge1-nic': 'aa:aa:aa:aa:aa:03',
+ 'lo': '00:00:00:00:00:00',
+ 'greptap0': '00:00:00:00:00:00',
+ 'eth1': 'aa:aa:aa:aa:aa:01',
+ 'tun0': None},
+ 'drivers': {'enp0s1': 'virtio_net',
+ 'enp0s2': 'e1000',
+ 'bond1': None,
+ 'bond1.101': None,
+ 'bridge1': None,
+ 'bridge1-nic': None,
+ 'lo': None,
+ 'greptap0': None,
+ 'eth1': 'mlx4_core',
+ 'tun0': None}}
+ data = {}
+
+ def _se_get_devicelist(self):
+ return list(self.data['devices'])
+
+ def _se_device_driver(self, name):
+ return self.data['drivers'][name]
+
+ def _se_device_devid(self, name):
+ return '0x%s' % sorted(list(self.data['drivers'].keys())).index(name)
+
+ def _se_get_interface_mac(self, name):
+ return self.data['macs'][name]
+
+ def _se_is_bridge(self, name):
+ return name in self.data['bridges']
+
+ def _se_is_vlan(self, name):
+ return name in self.data['vlans']
+
+ def _se_interface_has_own_mac(self, name):
+ return name in self.data['own_macs']
+
+ def _mock_setup(self):
+ self.data = copy.deepcopy(self._data)
+ self.data['devices'] = set(list(self.data['macs'].keys()))
+ mocks = ('get_devicelist', 'get_interface_mac', 'is_bridge',
+ 'interface_has_own_mac', 'is_vlan', 'device_driver',
+ 'device_devid')
+ self.mocks = {}
+ for n in mocks:
+ m = mock.patch('cloudinit.net.' + n,
+ side_effect=getattr(self, '_se_' + n))
+ self.addCleanup(m.stop)
+ self.mocks[n] = m.start()
+
+ def test_gi_includes_duplicate_macs(self):
+ self._mock_setup()
+ ret = net.get_interfaces()
+
+ self.assertIn('enp0s1', self._se_get_devicelist())
+ self.assertIn('eth1', self._se_get_devicelist())
+ found = [ent for ent in ret if 'aa:aa:aa:aa:aa:01' in ent]
+ self.assertEqual(len(found), 2)
+
+ def test_gi_excludes_any_without_mac_address(self):
+ self._mock_setup()
+ ret = net.get_interfaces()
+
+ self.assertIn('tun0', self._se_get_devicelist())
+ found = [ent for ent in ret if 'tun0' in ent]
+ self.assertEqual(len(found), 0)
+
+ def test_gi_excludes_stolen_macs(self):
+ self._mock_setup()
+ ret = net.get_interfaces()
+ self.mocks['interface_has_own_mac'].assert_has_calls(
+ [mock.call('enp0s1'), mock.call('bond1')], any_order=True)
+ expected = [
+ ('enp0s2', 'aa:aa:aa:aa:aa:02', 'e1000', '0x5'),
+ ('enp0s1', 'aa:aa:aa:aa:aa:01', 'virtio_net', '0x4'),
+ ('eth1', 'aa:aa:aa:aa:aa:01', 'mlx4_core', '0x6'),
+ ('lo', '00:00:00:00:00:00', None, '0x8'),
+ ('bridge1-nic', 'aa:aa:aa:aa:aa:03', None, '0x3'),
+ ]
+ self.assertEqual(sorted(expected), sorted(ret))
+
+ def test_gi_excludes_bridges(self):
+ self._mock_setup()
+ # add a device 'b1', make all return they have their "own mac",
+ # set everything other than 'b1' to be a bridge.
+ # then expect b1 is the only thing left.
+ self.data['macs']['b1'] = 'aa:aa:aa:aa:aa:b1'
+ self.data['drivers']['b1'] = None
+ self.data['devices'].add('b1')
+ self.data['bonds'] = []
+ self.data['own_macs'] = self.data['devices']
+ self.data['bridges'] = [f for f in self.data['devices'] if f != "b1"]
+ ret = net.get_interfaces()
+ self.assertEqual([('b1', 'aa:aa:aa:aa:aa:b1', None, '0x0')], ret)
+ self.mocks['is_bridge'].assert_has_calls(
+ [mock.call('bridge1'), mock.call('enp0s1'), mock.call('bond1'),
+ mock.call('b1')],
+ any_order=True)
+
+
class TestGetInterfacesByMac(CiTestCase):
_data = {'bonds': ['bond1'],
'bridges': ['bridge1'],
@@ -1627,6 +2577,19 @@ class TestGetInterfacesByMac(CiTestCase):
self.assertEqual('lo', ret[empty_mac])
+class TestInterfacesSorting(CiTestCase):
+
+ def test_natural_order(self):
+ data = ['ens5', 'ens6', 'ens3', 'ens20', 'ens13', 'ens2']
+ self.assertEqual(
+ sorted(data, key=_natural_sort_key),
+ ['ens2', 'ens3', 'ens5', 'ens6', 'ens13', 'ens20'])
+ data2 = ['enp2s0', 'enp2s3', 'enp0s3', 'enp0s13', 'enp0s8', 'enp1s2']
+ self.assertEqual(
+ sorted(data2, key=_natural_sort_key),
+ ['enp0s3', 'enp0s8', 'enp0s13', 'enp1s2', 'enp2s0', 'enp2s3'])
+
+
def _gzip_data(data):
with io.BytesIO() as iobuf:
gzfp = gzip.GzipFile(mode="wb", fileobj=iobuf)
@@ -1634,4 +2597,229 @@ def _gzip_data(data):
gzfp.close()
return iobuf.getvalue()
+
+class TestRenameInterfaces(CiTestCase):
+
+ @mock.patch('cloudinit.util.subp')
+ def test_rename_all(self, mock_subp):
+ renames = [
+ ('00:11:22:33:44:55', 'interface0', 'virtio_net', '0x3'),
+ ('00:11:22:33:44:aa', 'interface2', 'virtio_net', '0x5'),
+ ]
+ current_info = {
+ 'ens3': {
+ 'downable': True,
+ 'device_id': '0x3',
+ 'driver': 'virtio_net',
+ 'mac': '00:11:22:33:44:55',
+ 'name': 'ens3',
+ 'up': False},
+ 'ens5': {
+ 'downable': True,
+ 'device_id': '0x5',
+ 'driver': 'virtio_net',
+ 'mac': '00:11:22:33:44:aa',
+ 'name': 'ens5',
+ 'up': False},
+ }
+ net._rename_interfaces(renames, current_info=current_info)
+ print(mock_subp.call_args_list)
+ mock_subp.assert_has_calls([
+ mock.call(['ip', 'link', 'set', 'ens3', 'name', 'interface0'],
+ capture=True),
+ mock.call(['ip', 'link', 'set', 'ens5', 'name', 'interface2'],
+ capture=True),
+ ])
+
+ @mock.patch('cloudinit.util.subp')
+ def test_rename_no_driver_no_device_id(self, mock_subp):
+ renames = [
+ ('00:11:22:33:44:55', 'interface0', None, None),
+ ('00:11:22:33:44:aa', 'interface1', None, None),
+ ]
+ current_info = {
+ 'eth0': {
+ 'downable': True,
+ 'device_id': None,
+ 'driver': None,
+ 'mac': '00:11:22:33:44:55',
+ 'name': 'eth0',
+ 'up': False},
+ 'eth1': {
+ 'downable': True,
+ 'device_id': None,
+ 'driver': None,
+ 'mac': '00:11:22:33:44:aa',
+ 'name': 'eth1',
+ 'up': False},
+ }
+ net._rename_interfaces(renames, current_info=current_info)
+ print(mock_subp.call_args_list)
+ mock_subp.assert_has_calls([
+ mock.call(['ip', 'link', 'set', 'eth0', 'name', 'interface0'],
+ capture=True),
+ mock.call(['ip', 'link', 'set', 'eth1', 'name', 'interface1'],
+ capture=True),
+ ])
+
+ @mock.patch('cloudinit.util.subp')
+ def test_rename_all_bounce(self, mock_subp):
+ renames = [
+ ('00:11:22:33:44:55', 'interface0', 'virtio_net', '0x3'),
+ ('00:11:22:33:44:aa', 'interface2', 'virtio_net', '0x5'),
+ ]
+ current_info = {
+ 'ens3': {
+ 'downable': True,
+ 'device_id': '0x3',
+ 'driver': 'virtio_net',
+ 'mac': '00:11:22:33:44:55',
+ 'name': 'ens3',
+ 'up': True},
+ 'ens5': {
+ 'downable': True,
+ 'device_id': '0x5',
+ 'driver': 'virtio_net',
+ 'mac': '00:11:22:33:44:aa',
+ 'name': 'ens5',
+ 'up': True},
+ }
+ net._rename_interfaces(renames, current_info=current_info)
+ print(mock_subp.call_args_list)
+ mock_subp.assert_has_calls([
+ mock.call(['ip', 'link', 'set', 'ens3', 'down'], capture=True),
+ mock.call(['ip', 'link', 'set', 'ens3', 'name', 'interface0'],
+ capture=True),
+ mock.call(['ip', 'link', 'set', 'ens5', 'down'], capture=True),
+ mock.call(['ip', 'link', 'set', 'ens5', 'name', 'interface2'],
+ capture=True),
+ mock.call(['ip', 'link', 'set', 'interface0', 'up'], capture=True),
+ mock.call(['ip', 'link', 'set', 'interface2', 'up'], capture=True)
+ ])
+
+ @mock.patch('cloudinit.util.subp')
+ def test_rename_duplicate_macs(self, mock_subp):
+ renames = [
+ ('00:11:22:33:44:55', 'eth0', 'hv_netsvc', '0x3'),
+ ('00:11:22:33:44:55', 'vf1', 'mlx4_core', '0x5'),
+ ]
+ current_info = {
+ 'eth0': {
+ 'downable': True,
+ 'device_id': '0x3',
+ 'driver': 'hv_netsvc',
+ 'mac': '00:11:22:33:44:55',
+ 'name': 'eth0',
+ 'up': False},
+ 'eth1': {
+ 'downable': True,
+ 'device_id': '0x5',
+ 'driver': 'mlx4_core',
+ 'mac': '00:11:22:33:44:55',
+ 'name': 'eth1',
+ 'up': False},
+ }
+ net._rename_interfaces(renames, current_info=current_info)
+ print(mock_subp.call_args_list)
+ mock_subp.assert_has_calls([
+ mock.call(['ip', 'link', 'set', 'eth1', 'name', 'vf1'],
+ capture=True),
+ ])
+
+ @mock.patch('cloudinit.util.subp')
+ def test_rename_duplicate_macs_driver_no_devid(self, mock_subp):
+ renames = [
+ ('00:11:22:33:44:55', 'eth0', 'hv_netsvc', None),
+ ('00:11:22:33:44:55', 'vf1', 'mlx4_core', None),
+ ]
+ current_info = {
+ 'eth0': {
+ 'downable': True,
+ 'device_id': '0x3',
+ 'driver': 'hv_netsvc',
+ 'mac': '00:11:22:33:44:55',
+ 'name': 'eth0',
+ 'up': False},
+ 'eth1': {
+ 'downable': True,
+ 'device_id': '0x5',
+ 'driver': 'mlx4_core',
+ 'mac': '00:11:22:33:44:55',
+ 'name': 'eth1',
+ 'up': False},
+ }
+ net._rename_interfaces(renames, current_info=current_info)
+ print(mock_subp.call_args_list)
+ mock_subp.assert_has_calls([
+ mock.call(['ip', 'link', 'set', 'eth1', 'name', 'vf1'],
+ capture=True),
+ ])
+
+ @mock.patch('cloudinit.util.subp')
+ def test_rename_multi_mac_dups(self, mock_subp):
+ renames = [
+ ('00:11:22:33:44:55', 'eth0', 'hv_netsvc', '0x3'),
+ ('00:11:22:33:44:55', 'vf1', 'mlx4_core', '0x5'),
+ ('00:11:22:33:44:55', 'vf2', 'mlx4_core', '0x7'),
+ ]
+ current_info = {
+ 'eth0': {
+ 'downable': True,
+ 'device_id': '0x3',
+ 'driver': 'hv_netsvc',
+ 'mac': '00:11:22:33:44:55',
+ 'name': 'eth0',
+ 'up': False},
+ 'eth1': {
+ 'downable': True,
+ 'device_id': '0x5',
+ 'driver': 'mlx4_core',
+ 'mac': '00:11:22:33:44:55',
+ 'name': 'eth1',
+ 'up': False},
+ 'eth2': {
+ 'downable': True,
+ 'device_id': '0x7',
+ 'driver': 'mlx4_core',
+ 'mac': '00:11:22:33:44:55',
+ 'name': 'eth2',
+ 'up': False},
+ }
+ net._rename_interfaces(renames, current_info=current_info)
+ print(mock_subp.call_args_list)
+ mock_subp.assert_has_calls([
+ mock.call(['ip', 'link', 'set', 'eth1', 'name', 'vf1'],
+ capture=True),
+ mock.call(['ip', 'link', 'set', 'eth2', 'name', 'vf2'],
+ capture=True),
+ ])
+
+ @mock.patch('cloudinit.util.subp')
+ def test_rename_macs_case_insensitive(self, mock_subp):
+ """_rename_interfaces must support upper or lower case macs."""
+ renames = [
+ ('aa:aa:aa:aa:aa:aa', 'en0', None, None),
+ ('BB:BB:BB:BB:BB:BB', 'en1', None, None),
+ ('cc:cc:cc:cc:cc:cc', 'en2', None, None),
+ ('DD:DD:DD:DD:DD:DD', 'en3', None, None),
+ ]
+ current_info = {
+ 'eth0': {'downable': True, 'mac': 'AA:AA:AA:AA:AA:AA',
+ 'name': 'eth0', 'up': False},
+ 'eth1': {'downable': True, 'mac': 'bb:bb:bb:bb:bb:bb',
+ 'name': 'eth1', 'up': False},
+ 'eth2': {'downable': True, 'mac': 'cc:cc:cc:cc:cc:cc',
+ 'name': 'eth2', 'up': False},
+ 'eth3': {'downable': True, 'mac': 'DD:DD:DD:DD:DD:DD',
+ 'name': 'eth3', 'up': False},
+ }
+ net._rename_interfaces(renames, current_info=current_info)
+
+ expected = [
+ mock.call(['ip', 'link', 'set', 'eth%d' % i, 'name', 'en%d' % i],
+ capture=True)
+ for i in range(len(renames))]
+ mock_subp.assert_has_calls(expected)
+
+
# vi: ts=4 expandtab