[Kimchi-devel] [PATCH v2] List IPs of VM Ifaces

Christy Perez christy at linux.vnet.ibm.com
Mon Jun 15 22:56:13 UTC 2015


v2:
- Check VM's state for shutoff, and return empty list of IPs if so.
- RestTests: Start VM before checking for an IP in the list returned.

Signed-off-by: Christy Perez <christy at linux.vnet.ibm.com>
---
 docs/API.md                  |  1 +
 src/kimchi/mockmodel.py      |  9 +++++++++
 src/kimchi/model/vmifaces.py | 35 +++++++++++++++++++++++++++++++++++
 tests/test_rest.py           | 16 ++++++++++++++++
 4 files changed, 61 insertions(+)

diff --git a/docs/API.md b/docs/API.md
index e022c9e..10e80ac 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -299,6 +299,7 @@ A interface represents available network interface on VM.
     * bridge *(optional)*: the name of resource bridge, only be available when the
               interface type is bridge.
     * mac: Media Access Control Address of the VM interface.
+    * ips: A list of IP addresses associated with this MAC.
     * model *(optional)*: model of emulated network interface card. It will be one of these models:
              ne2k_pci, i82551, i82557b, i82559er, rtl8139, e1000, pcnet and virtio.
     * network *(optional)*: the name of resource network, only be available when the
diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py
index aaf1af2..3f74b80 100644
--- a/src/kimchi/mockmodel.py
+++ b/src/kimchi/mockmodel.py
@@ -24,6 +24,7 @@ import random
 import time
 
 import kimchi.model.cpuinfo
+import kimchi.model.vmifaces
 
 from lxml import objectify
 from lxml.builder import E
@@ -76,6 +77,7 @@ class MockModel(Model):
 
         kimchi.model.cpuinfo.get_topo_capabilities = \
             MockModel.get_topo_capabilities
+        kimchi.model.vmifaces.getDHCPLeases = MockModel.getDHCPLeases
         libvirt.virConnect.defineXML = MockModel.domainDefineXML
         libvirt.virDomain.XMLDesc = MockModel.domainXMLDesc
         libvirt.virDomain.undefine = MockModel.undefineDomain
@@ -235,6 +237,13 @@ class MockModel(Model):
         pool = vol.storagePoolLookupByVolume()
         pool.createXML(new_xml)
 
+    @staticmethod
+    def getDHCPLeases(net, mac):
+        return [{'iface': 'virbr1', 'ipaddr': '192.168.0.167',
+                 'hostname': 'kimchi', 'expirytime': 1433285036L,
+                 'prefix': 24, 'clientid': '01:%s' % mac,
+                 'mac': mac, 'iaid': None, 'type': 0}]
+
     def _probe_image(self, path):
         return ('unknown', 'unknown')
 
diff --git a/src/kimchi/model/vmifaces.py b/src/kimchi/model/vmifaces.py
index 93a769b..5df60fc 100644
--- a/src/kimchi/model/vmifaces.py
+++ b/src/kimchi/model/vmifaces.py
@@ -29,6 +29,14 @@ from kimchi.model.vms import DOM_STATE_MAP, VMModel
 from kimchi.xmlutils.interface import get_iface_xml
 
 
+def getDHCPLeases(net, mac):
+    try:
+        leases = net.DHCPLeases(mac)
+        return leases
+    except libvirt.libvirtError:
+        return []
+
+
 class VMIfacesModel(object):
     def __init__(self, **kargs):
         self.conn = kargs['conn']
@@ -132,9 +140,36 @@ class VMIfaceModel(object):
             info['network'] = iface.source.get('network')
         if info['type'] == 'bridge':
             info['bridge'] = iface.source.get('bridge')
+        info['ips'] = self._get_ips(vm, info['mac'], info['network'])
 
         return info
 
+    def _get_ips(self, vm, mac, network):
+        ips = []
+
+        # Return empty list if shutoff, even if leases still valid or ARP
+        #   cache has entries for this MAC.
+        conn = self.conn.get()
+        dom = VMModel.get_vm(vm, self.conn)
+        if DOM_STATE_MAP[dom.info()[0]] == "shutoff":
+            return ips
+
+        # An iface may have multiple IPs
+        # An IP could have been assigned without libvirt.
+        # First check the ARP cache.
+        with open('/proc/net/arp') as f:
+            ips = [line.split()[0] for line in f.xreadlines() if mac in line]
+        # Some ifaces may be inactive, so if the ARP cache didn't have them,
+        # and they happen to be assigned via DHCP, we can check there too.
+        net = conn.networkLookupByName(network)
+        leases = getDHCPLeases(net, mac)
+        for lease in leases:
+            ip = lease.get('ipaddr')
+            if ip not in ips:
+                ips.append(ip)
+
+        return ips
+
     def delete(self, vm, mac):
         dom = VMModel.get_vm(vm, self.conn)
         iface = self._get_vmiface(vm, mac)
diff --git a/tests/test_rest.py b/tests/test_rest.py
index c2d142f..f0e27e7 100644
--- a/tests/test_rest.py
+++ b/tests/test_rest.py
@@ -713,6 +713,7 @@ class RestTests(unittest.TestCase):
                 self.assertEquals(17, len(res['mac']))
                 self.assertEquals(get_template_default('old', 'nic_model'),
                                   res['model'])
+                self.assertTrue('ips' in res)
 
             # try to attach an interface without specifying 'model'
             req = json.dumps({'type': 'network'})
@@ -743,6 +744,21 @@ class RestTests(unittest.TestCase):
                                             newMacAddr).read())
             self.assertEquals(newMacAddr, iface['mac'])
 
+            # Start the VM
+            resp = self.request('/vms/test-vm/start', '{}', 'POST')
+            vm = json.loads(self.request('/vms/test-vm').read())
+            self.assertEquals('running', vm['state'])
+
+            # Check for an IP address
+            iface = json.loads(self.request('/vms/test-vm/ifaces/%s' %
+                                            newMacAddr).read())
+            self.assertTrue(len(iface['ips']) > 0)
+
+            # Force poweroff the VM
+            resp = self.request('/vms/test-vm/poweroff', '{}', 'POST')
+            vm = json.loads(self.request('/vms/test-vm').read())
+            self.assertEquals('shutoff', vm['state'])
+
             # detach network interface from vm
             resp = self.request('/vms/test-vm/ifaces/%s' % iface['mac'],
                                 '{}', 'DELETE')
-- 
2.1.0




More information about the Kimchi-devel mailing list