Merge lp:~shraddha-pandhe/cloud-init/cloud-init-ipv6-support into lp:~cloud-init-dev/cloud-init/trunk

Proposed by Shraddha Pandhe
Status: Merged
Approved by: Joshua Harlow
Approved revision: 1037
Merged at revision: 1042
Proposed branch: lp:~shraddha-pandhe/cloud-init/cloud-init-ipv6-support
Merge into: lp:~cloud-init-dev/cloud-init/trunk
Diff against target: 379 lines (+229/-33)
4 files modified
cloudinit/distros/net_util.py (+45/-24)
cloudinit/distros/rhel.py (+12/-0)
cloudinit/netinfo.py (+51/-9)
tests/unittests/test_distros/test_netconfig.py (+121/-0)
To merge this branch: bzr merge lp:~shraddha-pandhe/cloud-init/cloud-init-ipv6-support
Reviewer Review Type Date Requested Status
cloud-init Commiters Pending
Review via email: mp+242547@code.launchpad.net

Description of the change

IPv6 support for rhel

To post a comment you must log in.
Revision history for this message
Joshua Harlow (harlowja) :
1036. By Shraddha Pandhe

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

1037. By Shraddha Pandhe

Updated unittests
 + Scenario with multiple NICs

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cloudinit/distros/net_util.py'
2--- cloudinit/distros/net_util.py 2014-01-24 21:20:54 +0000
3+++ cloudinit/distros/net_util.py 2014-11-24 19:55:06 +0000
4@@ -113,6 +113,10 @@
5 for info in ifaces:
6 if 'iface' not in info:
7 continue
8+ use_ipv6 = False
9+ # Check if current device has an ipv6 IP
10+ if 'inet6' in info['iface']:
11+ use_ipv6 = True
12 iface_details = info['iface'].split(None)
13 dev_name = None
14 if len(iface_details) >= 1:
15@@ -122,6 +126,7 @@
16 if not dev_name:
17 continue
18 iface_info = {}
19+ iface_info['ipv6'] = {}
20 if len(iface_details) >= 3:
21 proto_type = iface_details[2].strip().lower()
22 # Seems like this can be 'loopback' which we don't
23@@ -129,35 +134,51 @@
24 if proto_type in ['dhcp', 'static']:
25 iface_info['bootproto'] = proto_type
26 # These can just be copied over
27- for k in ['netmask', 'address', 'gateway', 'broadcast']:
28- if k in info:
29- val = info[k].strip().lower()
30- if val:
31- iface_info[k] = val
32- # Name server info provided??
33- if 'dns-nameservers' in info:
34- iface_info['dns-nameservers'] = info['dns-nameservers'].split()
35- # Name server search info provided??
36- if 'dns-search' in info:
37- iface_info['dns-search'] = info['dns-search'].split()
38- # Is any mac address spoofing going on??
39- if 'hwaddress' in info:
40- hw_info = info['hwaddress'].lower().strip()
41- hw_split = hw_info.split(None, 1)
42- if len(hw_split) == 2 and hw_split[0].startswith('ether'):
43- hw_addr = hw_split[1]
44- if hw_addr:
45- iface_info['hwaddress'] = hw_addr
46- real_ifaces[dev_name] = iface_info
47+ if use_ipv6:
48+ for k in ['address', 'gateway']:
49+ if k in info:
50+ val = info[k].strip().lower()
51+ if val:
52+ iface_info['ipv6'][k] = val
53+ else:
54+ for k in ['netmask', 'address', 'gateway', 'broadcast']:
55+ if k in info:
56+ val = info[k].strip().lower()
57+ if val:
58+ iface_info[k] = val
59+ # Name server info provided??
60+ if 'dns-nameservers' in info:
61+ iface_info['dns-nameservers'] = info['dns-nameservers'].split()
62+ # Name server search info provided??
63+ if 'dns-search' in info:
64+ iface_info['dns-search'] = info['dns-search'].split()
65+ # Is any mac address spoofing going on??
66+ if 'hwaddress' in info:
67+ hw_info = info['hwaddress'].lower().strip()
68+ hw_split = hw_info.split(None, 1)
69+ if len(hw_split) == 2 and hw_split[0].startswith('ether'):
70+ hw_addr = hw_split[1]
71+ if hw_addr:
72+ iface_info['hwaddress'] = hw_addr
73+
74+ # If ipv6 is enabled, device will have multiple IPs.
75+ # Update the dictionary instead of overwriting it
76+ if dev_name in real_ifaces:
77+ real_ifaces[dev_name].update(iface_info)
78+ else:
79+ real_ifaces[dev_name] = iface_info
80 # Check for those that should be started on boot via 'auto'
81 for (cmd, args) in entries:
82+ args = args.split(None)
83+ if not args:
84+ continue
85+ dev_name = args[0].strip().lower()
86 if cmd == 'auto':
87 # Seems like auto can be like 'auto eth0 eth0:1' so just get the
88 # first part out as the device name
89- args = args.split(None)
90- if not args:
91- continue
92- dev_name = args[0].strip().lower()
93 if dev_name in real_ifaces:
94 real_ifaces[dev_name]['auto'] = True
95+ if cmd == 'iface' and 'inet6' in args:
96+ real_ifaces[dev_name]['inet6'] = True
97 return real_ifaces
98+
99
100=== modified file 'cloudinit/distros/rhel.py'
101--- cloudinit/distros/rhel.py 2014-10-17 19:32:41 +0000
102+++ cloudinit/distros/rhel.py 2014-11-24 19:55:06 +0000
103@@ -71,6 +71,7 @@
104 nameservers = []
105 searchservers = []
106 dev_names = entries.keys()
107+ use_ipv6 = False
108 for (dev, info) in entries.iteritems():
109 net_fn = self.network_script_tpl % (dev)
110 net_cfg = {
111@@ -83,6 +84,13 @@
112 'MACADDR': info.get('hwaddress'),
113 'ONBOOT': _make_sysconfig_bool(info.get('auto')),
114 }
115+ if info.get('inet6'):
116+ use_ipv6 = True
117+ net_cfg.update({
118+ 'IPV6INIT': _make_sysconfig_bool(True),
119+ 'IPV6ADDR': info.get('ipv6').get('address'),
120+ 'IPV6_DEFAULTGW': info.get('ipv6').get('gateway'),
121+ })
122 rhel_util.update_sysconfig_file(net_fn, net_cfg)
123 if 'dns-nameservers' in info:
124 nameservers.extend(info['dns-nameservers'])
125@@ -95,6 +103,10 @@
126 net_cfg = {
127 'NETWORKING': _make_sysconfig_bool(True),
128 }
129+ # If IPv6 interface present, enable ipv6 networking
130+ if use_ipv6:
131+ net_cfg['NETWORKING_IPV6'] = _make_sysconfig_bool(True)
132+ net_cfg['IPV6_AUTOCONF'] = _make_sysconfig_bool(False)
133 rhel_util.update_sysconfig_file(self.network_conf_fn, net_cfg)
134 return dev_names
135
136
137=== modified file 'cloudinit/netinfo.py'
138--- cloudinit/netinfo.py 2014-09-12 21:22:29 +0000
139+++ cloudinit/netinfo.py 2014-11-24 19:55:06 +0000
140@@ -72,6 +72,7 @@
141 "bcast:": "bcast", "broadcast": "bcast",
142 "mask:": "mask", "netmask": "mask",
143 "hwaddr": "hwaddr", "ether": "hwaddr",
144+ "scope": "scope",
145 }
146 for origfield, field in ifconfigfields.items():
147 target = "%s%s" % (field, fieldpost)
148@@ -96,7 +97,12 @@
149
150 def route_info():
151 (route_out, _err) = util.subp(["netstat", "-rn"])
152- routes = []
153+ (route_out6, _err6) = util.subp(["netstat", "-A inet6", "-n"])
154+
155+ routes = {}
156+ routes['ipv4'] = []
157+ routes['ipv6'] = []
158+
159 entries = route_out.splitlines()[1:]
160 for line in entries:
161 if not line:
162@@ -132,7 +138,26 @@
163 'iface': toks[7],
164 }
165
166- routes.append(entry)
167+ routes['ipv4'].append(entry)
168+
169+ entries6 = route_out6.splitlines()[1:]
170+ for line in entries6:
171+ if not line:
172+ continue
173+ toks = line.split()
174+
175+ if (len(toks) < 6 or toks[0] == "Kernel" or
176+ toks[0] == "Proto" or toks[0] == "Active"):
177+ continue
178+ entry = {
179+ 'proto': toks[0],
180+ 'recv-q': toks[1],
181+ 'send-q': toks[2],
182+ 'local address': toks[3],
183+ 'foreign address': toks[4],
184+ 'state': toks[5],
185+ }
186+ routes['ipv6'].append(entry)
187 return routes
188
189
190@@ -156,10 +181,12 @@
191 lines.append(util.center("Net device info failed", '!', 80))
192 netdev = None
193 if netdev is not None:
194- fields = ['Device', 'Up', 'Address', 'Mask', 'Hw-Address']
195+ fields = ['Device', 'Up', 'Address', 'Mask', 'Scope', 'Hw-Address']
196 tbl = PrettyTable(fields)
197 for (dev, d) in netdev.iteritems():
198- tbl.add_row([dev, d["up"], d["addr"], d["mask"], d["hwaddr"]])
199+ tbl.add_row([dev, d["up"], d["addr"], d["mask"], ".", d["hwaddr"]])
200+ if d["addr6"]:
201+ tbl.add_row([dev, d["up"], d["addr6"], ".", d["scope6"], d["hwaddr"]])
202 netdev_s = tbl.get_string()
203 max_len = len(max(netdev_s.splitlines(), key=len))
204 header = util.center("Net device info", "+", max_len)
205@@ -176,15 +203,30 @@
206 util.logexc(LOG, "Route info failed: %s" % e)
207 routes = None
208 if routes is not None:
209- fields = ['Route', 'Destination', 'Gateway',
210+ fields_v4 = ['Route', 'Destination', 'Gateway',
211 'Genmask', 'Interface', 'Flags']
212- tbl = PrettyTable(fields)
213- for (n, r) in enumerate(routes):
214+
215+ if routes.get('ipv6') is not None:
216+ fields_v6 = ['Route', 'Proto', 'Recv-Q', 'Send-Q', 'Local Address',
217+ 'Foreign Address', 'State']
218+
219+ tbl_v4 = PrettyTable(fields_v4)
220+ for (n, r) in enumerate(routes.get('ipv4')):
221 route_id = str(n)
222- tbl.add_row([route_id, r['destination'],
223+ tbl_v4.add_row([route_id, r['destination'],
224 r['gateway'], r['genmask'],
225 r['iface'], r['flags']])
226- route_s = tbl.get_string()
227+ route_s = tbl_v4.get_string()
228+ if fields_v6:
229+ tbl_v6 = PrettyTable(fields_v6)
230+ for (n, r) in enumerate(routes.get('ipv6')):
231+ route_id = str(n)
232+ tbl_v6.add_row([route_id, r['proto'],
233+ r['recv-q'], r['send-q'],
234+ r['local address'], r['foreign address'],
235+ r['state']])
236+ route_s = route_s + tbl_v6.get_string()
237+
238 max_len = len(max(route_s.splitlines(), key=len))
239 header = util.center("Route info", "+", max_len)
240 lines.extend([header, route_s])
241
242=== modified file 'tests/unittests/test_distros/test_netconfig.py'
243--- tests/unittests/test_distros/test_netconfig.py 2014-10-11 01:54:28 +0000
244+++ tests/unittests/test_distros/test_netconfig.py 2014-11-24 19:55:06 +0000
245@@ -30,6 +30,36 @@
246 iface eth1 inet dhcp
247 '''
248
249+BASE_NET_CFG_IPV6 = '''
250+auto lo
251+iface lo inet loopback
252+
253+auto eth0
254+iface eth0 inet static
255+ address 192.168.1.5
256+ netmask 255.255.255.0
257+ network 192.168.0.0
258+ broadcast 192.168.1.0
259+ gateway 192.168.1.254
260+
261+iface eth0 inet6 static
262+ address 2607:f0d0:1002:0011::2
263+ netmask 64
264+ gateway 2607:f0d0:1002:0011::1
265+
266+iface eth1 inet static
267+ address 192.168.1.6
268+ netmask 255.255.255.0
269+ network 192.168.0.0
270+ broadcast 192.168.1.0
271+ gateway 192.168.1.254
272+
273+iface eth1 inet6 static
274+ address 2607:f0d0:1002:0011::3
275+ netmask 64
276+ gateway 2607:f0d0:1002:0011::1
277+'''
278+
279
280 class WriteBuffer(object):
281 def __init__(self):
282@@ -174,6 +204,97 @@
283 self.assertCfgEquals(expected_buf, str(write_buf))
284 self.assertEquals(write_buf.mode, 0644)
285
286+ def test_write_ipv6_rhel(self):
287+ rh_distro = self._get_distro('rhel')
288+ write_mock = self.mocker.replace(util.write_file,
289+ spec=False, passthrough=False)
290+ load_mock = self.mocker.replace(util.load_file,
291+ spec=False, passthrough=False)
292+ exists_mock = self.mocker.replace(os.path.isfile,
293+ spec=False, passthrough=False)
294+
295+ write_bufs = {}
296+
297+ def replace_write(filename, content, mode=0644, omode="wb"):
298+ buf = WriteBuffer()
299+ buf.mode = mode
300+ buf.omode = omode
301+ buf.write(content)
302+ write_bufs[filename] = buf
303+
304+ exists_mock(mocker.ARGS)
305+ self.mocker.count(0, None)
306+ self.mocker.result(False)
307+
308+ load_mock(mocker.ARGS)
309+ self.mocker.count(0, None)
310+ self.mocker.result('')
311+
312+ for _i in range(0, 3):
313+ write_mock(mocker.ARGS)
314+ self.mocker.call(replace_write)
315+
316+ write_mock(mocker.ARGS)
317+ self.mocker.call(replace_write)
318+
319+ self.mocker.replay()
320+ rh_distro.apply_network(BASE_NET_CFG_IPV6, False)
321+
322+ self.assertEquals(len(write_bufs), 4)
323+ self.assertIn('/etc/sysconfig/network-scripts/ifcfg-lo', write_bufs)
324+ write_buf = write_bufs['/etc/sysconfig/network-scripts/ifcfg-lo']
325+ expected_buf = '''
326+DEVICE="lo"
327+ONBOOT=yes
328+'''
329+ self.assertCfgEquals(expected_buf, str(write_buf))
330+ self.assertEquals(write_buf.mode, 0644)
331+
332+ self.assertIn('/etc/sysconfig/network-scripts/ifcfg-eth0', write_bufs)
333+ write_buf = write_bufs['/etc/sysconfig/network-scripts/ifcfg-eth0']
334+ expected_buf = '''
335+DEVICE="eth0"
336+BOOTPROTO="static"
337+NETMASK="255.255.255.0"
338+IPADDR="192.168.1.5"
339+ONBOOT=yes
340+GATEWAY="192.168.1.254"
341+BROADCAST="192.168.1.0"
342+IPV6INIT=yes
343+IPV6ADDR="2607:f0d0:1002:0011::2"
344+IPV6_DEFAULTGW="2607:f0d0:1002:0011::1"
345+'''
346+ self.assertCfgEquals(expected_buf, str(write_buf))
347+ self.assertEquals(write_buf.mode, 0644)
348+ self.assertIn('/etc/sysconfig/network-scripts/ifcfg-eth1', write_bufs)
349+ write_buf = write_bufs['/etc/sysconfig/network-scripts/ifcfg-eth1']
350+ expected_buf = '''
351+DEVICE="eth1"
352+BOOTPROTO="static"
353+NETMASK="255.255.255.0"
354+IPADDR="192.168.1.6"
355+ONBOOT=no
356+GATEWAY="192.168.1.254"
357+BROADCAST="192.168.1.0"
358+IPV6INIT=yes
359+IPV6ADDR="2607:f0d0:1002:0011::3"
360+IPV6_DEFAULTGW="2607:f0d0:1002:0011::1"
361+'''
362+ self.assertCfgEquals(expected_buf, str(write_buf))
363+ self.assertEquals(write_buf.mode, 0644)
364+
365+ self.assertIn('/etc/sysconfig/network', write_bufs)
366+ write_buf = write_bufs['/etc/sysconfig/network']
367+ expected_buf = '''
368+# Created by cloud-init v. 0.7
369+NETWORKING=yes
370+NETWORKING_IPV6=yes
371+IPV6_AUTOCONF=no
372+'''
373+ self.assertCfgEquals(expected_buf, str(write_buf))
374+ self.assertEquals(write_buf.mode, 0644)
375+
376+
377 def test_simple_write_freebsd(self):
378 fbsd_distro = self._get_distro('freebsd')
379 util_mock = self.mocker.replace(util.write_file,