diff --git a/glean/cmd.py b/glean/cmd.py index 157c2c5..52b3518 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") @@ -317,7 +327,7 @@ def write_redhat_interfaces(interfaces, sys_interfaces, args): 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 @@ -345,6 +355,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]']: @@ -497,7 +516,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() @@ -536,7 +555,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]): @@ -550,7 +569,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' @@ -923,10 +942,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): @@ -1023,7 +1062,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 9edeb9d..aea42a4 100644 --- a/glean/tests/test_glean.py +++ b/glean/tests/test_glean.py @@ -84,7 +84,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] @@ -198,6 +198,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 = [] @@ -271,7 +274,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'):