From 8eedba0d3b9851bb0101407c5a070b7a975efa04 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Fri, 17 Oct 2014 12:32:41 -0700 Subject: Expose uses_systemd as a distro function Without this change the tests are currently failing on rhel7 since a location where a hostname file is written no longer exists at that location when systemd is active. To avoid this allow the test to inspect if the distro has systemd enabled and avoid testing the file when systemd is being used so the test passes. We likely need to figure out a better way to test features that no longer exist as files but exist as commands with systemd in general. --- cloudinit/distros/rhel.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'cloudinit/distros') diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py index e8abf111..1a269e08 100644 --- a/cloudinit/distros/rhel.py +++ b/cloudinit/distros/rhel.py @@ -98,7 +98,7 @@ class Distro(distros.Distro): rhel_util.update_sysconfig_file(self.network_conf_fn, net_cfg) return dev_names - def _dist_uses_systemd(self): + def uses_systemd(self): # Fedora 18 and RHEL 7 were the first adopters in their series (dist, vers) = util.system_info()['dist'][:2] major = (int)(vers.split('.')[0]) @@ -106,7 +106,7 @@ class Distro(distros.Distro): or (dist.startswith('Fedora') and major >= 18)) def apply_locale(self, locale, out_fn=None): - if self._dist_uses_systemd(): + if self.uses_systemd(): if not out_fn: out_fn = self.systemd_locale_conf_fn out_fn = self.systemd_locale_conf_fn @@ -119,7 +119,7 @@ class Distro(distros.Distro): rhel_util.update_sysconfig_file(out_fn, locale_cfg) def _write_hostname(self, hostname, out_fn): - if self._dist_uses_systemd(): + if self.uses_systemd(): util.subp(['hostnamectl', 'set-hostname', str(hostname)]) else: host_cfg = { @@ -135,14 +135,14 @@ class Distro(distros.Distro): return hostname def _read_system_hostname(self): - if self._dist_uses_systemd(): + if self.uses_systemd(): host_fn = self.systemd_hostname_conf_fn else: host_fn = self.hostname_conf_fn return (host_fn, self._read_hostname(host_fn)) def _read_hostname(self, filename, default=None): - if self._dist_uses_systemd(): + if self.uses_systemd(): (out, _err) = util.subp(['hostname']) if len(out): return out @@ -163,7 +163,7 @@ class Distro(distros.Distro): def set_timezone(self, tz): tz_file = self._find_tz_file(tz) - if self._dist_uses_systemd(): + if self.uses_systemd(): # Currently, timedatectl complains if invoked during startup # so for compatibility, create the link manually. util.del_file(self.tz_local_fn) -- cgit v1.2.3 From e88f6ed4c46fcb1069fe899606a8b6d95411c13f Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 21 Oct 2014 11:55:16 -0700 Subject: Handle strings/text type for 'ssh_authorized_keys' Instead of only expected a list, tuple, or set type allow for a string type to be passed in, and add log message that occurs if some other type is used that can not be correctly processed. --- cloudinit/distros/__init__.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'cloudinit/distros') diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 2599d9f2..d30098eb 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -387,8 +387,17 @@ class Distro(object): # Import SSH keys if 'ssh_authorized_keys' in kwargs: - keys = set(kwargs['ssh_authorized_keys']) or [] - ssh_util.setup_user_keys(keys, name, options=None) + # Try to handle this in a smart manner. + keys = kwargs['ssh_authorized_keys'] + if isinstance(keys, (basestring, str)): + keys = [keys] + if not isinstance(keys, (tuple, list, set)): + util.multi_log("Invalid type detected for" + " 'ssh_authorized_keys', expected list, string" + " or set.") + else: + keys = set(keys) or [] + ssh_util.setup_user_keys(keys, name, options=None) return True -- cgit v1.2.3 From 6fb6cfdea6ec31a69e749ddb638051c39256e7f3 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 21 Oct 2014 12:00:53 -0700 Subject: Also allow a dict to be used When a dict is passed in for 'ssh_authorized_keys' just extract the keys from the values of the dict (and discard the keys). --- cloudinit/distros/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'cloudinit/distros') diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index d30098eb..762529a6 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -391,10 +391,12 @@ class Distro(object): keys = kwargs['ssh_authorized_keys'] if isinstance(keys, (basestring, str)): keys = [keys] + if isinstance(keys, dict): + keys = list(keys.values()) if not isinstance(keys, (tuple, list, set)): util.multi_log("Invalid type detected for" " 'ssh_authorized_keys', expected list, string" - " or set.") + " , dict, or set.") else: keys = set(keys) or [] ssh_util.setup_user_keys(keys, name, options=None) -- cgit v1.2.3 From a6336edea275c409791807d16c1575ebd6895c9c Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 21 Oct 2014 12:08:50 -0700 Subject: Fix the word spacing --- cloudinit/distros/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'cloudinit/distros') diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 762529a6..7b05226a 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -396,7 +396,7 @@ class Distro(object): if not isinstance(keys, (tuple, list, set)): util.multi_log("Invalid type detected for" " 'ssh_authorized_keys', expected list, string" - " , dict, or set.") + ", dict, or set.") else: keys = set(keys) or [] ssh_util.setup_user_keys(keys, name, options=None) -- cgit v1.2.3 From 477a5418d55d45ddad55fcaa16ab3ac53652fdb9 Mon Sep 17 00:00:00 2001 From: Joshua Harlow Date: Tue, 21 Oct 2014 12:23:09 -0700 Subject: Use LOG.warn and handle the None case as well --- cloudinit/distros/__init__.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'cloudinit/distros') diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 7b05226a..83c2eebf 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -393,13 +393,14 @@ class Distro(object): keys = [keys] if isinstance(keys, dict): keys = list(keys.values()) - if not isinstance(keys, (tuple, list, set)): - util.multi_log("Invalid type detected for" - " 'ssh_authorized_keys', expected list, string" - ", dict, or set.") - else: - keys = set(keys) or [] - ssh_util.setup_user_keys(keys, name, options=None) + if keys is not None: + if not isinstance(keys, (tuple, list, set)): + LOG.warn("Invalid type '%s' detected for" + " 'ssh_authorized_keys', expected list," + " string, dict, or set.", type(keys)) + else: + keys = set(keys) or [] + ssh_util.setup_user_keys(keys, name, options=None) return True -- cgit v1.2.3 From bfbe8099b98bb97cfd96385fe31c023548734cbf Mon Sep 17 00:00:00 2001 From: Shraddha Pandhe Date: Fri, 21 Nov 2014 18:32:30 +0000 Subject: Add IPv6 Support for Rhel. This patch does the following: 1. Adds support to process network config with IPv6 2. Adds support to display 'ifconfig -a' information for IPv6 3. Adds support to display routing information for IPv6 --- cloudinit/distros/net_util.py | 60 ++++++++++++++++++++++++++++--------------- cloudinit/distros/rhel.py | 11 ++++++++ cloudinit/netinfo.py | 60 ++++++++++++++++++++++++++++++++++++------- 3 files changed, 102 insertions(+), 29 deletions(-) (limited to 'cloudinit/distros') diff --git a/cloudinit/distros/net_util.py b/cloudinit/distros/net_util.py index b9bcfd8b..f56f6ccd 100644 --- a/cloudinit/distros/net_util.py +++ b/cloudinit/distros/net_util.py @@ -113,6 +113,10 @@ def translate_network(settings): for info in ifaces: if 'iface' not in info: continue + use_ipv6 = False + # Check if current device has an ipv6 IP + if 'inet6' in info['iface']: + use_ipv6 = True iface_details = info['iface'].split(None) dev_name = None if len(iface_details) >= 1: @@ -122,6 +126,7 @@ def translate_network(settings): if not dev_name: continue iface_info = {} + iface_info['ipv6'] = {} if len(iface_details) >= 3: proto_type = iface_details[2].strip().lower() # Seems like this can be 'loopback' which we don't @@ -129,26 +134,39 @@ def translate_network(settings): if proto_type in ['dhcp', 'static']: iface_info['bootproto'] = proto_type # These can just be copied over - for k in ['netmask', 'address', 'gateway', 'broadcast']: - if k in info: - val = info[k].strip().lower() - if val: - iface_info[k] = val - # Name server info provided?? - if 'dns-nameservers' in info: - iface_info['dns-nameservers'] = info['dns-nameservers'].split() - # Name server search info provided?? - if 'dns-search' in info: - iface_info['dns-search'] = info['dns-search'].split() - # Is any mac address spoofing going on?? - if 'hwaddress' in info: - hw_info = info['hwaddress'].lower().strip() - hw_split = hw_info.split(None, 1) - if len(hw_split) == 2 and hw_split[0].startswith('ether'): - hw_addr = hw_split[1] - if hw_addr: - iface_info['hwaddress'] = hw_addr - real_ifaces[dev_name] = iface_info + if use_ipv6: + for k in ['address', 'gateway']: + if k in info: + val = info[k].strip().lower() + if val: + iface_info['ipv6'][k] = val + else: + for k in ['netmask', 'address', 'gateway', 'broadcast']: + if k in info: + val = info[k].strip().lower() + if val: + iface_info[k] = val + # Name server info provided?? + if 'dns-nameservers' in info: + iface_info['dns-nameservers'] = info['dns-nameservers'].split() + # Name server search info provided?? + if 'dns-search' in info: + iface_info['dns-search'] = info['dns-search'].split() + # Is any mac address spoofing going on?? + if 'hwaddress' in info: + hw_info = info['hwaddress'].lower().strip() + hw_split = hw_info.split(None, 1) + if len(hw_split) == 2 and hw_split[0].startswith('ether'): + hw_addr = hw_split[1] + if hw_addr: + iface_info['hwaddress'] = hw_addr + + # If ipv6 is enabled, device will have multiple IPs. + # Update the dictionary instead of overwriting it + if dev_name in real_ifaces: + real_ifaces[dev_name].update(iface_info) + else: + real_ifaces[dev_name] = iface_info # Check for those that should be started on boot via 'auto' for (cmd, args) in entries: if cmd == 'auto': @@ -160,4 +178,6 @@ def translate_network(settings): dev_name = args[0].strip().lower() if dev_name in real_ifaces: real_ifaces[dev_name]['auto'] = True + if cmd == 'iface' and 'inet6' in args: + real_ifaces[dev_name]['inet6'] = True return real_ifaces diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py index 1a269e08..fa3ccb38 100644 --- a/cloudinit/distros/rhel.py +++ b/cloudinit/distros/rhel.py @@ -71,6 +71,7 @@ class Distro(distros.Distro): nameservers = [] searchservers = [] dev_names = entries.keys() + use_ipv6 = False for (dev, info) in entries.iteritems(): net_fn = self.network_script_tpl % (dev) net_cfg = { @@ -83,6 +84,13 @@ class Distro(distros.Distro): 'MACADDR': info.get('hwaddress'), 'ONBOOT': _make_sysconfig_bool(info.get('auto')), } + if info.get('inet6'): + use_ipv6 = True + net_cfg.update({ + 'IPV6INIT': _make_sysconfig_bool(True), + 'IPV6ADDR': info.get('ipv6').get('address'), + 'IPV6_DEFAULTGW': info.get('ipv6').get('gateway'), + }) rhel_util.update_sysconfig_file(net_fn, net_cfg) if 'dns-nameservers' in info: nameservers.extend(info['dns-nameservers']) @@ -95,6 +103,9 @@ class Distro(distros.Distro): net_cfg = { 'NETWORKING': _make_sysconfig_bool(True), } + # If IPv6 interface present, enable ipv6 networking + if use_ipv6: + net_cfg['NETWORKING_IPV6'] = _make_sysconfig_bool(True) rhel_util.update_sysconfig_file(self.network_conf_fn, net_cfg) return dev_names diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py index 8d4df342..d891315b 100644 --- a/cloudinit/netinfo.py +++ b/cloudinit/netinfo.py @@ -72,6 +72,7 @@ def netdev_info(empty=""): "bcast:": "bcast", "broadcast": "bcast", "mask:": "mask", "netmask": "mask", "hwaddr": "hwaddr", "ether": "hwaddr", + "scope": "scope", } for origfield, field in ifconfigfields.items(): target = "%s%s" % (field, fieldpost) @@ -96,7 +97,12 @@ def netdev_info(empty=""): def route_info(): (route_out, _err) = util.subp(["netstat", "-rn"]) - routes = [] + (route_out6, _err6) = util.subp(["netstat", "-A inet6", "-n"]) + + routes = {} + routes['ipv4'] = [] + routes['ipv6'] = [] + entries = route_out.splitlines()[1:] for line in entries: if not line: @@ -132,7 +138,26 @@ def route_info(): 'iface': toks[7], } - routes.append(entry) + routes['ipv4'].append(entry) + + entries6 = route_out6.splitlines()[1:] + for line in entries6: + if not line: + continue + toks = line.split() + + if (len(toks) < 6 or toks[0] == "Kernel" or + toks[0] == "Proto" or toks[0] == "Active"): + continue + entry = { + 'proto': toks[0], + 'recv-q': toks[1], + 'send-q': toks[2], + 'local address': toks[3], + 'foreign address': toks[4], + 'state': toks[5], + } + routes['ipv6'].append(entry) return routes @@ -156,10 +181,12 @@ def netdev_pformat(): lines.append(util.center("Net device info failed", '!', 80)) netdev = None if netdev is not None: - fields = ['Device', 'Up', 'Address', 'Mask', 'Hw-Address'] + fields = ['Device', 'Up', 'Address', 'Mask', 'Scope', 'Hw-Address'] tbl = PrettyTable(fields) for (dev, d) in netdev.iteritems(): - tbl.add_row([dev, d["up"], d["addr"], d["mask"], d["hwaddr"]]) + tbl.add_row([dev, d["up"], d["addr"], d["mask"], ".", d["hwaddr"]]) + if d["addr6"]: + tbl.add_row([dev, d["up"], d["addr6"], ".", d["scope6"], d["hwaddr"]]) netdev_s = tbl.get_string() max_len = len(max(netdev_s.splitlines(), key=len)) header = util.center("Net device info", "+", max_len) @@ -176,15 +203,30 @@ def route_pformat(): util.logexc(LOG, "Route info failed: %s" % e) routes = None if routes is not None: - fields = ['Route', 'Destination', 'Gateway', + fields_v4 = ['Route', 'Destination', 'Gateway', 'Genmask', 'Interface', 'Flags'] - tbl = PrettyTable(fields) - for (n, r) in enumerate(routes): + + if routes.get('ipv6') is not None: + fields_v6 = ['Route', 'Proto', 'Recv-Q', 'Send-Q', 'Local Address', + 'Foreign Address', 'State'] + + tbl_v4 = PrettyTable(fields_v4) + for (n, r) in enumerate(routes.get('ipv4')): route_id = str(n) - tbl.add_row([route_id, r['destination'], + tbl_v4.add_row([route_id, r['destination'], r['gateway'], r['genmask'], r['iface'], r['flags']]) - route_s = tbl.get_string() + route_s = tbl_v4.get_string() + if fields_v6: + tbl_v6 = PrettyTable(fields_v6) + for (n, r) in enumerate(routes.get('ipv6')): + route_id = str(n) + tbl_v6.add_row([route_id, r['proto'], + r['recv-q'], r['send-q'], + r['local address'], r['foreign address'], + r['state']]) + route_s = route_s + tbl_v6.get_string() + max_len = len(max(route_s.splitlines(), key=len)) header = util.center("Route info", "+", max_len) lines.extend([header, route_s]) -- cgit v1.2.3 From 563467f2d62def448dbb8bacf33ae25782b2849e Mon Sep 17 00:00:00 2001 From: Shraddha Pandhe Date: Fri, 21 Nov 2014 22:17:26 +0000 Subject: Added unittests for IPv6 support for RHEL --- cloudinit/distros/rhel.py | 1 + tests/unittests/test_distros/test_netconfig.py | 93 ++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) (limited to 'cloudinit/distros') diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py index fa3ccb38..13335df5 100644 --- a/cloudinit/distros/rhel.py +++ b/cloudinit/distros/rhel.py @@ -106,6 +106,7 @@ class Distro(distros.Distro): # If IPv6 interface present, enable ipv6 networking if use_ipv6: net_cfg['NETWORKING_IPV6'] = _make_sysconfig_bool(True) + net_cfg['IPV6_AUTOCONF'] = _make_sysconfig_bool(False) rhel_util.update_sysconfig_file(self.network_conf_fn, net_cfg) return dev_names diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py index 35cc1f43..cb385da7 100644 --- a/tests/unittests/test_distros/test_netconfig.py +++ b/tests/unittests/test_distros/test_netconfig.py @@ -30,6 +30,24 @@ auto eth1 iface eth1 inet dhcp ''' +BASE_NET_CFG_IPV6 = ''' +auto lo +iface lo inet loopback + +auto eth0 +iface eth0 inet static + address 192.168.1.5 + netmask 255.255.255.0 + network 192.168.0.0 + broadcast 192.168.1.0 + gateway 192.168.1.254 + +iface eth0 inet6 static + address 2607:f0d0:1002:0011::2 + netmask 64 + gateway 2607:f0d0:1002:0011::1 +''' + class WriteBuffer(object): def __init__(self): @@ -174,6 +192,81 @@ NETWORKING=yes self.assertCfgEquals(expected_buf, str(write_buf)) self.assertEquals(write_buf.mode, 0644) + def test_write_ipv6_rhel(self): + rh_distro = self._get_distro('rhel') + write_mock = self.mocker.replace(util.write_file, + spec=False, passthrough=False) + load_mock = self.mocker.replace(util.load_file, + spec=False, passthrough=False) + exists_mock = self.mocker.replace(os.path.isfile, + spec=False, passthrough=False) + + write_bufs = {} + + def replace_write(filename, content, mode=0644, omode="wb"): + buf = WriteBuffer() + buf.mode = mode + buf.omode = omode + buf.write(content) + write_bufs[filename] = buf + + exists_mock(mocker.ARGS) + self.mocker.count(0, None) + self.mocker.result(False) + + load_mock(mocker.ARGS) + self.mocker.count(0, None) + self.mocker.result('') + + for _i in range(0, 2): + write_mock(mocker.ARGS) + self.mocker.call(replace_write) + + write_mock(mocker.ARGS) + self.mocker.call(replace_write) + + self.mocker.replay() + rh_distro.apply_network(BASE_NET_CFG_IPV6, False) + + self.assertEquals(len(write_bufs), 3) + self.assertIn('/etc/sysconfig/network-scripts/ifcfg-lo', write_bufs) + write_buf = write_bufs['/etc/sysconfig/network-scripts/ifcfg-lo'] + expected_buf = ''' +DEVICE="lo" +ONBOOT=yes +''' + self.assertCfgEquals(expected_buf, str(write_buf)) + self.assertEquals(write_buf.mode, 0644) + + self.assertIn('/etc/sysconfig/network-scripts/ifcfg-eth0', write_bufs) + write_buf = write_bufs['/etc/sysconfig/network-scripts/ifcfg-eth0'] + expected_buf = ''' +DEVICE="eth0" +BOOTPROTO="static" +NETMASK="255.255.255.0" +IPADDR="192.168.1.5" +ONBOOT=yes +GATEWAY="192.168.1.254" +BROADCAST="192.168.1.0" +IPV6INIT=yes +IPV6ADDR="2607:f0d0:1002:0011::2" +IPV6_DEFAULTGW="2607:f0d0:1002:0011::1" +''' + self.assertCfgEquals(expected_buf, str(write_buf)) + self.assertEquals(write_buf.mode, 0644) + + self.assertIn('/etc/sysconfig/network', write_bufs) + write_buf = write_bufs['/etc/sysconfig/network'] + expected_buf = ''' +# Created by cloud-init v. 0.7 +NETWORKING=yes +NETWORKING_IPV6=yes +IPV6_AUTOCONF=no +''' + self.assertCfgEquals(expected_buf, str(write_buf)) + self.assertEquals(write_buf.mode, 0644) + + def test_simple_write_freebsd(self): fbsd_distro = self._get_distro('freebsd') util_mock = self.mocker.replace(util.write_file, -- cgit v1.2.3 From 4dfba8913a24da7254bf47017a264b28c7a49cd2 Mon Sep 17 00:00:00 2001 From: Shraddha Pandhe Date: Mon, 24 Nov 2014 19:36:18 +0000 Subject: IPv6 support for rhel distro - Saw an issue in my earlier commit with multiple NICs. This commit fixes that issue, along with the indentation issue --- cloudinit/distros/net_util.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'cloudinit/distros') diff --git a/cloudinit/distros/net_util.py b/cloudinit/distros/net_util.py index f56f6ccd..f8c34846 100644 --- a/cloudinit/distros/net_util.py +++ b/cloudinit/distros/net_util.py @@ -169,15 +169,16 @@ def translate_network(settings): real_ifaces[dev_name] = iface_info # Check for those that should be started on boot via 'auto' for (cmd, args) in entries: + args = args.split(None) + if not args: + continue + dev_name = args[0].strip().lower() if cmd == 'auto': # Seems like auto can be like 'auto eth0 eth0:1' so just get the # first part out as the device name - args = args.split(None) - if not args: - continue - dev_name = args[0].strip().lower() if dev_name in real_ifaces: real_ifaces[dev_name]['auto'] = True if cmd == 'iface' and 'inet6' in args: - real_ifaces[dev_name]['inet6'] = True + real_ifaces[dev_name]['inet6'] = True return real_ifaces + -- cgit v1.2.3