
On 06/17/2015 08:24 AM, Aline Manera wrote:
Reviewed-by: Aline Manera <alinefm@linux.vnet.ibm.com>
Christy, we are current in a code freeze for Kimchi 1.5, so I will merge it as soon as 1.5 is released next week.
Thanks!
On 15/06/2015 19:56, Christy Perez wrote:
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@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')