yellow team mailing list archive
-
yellow team
-
Mailing list archive
-
Message #00809
[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',