diff options
Diffstat (limited to 'testing/mozbase/moznetwork')
-rw-r--r-- | testing/mozbase/moznetwork/moznetwork/__init__.py | 26 | ||||
-rw-r--r-- | testing/mozbase/moznetwork/moznetwork/moznetwork.py | 172 | ||||
-rw-r--r-- | testing/mozbase/moznetwork/setup.py | 29 | ||||
-rw-r--r-- | testing/mozbase/moznetwork/tests/manifest.ini | 1 | ||||
-rw-r--r-- | testing/mozbase/moznetwork/tests/test.py | 85 |
5 files changed, 313 insertions, 0 deletions
diff --git a/testing/mozbase/moznetwork/moznetwork/__init__.py b/testing/mozbase/moznetwork/moznetwork/__init__.py new file mode 100644 index 000000000..df2097cb0 --- /dev/null +++ b/testing/mozbase/moznetwork/moznetwork/__init__.py @@ -0,0 +1,26 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +""" +moznetwork is a very simple module designed for one task: getting the +network address of the current machine. + +Example usage: + +:: + + import moznetwork + + try: + ip = moznetwork.get_ip() + print "The external IP of your machine is '%s'" % ip + except moznetwork.NetworkError: + print "Unable to determine IP address of machine" + raise + +""" + +from moznetwork import get_ip + +__all__ = ['get_ip'] diff --git a/testing/mozbase/moznetwork/moznetwork/moznetwork.py b/testing/mozbase/moznetwork/moznetwork/moznetwork.py new file mode 100644 index 000000000..537649603 --- /dev/null +++ b/testing/mozbase/moznetwork/moznetwork/moznetwork.py @@ -0,0 +1,172 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +import argparse +import array +import re +import socket +import struct +import subprocess +import sys + +import mozinfo +import mozlog + +if mozinfo.isLinux: + import fcntl + + +class NetworkError(Exception): + """Exception thrown when unable to obtain interface or IP.""" + + +def _get_logger(): + logger = mozlog.get_default_logger(component='moznetwork') + if not logger: + logger = mozlog.unstructured.getLogger('moznetwork') + return logger + + +def _get_interface_list(): + """Provides a list of available network interfaces + as a list of tuples (name, ip)""" + logger = _get_logger() + logger.debug('Gathering interface list') + max_iface = 32 # Maximum number of interfaces(Aribtrary) + bytes = max_iface * 32 + is_32bit = (8 * struct.calcsize("P")) == 32 # Set Architecture + struct_size = 32 if is_32bit else 40 + + try: + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + names = array.array('B', '\0' * bytes) + outbytes = struct.unpack('iL', fcntl.ioctl( + s.fileno(), + 0x8912, # SIOCGIFCONF + struct.pack('iL', bytes, names.buffer_info()[0]) + ))[0] + namestr = names.tostring() + return [(namestr[i:i + 32].split('\0', 1)[0], + socket.inet_ntoa(namestr[i + 20:i + 24])) + for i in range(0, outbytes, struct_size)] + + except IOError: + raise NetworkError('Unable to call ioctl with SIOCGIFCONF') + + +def _proc_matches(args, regex): + """Helper returns the matches of regex in the output of a process created with + the given arguments""" + output = subprocess.Popen(args=args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT).stdout.read() + return re.findall(regex, output) + + +def _parse_ifconfig(): + """Parse the output of running ifconfig on mac in cases other methods + have failed""" + logger = _get_logger() + logger.debug('Parsing ifconfig') + + # Attempt to determine the default interface in use. + default_iface = _proc_matches(['route', '-n', 'get', 'default'], + 'interface: (\w+)') + + if default_iface: + addr_list = _proc_matches(['ifconfig', default_iface[0]], + 'inet (\d+.\d+.\d+.\d+)') + if addr_list: + logger.debug('Default interface: [%s] %s' % (default_iface[0], + addr_list[0])) + if not addr_list[0].startswith('127.'): + return addr_list[0] + + # Iterate over plausible interfaces if we didn't find a suitable default. + for iface in ['en%s' % i for i in range(10)]: + addr_list = _proc_matches(['ifconfig', iface], + 'inet (\d+.\d+.\d+.\d+)') + if addr_list: + logger.debug('Interface: [%s] %s' % (iface, addr_list[0])) + if not addr_list[0].startswith('127.'): + return addr_list[0] + + # Just return any that isn't localhost. If we can't find one, we have + # failed. + addrs = _proc_matches(['ifconfig'], + 'inet (\d+.\d+.\d+.\d+)') + try: + return [addr for addr in addrs if not addr.startswith('127.')][0] + except IndexError: + return None + + +def get_ip(): + """Provides an available network interface address, for example + "192.168.1.3". + + A `NetworkError` exception is raised in case of failure.""" + logger = _get_logger() + try: + hostname = socket.gethostname() + try: + logger.debug('Retrieving IP for %s' % hostname) + ips = socket.gethostbyname_ex(hostname)[2] + except socket.gaierror: # for Mac OS X + hostname += '.local' + logger.debug('Retrieving IP for %s' % hostname) + ips = socket.gethostbyname_ex(hostname)[2] + if len(ips) == 1: + ip = ips[0] + elif len(ips) > 1: + logger.debug('Multiple addresses found: %s' % ips) + # no fallback on Windows so take the first address + ip = ips[0] if mozinfo.isWin else None + else: + ip = None + except socket.gaierror: + # sometimes the hostname doesn't resolve to an ip address, in which + # case this will always fail + ip = None + + if ip is None or ip.startswith("127."): + if mozinfo.isLinux: + interfaces = _get_interface_list() + for ifconfig in interfaces: + logger.debug('Interface: [%s] %s' % (ifconfig[0], ifconfig[1])) + if ifconfig[0] == 'lo': + continue + else: + return ifconfig[1] + elif mozinfo.isMac: + ip = _parse_ifconfig() + + if ip is None: + raise NetworkError('Unable to obtain network address') + + return ip + + +def get_lan_ip(): + """Deprecated. Please use get_ip() instead.""" + return get_ip() + + +def cli(args=sys.argv[1:]): + parser = argparse.ArgumentParser( + description='Retrieve IP address') + mozlog.commandline.add_logging_group( + parser, + include_formatters=mozlog.commandline.TEXT_FORMATTERS + ) + + args = parser.parse_args() + mozlog.commandline.setup_logging( + 'mozversion', args, {'mach': sys.stdout}) + + _get_logger().info('IP address: %s' % get_ip()) + + +if __name__ == '__main__': + cli() diff --git a/testing/mozbase/moznetwork/setup.py b/testing/mozbase/moznetwork/setup.py new file mode 100644 index 000000000..2bc62f8dc --- /dev/null +++ b/testing/mozbase/moznetwork/setup.py @@ -0,0 +1,29 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +from setuptools import setup + +PACKAGE_VERSION = '0.27' + +deps = ['mozinfo', + 'mozlog >= 3.0', + ] + +setup(name='moznetwork', + version=PACKAGE_VERSION, + description="Library of network utilities for use in Mozilla testing", + long_description="see http://mozbase.readthedocs.org/", + classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers + keywords='mozilla', + author='Mozilla Automation and Tools team', + author_email='tools@lists.mozilla.org', + url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase', + license='MPL', + packages=['moznetwork'], + include_package_data=True, + zip_safe=False, + install_requires=deps, + entry_points={'console_scripts': [ + 'moznetwork = moznetwork:cli']}, + ) diff --git a/testing/mozbase/moznetwork/tests/manifest.ini b/testing/mozbase/moznetwork/tests/manifest.ini new file mode 100644 index 000000000..528fdea7b --- /dev/null +++ b/testing/mozbase/moznetwork/tests/manifest.ini @@ -0,0 +1 @@ +[test.py] diff --git a/testing/mozbase/moznetwork/tests/test.py b/testing/mozbase/moznetwork/tests/test.py new file mode 100644 index 000000000..79eee6b03 --- /dev/null +++ b/testing/mozbase/moznetwork/tests/test.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +""" +Unit-Tests for moznetwork +""" + +import os +import mock +import mozinfo +import moznetwork +import re +import subprocess +import unittest + + +def verify_ip_in_list(ip): + """ + Helper Method to check if `ip` is listed in Network Adresses + returned by ipconfig/ifconfig, depending on the platform in use + + :param ip: IPv4 address in the xxx.xxx.xxx.xxx format as a string + Example Usage: + verify_ip_in_list('192.168.0.1') + + returns True if the `ip` is in the list of IPs in ipconfig/ifconfig + """ + + # Regex to match IPv4 addresses. + # 0-255.0-255.0-255.0-255, note order is important here. + regexip = re.compile("((25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)\.){3}" + "(25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)") + + if mozinfo.isLinux or mozinfo.isMac or mozinfo.isBsd: + # if "/sbin/ifconfig" exist, use it because it may not be in the + # PATH (at least on some linux platforms) + if os.path.isfile('/sbin/ifconfig') and os.access('/sbin/ifconfig', + os.X_OK): + args = ['/sbin/ifconfig'] + else: + args = ["ifconfig"] + + if mozinfo.isWin: + args = ["ipconfig"] + + ps = subprocess.Popen(args, stdout=subprocess.PIPE) + standardoutput, standarderror = ps.communicate() + + # Generate a list of IPs by parsing the output of ip/ifconfig + ip_list = [x.group() for x in re.finditer(regexip, standardoutput)] + + # Check if ip is in list + if ip in ip_list: + return True + else: + return False + + +class TestGetIP(unittest.TestCase): + + def test_get_ip(self): + """ Attempt to test the IP address returned by + moznetwork.get_ip() is valid """ + + ip = moznetwork.get_ip() + + # Check the IP returned by moznetwork is in the list + self.assertTrue(verify_ip_in_list(ip)) + + def test_get_ip_using_get_interface(self): + """ Test that the control flow path for get_ip() using + _get_interface_list() is works """ + + if mozinfo.isLinux or mozinfo.isMac: + + with mock.patch('socket.gethostbyname') as byname: + # Force socket.gethostbyname to return None + byname.return_value = None + + ip = moznetwork.get_ip() + + # Check the IP returned by moznetwork is in the list + self.assertTrue(verify_ip_in_list(ip)) + + +if __name__ == '__main__': + unittest.main() |