Skip to content

Commit

Permalink
issue kimchi-project#548: Hotplug network interfaces
Browse files Browse the repository at this point in the history
Currently, network interfaces can only be added to shutoff VMs. However,
the use might want to attach an interface to a running VM as well.

Allow network interfaces to be attached also while the VM is running.
The related test cases have been updated to perform the same operations
on both a shutoff and a running VM.

Fix issue kimchi-project#548 (Kimchi does not support NIC hot plug) - backend only.

Signed-off-by: Crístian Deives <[email protected]>
  • Loading branch information
cd1 authored and alinefm committed Apr 20, 2015
1 parent aeb667d commit 81dd7ce
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 60 deletions.
1 change: 0 additions & 1 deletion src/kimchi/i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@

"KCHVMIF0001E": _("Interface %(iface)s does not exist in virtual machine %(name)s"),
"KCHVMIF0002E": _("Network %(network)s specified for virtual machine %(name)s does not exist"),
"KCHVMIF0003E": _("Do not support guest interface hot plug attachment"),
"KCHVMIF0004E": _("Supported virtual machine interfaces type is only network"),
"KCHVMIF0005E": _("Network name for virtual machine interface must be a string"),
"KCHVMIF0006E": _("Invalid network model card specified for virtual machine interface"),
Expand Down
46 changes: 29 additions & 17 deletions src/kimchi/model/vmifaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@
import libvirt
from lxml import etree, objectify

from kimchi.exception import InvalidOperation, InvalidParameter
from kimchi.exception import MissingParameter, NotFoundError
from kimchi.exception import InvalidParameter, MissingParameter, NotFoundError
from kimchi.model.config import CapabilitiesModel
from kimchi.model.vms import DOM_STATE_MAP, VMModel
from kimchi.xmlutils.interface import get_iface_xml
Expand Down Expand Up @@ -55,10 +54,6 @@ def create(self, vm, params):
raise InvalidParameter('KCHVMIF0002E',
{'name': vm, 'network': network})

dom = VMModel.get_vm(vm, self.conn)
if DOM_STATE_MAP[dom.info()[0]] != "shutoff":
raise InvalidOperation("KCHVMIF0003E")

macs = (iface.mac.get('address')
for iface in self.get_vmifaces(vm, self.conn))

Expand All @@ -67,10 +62,19 @@ def create(self, vm, params):
if params['mac'] not in macs:
break

dom = VMModel.get_vm(vm, self.conn)

os_data = VMModel.vm_get_os_metadata(dom, self.caps.metadata_support)
os_version, os_distro = os_data
xml = get_iface_xml(params, conn.getInfo()[0], os_distro, os_version)
dom.attachDeviceFlags(xml, libvirt.VIR_DOMAIN_AFFECT_CURRENT)

flags = 0
if dom.isPersistent():
flags |= libvirt.VIR_DOMAIN_AFFECT_CONFIG
if DOM_STATE_MAP[dom.info()[0]] != "shutoff":
flags |= libvirt.VIR_DOMAIN_AFFECT_LIVE

dom.attachDeviceFlags(xml, flags)

return params['mac']

Expand Down Expand Up @@ -125,14 +129,16 @@ def delete(self, vm, mac):
dom = VMModel.get_vm(vm, self.conn)
iface = self._get_vmiface(vm, mac)

if DOM_STATE_MAP[dom.info()[0]] != "shutoff":
raise InvalidOperation("KCHVMIF0003E")

if iface is None:
raise NotFoundError("KCHVMIF0001E", {'name': vm, 'iface': mac})

dom.detachDeviceFlags(etree.tostring(iface),
libvirt.VIR_DOMAIN_AFFECT_CURRENT)
flags = 0
if dom.isPersistent():
flags |= libvirt.VIR_DOMAIN_AFFECT_CONFIG
if DOM_STATE_MAP[dom.info()[0]] != "shutoff":
flags |= libvirt.VIR_DOMAIN_AFFECT_LIVE

dom.detachDeviceFlags(etree.tostring(iface), flags)

def update(self, vm, mac, params):
dom = VMModel.get_vm(vm, self.conn)
Expand All @@ -141,16 +147,22 @@ def update(self, vm, mac, params):
if iface is None:
raise NotFoundError("KCHVMIF0001E", {'name': vm, 'iface': mac})

# FIXME we will support to change the live VM configuration later.
flags = 0
if dom.isPersistent():
flags |= libvirt.VIR_DOMAIN_AFFECT_CONFIG
if DOM_STATE_MAP[dom.info()[0]] != "shutoff":
flags |= libvirt.VIR_DOMAIN_AFFECT_LIVE

if iface.attrib['type'] == 'network' and 'network' in params:
iface.source.attrib['network'] = params['network']
xml = etree.tostring(iface)
dom.updateDeviceFlags(xml, flags=libvirt.VIR_DOMAIN_AFFECT_CONFIG)

# change on the persisted VM configuration only.
if 'model' in params and dom.isPersistent():
dom.updateDeviceFlags(xml, flags=flags)

if 'model' in params:
iface.model.attrib["type"] = params['model']
xml = etree.tostring(iface)
dom.updateDeviceFlags(xml, flags=libvirt.VIR_DOMAIN_AFFECT_CONFIG)

dom.updateDeviceFlags(xml, flags=flags)

return mac
86 changes: 44 additions & 42 deletions tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,6 @@ def test_vm_ifaces(self):
params = {'name': 'test', 'disks': [], 'cdrom': UBUNTU_ISO}
inst.templates_create(params)
rollback.prependDefer(inst.template_delete, 'test')
params = {'name': 'kimchi-ifaces', 'template': '/templates/test'}
inst.vms_create(params)
rollback.prependDefer(inst.vm_delete, 'kimchi-ifaces')

# Create a network
net_name = 'test-network'
Expand All @@ -330,45 +327,50 @@ def test_vm_ifaces(self):
inst.network_activate(net_name)
rollback.prependDefer(inst.network_deactivate, net_name)

ifaces = inst.vmifaces_get_list('kimchi-ifaces')
self.assertEquals(1, len(ifaces))

iface = inst.vmiface_lookup('kimchi-ifaces', ifaces[0])
self.assertEquals(17, len(iface['mac']))
self.assertEquals("default", iface['network'])
self.assertIn("model", iface)

# attach network interface to vm
iface_args = {"type": "network",
"network": "test-network",
"model": "virtio"}
mac = inst.vmifaces_create('kimchi-ifaces', iface_args)
# detach network interface from vm
rollback.prependDefer(inst.vmiface_delete, 'kimchi-ifaces', mac)
self.assertEquals(17, len(mac))

iface = inst.vmiface_lookup('kimchi-ifaces', mac)
self.assertEquals("network", iface["type"])
self.assertEquals("test-network", iface['network'])
self.assertEquals("virtio", iface["model"])

# attach network interface to vm without providing model
iface_args = {"type": "network",
"network": "test-network"}
mac = inst.vmifaces_create('kimchi-ifaces', iface_args)
rollback.prependDefer(inst.vmiface_delete, 'kimchi-ifaces', mac)

iface = inst.vmiface_lookup('kimchi-ifaces', mac)
self.assertEquals("network", iface["type"])
self.assertEquals("test-network", iface['network'])

# update vm interface
iface_args = {"network": "default",
"model": "e1000"}
inst.vmiface_update('kimchi-ifaces', mac, iface_args)
iface = inst.vmiface_lookup('kimchi-ifaces', mac)
self.assertEquals("default", iface['network'])
self.assertEquals("e1000", iface["model"])
for vm_name in ['kimchi-ifaces', 'kimchi-ifaces-running']:
params = {'name': vm_name, 'template': '/templates/test'}
inst.vms_create(params)
rollback.prependDefer(inst.vm_delete, vm_name)

ifaces = inst.vmifaces_get_list(vm_name)
self.assertEquals(1, len(ifaces))

iface = inst.vmiface_lookup(vm_name, ifaces[0])
self.assertEquals(17, len(iface['mac']))
self.assertEquals("default", iface['network'])
self.assertIn("model", iface)

# attach network interface to vm
iface_args = {"type": "network",
"network": "test-network",
"model": "virtio"}
mac = inst.vmifaces_create(vm_name, iface_args)
# detach network interface from vm
rollback.prependDefer(inst.vmiface_delete, vm_name, mac)
self.assertEquals(17, len(mac))

iface = inst.vmiface_lookup(vm_name, mac)
self.assertEquals("network", iface["type"])
self.assertEquals("test-network", iface['network'])
self.assertEquals("virtio", iface["model"])

# attach network interface to vm without providing model
iface_args = {"type": "network",
"network": "test-network"}
mac = inst.vmifaces_create(vm_name, iface_args)
rollback.prependDefer(inst.vmiface_delete, vm_name, mac)

iface = inst.vmiface_lookup(vm_name, mac)
self.assertEquals("network", iface["type"])
self.assertEquals("test-network", iface['network'])

# update vm interface
iface_args = {"network": "default",
"model": "e1000"}
inst.vmiface_update(vm_name, mac, iface_args)
iface = inst.vmiface_lookup(vm_name, mac)
self.assertEquals("default", iface['network'])
self.assertEquals("e1000", iface["model"])

@unittest.skipUnless(utils.running_as_root(), 'Must be run as root')
def test_vm_disk(self):
Expand Down

0 comments on commit 81dd7ce

Please sign in to comment.