
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> This patch set depends on this patch: [PATCH V3 0/4] improve controller V1 -> V2 use the "mac" to identify the iface and remove "name" parameter $ curl -u user -H 'Accept: application/json' -H 'Content-type: application/json' \ http://localhost:800/vms/test-vm-8/ifaces/ [ { "mac":"52:54:00:00:00:08", "model":"virtio", "type":"network", "network":"default", "name":"52:54:00:00:00:08" } ] $ curl -u user -H 'Accept: application/json' -H 'Content-type: application/json' \ http://localhost:800/vms/test-vm-8/ifaces/52:54:00:00:00:08 { "mac":"52:54:00:00:00:08", "model":"virtio", "type":"network", "network":"default", "name":"52:54:00:00:00:08" } ShaoHe Feng (6): Add a control.vm module VM supports interfaces: update API VM supports interfaces: update model VM supports interfaces: update controller VM supports interfaces: update mockmodel VM supports interfaces: update testcase Makefile.am | 1 + configure.ac | 1 + contrib/kimchi.spec.fedora.in | 1 + contrib/kimchi.spec.suse.in | 1 + docs/API.md | 46 +++++++++++++++++++++++++++++++++++++ src/kimchi/control/Makefile.am | 2 ++ src/kimchi/control/vm/Makefile.am | 32 ++++++++++++++++++++++++++ src/kimchi/control/vm/__init__.py | 30 ++++++++++++++++++++++++ src/kimchi/control/vm/ifaces.py | 48 +++++++++++++++++++++++++++++++++++++++ src/kimchi/control/vms.py | 3 +++ src/kimchi/mockmodel.py | 33 +++++++++++++++++++++++++++ src/kimchi/model.py | 36 +++++++++++++++++++++++++++++ tests/test_model.py | 19 ++++++++++++++++ tests/test_rest.py | 31 +++++++++++++++++++++++++ 14 files changed, 284 insertions(+) create mode 100644 src/kimchi/control/vm/Makefile.am create mode 100644 src/kimchi/control/vm/__init__.py create mode 100644 src/kimchi/control/vm/ifaces.py -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> All vm devices should be in this module Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- Makefile.am | 1 + configure.ac | 1 + contrib/kimchi.spec.fedora.in | 1 + contrib/kimchi.spec.suse.in | 1 + src/kimchi/control/Makefile.am | 2 ++ src/kimchi/control/vm/Makefile.am | 32 ++++++++++++++++++++++++++++++++ src/kimchi/control/vm/__init__.py | 30 ++++++++++++++++++++++++++++++ 7 files changed, 68 insertions(+) create mode 100644 src/kimchi/control/vm/Makefile.am create mode 100644 src/kimchi/control/vm/__init__.py diff --git a/Makefile.am b/Makefile.am index 9ccac0e..68cec3b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -46,6 +46,7 @@ PEP8_WHITELIST = \ src/kimchi/cachebust.py \ src/kimchi/config.py.in \ src/kimchi/control/*.py \ + src/kimchi/control/vm/*.py \ src/kimchi/disks.py \ src/kimchi/distroloader.py \ src/kimchi/exception.py \ diff --git a/configure.ac b/configure.ac index aaed9de..27c8840 100644 --- a/configure.ac +++ b/configure.ac @@ -54,6 +54,7 @@ AC_CONFIG_FILES([ src/distros.d/Makefile src/kimchi/Makefile src/kimchi/control/Makefile + src/kimchi/control/vm/Makefile plugins/Makefile plugins/sample/Makefile plugins/sample/ui/Makefile diff --git a/contrib/kimchi.spec.fedora.in b/contrib/kimchi.spec.fedora.in index 77c64e9..982ee91 100644 --- a/contrib/kimchi.spec.fedora.in +++ b/contrib/kimchi.spec.fedora.in @@ -138,6 +138,7 @@ rm -rf $RPM_BUILD_ROOT %{_bindir}/kimchid %{python_sitelib}/kimchi/*.py* %{python_sitelib}/kimchi/control/*.py* +%{python_sitelib}/kimchi/control/vm/*.py* %{python_sitelib}/kimchi/API.json %{_datadir}/kimchi/doc/API.md %{_datadir}/kimchi/doc/README.md diff --git a/contrib/kimchi.spec.suse.in b/contrib/kimchi.spec.suse.in index 889b704..ea86aca 100644 --- a/contrib/kimchi.spec.suse.in +++ b/contrib/kimchi.spec.suse.in @@ -60,6 +60,7 @@ rm -rf $RPM_BUILD_ROOT %{_bindir}/kimchid %{python_sitelib}/kimchi/*.py* %{python_sitelib}/kimchi/control/*.py* +%{python_sitelib}/kimchi/control/vm/*.py* %{python_sitelib}/kimchi/API.json %{_datadir}/kimchi/doc/API.md %{_datadir}/kimchi/doc/README.md diff --git a/src/kimchi/control/Makefile.am b/src/kimchi/control/Makefile.am index 925c593..42f62c3 100644 --- a/src/kimchi/control/Makefile.am +++ b/src/kimchi/control/Makefile.am @@ -20,6 +20,8 @@ # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +SUBDIRS = vm + control_PYTHON = *.py controldir = $(pythondir)/kimchi/control diff --git a/src/kimchi/control/vm/Makefile.am b/src/kimchi/control/vm/Makefile.am new file mode 100644 index 0000000..17340af --- /dev/null +++ b/src/kimchi/control/vm/Makefile.am @@ -0,0 +1,32 @@ +# +# Kimchi +# +# Copyright IBM Corp, 2013 +# +# Authors: +# ShaoHe Feng <shaohef@linux.vnet.ibm.com> +# Royce Lv <lvroyce@linux.vnet.ibm.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +vm_PYTHON = \ + __init__.py + + +vmdir = $(pythondir)/kimchi/control/vm + +install-data-local: + $(MKDIR_P) $(DESTDIR)$(vmdir) diff --git a/src/kimchi/control/vm/__init__.py b/src/kimchi/control/vm/__init__.py new file mode 100644 index 0000000..53ed886 --- /dev/null +++ b/src/kimchi/control/vm/__init__.py @@ -0,0 +1,30 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2013 +# +# Authors: +# ShaoHe Feng <shaohef@linux.vnet.ibm.com> +# Royce Lv <lvroyce@linux.vnet.ibm.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import os + + +from kimchi.utils import load_url_sub_node + + +sub_nodes = load_url_sub_node(os.path.dirname(__file__), __name__) -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Now kimchi supports network get/create/delete/activate/deactivate REST API. We need to support the attach/detach/ a Network to a VM. https://github.com/kimchi-project/kimchi/wiki/customize-VM#organization-of-s... And get the info of Network attached to a VM. more libvirt network interfaces info: http://libvirt.org/formatdomain.html#elementsNICS Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- docs/API.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/API.md b/docs/API.md index f872eab..c727cf1 100644 --- a/docs/API.md +++ b/docs/API.md @@ -149,6 +149,52 @@ Represents a snapshot of the Virtual Machine's primary monitor. * null: Graphics is disabled or type not supported * listen: The network which the vnc/spice server listens on. +### Sub-Collection: Virtual Machine Network Interfaces + +**URI:** /vms/*:name*/ifaces + +Represents all network interfaces attached to a Virtual Machine. + +**Methods:** + +* **GET**: Retrieve a summarized list of all network interfaces attached to a Virtual Machine. + +* **POST**: attach a network interface to VM + * model *(optional)*: model of emulated network interface card. It can be one of these models: + ne2k_pci, i82551, i82557b, i82559er, rtl8139, e1000, pcnet and virtio. + When model is missing, libvirt will set 'rtl8139' as default value. + * network *(optional)*: the name of resource network, it is required when the + interface type is network. + * type: The type of VM network interface that libvirt supports. + Now kimchi just supports 'network' type. + +### Sub-Resource: Virtual Machine Network Interface + +**URI:** /vms/*:name*/ifaces/*:mac* + +A interface represents available network interface on VM. + +**Methods:** + +* **GET**: Retrieve the full description of the VM network interface + * 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. + * model: 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 + interface type is network. + * type: The type of VM network interface that libvirt supports. + It will be one of these types: 'network', 'bridge', 'user','ethernet', + 'direct', 'hostdev', 'mcast', 'server' and 'client'. + +* **DELETE**: detach the network interface from VM + +**Actions (POST):** + +*No actions defined* + + ### Resource: Template **URI:** /templates/*:name* -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Support vm ifaces collection GET method $ curl -u user -H 'Accept: application/json' -H 'Content-type: application/json' http://localhost:800/vms/vm-name/ifaces/ Support vm iface resource GET method $ curl -u user -H 'Accept: application/json' -H 'Content-type: application/json' http://localhost:800/vms/vm-name/ifaces/mac Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/model.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/kimchi/model.py b/src/kimchi/model.py index ea528c6..4acc5e8 100644 --- a/src/kimchi/model.py +++ b/src/kimchi/model.py @@ -46,6 +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 xml.etree import ElementTree @@ -970,6 +971,41 @@ class Model(object): iface.destroy() iface.undefine() + def _get_vmifaces(self, vm): + dom = self._get_vm(vm) + xml = dom.XMLDesc(0) + root = objectify.fromstring(xml) + + return root.devices.findall("interface") + + def _get_vmiface(self, vm, mac): + ifaces = self._get_vmifaces(vm) + + for iface in ifaces: + if iface.mac.get('address') == mac: + return iface + return None + + def vmifaces_get_list(self, vm): + return [iface.mac.get('address') for iface in self._get_vmifaces(vm)] + + def vmiface_lookup(self, vm, mac): + info = {} + + iface = self._get_vmiface(vm, mac) + if iface is None: + raise NotFoundError('iface: "%s"' % mac) + + info['type'] = iface.attrib['type'] + info['mac'] = iface.mac.get('address') + info['model'] = iface.model.get('type') + if info['type'] == 'network': + info['network'] = iface.source.get('network') + if info['type'] == 'bridge': + info['bridge'] = iface.source.get('bridge') + + return info + def add_task(self, target_uri, fn, opaque=None): id = self.next_taskid self.next_taskid = self.next_taskid + 1 -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> add VmIfaces collection and VmIface resource Update VM resource. Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/control/vm/ifaces.py | 48 +++++++++++++++++++++++++++++++++++++++++ src/kimchi/control/vms.py | 3 +++ 2 files changed, 51 insertions(+) create mode 100644 src/kimchi/control/vm/ifaces.py diff --git a/src/kimchi/control/vm/ifaces.py b/src/kimchi/control/vm/ifaces.py new file mode 100644 index 0000000..0473dcb --- /dev/null +++ b/src/kimchi/control/vm/ifaces.py @@ -0,0 +1,48 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2013 +# +# Authors: +# ShaoHe Feng <shaohef@linux.vnet.ibm.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +from kimchi.control.base import Collection, Resource +from kimchi.utils import UrlSubNode + + +@UrlSubNode("ifaces") +class VMIfaces(Collection): + def __init__(self, model, vm): + super(VMIfaces, self).__init__(model) + self.resource = VMIface + self.vm = vm + self.resource_args = [self.vm, ] + self.model_args = [self.vm, ] + + +class VMIface(Resource): + def __init__(self, model, vm, ident): + super(VMIface, self).__init__(model, ident) + self.vm = vm + self.ident = ident + self.info = {} + self.model_args = [self.vm, self.ident] + self.uri_fmt = '/vms/%s/iface/%s' + + @property + def data(self): + return self.info diff --git a/src/kimchi/control/vms.py b/src/kimchi/control/vms.py index f4646d2..f95783c 100644 --- a/src/kimchi/control/vms.py +++ b/src/kimchi/control/vms.py @@ -24,6 +24,7 @@ from kimchi.control.base import Collection, Resource from kimchi.control.utils import internal_redirect +from kimchi.control.vm import sub_nodes from kimchi.utils import UrlSubNode @@ -40,6 +41,8 @@ class VM(Resource): self.update_params = ["name"] self.screenshot = VMScreenShot(model, ident) self.uri_fmt = '/vms/%s' + for ident, node in sub_nodes.items(): + setattr(self, ident, node(model, self.ident.decode("utf-8"))) self.start = self.generate_action_handler('start') self.stop = self.generate_action_handler('stop') self.connect = self.generate_action_handler('connect') -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Support vm ifaces collection GET method $ curl -u user -H 'Accept: application/json' -H 'Content-type: application/json' http://localhost:800/vms/vm-name/ifaces/ Support vm iface resource GET method $ curl -u user -H 'Accept: application/json' -H 'Content-type: application/json' http://localhost:800/vms/vm-name/ifaces/mac Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- src/kimchi/mockmodel.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 4ef3fa6..e037366 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -488,6 +488,19 @@ class MockModel(object): def networks_get_list(self): return sorted(self._mock_networks.keys()) + def vmifaces_get_list(self, vm): + dom = self._get_vm(vm) + macs = dom.ifaces.keys() + return macs + + def vmiface_lookup(self, vm, mac): + dom = self._get_vm(vm) + try: + info = dom.ifaces[mac].info + except KeyError: + raise NotFoundError('iface: "%s"' % mac) + return info + def tasks_get_list(self): with self.objstore as session: return session.get_list('task') @@ -626,12 +639,32 @@ class MockVMTemplate(VMTemplate): return disk_paths +class MockVMIface(object): + counter = 0 + + def __init__(self, network=None): + self.__class__.counter += 1 + self.info = {'type': 'network', + 'model': 'virtio', + 'network': network if network else "net-%s" % self.counter, + 'mac': self.get_mac() + } + + @classmethod + def get_mac(cls): + mac = ":".join(["52", "54"] + ["%02x" % (cls.counter/(256**i) % 256) + for i in range(3, -1, -1)]) + return mac + + class MockVM(object): def __init__(self, uuid, name, template_info): self.uuid = uuid self.name = name self.disk_paths = [] self.networks = template_info['networks'] + ifaces = [MockVMIface(net) for net in self.networks] + self.ifaces = dict([(iface.info['mac'], iface) for iface in ifaces]) self.info = {'state': 'shutoff', 'stats': "{'cpu_utilization': 20, 'net_throughput' : 35, \ 'net_throughput_peak': 100, 'io_throughput': 45, \ -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> update test_rest and test_model Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> --- tests/test_model.py | 19 +++++++++++++++++++ tests/test_rest.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/tests/test_model.py b/tests/test_model.py index 8077f4d..c6c5d0e 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -121,6 +121,25 @@ class ModelTests(unittest.TestCase): inst.template_delete('test') @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') + def test_vm_ifaces(self): + inst = kimchi.model.Model(objstore_loc=self.tmp_store) + with RollbackContext() as rollback: + params = {'name': 'test', 'disks': []} + 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') + + 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) + + @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 a8e5842..5543cb5 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -340,6 +340,37 @@ class RestTests(unittest.TestCase): resp = self.request('/templates/test', '{}', 'DELETE') self.assertEquals(204, resp.status) + def test_vm_iface(self): + + with RollbackContext() as rollback: + # Create a template as a base for our VMs + req = json.dumps({'name': 'test', 'cdrom': '/nonexistent.iso'}) + resp = self.request('/templates', req, 'POST') + self.assertEquals(201, resp.status) + # Delete the template + rollback.prependDefer(self.request, + '/templates/test', '{}', 'DELETE') + + # Create a VM with default args + req = json.dumps({'name': 'test-vm', + 'template': '/templates/test'}) + resp = self.request('/vms', req, 'POST') + self.assertEquals(201, resp.status) + # Delete the VM + rollback.prependDefer(self.request, + '/vms/test-vm', '{}', 'DELETE') + + ifaces = json.loads(self.request('/vms/test-vm/ifaces').read()) + self.assertEquals(1, len(ifaces)) + + for iface in ifaces: + res = json.loads(self.request('/vms/test-vm/ifaces/%s' % + iface['mac']).read()) + self.assertEquals('default', res['network']) + self.assertEquals(17, len(res['mac'])) + self.assertEquals('virtio', res['model']) + + def test_vm_customise_storage(self): # Create a Template req = json.dumps({'name': 'test', 'cdrom': '/nonexistent.iso', -- 1.8.4.2
participants (1)
-
shaohef@linux.vnet.ibm.com