import subprocess
import unittest

import mock
import netifaces

import charmhelpers.contrib.network.ip as net_ip
from mock import patch, MagicMock

import nose.tools
import six

if not six.PY3:
    builtin_open = '__builtin__.open'
    builtin_import = '__builtin__.__import__'
else:
    builtin_open = 'builtins.open'
    builtin_import = 'builtins.__import__'

DUMMY_ADDRESSES = {
    'lo': {
        17: [{'peer': '00:00:00:00:00:00',
              'addr': '00:00:00:00:00:00'}],
        2: [{'peer': '127.0.0.1', 'netmask':
             '255.0.0.0', 'addr': '127.0.0.1'}],
        10: [{'netmask': 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
              'addr': '::1'}]
    },
    'eth0': {
        2: [{'addr': '192.168.1.55',
             'broadcast': '192.168.1.255',
             'netmask': '255.255.255.0'}],
        10: [{'addr': '2a01:348:2f4:0:685e:5748:ae62:209f',
              'netmask': 'ffff:ffff:ffff:ffff::'},
             {'addr': 'fe80::3e97:eff:fe8b:1cf7%eth0',
              'netmask': 'ffff:ffff:ffff:ffff::'},
             {'netmask': 'ffff:ffff:ffff:ffff::/64',
              'addr': 'fd2d:dec4:cf59:3c16::1'},
             {'addr': '2001:db8:1::',
              'netmask': 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'}],
        17: [{'addr': '3c:97:0e:8b:1c:f7',
              'broadcast': 'ff:ff:ff:ff:ff:ff'}]
    },
    'eth0:1': {
        2: [{'addr': '192.168.1.56',
             'broadcast': '192.168.1.255',
             'netmask': '255.255.255.0'}],
    },
    'eth1': {
        2: [{'addr': '10.5.0.1',
             'broadcast': '10.5.255.255',
             'netmask': '255.255.0.0'},
            {'addr': '10.6.0.2',
             'broadcast': '10.6.0.255',
             'netmask': '255.255.255.0'}],
        3: [{'addr': 'fe80::3e97:eff:fe8b:1cf7%eth1',
             'netmask': 'ffff:ffff:ffff:ffff::'}],
        17: [{'addr': '3c:97:0e:8b:1c:f7',
              'broadcast': 'ff:ff:ff:ff:ff:ff'}]
    },
    'eth2': {
        10: [{'addr': '3a01:348:2f4:0:685e:5748:ae62:209f',
              'netmask': 'ffff:ffff:ffff:ffff::'},
             {'addr': 'fe80::3e97:edd:fe8b:1cf7%eth0',
              'netmask': 'ffff:ffff:ffff:ffff::'}],
        17: [{'addr': '3c:97:0e:8b:1c:f7',
              'broadcast': 'ff:ff:ff:ff:ff:ff'}]
    },
    'eth2:1': {
        2: [{'addr': '192.168.10.58',
             'broadcast': '192.168.1.255',
             'netmask': '255.255.255.0'}],
    },
}

IP_OUTPUT = b"""link/ether fa:16:3e:2a:cc:ce brd ff:ff:ff:ff:ff:ff
    inet 10.5.16.93/16 brd 10.5.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 2001:db8:1:0:d0cf:528c:23eb:6000/64 scope global
       valid_lft forever preferred_lft forever
    inet6 2001:db8:1:0:2918:3444:852:5b8a/64 scope global temporary dynamic
       valid_lft 86400sec preferred_lft 14400sec
    inet6 2001:db8:1:0:f816:3eff:fe2a:ccce/64 scope global dynamic
       valid_lft 86400sec preferred_lft 14400sec
    inet6 fe80::f816:3eff:fe2a:ccce/64 scope link
       valid_lft forever preferred_lft forever
"""

IP2_OUTPUT = b"""link/ether fa:16:3e:2a:cc:ce brd ff:ff:ff:ff:ff:ff
    inet 10.5.16.93/16 brd 10.5.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 2001:db8:1:0:d0cf:528c:23eb:6000/64 scope global
       valid_lft forever preferred_lft forever
    inet6 2001:db8:1:0:2918:3444:852:5b8a/64 scope global temporary dynamic
       valid_lft 86400sec preferred_lft 14400sec
    inet6 2001:db8:1:0:f816:3eff:fe2a:ccce/64 scope global mngtmpaddr dynamic
       valid_lft 86400sec preferred_lft 14400sec
    inet6 fe80::f816:3eff:fe2a:ccce/64 scope link
       valid_lft forever preferred_lft forever
"""

IP_OUTPUT_NO_VALID = b"""link/ether fa:16:3e:2a:cc:ce brd ff:ff:ff:ff:ff:ff
    inet 10.5.16.93/16 brd 10.5.255.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 2001:db8:1:0:2918:3444:852:5b8a/64 scope global temporary dynamic
       valid_lft 86400sec preferred_lft 14400sec
    inet6 fe80::f816:3eff:fe2a:ccce/64 scope link
       valid_lft forever preferred_lft forever
"""


class FakeAnswer(object):
    def __init__(self, ip):
        self.ip = ip

    def __str__(self):
        return self.ip


class FakeResolver(object):
    def __init__(self, ip):
        self.ip = ip

    def query(self, hostname, query_type):
        if self.ip == '':
            return []
        else:
            return [FakeAnswer(self.ip)]


class FakeReverse(object):
    def from_address(self, address):
        return '156.94.189.91.in-addr.arpa'


class FakeDNSName(object):
    def __init__(self, dnsname):
        pass


class FakeDNS(object):
    def __init__(self, ip):
        self.resolver = FakeResolver(ip)
        self.reversename = FakeReverse()
        self.name = MagicMock()
        self.name.Name = FakeDNSName


class IPTest(unittest.TestCase):

    def mock_ifaddresses(self, iface):
        return DUMMY_ADDRESSES[iface]

    def test_get_address_in_network_with_invalid_net(self):
        for net in ['192.168.300/22', '192.168.1.0/2a', '2.a']:
            self.assertRaises(ValueError,
                              net_ip.get_address_in_network,
                              net)

    def _test_get_address_in_network(self, expect_ip_addr,
                                     network, fallback=None, fatal=False):

        def side_effect(iface):
            return DUMMY_ADDRESSES[iface]

        with mock.patch.object(netifaces, 'interfaces') as interfaces:
            interfaces.return_value = sorted(DUMMY_ADDRESSES.keys())
            with mock.patch.object(netifaces, 'ifaddresses') as ifaddresses:
                ifaddresses.side_effect = side_effect
                if not fatal:
                    self.assertEqual(expect_ip_addr,
                                     net_ip.get_address_in_network(network,
                                                                   fallback,
                                                                   fatal))
                else:
                    net_ip.get_address_in_network(network, fallback, fatal)

    @mock.patch.object(subprocess, 'call')
    def test_get_address_in_network_with_none(self, popen):
        fallback = '10.10.10.10'
        self.assertEqual(fallback,
                         net_ip.get_address_in_network(None, fallback))
        self.assertEqual(None,
                         net_ip.get_address_in_network(None))

        self.assertRaises(ValueError, self._test_get_address_in_network,
                          None, None, fatal=True)

    def test_get_address_in_network_ipv4(self):
        self._test_get_address_in_network('192.168.1.55', '192.168.1.0/24')

    def test_get_address_in_network_ipv4_multi(self):
        # Assumes that there is an address configured on both but the first
        # one is picked#
        self._test_get_address_in_network('192.168.1.55',
                                          '192.168.1.0/24 192.168.10.0/24')

    def test_get_address_in_network_ipv4_multi2(self):
        # Assumes that there is nothing configured on 192.168.11.0/24
        self._test_get_address_in_network('192.168.10.58',
                                          '192.168.11.0/24 192.168.10.0/24')

    def test_get_address_in_network_ipv4_secondary(self):
        self._test_get_address_in_network('10.6.0.2',
                                          '10.6.0.0/24')

    def test_get_address_in_network_ipv6(self):
        self._test_get_address_in_network('2a01:348:2f4:0:685e:5748:ae62:209f',
                                          '2a01:348:2f4::/64')

    def test_get_address_in_network_with_non_existent_net(self):
        self._test_get_address_in_network(None, '172.16.0.0/16')

    def test_get_address_in_network_fallback_works(self):
        fallback = '10.10.0.0'
        self._test_get_address_in_network(fallback, '172.16.0.0/16', fallback)

    @mock.patch.object(subprocess, 'call')
    def test_get_address_in_network_not_found_fatal(self, popen):
        self.assertRaises(ValueError, self._test_get_address_in_network,
                          None, '172.16.0.0/16', fatal=True)

    def test_get_address_in_network_not_found_not_fatal(self):
        self._test_get_address_in_network(None, '172.16.0.0/16', fatal=False)

    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_address_in_network_netmask(self, _interfaces, _ifaddresses):
        """
        Validates that get_address_in_network works with a netmask
        that uses the format 'ffff:ffff:ffff::/prefixlen'
        """
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        _ifaddresses.side_effect = DUMMY_ADDRESSES.__getitem__
        self._test_get_address_in_network('fd2d:dec4:cf59:3c16::1',
                                          'fd2d:dec4:cf59:3c16::/64',
                                          fatal=False)

    def test_is_address_in_network(self):
        self.assertTrue(
            net_ip.is_address_in_network(
                '192.168.1.0/24',
                '192.168.1.1'))
        self.assertFalse(
            net_ip.is_address_in_network(
                '192.168.1.0/24',
                '10.5.1.1'))
        self.assertRaises(ValueError, net_ip.is_address_in_network,
                          'broken', '192.168.1.1')
        self.assertRaises(ValueError, net_ip.is_address_in_network,
                          '192.168.1.0/24', 'hostname')
        self.assertTrue(
            net_ip.is_address_in_network(
                '2a01:348:2f4::/64',
                '2a01:348:2f4:0:685e:5748:ae62:209f')
        )
        self.assertFalse(
            net_ip.is_address_in_network(
                '2a01:348:2f4::/64',
                'fdfc:3bd5:210b:cc8d:8c80:9e10:3f07:371')
        )

    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_iface_for_address(self, _interfaces, _ifaddresses):
        def mock_ifaddresses(iface):
            return DUMMY_ADDRESSES[iface]
        _interfaces.return_value = ['eth0', 'eth1']
        _ifaddresses.side_effect = mock_ifaddresses
        self.assertEquals(
            net_ip.get_iface_for_address('192.168.1.220'),
            'eth0')
        self.assertEquals(net_ip.get_iface_for_address('10.5.20.4'), 'eth1')
        self.assertEquals(
            net_ip.get_iface_for_address('2a01:348:2f4:0:685e:5748:ae62:210f'),
            'eth0'
        )
        self.assertEquals(net_ip.get_iface_for_address('172.4.5.5'), None)

    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_netmask_for_address(self, _interfaces, _ifaddresses):
        def mock_ifaddresses(iface):
            return DUMMY_ADDRESSES[iface]
        _interfaces.return_value = ['eth0', 'eth1']
        _ifaddresses.side_effect = mock_ifaddresses
        self.assertEquals(
            net_ip.get_netmask_for_address('192.168.1.220'),
            '255.255.255.0')
        self.assertEquals(
            net_ip.get_netmask_for_address('10.5.20.4'),
            '255.255.0.0')
        self.assertEquals(net_ip.get_netmask_for_address('172.4.5.5'), None)
        self.assertEquals(
            net_ip.get_netmask_for_address(
                '2a01:348:2f4:0:685e:5748:ae62:210f'),
            '64'
        )
        self.assertEquals(
            net_ip.get_netmask_for_address('2001:db8:1::'),
            '128'
        )

    def test_is_ipv6(self):
        self.assertFalse(net_ip.is_ipv6('myhost'))
        self.assertFalse(net_ip.is_ipv6('172.4.5.5'))
        self.assertTrue(net_ip.is_ipv6('2a01:348:2f4:0:685e:5748:ae62:209f'))

    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_ipv6_addr_no_ipv6(self, _interfaces, _ifaddresses):
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        _ifaddresses.side_effect = DUMMY_ADDRESSES.__getitem__
        with nose.tools.assert_raises(Exception):
            net_ip.get_ipv6_addr('eth0:1')

    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_ipv6_addr_no_global_ipv6(self, _interfaces,
                                          _ifaddresses):
        DUMMY_ADDRESSES = {
            'eth0': {
                10: [{'addr': 'fe80::3e97:eff:fe8b:1cf7%eth0',
                      'netmask': 'ffff:ffff:ffff:ffff::'}],
            }
        }
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        _ifaddresses.side_effect = DUMMY_ADDRESSES.__getitem__
        self.assertRaises(Exception, net_ip.get_ipv6_addr)

    @patch('charmhelpers.contrib.network.ip.get_iface_from_addr')
    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_ipv6_addr_exc_list(self, _interfaces, _ifaddresses,
                                    mock_get_iface_from_addr):
        def mock_ifaddresses(iface):
            return DUMMY_ADDRESSES[iface]

        _interfaces.return_value = ['eth0', 'eth1']
        _ifaddresses.side_effect = mock_ifaddresses

        result = net_ip.get_ipv6_addr(
            exc_list='2a01:348:2f4:0:685e:5748:ae62:209f',
            inc_aliases=True,
            fatal=False
        )
        self.assertEqual([], result)

    @patch('charmhelpers.contrib.network.ip.get_iface_from_addr')
    @patch('charmhelpers.contrib.network.ip.subprocess.check_output')
    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_ipv6_addr(self, _interfaces, _ifaddresses, mock_check_out,
                           mock_get_iface_from_addr):
        mock_get_iface_from_addr.return_value = 'eth0'
        mock_check_out.return_value = \
            b"inet6 2a01:348:2f4:0:685e:5748:ae62:209f/64 scope global dynamic"
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        _ifaddresses.side_effect = DUMMY_ADDRESSES.__getitem__
        result = net_ip.get_ipv6_addr(dynamic_only=False)
        self.assertEqual(['2a01:348:2f4:0:685e:5748:ae62:209f'], result)

    @patch('charmhelpers.contrib.network.ip.get_iface_from_addr')
    @patch('charmhelpers.contrib.network.ip.subprocess.check_output')
    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_ipv6_addr_global_dynamic(self, _interfaces, _ifaddresses,
                                          mock_check_out,
                                          mock_get_iface_from_addr):
        mock_get_iface_from_addr.return_value = 'eth0'
        mock_check_out.return_value = \
            b"inet6 2a01:348:2f4:0:685e:5748:ae62:209f/64 scope global dynamic"
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        _ifaddresses.side_effect = DUMMY_ADDRESSES.__getitem__
        result = net_ip.get_ipv6_addr(dynamic_only=False)
        self.assertEqual(['2a01:348:2f4:0:685e:5748:ae62:209f'], result)

    @patch.object(netifaces, 'interfaces')
    def test_get_ipv6_addr_invalid_nic(self, _interfaces):
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        self.assertRaises(Exception, net_ip.get_ipv6_addr, 'eth1')

    @patch('charmhelpers.contrib.network.ip.subprocess.check_output')
    def test_is_ipv6_disabled(self, mock_check_output):
        # verify that the function does look for the right thing
        mock_check_output.return_value = """
        Some lines before
        net.ipv6.conf.all.disable_ipv6 = 1
        Some lines afterward
        """
        self.assertTrue(net_ip.is_ipv6_disabled())
        mock_check_output.assert_called_once_with(
            ['sysctl', 'net.ipv6.conf.all.disable_ipv6'],
            stderr=subprocess.STDOUT, universal_newlines=True)
        # if it isn't there, it must return false
        mock_check_output.return_value = ""
        self.assertFalse(net_ip.is_ipv6_disabled())
        # If the syscall returns an error, then return True

        def fake_check_call(*args, **kwargs):
            raise subprocess.CalledProcessError(['called'], 1)
        mock_check_output.side_effect = fake_check_call
        self.assertTrue(net_ip.is_ipv6_disabled())

    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_iface_addr(self, _interfaces, _ifaddresses):
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        _ifaddresses.side_effect = DUMMY_ADDRESSES.__getitem__
        result = net_ip.get_iface_addr("eth0")
        self.assertEqual(["192.168.1.55"], result)

    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_iface_addr_excaliases(self, _interfaces, _ifaddresses):
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        _ifaddresses.side_effect = DUMMY_ADDRESSES.__getitem__
        result = net_ip.get_iface_addr("eth0")
        self.assertEqual(['192.168.1.55'], result)

    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_iface_addr_incaliases(self, _interfaces, _ifaddresses):
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        _ifaddresses.side_effect = DUMMY_ADDRESSES.__getitem__
        result = net_ip.get_iface_addr("eth0", inc_aliases=True)
        self.assertEqual(['192.168.1.55', '192.168.1.56'], result)

    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_iface_addr_exclist(self, _interfaces, _ifaddresses):
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        _ifaddresses.side_effect = DUMMY_ADDRESSES.__getitem__
        result = net_ip.get_iface_addr("eth0", inc_aliases=True,
                                       exc_list=['192.168.1.55'])
        self.assertEqual(['192.168.1.56'], result)

    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_iface_addr_mixedaddr(self, _interfaces, _ifaddresses):
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        _ifaddresses.side_effect = DUMMY_ADDRESSES.__getitem__
        result = net_ip.get_iface_addr("eth2", inc_aliases=True)
        self.assertEqual(["192.168.10.58"], result)

    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_iface_addr_full_interface_path(self, _interfaces,
                                                _ifaddresses):
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        _ifaddresses.side_effect = DUMMY_ADDRESSES.__getitem__
        result = net_ip.get_iface_addr("/dev/eth0")
        self.assertEqual(["192.168.1.55"], result)

    @patch.object(netifaces, 'interfaces')
    def test_get_iface_addr_invalid_type(self, _interfaces):
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        with nose.tools.assert_raises(Exception):
            net_ip.get_iface_addr(iface='eth0', inet_type='AF_BOB')

    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_iface_addr_invalid_interface(self, _interfaces, _ifaddresses):
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        result = net_ip.get_ipv4_addr("eth3", fatal=False)
        self.assertEqual([], result)

    @patch.object(netifaces, 'interfaces')
    def test_get_iface_addr_invalid_interface_fatal(self, _interfaces):
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        with nose.tools.assert_raises(Exception):
            net_ip.get_ipv4_addr("eth3", fatal=True)

    @patch.object(netifaces, 'interfaces')
    def test_get_iface_addr_invalid_interface_fatal_incaliases(self,
                                                               _interfaces):
        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        with nose.tools.assert_raises(Exception):
            net_ip.get_ipv4_addr("eth3", fatal=True, inc_aliases=True)

    @patch.object(netifaces, 'ifaddresses')
    @patch.object(netifaces, 'interfaces')
    def test_get_get_iface_addr_interface_has_no_ipv4(self, _interfaces,
                                                      _ifaddresses):

        # This will raise a KeyError since we are looking for "2"
        # (actally, netiface.AF_INET).
        DUMMY_ADDRESSES = {
            'eth0': {
                10: [{'addr': 'fe80::3e97:eff:fe8b:1cf7%eth0',
                      'netmask': 'ffff:ffff:ffff:ffff::'}],
            }
        }

        _interfaces.return_value = DUMMY_ADDRESSES.keys()
        _ifaddresses.side_effect = DUMMY_ADDRESSES.__getitem__

        result = net_ip.get_ipv4_addr("eth0", fatal=False)
        self.assertEqual([], result)

    @patch('glob.glob')
    def test_get_bridges(self, _glob):
        _glob.return_value = ['/sys/devices/virtual/net/br0/bridge']
        self.assertEqual(['br0'], net_ip.get_bridges())

    @patch.object(net_ip, 'get_bridges')
    @patch('glob.glob')
    def test_get_bridge_nics(self, _glob, _get_bridges):
        _glob.return_value = ['/sys/devices/virtual/net/br0/brif/eth4',
                              '/sys/devices/virtual/net/br0/brif/eth5']
        self.assertEqual(['eth4', 'eth5'], net_ip.get_bridge_nics('br0'))

    @patch.object(net_ip, 'get_bridges')
    @patch('glob.glob')
    def test_get_bridge_nics_invalid_br(self, _glob, _get_bridges):
        _glob.return_value = []
        self.assertEqual([], net_ip.get_bridge_nics('br1'))

    @patch.object(net_ip, 'get_bridges')
    @patch.object(net_ip, 'get_bridge_nics')
    def test_is_bridge_member(self, _get_bridge_nics, _get_bridges):
        _get_bridges.return_value = ['br0']
        _get_bridge_nics.return_value = ['eth4', 'eth5']
        self.assertTrue(net_ip.is_bridge_member('eth4'))
        self.assertFalse(net_ip.is_bridge_member('eth6'))

    def test_format_ipv6_addr(self):
        DUMMY_ADDRESS = '2001:db8:1:0:f131:fc84:ea37:7d4'
        self.assertEquals(net_ip.format_ipv6_addr(DUMMY_ADDRESS),
                          '[2001:db8:1:0:f131:fc84:ea37:7d4]')

    def test_format_invalid_ipv6_addr(self):
        INVALID_IPV6_ADDR = 'myhost'
        self.assertEquals(net_ip.format_ipv6_addr(INVALID_IPV6_ADDR),
                          None)

    @patch('charmhelpers.contrib.network.ip.get_iface_from_addr')
    @patch('charmhelpers.contrib.network.ip.subprocess.check_output')
    @patch('charmhelpers.contrib.network.ip.get_iface_addr')
    def test_get_ipv6_global_address(self, mock_get_iface_addr, mock_check_out,
                                     mock_get_iface_from_addr):
        mock_get_iface_from_addr.return_value = 'eth0'
        mock_check_out.return_value = IP_OUTPUT
        scope_global_addr = '2001:db8:1:0:d0cf:528c:23eb:6000'
        scope_global_dyn_addr = '2001:db8:1:0:f816:3eff:fe2a:ccce'
        mock_get_iface_addr.return_value = [scope_global_addr,
                                            scope_global_dyn_addr,
                                            '2001:db8:1:0:2918:3444:852:5b8a',
                                            'fe80::f816:3eff:fe2a:ccce%eth0']
        self.assertEqual([scope_global_addr, scope_global_dyn_addr],
                         net_ip.get_ipv6_addr(dynamic_only=False))

    @patch('charmhelpers.contrib.network.ip.get_iface_from_addr')
    @patch('charmhelpers.contrib.network.ip.subprocess.check_output')
    @patch('charmhelpers.contrib.network.ip.get_iface_addr')
    def test_get_ipv6_global_dynamic_address(self, mock_get_iface_addr,
                                             mock_check_out,
                                             mock_get_iface_from_addr):
        mock_get_iface_from_addr.return_value = 'eth0'
        mock_check_out.return_value = IP_OUTPUT
        scope_global_addr = '2001:db8:1:0:d0cf:528c:23eb:6000'
        scope_global_dyn_addr = '2001:db8:1:0:f816:3eff:fe2a:ccce'
        mock_get_iface_addr.return_value = [scope_global_addr,
                                            scope_global_dyn_addr,
                                            '2001:db8:1:0:2918:3444:852:5b8a',
                                            'fe80::f816:3eff:fe2a:ccce%eth0']
        self.assertEqual([scope_global_dyn_addr], net_ip.get_ipv6_addr())

    @patch('charmhelpers.contrib.network.ip.get_iface_from_addr')
    @patch('charmhelpers.contrib.network.ip.subprocess.check_output')
    @patch('charmhelpers.contrib.network.ip.get_iface_addr')
    def test_get_ipv6_global_dynamic_address_ip2(self, mock_get_iface_addr,
                                                 mock_check_out,
                                                 mock_get_iface_from_addr):
        mock_get_iface_from_addr.return_value = 'eth0'
        mock_check_out.return_value = IP2_OUTPUT
        scope_global_addr = '2001:db8:1:0:d0cf:528c:23eb:6000'
        scope_global_dyn_addr = '2001:db8:1:0:f816:3eff:fe2a:ccce'
        mock_get_iface_addr.return_value = [scope_global_addr,
                                            scope_global_dyn_addr,
                                            '2001:db8:1:0:2918:3444:852:5b8a',
                                            'fe80::f816:3eff:fe2a:ccce%eth0']
        self.assertEqual([scope_global_dyn_addr], net_ip.get_ipv6_addr())

    @patch('charmhelpers.contrib.network.ip.subprocess.check_output')
    @patch('charmhelpers.contrib.network.ip.get_iface_addr')
    def test_get_ipv6_global_dynamic_address_invalid_address(
            self, mock_get_iface_addr, mock_check_out):
        mock_get_iface_addr.return_value = []
        with nose.tools.assert_raises(Exception):
            net_ip.get_ipv6_addr()

        mock_get_iface_addr.return_value = ['2001:db8:1:0:2918:3444:852:5b8a']
        mock_check_out.return_value = IP_OUTPUT_NO_VALID
        with nose.tools.assert_raises(Exception):
            net_ip.get_ipv6_addr()

    @patch('charmhelpers.contrib.network.ip.get_iface_addr')
    def test_get_ipv6_addr_w_iface(self, mock_get_iface_addr):
        mock_get_iface_addr.return_value = []
        net_ip.get_ipv6_addr(iface='testif', fatal=False)
        mock_get_iface_addr.assert_called_once_with(iface='testif',
                                                    inet_type='AF_INET6',
                                                    inc_aliases=False,
                                                    fatal=False, exc_list=None)

    @patch('charmhelpers.contrib.network.ip.unit_get')
    @patch('charmhelpers.contrib.network.ip.get_iface_from_addr')
    @patch('charmhelpers.contrib.network.ip.get_iface_addr')
    def test_get_ipv6_addr_no_iface(self, mock_get_iface_addr,
                                    mock_get_iface_from_addr, mock_unit_get):
        mock_unit_get.return_value = '1.2.3.4'
        mock_get_iface_addr.return_value = []
        mock_get_iface_from_addr.return_value = "testif"
        net_ip.get_ipv6_addr(fatal=False)
        mock_get_iface_from_addr.assert_called_once_with('1.2.3.4')
        mock_get_iface_addr.assert_called_once_with(iface='testif',
                                                    inet_type='AF_INET6',
                                                    inc_aliases=False,
                                                    fatal=False, exc_list=None)

    @patch('netifaces.interfaces')
    @patch('netifaces.ifaddresses')
    @patch('charmhelpers.contrib.network.ip.log')
    def test_get_iface_from_addr(self, mock_log, mock_ifaddresses,
                                 mock_interfaces):
        mock_ifaddresses.side_effect = lambda iface: DUMMY_ADDRESSES[iface]
        mock_interfaces.return_value = sorted(DUMMY_ADDRESSES.keys())
        addr = 'fe80::3e97:eff:fe8b:1cf7'
        self.assertEqual(net_ip.get_iface_from_addr(addr), 'eth0')

        with nose.tools.assert_raises(Exception):
            net_ip.get_iface_from_addr('1.2.3.4')

    def test_is_ip(self):
        self.assertTrue(net_ip.is_ip('10.0.0.1'))
        self.assertTrue(net_ip.is_ip('2001:db8:1:0:2918:3444:852:5b8a'))
        self.assertFalse(net_ip.is_ip('www.ubuntu.com'))

    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_get_host_ip_with_hostname(self, apt_install):
        fake_dns = FakeDNS('10.0.0.1')
        with patch(builtin_import, side_effect=[fake_dns]):
            ip = net_ip.get_host_ip('www.ubuntu.com')
        self.assertEquals(ip, '10.0.0.1')

    @patch('charmhelpers.contrib.network.ip.ns_query')
    @patch('charmhelpers.contrib.network.ip.socket.gethostbyname')
    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_get_host_ip_with_hostname_no_dns(self, apt_install, socket,
                                              ns_query):
        ns_query.return_value = []
        fake_dns = FakeDNS(None)
        socket.return_value = '10.0.0.1'
        with patch(builtin_import, side_effect=[fake_dns]):
            ip = net_ip.get_host_ip('www.ubuntu.com')
        self.assertEquals(ip, '10.0.0.1')

    @patch('charmhelpers.contrib.network.ip.log')
    @patch('charmhelpers.contrib.network.ip.ns_query')
    @patch('charmhelpers.contrib.network.ip.socket.gethostbyname')
    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_get_host_ip_with_hostname_fallback(self, apt_install, socket,
                                                ns_query, *args):
        ns_query.return_value = []
        fake_dns = FakeDNS(None)

        def r():
            raise Exception()

        socket.side_effect = r
        with patch(builtin_import, side_effect=[fake_dns]):
            ip = net_ip.get_host_ip('www.ubuntu.com', fallback='127.0.0.1')
        self.assertEquals(ip, '127.0.0.1')

    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_get_host_ip_with_ip(self, apt_install):
        fake_dns = FakeDNS('5.5.5.5')
        with patch(builtin_import, side_effect=[fake_dns]):
            ip = net_ip.get_host_ip('4.2.2.1')
        self.assertEquals(ip, '4.2.2.1')

    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_ns_query_trigger_apt_install(self, apt_install):
        fake_dns = FakeDNS('5.5.5.5')
        with patch(builtin_import, side_effect=[ImportError, fake_dns]):
            nsq = net_ip.ns_query('5.5.5.5')
            if six.PY2:
                apt_install.assert_called_with('python-dnspython', fatal=True)
            else:
                apt_install.assert_called_with('python3-dnspython', fatal=True)
        self.assertEquals(nsq, '5.5.5.5')

    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_ns_query_ptr_record(self, apt_install):
        fake_dns = FakeDNS('127.0.0.1')
        with patch(builtin_import, side_effect=[fake_dns]):
            nsq = net_ip.ns_query('127.0.0.1')
        self.assertEquals(nsq, '127.0.0.1')

    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_ns_query_a_record(self, apt_install):
        fake_dns = FakeDNS('127.0.0.1')
        fake_dns_name = FakeDNSName('www.somedomain.tld')
        with patch(builtin_import, side_effect=[fake_dns]):
            nsq = net_ip.ns_query(fake_dns_name)
        self.assertEquals(nsq, '127.0.0.1')

    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_ns_query_blank_record(self, apt_install):
        fake_dns = FakeDNS(None)
        with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
            nsq = net_ip.ns_query(None)
        self.assertEquals(nsq, None)

    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_ns_query_lookup_fail(self, apt_install):
        fake_dns = FakeDNS('')
        with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
            nsq = net_ip.ns_query('nonexistant')
        self.assertEquals(nsq, None)

    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_get_hostname_with_ip(self, apt_install):
        fake_dns = FakeDNS('www.ubuntu.com')
        with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
            hn = net_ip.get_hostname('4.2.2.1')
        self.assertEquals(hn, 'www.ubuntu.com')

    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_get_hostname_with_ip_not_fqdn(self, apt_install):
        fake_dns = FakeDNS('packages.ubuntu.com')
        with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
            hn = net_ip.get_hostname('4.2.2.1', fqdn=False)
        self.assertEquals(hn, 'packages')

    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_get_hostname_with_hostname(self, apt_install):
        hn = net_ip.get_hostname('www.ubuntu.com')
        self.assertEquals(hn, 'www.ubuntu.com')

    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_get_hostname_with_hostname_trailingdot(self, apt_install):
        hn = net_ip.get_hostname('www.ubuntu.com.')
        self.assertEquals(hn, 'www.ubuntu.com')

    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_get_hostname_with_hostname_not_fqdn(self, apt_install):
        hn = net_ip.get_hostname('packages.ubuntu.com', fqdn=False)
        self.assertEquals(hn, 'packages')

    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_get_hostname_trigger_apt_install(self, apt_install):
        fake_dns = FakeDNS('www.ubuntu.com')
        with patch(builtin_import, side_effect=[ImportError, fake_dns,
                                                fake_dns]):
            hn = net_ip.get_hostname('4.2.2.1')
            if six.PY2:
                apt_install.assert_called_with('python-dnspython', fatal=True)
            else:
                apt_install.assert_called_with('python3-dnspython', fatal=True)

        self.assertEquals(hn, 'www.ubuntu.com')

    @patch('charmhelpers.contrib.network.ip.socket.gethostbyaddr')
    @patch('charmhelpers.contrib.network.ip.ns_query')
    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_get_hostname_lookup_fail(self, apt_install, ns_query, socket):
        fake_dns = FakeDNS('www.ubuntu.com')
        ns_query.return_value = []
        socket.return_value = ()
        with patch(builtin_import, side_effect=[fake_dns, fake_dns]):
            hn = net_ip.get_hostname('4.2.2.1')
        self.assertEquals(hn, None)

    @patch('charmhelpers.contrib.network.ip.socket.gethostbyaddr')
    @patch('charmhelpers.contrib.network.ip.ns_query')
    @patch('charmhelpers.contrib.network.ip.apt_install')
    def test_get_hostname_lookup_fail_gethostbyaddr_fallback(
            self, apt_install, ns_query, socket):
        fake_dns = FakeDNS('www.ubuntu.com')
        ns_query.return_value = []
        socket.return_value = ("www.ubuntu.com", "", "")
        with patch(builtin_import, side_effect=[fake_dns]):
            hn = net_ip.get_hostname('4.2.2.1')
        self.assertEquals(hn, "www.ubuntu.com")

    @patch('charmhelpers.contrib.network.ip.subprocess.call')
    def test_port_has_listener(self, subprocess_call):
        subprocess_call.return_value = 1
        self.assertEqual(net_ip.port_has_listener('ip-address', 50), False)
        subprocess_call.assert_called_with(['nc', '-z', 'ip-address', '50'])
        subprocess_call.return_value = 0
        self.assertEqual(net_ip.port_has_listener('ip-address', 70), True)
        subprocess_call.assert_called_with(['nc', '-z', 'ip-address', '70'])

    @patch.object(net_ip, 'log', lambda *args, **kwargs: None)
    @patch.object(net_ip, 'config')
    @patch.object(net_ip, 'network_get_primary_address')
    @patch.object(net_ip, 'get_address_in_network')
    @patch.object(net_ip, 'unit_get')
    @patch.object(net_ip, 'get_ipv6_addr')
    @patch.object(net_ip, 'assert_charm_supports_ipv6')
    def test_get_relation_ip(self, assert_charm_supports_ipv6, get_ipv6_addr,
                             unit_get, get_address_in_network,
                             network_get_primary_address, config):
        ACCESS_IP = '10.50.1.1'
        ACCESS_NETWORK = '10.50.1.0/24'
        AMQP_IP = '10.200.1.1'
        IPV6_IP = '2001:DB8::1'
        DEFAULT_IP = '172.16.1.1'
        assert_charm_supports_ipv6.return_value = True
        get_ipv6_addr.return_value = [IPV6_IP]
        unit_get.return_value = DEFAULT_IP
        get_address_in_network.return_value = DEFAULT_IP
        network_get_primary_address.return_value = AMQP_IP

        # Network-get calls
        _config = {'prefer-ipv6': False}
        config.side_effect = lambda key: _config.get(key)

        network_get_primary_address.side_effect = NotImplementedError
        self.assertEqual(DEFAULT_IP, net_ip.get_relation_ip('amqp'))

        network_get_primary_address.side_effect = net_ip.NoNetworkBinding
        self.assertEqual(DEFAULT_IP, net_ip.get_relation_ip('doesnotexist'))

        network_get_primary_address.side_effect = None
        self.assertEqual(AMQP_IP, net_ip.get_relation_ip('amqp'))

        self.assertFalse(get_address_in_network.called)

        # Specific CIDR network
        get_address_in_network.return_value = ACCESS_IP
        network_get_primary_address.return_value = DEFAULT_IP
        self.assertEqual(
            ACCESS_IP,
            net_ip.get_relation_ip('shared-db',
                                   cidr_network=ACCESS_NETWORK))
        get_address_in_network.assert_called_with(ACCESS_NETWORK, DEFAULT_IP)

        self.assertFalse(assert_charm_supports_ipv6.called)

        # IPv6
        _config = {'prefer-ipv6': True}
        config.side_effect = lambda key: _config.get(key)
        self.assertEqual(IPV6_IP, net_ip.get_relation_ip('amqp'))
        assert_charm_supports_ipv6.assert_called_with()
