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
=== modified file 'charmhelpers/contrib/network/ip.py'
--- charmhelpers/contrib/network/ip.py 2015-01-22 06:06:03 +0000
+++ charmhelpers/contrib/network/ip.py 2015-02-26 23:24:37 +0000
@@ -17,13 +17,16 @@
17import glob17import glob
18import re18import re
19import subprocess19import subprocess
20import six
21import socket
2022
21from functools import partial23from functools import partial
2224
23from charmhelpers.core.hookenv import unit_get25from charmhelpers.core.hookenv import unit_get
24from charmhelpers.fetch import apt_install26from charmhelpers.fetch import apt_install
25from charmhelpers.core.hookenv import (27from charmhelpers.core.hookenv import (
26 log28 log,
29 WARNING,
27)30)
2831
29try:32try:
@@ -365,3 +368,83 @@
365 return True368 return True
366369
367 return False370 return False
371
372
373def is_ip(address):
374 """
375 Returns True if address is a valid IP address.
376 """
377 try:
378 # Test to see if already an IPv4 address
379 socket.inet_aton(address)
380 return True
381 except socket.error:
382 return False
383
384
385def ns_query(address):
386 try:
387 import dns.resolver
388 except ImportError:
389 apt_install('python-dnspython')
390 import dns.resolver
391
392 if isinstance(address, dns.name.Name):
393 rtype = 'PTR'
394 elif isinstance(address, six.string_types):
395 rtype = 'A'
396 else:
397 return None
398
399 answers = dns.resolver.query(address, rtype)
400 if answers:
401 return str(answers[0])
402 return None
403
404
405def get_host_ip(hostname, fallback=None):
406 """
407 Resolves the IP for a given hostname, or returns
408 the input if it is already an IP.
409 """
410 if is_ip(hostname):
411 return hostname
412
413 ip_addr = ns_query(hostname)
414 if not ip_addr:
415 try:
416 ip_addr = socket.gethostbyname(hostname)
417 except:
418 log("Failed to resolve hostname '%s'" % (hostname),
419 level=WARNING)
420 return fallback
421 return ip_addr
422
423
424def get_hostname(address, fqdn=True):
425 """
426 Resolves hostname for given IP, or returns the input
427 if it is already a hostname.
428 """
429 if is_ip(address):
430 try:
431 import dns.reversename
432 except ImportError:
433 apt_install("python-dnspython")
434 import dns.reversename
435
436 rev = dns.reversename.from_address(address)
437 result = ns_query(rev)
438 if not result:
439 return None
440 else:
441 result = address
442
443 if fqdn:
444 # strip trailing .
445 if result.endswith('.'):
446 return result[:-1]
447 else:
448 return result
449 else:
450 return result.split('.')[0]
368451
=== modified file 'charmhelpers/contrib/openstack/utils.py'
--- charmhelpers/contrib/openstack/utils.py 2015-02-16 21:55:34 +0000
+++ charmhelpers/contrib/openstack/utils.py 2015-02-26 23:24:37 +0000
@@ -23,12 +23,13 @@
23import subprocess23import subprocess
24import json24import json
25import os25import os
26import socket
27import sys26import sys
2827
29import six28import six
30import yaml29import yaml
3130
31from charmhelpers.contrib.network import ip
32
32from charmhelpers.core.hookenv import (33from charmhelpers.core.hookenv import (
33 config,34 config,
34 log as juju_log,35 log as juju_log,
@@ -421,77 +422,10 @@
421 else:422 else:
422 zap_disk(block_device)423 zap_disk(block_device)
423424
424425is_ip = ip.is_ip
425def is_ip(address):426ns_query = ip.ns_query
426 """427get_host_ip = ip.get_host_ip
427 Returns True if address is a valid IP address.428get_hostname = ip.get_hostname
428 """
429 try:
430 # Test to see if already an IPv4 address
431 socket.inet_aton(address)
432 return True
433 except socket.error:
434 return False
435
436
437def ns_query(address):
438 try:
439 import dns.resolver
440 except ImportError:
441 apt_install('python-dnspython')
442 import dns.resolver
443
444 if isinstance(address, dns.name.Name):
445 rtype = 'PTR'
446 elif isinstance(address, six.string_types):
447 rtype = 'A'
448 else:
449 return None
450
451 answers = dns.resolver.query(address, rtype)
452 if answers:
453 return str(answers[0])
454 return None
455
456
457def get_host_ip(hostname):
458 """
459 Resolves the IP for a given hostname, or returns
460 the input if it is already an IP.
461 """
462 if is_ip(hostname):
463 return hostname
464
465 return ns_query(hostname)
466
467
468def get_hostname(address, fqdn=True):
469 """
470 Resolves hostname for given IP, or returns the input
471 if it is already a hostname.
472 """
473 if is_ip(address):
474 try:
475 import dns.reversename
476 except ImportError:
477 apt_install('python-dnspython')
478 import dns.reversename
479
480 rev = dns.reversename.from_address(address)
481 result = ns_query(rev)
482 if not result:
483 return None
484 else:
485 result = address
486
487 if fqdn:
488 # strip trailing .
489 if result.endswith('.'):
490 return result[:-1]
491 else:
492 return result
493 else:
494 return result.split('.')[0]
495429
496430
497def get_matchmaker_map(mm_file='/etc/oslo/matchmaker_ring.json'):431def get_matchmaker_map(mm_file='/etc/oslo/matchmaker_ring.json'):
498432
=== modified file 'tests/contrib/network/test_ip.py'
--- tests/contrib/network/test_ip.py 2014-11-25 15:07:02 +0000
+++ tests/contrib/network/test_ip.py 2015-02-26 23:24:37 +0000
@@ -5,8 +5,17 @@
5import netifaces5import netifaces
66
7import charmhelpers.contrib.network.ip as net_ip7import charmhelpers.contrib.network.ip as net_ip
8from mock import patch8from mock import patch, MagicMock
9
9import nose.tools10import nose.tools
11import six
12
13if not six.PY3:
14 builtin_open = '__builtin__.open'
15 builtin_import = '__builtin__.__import__'
16else:
17 builtin_open = 'builtins.open'
18 builtin_import = 'builtins.__import__'
1019
11DUMMY_ADDRESSES = {20DUMMY_ADDRESSES = {
12 'lo': {21 'lo': {
@@ -82,6 +91,43 @@
82"""91"""
8392
8493
94class FakeAnswer(object):
95 def __init__(self, ip):
96 self.ip = ip
97
98 def __str__(self):
99 return self.ip
100
101
102class FakeResolver(object):
103 def __init__(self, ip):
104 self.ip = ip
105
106 def query(self, hostname, query_type):
107 if self.ip == '':
108 return []
109 else:
110 return [FakeAnswer(self.ip)]
111
112
113class FakeReverse(object):
114 def from_address(self, address):
115 return '156.94.189.91.in-addr.arpa'
116
117
118class FakeDNSName(object):
119 def __init__(self, dnsname):
120 pass
121
122
123class FakeDNS(object):
124 def __init__(self, ip):
125 self.resolver = FakeResolver(ip)
126 self.reversename = FakeReverse()
127 self.name = MagicMock()
128 self.name.Name = FakeDNSName
129
130
85class IPTest(unittest.TestCase):131class IPTest(unittest.TestCase):
86132
87 def mock_ifaddresses(self, iface):133 def mock_ifaddresses(self, iface):
@@ -501,3 +547,134 @@
501547
502 with nose.tools.assert_raises(Exception):548 with nose.tools.assert_raises(Exception):
503 net_ip.get_iface_from_addr('1.2.3.4')549 net_ip.get_iface_from_addr('1.2.3.4')
550
551 def test_is_ip(self):
552 self.assertTrue(net_ip.is_ip('10.0.0.1'))
553 self.assertFalse(net_ip.is_ip('www.ubuntu.com'))
554
555 @patch('charmhelpers.contrib.network.ip.apt_install')
556 def test_get_host_ip_with_hostname(self, apt_install):
557 fake_dns = FakeDNS('10.0.0.1')
558 with patch(builtin_import, side_effect=[fake_dns]):
559 ip = net_ip.get_host_ip('www.ubuntu.com')
560 self.assertEquals(ip, '10.0.0.1')
561
562 @patch('charmhelpers.contrib.network.ip.ns_query')
563 @patch('charmhelpers.contrib.network.ip.socket.gethostbyname')
564 @patch('charmhelpers.contrib.network.ip.apt_install')
565 def test_get_host_ip_with_hostname_no_dns(self, apt_install, socket,
566 ns_query):
567 ns_query.return_value = []
568 fake_dns = FakeDNS(None)
569 socket.return_value = '10.0.0.1'
570 with patch(builtin_import, side_effect=[fake_dns]):
571 ip = net_ip.get_host_ip('www.ubuntu.com')
572 self.assertEquals(ip, '10.0.0.1')
573
574 @patch('charmhelpers.contrib.network.ip.log')
575 @patch('charmhelpers.contrib.network.ip.ns_query')
576 @patch('charmhelpers.contrib.network.ip.socket.gethostbyname')
577 @patch('charmhelpers.contrib.network.ip.apt_install')
578 def test_get_host_ip_with_hostname_fallback(self, apt_install, socket,
579 ns_query, *args):
580 ns_query.return_value = []
581 fake_dns = FakeDNS(None)
582
583 def r():
584 raise Exception()
585
586 socket.side_effect = r
587 with patch(builtin_import, side_effect=[fake_dns]):
588 ip = net_ip.get_host_ip('www.ubuntu.com', fallback='127.0.0.1')
589 self.assertEquals(ip, '127.0.0.1')
590
591 @patch('charmhelpers.contrib.network.ip.apt_install')
592 def test_get_host_ip_with_ip(self, apt_install):
593 fake_dns = FakeDNS('5.5.5.5')
594 with patch(builtin_import, side_effect=[fake_dns]):
595 ip = net_ip.get_host_ip('4.2.2.1')
596 self.assertEquals(ip, '4.2.2.1')
597
598 @patch('charmhelpers.contrib.network.ip.apt_install')
599 def test_ns_query_trigger_apt_install(self, apt_install):
600 fake_dns = FakeDNS('5.5.5.5')
601 with patch(builtin_import, side_effect=[ImportError, fake_dns]):
602 nsq = net_ip.ns_query('5.5.5.5')
603 apt_install.assert_called_with('python-dnspython')
604 self.assertEquals(nsq, '5.5.5.5')
605
606 @patch('charmhelpers.contrib.network.ip.apt_install')
607 def test_ns_query_ptr_record(self, apt_install):
608 fake_dns = FakeDNS('127.0.0.1')
609 with patch(builtin_import, side_effect=[fake_dns]):
610 nsq = net_ip.ns_query('127.0.0.1')
611 self.assertEquals(nsq, '127.0.0.1')
612
613 @patch('charmhelpers.contrib.network.ip.apt_install')
614 def test_ns_query_a_record(self, apt_install):
615 fake_dns = FakeDNS('127.0.0.1')
616 fake_dns_name = FakeDNSName('www.somedomain.tld')
617 with patch(builtin_import, side_effect=[fake_dns]):
618 nsq = net_ip.ns_query(fake_dns_name)
619 self.assertEquals(nsq, '127.0.0.1')
620
621 @patch('charmhelpers.contrib.network.ip.apt_install')
622 def test_ns_query_blank_record(self, apt_install):
623 fake_dns = FakeDNS(None)
624 with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
625 nsq = net_ip.ns_query(None)
626 self.assertEquals(nsq, None)
627
628 @patch('charmhelpers.contrib.network.ip.apt_install')
629 def test_ns_query_lookup_fail(self, apt_install):
630 fake_dns = FakeDNS('')
631 with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
632 nsq = net_ip.ns_query('nonexistant')
633 self.assertEquals(nsq, None)
634
635 @patch('charmhelpers.contrib.network.ip.apt_install')
636 def test_get_hostname_with_ip(self, apt_install):
637 fake_dns = FakeDNS('www.ubuntu.com')
638 with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
639 hn = net_ip.get_hostname('4.2.2.1')
640 self.assertEquals(hn, 'www.ubuntu.com')
641
642 @patch('charmhelpers.contrib.network.ip.apt_install')
643 def test_get_hostname_with_ip_not_fqdn(self, apt_install):
644 fake_dns = FakeDNS('packages.ubuntu.com')
645 with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
646 hn = net_ip.get_hostname('4.2.2.1', fqdn=False)
647 self.assertEquals(hn, 'packages')
648
649 @patch('charmhelpers.contrib.network.ip.apt_install')
650 def test_get_hostname_with_hostname(self, apt_install):
651 hn = net_ip.get_hostname('www.ubuntu.com')
652 self.assertEquals(hn, 'www.ubuntu.com')
653
654 @patch('charmhelpers.contrib.network.ip.apt_install')
655 def test_get_hostname_with_hostname_trailingdot(self, apt_install):
656 hn = net_ip.get_hostname('www.ubuntu.com.')
657 self.assertEquals(hn, 'www.ubuntu.com')
658
659 @patch('charmhelpers.contrib.network.ip.apt_install')
660 def test_get_hostname_with_hostname_not_fqdn(self, apt_install):
661 hn = net_ip.get_hostname('packages.ubuntu.com', fqdn=False)
662 self.assertEquals(hn, 'packages')
663
664 @patch('charmhelpers.contrib.network.ip.apt_install')
665 def test_get_hostname_trigger_apt_install(self, apt_install):
666 fake_dns = FakeDNS('www.ubuntu.com')
667 with patch(builtin_import, side_effect=[ImportError, fake_dns,
668 fake_dns]):
669 hn = net_ip.get_hostname('4.2.2.1')
670 apt_install.assert_called_with('python-dnspython')
671 self.assertEquals(hn, 'www.ubuntu.com')
672
673 @patch('charmhelpers.contrib.network.ip.ns_query')
674 @patch('charmhelpers.contrib.network.ip.apt_install')
675 def test_get_hostname_lookup_fail(self, apt_install, ns_query):
676 fake_dns = FakeDNS('www.ubuntu.com')
677 ns_query.return_value = []
678 with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
679 hn = net_ip.get_hostname('4.2.2.1')
680 self.assertEquals(hn, None)
504681
=== modified file 'tests/contrib/openstack/test_openstack_utils.py'
--- tests/contrib/openstack/test_openstack_utils.py 2014-12-03 13:27:17 +0000
+++ tests/contrib/openstack/test_openstack_utils.py 2015-02-26 23:24:37 +0000
@@ -595,107 +595,6 @@
595 openstack.clean_storage('/dev/vdb')595 openstack.clean_storage('/dev/vdb')
596 zap_disk.assert_called_with('/dev/vdb')596 zap_disk.assert_called_with('/dev/vdb')
597597
598 def test_is_ip(self):
599 self.assertTrue(openstack.is_ip('10.0.0.1'))
600 self.assertFalse(openstack.is_ip('www.ubuntu.com'))
601
602 @patch.object(openstack, 'apt_install')
603 def test_get_host_ip_with_hostname(self, apt_install):
604 fake_dns = FakeDNS('10.0.0.1')
605 with patch(builtin_import, side_effect=[fake_dns]):
606 ip = openstack.get_host_ip('www.ubuntu.com')
607 self.assertEquals(ip, '10.0.0.1')
608
609 @patch.object(openstack, 'apt_install')
610 def test_get_host_ip_with_ip(self, apt_install):
611 fake_dns = FakeDNS('5.5.5.5')
612 with patch(builtin_import, side_effect=[fake_dns]):
613 ip = openstack.get_host_ip('4.2.2.1')
614 self.assertEquals(ip, '4.2.2.1')
615
616 @patch.object(openstack, 'apt_install')
617 def test_ns_query_trigger_apt_install(self, apt_install):
618 fake_dns = FakeDNS('5.5.5.5')
619 with patch(builtin_import, side_effect=[ImportError, fake_dns]):
620 nsq = openstack.ns_query('5.5.5.5')
621 apt_install.assert_called_with('python-dnspython')
622 self.assertEquals(nsq, '5.5.5.5')
623
624 @patch.object(openstack, 'apt_install')
625 def test_ns_query_ptr_record(self, apt_install):
626 fake_dns = FakeDNS('127.0.0.1')
627 with patch(builtin_import, side_effect=[fake_dns]):
628 nsq = openstack.ns_query('127.0.0.1')
629 self.assertEquals(nsq, '127.0.0.1')
630
631 @patch.object(openstack, 'apt_install')
632 def test_ns_query_a_record(self, apt_install):
633 fake_dns = FakeDNS('127.0.0.1')
634 fake_dns_name = FakeDNSName('www.somedomain.tld')
635 with patch(builtin_import, side_effect=[fake_dns]):
636 nsq = openstack.ns_query(fake_dns_name)
637 self.assertEquals(nsq, '127.0.0.1')
638
639 @patch.object(openstack, 'apt_install')
640 def test_ns_query_blank_record(self, apt_install):
641 fake_dns = FakeDNS(None)
642 with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
643 nsq = openstack.ns_query(None)
644 self.assertEquals(nsq, None)
645
646 @patch.object(openstack, 'apt_install')
647 def test_ns_query_lookup_fail(self, apt_install):
648 fake_dns = FakeDNS('')
649 with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
650 nsq = openstack.ns_query('nonexistant')
651 self.assertEquals(nsq, None)
652
653 @patch.object(openstack, 'apt_install')
654 def test_get_hostname_with_ip(self, apt_install):
655 fake_dns = FakeDNS('www.ubuntu.com')
656 with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
657 hn = openstack.get_hostname('4.2.2.1')
658 self.assertEquals(hn, 'www.ubuntu.com')
659
660 @patch.object(openstack, 'apt_install')
661 def test_get_hostname_with_ip_not_fqdn(self, apt_install):
662 fake_dns = FakeDNS('packages.ubuntu.com')
663 with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
664 hn = openstack.get_hostname('4.2.2.1', fqdn=False)
665 self.assertEquals(hn, 'packages')
666
667 @patch.object(openstack, 'apt_install')
668 def test_get_hostname_with_hostname(self, apt_install):
669 hn = openstack.get_hostname('www.ubuntu.com')
670 self.assertEquals(hn, 'www.ubuntu.com')
671
672 @patch.object(openstack, 'apt_install')
673 def test_get_hostname_with_hostname_trailingdot(self, apt_install):
674 hn = openstack.get_hostname('www.ubuntu.com.')
675 self.assertEquals(hn, 'www.ubuntu.com')
676
677 @patch.object(openstack, 'apt_install')
678 def test_get_hostname_with_hostname_not_fqdn(self, apt_install):
679 hn = openstack.get_hostname('packages.ubuntu.com', fqdn=False)
680 self.assertEquals(hn, 'packages')
681
682 @patch.object(openstack, 'apt_install')
683 def test_get_hostname_trigger_apt_install(self, apt_install):
684 fake_dns = FakeDNS('www.ubuntu.com')
685 with patch(builtin_import, side_effect=[ImportError, fake_dns, fake_dns]):
686 hn = openstack.get_hostname('4.2.2.1')
687 apt_install.assert_called_with('python-dnspython')
688 self.assertEquals(hn, 'www.ubuntu.com')
689
690 @patch.object(openstack, 'ns_query')
691 @patch.object(openstack, 'apt_install')
692 def test_get_hostname_lookup_fail(self, apt_install, ns_query):
693 fake_dns = FakeDNS('www.ubuntu.com')
694 ns_query.return_value = []
695 with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
696 hn = openstack.get_hostname('4.2.2.1')
697 self.assertEquals(hn, None)
698
699 @patch('os.path.isfile')598 @patch('os.path.isfile')
700 @patch(builtin_open)599 @patch(builtin_open)
701 def test_get_matchmaker_map(self, _open, _isfile):600 def test_get_matchmaker_map(self, _open, _isfile):
@@ -846,7 +745,8 @@
846745
847 openstack._git_clone_and_install_single(repo, branch)746 openstack._git_clone_and_install_single(repo, branch)
848 mkdir.assert_called_with(dest_parent_dir)747 mkdir.assert_called_with(dest_parent_dir)
849 install_remote.assert_called_with(repo, dest=dest_parent_dir, branch=branch)748 install_remote.assert_called_with(repo, dest=dest_parent_dir,
749 branch=branch)
850 assert not _git_update_reqs.called750 assert not _git_update_reqs.called
851 pip_install.assert_called_with(dest_dir)751 pip_install.assert_called_with(dest_dir)
852752
@@ -871,7 +771,8 @@
871771
872 openstack._git_clone_and_install_single(repo, branch, True)772 openstack._git_clone_and_install_single(repo, branch, True)
873 mkdir.assert_called_with(dest_parent_dir)773 mkdir.assert_called_with(dest_parent_dir)
874 install_remote.assert_called_with(repo, dest=dest_parent_dir, branch=branch)774 install_remote.assert_called_with(repo, dest=dest_parent_dir,
775 branch=branch)
875 _git_update_reqs.assert_called_with(dest_dir, reqs_dir)776 _git_update_reqs.assert_called_with(dest_dir, reqs_dir)
876 pip_install.assert_called_with(dest_dir)777 pip_install.assert_called_with(dest_dir)
877778

Subscribers

People subscribed via source and target branches