← Back to team overview

yellow team mailing list archive

[Merge] lp:~frankban/lpsetup/search-interface2 into lp:lpsetup

 

Francesco Banconi has proposed merging lp:~frankban/lpsetup/search-interface2 into lp:lpsetup.

Requested reviews:
  Launchpad Yellow Squad (yellow)

For more details, see:
https://code.launchpad.net/~frankban/lpsetup/search-interface2/+merge/104521

== Changes ==

This branch adds to lxcip the ability to search network interfaces.
This is done looking for directories under /proc/[container pid]/root/sys/class/net/.
Some changes were required to accomplish this:

- changed the ui: 
  -n [container] returns a list of interfaces found (excluding loopback)
  -n [container] -i [interface] just displays the ip address of the given interface
  deleted the -s option, no longer required

- added a `get_interfaces` function

- changed `get_ip` (now `get_ip_addresses`) to accept a list of network interfaces and return the corresponding sequence of ip addresses

Other minor fixes:

- tests stopping and restarting lxc are now more robust: the container is correctly restarted if the test fails.
- updated *lpsetup* LXC_IP command to reflect ui changes.


== Tests ==


$ nosetests
........................................................
Name                  Stmts   Miss  Cover   Missing
---------------------------------------------------
lpsetup                   6      1    83%   16
lpsetup.argparser       125      6    95%   113, 221, 278-279, 298, 307
lpsetup.exceptions        6      0   100%   
lpsetup.handlers         55      1    98%   55
lpsetup.settings         29      0   100%   
lpsetup.subcommands       0      0   100%   
lpsetup.utils           130     32    75%   81, 115-125, 140, 191, 201, 222-224, 242-248, 263-264, 276-282
---------------------------------------------------
TOTAL                   351     40    89%   
----------------------------------------------------------------------
Ran 56 tests in 0.549s

OK



$ cd lplxcip/ && sudo nosetests
...................................
----------------------------------------------------------------------
Ran 35 tests in 20.308s

OK

-- 
https://code.launchpad.net/~frankban/lpsetup/search-interface2/+merge/104521
Your team Launchpad Yellow Squad is requested to review the proposed merge of lp:~frankban/lpsetup/search-interface2 into lp:lpsetup.
=== modified file 'lplxcip/lxcip.py'
--- lplxcip/lxcip.py	2012-04-27 08:26:14 +0000
+++ lplxcip/lxcip.py	2012-05-03 10:50:57 +0000
@@ -24,7 +24,6 @@
     'not_installed': 'lxc does not seem to be installed',
     'not_root': 'you must be root',
     }
-INTERFACE = 'eth0'
 # The ioctl command to retrieve the interface address.
 SIOCGIFADDR = 0x8915
 
@@ -36,14 +35,10 @@
         '-n', '--name', required=True,
         help='The name of the container. ')
     parser.add_argument(
-        '-i', '--interface', default=INTERFACE,
-        help='The network interface used to obtain the ip address. '
-             '[DEFAULT={}]'.format(INTERFACE))
-    parser.add_argument(
-        '-s', '--short', action='store_true',
-        help='Display the ip address using the short output format.')
+        '-i', '--interface',
+        help='Display the ip address of the specified network interface.')
     namespace = parser.parse_args()
-    return namespace.name, namespace.interface, namespace.short
+    return namespace.name, namespace.interface
 
 
 def _error(code):
@@ -52,9 +47,12 @@
         '{}: error: {}'.format(os.path.basename(sys.argv[0]), ERRORS[code]))
 
 
-def _output(name, ip, short):
-    """Format the output displaying the ip address of the container."""
-    return ip if short else '{}: {}'.format(name, ip)
+def _output(interfaces, ip_addresses, short):
+    """Format the output displaying the ip addresses of the container."""
+    if short:
+        return ip_addresses[0]
+    interface_ip_map = zip(interfaces, ip_addresses)
+    return '\n'.join('{}: {}'.format(*i) for i in interface_ip_map)
 
 
 def _wrap(function, error_code):
@@ -158,43 +156,72 @@
 
 
 @root_required
-def get_ip(pid, interface):
-    """Return the ip address of LXC `interface`, given the container's `pid`.
-
-    Raise OSError if the container is not found or the ip address is not
-    retreivable.
+def get_interfaces(pid, exclude=()):
+    """Return a list of active net interfaces, given the container's `pid`.
+
+    Raise OSError if the container does not exist, is not running, or if
+    no interface is found.
+    """
+    path = '/proc/{}/root/sys/class/net/'.format(pid)
+    try:
+        interfaces = [
+            i for i in os.listdir(path)
+            if i not in exclude and
+            os.path.isdir(os.path.join(path, i))
+            ]
+    except OSError:
+        raise _error('not_found')
+    if not interfaces:
+        raise _error('not_connected')
+    return interfaces
+
+
+@root_required
+def get_ip_addresses(pid, interfaces):
+    """Return ip addresses of LXC `interfaces`, given the container's `pid`.
+
+    Raise OSError if the container is not found or one the ip addresses
+    is not retrievable.
 
     Note that `socket.gethostbyname` is not usable in this context: it uses
     the system's dns resolver that by default does not resolve lxc names.
     """
+    ip_addresses = []
     with SetNamespace(pid):
         # Retrieve the ip address for the given network interface.
         # Original from http://code.activestate.com/recipes/
         #     439094-get-the-ip-address-associated-with-a-network-inter/
         with closing(socket.socket(socket.AF_INET, socket.SOCK_DGRAM)) as s:
-            # Slice the interface because the buffer size used to hold an
-            # interface name, including its terminating zero byte,
-            # is 16 in Linux (see /usr/include/linux/if.h).
-            pack = struct.pack('256s', interface[:15])
-            try:
-                # Use the ioctl Unix routine to request SIOCGIFADDR.
-                binary_ip = fcntl.ioctl(s.fileno(), SIOCGIFADDR, pack)[20:24]
-                # Convert the packet ipv4 address to its standard dotted-quad
-                # string representation.
-                ip = socket.inet_ntoa(binary_ip)
-            except (IOError, socket.error):
-                raise _error('not_connected')
-    return ip
+            for interface in interfaces:
+                # Slice the interface because the buffer size used to hold an
+                # interface name, including its terminating zero byte,
+                # is 16 in Linux (see /usr/include/linux/if.h).
+                packed = struct.pack('256s', interface[:15])
+                try:
+                    # Use the ioctl Unix routine to request SIOCGIFADDR.
+                    binary_ip = fcntl.ioctl(
+                        s.fileno(), SIOCGIFADDR, packed)[20:24]
+                    # Convert the packet ipv4 address to its standard
+                    # dotted-quad string representation.
+                    ip = socket.inet_ntoa(binary_ip)
+                except (IOError, socket.error):
+                    raise _error('not_connected')
+                ip_addresses.append(ip)
+    return ip_addresses
 
 
 def main():
-    name, interface, short = _parse_args()
+    name, interface = _parse_args()
     try:
         pid = get_pid(name)
-        ip = get_ip(pid, interface)
+        if interface is None:
+            interfaces = get_interfaces(pid, exclude=['lo'])
+        else:
+            interfaces = [interface]
+        ip_addresses = get_ip_addresses(pid, interfaces)
     except (KeyboardInterrupt, OSError) as err:
         return err
-    print _output(name, ip, short)
+    print _output(interfaces, ip_addresses, interface is not None)
 
 
 if __name__ == '__main__':

=== modified file 'lplxcip/tests/test_helpers.py'
--- lplxcip/tests/test_helpers.py	2012-04-26 11:26:15 +0000
+++ lplxcip/tests/test_helpers.py	2012-05-03 10:50:57 +0000
@@ -23,18 +23,22 @@
 
 class OutputTest(unittest.TestCase):
 
-    name = 'lxc'
-    ip = '10.0.3.100'
+    interfaces = ['eth0', 'lo']
+    ip_addresses = ['10.0.3.100', '127.0.0.1']
 
     def test_short(self):
         # Ensure the short output just displays the ip address.
-        output = lxcip._output(self.name, self.ip, True)
-        self.assertEqual(self.ip, output)
+        output = lxcip._output(
+            self.interfaces[:1], self.ip_addresses[:1], True)
+        self.assertEqual(self.ip_addresses[0], output)
 
     def test_verbose(self):
         # Ensure the verbose output is correctly generated.
-        output = lxcip._output(self.name, self.ip, False)
-        self.assertEqual('{}: {}'.format(self.name, self.ip), output)
+        output = lxcip._output(self.interfaces, self.ip_addresses, False)
+        expected = '{}: {}\n{}: {}'.format(
+            self.interfaces[0], self.ip_addresses[0],
+            self.interfaces[1], self.ip_addresses[1])
+        self.assertEqual(expected, output)
 
 
 class RedirectStderrTest(unittest.TestCase):

=== modified file 'lplxcip/tests/test_lxcip.py'
--- lplxcip/tests/test_lxcip.py	2012-04-27 08:26:14 +0000
+++ lplxcip/tests/test_lxcip.py	2012-05-03 10:50:57 +0000
@@ -24,28 +24,78 @@
     lxc.destroy()
 
 
+class GetInterfacesTest(utils.ErrorTestMixin, unittest.TestCase):
+
+    def setUp(self):
+        self.pid = utils.get_pid(lxc.name)
+
+    def test_all_interfaces(self):
+        # Ensure all interfaces are correctly retrieved for a running LXC.
+        interfaces = utils.get_interfaces(self.pid)
+        self.assertItemsEqual(['eth0', 'lo'], interfaces)
+
+    def test_exclude(self):
+        # Ensure interfaces are correctly excluded from the returned list.
+        interfaces = utils.get_interfaces(self.pid, exclude=['lo'])
+        self.assertEqual(['eth0'], interfaces)
+
+    def test_container_does_not_exist(self):
+        # Ensure an OSError is raised if the container does not exist.
+        with self.assertOSError('not_found'):
+            utils.get_interfaces(0)
+
+    def test_container_not_running(self):
+        # Ensure an OSError is raised if the container is not running.
+        lxc.stop()
+        try:
+            with self.assertOSError('not_found'):
+                lxcip.get_interfaces(self.pid)
+        finally:
+            lxc.start()
+
+    def test_interfaces_not_found(self):
+        # Ensure an OSError is raised if no interface is found.
+        with self.assertOSError('not_connected'):
+            lxcip.get_interfaces(self.pid, exclude=['eth0', 'lo'])
+
+    def test_not_root(self):
+        # An OSerror is raised if the current user is not root.
+        with utils.mock_geteuid(1000):
+            with self.assertOSError('not_root'):
+                lxcip.get_interfaces(self.pid)
+
+
 class LXCIpTest(utils.ErrorTestMixin, unittest.TestCase):
 
     name = lxc.name
+    interfaces = ['eth0']
 
-    def test_get_ip(self):
+    def test_ip_address(self):
         # Ensure the ip address of the container is correctly retrieved.
         pid = utils.get_pid(self.name)
-        ip = utils.get_ip(pid, lxcip.INTERFACE)
+        ip = utils.get_ip_addresses(pid, self.interfaces)[0]
         self.assertEqual(self.name, utils.gethostbyaddr(ip)[0])
 
     def test_loopback(self):
         # Ensure the loopback ip address is correctly retrieved.
         pid = utils.get_pid(self.name)
-        ip = utils.get_ip(pid, 'lo')
+        ip = utils.get_ip_addresses(pid, ['lo'])[0]
         self.assertEqual('127.0.0.1', ip)
 
+    def test_multiple_interfaces(self):
+        # Ensure the ip address of the container is correctly retrieved for
+        # each interface.
+        pid = utils.get_pid(self.name)
+        eth0, lo = utils.get_ip_addresses(pid, ['eth0', 'lo'])
+        self.assertEqual(self.name, utils.gethostbyaddr(eth0)[0])
+        self.assertEqual('127.0.0.1', lo)
+
     def test_invalid_interface(self):
         # Ensure an OSError is raised if the ip addres is requested for
         # a non existent interface.
         pid = utils.get_pid(self.name)
         with self.assertOSError('not_connected'):
-            lxcip.get_ip(pid, '__does_not_exist__')
+            lxcip.get_ip_addresses(pid, ['__does_not_exist__'])
 
     def test_invalid_name(self):
         # If the container does not exist or is not running, trying to obtain
@@ -57,26 +107,28 @@
         # If the container does not exist or is not running, trying to obtain
         # the ip address raises an OSError.
         with self.assertOSError('not_found'):
-            lxcip.get_ip(0, lxcip.INTERFACE)
+            lxcip.get_ip_addresses(0, self.interfaces)
 
     def test_not_root(self):
-        # An OSerror is raised by get_pid and get_ip if the current user
-        # is not root.
+        # An OSerror is raised by get_pid and get_ip_addresses if
+        # the current user is not root.
         with utils.mock_geteuid(1000):
             with self.assertOSError('not_root'):
                 lxcip.get_pid(self.name)
             with self.assertOSError('not_root'):
-                lxcip.get_ip(1, lxcip.INTERFACE)
+                lxcip.get_ip_addresses(1, self.interfaces)
 
     def test_restart(self):
         # Ensure the functions work as expected if the container is
         # stopped and then restarted.
         lxc.stop()
-        with self.assertOSError('not_found'):
-            lxcip.get_pid(self.name)
-        lxc.start()
+        try:
+            with self.assertOSError('not_found'):
+                lxcip.get_pid(self.name)
+        finally:
+            lxc.start()
         pid = utils.get_pid(self.name)
-        ip = utils.get_ip(pid, lxcip.INTERFACE)
+        ip = utils.get_ip_addresses(pid, self.interfaces)[0]
         self.assertEqual(self.name, utils.gethostbyaddr(ip)[0])
 
     def test_race_condition(self):
@@ -84,9 +136,11 @@
         # stopped during the script execution.
         pid = utils.get_pid(self.name)
         lxc.stop()
-        with self.assertOSError('not_found'):
-            lxcip.get_ip(pid, lxcip.INTERFACE)
-        lxc.start()
-        # Wait for the container to be up again before proceeding with
-        # other tests.
-        utils.get_pid(self.name)
+        try:
+            with self.assertOSError('not_found'):
+                lxcip.get_ip_addresses(pid, self.interfaces)
+        finally:
+            lxc.start()
+            # Wait for the container to be up again before proceeding with
+            # other tests.
+            utils.get_pid(self.name)

=== modified file 'lplxcip/tests/utils.py'
--- lplxcip/tests/utils.py	2012-04-26 11:26:15 +0000
+++ lplxcip/tests/utils.py	2012-05-03 10:50:57 +0000
@@ -127,6 +127,7 @@
     return decorator
 
 
+get_interfaces = retry(OSError)(lxcip.get_interfaces)
+get_ip_addresses = retry(OSError)(lxcip.get_ip_addresses)
 get_pid = retry(OSError)(lxcip.get_pid)
-get_ip = retry(OSError)(lxcip.get_ip)
 gethostbyaddr = retry(socket.herror)(socket.gethostbyaddr)

=== modified file 'lpsetup/settings.py'
--- lpsetup/settings.py	2012-04-30 16:53:35 +0000
+++ lpsetup/settings.py	2012-05-03 10:50:57 +0000
@@ -60,7 +60,7 @@
 LXC_GUEST_ARCH = 'i386'
 LXC_GUEST_CHOICES = ('lucid', 'oneiric', 'precise')
 LXC_GUEST_OS = LXC_GUEST_CHOICES[0]
-LXC_IP_COMMAND = ('/usr/bin/lp-lxc-ip', '-s', '-i', 'eth0', '-n')
+LXC_IP_COMMAND = ('/usr/bin/lp-lxc-ip', '-i', 'eth0', '-n')
 LXC_LEASES = (
     '/var/lib/dhcp3/dhclient.eth0.leases',
     '/var/lib/dhcp/dhclient.eth0.leases',