diff --git a/cloudinit/distros/photon.py b/cloudinit/distros/photon.py index 8b78f98f856..45125be7e92 100644 --- a/cloudinit/distros/photon.py +++ b/cloudinit/distros/photon.py @@ -10,10 +10,8 @@ from cloudinit import distros from cloudinit import helpers from cloudinit import log as logging -from cloudinit.distros import net_util from cloudinit.settings import PER_INSTANCE from cloudinit.distros import rhel_util as rhutil -from cloudinit.net.network_state import mask_to_net_prefix from cloudinit.distros.parsers.hostname import HostnameConf LOG = logging.getLogger(__name__) @@ -79,9 +77,6 @@ def install_packages(self, pkglist): self.package_command('install', pkgs=pkglist) def _write_network_config(self, netconfig): - if isinstance(netconfig, str): - self._write_network_(netconfig) - return return self._supported_write_network_config(netconfig) def _bring_up_interfaces(self, device_names): @@ -141,215 +136,3 @@ def package_command(self, command, args=None, pkgs=None): def update_package_sources(self): self._runner.run('update-sources', self.package_command, ['makecache'], freq=PER_INSTANCE) - - def _generate_resolv_conf(self): - resolv_conf_fn = self.resolve_conf_fn - resolv_templ_fn = 'systemd.resolved.conf' - - return resolv_conf_fn, resolv_templ_fn - - def _write_network_(self, settings): - entries = net_util.translate_network(settings) - LOG.debug('Translated ubuntu style network settings %s into %s', - settings, entries) - route_entries = [] - route_entries = translate_routes(settings) - dev_names = entries.keys() - nameservers = [] - searchdomains = [] - # Format for systemd - for (dev, info) in entries.items(): - if 'dns-nameservers' in info: - nameservers.extend(info['dns-nameservers']) - if 'dns-search' in info: - searchdomains.extend(info['dns-search']) - if dev == 'lo': - continue - - net_fn = self.network_conf_dir + '10-cloud-init-' + dev - net_fn += '.network' - dhcp_enabled = 'no' - if info.get('bootproto') == 'dhcp': - if (settings.find('inet dhcp') >= 0 and - settings.find('inet6 dhcp') >= 0): - dhcp_enabled = 'yes' - else: - if info.get('inet6') is True: - dhcp_enabled = 'ipv6' - else: - dhcp_enabled = 'ipv4' - - net_cfg = { - 'Name': dev, - 'DHCP': dhcp_enabled, - } - - if info.get('hwaddress'): - net_cfg['MACAddress'] = info.get('hwaddress') - if info.get('address'): - net_cfg['Address'] = '%s' % (info.get('address')) - if info.get('netmask'): - net_cfg['Address'] += '/%s' % ( - mask_to_net_prefix(info.get('netmask'))) - if info.get('gateway'): - net_cfg['Gateway'] = info.get('gateway') - if info.get('dns-nameservers'): - net_cfg['DNS'] = str( - tuple(info.get('dns-nameservers'))).replace(',', '') - if info.get('dns-search'): - net_cfg['Domains'] = str( - tuple(info.get('dns-search'))).replace(',', '') - route_entry = [] - if dev in route_entries: - route_entry = route_entries[dev] - route_index = 0 - found = True - while found: - route_name = 'routes.' + str(route_index) - if route_name in route_entries[dev]: - val = str(tuple(route_entries[dev][route_name])) - val = val.replace(',', '') - if val: - net_cfg[route_name] = val - else: - found = False - route_index += 1 - - if info.get('auto'): - self._write_interface_file(net_fn, net_cfg, route_entry) - - resolve_data = [] - new_resolve_data = [] - with open(self.resolve_conf_fn, 'r') as rf: - resolve_data = rf.readlines() - LOG.debug('Old Resolve Data\n') - LOG.debug('%s', resolve_data) - for item in resolve_data: - if ((nameservers and ('DNS=' in item)) or - (searchdomains and ('Domains=' in item))): - continue - else: - new_resolve_data.append(item) - - new_resolve_data = new_resolve_data + \ - convert_resolv_conf(nameservers, searchdomains) - LOG.debug('New resolve data\n') - LOG.debug('%s', new_resolve_data) - if nameservers or searchdomains: - util.write_file(self.resolve_conf_fn, ''.join(new_resolve_data)) - - return dev_names - - def _write_interface_file(self, net_fn, net_cfg, route_entry): - if not net_cfg['Name']: - return - content = '[Match]\n' - content += 'Name=%s\n' % (net_cfg['Name']) - if 'MACAddress' in net_cfg: - content += 'MACAddress=%s\n' % (net_cfg['MACAddress']) - content += '[Network]\n' - - if 'DHCP' in net_cfg and net_cfg['DHCP'] in {'yes', 'ipv4', 'ipv6'}: - content += 'DHCP=%s\n' % (net_cfg['DHCP']) - else: - if 'Address' in net_cfg: - content += 'Address=%s\n' % (net_cfg['Address']) - if 'Gateway' in net_cfg: - content += 'Gateway=%s\n' % (net_cfg['Gateway']) - if 'DHCP' in net_cfg and net_cfg['DHCP'] == 'no': - content += 'DHCP=%s\n' % (net_cfg['DHCP']) - - route_index = 0 - found = True - if route_entry: - while found: - route_name = 'routes.' + str(route_index) - if route_name in route_entry: - content += '[Route]\n' - if len(route_entry[route_name]) != 2: - continue - content += 'Gateway=%s\n' % ( - route_entry[route_name][0]) - content += 'Destination=%s\n' % ( - route_entry[route_name][1]) - else: - found = False - route_index += 1 - - util.write_file(net_fn, content) - - -def convert_resolv_conf(nameservers, searchdomains): - ''' Returns a string formatted for resolv.conf ''' - result = [] - if nameservers: - nslist = 'DNS=' - for ns in nameservers: - nslist = nslist + '%s ' % ns - nslist = nslist + '\n' - result.append(str(nslist)) - if searchdomains: - sdlist = 'Domains=' - for sd in searchdomains: - sdlist = sdlist + '%s ' % sd - sdlist = sdlist + '\n' - result.append(str(sdlist)) - return result - - -def translate_routes(settings): - entries = [] - for line in settings.splitlines(): - line = line.strip() - if not line or line.startswith('#'): - continue - split_up = line.split(None, 1) - if len(split_up) <= 1: - continue - entries.append(split_up) - consume = {} - ifaces = [] - for (cmd, args) in entries: - if cmd == 'iface': - if consume: - ifaces.append(consume) - consume = {} - consume[cmd] = args - else: - consume[cmd] = args - - absorb = False - for (cmd, args) in consume.items(): - if cmd == 'iface': - absorb = True - if absorb: - ifaces.append(consume) - out_ifaces = {} - for info in ifaces: - if 'iface' not in info: - continue - iface_details = info['iface'].split(None) - dev_name = None - if len(iface_details) >= 1: - dev = iface_details[0].strip().lower() - if dev: - dev_name = dev - if not dev_name: - continue - route_info = {} - route_index = 0 - found = True - while found: - route_name = 'routes.' + str(route_index) - if route_name in info: - val = info[route_name].split() - if val: - route_info[route_name] = val - else: - found = False - route_index += 1 - if dev_name in out_ifaces: - out_ifaces[dev_name].update(route_info) - else: - out_ifaces[dev_name] = route_info - return out_ifaces diff --git a/cloudinit/net/networkd.py b/cloudinit/net/networkd.py index 71f87995884..2dffce59b67 100644 --- a/cloudinit/net/networkd.py +++ b/cloudinit/net/networkd.py @@ -35,6 +35,8 @@ def update_section(self, sec, key, val): for k in self.conf_dict.keys(): if k == sec: self.conf_dict[k].append(key+'='+str(val)) + # remove duplicates from list + self.conf_dict[k] = list(dict.fromkeys(self.conf_dict[k])) self.conf_dict[k].sort() def get_final_conf(self): @@ -103,19 +105,27 @@ def generate_link_section(self, iface, cfg): def parse_routes(self, conf, cfg): sec = 'Route' + route_cfg_map = { + 'gateway': 'Gateway', + 'network': 'Destination', + 'metric': 'Metric', + } + + # prefix is derived using netmask by network_state + prefix = '' + if 'prefix' in conf: + prefix = '/' + str(conf['prefix']) + for k, v in conf.items(): - if k == 'gateway': - cfg.update_section(sec, 'Gateway', v) - elif k == 'network': - tmp = v - if 'prefix' in conf: - tmp += '/' + str(conf['prefix']) - cfg.update_section(sec, 'Destination', tmp) - elif k == 'metric': - cfg.update_section(sec, 'Metric', v) + if k not in route_cfg_map: + continue + if k == 'network': + v += prefix + cfg.update_section(sec, route_cfg_map[k], v) def parse_subnets(self, iface, cfg): dhcp = 'no' + sec = 'Network' for e in iface.get('subnets', []): t = e['type'] if t == 'dhcp4' or t == 'dhcp': @@ -131,21 +141,24 @@ def parse_subnets(self, iface, cfg): if 'routes' in e and e['routes']: for i in e['routes']: self.parse_routes(i, cfg) - elif 'address' in e: + if 'address' in e: + subnet_cfg_map = { + 'address': 'Address', + 'gateway': 'Gateway', + 'dns_nameservers': 'DNS', + 'dns_search': 'Domains', + } for k, v in e.items(): if k == 'address': - tmp = v if 'prefix' in e: - tmp += '/' + str(e['prefix']) - cfg.update_section('Address', 'Address', tmp) + v += '/' + str(e['prefix']) + cfg.update_section('Address', subnet_cfg_map[k], v) elif k == 'gateway': - cfg.update_section('Route', 'Gateway', v) - elif k == 'dns_nameservers': - cfg.update_section('Network', 'DNS', ' '.join(v)) - elif k == 'dns_search': - cfg.update_section('Network', 'Domains', ' '.join(v)) + cfg.update_section('Route', subnet_cfg_map[k], v) + elif k == 'dns_nameservers' or k == 'dns_search': + cfg.update_section(sec, subnet_cfg_map[k], ' '.join(v)) - cfg.update_section('Network', 'DHCP', dhcp) + cfg.update_section(sec, 'DHCP', dhcp) # This is to accommodate extra keys present in VMware config def dhcp_domain(self, d, cfg): diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl index d6dbb833852..cb2a625b069 100644 --- a/config/cloud.cfg.tmpl +++ b/config/cloud.cfg.tmpl @@ -44,20 +44,12 @@ ssh_pwauth: 0 # This will cause the set+update hostname module to not operate (if true) preserve_hostname: false +# If you use datasource_list array, keep array items in a single line. +# If you use multi line array, ds-identify script won't read array items. {% if variant.endswith("bsd") %} # This should not be required, but leave it in place until the real cause of # not finding -any- datasources is resolved. datasource_list: ['NoCloud', 'ConfigDrive', 'Azure', 'OpenStack', 'Ec2'] -{% elif variant in ["photon"] %} -# Datasources to check for cloud-config -datasource_list: [ - NoCloud, - ConfigDrive, - OVF, - OpenStack, - VMwareGuestInfo, - None - ] {% endif %} # Example datasource config # datasource: diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index 562ee04a4d6..d09e46af71d 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -15,6 +15,7 @@ FilesystemMockingTestCase, dir2dict) from cloudinit import subp from cloudinit import util +from cloudinit import safeyaml BASE_NET_CFG = ''' auto lo @@ -88,6 +89,24 @@ 'type': 'physical'}], 'version': 1} +V1_NET_CFG_WITH_DUPS = """\ +# same value in interface specific dns and global dns +# should produce single entry in network file +version: 1 +config: + - type: physical + name: eth0 + subnets: + - type: static + address: 192.168.0.102/24 + dns_nameservers: [1.2.3.4] + dns_search: [test.com] + interface: eth0 + - type: nameserver + address: [1.2.3.4] + search: [test.com] +""" + V1_NET_CFG_OUTPUT = """\ # This file is generated from information provided by the datasource. Changes # to it will not persist across an instance reboot. To disable cloud-init's @@ -867,6 +886,28 @@ def test_photon_network_config_v2(self): V2_NET_CFG, expected_cfgs.copy()) + def test_photon_network_config_v1_with_duplicates(self): + expected = """\ + [Match] + Name=eth0 + [Network] + DHCP=no + DNS=1.2.3.4 + Domains=test.com + [Address] + Address=192.168.0.102/24""" + + net_cfg = safeyaml.load(V1_NET_CFG_WITH_DUPS) + + expected = self.create_conf_dict(expected.splitlines()) + expected_cfgs = { + self.nwk_file_path('eth0'): expected, + } + + self._apply_and_verify(self.distro.apply_network_config, + net_cfg, + expected_cfgs.copy()) + def get_mode(path, target=None): return os.stat(subp.target_path(target, path)).st_mode & 0o777 diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py index b2ddbf9985d..1aab51ee3d7 100644 --- a/tests/unittests/test_net.py +++ b/tests/unittests/test_net.py @@ -825,10 +825,14 @@ [Match] Name=eth99 MACAddress=c0:d6:9f:2c:e8:80 + [Address] + Address=192.168.21.3/24 [Network] DHCP=ipv4 + Domains=barley.maas sach.maas Domains=wark.maas DNS=1.2.3.4 5.6.7.8 + DNS=8.8.8.8 8.8.4.4 [Route] Gateway=65.61.151.37 Destination=0.0.0.0/0