Merge lp:~niedbalski/charm-helpers/ip-host-translation into lp:charm-helpers

Proposed by Jorge Niedbalski
Status: Merged
Merged at revision: 326
Proposed branch: lp:~niedbalski/charm-helpers/ip-host-translation
Merge into: lp:charm-helpers
Diff against target: 539 lines (+272/-177)
4 files modified
charmhelpers/contrib/network/ip.py (+84/-1)
charmhelpers/contrib/openstack/utils.py (+6/-72)
tests/contrib/network/test_ip.py (+178/-1)
tests/contrib/openstack/test_openstack_utils.py (+4/-103)
To merge this branch: bzr merge lp:~niedbalski/charm-helpers/ip-host-translation
Reviewer Review Type Date Requested Status
Billy Olsen Approve
charmers Pending
Review via email: mp+251189@code.launchpad.net

Description of the change

- Moved out all the host<->ip translation logic from contrib.openstack to contrib.network module.
- Re-factored the get_host_ip method to also check for the socket.gethostbyname and fallback to the defined 'fallback' parameter if no host is found.
- Added tests for covering the changes.

To post a comment you must log in.
325. By Jorge Niedbalski

Minor lints

326. By Jorge Niedbalski

Minor lints

Revision history for this message
Billy Olsen (billy-olsen) wrote :

LGTM approve

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/network/ip.py'
2--- charmhelpers/contrib/network/ip.py 2015-01-22 06:06:03 +0000
3+++ charmhelpers/contrib/network/ip.py 2015-02-26 23:24:37 +0000
4@@ -17,13 +17,16 @@
5 import glob
6 import re
7 import subprocess
8+import six
9+import socket
10
11 from functools import partial
12
13 from charmhelpers.core.hookenv import unit_get
14 from charmhelpers.fetch import apt_install
15 from charmhelpers.core.hookenv import (
16- log
17+ log,
18+ WARNING,
19 )
20
21 try:
22@@ -365,3 +368,83 @@
23 return True
24
25 return False
26+
27+
28+def is_ip(address):
29+ """
30+ Returns True if address is a valid IP address.
31+ """
32+ try:
33+ # Test to see if already an IPv4 address
34+ socket.inet_aton(address)
35+ return True
36+ except socket.error:
37+ return False
38+
39+
40+def ns_query(address):
41+ try:
42+ import dns.resolver
43+ except ImportError:
44+ apt_install('python-dnspython')
45+ import dns.resolver
46+
47+ if isinstance(address, dns.name.Name):
48+ rtype = 'PTR'
49+ elif isinstance(address, six.string_types):
50+ rtype = 'A'
51+ else:
52+ return None
53+
54+ answers = dns.resolver.query(address, rtype)
55+ if answers:
56+ return str(answers[0])
57+ return None
58+
59+
60+def get_host_ip(hostname, fallback=None):
61+ """
62+ Resolves the IP for a given hostname, or returns
63+ the input if it is already an IP.
64+ """
65+ if is_ip(hostname):
66+ return hostname
67+
68+ ip_addr = ns_query(hostname)
69+ if not ip_addr:
70+ try:
71+ ip_addr = socket.gethostbyname(hostname)
72+ except:
73+ log("Failed to resolve hostname '%s'" % (hostname),
74+ level=WARNING)
75+ return fallback
76+ return ip_addr
77+
78+
79+def get_hostname(address, fqdn=True):
80+ """
81+ Resolves hostname for given IP, or returns the input
82+ if it is already a hostname.
83+ """
84+ if is_ip(address):
85+ try:
86+ import dns.reversename
87+ except ImportError:
88+ apt_install("python-dnspython")
89+ import dns.reversename
90+
91+ rev = dns.reversename.from_address(address)
92+ result = ns_query(rev)
93+ if not result:
94+ return None
95+ else:
96+ result = address
97+
98+ if fqdn:
99+ # strip trailing .
100+ if result.endswith('.'):
101+ return result[:-1]
102+ else:
103+ return result
104+ else:
105+ return result.split('.')[0]
106
107=== modified file 'charmhelpers/contrib/openstack/utils.py'
108--- charmhelpers/contrib/openstack/utils.py 2015-02-16 21:55:34 +0000
109+++ charmhelpers/contrib/openstack/utils.py 2015-02-26 23:24:37 +0000
110@@ -23,12 +23,13 @@
111 import subprocess
112 import json
113 import os
114-import socket
115 import sys
116
117 import six
118 import yaml
119
120+from charmhelpers.contrib.network import ip
121+
122 from charmhelpers.core.hookenv import (
123 config,
124 log as juju_log,
125@@ -421,77 +422,10 @@
126 else:
127 zap_disk(block_device)
128
129-
130-def is_ip(address):
131- """
132- Returns True if address is a valid IP address.
133- """
134- try:
135- # Test to see if already an IPv4 address
136- socket.inet_aton(address)
137- return True
138- except socket.error:
139- return False
140-
141-
142-def ns_query(address):
143- try:
144- import dns.resolver
145- except ImportError:
146- apt_install('python-dnspython')
147- import dns.resolver
148-
149- if isinstance(address, dns.name.Name):
150- rtype = 'PTR'
151- elif isinstance(address, six.string_types):
152- rtype = 'A'
153- else:
154- return None
155-
156- answers = dns.resolver.query(address, rtype)
157- if answers:
158- return str(answers[0])
159- return None
160-
161-
162-def get_host_ip(hostname):
163- """
164- Resolves the IP for a given hostname, or returns
165- the input if it is already an IP.
166- """
167- if is_ip(hostname):
168- return hostname
169-
170- return ns_query(hostname)
171-
172-
173-def get_hostname(address, fqdn=True):
174- """
175- Resolves hostname for given IP, or returns the input
176- if it is already a hostname.
177- """
178- if is_ip(address):
179- try:
180- import dns.reversename
181- except ImportError:
182- apt_install('python-dnspython')
183- import dns.reversename
184-
185- rev = dns.reversename.from_address(address)
186- result = ns_query(rev)
187- if not result:
188- return None
189- else:
190- result = address
191-
192- if fqdn:
193- # strip trailing .
194- if result.endswith('.'):
195- return result[:-1]
196- else:
197- return result
198- else:
199- return result.split('.')[0]
200+is_ip = ip.is_ip
201+ns_query = ip.ns_query
202+get_host_ip = ip.get_host_ip
203+get_hostname = ip.get_hostname
204
205
206 def get_matchmaker_map(mm_file='/etc/oslo/matchmaker_ring.json'):
207
208=== modified file 'tests/contrib/network/test_ip.py'
209--- tests/contrib/network/test_ip.py 2014-11-25 15:07:02 +0000
210+++ tests/contrib/network/test_ip.py 2015-02-26 23:24:37 +0000
211@@ -5,8 +5,17 @@
212 import netifaces
213
214 import charmhelpers.contrib.network.ip as net_ip
215-from mock import patch
216+from mock import patch, MagicMock
217+
218 import nose.tools
219+import six
220+
221+if not six.PY3:
222+ builtin_open = '__builtin__.open'
223+ builtin_import = '__builtin__.__import__'
224+else:
225+ builtin_open = 'builtins.open'
226+ builtin_import = 'builtins.__import__'
227
228 DUMMY_ADDRESSES = {
229 'lo': {
230@@ -82,6 +91,43 @@
231 """
232
233
234+class FakeAnswer(object):
235+ def __init__(self, ip):
236+ self.ip = ip
237+
238+ def __str__(self):
239+ return self.ip
240+
241+
242+class FakeResolver(object):
243+ def __init__(self, ip):
244+ self.ip = ip
245+
246+ def query(self, hostname, query_type):
247+ if self.ip == '':
248+ return []
249+ else:
250+ return [FakeAnswer(self.ip)]
251+
252+
253+class FakeReverse(object):
254+ def from_address(self, address):
255+ return '156.94.189.91.in-addr.arpa'
256+
257+
258+class FakeDNSName(object):
259+ def __init__(self, dnsname):
260+ pass
261+
262+
263+class FakeDNS(object):
264+ def __init__(self, ip):
265+ self.resolver = FakeResolver(ip)
266+ self.reversename = FakeReverse()
267+ self.name = MagicMock()
268+ self.name.Name = FakeDNSName
269+
270+
271 class IPTest(unittest.TestCase):
272
273 def mock_ifaddresses(self, iface):
274@@ -501,3 +547,134 @@
275
276 with nose.tools.assert_raises(Exception):
277 net_ip.get_iface_from_addr('1.2.3.4')
278+
279+ def test_is_ip(self):
280+ self.assertTrue(net_ip.is_ip('10.0.0.1'))
281+ self.assertFalse(net_ip.is_ip('www.ubuntu.com'))
282+
283+ @patch('charmhelpers.contrib.network.ip.apt_install')
284+ def test_get_host_ip_with_hostname(self, apt_install):
285+ fake_dns = FakeDNS('10.0.0.1')
286+ with patch(builtin_import, side_effect=[fake_dns]):
287+ ip = net_ip.get_host_ip('www.ubuntu.com')
288+ self.assertEquals(ip, '10.0.0.1')
289+
290+ @patch('charmhelpers.contrib.network.ip.ns_query')
291+ @patch('charmhelpers.contrib.network.ip.socket.gethostbyname')
292+ @patch('charmhelpers.contrib.network.ip.apt_install')
293+ def test_get_host_ip_with_hostname_no_dns(self, apt_install, socket,
294+ ns_query):
295+ ns_query.return_value = []
296+ fake_dns = FakeDNS(None)
297+ socket.return_value = '10.0.0.1'
298+ with patch(builtin_import, side_effect=[fake_dns]):
299+ ip = net_ip.get_host_ip('www.ubuntu.com')
300+ self.assertEquals(ip, '10.0.0.1')
301+
302+ @patch('charmhelpers.contrib.network.ip.log')
303+ @patch('charmhelpers.contrib.network.ip.ns_query')
304+ @patch('charmhelpers.contrib.network.ip.socket.gethostbyname')
305+ @patch('charmhelpers.contrib.network.ip.apt_install')
306+ def test_get_host_ip_with_hostname_fallback(self, apt_install, socket,
307+ ns_query, *args):
308+ ns_query.return_value = []
309+ fake_dns = FakeDNS(None)
310+
311+ def r():
312+ raise Exception()
313+
314+ socket.side_effect = r
315+ with patch(builtin_import, side_effect=[fake_dns]):
316+ ip = net_ip.get_host_ip('www.ubuntu.com', fallback='127.0.0.1')
317+ self.assertEquals(ip, '127.0.0.1')
318+
319+ @patch('charmhelpers.contrib.network.ip.apt_install')
320+ def test_get_host_ip_with_ip(self, apt_install):
321+ fake_dns = FakeDNS('5.5.5.5')
322+ with patch(builtin_import, side_effect=[fake_dns]):
323+ ip = net_ip.get_host_ip('4.2.2.1')
324+ self.assertEquals(ip, '4.2.2.1')
325+
326+ @patch('charmhelpers.contrib.network.ip.apt_install')
327+ def test_ns_query_trigger_apt_install(self, apt_install):
328+ fake_dns = FakeDNS('5.5.5.5')
329+ with patch(builtin_import, side_effect=[ImportError, fake_dns]):
330+ nsq = net_ip.ns_query('5.5.5.5')
331+ apt_install.assert_called_with('python-dnspython')
332+ self.assertEquals(nsq, '5.5.5.5')
333+
334+ @patch('charmhelpers.contrib.network.ip.apt_install')
335+ def test_ns_query_ptr_record(self, apt_install):
336+ fake_dns = FakeDNS('127.0.0.1')
337+ with patch(builtin_import, side_effect=[fake_dns]):
338+ nsq = net_ip.ns_query('127.0.0.1')
339+ self.assertEquals(nsq, '127.0.0.1')
340+
341+ @patch('charmhelpers.contrib.network.ip.apt_install')
342+ def test_ns_query_a_record(self, apt_install):
343+ fake_dns = FakeDNS('127.0.0.1')
344+ fake_dns_name = FakeDNSName('www.somedomain.tld')
345+ with patch(builtin_import, side_effect=[fake_dns]):
346+ nsq = net_ip.ns_query(fake_dns_name)
347+ self.assertEquals(nsq, '127.0.0.1')
348+
349+ @patch('charmhelpers.contrib.network.ip.apt_install')
350+ def test_ns_query_blank_record(self, apt_install):
351+ fake_dns = FakeDNS(None)
352+ with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
353+ nsq = net_ip.ns_query(None)
354+ self.assertEquals(nsq, None)
355+
356+ @patch('charmhelpers.contrib.network.ip.apt_install')
357+ def test_ns_query_lookup_fail(self, apt_install):
358+ fake_dns = FakeDNS('')
359+ with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
360+ nsq = net_ip.ns_query('nonexistant')
361+ self.assertEquals(nsq, None)
362+
363+ @patch('charmhelpers.contrib.network.ip.apt_install')
364+ def test_get_hostname_with_ip(self, apt_install):
365+ fake_dns = FakeDNS('www.ubuntu.com')
366+ with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
367+ hn = net_ip.get_hostname('4.2.2.1')
368+ self.assertEquals(hn, 'www.ubuntu.com')
369+
370+ @patch('charmhelpers.contrib.network.ip.apt_install')
371+ def test_get_hostname_with_ip_not_fqdn(self, apt_install):
372+ fake_dns = FakeDNS('packages.ubuntu.com')
373+ with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
374+ hn = net_ip.get_hostname('4.2.2.1', fqdn=False)
375+ self.assertEquals(hn, 'packages')
376+
377+ @patch('charmhelpers.contrib.network.ip.apt_install')
378+ def test_get_hostname_with_hostname(self, apt_install):
379+ hn = net_ip.get_hostname('www.ubuntu.com')
380+ self.assertEquals(hn, 'www.ubuntu.com')
381+
382+ @patch('charmhelpers.contrib.network.ip.apt_install')
383+ def test_get_hostname_with_hostname_trailingdot(self, apt_install):
384+ hn = net_ip.get_hostname('www.ubuntu.com.')
385+ self.assertEquals(hn, 'www.ubuntu.com')
386+
387+ @patch('charmhelpers.contrib.network.ip.apt_install')
388+ def test_get_hostname_with_hostname_not_fqdn(self, apt_install):
389+ hn = net_ip.get_hostname('packages.ubuntu.com', fqdn=False)
390+ self.assertEquals(hn, 'packages')
391+
392+ @patch('charmhelpers.contrib.network.ip.apt_install')
393+ def test_get_hostname_trigger_apt_install(self, apt_install):
394+ fake_dns = FakeDNS('www.ubuntu.com')
395+ with patch(builtin_import, side_effect=[ImportError, fake_dns,
396+ fake_dns]):
397+ hn = net_ip.get_hostname('4.2.2.1')
398+ apt_install.assert_called_with('python-dnspython')
399+ self.assertEquals(hn, 'www.ubuntu.com')
400+
401+ @patch('charmhelpers.contrib.network.ip.ns_query')
402+ @patch('charmhelpers.contrib.network.ip.apt_install')
403+ def test_get_hostname_lookup_fail(self, apt_install, ns_query):
404+ fake_dns = FakeDNS('www.ubuntu.com')
405+ ns_query.return_value = []
406+ with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
407+ hn = net_ip.get_hostname('4.2.2.1')
408+ self.assertEquals(hn, None)
409
410=== modified file 'tests/contrib/openstack/test_openstack_utils.py'
411--- tests/contrib/openstack/test_openstack_utils.py 2014-12-03 13:27:17 +0000
412+++ tests/contrib/openstack/test_openstack_utils.py 2015-02-26 23:24:37 +0000
413@@ -595,107 +595,6 @@
414 openstack.clean_storage('/dev/vdb')
415 zap_disk.assert_called_with('/dev/vdb')
416
417- def test_is_ip(self):
418- self.assertTrue(openstack.is_ip('10.0.0.1'))
419- self.assertFalse(openstack.is_ip('www.ubuntu.com'))
420-
421- @patch.object(openstack, 'apt_install')
422- def test_get_host_ip_with_hostname(self, apt_install):
423- fake_dns = FakeDNS('10.0.0.1')
424- with patch(builtin_import, side_effect=[fake_dns]):
425- ip = openstack.get_host_ip('www.ubuntu.com')
426- self.assertEquals(ip, '10.0.0.1')
427-
428- @patch.object(openstack, 'apt_install')
429- def test_get_host_ip_with_ip(self, apt_install):
430- fake_dns = FakeDNS('5.5.5.5')
431- with patch(builtin_import, side_effect=[fake_dns]):
432- ip = openstack.get_host_ip('4.2.2.1')
433- self.assertEquals(ip, '4.2.2.1')
434-
435- @patch.object(openstack, 'apt_install')
436- def test_ns_query_trigger_apt_install(self, apt_install):
437- fake_dns = FakeDNS('5.5.5.5')
438- with patch(builtin_import, side_effect=[ImportError, fake_dns]):
439- nsq = openstack.ns_query('5.5.5.5')
440- apt_install.assert_called_with('python-dnspython')
441- self.assertEquals(nsq, '5.5.5.5')
442-
443- @patch.object(openstack, 'apt_install')
444- def test_ns_query_ptr_record(self, apt_install):
445- fake_dns = FakeDNS('127.0.0.1')
446- with patch(builtin_import, side_effect=[fake_dns]):
447- nsq = openstack.ns_query('127.0.0.1')
448- self.assertEquals(nsq, '127.0.0.1')
449-
450- @patch.object(openstack, 'apt_install')
451- def test_ns_query_a_record(self, apt_install):
452- fake_dns = FakeDNS('127.0.0.1')
453- fake_dns_name = FakeDNSName('www.somedomain.tld')
454- with patch(builtin_import, side_effect=[fake_dns]):
455- nsq = openstack.ns_query(fake_dns_name)
456- self.assertEquals(nsq, '127.0.0.1')
457-
458- @patch.object(openstack, 'apt_install')
459- def test_ns_query_blank_record(self, apt_install):
460- fake_dns = FakeDNS(None)
461- with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
462- nsq = openstack.ns_query(None)
463- self.assertEquals(nsq, None)
464-
465- @patch.object(openstack, 'apt_install')
466- def test_ns_query_lookup_fail(self, apt_install):
467- fake_dns = FakeDNS('')
468- with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
469- nsq = openstack.ns_query('nonexistant')
470- self.assertEquals(nsq, None)
471-
472- @patch.object(openstack, 'apt_install')
473- def test_get_hostname_with_ip(self, apt_install):
474- fake_dns = FakeDNS('www.ubuntu.com')
475- with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
476- hn = openstack.get_hostname('4.2.2.1')
477- self.assertEquals(hn, 'www.ubuntu.com')
478-
479- @patch.object(openstack, 'apt_install')
480- def test_get_hostname_with_ip_not_fqdn(self, apt_install):
481- fake_dns = FakeDNS('packages.ubuntu.com')
482- with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
483- hn = openstack.get_hostname('4.2.2.1', fqdn=False)
484- self.assertEquals(hn, 'packages')
485-
486- @patch.object(openstack, 'apt_install')
487- def test_get_hostname_with_hostname(self, apt_install):
488- hn = openstack.get_hostname('www.ubuntu.com')
489- self.assertEquals(hn, 'www.ubuntu.com')
490-
491- @patch.object(openstack, 'apt_install')
492- def test_get_hostname_with_hostname_trailingdot(self, apt_install):
493- hn = openstack.get_hostname('www.ubuntu.com.')
494- self.assertEquals(hn, 'www.ubuntu.com')
495-
496- @patch.object(openstack, 'apt_install')
497- def test_get_hostname_with_hostname_not_fqdn(self, apt_install):
498- hn = openstack.get_hostname('packages.ubuntu.com', fqdn=False)
499- self.assertEquals(hn, 'packages')
500-
501- @patch.object(openstack, 'apt_install')
502- def test_get_hostname_trigger_apt_install(self, apt_install):
503- fake_dns = FakeDNS('www.ubuntu.com')
504- with patch(builtin_import, side_effect=[ImportError, fake_dns, fake_dns]):
505- hn = openstack.get_hostname('4.2.2.1')
506- apt_install.assert_called_with('python-dnspython')
507- self.assertEquals(hn, 'www.ubuntu.com')
508-
509- @patch.object(openstack, 'ns_query')
510- @patch.object(openstack, 'apt_install')
511- def test_get_hostname_lookup_fail(self, apt_install, ns_query):
512- fake_dns = FakeDNS('www.ubuntu.com')
513- ns_query.return_value = []
514- with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
515- hn = openstack.get_hostname('4.2.2.1')
516- self.assertEquals(hn, None)
517-
518 @patch('os.path.isfile')
519 @patch(builtin_open)
520 def test_get_matchmaker_map(self, _open, _isfile):
521@@ -846,7 +745,8 @@
522
523 openstack._git_clone_and_install_single(repo, branch)
524 mkdir.assert_called_with(dest_parent_dir)
525- install_remote.assert_called_with(repo, dest=dest_parent_dir, branch=branch)
526+ install_remote.assert_called_with(repo, dest=dest_parent_dir,
527+ branch=branch)
528 assert not _git_update_reqs.called
529 pip_install.assert_called_with(dest_dir)
530
531@@ -871,7 +771,8 @@
532
533 openstack._git_clone_and_install_single(repo, branch, True)
534 mkdir.assert_called_with(dest_parent_dir)
535- install_remote.assert_called_with(repo, dest=dest_parent_dir, branch=branch)
536+ install_remote.assert_called_with(repo, dest=dest_parent_dir,
537+ branch=branch)
538 _git_update_reqs.assert_called_with(dest_dir, reqs_dir)
539 pip_install.assert_called_with(dest_dir)
540

Subscribers

People subscribed via source and target branches