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 abc
import collections import collections
import time import time
import warnings
from oslo.config import cfg from oslo.config import cfg
@ -42,6 +41,7 @@ LOG = logging.getLogger(__name__)
NetworkDetails = collections.namedtuple( NetworkDetails = collections.namedtuple(
"NetworkDetails", "NetworkDetails",
[ [
"name",
"mac", "mac",
"address", "address",
"netmask", "netmask",
@ -98,7 +98,7 @@ class BaseMetadataService(object):
pass pass
def get_content(self, name): def get_content(self, name):
pass """Get raw content within a service."""
def get_user_data(self): def get_user_data(self):
pass pass
@ -109,11 +109,6 @@ class BaseMetadataService(object):
def get_public_keys(self): def get_public_keys(self):
pass 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): def get_network_details(self):
"""Return a list of `NetworkDetails` objects. """Return a list of `NetworkDetails` objects.

View File

@ -12,6 +12,7 @@
# 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 json import json
import posixpath import posixpath
@ -19,9 +20,11 @@ from oslo.config import cfg
from cloudbaseinit.metadata.services import base from cloudbaseinit.metadata.services import base
from cloudbaseinit.openstack.common import log as logging from cloudbaseinit.openstack.common import log as logging
from cloudbaseinit.utils import debiface
from cloudbaseinit.utils import encoding from cloudbaseinit.utils import encoding
from cloudbaseinit.utils import x509constants from cloudbaseinit.utils import x509constants
opts = [ opts = [
cfg.StrOpt('metadata_base_url', default='http://169.254.169.254/', cfg.StrOpt('metadata_base_url', default='http://169.254.169.254/',
help='The base URL where the service looks for metadata'), help='The base URL where the service looks for metadata'),
@ -63,8 +66,19 @@ class BaseOpenStackService(base.BaseMetadataService):
if public_keys: if public_keys:
return public_keys.values() return public_keys.values()
def get_network_config(self): def get_network_details(self):
return self._get_meta_data().get('network_config') 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): def get_admin_password(self):
meta_data = self._get_meta_data() meta_data = self._get_meta_data()

View File

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

View File

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

View File

@ -12,6 +12,7 @@
# 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 ctypes import ctypes
from ctypes import wintypes from ctypes import wintypes
import os import os
@ -541,19 +542,21 @@ class WindowsUtils(base.BaseOSUtils):
"Cannot set static IP address on network adapter") "Cannot set static IP address on network adapter")
reboot_required = (ret_val == 1) reboot_required = (ret_val == 1)
LOG.debug("Setting static gateways") if gateway:
(ret_val,) = adapter_config.SetGateways([gateway], [1]) LOG.debug("Setting static gateways")
if ret_val > 1: (ret_val,) = adapter_config.SetGateways([gateway], [1])
raise exception.CloudbaseInitException( if ret_val > 1:
"Cannot set gateway on network adapter") raise exception.CloudbaseInitException(
reboot_required = reboot_required or ret_val == 1 "Cannot set gateway on network adapter")
reboot_required = reboot_required or ret_val == 1
LOG.debug("Setting static DNS servers") if dnsnameservers:
(ret_val,) = adapter_config.SetDNSServerSearchOrder(dnsnameservers) LOG.debug("Setting static DNS servers")
if ret_val > 1: (ret_val,) = adapter_config.SetDNSServerSearchOrder(dnsnameservers)
raise exception.CloudbaseInitException( if ret_val > 1:
"Cannot set DNS on network adapter") raise exception.CloudbaseInitException(
reboot_required = reboot_required or ret_val == 1 "Cannot set DNS on network adapter")
reboot_required = reboot_required or ret_val == 1
return reboot_required return reboot_required

View File

@ -13,111 +13,102 @@
# under the License. # under the License.
import re
from oslo.config import cfg
from cloudbaseinit import exception from cloudbaseinit import exception
from cloudbaseinit.metadata.services import base as service_base 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 import base as plugin_base from cloudbaseinit.plugins import base as plugin_base
from cloudbaseinit.utils import encoding
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
opts = [ # Mandatory network details are marked with True. And
cfg.StrOpt('network_adapter', default=None, help='Network adapter to ' # if the key is a tuple, then at least one field must exists.
'configure. If not specified, the first available ethernet ' NET_REQUIRE = {
'adapter will be chosen.'), ("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): class NetworkConfigPlugin(plugin_base.BasePlugin):
def execute(self, service, shared_data): 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() 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:
network_config = service.get_network_config() return (plugin_base.PLUGIN_EXECUTION_DONE, False)
if not network_config:
return (plugin_base.PLUGIN_EXECUTION_DONE, False)
# ---- BEGIN deprecated code ---- # check and save NICs by MAC
if not network_details: network_adapters = osutils.get_network_adapters()
if 'content_path' not in network_config: network_details = _preprocess_nics(network_details,
return (plugin_base.PLUGIN_EXECUTION_DONE, False) network_adapters)
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
macnics = {} macnics = {}
for nic in network_details: 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 # 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 adapter_macs = [pair[1] for pair in network_adapters]
osutils.get_network_adapters()]
if not adapter_macs:
raise exception.CloudbaseInitException(
"no network adapters available")
# configure each one
reboot_required = False reboot_required = False
configured = False configured = False
for mac in adapter_macs: for mac in adapter_macs:

View File

@ -1,5 +1,3 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 Cloudbase Solutions Srl # Copyright 2013 Cloudbase Solutions Srl
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -15,43 +13,103 @@
# under the License. # 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): def get_fake_metadata_json(version):
if version == '2013-04-04': data1 = {
return {"random_seed": "random_seed": "Wn51FGjZa3vlZtTxJuPr96oCf+X8jqbA9U2XR5wNdnApy1fz"
"Wn51FGjZa3vlZtTxJuPr96oCf+X8jqbA9U2XR5wNdnApy1fz" "/2NNssUwPoNzG6etw9RBn+XiZ0zKWnFzMsTopaN7WwYjWTnI"
"/2NNssUwPoNzG6etw9RBn+XiZ0zKWnFzMsTopaN7WwYjWTnIsVw3cpIk" "sVw3cpIkTd579wQgoEr1ANqhfO3qTvkOVNMhzTAw1ps+wqRm"
"Td579wQgoEr1ANqhfO3qTvkOVNMhzTAw1ps+wqRmkLxH+1qYJnX06Gcd" "kLxH+1qYJnX06GcdKRRGkWTaOSlTkieA0LO2oTGFlbFDWcOW"
"KRRGkWTaOSlTkieA0LO2oTGFlbFDWcOW2vT5BvSBmqP7vNLzbLDMTc7M" "2vT5BvSBmqP7vNLzbLDMTc7MIWRBzwmtcVPC17QL6EhZJTUc"
"IWRBzwmtcVPC17QL6EhZJTUcZ0mTz7l0R0DocLmFwHEXFEEr+q4WaJjt" "Z0mTz7l0R0DocLmFwHEXFEEr+q4WaJjt1ejOOxVM3tiT7D8Y"
"1ejOOxVM3tiT7D8YpRZnnGNPfvEhq1yVMUoi8yv9pFmMmXicNBhm6zDK" "pRZnnGNPfvEhq1yVMUoi8yv9pFmMmXicNBhm6zDKVjcWk0gf"
"VjcWk0gfbvaQcMnnOLrrE1VxAAzyNyPIXBI/H7AAHz2ECz7dgd2/4ocv" "bvaQcMnnOLrrE1VxAAzyNyPIXBI/H7AAHz2ECz7dgd2/4ocv"
"3bmTRY3hhcUKtNuat2IOvSGgMBUGdWnLorQGFz8t0/bcYhE0Dve35U6H" "3bmTRY3hhcUKtNuat2IOvSGgMBUGdWnLorQGFz8t0/bcYhE0"
"mtj78ydV/wmQWG0iq49NX6hk+VUmZtSZztlkbsaa7ajNjZ+Md9oZtlhX" "Dve35U6Hmtj78ydV/wmQWG0iq49NX6hk+VUmZtSZztlkbsaa"
"Z5vJuhRXnHiCm7dRNO8Xo6HffEBH5A4smQ1T2Kda+1c18DZrY7+iQJRi" "7ajNjZ+Md9oZtlhXZ5vJuhRXnHiCm7dRNO8Xo6HffEBH5A4s"
"fa6witPCw0tXkQ6nlCLqL2weJD1XMiTZLSM/XsZFGGSkKCKvKLEqQrI/" "mQ1T2Kda+1c18DZrY7+iQJRifa6witPCw0tXkQ6nlCLqL2we"
"XFUq/TA6B4aLGFlmmhOO/vMJcht06O8qVU/xtd5Mv/MRFzYaSG568Z/m" "JD1XMiTZLSM/XsZFGGSkKCKvKLEqQrI/XFUq/TA6B4aLGFlm"
"hk4vYLYdQYAA+pXRW9A=", "mhOO/vMJcht06O8qVU/xtd5Mv/MRFzYaSG568Z/mhk4vYLYd"
"uuid": "4b32ddf7-7941-4c36-a854-a1f5ac45b318", "QYAA+pXRW9A=",
"availability_zone": "nova", "uuid": "4b32ddf7-7941-4c36-a854-a1f5ac45b318",
"hostname": "windows.novalocal", "availability_zone": "nova",
"launch_index": 0, "hostname": "windows.novalocal",
"public_keys": {"key": "ssh-rsa " "launch_index": 0,
"AAAAB3NzaC1yc2EAAAADAQABAAABA" "public_keys": {
"QDf7kQHq7zvBod3yIZs0tB/AOOZz5pab7qt/h" "key":
"78VF7yi6qTsFdUnQxRue43R/75wa9EEyokgYR" "ssh-rsa "
"LKIN+Jq2A5tXNMcK+rNOCzLJFtioAwEl+S6VL" "AAAAB3NzaC1yc2EAAAADAQABAAABA"
"G9jfkbUv++7zoSMOsanNmEDvG0B79MpyECFCl" "QDf7kQHq7zvBod3yIZs0tB/AOOZz5pab7qt/h"
"th2DsdE4MQypify35U5ri5Qi7E6PEYAsU65LF" "78VF7yi6qTsFdUnQxRue43R/75wa9EEyokgYR"
"MG2boeCIB29BEooE6AgPr2DuJeJ+2uw+YScF9" "LKIN+Jq2A5tXNMcK+rNOCzLJFtioAwEl+S6VL"
"FV3og4Wyz5zipPVh8YpVev6dlg0tRWUrCtZF9" "G9jfkbUv++7zoSMOsanNmEDvG0B79MpyECFCl"
"IODpCTrT3vsPRG3xz7CppR+vGi/1gLXHtJCRj" "th2DsdE4MQypify35U5ri5Qi7E6PEYAsU65LF"
"frHwkY6cXyhypNmkU99K/wMqSv30vsDwdnsQ1" "MG2boeCIB29BEooE6AgPr2DuJeJ+2uw+YScF9"
"q3YhLarMHB Generated by Nova\n", "FV3og4Wyz5zipPVh8YpVev6dlg0tRWUrCtZF9"
0: "windows"}, "IODpCTrT3vsPRG3xz7CppR+vGi/1gLXHtJCRj"
"network_config": {"content_path": "network", "frHwkY6cXyhypNmkU99K/wMqSv30vsDwdnsQ1"
'debian_config': 'iface eth0 inet static' "q3YhLarMHB Generated by Nova\n",
'address 10.11.12.13' 0: "windows"
'broadcast 0.0.0.0' },
'netmask 255.255.255.255' "network_config": {
'gateway 1.2.3.4' "content_path": "network",
'dns-nameserver 8.8.8.8'}} "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 # Copyright 2014 Cloudbase Solutions Srl
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # License for the specific language governing permissions and limitations
# under the License. # under the License.
import mock
import functools
import posixpath import posixpath
import unittest import unittest
import mock
from oslo.config import cfg
from cloudbaseinit.metadata.services import base from cloudbaseinit.metadata.services import base
from cloudbaseinit.metadata.services import baseopenstackservice from cloudbaseinit.metadata.services import baseopenstackservice
from cloudbaseinit.tests.metadata import fake_json_response
from cloudbaseinit.utils import x509constants from cloudbaseinit.utils import x509constants
from oslo.config import cfg
CONF = cfg.CONF CONF = cfg.CONF
MODPATH = "cloudbaseinit.metadata.services.baseopenstackservice"
class TestBaseOpenStackService(unittest.TestCase):
class BaseOpenStackServiceTest(unittest.TestCase):
def setUp(self): def setUp(self):
CONF.set_override('retry_count_interval', 0) CONF.set_override("retry_count_interval", 0)
self._service = baseopenstackservice.BaseOpenStackService() 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") ".BaseOpenStackService._get_cache_data")
def test_get_content(self, mock_get_cache_data): def test_get_content(self, mock_get_cache_data):
response = self._service.get_content('fake name') response = self._service.get_content('fake name')
@ -39,7 +54,7 @@ class BaseOpenStackServiceTest(unittest.TestCase):
mock_get_cache_data.assert_called_once_with(path) mock_get_cache_data.assert_called_once_with(path)
self.assertEqual(mock_get_cache_data.return_value, response) self.assertEqual(mock_get_cache_data.return_value, response)
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice" @mock.patch(MODPATH +
".BaseOpenStackService._get_cache_data") ".BaseOpenStackService._get_cache_data")
def test_get_user_data(self, mock_get_cache_data): def test_get_user_data(self, mock_get_cache_data):
response = self._service.get_user_data() response = self._service.get_user_data()
@ -47,7 +62,7 @@ class BaseOpenStackServiceTest(unittest.TestCase):
mock_get_cache_data.assert_called_once_with(path) mock_get_cache_data.assert_called_once_with(path)
self.assertEqual(mock_get_cache_data.return_value, response) self.assertEqual(mock_get_cache_data.return_value, response)
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice" @mock.patch(MODPATH +
".BaseOpenStackService._get_cache_data") ".BaseOpenStackService._get_cache_data")
def test_get_meta_data(self, mock_get_cache_data): def test_get_meta_data(self, mock_get_cache_data):
mock_get_cache_data.return_value = b'{"fake": "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) mock_get_cache_data.assert_called_with(path)
self.assertEqual({"fake": "data"}, response) self.assertEqual({"fake": "data"}, response)
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice" @mock.patch(MODPATH +
".BaseOpenStackService._get_meta_data") ".BaseOpenStackService._get_meta_data")
def test_get_instance_id(self, mock_get_meta_data): def test_get_instance_id(self, mock_get_meta_data):
response = self._service.get_instance_id() 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, self.assertEqual(mock_get_meta_data.return_value.get.return_value,
response) response)
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice" @mock.patch(MODPATH +
".BaseOpenStackService._get_meta_data") ".BaseOpenStackService._get_meta_data")
def test_get_host_name(self, mock_get_meta_data): def test_get_host_name(self, mock_get_meta_data):
response = self._service.get_host_name() 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, self.assertEqual(mock_get_meta_data.return_value.get.return_value,
response) response)
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice" @mock.patch(MODPATH +
".BaseOpenStackService._get_meta_data") ".BaseOpenStackService._get_meta_data")
def test_get_public_keys(self, mock_get_meta_data): def test_get_public_keys(self, mock_get_meta_data):
response = self._service.get_public_keys() 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') mock_get_meta_data().get.assert_called_once_with('public_keys')
self.assertEqual(mock_get_meta_data().get().values(), response) self.assertEqual(mock_get_meta_data().get().values(), response)
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice" @mock.patch(MODPATH +
".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"
".BaseOpenStackService._get_meta_data") ".BaseOpenStackService._get_meta_data")
def _test_get_admin_password(self, mock_get_meta_data, meta_data): def _test_get_admin_password(self, mock_get_meta_data, meta_data):
mock_get_meta_data.return_value = 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'): elif meta_data and 'admin_pass' in meta_data.get('meta'):
self.assertEqual(meta_data.get('meta')['admin_pass'], response) self.assertEqual(meta_data.get('meta')['admin_pass'], response)
else: else:
self.assertEqual(None, response) self.assertIsNone(response)
def test_get_admin_pass(self): def test_get_admin_pass(self):
self._test_get_admin_password(meta_data={'admin_pass': 'fake pass'}) 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): def test_get_admin_pass_no_pass(self):
self._test_get_admin_password(meta_data={}) self._test_get_admin_password(meta_data={})
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice" @mock.patch(MODPATH +
".BaseOpenStackService._get_meta_data") ".BaseOpenStackService._get_meta_data")
@mock.patch("cloudbaseinit.metadata.services.baseopenstackservice" @mock.patch(MODPATH +
".BaseOpenStackService.get_user_data") ".BaseOpenStackService.get_user_data")
def _test_get_client_auth_certs(self, mock_get_user_data, def _test_get_client_auth_certs(self, mock_get_user_data,
mock_get_meta_data, meta_data, mock_get_meta_data, meta_data,
@ -132,7 +139,7 @@ class BaseOpenStackServiceTest(unittest.TestCase):
mock_get_user_data.assert_called_once_with() mock_get_user_data.assert_called_once_with()
self.assertEqual([ret_value], response) self.assertEqual([ret_value], response)
elif ret_value is base.NotExistingMetadataException: elif ret_value is base.NotExistingMetadataException:
self.assertEqual(None, response) self.assertIsNone(response)
def test_get_client_auth_certs(self): def test_get_client_auth_certs(self):
self._test_get_client_auth_certs( 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): def test_get_client_auth_certs_no_cert_data_exception(self):
self._test_get_client_auth_certs( self._test_get_client_auth_certs(
meta_data={}, ret_value=base.NotExistingMetadataException) 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( details = base.NetworkDetails(
opennebulaservice.IF_FORMAT.format(iid=iid),
MAC, MAC,
ADDRESS, ADDRESS,
NETMASK, NETMASK,
@ -302,8 +303,9 @@ class TestLoadedOpenNebulaService(_TestOpenNebulaService):
def test_multiple_nics(self): def test_multiple_nics(self):
self.load_context(context=CONTEXT2) self.load_context(context=CONTEXT2)
details = _get_nic_details() nic1 = _get_nic_details(iid=0)
network_details = [details] * 2 nic2 = _get_nic_details(iid=1)
network_details = [nic1, nic2]
self.assertEqual( self.assertEqual(
network_details, network_details,
self._service.get_network_details() self._service.get_network_details()

View File

@ -13,163 +13,226 @@
# under the License. # under the License.
import re import functools
import unittest import unittest
import mock import mock
from oslo.config import cfg
from cloudbaseinit import exception from cloudbaseinit import exception
from cloudbaseinit.metadata.services import base as service_base from cloudbaseinit.metadata.services import base as service_base
from cloudbaseinit.plugins import base as plugin_base from cloudbaseinit.plugins import base as plugin_base
from cloudbaseinit.plugins.windows import networkconfig from cloudbaseinit.plugins.windows import networkconfig
from cloudbaseinit.tests.metadata import fake_json_response
CONF = cfg.CONF
class TestNetworkConfigPlugin(unittest.TestCase): class TestNetworkConfigPlugin(unittest.TestCase):
def setUp(self): def setUp(self):
self._network_plugin = networkconfig.NetworkConfigPlugin() self._setUp()
self.fake_data = fake_json_response.get_fake_metadata_json(
'2013-04-04')
@mock.patch('cloudbaseinit.osutils.factory.get_os_utils') @mock.patch("cloudbaseinit.osutils.factory.get_os_utils")
def _test_execute(self, mock_get_os_utils, def _test_execute(self, mock_get_os_utils,
search_result=mock.MagicMock(), network_adapters=None,
no_adapter_name=False, no_adapters=False, network_details=None,
using_content=0, details_list=None, invalid_details=False,
missing_content_path=False): missed_adapters=[],
fake_adapter = ("fake_name_0", "fake_mac_0") extra_network_details=[]):
# prepare mock environment
mock_service = mock.MagicMock() mock_service = mock.MagicMock()
mock_shared_data = mock.Mock()
mock_osutils = mock.MagicMock() mock_osutils = mock.MagicMock()
mock_ndetails = mock.Mock() mock_service.get_network_details.return_value = network_details
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_get_os_utils.return_value = mock_osutils mock_get_os_utils.return_value = mock_osutils
mock_osutils.set_static_network_config.return_value = False mock_osutils.get_network_adapters.return_value = network_adapters
# service method setup mock_osutils.set_static_network_config.return_value = True
methods = ["get_network_config", "get_content", "get_network_details"] network_execute = functools.partial(
for method in methods: self._network_plugin.execute,
mock_method = getattr(mock_service, method) mock_service, mock_shared_data
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]
# actual tests # actual tests
if search_result is None and using_content == 1: if not network_details:
self.assertRaises(exception.CloudbaseInitException, ret = network_execute()
self._network_plugin.execute, self.assertEqual((plugin_base.PLUGIN_EXECUTION_DONE, False), ret)
mock_service, fake_shared_data)
return return
if no_adapters: if invalid_details:
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:
with self.assertRaises(exception.CloudbaseInitException): with self.assertRaises(exception.CloudbaseInitException):
self._network_plugin.execute(mock_service, network_execute()
fake_shared_data) return
mock_service.get_network_details.reset_mock() if not network_adapters:
mock_ndetails = service_base.NetworkDetails(*details_list) with self.assertRaises(exception.CloudbaseInitException):
mock_service.get_network_details.return_value = [mock_ndetails] network_execute()
response = self._network_plugin.execute(mock_service, return
fake_shared_data) # good to go for the configuration process
mock_service.get_network_details.assert_called_once_with() ret = network_execute()
mac = mock_ndetails.mac calls = []
( for adapter in set(network_adapters) - set(missed_adapters):
address, nics = [nic for nic in (network_details +
netmask, extra_network_details)
broadcast, if nic.mac == adapter[1]]
gateway, self.assertTrue(nics) # missed_adapters should do the job
dnsnameservers nic = nics[0]
) = map(lambda attr: getattr(mock_ndetails, attr), attrs) call = mock.call(
if using_content in (1, 2) and not missing_content_path: nic.mac,
mock_osutils.set_static_network_config.assert_called_once_with( nic.address,
mac, nic.netmask,
address, nic.broadcast,
netmask, nic.gateway,
broadcast, nic.dnsnameservers
gateway,
dnsnameservers
) )
self.assertEqual((plugin_base.PLUGIN_EXECUTION_DONE, False), calls.append(call)
response) 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): def _setUp(self, same_names=True):
self._test_execute(using_content=1) # 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): def test_execute_no_network_details(self):
self._test_execute(using_content=1, missing_content_path=True) self._network_details[:] = []
self._partial_test_execute()
def test_execute_no_debian(self): def test_execute_no_network_adapters(self):
self._test_execute(search_result=None, using_content=1) self._network_adapters[:] = []
self._partial_test_execute()
def test_execute_no_adapter_name(self): def test_execute_invalid_network_details(self):
self._test_execute(no_adapter_name=True, using_content=1) self._network_details.append([None] * 6)
self._partial_test_execute(invalid_details=True)
def test_execute_no_adapter_name_or_adapters(self): def test_execute_single(self):
self._test_execute(no_adapter_name=True, no_adapters=True, for _ in range(self._count - 1):
using_content=1) self._network_adapters.pop()
self._network_details.pop()
self._partial_test_execute()
def test_execute_network_details(self): def test_execute_multiple(self):
self._test_execute(using_content=2) self._partial_test_execute()
def test_execute_no_config_or_details(self): def test_execute_missing_one(self):
self._test_execute(using_content=0) 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