Merge lp:~wenjianhn/charm-helpers/get-addr-in-net into lp:charm-helpers

Proposed by Jian Wen
Status: Merged
Merged at revision: 109
Proposed branch: lp:~wenjianhn/charm-helpers/get-addr-in-net
Merge into: lp:charm-helpers
Diff against target: 169 lines (+152/-0)
3 files modified
charmhelpers/contrib/network/ip.py (+69/-0)
test_requirements.txt (+2/-0)
tests/contrib/network/test_ip.py (+81/-0)
To merge this branch: bzr merge lp:~wenjianhn/charm-helpers/get-addr-in-net
Reviewer Review Type Date Requested Status
James Page Approve
Review via email: mp+199390@code.launchpad.net

Description of the change

This patch adds a networking helper to get an IPv4 address in a
specified network.

To post a comment you must log in.
Revision history for this message
James Page (james-page) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'charmhelpers/contrib/network/ip.py'
2--- charmhelpers/contrib/network/ip.py 1970-01-01 00:00:00 +0000
3+++ charmhelpers/contrib/network/ip.py 2013-12-18 05:53:32 +0000
4@@ -0,0 +1,69 @@
5+import sys
6+
7+from charmhelpers.fetch import apt_install
8+from charmhelpers.core.hookenv import (
9+ ERROR, log,
10+)
11+
12+try:
13+ import netifaces
14+except ImportError:
15+ apt_install('python-netifaces')
16+ import netifaces
17+
18+try:
19+ import netaddr
20+except ImportError:
21+ apt_install('python-netaddr')
22+ import netaddr
23+
24+
25+def _validate_cidr(network):
26+ try:
27+ netaddr.IPNetwork(network)
28+ except (netaddr.core.AddrFormatError, ValueError):
29+ raise ValueError("Network (%s) is not in CIDR presentation format" %
30+ network)
31+
32+
33+def get_address_in_network(network, fallback=None, fatal=False):
34+ """
35+ Get an IPv4 address within the network from the host.
36+
37+ Args:
38+ network (str): CIDR presentation format. For example,
39+ '192.168.1.0/24'.
40+ fallback (str): If no address is found, return fallback.
41+ fatal (boolean): If no address is found, fallback is not
42+ set and fatal is True then exit(1).
43+ """
44+
45+ def not_found_error_out():
46+ log("No IP address found in network: %s" % network,
47+ level=ERROR)
48+ sys.exit(1)
49+
50+ if network is None:
51+ if fallback is not None:
52+ return fallback
53+ else:
54+ if fatal:
55+ not_found_error_out()
56+
57+ _validate_cidr(network)
58+ for iface in netifaces.interfaces():
59+ addresses = netifaces.ifaddresses(iface)
60+ if netifaces.AF_INET in addresses:
61+ addr = addresses[netifaces.AF_INET][0]['addr']
62+ netmask = addresses[netifaces.AF_INET][0]['netmask']
63+ cidr = netaddr.IPNetwork("%s/%s" % (addr, netmask))
64+ if cidr in netaddr.IPNetwork(network):
65+ return str(cidr.ip)
66+
67+ if fallback is not None:
68+ return fallback
69+
70+ if fatal:
71+ not_found_error_out()
72+
73+ return None
74
75=== modified file 'test_requirements.txt'
76--- test_requirements.txt 2013-05-22 08:12:53 +0000
77+++ test_requirements.txt 2013-12-18 05:53:32 +0000
78@@ -16,3 +16,5 @@
79 testtools==0.9.31
80 Tempita==0.5.1
81 bzr+http://bazaar.launchpad.net/~yellow/python-shelltoolbox/trunk@17#egg=shelltoolbox
82+netifaces==0.6
83+netaddr==0.7.5
84
85=== added file 'tests/contrib/network/test_ip.py'
86--- tests/contrib/network/test_ip.py 1970-01-01 00:00:00 +0000
87+++ tests/contrib/network/test_ip.py 2013-12-18 05:53:32 +0000
88@@ -0,0 +1,81 @@
89+import subprocess
90+import unittest
91+
92+import mock
93+import netifaces
94+
95+import charmhelpers.contrib.network.ip as net_ip
96+
97+
98+class IPTest(unittest.TestCase):
99+
100+ def test_get_address_in_network_with_invalid_net(self):
101+ for net in ['192.168.300/22', '192.168.1.0/2a', '2.a']:
102+ self.assertRaises(ValueError,
103+ net_ip.get_address_in_network,
104+ net)
105+
106+ def _test_get_address_in_network(self, expect_ip_addr,
107+ network, fallback=None, fatal=False):
108+
109+ def side_effect(iface):
110+ ffff = 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'
111+ results = {'lo': {17: [{'peer': '00:00:00:00:00:00',
112+ 'addr': '00:00:00:00:00:00'}],
113+ 2: [{'peer': '127.0.0.1', 'netmask':
114+ '255.0.0.0', 'addr': '127.0.0.1'}],
115+ 10: [{'netmask': ffff,
116+ 'addr': '::1'}]
117+ },
118+ 'eth0': {17: [{'broadcast': 'ff:ff:ff:ff:ff:ff',
119+ 'addr': '28:92:4a:19:8c:e8'}]
120+ },
121+ 'eth2': {17: [{'broadcast': 'ff:ff:ff:ff:ff:ff',
122+ 'addr': 'e0:06:e6:41:dd:dd'}],
123+ 2: [{'broadcast': '192.168.1.255',
124+ 'netmask': '255.255.255.0',
125+ 'addr': '192.168.1.108'}],
126+ 10: [{'netmask': 'ffff:ffff:ffff:ffff::',
127+ 'addr': 'fe80::e206:e6ff:fe41:dddd%eth2'}
128+ ]
129+ },
130+ }
131+ return results[iface]
132+
133+ with mock.patch.object(netifaces, 'interfaces') as interfaces:
134+ interfaces.return_value = ['lo', 'eth0', 'eth2']
135+ with mock.patch.object(netifaces, 'ifaddresses') as ifaddresses:
136+ ifaddresses.side_effect = side_effect
137+ if not fatal:
138+ self.assertEqual(expect_ip_addr,
139+ net_ip.get_address_in_network(
140+ network, fallback, fatal))
141+ else:
142+ net_ip.get_address_in_network(network, fallback, fatal)
143+
144+ @mock.patch.object(subprocess, 'call')
145+ def test_get_address_in_network_with_none(self, popen):
146+ fallback = '10.10.10.10'
147+ self.assertEqual(fallback,
148+ net_ip.get_address_in_network(None, fallback))
149+
150+ self.assertRaises(SystemExit, self._test_get_address_in_network,
151+ None, None, fatal=True)
152+
153+ def test_get_address_in_network_works(self):
154+ self._test_get_address_in_network('192.168.1.108', '192.168.1.0/24')
155+
156+ def test_get_address_in_network_with_non_existent_net(self):
157+ self._test_get_address_in_network(None, '172.16.0.0/16')
158+
159+ def test_get_address_in_network_fallback_works(self):
160+ fallback = '10.10.0.0'
161+ self._test_get_address_in_network(fallback, '172.16.0.0/16', fallback)
162+
163+ @mock.patch.object(subprocess, 'call')
164+ def test_get_address_in_network_not_found_fatal(self, popen):
165+ self.assertRaises(SystemExit, self._test_get_address_in_network,
166+ None, '172.16.0.0/16', fatal=True)
167+
168+ def test_get_address_in_network_not_found_not_fatal(self):
169+ self._test_get_address_in_network(None, '172.16.0.0/16', fatal=False)

Subscribers

People subscribed via source and target branches