[PATCH 0/4] support create/delete VMIface

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> This patch depends on "[PATCH V1 0/6] VM supports interfaces" 1. get all vms: $ curl -u <user> -H 'Accept: application/json' -H 'Content-type: application/json' http://localhost:8000/vms 2. get all networks: $ curl -u <user> -H 'Accept: application/json' -H 'Content-type: application/json' http://localhost:8000/networks 3. get all ifaces of a vm $ curl -u <user> -H 'Accept: application/json' -H 'Content-type: application/json' http://localhost:8000/vms/test-vm-0/ifaces/ [ { "mac":"52:54:00:00:00:01", "model":"virtio", "type":"network", "network":"default" } ] 4. attach a new iface to vm $ curl -u <user> -H 'Accept: application/json' -H 'Content-type: application/json' http://localhost:8000/vms/test-vm-8/ifaces -X POST -d ' { "type":"network", "network":"test-network-0" } ' { "mac":"52:54:00:00:00:0d", "model":"virtio", "type":"network", "network":"test-network-0" } 5. detach a iface from vm $ curl -u <user> -H 'Accept: application/json' -H 'Content-type: application/json' http://localhost:8000/vms/test-vm-8/ifaces/52:54:00:00:00:0d -X DELETE ShaoHe Feng (4): support create/delete VMIface: update model support create/delete VMIface: update mockmodel support create/delete VMIface: update API.json support create/delete VMIface: update testcase src/kimchi/API.json | 21 ++++++++++++++++++++ src/kimchi/mockmodel.py | 21 ++++++++++++++++++++ src/kimchi/model.py | 51 ++++++++++++++++++++++++++++++++++++++++++++++++- tests/test_model.py | 22 +++++++++++++++++++++ tests/test_rest.py | 27 ++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 1 deletion(-) -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> attach a network interface to vm in model detach a network interface for vm in model Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/model.py | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/kimchi/model.py b/src/kimchi/model.py index 383509d..fbc920d 100644 --- a/src/kimchi/model.py +++ b/src/kimchi/model.py @@ -33,6 +33,7 @@ import logging import os import platform import psutil +import random import re import shutil import subprocess @@ -45,7 +46,7 @@ import uuid from cherrypy.process.plugins import BackgroundTask from cherrypy.process.plugins import SimplePlugin from collections import defaultdict -from lxml import objectify +from lxml import objectify, etree from xml.etree import ElementTree @@ -968,6 +969,44 @@ class Model(object): iface.destroy() iface.undefine() + def vmifaces_create(self, vm, params): + def randomMAC(): + mac = [0x52, 0x54, 0x00, + random.randint(0x00, 0x7f), + random.randint(0x00, 0xff), + random.randint(0x00, 0xff)] + return ':'.join(map(lambda x: "%02x" % x, mac)) + + if (params["type"] == "network" and + params["network"] not in self.networks_get_list()): + raise InvalidParameter("%s is not an available network" % + params["network"]) + + dom = self._get_vm(vm) + macs = (iface.mac.get('address') + for iface in self._get_vmifaces(vm)) + + mac = randomMAC() + while True: + if mac not in macs: + break + mac = randomMAC() + + E = objectify.E + children = [E.mac(address=mac)] + ("network" in params.keys() and + children.append(E.source(network=params['network']))) + ("model" in params.keys() and + children.append(E.model(type=params['model']))) + attrib = {"type": params["type"]} + + xml = etree.tostring(E.interface(*children, **attrib)) + + dom.attachDeviceFlags(xml, + libvirt.VIR_DOMAIN_AFFECT_CURRENT) + + return mac + def _get_vmifaces(self, vm): dom = self._get_vm(vm) xml = dom.XMLDesc(0) @@ -1003,6 +1042,16 @@ class Model(object): return info + def vmiface_delete(self, vm, mac): + dom = self._get_vm(vm) + iface = self._get_vmiface(vm, mac) + + if iface is None: + raise NotFoundError('iface: "%s"' % mac) + + dom.detachDeviceFlags(etree.tostring(iface), + libvirt.VIR_DOMAIN_AFFECT_CURRENT) + def add_task(self, target_uri, fn, opaque=None): id = self.next_taskid self.next_taskid = self.next_taskid + 1 -- 1.8.4.2

Am 21-01-2014 15:25, schrieb shaohef@linux.vnet.ibm.com:
@@ -45,7 +46,7 @@ import uuid from cherrypy.process.plugins import BackgroundTask from cherrypy.process.plugins import SimplePlugin from collections import defaultdict -from lxml import objectify +from lxml import objectify, etree from xml.etree import ElementTree Please import the elements in alphabetical order. + dom.attachDeviceFlags(xml, + libvirt.VIR_DOMAIN_AFFECT_CURRENT) The two lines above can be merged into one, it won't be longer than 80 characters.

On 01/22/2014 01:25 AM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
attach a network interface to vm in model detach a network interface for vm in model
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/model.py | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-)
diff --git a/src/kimchi/model.py b/src/kimchi/model.py index 383509d..fbc920d 100644 --- a/src/kimchi/model.py +++ b/src/kimchi/model.py @@ -33,6 +33,7 @@ import logging import os import platform import psutil +import random import re import shutil import subprocess @@ -45,7 +46,7 @@ import uuid from cherrypy.process.plugins import BackgroundTask from cherrypy.process.plugins import SimplePlugin from collections import defaultdict -from lxml import objectify +from lxml import objectify, etree from xml.etree import ElementTree
@@ -968,6 +969,44 @@ class Model(object): iface.destroy() iface.undefine()
+ def vmifaces_create(self, vm, params): + def randomMAC(): + mac = [0x52, 0x54, 0x00, + random.randint(0x00, 0x7f), + random.randint(0x00, 0xff), + random.randint(0x00, 0xff)] + return ':'.join(map(lambda x: "%02x" % x, mac)) + + if (params["type"] == "network" and + params["network"] not in self.networks_get_list()): + raise InvalidParameter("%s is not an available network" % + params["network"]) + + dom = self._get_vm(vm) + macs = (iface.mac.get('address') + for iface in self._get_vmifaces(vm)) + + mac = randomMAC() + while True: + if mac not in macs: + break + mac = randomMAC() + + E = objectify.E + children = [E.mac(address=mac)] + ("network" in params.keys() and + children.append(E.source(network=params['network']))) + ("model" in params.keys() and + children.append(E.model(type=params['model']))) + attrib = {"type": params["type"]} Any reason not using lxml.builder ? + + xml = etree.tostring(E.interface(*children, **attrib)) + + dom.attachDeviceFlags(xml, + libvirt.VIR_DOMAIN_AFFECT_CURRENT) I think we need affect both the running guest state and persist config. + + return mac + def _get_vmifaces(self, vm): dom = self._get_vm(vm) xml = dom.XMLDesc(0) @@ -1003,6 +1042,16 @@ class Model(object):
return info
+ def vmiface_delete(self, vm, mac): + dom = self._get_vm(vm) + iface = self._get_vmiface(vm, mac) + + if iface is None: + raise NotFoundError('iface: "%s"' % mac) + + dom.detachDeviceFlags(etree.tostring(iface), + libvirt.VIR_DOMAIN_AFFECT_CURRENT) + def add_task(self, target_uri, fn, opaque=None): id = self.next_taskid self.next_taskid = self.next_taskid + 1

On 01/22/2014 10:14 AM, Mark Wu wrote:
On 01/22/2014 01:25 AM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
attach a network interface to vm in model detach a network interface for vm in model
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/model.py | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-)
diff --git a/src/kimchi/model.py b/src/kimchi/model.py index 383509d..fbc920d 100644 --- a/src/kimchi/model.py +++ b/src/kimchi/model.py @@ -33,6 +33,7 @@ import logging import os import platform import psutil +import random import re import shutil import subprocess @@ -45,7 +46,7 @@ import uuid from cherrypy.process.plugins import BackgroundTask from cherrypy.process.plugins import SimplePlugin from collections import defaultdict -from lxml import objectify +from lxml import objectify, etree from xml.etree import ElementTree
@@ -968,6 +969,44 @@ class Model(object): iface.destroy() iface.undefine()
+ def vmifaces_create(self, vm, params): + def randomMAC(): + mac = [0x52, 0x54, 0x00, + random.randint(0x00, 0x7f), + random.randint(0x00, 0xff), + random.randint(0x00, 0xff)] + return ':'.join(map(lambda x: "%02x" % x, mac)) + + if (params["type"] == "network" and + params["network"] not in self.networks_get_list()): + raise InvalidParameter("%s is not an available network" % + params["network"]) + + dom = self._get_vm(vm) + macs = (iface.mac.get('address') + for iface in self._get_vmifaces(vm)) + + mac = randomMAC() + while True: + if mac not in macs: + break + mac = randomMAC() + + E = objectify.E + children = [E.mac(address=mac)] + ("network" in params.keys() and + children.append(E.source(network=params['network']))) + ("model" in params.keys() and + children.append(E.model(type=params['model']))) + attrib = {"type": params["type"]} Any reason not using lxml.builder ?
lxml.objectify.ElementMaker from objectify.so, http://lxml.de/objectify.html lxml.builder.ElementMaker from builder.py but seems they are same usage for both tow class instances. In [15]: lxml.objectify.ElementMaker Type: type String Form:<type 'lxml.objectify.ElementMaker'> File: /usr/lib64/python2.7/site-packages/lxml/objectify.so Docstring: ElementMaker(self, namespace=None, nsmap=None, annotate=True, makeelement=None) In [17]: lxml.builder.ElementMaker? Type: type String Form:<class 'lxml.builder.ElementMaker'> File: /usr/lib64/python2.7/site-packages/lxml/builder.py Definition: lxml.builder.ElementMaker(self, tag, *children, **attrib) Docstring: Element generator factory. ... Constructor information: Definition:lxml.builder.ElementMaker(self, typemap=None, namespace=None, nsmap=None, makeelement=None)
+ + xml = etree.tostring(E.interface(*children, **attrib)) + + dom.attachDeviceFlags(xml, + libvirt.VIR_DOMAIN_AFFECT_CURRENT) I think we need affect both the running guest state and persist config. + + return mac + def _get_vmifaces(self, vm): dom = self._get_vm(vm) xml = dom.XMLDesc(0) @@ -1003,6 +1042,16 @@ class Model(object):
return info
+ def vmiface_delete(self, vm, mac): + dom = self._get_vm(vm) + iface = self._get_vmiface(vm, mac) + + if iface is None: + raise NotFoundError('iface: "%s"' % mac) + + dom.detachDeviceFlags(etree.tostring(iface), + libvirt.VIR_DOMAIN_AFFECT_CURRENT) + def add_task(self, target_uri, fn, opaque=None): id = self.next_taskid self.next_taskid = self.next_taskid + 1
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

On 01/22/2014 10:14 AM, Mark Wu wrote:
On 01/22/2014 01:25 AM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
attach a network interface to vm in model detach a network interface for vm in model
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/model.py | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-)
diff --git a/src/kimchi/model.py b/src/kimchi/model.py index 383509d..fbc920d 100644 --- a/src/kimchi/model.py +++ b/src/kimchi/model.py @@ -33,6 +33,7 @@ import logging import os import platform import psutil +import random import re import shutil import subprocess @@ -45,7 +46,7 @@ import uuid from cherrypy.process.plugins import BackgroundTask from cherrypy.process.plugins import SimplePlugin from collections import defaultdict -from lxml import objectify +from lxml import objectify, etree from xml.etree import ElementTree
@@ -968,6 +969,44 @@ class Model(object): iface.destroy() iface.undefine()
+ def vmifaces_create(self, vm, params): + def randomMAC(): + mac = [0x52, 0x54, 0x00, + random.randint(0x00, 0x7f), + random.randint(0x00, 0xff), + random.randint(0x00, 0xff)] + return ':'.join(map(lambda x: "%02x" % x, mac)) + + if (params["type"] == "network" and + params["network"] not in self.networks_get_list()): + raise InvalidParameter("%s is not an available network" % + params["network"]) + + dom = self._get_vm(vm) + macs = (iface.mac.get('address') + for iface in self._get_vmifaces(vm)) + + mac = randomMAC() + while True: + if mac not in macs: + break + mac = randomMAC() + + E = objectify.E + children = [E.mac(address=mac)] + ("network" in params.keys() and + children.append(E.source(network=params['network']))) + ("model" in params.keys() and + children.append(E.model(type=params['model']))) + attrib = {"type": params["type"]} Any reason not using lxml.builder ?
seems a difference. objectify support python type date. builder does not support python type date. In [74]: from lxml.builder import E In [75]: root = E.root( E.a("5"), E.b("6.1"), E.c("True"), E.d("how", tell="me") ) In [76]: print(etree.tostring(root, pretty_print=True)) <root> <a>5</a> <b>6.1</b> <c>True</c> <d tell="me">how</d> </root> In [79]: from lxml.objectify import E In [80]: root = E.root( E.a(5), E.b(6.1), E.c(True), E.d("how", tell="me") ) In [81]: print(etree.tostring(root, pretty_print=True)) <root xmlns:py="http://codespeak.net/lxml/objectify/pytype" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <a py:pytype="int">5</a> <b py:pytype="float">6.1</b> <c py:pytype="bool">true</c> <d tell="me" py:pytype="str">how</d> </root>
+ + xml = etree.tostring(E.interface(*children, **attrib)) + + dom.attachDeviceFlags(xml, + libvirt.VIR_DOMAIN_AFFECT_CURRENT) I think we need affect both the running guest state and persist config. + + return mac + def _get_vmifaces(self, vm): dom = self._get_vm(vm) xml = dom.XMLDesc(0) @@ -1003,6 +1042,16 @@ class Model(object):
return info
+ def vmiface_delete(self, vm, mac): + dom = self._get_vm(vm) + iface = self._get_vmiface(vm, mac) + + if iface is None: + raise NotFoundError('iface: "%s"' % mac) + + dom.detachDeviceFlags(etree.tostring(iface), + libvirt.VIR_DOMAIN_AFFECT_CURRENT) + def add_task(self, target_uri, fn, opaque=None): id = self.next_taskid self.next_taskid = self.next_taskid + 1
-- Thanks and best regards! Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> attach a network interface to vm in mockmodel detach a network interface for vm in mockmodel Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/mockmodel.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index e037366..bc63139 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -488,6 +488,20 @@ class MockModel(object): def networks_get_list(self): return sorted(self._mock_networks.keys()) + def vmifaces_create(self, vm, params): + if (params["type"] == "network" and + params["network"] not in self.networks_get_list()): + raise InvalidParameter("%s is not an available network" % + params["network"]) + dom = self._get_vm(vm) + iface = MockVMIface(params["network"]) + ("model" in params.keys() and + iface.info.update({"model": params["model"]})) + + mac = iface.info['mac'] + dom.ifaces[mac] = iface + return mac + def vmifaces_get_list(self, vm): dom = self._get_vm(vm) macs = dom.ifaces.keys() @@ -501,6 +515,13 @@ class MockModel(object): raise NotFoundError('iface: "%s"' % mac) return info + def vmiface_delete(self, vm, mac): + dom = self._get_vm(vm) + try: + del dom.ifaces[mac] + except KeyError: + raise NotFoundError('iface: "%s"' % mac) + def tasks_get_list(self): with self.objstore as session: return session.get_list('task') -- 1.8.4.2

Reviewed-by: Crístian Viana <vianac@linux.vnet.ibm.com> Am 21-01-2014 15:25, schrieb shaohef@linux.vnet.ibm.com:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
attach a network interface to vm in mockmodel detach a network interface for vm in mockmodel
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com>

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> add vmifaces_create parameters verification in API.json Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/API.json | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/kimchi/API.json b/src/kimchi/API.json index 46818d4..590fe88 100644 --- a/src/kimchi/API.json +++ b/src/kimchi/API.json @@ -156,6 +156,27 @@ } } }, + "vmifaces_create": { + "type": "object", + "properties": { + "type": { + "description": "The type of VM network interface that libvirt supports", + "type": "string", + "pattern": "^network$", + "required": true + }, + "network": { + "description": "the name of one available network", + "minLength": 1, + "type": "string" + }, + "model": { + "description": "model of emulated network interface card", + "type": "string", + "pattern": "^ne2k_pci|i82551|i82557b|i82559er|rtl8139|e1000|pcnet|virtio$", + } + } + }, "templates_create": { "type": "object", "properties": { -- 1.8.4.2

Reviewed-by: Crístian Viana <vianac@linux.vnet.ibm.com> Am 21-01-2014 15:25, schrieb shaohef@linux.vnet.ibm.com:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
add vmifaces_create parameters verification in API.json
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com>

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> updage tests/test_model.py and tests/test_rest.py Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- tests/test_model.py | 22 ++++++++++++++++++++++ tests/test_rest.py | 27 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/tests/test_model.py b/tests/test_model.py index c6c5d0e..547ad6b 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -131,6 +131,14 @@ class ModelTests(unittest.TestCase): inst.vms_create(params) rollback.prependDefer(inst.vm_delete, 'kimchi-ifaces') + # Create a network + net_name = 'test-network' + net_args = {'name': net_name, + 'connection': 'nat', + 'subnet': '127.0.100.0/24'} + inst.networks_create(net_args) + rollback.prependDefer(inst.network_delete, net_name) + ifaces = inst.vmifaces_get_list('kimchi-ifaces') self.assertEquals(1, len(ifaces)) @@ -139,6 +147,20 @@ class ModelTests(unittest.TestCase): 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) + self.assertEquals(17, len(mac)) + # detach network interface from vm + 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']) + self.assertEquals("virtio", iface["model"]) + @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_vm_storage_provisioning(self): inst = kimchi.model.Model(objstore_loc=self.tmp_store) diff --git a/tests/test_rest.py b/tests/test_rest.py index 5543cb5..935db9a 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -360,6 +360,16 @@ class RestTests(unittest.TestCase): rollback.prependDefer(self.request, '/vms/test-vm', '{}', 'DELETE') + # Create a network + req = json.dumps({'name': 'test-network', + 'connection': 'nat', + 'net': '127.0.1.0/24'}) + resp = self.request('/networks', req, 'POST') + self.assertEquals(201, resp.status) + # Delete the network + rollback.prependDefer(self.request, + '/networks/test-network', '{}', 'DELETE') + ifaces = json.loads(self.request('/vms/test-vm/ifaces').read()) self.assertEquals(1, len(ifaces)) @@ -370,6 +380,23 @@ class RestTests(unittest.TestCase): self.assertEquals(17, len(res['mac'])) self.assertEquals('virtio', res['model']) + # attach network interface to vm + req = json.dumps({"type": "network", + "network": "test-network", + "model": "virtio"}) + resp = self.request('/vms/test-vm/ifaces', req, 'POST') + self.assertEquals(201, resp.status) + iface = json.loads(resp.read()) + + self.assertEquals('test-network', iface['network']) + self.assertEquals(17, len(iface['mac'])) + self.assertEquals('virtio', iface['model']) + self.assertEquals('network', iface['type']) + + # detach network interface from vm + resp = self.request('/vms/test-vm/ifaces' % iface['mac'], + '{}', 'DELETE') + self.assertEquals(204, resp.status) def test_vm_customise_storage(self): # Create a Template -- 1.8.4.2

Reviewed-by: Crístian Viana <vianac@linux.vnet.ibm.com> Am 21-01-2014 15:25, schrieb shaohef@linux.vnet.ibm.com:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
updage tests/test_model.py and tests/test_rest.py
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
participants (4)
-
Crístian Viana
-
Mark Wu
-
shaohef@linux.vnet.ibm.com
-
Sheldon