From 84f6dd118a4c28f23f3a25fc8a6b5dae012f7fd8 Mon Sep 17 00:00:00 2001 From: Matthew Thode Date: Fri, 12 Oct 2018 11:40:54 -0500 Subject: [PATCH] write dns info to networkd Write dns info to networkd so it can be consumed by resolved if resolved is enabled. Change-Id: Ia7b55e9a538cc6f060bc08da85123d7bb3a4f73e --- glean/cmd.py | 55 ++++++++++++++++--- .../config/openstack/latest/network_data.json | 12 +++- .../test/rax-iad.networkd.network.out.dns | 52 ++++++++++++++++++ glean/tests/test_glean.py | 8 ++- 4 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 glean/tests/fixtures/test/rax-iad.networkd.network.out.dns diff --git a/glean/cmd.py b/glean/cmd.py index cc604f1..f7b595e 100644 --- a/glean/cmd.py +++ b/glean/cmd.py @@ -31,6 +31,16 @@ from glean import systemlock from glean import utils from glean._vendor import distro +try: + import configparser +except ImportError: + import ConfigParser as configparser + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + log = logging.getLogger("glean") @@ -312,7 +322,7 @@ def write_redhat_interfaces(interfaces, sys_interfaces, distro): return files_to_write -def _write_networkd_interface(name, interfaces, files_struct=dict()): +def _write_networkd_interface(name, interfaces, args, files_struct=dict()): vlans = [] for interface in interfaces: iname = name @@ -340,6 +350,15 @@ def _write_networkd_interface(name, interfaces, files_struct=dict()): ('bond_mode' in interface)): if '[Network]' not in files_struct[network_file]: files_struct[network_file]['[Network]'] = list() + if 'services' in interface: + for service in interface['services']: + if service['type'] == 'dns': + if not args.skip_dns: + files_struct[network_file]['[Network]'].append( + 'DNS={address}'.format( + address=service['address'] + ) + ) # dhcp network, set to yes if both dhcp6 and dhcp4 are set if interface['type'] == 'ipv4_dhcp': if 'DHCP=ipv6' in files_struct[network_file]['[Network]']: @@ -492,7 +511,7 @@ def _write_networkd_interface(name, interfaces, files_struct=dict()): return files_struct -def write_networkd_interfaces(interfaces, sys_interfaces): +def write_networkd_interfaces(interfaces, sys_interfaces, args): files_to_write = dict() gen_intfs = {} files_struct = dict() @@ -531,7 +550,7 @@ def write_networkd_interfaces(interfaces, sys_interfaces): intf.get('link', intf['id']) for intf in interfs if 'bond_mode' in intf) files_struct = _write_networkd_interface( - interface_name, interfs, files_struct) + interface_name, interfs, args, files_struct) for mac, iname in sorted( sys_interfaces.items(), key=lambda x: x[1]): @@ -545,7 +564,7 @@ def write_networkd_interfaces(interfaces, sys_interfaces): continue interface = {'type': 'ipv4_dhcp', 'mac_address': mac} files_struct = _write_networkd_interface( - iname, [interface], files_struct) + iname, [interface], args, files_struct) for networkd_file in files_struct: file_contents = '# Automatically generated, do not edit\n' @@ -912,10 +931,30 @@ def write_debian_interfaces(interfaces, sys_interfaces): def write_dns_info(dns_servers): - results = "" + resolve_confs = {} + resolv_nameservers = "" for server in dns_servers: - results += "nameserver {0}\n".format(server) - return {'/etc/resolv.conf': results} + resolv_nameservers += "nameserver {0}\n".format(server) + resolve_confs['/etc/resolv.conf'] = resolv_nameservers + # set up resolved if available + if os.path.isfile('/etc/systemd/resolved.conf'): + # read the existing config so we only overwrite what's needed + resolved_conf = configparser.ConfigParser() + resolved_conf.read('/etc/systemd/resolved.conf') + # create config section if not created + if not resolved_conf.has_section('Resolve'): + resolved_conf.add_section('Resolve') + # write space separated dns servers + resolved_conf.set('Resolve', 'DNS', " ".join(dns_servers)) + # use stringio to output the resulting config to string + # configparser only outputs to file descriptors + resolved_conf_fd = StringIO("") + resolved_conf.write(resolved_conf_fd) + resolved_conf_output = resolved_conf_fd.getvalue() + resolved_conf_fd.close() + # add the config to files to be written + resolve_confs['/etc/systemd/resolved.conf'] = resolved_conf_output + return resolve_confs def get_config_drive_interfaces(net): @@ -1012,7 +1051,7 @@ def write_static_network_info( ) elif args.distro in 'networkd': files_to_write.update( - write_networkd_interfaces(interfaces, sys_interfaces) + write_networkd_interfaces(interfaces, sys_interfaces, args) ) else: return False diff --git a/glean/tests/fixtures/rax-iad/mnt/config/openstack/latest/network_data.json b/glean/tests/fixtures/rax-iad/mnt/config/openstack/latest/network_data.json index 0dd92ff..796d9df 100644 --- a/glean/tests/fixtures/rax-iad/mnt/config/openstack/latest/network_data.json +++ b/glean/tests/fixtures/rax-iad/mnt/config/openstack/latest/network_data.json @@ -43,7 +43,17 @@ } ], "ip_address": "2001:4802:7807:103:be76:4eff:fe20:d72f", - "id": "network1" + "id": "network1", + "services": [ + { + "address": "1111:2222:3333:4444::1234", + "type": "dns" + }, + { + "address": "1.2.3.4", + "type": "dns" + } + ] }, { "network_id": "11111111-1111-1111-1111-111111111111", diff --git a/glean/tests/fixtures/test/rax-iad.networkd.network.out.dns b/glean/tests/fixtures/test/rax-iad.networkd.network.out.dns new file mode 100644 index 0000000..a32488e --- /dev/null +++ b/glean/tests/fixtures/test/rax-iad.networkd.network.out.dns @@ -0,0 +1,52 @@ +### Write /etc/resolv.conf +nameserver 69.20.0.196 +nameserver 69.20.0.164 +### Write /etc/systemd/network/eth0.network +# Automatically generated, do not edit +[Match] +MACAddress=bc:76:4e:20:d7:2f +Name=eth0 + +[Network] +DNS=1.2.3.4 +DNS=1111:2222:3333:4444::1234 +IPv6AcceptRA=no + +[Address] +Address=146.20.110.113/24 + +[Address] +Address=2001:4802:7807:103:be76:4eff:fe20:d72f/64 + +[Route] +Destination=0.0.0.0/0 +Gateway=146.20.110.1 + +[Route] +Destination=::/0 +Gateway=fe80::def + +[Route] +Destination=fd30::/48 +Gateway=fe80::f001 + +### Write /etc/systemd/network/eth1.network +# Automatically generated, do not edit +[Match] +MACAddress=bc:76:4e:20:d7:33 +Name=eth1 + +[Network] +IPv6AcceptRA=no + +[Address] +Address=10.210.32.174/19 + +[Route] +Destination=10.176.0.0/12 +Gateway=10.210.32.1 + +[Route] +Destination=10.208.0.0/12 +Gateway=10.210.32.1 + diff --git a/glean/tests/test_glean.py b/glean/tests/test_glean.py index 0beb259..d861128 100644 --- a/glean/tests/test_glean.py +++ b/glean/tests/test_glean.py @@ -85,7 +85,7 @@ class TestGlean(base.BaseTestCase): '/etc/conf.d', '/etc/init.d', '/etc/sysconfig/network', '/etc/systemd/network') mock_files = ('/etc/resolv.conf', '/etc/hostname', '/etc/hosts', - '/bin/systemctl') + '/etc/systemd/resolved.conf', '/bin/systemctl') if (path.startswith(mock_dirs) or path in mock_files): try: mock_handle = self.file_handle_mocks[path] @@ -193,6 +193,9 @@ class TestGlean(base.BaseTestCase): output_filename = '%s.%s.network.out' % (provider, distro.lower()) output_path = os.path.join(sample_data_path, 'test', output_filename) + if not skip_dns: + if os.path.exists(output_path + '.dns'): + output_path = output_path + '.dns' # Generate a list of (dest, content) into write_blocks to assert write_blocks = [] @@ -260,7 +263,8 @@ class TestGlean(base.BaseTestCase): # comes up with "--interface". This simulates that. def test_glean_systemd(self): with mock.patch('glean.systemlock.Lock'): - self._assert_distro_provider(self.distro, self.style, 'eth0') + self._assert_distro_provider(self.distro, self.style, + 'eth0', skip_dns=True) def test_glean_skip_dns(self): with mock.patch('glean.systemlock.Lock'):