Remove deprecated static network config method

Refactor all the services in such way that they support
the new `get_network_details` method instead of the old
`get_network_config` method for statically configuring
network interfaces through the networkconfig plugin.

Change-Id: Idb519935da0d3aee316ad9bc9451f74bc80b12ba
This commit is contained in:
Cosmin Poieana 2014-12-01 20:03:26 +02:00
parent b0aa3b56b8
commit 623e76997d
12 changed files with 681 additions and 305 deletions

View File

@ -16,7 +16,6 @@
import abc
import collections
import time
import warnings
from oslo.config import cfg
@ -42,6 +41,7 @@ LOG = logging.getLogger(__name__)
NetworkDetails = collections.namedtuple(
"NetworkDetails",
[
"name",
"mac",
"address",
"netmask",
@ -98,7 +98,7 @@ class BaseMetadataService(object):
pass
def get_content(self, name):
pass
"""Get raw content within a service."""
def get_user_data(self):
pass
@ -109,11 +109,6 @@ class BaseMetadataService(object):
def get_public_keys(self):
pass
def get_network_config(self):
"""Deprecated, use `get_network_details` instead."""
warnings.warn("deprecated method, use `get_network_details`",
DeprecationWarning)
def get_network_details(self):
"""Return a list of `NetworkDetails` objects.

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
import posixpath
@ -19,9 +20,11 @@ from oslo.config import cfg
from cloudbaseinit.metadata.services import base
from cloudbaseinit.openstack.common import log as logging
from cloudbaseinit.utils import debiface
from cloudbaseinit.utils import encoding
from cloudbaseinit.utils import x509constants
opts = [
cfg.StrOpt('metadata_base_url', default='http://169.254.169.254/',
help='The base URL where the service looks for metadata'),
@ -63,8 +66,19 @@ class BaseOpenStackService(base.BaseMetadataService):
if public_keys:
return public_keys.values()
def get_network_config(self):
return self._get_meta_data().get('network_config')
def get_network_details(self):
network_config = self._get_meta_data().get('network_config')
if not network_config:
return None
key = "content_path"
if key not in network_config:
return None
content_name = network_config[key].rsplit("/", 1)[-1]
content = self.get_content(content_name)
content = encoding.get_as_string(content)
return debiface.parse(content)
def get_admin_password(self):
meta_data = self._get_meta_data()

View File

@ -101,6 +101,6 @@ class EC2Service(base.BaseMetadataService):
return ssh_keys
def get_network_config(self):
# TODO(alexpilotti): add static network support
def get_network_details(self):
# TODO(cpoieana): add static network config support
pass

View File

@ -31,6 +31,8 @@ LOG = logging.getLogger(__name__)
CONTEXT_FILE = "context.sh"
INSTANCE_ID = "iid-dsopennebula"
# interface name default template
IF_FORMAT = "eth{iid}"
# metadata identifiers
HOST_NAME = ["SET_HOSTNAME", "HOSTNAME"]
@ -211,15 +213,21 @@ class OpenNebulaService(base.BaseMetadataService):
# get existing values
mac = self._get_cache_data(MAC, iid=iid).upper()
address = self._get_cache_data(ADDRESS, iid=iid)
gateway = self._get_cache_data(GATEWAY, iid=iid)
# try to find/predict and compute the rest
try:
gateway = self._get_cache_data(GATEWAY, iid=iid)
except base.NotExistingMetadataException:
gateway = None
try:
netmask = self._get_cache_data(NETMASK, iid=iid)
except base.NotExistingMetadataException:
if not gateway:
raise
netmask = self._calculate_netmask(address, gateway)
broadcast = self._compute_broadcast(address, netmask)
# gather them as namedtuple objects
details = base.NetworkDetails(
IF_FORMAT.format(iid=iid),
mac,
address,
netmask,

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import ctypes
from ctypes import wintypes
import os
@ -541,19 +542,21 @@ class WindowsUtils(base.BaseOSUtils):
"Cannot set static IP address on network adapter")
reboot_required = (ret_val == 1)
LOG.debug("Setting static gateways")
(ret_val,) = adapter_config.SetGateways([gateway], [1])
if ret_val > 1:
raise exception.CloudbaseInitException(
"Cannot set gateway on network adapter")
reboot_required = reboot_required or ret_val == 1
if gateway:
LOG.debug("Setting static gateways")
(ret_val,) = adapter_config.SetGateways([gateway], [1])
if ret_val > 1:
raise exception.CloudbaseInitException(
"Cannot set gateway on network adapter")
reboot_required = reboot_required or ret_val == 1
LOG.debug("Setting static DNS servers")
(ret_val,) = adapter_config.SetDNSServerSearchOrder(dnsnameservers)
if ret_val > 1:
raise exception.CloudbaseInitException(
"Cannot set DNS on network adapter")
reboot_required = reboot_required or ret_val == 1
if dnsnameservers:
LOG.debug("Setting static DNS servers")
(ret_val,) = adapter_config.SetDNSServerSearchOrder(dnsnameservers)
if ret_val > 1:
raise exception.CloudbaseInitException(
"Cannot set DNS on network adapter")
reboot_required = reboot_required or ret_val == 1
return reboot_required

View File

@ -13,111 +13,102 @@
# under the License.
import re
from oslo.config import cfg
from cloudbaseinit import exception
from cloudbaseinit.metadata.services import base as service_base
from cloudbaseinit.openstack.common import log as logging
from cloudbaseinit.osutils import factory as osutils_factory
from cloudbaseinit.plugins import base as plugin_base
from cloudbaseinit.utils import encoding
LOG = logging.getLogger(__name__)
opts = [
cfg.StrOpt('network_adapter', default=None, help='Network adapter to '
'configure. If not specified, the first available ethernet '
'adapter will be chosen.'),
]
# Mandatory network details are marked with True. And
# if the key is a tuple, then at least one field must exists.
NET_REQUIRE = {
("name", "mac"): True,
"address": True,
"netmask": True,
"broadcast": False, # currently not used
"gateway": False,
"dnsnameservers": False
}
CONF = cfg.CONF
CONF.register_opts(opts)
def _preprocess_nics(network_details, network_adapters):
"""Check NICs and fill missing data if possible."""
# initial checks
if not network_adapters:
raise exception.CloudbaseInitException(
"no network adapters available")
# Sort VM adapters by name (assuming that those
# from the context are in correct order).
network_adapters = sorted(network_adapters, key=lambda arg: arg[0])
_network_details = [] # store here processed data
# check and update every NetworkDetails object
ind = 0
total = len(network_adapters)
for nic in network_details:
if not isinstance(nic, service_base.NetworkDetails):
raise exception.CloudbaseInitException(
"invalid NetworkDetails object {!r}"
.format(type(nic))
)
# check requirements
final_status = True
for fields, status in NET_REQUIRE.items():
if not status:
continue # skip 'not required' entries
if not isinstance(fields, tuple):
fields = (fields,)
final_status = any([getattr(nic, field) for field in fields])
if not final_status:
LOG.error("Incomplete NetworkDetails object %s", nic)
break
if final_status:
# Complete hardware address if missing by selecting
# the corresponding MAC in terms of naming, then ordering.
if not nic.mac:
mac = None
# by name
macs = [adapter[1] for adapter in network_adapters
if adapter[0] == nic.name]
mac = macs[0] if macs else None
# or by order
if not mac and ind < total:
mac = network_adapters[ind][1]
nic = service_base.NetworkDetails(
nic.name,
mac,
nic.address,
nic.netmask,
nic.broadcast,
nic.gateway,
nic.dnsnameservers
)
_network_details.append(nic)
ind += 1
return _network_details
class NetworkConfigPlugin(plugin_base.BasePlugin):
def execute(self, service, shared_data):
# FIXME(cpoieana): `network_config` is deprecated
# * refactor all services by providing NetworkDetails objects *
# Also, the old method is not supporting multiple NICs.
osutils = osutils_factory.get_os_utils()
network_details = service.get_network_details()
if not network_details:
network_config = service.get_network_config()
if not network_config:
return (plugin_base.PLUGIN_EXECUTION_DONE, False)
return (plugin_base.PLUGIN_EXECUTION_DONE, False)
# ---- BEGIN deprecated code ----
if not network_details:
if 'content_path' not in network_config:
return (plugin_base.PLUGIN_EXECUTION_DONE, False)
content_path = network_config['content_path']
content_name = content_path.rsplit('/', 1)[-1]
debian_network_conf = service.get_content(content_name)
debian_network_conf = encoding.get_as_string(debian_network_conf)
LOG.debug('network config content:\n%s' % debian_network_conf)
# TODO(alexpilotti): implement a proper grammar
m = re.search(r'iface eth0 inet static\s+'
r'address\s+(?P<address>[^\s]+)\s+'
r'netmask\s+(?P<netmask>[^\s]+)\s+'
r'broadcast\s+(?P<broadcast>[^\s]+)\s+'
r'gateway\s+(?P<gateway>[^\s]+)\s+'
r'dns\-nameservers\s+'
r'(?P<dnsnameservers>[^\r\n]+)\s+',
debian_network_conf)
if not m:
raise exception.CloudbaseInitException(
"network_config format not recognized")
mac = None
network_adapters = osutils.get_network_adapters()
if network_adapters:
adapter_name = CONF.network_adapter
if adapter_name:
# configure with the specified one
for network_adapter in network_adapters:
if network_adapter[0] == adapter_name:
mac = network_adapter[1]
break
else:
# configure with the first one
mac = network_adapters[0][1]
network_details = [
service_base.NetworkDetails(
mac,
m.group('address'),
m.group('netmask'),
m.group('broadcast'),
m.group('gateway'),
m.group('dnsnameservers').strip().split(' ')
)
]
# ---- END deprecated code ----
# check NICs' type and save them by MAC
# check and save NICs by MAC
network_adapters = osutils.get_network_adapters()
network_details = _preprocess_nics(network_details,
network_adapters)
macnics = {}
for nic in network_details:
if not isinstance(nic, service_base.NetworkDetails):
raise exception.CloudbaseInitException(
"invalid NetworkDetails object {!r}"
.format(type(nic))
)
# assuming that the MAC address is unique
macnics[nic.mac] = nic
# try configuring all the available adapters
adapter_macs = [pair[1] for pair in
osutils.get_network_adapters()]
if not adapter_macs:
raise exception.CloudbaseInitException(
"no network adapters available")
# configure each one
adapter_macs = [pair[1] for pair in network_adapters]
reboot_required = False
configured = False
for mac in adapter_macs:

View File

@ -1,5 +1,3 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Cloudbase Solutions Srl
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -15,43 +13,103 @@
# under the License.
NAME0 = "eth0"
MAC0 = "fa:16:3e:2d:ec:cd"
ADDRESS0 = "10.0.0.15"
NETMASK0 = "255.255.255.0"
BROADCAST0 = "10.0.0.255"
GATEWAY0 = "10.0.0.1"
DNSNS0 = "208.67.220.220 208.67.222.222"
NAME1 = "eth1"
ADDRESS1 = "10.1.0.2"
NETMASK1 = "255.255.255.0"
BROADCAST1 = "10.1.0.255"
GATEWAY1 = "10.1.0.1"
def get_fake_metadata_json(version):
if version == '2013-04-04':
return {"random_seed":
"Wn51FGjZa3vlZtTxJuPr96oCf+X8jqbA9U2XR5wNdnApy1fz"
"/2NNssUwPoNzG6etw9RBn+XiZ0zKWnFzMsTopaN7WwYjWTnIsVw3cpIk"
"Td579wQgoEr1ANqhfO3qTvkOVNMhzTAw1ps+wqRmkLxH+1qYJnX06Gcd"
"KRRGkWTaOSlTkieA0LO2oTGFlbFDWcOW2vT5BvSBmqP7vNLzbLDMTc7M"
"IWRBzwmtcVPC17QL6EhZJTUcZ0mTz7l0R0DocLmFwHEXFEEr+q4WaJjt"
"1ejOOxVM3tiT7D8YpRZnnGNPfvEhq1yVMUoi8yv9pFmMmXicNBhm6zDK"
"VjcWk0gfbvaQcMnnOLrrE1VxAAzyNyPIXBI/H7AAHz2ECz7dgd2/4ocv"
"3bmTRY3hhcUKtNuat2IOvSGgMBUGdWnLorQGFz8t0/bcYhE0Dve35U6H"
"mtj78ydV/wmQWG0iq49NX6hk+VUmZtSZztlkbsaa7ajNjZ+Md9oZtlhX"
"Z5vJuhRXnHiCm7dRNO8Xo6HffEBH5A4smQ1T2Kda+1c18DZrY7+iQJRi"
"fa6witPCw0tXkQ6nlCLqL2weJD1XMiTZLSM/XsZFGGSkKCKvKLEqQrI/"
"XFUq/TA6B4aLGFlmmhOO/vMJcht06O8qVU/xtd5Mv/MRFzYaSG568Z/m"
"hk4vYLYdQYAA+pXRW9A=",
"uuid": "4b32ddf7-7941-4c36-a854-a1f5ac45b318",
"availability_zone": "nova",
"hostname": "windows.novalocal",
"launch_index": 0,
"public_keys": {"key": "ssh-rsa "
"AAAAB3NzaC1yc2EAAAADAQABAAABA"
"QDf7kQHq7zvBod3yIZs0tB/AOOZz5pab7qt/h"
"78VF7yi6qTsFdUnQxRue43R/75wa9EEyokgYR"
"LKIN+Jq2A5tXNMcK+rNOCzLJFtioAwEl+S6VL"
"G9jfkbUv++7zoSMOsanNmEDvG0B79MpyECFCl"
"th2DsdE4MQypify35U5ri5Qi7E6PEYAsU65LF"
"MG2boeCIB29BEooE6AgPr2DuJeJ+2uw+YScF9"
"FV3og4Wyz5zipPVh8YpVev6dlg0tRWUrCtZF9"
"IODpCTrT3vsPRG3xz7CppR+vGi/1gLXHtJCRj"
"frHwkY6cXyhypNmkU99K/wMqSv30vsDwdnsQ1"
"q3YhLarMHB Generated by Nova\n",
0: "windows"},
"network_config": {"content_path": "network",
'debian_config': 'iface eth0 inet static'
'address 10.11.12.13'
'broadcast 0.0.0.0'
'netmask 255.255.255.255'
'gateway 1.2.3.4'
'dns-nameserver 8.8.8.8'}}
data1 = {
"random_seed": "Wn51FGjZa3vlZtTxJuPr96oCf+X8jqbA9U2XR5wNdnApy1fz"
"/2NNssUwPoNzG6etw9RBn+XiZ0zKWnFzMsTopaN7WwYjWTnI"
"sVw3cpIkTd579wQgoEr1ANqhfO3qTvkOVNMhzTAw1ps+wqRm"
"kLxH+1qYJnX06GcdKRRGkWTaOSlTkieA0LO2oTGFlbFDWcOW"
"2vT5BvSBmqP7vNLzbLDMTc7MIWRBzwmtcVPC17QL6EhZJTUc"
"Z0mTz7l0R0DocLmFwHEXFEEr+q4WaJjt1ejOOxVM3tiT7D8Y"
"pRZnnGNPfvEhq1yVMUoi8yv9pFmMmXicNBhm6zDKVjcWk0gf"
"bvaQcMnnOLrrE1VxAAzyNyPIXBI/H7AAHz2ECz7dgd2/4ocv"
"3bmTRY3hhcUKtNuat2IOvSGgMBUGdWnLorQGFz8t0/bcYhE0"
"Dve35U6Hmtj78ydV/wmQWG0iq49NX6hk+VUmZtSZztlkbsaa"
"7ajNjZ+Md9oZtlhXZ5vJuhRXnHiCm7dRNO8Xo6HffEBH5A4s"
"mQ1T2Kda+1c18DZrY7+iQJRifa6witPCw0tXkQ6nlCLqL2we"
"JD1XMiTZLSM/XsZFGGSkKCKvKLEqQrI/XFUq/TA6B4aLGFlm"
"mhOO/vMJcht06O8qVU/xtd5Mv/MRFzYaSG568Z/mhk4vYLYd"
"QYAA+pXRW9A=",
"uuid": "4b32ddf7-7941-4c36-a854-a1f5ac45b318",
"availability_zone": "nova",
"hostname": "windows.novalocal",
"launch_index": 0,
"public_keys": {
"key":
"ssh-rsa "
"AAAAB3NzaC1yc2EAAAADAQABAAABA"
"QDf7kQHq7zvBod3yIZs0tB/AOOZz5pab7qt/h"
"78VF7yi6qTsFdUnQxRue43R/75wa9EEyokgYR"
"LKIN+Jq2A5tXNMcK+rNOCzLJFtioAwEl+S6VL"
"G9jfkbUv++7zoSMOsanNmEDvG0B79MpyECFCl"
"th2DsdE4MQypify35U5ri5Qi7E6PEYAsU65LF"
"MG2boeCIB29BEooE6AgPr2DuJeJ+2uw+YScF9"
"FV3og4Wyz5zipPVh8YpVev6dlg0tRWUrCtZF9"
"IODpCTrT3vsPRG3xz7CppR+vGi/1gLXHtJCRj"
"frHwkY6cXyhypNmkU99K/wMqSv30vsDwdnsQ1"
"q3YhLarMHB Generated by Nova\n",
0: "windows"
},
"network_config": {
"content_path": "network",
"debian_config": ("""
# Injected by Nova on instance boot
#
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
# The loopback network interface
auto lo
iface lo inet loopback
auto {name0}
iface {name0} inet static
hwaddress ether {mac0}
address {address0}
netmask {netmask0}
broadcast {broadcast0}
gateway {gateway0}
dns-nameservers {dnsns0}
auto {name1}
iface {name1} inet static
address {address1}
netmask {netmask1}
broadcast {broadcast1}
gateway {gateway1}
""").format(name0=NAME0, # eth0 (IPv4)
mac0=MAC0,
address0=ADDRESS0,
broadcast0=BROADCAST0,
netmask0=NETMASK0,
gateway0=GATEWAY0,
dnsns0=DNSNS0,
# eth1 (IPv4)
name1=NAME1,
address1=ADDRESS1,
broadcast1=BROADCAST1,
netmask1=NETMASK1,
gateway1=GATEWAY1)
}
}
datadict = {
"2013-04-04": data1
}
return datadict.get(version)

View File

@ -1,5 +1,3 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2014 Cloudbase Solutions Srl
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -14,24 +12,41 @@
# License for the specific language governing permissions and limitations
# under the License.
import mock
import functools
import posixpath
import unittest
import mock
from oslo.config import cfg
from cloudbaseinit.metadata.services import base
from cloudbaseinit.metadata.services import baseopenstackservice
from cloudbaseinit.tests.metadata import fake_json_response
from cloudbaseinit.utils import x509constants
from oslo.config import cfg
CONF = cfg.CONF
MODPATH = "cloudbaseinit.metadata.services.baseopenstackservice"
class TestBaseOpenStackService(unittest.TestCase):
class BaseOpenStackServiceTest(unittest.TestCase):
def setUp(self):
CONF.set_override('retry_count_interval', 0)
CONF.set_override("retry_count_interval", 0)
self._service = baseopenstackservice.BaseOpenStackService()
date = "2013-04-04"
fake_metadata = fake_json_response.get_fake_metadata_json(date)
self._fake_network_config = fake_metadata["network_config"]
self._fake_content = self._fake_network_config["debian_config"]
self._partial_test_get_network_details = functools.partial(
self._test_get_network_details,
network_config=self._fake_network_config,
content=self._fake_content
)
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice"
@mock.patch(MODPATH +
".BaseOpenStackService._get_cache_data")
def test_get_content(self, mock_get_cache_data):
response = self._service.get_content('fake name')
@ -39,7 +54,7 @@ class BaseOpenStackServiceTest(unittest.TestCase):
mock_get_cache_data.assert_called_once_with(path)
self.assertEqual(mock_get_cache_data.return_value, response)
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice"
@mock.patch(MODPATH +
".BaseOpenStackService._get_cache_data")
def test_get_user_data(self, mock_get_cache_data):
response = self._service.get_user_data()
@ -47,7 +62,7 @@ class BaseOpenStackServiceTest(unittest.TestCase):
mock_get_cache_data.assert_called_once_with(path)
self.assertEqual(mock_get_cache_data.return_value, response)
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice"
@mock.patch(MODPATH +
".BaseOpenStackService._get_cache_data")
def test_get_meta_data(self, mock_get_cache_data):
mock_get_cache_data.return_value = b'{"fake": "data"}'
@ -57,7 +72,7 @@ class BaseOpenStackServiceTest(unittest.TestCase):
mock_get_cache_data.assert_called_with(path)
self.assertEqual({"fake": "data"}, response)
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice"
@mock.patch(MODPATH +
".BaseOpenStackService._get_meta_data")
def test_get_instance_id(self, mock_get_meta_data):
response = self._service.get_instance_id()
@ -66,7 +81,7 @@ class BaseOpenStackServiceTest(unittest.TestCase):
self.assertEqual(mock_get_meta_data.return_value.get.return_value,
response)
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice"
@mock.patch(MODPATH +
".BaseOpenStackService._get_meta_data")
def test_get_host_name(self, mock_get_meta_data):
response = self._service.get_host_name()
@ -75,7 +90,7 @@ class BaseOpenStackServiceTest(unittest.TestCase):
self.assertEqual(mock_get_meta_data.return_value.get.return_value,
response)
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice"
@mock.patch(MODPATH +
".BaseOpenStackService._get_meta_data")
def test_get_public_keys(self, mock_get_meta_data):
response = self._service.get_public_keys()
@ -83,15 +98,7 @@ class BaseOpenStackServiceTest(unittest.TestCase):
mock_get_meta_data().get.assert_called_once_with('public_keys')
self.assertEqual(mock_get_meta_data().get().values(), response)
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice"
".BaseOpenStackService._get_meta_data")
def test_get_network_config(self, mock_get_meta_data):
response = self._service.get_network_config()
mock_get_meta_data.assert_called_once_with()
mock_get_meta_data().get.assert_called_once_with('network_config')
self.assertEqual(response, mock_get_meta_data().get())
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice"
@mock.patch(MODPATH +
".BaseOpenStackService._get_meta_data")
def _test_get_admin_password(self, mock_get_meta_data, meta_data):
mock_get_meta_data.return_value = meta_data
@ -102,7 +109,7 @@ class BaseOpenStackServiceTest(unittest.TestCase):
elif meta_data and 'admin_pass' in meta_data.get('meta'):
self.assertEqual(meta_data.get('meta')['admin_pass'], response)
else:
self.assertEqual(None, response)
self.assertIsNone(response)
def test_get_admin_pass(self):
self._test_get_admin_password(meta_data={'admin_pass': 'fake pass'})
@ -114,9 +121,9 @@ class BaseOpenStackServiceTest(unittest.TestCase):
def test_get_admin_pass_no_pass(self):
self._test_get_admin_password(meta_data={})
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice"
@mock.patch(MODPATH +
".BaseOpenStackService._get_meta_data")
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice"
@mock.patch(MODPATH +
".BaseOpenStackService.get_user_data")
def _test_get_client_auth_certs(self, mock_get_user_data,
mock_get_meta_data, meta_data,
@ -132,7 +139,7 @@ class BaseOpenStackServiceTest(unittest.TestCase):
mock_get_user_data.assert_called_once_with()
self.assertEqual([ret_value], response)
elif ret_value is base.NotExistingMetadataException:
self.assertEqual(None, response)
self.assertIsNone(response)
def test_get_client_auth_certs(self):
self._test_get_client_auth_certs(
@ -145,3 +152,75 @@ class BaseOpenStackServiceTest(unittest.TestCase):
def test_get_client_auth_certs_no_cert_data_exception(self):
self._test_get_client_auth_certs(
meta_data={}, ret_value=base.NotExistingMetadataException)
@mock.patch(MODPATH +
".BaseOpenStackService.get_content")
@mock.patch(MODPATH +
".BaseOpenStackService._get_meta_data")
def _test_get_network_details(self,
mock_get_meta_data,
mock_get_content,
network_config=None,
content=None,
search_fail=False,
no_path=False):
# mock obtained data
mock_get_meta_data().get.return_value = network_config
mock_get_content.return_value = content
# actual tests
if search_fail:
ret = self._service.get_network_details()
self.assertFalse(ret)
return
ret = self._service.get_network_details()
mock_get_meta_data().get.assert_called_once_with("network_config")
if network_config and not no_path:
mock_get_content.assert_called_once_with("network")
if not network_config:
self.assertIsNone(ret)
return
if no_path:
self.assertIsNone(ret)
return
# check returned NICs details
nic1 = base.NetworkDetails(
fake_json_response.NAME0,
fake_json_response.MAC0.upper(),
fake_json_response.ADDRESS0,
fake_json_response.NETMASK0,
fake_json_response.BROADCAST0,
fake_json_response.GATEWAY0,
fake_json_response.DNSNS0.split()
)
nic2 = base.NetworkDetails(
fake_json_response.NAME1,
None,
fake_json_response.ADDRESS1,
fake_json_response.NETMASK1,
fake_json_response.BROADCAST1,
fake_json_response.GATEWAY1,
None
)
self.assertEqual([nic1, nic2], ret)
def test_get_network_details_no_config(self):
self._partial_test_get_network_details(
network_config=None
)
def test_get_network_details_no_path(self):
self._fake_network_config.pop("content_path", None)
self._partial_test_get_network_details(
network_config=self._fake_network_config,
no_path=True
)
def test_get_network_details_search_fail(self):
self._fake_content = "invalid format"
self._partial_test_get_network_details(
content=self._fake_content,
search_fail=True
)
def test_get_network_details(self):
self._partial_test_get_network_details()

View File

@ -97,8 +97,9 @@ ETH1_MAC='{mac}'
)
def _get_nic_details():
def _get_nic_details(iid=0):
details = base.NetworkDetails(
opennebulaservice.IF_FORMAT.format(iid=iid),
MAC,
ADDRESS,
NETMASK,
@ -302,8 +303,9 @@ class TestLoadedOpenNebulaService(_TestOpenNebulaService):
def test_multiple_nics(self):
self.load_context(context=CONTEXT2)
details = _get_nic_details()
network_details = [details] * 2
nic1 = _get_nic_details(iid=0)
nic2 = _get_nic_details(iid=1)
network_details = [nic1, nic2]
self.assertEqual(
network_details,
self._service.get_network_details()

View File

@ -13,163 +13,226 @@
# under the License.
import re
import functools
import unittest
import mock
from oslo.config import cfg
from cloudbaseinit import exception
from cloudbaseinit.metadata.services import base as service_base
from cloudbaseinit.plugins import base as plugin_base
from cloudbaseinit.plugins.windows import networkconfig
from cloudbaseinit.tests.metadata import fake_json_response
CONF = cfg.CONF
class TestNetworkConfigPlugin(unittest.TestCase):
def setUp(self):
self._network_plugin = networkconfig.NetworkConfigPlugin()
self.fake_data = fake_json_response.get_fake_metadata_json(
'2013-04-04')
self._setUp()
@mock.patch('cloudbaseinit.osutils.factory.get_os_utils')
@mock.patch("cloudbaseinit.osutils.factory.get_os_utils")
def _test_execute(self, mock_get_os_utils,
search_result=mock.MagicMock(),
no_adapter_name=False, no_adapters=False,
using_content=0, details_list=None,
missing_content_path=False):
fake_adapter = ("fake_name_0", "fake_mac_0")
network_adapters=None,
network_details=None,
invalid_details=False,
missed_adapters=[],
extra_network_details=[]):
# prepare mock environment
mock_service = mock.MagicMock()
mock_shared_data = mock.Mock()
mock_osutils = mock.MagicMock()
mock_ndetails = mock.Mock()
re.search = mock.MagicMock(return_value=search_result)
fake_shared_data = 'fake shared data'
network_config = self.fake_data['network_config']
if not details_list:
details_list = [None] * 6
details_list[0] = fake_adapter[1] # set MAC for matching
if no_adapter_name: # nothing provided in the config file
CONF.set_override("network_adapter", None)
else:
CONF.set_override("network_adapter", fake_adapter[0])
mock_osutils.get_network_adapters.return_value = [
fake_adapter,
# and other adapters
("name1", "mac1"),
("name2", "mac2")
]
mock_service.get_network_details.return_value = network_details
mock_get_os_utils.return_value = mock_osutils
mock_osutils.set_static_network_config.return_value = False
# service method setup
methods = ["get_network_config", "get_content", "get_network_details"]
for method in methods:
mock_method = getattr(mock_service, method)
mock_method.return_value = None
if using_content == 1:
mock_service.get_network_config.return_value = network_config
mock_service.get_content.return_value = search_result
elif using_content == 2:
mock_service.get_network_details.return_value = [mock_ndetails]
mock_osutils.get_network_adapters.return_value = network_adapters
mock_osutils.set_static_network_config.return_value = True
network_execute = functools.partial(
self._network_plugin.execute,
mock_service, mock_shared_data
)
# actual tests
if search_result is None and using_content == 1:
self.assertRaises(exception.CloudbaseInitException,
self._network_plugin.execute,
mock_service, fake_shared_data)
if not network_details:
ret = network_execute()
self.assertEqual((plugin_base.PLUGIN_EXECUTION_DONE, False), ret)
return
if no_adapters:
mock_osutils.get_network_adapters.return_value = []
self.assertRaises(exception.CloudbaseInitException,
self._network_plugin.execute,
mock_service, fake_shared_data)
return
attrs = [
"address",
"netmask",
"broadcast",
"gateway",
"dnsnameservers",
]
if using_content == 0:
response = self._network_plugin.execute(mock_service,
fake_shared_data)
elif using_content == 1:
if missing_content_path:
mock_service.get_network_config.return_value.pop(
"content_path", None
)
response = self._network_plugin.execute(mock_service,
fake_shared_data)
if not missing_content_path:
mock_service.get_network_config.assert_called_once_with()
mock_service.get_content.assert_called_once_with(
network_config['content_path'])
adapters = mock_osutils.get_network_adapters()
if CONF.network_adapter:
mac = [pair[1] for pair in adapters
if pair == fake_adapter][0]
else:
mac = adapters[0][1]
(
address,
netmask,
broadcast,
gateway,
dnsnameserver
) = map(search_result.group, attrs)
dnsnameservers = dnsnameserver.strip().split(" ")
elif using_content == 2:
if invalid_details:
with self.assertRaises(exception.CloudbaseInitException):
self._network_plugin.execute(mock_service,
fake_shared_data)
mock_service.get_network_details.reset_mock()
mock_ndetails = service_base.NetworkDetails(*details_list)
mock_service.get_network_details.return_value = [mock_ndetails]
response = self._network_plugin.execute(mock_service,
fake_shared_data)
mock_service.get_network_details.assert_called_once_with()
mac = mock_ndetails.mac
(
address,
netmask,
broadcast,
gateway,
dnsnameservers
) = map(lambda attr: getattr(mock_ndetails, attr), attrs)
if using_content in (1, 2) and not missing_content_path:
mock_osutils.set_static_network_config.assert_called_once_with(
mac,
address,
netmask,
broadcast,
gateway,
dnsnameservers
network_execute()
return
if not network_adapters:
with self.assertRaises(exception.CloudbaseInitException):
network_execute()
return
# good to go for the configuration process
ret = network_execute()
calls = []
for adapter in set(network_adapters) - set(missed_adapters):
nics = [nic for nic in (network_details +
extra_network_details)
if nic.mac == adapter[1]]
self.assertTrue(nics) # missed_adapters should do the job
nic = nics[0]
call = mock.call(
nic.mac,
nic.address,
nic.netmask,
nic.broadcast,
nic.gateway,
nic.dnsnameservers
)
self.assertEqual((plugin_base.PLUGIN_EXECUTION_DONE, False),
response)
calls.append(call)
mock_osutils.set_static_network_config.assert_has_calls(
calls, any_order=True)
reboot = len(missed_adapters) != self._count
self.assertEqual((plugin_base.PLUGIN_EXECUTION_DONE, reboot), ret)
def test_execute(self):
self._test_execute(using_content=1)
def _setUp(self, same_names=True):
# Generate fake pairs of NetworkDetails objects and
# local ethernet network adapters.
self._count = 3
details_names = [
"eth0",
"eth1",
"eth2"
]
if same_names:
adapters_names = details_names[:]
else:
adapters_names = ["vm " + name for name in details_names]
macs = [
"54:EE:75:19:F4:61",
"54:EE:75:19:F4:62",
"54:EE:75:19:F4:63"
]
addresses = [
"192.168.122.101",
"192.168.103.104",
"192.168.122.105",
]
netmasks = [
"255.255.255.0",
"255.255.0.0",
"255.255.255.128",
]
broadcasts = [
"192.168.122.255",
"192.168.255.255",
"192.168.122.127",
]
gateway = [
"192.168.122.1",
"192.168.122.16",
"192.168.122.32",
]
dnsnses = [
"8.8.8.8",
"8.8.8.8 8.8.4.4",
"8.8.8.8 0.0.0.0",
]
self._network_adapters = []
self._network_details = []
for ind in range(self._count):
adapter = (adapters_names[ind], macs[ind])
nic = service_base.NetworkDetails(
details_names[ind],
macs[ind],
addresses[ind],
netmasks[ind],
broadcasts[ind],
gateway[ind],
dnsnses[ind].split()
)
self._network_adapters.append(adapter)
self._network_details.append(nic)
# get the network config plugin
self._network_plugin = networkconfig.NetworkConfigPlugin()
# execution wrapper
self._partial_test_execute = functools.partial(
self._test_execute,
network_adapters=self._network_adapters,
network_details=self._network_details
)
def test_execute_missing_content_path(self):
self._test_execute(using_content=1, missing_content_path=True)
def test_execute_no_network_details(self):
self._network_details[:] = []
self._partial_test_execute()
def test_execute_no_debian(self):
self._test_execute(search_result=None, using_content=1)
def test_execute_no_network_adapters(self):
self._network_adapters[:] = []
self._partial_test_execute()
def test_execute_no_adapter_name(self):
self._test_execute(no_adapter_name=True, using_content=1)
def test_execute_invalid_network_details(self):
self._network_details.append([None] * 6)
self._partial_test_execute(invalid_details=True)
def test_execute_no_adapter_name_or_adapters(self):
self._test_execute(no_adapter_name=True, no_adapters=True,
using_content=1)
def test_execute_single(self):
for _ in range(self._count - 1):
self._network_adapters.pop()
self._network_details.pop()
self._partial_test_execute()
def test_execute_network_details(self):
self._test_execute(using_content=2)
def test_execute_multiple(self):
self._partial_test_execute()
def test_execute_no_config_or_details(self):
self._test_execute(using_content=0)
def test_execute_missing_one(self):
self.assertGreater(self._count, 1)
self._network_details.pop(0)
adapter = self._network_adapters[0]
self._partial_test_execute(missed_adapters=[adapter])
def test_execute_missing_all(self):
nic = self._network_details[0]
nic = service_base.NetworkDetails(
nic.name,
"00" + nic.mac[2:],
nic.address,
nic.netmask,
nic.broadcast,
nic.gateway,
nic.dnsnameservers
)
self._network_details[:] = [nic]
self._partial_test_execute(missed_adapters=self._network_adapters)
def _test_execute_missing_smth(self, name=False, mac=False,
address=False, gateway=False,
fail=False):
ind = self._count - 1
nic = self._network_details[ind]
nic2 = service_base.NetworkDetails(
None if name else nic.name,
None if mac else nic.mac,
None if address else nic.address,
nic.netmask,
nic.broadcast,
None if gateway else nic.gateway,
nic.dnsnameservers
)
self._network_details[ind] = nic2
# excluding address and gateway switches...
if not fail:
# even this way, all adapters should be configured
missed_adapters = []
extra_network_details = [nic]
else:
# both name and MAC are missing, so we can't make the match
missed_adapters = [self._network_adapters[ind]]
extra_network_details = []
self._partial_test_execute(
missed_adapters=missed_adapters,
extra_network_details=extra_network_details
)
def test_execute_missing_mac(self):
self._test_execute_missing_smth(mac=True)
def test_execute_missing_mac2(self):
self._setUp(same_names=False)
self._test_execute_missing_smth(mac=True)
def test_execute_missing_name_mac(self):
self._test_execute_missing_smth(name=True, mac=True, fail=True)
def test_execute_missing_address(self):
self._test_execute_missing_smth(address=True, fail=True)
def test_execute_missing_gateway(self):
self._test_execute_missing_smth(gateway=True)

View File

@ -0,0 +1,63 @@
# Copyright 2014 Cloudbase Solutions Srl
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import unittest
from cloudbaseinit.metadata.services import base as service_base
from cloudbaseinit.tests.metadata import fake_json_response
from cloudbaseinit.utils import debiface
class TestInterfacesParser(unittest.TestCase):
def setUp(self):
date = "2013-04-04"
content = fake_json_response.get_fake_metadata_json(date)
self.data = content["network_config"]["debian_config"]
def _test_parse_nics(self, no_nics=False):
nics = debiface.parse(self.data)
if no_nics:
self.assertFalse(nics)
return
# check what we've got
nic0 = service_base.NetworkDetails(
fake_json_response.NAME0,
fake_json_response.MAC0.upper(),
fake_json_response.ADDRESS0,
fake_json_response.NETMASK0,
fake_json_response.BROADCAST0,
fake_json_response.GATEWAY0,
fake_json_response.DNSNS0.split()
)
nic1 = service_base.NetworkDetails(
fake_json_response.NAME1,
None,
fake_json_response.ADDRESS1,
fake_json_response.NETMASK1,
fake_json_response.BROADCAST1,
fake_json_response.GATEWAY1,
None
)
self.assertEqual([nic0, nic1], nics)
def test_nothing_to_parse(self):
invalid = [None, "", 324242, ("dasd", "dsa")]
for data in invalid:
self.data = data
self._test_parse_nics(no_nics=True)
def test_parse(self):
self._test_parse_nics()

View File

@ -0,0 +1,100 @@
# Copyright 2014 Cloudbase Solutions Srl
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import re
import six
from cloudbaseinit.metadata.services import base as service_base
from cloudbaseinit.openstack.common import log as logging
LOG = logging.getLogger(__name__)
NAME = "name"
MAC = "mac"
ADDRESS = "address"
NETMASK = "netmask"
BROADCAST = "broadcast"
GATEWAY = "gateway"
DNSNS = "dnsnameservers"
# fields of interest (order and regexp)
FIELDS = {
NAME: (0, re.compile(r"iface\s+(?P<{}>\S+)"
r"\s+inet\s+static".format(NAME))),
MAC: (1, re.compile(r"hwaddress\s+ether\s+"
r"(?P<{}>\S+)".format(MAC))),
ADDRESS: (2, re.compile(r"address\s+"
r"(?P<{}>\S+)".format(ADDRESS))),
NETMASK: (3, re.compile(r"netmask\s+"
r"(?P<{}>\S+)".format(NETMASK))),
BROADCAST: (4, re.compile(r"broadcast\s+"
r"(?P<{}>\S+)".format(BROADCAST))),
GATEWAY: (5, re.compile(r"gateway\s+"
r"(?P<{}>\S+)".format(GATEWAY))),
DNSNS: (6, re.compile(r"dns-nameservers\s+(?P<{}>.+)".format(DNSNS)))
}
IFACE_TEMPLATE = dict.fromkeys(range(len(FIELDS)))
def _get_field(line):
for field, (index, regex) in FIELDS.items():
match = regex.match(line)
if match:
return index, match.group(field)
def _add_nic(iface, nics):
if not iface:
return
details = [iface[key] for key in sorted(iface)]
LOG.debug("Found new interface: %s", details)
# each missing detail is marked as None
nic = service_base.NetworkDetails(*details)
nics.append(nic)
def parse(data):
"""Parse the received content and obtain network details."""
# TODO(cpoieana): support IPv6 flavors
if not data or not isinstance(data, six.string_types):
LOG.error("Invalid debian config to parse:\n%s", data)
return
LOG.info("Parsing debian config...\n%s", data)
nics = [] # list of NetworkDetails objects
iface = {}
# take each line and process it
for line in data.split("\n"):
line = line.strip()
if not line or line.startswith("#"):
continue
ret = _get_field(line)
if not ret:
continue
# save the detail
index = ret[0]
if index == 0:
# 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