summaryrefslogtreecommitdiff
path: root/cloudinit
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit')
-rw-r--r--cloudinit/net/network_state.py66
-rw-r--r--cloudinit/net/tests/test_network_state.py103
2 files changed, 154 insertions, 15 deletions
diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py
index e8bf9e39..8018cfb9 100644
--- a/cloudinit/net/network_state.py
+++ b/cloudinit/net/network_state.py
@@ -237,6 +237,7 @@ class NetworkStateInterpreter(metaclass=CommandHandlerMeta):
self._network_state = copy.deepcopy(self.initial_network_state)
self._network_state['config'] = config
self._parsed = False
+ self._interface_dns_map = {}
@property
def network_state(self):
@@ -310,6 +311,21 @@ class NetworkStateInterpreter(metaclass=CommandHandlerMeta):
LOG.warning("Skipping invalid command: %s", command,
exc_info=True)
LOG.debug(self.dump_network_state())
+ for interface, dns in self._interface_dns_map.items():
+ iface = None
+ try:
+ iface = self._network_state['interfaces'][interface]
+ except KeyError as e:
+ raise ValueError(
+ 'Nameserver specified for interface {0}, '
+ 'but interface {0} does not exist!'.format(interface)
+ ) from e
+ if iface:
+ nameservers, search = dns
+ iface['dns'] = {
+ 'addresses': nameservers,
+ 'search': search,
+ }
def parse_config_v2(self, skip_broken=True):
for command_type, command in self._config.items():
@@ -526,21 +542,40 @@ class NetworkStateInterpreter(metaclass=CommandHandlerMeta):
def handle_infiniband(self, command):
self.handle_physical(command)
- @ensure_command_keys(['address'])
- def handle_nameserver(self, command):
- dns = self._network_state.get('dns')
+ def _parse_dns(self, command):
+ nameservers = []
+ search = []
if 'address' in command:
addrs = command['address']
if not type(addrs) == list:
addrs = [addrs]
for addr in addrs:
- dns['nameservers'].append(addr)
+ nameservers.append(addr)
if 'search' in command:
paths = command['search']
if not isinstance(paths, list):
paths = [paths]
for path in paths:
- dns['search'].append(path)
+ search.append(path)
+ return nameservers, search
+
+ @ensure_command_keys(['address'])
+ def handle_nameserver(self, command):
+ dns = self._network_state.get('dns')
+ nameservers, search = self._parse_dns(command)
+ if 'interface' in command:
+ self._interface_dns_map[command['interface']] = (
+ nameservers, search
+ )
+ else:
+ dns['nameservers'].extend(nameservers)
+ dns['search'].extend(search)
+
+ @ensure_command_keys(['address'])
+ def _handle_individual_nameserver(self, command, iface):
+ _iface = self._network_state.get('interfaces')
+ nameservers, search = self._parse_dns(command)
+ _iface[iface]['dns'] = {'nameservers': nameservers, 'search': search}
@ensure_command_keys(['destination'])
def handle_route(self, command):
@@ -706,16 +741,17 @@ class NetworkStateInterpreter(metaclass=CommandHandlerMeta):
def _v2_common(self, cfg):
LOG.debug('v2_common: handling config:\n%s', cfg)
- if 'nameservers' in cfg:
- search = cfg.get('nameservers').get('search', [])
- dns = cfg.get('nameservers').get('addresses', [])
- name_cmd = {'type': 'nameserver'}
- if len(search) > 0:
- name_cmd.update({'search': search})
- if len(dns) > 0:
- name_cmd.update({'addresses': dns})
- LOG.debug('v2(nameserver) -> v1(nameserver):\n%s', name_cmd)
- self.handle_nameserver(name_cmd)
+ for iface, dev_cfg in cfg.items():
+ if 'nameservers' in dev_cfg:
+ search = dev_cfg.get('nameservers').get('search', [])
+ dns = dev_cfg.get('nameservers').get('addresses', [])
+ name_cmd = {'type': 'nameserver'}
+ if len(search) > 0:
+ name_cmd.update({'search': search})
+ if len(dns) > 0:
+ name_cmd.update({'address': dns})
+ self.handle_nameserver(name_cmd)
+ self._handle_individual_nameserver(name_cmd, iface)
def _handle_bond_bridge(self, command, cmd_type=None):
"""Common handler for bond and bridge types"""
diff --git a/cloudinit/net/tests/test_network_state.py b/cloudinit/net/tests/test_network_state.py
index 07d726e2..fc4724a1 100644
--- a/cloudinit/net/tests/test_network_state.py
+++ b/cloudinit/net/tests/test_network_state.py
@@ -2,12 +2,62 @@
from unittest import mock
+import pytest
+
+from cloudinit import safeyaml
from cloudinit.net import network_state
from cloudinit.tests.helpers import CiTestCase
netstate_path = 'cloudinit.net.network_state'
+_V1_CONFIG_NAMESERVERS = """\
+network:
+ version: 1
+ config:
+ - type: nameserver
+ interface: {iface}
+ address:
+ - 192.168.1.1
+ - 8.8.8.8
+ search:
+ - spam.local
+ - type: nameserver
+ address:
+ - 192.168.1.0
+ - 4.4.4.4
+ search:
+ - eggs.local
+ - type: physical
+ name: eth0
+ mac_address: '00:11:22:33:44:55'
+ - type: physical
+ name: eth1
+ mac_address: '66:77:88:99:00:11'
+"""
+
+V1_CONFIG_NAMESERVERS_VALID = _V1_CONFIG_NAMESERVERS.format(iface='eth1')
+V1_CONFIG_NAMESERVERS_INVALID = _V1_CONFIG_NAMESERVERS.format(iface='eth90')
+
+V2_CONFIG_NAMESERVERS = """\
+network:
+ version: 2
+ ethernets:
+ eth0:
+ match:
+ macaddress: '00:11:22:33:44:55'
+ nameservers:
+ search: [spam.local, eggs.local]
+ addresses: [8.8.8.8]
+ eth1:
+ match:
+ macaddress: '66:77:88:99:00:11'
+ nameservers:
+ search: [foo.local, bar.local]
+ addresses: [4.4.4.4]
+"""
+
+
class TestNetworkStateParseConfig(CiTestCase):
def setUp(self):
@@ -55,4 +105,57 @@ class TestNetworkStateParseConfigV2(CiTestCase):
self.assertEqual(ncfg, nsi.as_dict()['config'])
+class TestNetworkStateParseNameservers:
+ def _parse_network_state_from_config(self, config):
+ yaml = safeyaml.load(config)
+ return network_state.parse_net_config_data(yaml['network'])
+
+ def test_v1_nameservers_valid(self):
+ config = self._parse_network_state_from_config(
+ V1_CONFIG_NAMESERVERS_VALID)
+
+ # If an interface was specified, DNS shouldn't be in the global list
+ assert ['192.168.1.0', '4.4.4.4'] == sorted(
+ config.dns_nameservers)
+ assert ['eggs.local'] == config.dns_searchdomains
+
+ # If an interface was specified, DNS should be part of the interface
+ for iface in config.iter_interfaces():
+ if iface['name'] == 'eth1':
+ assert iface['dns']['addresses'] == ['192.168.1.1', '8.8.8.8']
+ assert iface['dns']['search'] == ['spam.local']
+ else:
+ assert 'dns' not in iface
+
+ def test_v1_nameservers_invalid(self):
+ with pytest.raises(ValueError):
+ self._parse_network_state_from_config(
+ V1_CONFIG_NAMESERVERS_INVALID)
+
+ def test_v2_nameservers(self):
+ config = self._parse_network_state_from_config(V2_CONFIG_NAMESERVERS)
+
+ # Ensure DNS defined on interface exists on interface
+ for iface in config.iter_interfaces():
+ if iface['name'] == 'eth0':
+ assert iface['dns'] == {
+ 'nameservers': ['8.8.8.8'],
+ 'search': ['spam.local', 'eggs.local'],
+ }
+ else:
+ assert iface['dns'] == {
+ 'nameservers': ['4.4.4.4'],
+ 'search': ['foo.local', 'bar.local']
+ }
+
+ # Ensure DNS defined on interface also exists globally (since there
+ # is no global DNS definitions in v2)
+ assert ['4.4.4.4', '8.8.8.8'] == sorted(config.dns_nameservers)
+ assert [
+ 'bar.local',
+ 'eggs.local',
+ 'foo.local',
+ 'spam.local',
+ ] == sorted(config.dns_searchdomains)
+
# vi: ts=4 expandtab