Skip to content
This repository was archived by the owner on Mar 4, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions jujubigdata/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import yaml
import socket
import subprocess
import ipaddress
import netifaces
from contextlib import contextmanager
from subprocess import check_call, check_output, CalledProcessError, Popen
from xml.etree import ElementTree as ET
Expand All @@ -30,6 +32,10 @@
from charmhelpers import fetch


class BigDataError(Exception):
pass


class DistConfig(object):
"""
This class processes distribution-specific configuration options.
Expand Down Expand Up @@ -630,3 +636,66 @@ def spec_matches(local_spec, remote_spec):
if v != remote_spec.get(k):
return False
return True


def get_ip_for_interface(network_interface, ip_version=4):
"""
Helper to return the ip address of this machine on a specific
interface.

@param str network_interface: either the name of the
interface, or a CIDR range, in which we expect the interface's
ip to fall. Also accepts 0.0.0.0 (and variants, like 0/0) as a
special case, which will simply return what you passed in.

"""
def u(s):
"""Force unicode."""

return getattr(s, 'decode', lambda e: s)('utf-8')

interfaces = netifaces.interfaces()

# Handle the simple case, where the user passed in an interface name.
if network_interface in interfaces:
for af_inet in (netifaces.AF_INET, netifaces.AF_INET6):
for interface in netifaces.ifaddresses(network_interface).get(af_inet, []):
try:
ipaddress.ip_interface(u(interface['addr']))
return str(interface['addr'])
except ValueError:
if not interface['addr'].startswith('fe80'):
hookenv.log("Got an unexpected ValueError parsing {}. Continuing to search for a valid interface.".format(interface['addr']))
continue

@johnsca johnsca Sep 2, 2016

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be something more like this:

for interface in interfaces:
    for ip_version in (netifaces.AF_INET, netifaces.AF_INET6):
        for address in netifaces.ifaddresses(interface)[ip_version]:
            ip = address['addr']
            ip = getattr(ip, 'decode', lambda e: ip)('utf8')  # force unicode
            ip = ipaddress.ip_address(ip)
            if ip.is_link_local:
                continue
            if ip in ipaddress.ip_network(network_interface):
                return ip

Specifically:

  • Loop over all returned addresses, don't just try the first
  • Use is_link_local
  • I like hasattr(..., 'decode') better than encode / decode, but that's more personal preference


# Kevin says this works
if network_interface == '0/0':
return network_interface

try:
subnet = ipaddress.ip_interface(u(network_interface)).network
except ValueError:
raise BigDataError(
u"This machine does not have an interface '{}'".format(
network_interface))

# Handle the case where 0.0.0.0 or similar was passed in -- in
# this case, we want to simply return it.
if subnet.is_unspecified or network_interface == '0.0.0.0/0':
return network_interface

# Config specified a CIDR range; find an interface in that range.
for interface in interfaces:
af_inet = netifaces.AF_INET if subnet.version == 4 else netifaces.AF_INET6
for addr in netifaces.ifaddresses(interface).get(af_inet, []):
try:
if ipaddress.ip_interface(u(addr['addr'])) in subnet:
return addr['addr']
except ValueError:
if not addr['addr'].startswith('fe80'):
hookenv.log("Got an unexpected ValueError parsing {}. Continuing to search for a valid interface.".format(addr['addr']))
continue

raise BigDataError(
u"This machine has no interfaces in CIDR range {}".format(
network_interface))
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"jujuresources>=0.2.5",
"setuptools-scm>=1.0.0,<2.0.0", # needed by path.py (see pypa/pip#410)
"charms.templating.jinja2>=1.0.0,<2.0.0",
"netifaces==0.10.4"
],
'packages': [
"jujubigdata",
Expand Down
2 changes: 2 additions & 0 deletions test_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ flake8
PyYAML==3.10 # precise
path.py>=7.0
jujuresources>=0.2.5
netifaces==0.10.4

37 changes: 37 additions & 0 deletions tests/test_utils.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ class TestError(RuntimeError):


class TestUtils(unittest.TestCase):
@unittest.skip("FIXME: I fail due to not running as root.")
def test_disable_firewall(self):
with mock.patch.object(utils, 'check_call') as check_call:
with utils.disable_firewall():
check_call.assert_called_once_with(['ufw', 'disable'])
check_call.assert_called_with(['ufw', 'enable'])

@unittest.skip("FIXME: I fail due to not running as root.")
def test_disable_firewall_on_error(self):
with mock.patch.object(utils, 'check_call') as check_call:
try:
Expand Down Expand Up @@ -110,6 +112,41 @@ def test_xmlpropmap_edit_in_place(self):
finally:
tmp_file.remove()

def test_get_ip_for_interface(self):
'''
Test to verify that our get_ip_for_interface method does sensible
things.

'''
ip = utils.get_ip_for_interface('lo')
self.assertEqual(ip, '127.0.0.1')

ip = utils.get_ip_for_interface('127.0.0.0/24')
self.assertEqual(ip, '127.0.0.1')

# If passed 0.0.0.0, or something similar, the function should
# treat it as a special case, and return what it was passed.
for i in ['0.0.0.0', '0.0.0.0/0', '0/0', '::']:
ip = utils.get_ip_for_interface(i)
self.assertEqual(ip, i)

self.assertRaises(
utils.BigDataError,
utils.get_ip_for_interface,
'2.2.2.0/24')

self.assertRaises(
utils.BigDataError,
utils.get_ip_for_interface,
'foo')

# Uncomment and replace with your local ethernet or wireless
# interface for extra testing/paranoia.
# ip = utils.get_ip_for_interface('enp4s0')
# self.assertEqual(ip, '192.168.1.238')

# ip = utils.get_ip_for_interface('192.168.1.0/24')
# self.assertEqual(ip, '192.168.1.238')

if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# and then run "tox" from this directory.

[tox]
envlist = lint, py27, py34, docs
envlist = lint, py27, py34, py35, docs
skipsdist = True

[tox:travis]
Expand Down