diff --git a/cloudbaseinit/metadata/services/base.py b/cloudbaseinit/metadata/services/base.py
index f704f446..ac4508e4 100644
--- a/cloudbaseinit/metadata/services/base.py
+++ b/cloudbaseinit/metadata/services/base.py
@@ -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.
diff --git a/cloudbaseinit/metadata/services/baseopenstackservice.py b/cloudbaseinit/metadata/services/baseopenstackservice.py
index 7973df0b..3b183337 100644
--- a/cloudbaseinit/metadata/services/baseopenstackservice.py
+++ b/cloudbaseinit/metadata/services/baseopenstackservice.py
@@ -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()
diff --git a/cloudbaseinit/metadata/services/ec2service.py b/cloudbaseinit/metadata/services/ec2service.py
index 6b8ace82..7165a7ab 100644
--- a/cloudbaseinit/metadata/services/ec2service.py
+++ b/cloudbaseinit/metadata/services/ec2service.py
@@ -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
diff --git a/cloudbaseinit/metadata/services/opennebulaservice.py b/cloudbaseinit/metadata/services/opennebulaservice.py
index 0985fd3d..6a71a34f 100644
--- a/cloudbaseinit/metadata/services/opennebulaservice.py
+++ b/cloudbaseinit/metadata/services/opennebulaservice.py
@@ -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,
diff --git a/cloudbaseinit/osutils/windows.py b/cloudbaseinit/osutils/windows.py
index 5eccf575..59f41d2b 100644
--- a/cloudbaseinit/osutils/windows.py
+++ b/cloudbaseinit/osutils/windows.py
@@ -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
diff --git a/cloudbaseinit/plugins/windows/networkconfig.py b/cloudbaseinit/plugins/windows/networkconfig.py
index 1d9d60cd..88036f1f 100644
--- a/cloudbaseinit/plugins/windows/networkconfig.py
+++ b/cloudbaseinit/plugins/windows/networkconfig.py
@@ -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
[^\s]+)\s+'
- r'netmask\s+(?P[^\s]+)\s+'
- r'broadcast\s+(?P[^\s]+)\s+'
- r'gateway\s+(?P[^\s]+)\s+'
- r'dns\-nameservers\s+'
- r'(?P[^\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:
diff --git a/cloudbaseinit/tests/metadata/fake_json_response.py b/cloudbaseinit/tests/metadata/fake_json_response.py
index 452cff0a..0111ebff 100644
--- a/cloudbaseinit/tests/metadata/fake_json_response.py
+++ b/cloudbaseinit/tests/metadata/fake_json_response.py
@@ -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)
diff --git a/cloudbaseinit/tests/metadata/services/test_baseopenstackservice.py b/cloudbaseinit/tests/metadata/services/test_baseopenstackservice.py
index bc2b41f7..0569e4bc 100644
--- a/cloudbaseinit/tests/metadata/services/test_baseopenstackservice.py
+++ b/cloudbaseinit/tests/metadata/services/test_baseopenstackservice.py
@@ -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()
diff --git a/cloudbaseinit/tests/metadata/services/test_opennebulaservice.py b/cloudbaseinit/tests/metadata/services/test_opennebulaservice.py
index a9a2a2a6..86dd495f 100644
--- a/cloudbaseinit/tests/metadata/services/test_opennebulaservice.py
+++ b/cloudbaseinit/tests/metadata/services/test_opennebulaservice.py
@@ -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()
diff --git a/cloudbaseinit/tests/plugins/windows/test_networkconfig.py b/cloudbaseinit/tests/plugins/windows/test_networkconfig.py
index 18801154..879e496b 100644
--- a/cloudbaseinit/tests/plugins/windows/test_networkconfig.py
+++ b/cloudbaseinit/tests/plugins/windows/test_networkconfig.py
@@ -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)
diff --git a/cloudbaseinit/tests/utils/test_debiface.py b/cloudbaseinit/tests/utils/test_debiface.py
new file mode 100644
index 00000000..f72ed180
--- /dev/null
+++ b/cloudbaseinit/tests/utils/test_debiface.py
@@ -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()
diff --git a/cloudbaseinit/utils/debiface.py b/cloudbaseinit/utils/debiface.py
new file mode 100644
index 00000000..4efd24cf
--- /dev/null
+++ b/cloudbaseinit/utils/debiface.py
@@ -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