Add support for IPv6 network configuration
* proper parsing of network content metadata, including v6 support * v6 addresses static network configuration support (address, netmask, gateway) * remove redundant code in networkconfig and osutils network related tests Change-Id: Ieb2c18e29d0c275feb3a03e7340b3c91327705ba
This commit is contained in:
@@ -44,9 +44,12 @@ NetworkDetails = collections.namedtuple(
|
|||||||
"name",
|
"name",
|
||||||
"mac",
|
"mac",
|
||||||
"address",
|
"address",
|
||||||
|
"address6",
|
||||||
"netmask",
|
"netmask",
|
||||||
|
"netmask6",
|
||||||
"broadcast",
|
"broadcast",
|
||||||
"gateway",
|
"gateway",
|
||||||
|
"gateway6",
|
||||||
"dnsnameservers",
|
"dnsnameservers",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@@ -227,13 +227,17 @@ class OpenNebulaService(base.BaseMetadataService):
|
|||||||
broadcast = self._compute_broadcast(address, netmask)
|
broadcast = self._compute_broadcast(address, netmask)
|
||||||
# gather them as namedtuple objects
|
# gather them as namedtuple objects
|
||||||
details = base.NetworkDetails(
|
details = base.NetworkDetails(
|
||||||
IF_FORMAT.format(iid=iid),
|
name=IF_FORMAT.format(iid=iid),
|
||||||
mac,
|
mac=mac,
|
||||||
address,
|
address=address,
|
||||||
netmask,
|
address6=None,
|
||||||
broadcast,
|
netmask=netmask,
|
||||||
gateway,
|
netmask6=None,
|
||||||
self._get_cache_data(DNSNS, iid=iid).split(" ")
|
broadcast=broadcast,
|
||||||
|
gateway=gateway,
|
||||||
|
gateway6=None,
|
||||||
|
dnsnameservers=self._get_cache_data(DNSNS,
|
||||||
|
iid=iid).split(" ")
|
||||||
)
|
)
|
||||||
except base.NotExistingMetadataException:
|
except base.NotExistingMetadataException:
|
||||||
LOG.debug("Incomplete NIC details")
|
LOG.debug("Incomplete NIC details")
|
||||||
|
@@ -39,6 +39,10 @@ from cloudbaseinit.utils.windows import timezone
|
|||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
AF_INET6 = 23
|
||||||
|
UNICAST = 1
|
||||||
|
MANUAL = 1
|
||||||
|
PREFERRED_ADDR = 4
|
||||||
|
|
||||||
advapi32 = ctypes.windll.advapi32
|
advapi32 = ctypes.windll.advapi32
|
||||||
kernel32 = ctypes.windll.kernel32
|
kernel32 = ctypes.windll.kernel32
|
||||||
@@ -541,13 +545,13 @@ class WindowsUtils(base.BaseOSUtils):
|
|||||||
broadcast, gateway, dnsnameservers):
|
broadcast, gateway, dnsnameservers):
|
||||||
conn = wmi.WMI(moniker='//./root/cimv2')
|
conn = wmi.WMI(moniker='//./root/cimv2')
|
||||||
|
|
||||||
q = conn.query("SELECT * FROM Win32_NetworkAdapter WHERE "
|
query = conn.query("SELECT * FROM Win32_NetworkAdapter WHERE "
|
||||||
"MACAddress = '{}'".format(mac_address))
|
"MACAddress = '{}'".format(mac_address))
|
||||||
if not len(q):
|
if not len(query):
|
||||||
raise exception.CloudbaseInitException(
|
raise exception.CloudbaseInitException(
|
||||||
"Network adapter not found")
|
"Network adapter not found")
|
||||||
|
|
||||||
adapter_config = q[0].associators(
|
adapter_config = query[0].associators(
|
||||||
wmi_result_class='Win32_NetworkAdapterConfiguration')[0]
|
wmi_result_class='Win32_NetworkAdapterConfiguration')[0]
|
||||||
|
|
||||||
LOG.debug("Setting static IP address")
|
LOG.debug("Setting static IP address")
|
||||||
@@ -575,6 +579,57 @@ class WindowsUtils(base.BaseOSUtils):
|
|||||||
|
|
||||||
return reboot_required
|
return reboot_required
|
||||||
|
|
||||||
|
def set_static_network_config_v6(self, mac_address, address6,
|
||||||
|
netmask6, gateway6):
|
||||||
|
"""Set IPv6 info for a network card."""
|
||||||
|
|
||||||
|
# Get local properties by MAC identification.
|
||||||
|
adapters = network.get_adapter_addresses()
|
||||||
|
for adapter in adapters:
|
||||||
|
if mac_address == adapter["mac_address"]:
|
||||||
|
ifname = adapter["friendly_name"]
|
||||||
|
ifindex = adapter["interface_index"]
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise exception.CloudbaseInitException(
|
||||||
|
"Adapter with MAC {!r} not available".format(mac_address))
|
||||||
|
|
||||||
|
# TODO(cpoieana): Extend support for other platforms.
|
||||||
|
# Currently windows8 @ ws2012 or above.
|
||||||
|
if not self.check_os_version(6, 2):
|
||||||
|
LOG.warning("Setting IPv6 info not available "
|
||||||
|
"on this system")
|
||||||
|
return
|
||||||
|
conn = wmi.WMI(moniker='//./root/StandardCimv2')
|
||||||
|
query = conn.query("SELECT * FROM MSFT_NetIPAddress "
|
||||||
|
"WHERE InterfaceAlias = '{}'".format(ifname))
|
||||||
|
netip = query[0]
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"InterfaceIndex": ifindex,
|
||||||
|
"InterfaceAlias": ifname,
|
||||||
|
"IPAddress": address6,
|
||||||
|
"AddressFamily": AF_INET6,
|
||||||
|
"PrefixLength": netmask6,
|
||||||
|
# Manual set type.
|
||||||
|
"Type": UNICAST,
|
||||||
|
"PrefixOrigin": MANUAL,
|
||||||
|
"SuffixOrigin": MANUAL,
|
||||||
|
"AddressState": PREFERRED_ADDR,
|
||||||
|
# No expiry.
|
||||||
|
"ValidLifetime": None,
|
||||||
|
"PreferredLifetime": None,
|
||||||
|
"SkipAsSource": False,
|
||||||
|
"DefaultGateway": gateway6,
|
||||||
|
"PolicyStore": None,
|
||||||
|
"PassThru": False,
|
||||||
|
}
|
||||||
|
LOG.debug("Setting IPv6 info for %s", ifname)
|
||||||
|
try:
|
||||||
|
netip.Create(**params)
|
||||||
|
except wmi.x_wmi as exc:
|
||||||
|
raise exception.CloudbaseInitException(exc.com_error)
|
||||||
|
|
||||||
def _get_config_key_name(self, section):
|
def _get_config_key_name(self, section):
|
||||||
key_name = self._config_key
|
key_name = self._config_key
|
||||||
if section:
|
if section:
|
||||||
|
@@ -20,6 +20,7 @@ from cloudbaseinit.metadata.services import base as service_base
|
|||||||
from cloudbaseinit.openstack.common import log as logging
|
from cloudbaseinit.openstack.common import log as logging
|
||||||
from cloudbaseinit.osutils import factory as osutils_factory
|
from cloudbaseinit.osutils import factory as osutils_factory
|
||||||
from cloudbaseinit.plugins.common import base as plugin_base
|
from cloudbaseinit.plugins.common import base as plugin_base
|
||||||
|
from cloudbaseinit.utils import network
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@@ -28,10 +29,10 @@ LOG = logging.getLogger(__name__)
|
|||||||
# if the key is a tuple, then at least one field must exists.
|
# if the key is a tuple, then at least one field must exists.
|
||||||
NET_REQUIRE = {
|
NET_REQUIRE = {
|
||||||
("name", "mac"): True,
|
("name", "mac"): True,
|
||||||
"address": True,
|
("address", "address6"): True,
|
||||||
"netmask": True,
|
("netmask", "netmask6"): True,
|
||||||
"broadcast": False, # currently not used
|
"broadcast": False, # currently not used
|
||||||
"gateway": False,
|
("gateway", "gateway6"): False,
|
||||||
"dnsnameservers": False
|
"dnsnameservers": False
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,30 +77,44 @@ def _preprocess_nics(network_details, network_adapters):
|
|||||||
fields = (fields,)
|
fields = (fields,)
|
||||||
final_status = any([getattr(nic, field) for field in fields])
|
final_status = any([getattr(nic, field) for field in fields])
|
||||||
if not final_status:
|
if not final_status:
|
||||||
LOG.error("Incomplete NetworkDetails object %s", nic)
|
|
||||||
break
|
break
|
||||||
|
address, netmask = nic.address, nic.netmask
|
||||||
if final_status:
|
if final_status:
|
||||||
# Complete hardware address if missing by selecting
|
# Additional check for info version.
|
||||||
# the corresponding MAC in terms of naming, then ordering.
|
if not (address and netmask):
|
||||||
if not nic.mac:
|
final_status = nic.address6 and nic.netmask6
|
||||||
# By name...
|
if final_status:
|
||||||
macs = [adapter[1] for adapter in network_adapters
|
address = address or network.address6_to_4_truncate(
|
||||||
if adapter[0] == nic.name]
|
nic.address6)
|
||||||
mac = macs[0] if macs else None
|
netmask = netmask or network.netmask6_to_4_truncate(
|
||||||
# ...or by order.
|
nic.netmask6)
|
||||||
idx = _name2idx(nic.name)
|
if not final_status:
|
||||||
if not mac and idx < total:
|
LOG.error("Incomplete NetworkDetails object %s", nic)
|
||||||
mac = network_adapters[idx][1]
|
continue
|
||||||
nic = service_base.NetworkDetails(
|
# Complete hardware address if missing by selecting
|
||||||
nic.name,
|
# the corresponding MAC in terms of naming, then ordering.
|
||||||
mac,
|
if not nic.mac:
|
||||||
nic.address,
|
# By name...
|
||||||
nic.netmask,
|
macs = [adapter[1] for adapter in network_adapters
|
||||||
nic.broadcast,
|
if adapter[0] == nic.name]
|
||||||
nic.gateway,
|
mac = macs[0] if macs else None
|
||||||
nic.dnsnameservers
|
# ...or by order.
|
||||||
)
|
idx = _name2idx(nic.name)
|
||||||
refined_network_details.append(nic)
|
if not mac and idx < total:
|
||||||
|
mac = network_adapters[idx][1]
|
||||||
|
nic = service_base.NetworkDetails(
|
||||||
|
nic.name,
|
||||||
|
mac,
|
||||||
|
address,
|
||||||
|
nic.address6,
|
||||||
|
netmask,
|
||||||
|
nic.netmask6,
|
||||||
|
nic.broadcast,
|
||||||
|
nic.gateway,
|
||||||
|
nic.gateway6,
|
||||||
|
nic.dnsnameservers
|
||||||
|
)
|
||||||
|
refined_network_details.append(nic)
|
||||||
return refined_network_details
|
return refined_network_details
|
||||||
|
|
||||||
|
|
||||||
@@ -109,18 +124,18 @@ class NetworkConfigPlugin(plugin_base.BasePlugin):
|
|||||||
osutils = osutils_factory.get_os_utils()
|
osutils = osutils_factory.get_os_utils()
|
||||||
network_details = service.get_network_details()
|
network_details = service.get_network_details()
|
||||||
if not network_details:
|
if not network_details:
|
||||||
return (plugin_base.PLUGIN_EXECUTION_DONE, False)
|
return plugin_base.PLUGIN_EXECUTION_DONE, False
|
||||||
|
|
||||||
# check and save NICs by MAC
|
# Check and save NICs by MAC.
|
||||||
network_adapters = osutils.get_network_adapters()
|
network_adapters = osutils.get_network_adapters()
|
||||||
network_details = _preprocess_nics(network_details,
|
network_details = _preprocess_nics(network_details,
|
||||||
network_adapters)
|
network_adapters)
|
||||||
macnics = {}
|
macnics = {}
|
||||||
for nic in network_details:
|
for nic in network_details:
|
||||||
# assuming that the MAC address is unique
|
# Assuming that the MAC address is unique.
|
||||||
macnics[nic.mac] = nic
|
macnics[nic.mac] = nic
|
||||||
|
|
||||||
# try configuring all the available adapters
|
# Try configuring all the available adapters.
|
||||||
adapter_macs = [pair[1] for pair in network_adapters]
|
adapter_macs = [pair[1] for pair in network_adapters]
|
||||||
reboot_required = False
|
reboot_required = False
|
||||||
configured = False
|
configured = False
|
||||||
@@ -139,10 +154,18 @@ class NetworkConfigPlugin(plugin_base.BasePlugin):
|
|||||||
nic.dnsnameservers
|
nic.dnsnameservers
|
||||||
)
|
)
|
||||||
reboot_required = reboot or reboot_required
|
reboot_required = reboot or reboot_required
|
||||||
|
# Set v6 info too if available.
|
||||||
|
if nic.address6 and nic.netmask6:
|
||||||
|
osutils.set_static_network_config_v6(
|
||||||
|
mac,
|
||||||
|
nic.address6,
|
||||||
|
nic.netmask6,
|
||||||
|
nic.gateway6
|
||||||
|
)
|
||||||
configured = True
|
configured = True
|
||||||
for mac in macnics:
|
for mac in macnics:
|
||||||
LOG.warn("Details not used for adapter %s", mac)
|
LOG.warn("Details not used for adapter %s", mac)
|
||||||
if not configured:
|
if not configured:
|
||||||
LOG.error("No adapters were configured")
|
LOG.error("No adapters were configured")
|
||||||
|
|
||||||
return (plugin_base.PLUGIN_EXECUTION_DONE, reboot_required)
|
return plugin_base.PLUGIN_EXECUTION_DONE, reboot_required
|
||||||
|
@@ -16,16 +16,22 @@
|
|||||||
NAME0 = "eth0"
|
NAME0 = "eth0"
|
||||||
MAC0 = "fa:16:3e:2d:ec:cd"
|
MAC0 = "fa:16:3e:2d:ec:cd"
|
||||||
ADDRESS0 = "10.0.0.15"
|
ADDRESS0 = "10.0.0.15"
|
||||||
|
ADDRESS60 = "2001:db8::3"
|
||||||
NETMASK0 = "255.255.255.0"
|
NETMASK0 = "255.255.255.0"
|
||||||
|
NETMASK60 = "64"
|
||||||
BROADCAST0 = "10.0.0.255"
|
BROADCAST0 = "10.0.0.255"
|
||||||
GATEWAY0 = "10.0.0.1"
|
GATEWAY0 = "10.0.0.1"
|
||||||
|
GATEWAY60 = "2001:db8::1"
|
||||||
DNSNS0 = "208.67.220.220 208.67.222.222"
|
DNSNS0 = "208.67.220.220 208.67.222.222"
|
||||||
|
|
||||||
NAME1 = "eth1"
|
NAME1 = "eth1"
|
||||||
ADDRESS1 = "10.1.0.2"
|
ADDRESS1 = "10.1.0.2"
|
||||||
|
ADDRESS61 = "::ffff:a00:1"
|
||||||
NETMASK1 = "255.255.255.0"
|
NETMASK1 = "255.255.255.0"
|
||||||
|
NETMASK61 = None
|
||||||
BROADCAST1 = "10.1.0.255"
|
BROADCAST1 = "10.1.0.255"
|
||||||
GATEWAY1 = "10.1.0.1"
|
GATEWAY1 = "10.1.0.1"
|
||||||
|
GATEWAY61 = "2001::ffff:a00:1b"
|
||||||
|
|
||||||
|
|
||||||
def get_fake_metadata_json(version):
|
def get_fake_metadata_json(version):
|
||||||
@@ -67,6 +73,8 @@ def get_fake_metadata_json(version):
|
|||||||
},
|
},
|
||||||
"network_config": {
|
"network_config": {
|
||||||
"content_path": "network",
|
"content_path": "network",
|
||||||
|
# This is not actually in the metadata json file,
|
||||||
|
# but is present here for the ease of reading such information.
|
||||||
"debian_config": ("""
|
"debian_config": ("""
|
||||||
# Injected by Nova on instance boot
|
# Injected by Nova on instance boot
|
||||||
#
|
#
|
||||||
@@ -85,6 +93,8 @@ iface {name0} inet static
|
|||||||
broadcast {broadcast0}
|
broadcast {broadcast0}
|
||||||
gateway {gateway0}
|
gateway {gateway0}
|
||||||
dns-nameservers {dnsns0}
|
dns-nameservers {dnsns0}
|
||||||
|
post-up ip -6 addr add {address60}/{netmask60} dev {name0}
|
||||||
|
post-up ip -6 route add default via {gateway60} dev {name0}
|
||||||
|
|
||||||
auto {name1}
|
auto {name1}
|
||||||
iface {name1} inet static
|
iface {name1} inet static
|
||||||
@@ -92,19 +102,30 @@ iface {name1} inet static
|
|||||||
netmask {netmask1}
|
netmask {netmask1}
|
||||||
broadcast {broadcast1}
|
broadcast {broadcast1}
|
||||||
gateway {gateway1}
|
gateway {gateway1}
|
||||||
""").format(name0=NAME0, # eth0 (IPv4)
|
iface eth2 inet6 static
|
||||||
|
address {address61}
|
||||||
|
netmask {netmask61}
|
||||||
|
gateway {gateway61}
|
||||||
|
""").format(name0=NAME0, # eth0 (IPv4/6)
|
||||||
mac0=MAC0,
|
mac0=MAC0,
|
||||||
address0=ADDRESS0,
|
address0=ADDRESS0,
|
||||||
broadcast0=BROADCAST0,
|
broadcast0=BROADCAST0,
|
||||||
netmask0=NETMASK0,
|
netmask0=NETMASK0,
|
||||||
gateway0=GATEWAY0,
|
gateway0=GATEWAY0,
|
||||||
dnsns0=DNSNS0,
|
dnsns0=DNSNS0,
|
||||||
# eth1 (IPv4)
|
address60=ADDRESS60,
|
||||||
|
netmask60=NETMASK60,
|
||||||
|
gateway60=GATEWAY60,
|
||||||
|
# eth1 (IPv4/6)
|
||||||
name1=NAME1,
|
name1=NAME1,
|
||||||
address1=ADDRESS1,
|
address1=ADDRESS1,
|
||||||
broadcast1=BROADCAST1,
|
broadcast1=BROADCAST1,
|
||||||
netmask1=NETMASK1,
|
netmask1=NETMASK1,
|
||||||
gateway1=GATEWAY1)
|
gateway1=GATEWAY1,
|
||||||
|
address61=ADDRESS61,
|
||||||
|
netmask61=NETMASK61,
|
||||||
|
gateway61=GATEWAY61
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -186,25 +186,31 @@ class TestBaseOpenStackService(unittest.TestCase):
|
|||||||
self.assertIsNone(ret)
|
self.assertIsNone(ret)
|
||||||
return
|
return
|
||||||
# check returned NICs details
|
# check returned NICs details
|
||||||
nic1 = base.NetworkDetails(
|
nic0 = base.NetworkDetails(
|
||||||
fake_json_response.NAME0,
|
fake_json_response.NAME0,
|
||||||
fake_json_response.MAC0.upper(),
|
fake_json_response.MAC0.upper(),
|
||||||
fake_json_response.ADDRESS0,
|
fake_json_response.ADDRESS0,
|
||||||
|
fake_json_response.ADDRESS60,
|
||||||
fake_json_response.NETMASK0,
|
fake_json_response.NETMASK0,
|
||||||
|
fake_json_response.NETMASK60,
|
||||||
fake_json_response.BROADCAST0,
|
fake_json_response.BROADCAST0,
|
||||||
fake_json_response.GATEWAY0,
|
fake_json_response.GATEWAY0,
|
||||||
|
fake_json_response.GATEWAY60,
|
||||||
fake_json_response.DNSNS0.split()
|
fake_json_response.DNSNS0.split()
|
||||||
)
|
)
|
||||||
nic2 = base.NetworkDetails(
|
nic1 = base.NetworkDetails(
|
||||||
fake_json_response.NAME1,
|
fake_json_response.NAME1,
|
||||||
None,
|
None,
|
||||||
fake_json_response.ADDRESS1,
|
fake_json_response.ADDRESS1,
|
||||||
|
fake_json_response.ADDRESS61,
|
||||||
fake_json_response.NETMASK1,
|
fake_json_response.NETMASK1,
|
||||||
|
fake_json_response.NETMASK61,
|
||||||
fake_json_response.BROADCAST1,
|
fake_json_response.BROADCAST1,
|
||||||
fake_json_response.GATEWAY1,
|
fake_json_response.GATEWAY1,
|
||||||
|
fake_json_response.GATEWAY61,
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
self.assertEqual([nic1, nic2], ret)
|
self.assertEqual([nic0, nic1], ret)
|
||||||
|
|
||||||
def test_get_network_details_no_config(self):
|
def test_get_network_details_no_config(self):
|
||||||
self._partial_test_get_network_details(
|
self._partial_test_get_network_details(
|
||||||
|
@@ -105,9 +105,12 @@ def _get_nic_details(iid=0):
|
|||||||
opennebulaservice.IF_FORMAT.format(iid=iid),
|
opennebulaservice.IF_FORMAT.format(iid=iid),
|
||||||
MAC,
|
MAC,
|
||||||
ADDRESS,
|
ADDRESS,
|
||||||
|
None,
|
||||||
NETMASK,
|
NETMASK,
|
||||||
|
None,
|
||||||
BROADCAST,
|
BROADCAST,
|
||||||
GATEWAY,
|
GATEWAY,
|
||||||
|
None,
|
||||||
DNSNS.split(" ")
|
DNSNS.split(" ")
|
||||||
)
|
)
|
||||||
return details
|
return details
|
||||||
@@ -307,9 +310,9 @@ class TestLoadedOpenNebulaService(_TestOpenNebulaService):
|
|||||||
|
|
||||||
def test_multiple_nics(self):
|
def test_multiple_nics(self):
|
||||||
self.load_context(context=CONTEXT2)
|
self.load_context(context=CONTEXT2)
|
||||||
nic1 = _get_nic_details(iid=0)
|
nic0 = _get_nic_details(iid=0)
|
||||||
nic2 = _get_nic_details(iid=1)
|
nic1 = _get_nic_details(iid=1)
|
||||||
network_details = [nic1, nic2]
|
network_details = [nic0, nic1]
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
network_details,
|
network_details,
|
||||||
self._service.get_network_details()
|
self._service.get_network_details()
|
||||||
|
@@ -13,6 +13,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import functools
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@@ -30,6 +31,11 @@ from cloudbaseinit.tests import testutils
|
|||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
|
class WMIError(Exception):
|
||||||
|
|
||||||
|
com_error = "fake data"
|
||||||
|
|
||||||
|
|
||||||
class TestWindowsUtils(testutils.CloudbaseInitTestBase):
|
class TestWindowsUtils(testutils.CloudbaseInitTestBase):
|
||||||
'''Tests for the windows utils class.'''
|
'''Tests for the windows utils class.'''
|
||||||
|
|
||||||
@@ -48,6 +54,7 @@ class TestWindowsUtils(testutils.CloudbaseInitTestBase):
|
|||||||
self._win32process_mock = mock.MagicMock()
|
self._win32process_mock = mock.MagicMock()
|
||||||
self._win32security_mock = mock.MagicMock()
|
self._win32security_mock = mock.MagicMock()
|
||||||
self._wmi_mock = mock.MagicMock()
|
self._wmi_mock = mock.MagicMock()
|
||||||
|
self._wmi_mock.x_wmi = WMIError
|
||||||
self._moves_mock = mock.MagicMock()
|
self._moves_mock = mock.MagicMock()
|
||||||
self._xmlrpc_client_mock = mock.MagicMock()
|
self._xmlrpc_client_mock = mock.MagicMock()
|
||||||
self._ctypes_mock = mock.MagicMock()
|
self._ctypes_mock = mock.MagicMock()
|
||||||
@@ -529,121 +536,163 @@ class TestWindowsUtils(testutils.CloudbaseInitTestBase):
|
|||||||
def test_get_network_adapters_xp_2003(self):
|
def test_get_network_adapters_xp_2003(self):
|
||||||
self._test_get_network_adapters(True)
|
self._test_get_network_adapters(True)
|
||||||
|
|
||||||
@mock.patch('cloudbaseinit.osutils.windows.WindowsUtils'
|
def _test_set_static_network_config(self, adapter=True, static_val=(0,),
|
||||||
'._sanitize_wmi_input')
|
gateway_val=(0,), dns_val=(0,)):
|
||||||
def _test_set_static_network_config(self, mock_sanitize_wmi_input,
|
|
||||||
adapter, ret_val1=None,
|
|
||||||
ret_val2=None, ret_val3=None):
|
|
||||||
conn = self._wmi_mock.WMI
|
conn = self._wmi_mock.WMI
|
||||||
address = '10.10.10.10'
|
|
||||||
mac_address = '54:EE:75:19:F4:61'
|
mac_address = '54:EE:75:19:F4:61'
|
||||||
|
address = '10.10.10.10'
|
||||||
broadcast = '0.0.0.0'
|
broadcast = '0.0.0.0'
|
||||||
dns_list = ['8.8.8.8']
|
dns_list = ['8.8.8.8']
|
||||||
|
set_static_call = functools.partial(
|
||||||
|
self._winutils.set_static_network_config,
|
||||||
|
mac_address, address, self._NETMASK,
|
||||||
|
broadcast, self._GATEWAY, dns_list
|
||||||
|
)
|
||||||
|
|
||||||
if not adapter:
|
if adapter:
|
||||||
|
adapter = mock.MagicMock()
|
||||||
|
else:
|
||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
exception.CloudbaseInitException,
|
exception.CloudbaseInitException,
|
||||||
self._winutils.set_static_network_config,
|
set_static_call
|
||||||
mac_address, address, self._NETMASK,
|
)
|
||||||
broadcast, self._GATEWAY, dns_list)
|
return
|
||||||
|
|
||||||
|
conn.return_value.query.return_value = adapter
|
||||||
|
adapter_config = adapter[0].associators.return_value[0]
|
||||||
|
adapter_config.EnableStatic.return_value = static_val
|
||||||
|
adapter_config.SetGateways.return_value = gateway_val
|
||||||
|
adapter_config.SetDNSServerSearchOrder.return_value = dns_val
|
||||||
|
adapter.__len__.return_value = 1
|
||||||
|
if static_val[0] > 1 or gateway_val[0] > 1 or dns_val[0] > 1:
|
||||||
|
self.assertRaises(
|
||||||
|
exception.CloudbaseInitException,
|
||||||
|
set_static_call)
|
||||||
else:
|
else:
|
||||||
conn.return_value.query.return_value = adapter
|
response = set_static_call()
|
||||||
adapter_config = adapter[0].associators()[0]
|
if static_val[0] or gateway_val[0] or dns_val[0]:
|
||||||
adapter_config.EnableStatic.return_value = ret_val1
|
self.assertTrue(response)
|
||||||
adapter_config.SetGateways.return_value = ret_val2
|
|
||||||
adapter_config.SetDNSServerSearchOrder.return_value = ret_val3
|
|
||||||
adapter.__len__.return_value = 1
|
|
||||||
|
|
||||||
if ret_val1[0] > 1:
|
|
||||||
self.assertRaises(
|
|
||||||
exception.CloudbaseInitException,
|
|
||||||
self._winutils.set_static_network_config,
|
|
||||||
mac_address, address, self._NETMASK,
|
|
||||||
broadcast, self._GATEWAY, dns_list)
|
|
||||||
|
|
||||||
elif ret_val2[0] > 1:
|
|
||||||
self.assertRaises(
|
|
||||||
exception.CloudbaseInitException,
|
|
||||||
self._winutils.set_static_network_config,
|
|
||||||
mac_address, address, self._NETMASK,
|
|
||||||
broadcast, self._GATEWAY, dns_list)
|
|
||||||
|
|
||||||
elif ret_val3[0] > 1:
|
|
||||||
self.assertRaises(
|
|
||||||
exception.CloudbaseInitException,
|
|
||||||
self._winutils.set_static_network_config,
|
|
||||||
mac_address, address, self._NETMASK,
|
|
||||||
broadcast, self._GATEWAY, dns_list)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
response = self._winutils.set_static_network_config(
|
self.assertFalse(response)
|
||||||
mac_address, address, self._NETMASK,
|
|
||||||
broadcast, self._GATEWAY, dns_list)
|
|
||||||
|
|
||||||
if ret_val1[0] or ret_val2[0] or ret_val3[0] == 1:
|
select = ("SELECT * FROM Win32_NetworkAdapter WHERE "
|
||||||
self.assertTrue(response)
|
"MACAddress = '{}'".format(mac_address))
|
||||||
else:
|
conn.return_value.query.assert_called_once_with(select)
|
||||||
self.assertFalse(response)
|
adapter[0].associators.assert_called_with(
|
||||||
adapter_config.EnableStatic.assert_called_with(
|
wmi_result_class='Win32_NetworkAdapterConfiguration')
|
||||||
[address], [self._NETMASK])
|
adapter_config.EnableStatic.assert_called_with(
|
||||||
adapter_config.SetGateways.assert_called_with(
|
[address], [self._NETMASK])
|
||||||
[self._GATEWAY], [1])
|
adapter_config.SetGateways.assert_called_with(
|
||||||
adapter_config.SetDNSServerSearchOrder.assert_called_with(
|
[self._GATEWAY], [1])
|
||||||
dns_list)
|
adapter_config.SetDNSServerSearchOrder.assert_called_with(
|
||||||
|
dns_list)
|
||||||
|
|
||||||
adapter[0].associators.assert_called_with(
|
@mock.patch("cloudbaseinit.utils.windows.network"
|
||||||
wmi_result_class='Win32_NetworkAdapterConfiguration')
|
".get_adapter_addresses")
|
||||||
conn.return_value.query.assert_called_with(
|
def _test_set_static_network_config_v6(self, mock_get_adapter_addresses,
|
||||||
"SELECT * FROM Win32_NetworkAdapter WHERE "
|
v6adapters=True, v6error=False):
|
||||||
"MACAddress = '{}'".format(mac_address)
|
friendly_name = "Ethernet0"
|
||||||
)
|
interface_index = "4"
|
||||||
|
mac_address = '54:EE:75:19:F4:61'
|
||||||
|
address6 = "2001:db8::3"
|
||||||
|
netmask6 = "64"
|
||||||
|
gateway6 = "2001:db8::1"
|
||||||
|
|
||||||
|
conn = self._wmi_mock.WMI
|
||||||
|
netip = conn.return_value.query.return_value[0]
|
||||||
|
if v6error:
|
||||||
|
netip.Create.side_effect = WMIError
|
||||||
|
adapter_addresses = []
|
||||||
|
if v6adapters:
|
||||||
|
adapter_addresses = [
|
||||||
|
{
|
||||||
|
"mac_address": mac_address,
|
||||||
|
"friendly_name": friendly_name,
|
||||||
|
"interface_index": interface_index
|
||||||
|
}
|
||||||
|
]
|
||||||
|
mock_get_adapter_addresses.return_value = adapter_addresses
|
||||||
|
|
||||||
|
set_static_call = functools.partial(
|
||||||
|
self._winutils.set_static_network_config_v6,
|
||||||
|
mac_address, address6, netmask6, gateway6)
|
||||||
|
|
||||||
|
if not v6adapters or v6error:
|
||||||
|
self.assertRaises(
|
||||||
|
exception.CloudbaseInitException,
|
||||||
|
set_static_call)
|
||||||
|
else:
|
||||||
|
set_static_call()
|
||||||
|
mock_get_adapter_addresses.assert_called_once_with()
|
||||||
|
select = ("SELECT * FROM MSFT_NetIPAddress "
|
||||||
|
"WHERE InterfaceAlias = '{}'".format(friendly_name))
|
||||||
|
conn.return_value.query.assert_called_once_with(select)
|
||||||
|
params = {
|
||||||
|
"InterfaceIndex": interface_index,
|
||||||
|
"InterfaceAlias": friendly_name,
|
||||||
|
"IPAddress": address6,
|
||||||
|
"AddressFamily": self.windows_utils.AF_INET6,
|
||||||
|
"PrefixLength": netmask6,
|
||||||
|
# Manual set type.
|
||||||
|
"Type": self.windows_utils.UNICAST,
|
||||||
|
"PrefixOrigin": self.windows_utils.MANUAL,
|
||||||
|
"SuffixOrigin": self.windows_utils.MANUAL,
|
||||||
|
"AddressState": self.windows_utils.PREFERRED_ADDR,
|
||||||
|
# No expiry.
|
||||||
|
"ValidLifetime": None,
|
||||||
|
"PreferredLifetime": None,
|
||||||
|
"SkipAsSource": False,
|
||||||
|
"DefaultGateway": gateway6,
|
||||||
|
"PolicyStore": None,
|
||||||
|
"PassThru": False,
|
||||||
|
}
|
||||||
|
netip.Create.assert_called_once_with(**params)
|
||||||
|
|
||||||
def test_set_static_network_config(self):
|
def test_set_static_network_config(self):
|
||||||
adapter = mock.MagicMock()
|
|
||||||
ret_val1 = (1,)
|
ret_val1 = (1,)
|
||||||
ret_val2 = (1,)
|
ret_val2 = (1,)
|
||||||
ret_val3 = (0,)
|
ret_val3 = (0,)
|
||||||
self._test_set_static_network_config(adapter=adapter,
|
self._test_set_static_network_config(static_val=ret_val1,
|
||||||
ret_val1=ret_val1,
|
gateway_val=ret_val2,
|
||||||
ret_val2=ret_val2,
|
dns_val=ret_val3)
|
||||||
ret_val3=ret_val3)
|
|
||||||
|
|
||||||
def test_set_static_network_config_query_fail(self):
|
def test_set_static_network_config_query_fail(self):
|
||||||
self._test_set_static_network_config(adapter=None)
|
self._test_set_static_network_config(adapter=False)
|
||||||
|
|
||||||
def test_set_static_network_config_cannot_set_ip(self):
|
def test_set_static_network_config_cannot_set_ip(self):
|
||||||
adapter = mock.MagicMock()
|
|
||||||
ret_val1 = (2,)
|
ret_val1 = (2,)
|
||||||
self._test_set_static_network_config(adapter=adapter,
|
self._test_set_static_network_config(static_val=ret_val1)
|
||||||
ret_val1=ret_val1)
|
|
||||||
|
|
||||||
def test_set_static_network_config_cannot_set_gateway(self):
|
def test_set_static_network_config_cannot_set_gateway(self):
|
||||||
adapter = mock.MagicMock()
|
|
||||||
ret_val1 = (1,)
|
ret_val1 = (1,)
|
||||||
ret_val2 = (2,)
|
ret_val2 = (2,)
|
||||||
self._test_set_static_network_config(adapter=adapter,
|
self._test_set_static_network_config(static_val=ret_val1,
|
||||||
ret_val1=ret_val1,
|
gateway_val=ret_val2)
|
||||||
ret_val2=ret_val2)
|
|
||||||
|
|
||||||
def test_set_static_network_config_cannot_set_DNS(self):
|
def test_set_static_network_config_cannot_set_DNS(self):
|
||||||
adapter = mock.MagicMock()
|
|
||||||
ret_val1 = (1,)
|
ret_val1 = (1,)
|
||||||
ret_val2 = (1,)
|
ret_val2 = (1,)
|
||||||
ret_val3 = (2,)
|
ret_val3 = (2,)
|
||||||
self._test_set_static_network_config(adapter=adapter,
|
self._test_set_static_network_config(static_val=ret_val1,
|
||||||
ret_val1=ret_val1,
|
gateway_val=ret_val2,
|
||||||
ret_val2=ret_val2,
|
dns_val=ret_val3)
|
||||||
ret_val3=ret_val3)
|
|
||||||
|
|
||||||
def test_set_static_network_config_no_reboot(self):
|
def test_set_static_network_config_no_reboot(self):
|
||||||
adapter = mock.MagicMock()
|
|
||||||
ret_val1 = (0,)
|
ret_val1 = (0,)
|
||||||
ret_val2 = (0,)
|
ret_val2 = (0,)
|
||||||
ret_val3 = (0,)
|
ret_val3 = (0,)
|
||||||
self._test_set_static_network_config(adapter=adapter,
|
self._test_set_static_network_config(static_val=ret_val1,
|
||||||
ret_val1=ret_val1,
|
gateway_val=ret_val2,
|
||||||
ret_val2=ret_val2,
|
dns_val=ret_val3)
|
||||||
ret_val3=ret_val3)
|
|
||||||
|
def test_set_static_network_config_v6(self):
|
||||||
|
self._test_set_static_network_config_v6()
|
||||||
|
|
||||||
|
def test_set_static_network_config_v6_no_adapters(self):
|
||||||
|
self._test_set_static_network_config_v6(v6adapters=False)
|
||||||
|
|
||||||
|
def test_set_static_network_config_v6_error(self):
|
||||||
|
self._test_set_static_network_config_v6(v6error=True)
|
||||||
|
|
||||||
def _test_get_config_key_name(self, section):
|
def _test_get_config_key_name(self, section):
|
||||||
response = self._winutils._get_config_key_name(section)
|
response = self._winutils._get_config_key_name(section)
|
||||||
|
@@ -39,7 +39,7 @@ class TestNetworkConfigPlugin(unittest.TestCase):
|
|||||||
invalid_details=False,
|
invalid_details=False,
|
||||||
missed_adapters=[],
|
missed_adapters=[],
|
||||||
extra_network_details=[]):
|
extra_network_details=[]):
|
||||||
# prepare mock environment
|
# Prepare mock environment.
|
||||||
mock_service = mock.MagicMock()
|
mock_service = mock.MagicMock()
|
||||||
mock_shared_data = mock.Mock()
|
mock_shared_data = mock.Mock()
|
||||||
mock_osutils = mock.MagicMock()
|
mock_osutils = mock.MagicMock()
|
||||||
@@ -51,22 +51,18 @@ class TestNetworkConfigPlugin(unittest.TestCase):
|
|||||||
self._network_plugin.execute,
|
self._network_plugin.execute,
|
||||||
mock_service, mock_shared_data
|
mock_service, mock_shared_data
|
||||||
)
|
)
|
||||||
# actual tests
|
# Actual tests.
|
||||||
if not network_details:
|
if not network_details:
|
||||||
ret = network_execute()
|
ret = network_execute()
|
||||||
self.assertEqual((plugin_base.PLUGIN_EXECUTION_DONE, False), ret)
|
self.assertEqual((plugin_base.PLUGIN_EXECUTION_DONE, False), ret)
|
||||||
return
|
return
|
||||||
if invalid_details:
|
if invalid_details or not network_adapters:
|
||||||
with self.assertRaises(exception.CloudbaseInitException):
|
with self.assertRaises(exception.CloudbaseInitException):
|
||||||
network_execute()
|
network_execute()
|
||||||
return
|
return
|
||||||
if not network_adapters:
|
# Good to go for the configuration process.
|
||||||
with self.assertRaises(exception.CloudbaseInitException):
|
|
||||||
network_execute()
|
|
||||||
return
|
|
||||||
# good to go for the configuration process
|
|
||||||
ret = network_execute()
|
ret = network_execute()
|
||||||
calls = []
|
calls, calls6 = [], []
|
||||||
for adapter in set(network_adapters) - set(missed_adapters):
|
for adapter in set(network_adapters) - set(missed_adapters):
|
||||||
nics = [nic for nic in (network_details +
|
nics = [nic for nic in (network_details +
|
||||||
extra_network_details)
|
extra_network_details)
|
||||||
@@ -81,9 +77,25 @@ class TestNetworkConfigPlugin(unittest.TestCase):
|
|||||||
nic.gateway,
|
nic.gateway,
|
||||||
nic.dnsnameservers
|
nic.dnsnameservers
|
||||||
)
|
)
|
||||||
|
call6 = mock.call(
|
||||||
|
nic.mac,
|
||||||
|
nic.address6,
|
||||||
|
nic.netmask6,
|
||||||
|
nic.gateway6
|
||||||
|
)
|
||||||
calls.append(call)
|
calls.append(call)
|
||||||
|
if nic.address6 and nic.netmask6:
|
||||||
|
calls6.append(call6)
|
||||||
|
self.assertEqual(
|
||||||
|
len(calls),
|
||||||
|
mock_osutils.set_static_network_config.call_count)
|
||||||
|
self.assertEqual(
|
||||||
|
len(calls6),
|
||||||
|
mock_osutils.set_static_network_config_v6.call_count)
|
||||||
mock_osutils.set_static_network_config.assert_has_calls(
|
mock_osutils.set_static_network_config.assert_has_calls(
|
||||||
calls, any_order=True)
|
calls, any_order=True)
|
||||||
|
mock_osutils.set_static_network_config_v6.assert_has_calls(
|
||||||
|
calls6, any_order=True)
|
||||||
reboot = len(missed_adapters) != self._count
|
reboot = len(missed_adapters) != self._count
|
||||||
self.assertEqual((plugin_base.PLUGIN_EXECUTION_DONE, reboot), ret)
|
self.assertEqual((plugin_base.PLUGIN_EXECUTION_DONE, reboot), ret)
|
||||||
|
|
||||||
@@ -108,11 +120,21 @@ class TestNetworkConfigPlugin(unittest.TestCase):
|
|||||||
"192.168.103.104",
|
"192.168.103.104",
|
||||||
"192.168.122.105",
|
"192.168.122.105",
|
||||||
]
|
]
|
||||||
|
addresses6 = [
|
||||||
|
"::ffff:c0a8:7a65",
|
||||||
|
"::ffff:c0a8:6768",
|
||||||
|
"::ffff:c0a8:7a69"
|
||||||
|
]
|
||||||
netmasks = [
|
netmasks = [
|
||||||
"255.255.255.0",
|
"255.255.255.0",
|
||||||
"255.255.0.0",
|
"255.255.0.0",
|
||||||
"255.255.255.128",
|
"255.255.255.128",
|
||||||
]
|
]
|
||||||
|
netmasks6 = [
|
||||||
|
"96",
|
||||||
|
"64",
|
||||||
|
"100"
|
||||||
|
]
|
||||||
broadcasts = [
|
broadcasts = [
|
||||||
"192.168.122.255",
|
"192.168.122.255",
|
||||||
"192.168.255.255",
|
"192.168.255.255",
|
||||||
@@ -123,6 +145,11 @@ class TestNetworkConfigPlugin(unittest.TestCase):
|
|||||||
"192.168.122.16",
|
"192.168.122.16",
|
||||||
"192.168.122.32",
|
"192.168.122.32",
|
||||||
]
|
]
|
||||||
|
gateways6 = [
|
||||||
|
"::ffff:c0a8:7a01",
|
||||||
|
"::ffff:c0a8:7a10",
|
||||||
|
"::ffff:c0a8:7a20"
|
||||||
|
]
|
||||||
dnsnses = [
|
dnsnses = [
|
||||||
"8.8.8.8",
|
"8.8.8.8",
|
||||||
"8.8.8.8 8.8.4.4",
|
"8.8.8.8 8.8.4.4",
|
||||||
@@ -136,16 +163,19 @@ class TestNetworkConfigPlugin(unittest.TestCase):
|
|||||||
details_names[ind],
|
details_names[ind],
|
||||||
None if no_macs else macs[ind],
|
None if no_macs else macs[ind],
|
||||||
addresses[ind],
|
addresses[ind],
|
||||||
|
addresses6[ind],
|
||||||
netmasks[ind],
|
netmasks[ind],
|
||||||
|
netmasks6[ind],
|
||||||
broadcasts[ind],
|
broadcasts[ind],
|
||||||
gateways[ind],
|
gateways[ind],
|
||||||
|
gateways6[ind],
|
||||||
dnsnses[ind].split()
|
dnsnses[ind].split()
|
||||||
)
|
)
|
||||||
self._network_adapters.append(adapter)
|
self._network_adapters.append(adapter)
|
||||||
self._network_details.append(nic)
|
self._network_details.append(nic)
|
||||||
# get the network config plugin
|
# Get the network config plugin.
|
||||||
self._network_plugin = networkconfig.NetworkConfigPlugin()
|
self._network_plugin = networkconfig.NetworkConfigPlugin()
|
||||||
# execution wrapper
|
# Execution wrapper.
|
||||||
self._partial_test_execute = functools.partial(
|
self._partial_test_execute = functools.partial(
|
||||||
self._test_execute,
|
self._test_execute,
|
||||||
network_adapters=self._network_adapters,
|
network_adapters=self._network_adapters,
|
||||||
@@ -189,36 +219,44 @@ class TestNetworkConfigPlugin(unittest.TestCase):
|
|||||||
nic.name,
|
nic.name,
|
||||||
"00" + nic.mac[2:],
|
"00" + nic.mac[2:],
|
||||||
nic.address,
|
nic.address,
|
||||||
|
nic.address6,
|
||||||
nic.netmask,
|
nic.netmask,
|
||||||
|
nic.netmask6,
|
||||||
nic.broadcast,
|
nic.broadcast,
|
||||||
nic.gateway,
|
nic.gateway,
|
||||||
|
nic.gateway6,
|
||||||
nic.dnsnameservers
|
nic.dnsnameservers
|
||||||
)
|
)
|
||||||
self._network_details[:] = [nic]
|
self._network_details[:] = [nic]
|
||||||
self._partial_test_execute(missed_adapters=self._network_adapters)
|
self._partial_test_execute(missed_adapters=self._network_adapters)
|
||||||
|
|
||||||
def _test_execute_missing_smth(self, name=False, mac=False,
|
def _test_execute_missing_smth(self, name=False, mac=False,
|
||||||
address=False, gateway=False,
|
address=False, address6=False,
|
||||||
fail=False):
|
netmask=False, netmask6=False,
|
||||||
|
gateway=False, fail=False):
|
||||||
ind = self._count - 1
|
ind = self._count - 1
|
||||||
nic = self._network_details[ind]
|
nic = self._network_details[ind]
|
||||||
nic2 = service_base.NetworkDetails(
|
nic2 = service_base.NetworkDetails(
|
||||||
None if name else nic.name,
|
None if name else nic.name,
|
||||||
None if mac else nic.mac,
|
None if mac else nic.mac,
|
||||||
None if address else nic.address,
|
None if address else nic.address,
|
||||||
nic.netmask,
|
None if address6 else nic.address6,
|
||||||
|
None if netmask else nic.netmask,
|
||||||
|
None if netmask6 else nic.netmask6,
|
||||||
nic.broadcast,
|
nic.broadcast,
|
||||||
None if gateway else nic.gateway,
|
None if gateway else nic.gateway,
|
||||||
|
None if gateway else nic.gateway6,
|
||||||
nic.dnsnameservers
|
nic.dnsnameservers
|
||||||
)
|
)
|
||||||
self._network_details[ind] = nic2
|
self._network_details[ind] = nic2
|
||||||
# excluding address and gateway switches...
|
# Excluding address and gateway switches...
|
||||||
if not fail:
|
if not fail:
|
||||||
# even this way, all adapters should be configured
|
# Even this way, all adapters should be configured.
|
||||||
missed_adapters = []
|
missed_adapters = []
|
||||||
extra_network_details = [nic]
|
extra_network_details = [nic]
|
||||||
else:
|
else:
|
||||||
# both name and MAC are missing, so we can't make the match
|
# Both name and MAC are missing, so we can't make the match.
|
||||||
|
# Or other vital details.
|
||||||
missed_adapters = [self._network_adapters[ind]]
|
missed_adapters = [self._network_adapters[ind]]
|
||||||
extra_network_details = []
|
extra_network_details = []
|
||||||
self._partial_test_execute(
|
self._partial_test_execute(
|
||||||
@@ -237,7 +275,20 @@ class TestNetworkConfigPlugin(unittest.TestCase):
|
|||||||
self._test_execute_missing_smth(name=True, mac=True, fail=True)
|
self._test_execute_missing_smth(name=True, mac=True, fail=True)
|
||||||
|
|
||||||
def test_execute_missing_address(self):
|
def test_execute_missing_address(self):
|
||||||
self._test_execute_missing_smth(address=True, fail=True)
|
self._test_execute_missing_smth(address=True)
|
||||||
|
|
||||||
|
def test_execute_missing_netmask(self):
|
||||||
|
self._test_execute_missing_smth(netmask=True)
|
||||||
|
|
||||||
|
def test_execute_missing_address6(self):
|
||||||
|
self._test_execute_missing_smth(address6=True)
|
||||||
|
|
||||||
|
def test_execute_missing_netmask6(self):
|
||||||
|
self._test_execute_missing_smth(netmask6=True)
|
||||||
|
|
||||||
|
def test_execute_missing_address_netmask6(self):
|
||||||
|
self._test_execute_missing_smth(address=True, netmask6=True,
|
||||||
|
fail=True)
|
||||||
|
|
||||||
def test_execute_missing_gateway(self):
|
def test_execute_missing_gateway(self):
|
||||||
self._test_execute_missing_smth(gateway=True)
|
self._test_execute_missing_smth(gateway=True)
|
||||||
|
@@ -37,18 +37,24 @@ class TestInterfacesParser(unittest.TestCase):
|
|||||||
fake_json_response.NAME0,
|
fake_json_response.NAME0,
|
||||||
fake_json_response.MAC0.upper(),
|
fake_json_response.MAC0.upper(),
|
||||||
fake_json_response.ADDRESS0,
|
fake_json_response.ADDRESS0,
|
||||||
|
fake_json_response.ADDRESS60,
|
||||||
fake_json_response.NETMASK0,
|
fake_json_response.NETMASK0,
|
||||||
|
fake_json_response.NETMASK60,
|
||||||
fake_json_response.BROADCAST0,
|
fake_json_response.BROADCAST0,
|
||||||
fake_json_response.GATEWAY0,
|
fake_json_response.GATEWAY0,
|
||||||
|
fake_json_response.GATEWAY60,
|
||||||
fake_json_response.DNSNS0.split()
|
fake_json_response.DNSNS0.split()
|
||||||
)
|
)
|
||||||
nic1 = service_base.NetworkDetails(
|
nic1 = service_base.NetworkDetails(
|
||||||
fake_json_response.NAME1,
|
fake_json_response.NAME1,
|
||||||
None,
|
None,
|
||||||
fake_json_response.ADDRESS1,
|
fake_json_response.ADDRESS1,
|
||||||
|
fake_json_response.ADDRESS61,
|
||||||
fake_json_response.NETMASK1,
|
fake_json_response.NETMASK1,
|
||||||
|
fake_json_response.NETMASK61,
|
||||||
fake_json_response.BROADCAST1,
|
fake_json_response.BROADCAST1,
|
||||||
fake_json_response.GATEWAY1,
|
fake_json_response.GATEWAY1,
|
||||||
|
fake_json_response.GATEWAY61,
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
self.assertEqual([nic0, nic1], nics)
|
self.assertEqual([nic0, nic1], nics)
|
||||||
|
@@ -56,3 +56,25 @@ class NetworkUtilsTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_test_check_metadata_ip_route_fail(self):
|
def test_test_check_metadata_ip_route_fail(self):
|
||||||
self._test_check_metadata_ip_route(side_effect=Exception)
|
self._test_check_metadata_ip_route(side_effect=Exception)
|
||||||
|
|
||||||
|
def test_address6_to_4_truncate(self):
|
||||||
|
address_map = {
|
||||||
|
"0:0:0:0:0:ffff:c0a8:f": "192.168.0.15",
|
||||||
|
"::ffff:c0a8:e": "192.168.0.14",
|
||||||
|
"::1": "0.0.0.1",
|
||||||
|
"1:2:3:4:5::8": "0.0.0.8",
|
||||||
|
"::": "0.0.0.0",
|
||||||
|
"::7f00:1": "127.0.0.1"
|
||||||
|
}
|
||||||
|
for v6, v4 in address_map.items():
|
||||||
|
self.assertEqual(v4, network.address6_to_4_truncate(v6))
|
||||||
|
|
||||||
|
def test_netmask6_to_4_truncate(self):
|
||||||
|
netmask_map = {
|
||||||
|
"128": "255.255.255.255",
|
||||||
|
"96": "255.255.255.0",
|
||||||
|
"0": "0.0.0.0",
|
||||||
|
"100": "255.255.255.128"
|
||||||
|
}
|
||||||
|
for v6, v4 in netmask_map.items():
|
||||||
|
self.assertEqual(v4, network.netmask6_to_4_truncate(v6))
|
||||||
|
@@ -26,75 +26,105 @@ LOG = logging.getLogger(__name__)
|
|||||||
NAME = "name"
|
NAME = "name"
|
||||||
MAC = "mac"
|
MAC = "mac"
|
||||||
ADDRESS = "address"
|
ADDRESS = "address"
|
||||||
|
ADDRESS6 = "address6"
|
||||||
NETMASK = "netmask"
|
NETMASK = "netmask"
|
||||||
|
NETMASK6 = "netmask6"
|
||||||
BROADCAST = "broadcast"
|
BROADCAST = "broadcast"
|
||||||
GATEWAY = "gateway"
|
GATEWAY = "gateway"
|
||||||
|
GATEWAY6 = "gateway6"
|
||||||
DNSNS = "dnsnameservers"
|
DNSNS = "dnsnameservers"
|
||||||
# fields of interest (order and regexp)
|
# Fields of interest by regexps.
|
||||||
FIELDS = {
|
FIELDS = {
|
||||||
NAME: (0, re.compile(r"iface\s+(?P<{}>\S+)"
|
NAME: re.compile(r"iface\s+(?P<{}>\S+)"
|
||||||
r"\s+inet\s+static".format(NAME))),
|
r"\s+inet6?\s+static".format(NAME)),
|
||||||
MAC: (1, re.compile(r"hwaddress\s+ether\s+"
|
MAC: re.compile(r"hwaddress\s+ether\s+"
|
||||||
r"(?P<{}>\S+)".format(MAC))),
|
r"(?P<{}>\S+)".format(MAC)),
|
||||||
ADDRESS: (2, re.compile(r"address\s+"
|
ADDRESS: re.compile(r"address\s+"
|
||||||
r"(?P<{}>\S+)".format(ADDRESS))),
|
r"(?P<{}>\S+)".format(ADDRESS)),
|
||||||
NETMASK: (3, re.compile(r"netmask\s+"
|
ADDRESS6: re.compile(r"post-up ip -6 addr add (?P<{}>[^/]+)/"
|
||||||
r"(?P<{}>\S+)".format(NETMASK))),
|
r"(\d+) dev".format(ADDRESS6)),
|
||||||
BROADCAST: (4, re.compile(r"broadcast\s+"
|
NETMASK: re.compile(r"netmask\s+"
|
||||||
r"(?P<{}>\S+)".format(BROADCAST))),
|
r"(?P<{}>\S+)".format(NETMASK)),
|
||||||
GATEWAY: (5, re.compile(r"gateway\s+"
|
NETMASK6: re.compile(r"post-up ip -6 addr add ([^/]+)/"
|
||||||
r"(?P<{}>\S+)".format(GATEWAY))),
|
r"(?P<{}>\d+) dev".format(NETMASK6)),
|
||||||
DNSNS: (6, re.compile(r"dns-nameservers\s+(?P<{}>.+)".format(DNSNS)))
|
BROADCAST: re.compile(r"broadcast\s+"
|
||||||
|
r"(?P<{}>\S+)".format(BROADCAST)),
|
||||||
|
GATEWAY: re.compile(r"gateway\s+"
|
||||||
|
r"(?P<{}>\S+)".format(GATEWAY)),
|
||||||
|
GATEWAY6: re.compile(r"post-up ip -6 route add default via "
|
||||||
|
r"(?P<{}>.+) dev".format(GATEWAY6)),
|
||||||
|
DNSNS: re.compile(r"dns-nameservers\s+(?P<{}>.+)".format(DNSNS))
|
||||||
}
|
}
|
||||||
IFACE_TEMPLATE = dict.fromkeys(range(len(FIELDS)))
|
IFACE_TEMPLATE = dict.fromkeys(FIELDS.keys())
|
||||||
|
# Map IPv6 availability by value index under `NetworkDetails`.
|
||||||
|
V6_PROXY = {
|
||||||
|
ADDRESS: ADDRESS6,
|
||||||
|
NETMASK: NETMASK6,
|
||||||
|
GATEWAY: GATEWAY6
|
||||||
|
}
|
||||||
|
DETAIL_PREPROCESS = {
|
||||||
|
MAC: lambda value: value.upper(),
|
||||||
|
DNSNS: lambda value: value.strip().split()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _get_iface_blocks(data):
|
||||||
|
""""Yield interface blocks as pairs of v4 and v6 halves."""
|
||||||
|
lines, lines6 = [], []
|
||||||
|
crt_lines = lines
|
||||||
|
for line in data.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
if "iface" in line:
|
||||||
|
if "inet6" in line:
|
||||||
|
crt_lines = lines6
|
||||||
|
continue
|
||||||
|
if lines:
|
||||||
|
yield lines, lines6
|
||||||
|
lines[:] = []
|
||||||
|
lines6[:] = []
|
||||||
|
crt_lines = lines
|
||||||
|
crt_lines.append(line)
|
||||||
|
if lines:
|
||||||
|
yield lines, lines6
|
||||||
|
|
||||||
|
|
||||||
def _get_field(line):
|
def _get_field(line):
|
||||||
for field, (index, regex) in FIELDS.items():
|
for field, regex in FIELDS.items():
|
||||||
match = regex.match(line)
|
match = regex.match(line)
|
||||||
if match:
|
if match:
|
||||||
return index, match.group(field)
|
yield field, match.group(field)
|
||||||
|
|
||||||
|
|
||||||
def _add_nic(iface, nics):
|
def _add_nic(iface, nics):
|
||||||
if not iface:
|
if not iface or iface == IFACE_TEMPLATE:
|
||||||
return
|
return # no information gathered
|
||||||
details = [iface[key] for key in sorted(iface)]
|
LOG.debug("Found new interface: %s", iface)
|
||||||
LOG.debug("Found new interface: %s", details)
|
# Each missing detail is marked as None.
|
||||||
# each missing detail is marked as None
|
nic = service_base.NetworkDetails(**iface)
|
||||||
nic = service_base.NetworkDetails(*details)
|
|
||||||
nics.append(nic)
|
nics.append(nic)
|
||||||
|
|
||||||
|
|
||||||
def parse(data):
|
def parse(data):
|
||||||
"""Parse the received content and obtain network details."""
|
"""Parse the received content and obtain network details."""
|
||||||
# TODO(cpoieana): support IPv6 flavors
|
|
||||||
if not data or not isinstance(data, six.string_types):
|
if not data or not isinstance(data, six.string_types):
|
||||||
LOG.error("Invalid debian config to parse:\n%s", data)
|
LOG.error("Invalid Debian config to parse:\n%s", data)
|
||||||
return
|
return
|
||||||
LOG.info("Parsing debian config...\n%s", data)
|
|
||||||
|
LOG.info("Parsing Debian config...\n%s", data)
|
||||||
nics = [] # list of NetworkDetails objects
|
nics = [] # list of NetworkDetails objects
|
||||||
iface = {}
|
for lines_pair in _get_iface_blocks(data):
|
||||||
# take each line and process it
|
iface = IFACE_TEMPLATE.copy()
|
||||||
for line in data.split("\n"):
|
for lines, use_proxy in zip(lines_pair, (False, True)):
|
||||||
line = line.strip()
|
for line in lines:
|
||||||
if not line or line.startswith("#"):
|
for field, value in _get_field(line):
|
||||||
continue
|
if use_proxy:
|
||||||
ret = _get_field(line)
|
field = V6_PROXY.get(field)
|
||||||
if not ret:
|
if not field:
|
||||||
continue
|
continue
|
||||||
# save the detail
|
func = DETAIL_PREPROCESS.get(field, lambda value: value)
|
||||||
index = ret[0]
|
iface[field] = func(value) if value != "None" else None
|
||||||
if index == 0:
|
_add_nic(iface, nics)
|
||||||
# we found a new interface
|
|
||||||
_add_nic(iface, nics)
|
|
||||||
iface = IFACE_TEMPLATE.copy()
|
|
||||||
value = ret[1]
|
|
||||||
if index == 1:
|
|
||||||
value = value.upper()
|
|
||||||
elif index == 6:
|
|
||||||
value = value.strip().split()
|
|
||||||
iface[index] = value
|
|
||||||
# also add the last one
|
|
||||||
_add_nic(iface, nics)
|
|
||||||
return nics
|
return nics
|
||||||
|
@@ -12,6 +12,10 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
|
||||||
|
import binascii
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from six.moves.urllib import parse
|
from six.moves.urllib import parse
|
||||||
@@ -62,3 +66,20 @@ def check_metadata_ip_route(metadata_url):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
# Ignore it
|
# Ignore it
|
||||||
LOG.exception(ex)
|
LOG.exception(ex)
|
||||||
|
|
||||||
|
|
||||||
|
def address6_to_4_truncate(address6):
|
||||||
|
"""Try to obtain IPv4 address from version 6."""
|
||||||
|
chunks = address6.split(":")
|
||||||
|
hi, lo = chunks[-2], chunks[-1]
|
||||||
|
network_address = binascii.unhexlify(hi.zfill(4) + lo.zfill(4))
|
||||||
|
return socket.inet_ntoa(network_address)
|
||||||
|
|
||||||
|
|
||||||
|
def netmask6_to_4_truncate(netmask6):
|
||||||
|
"""Try to obtain IPv4 netmask from version 6."""
|
||||||
|
# Harsh 128bit to 32bit.
|
||||||
|
length = int(int(netmask6) / 4)
|
||||||
|
mask = "1" * length + "0" * (32 - length)
|
||||||
|
network_address = struct.pack("!L", int(mask, 2))
|
||||||
|
return socket.inet_ntoa(network_address)
|
||||||
|
Reference in New Issue
Block a user