[PATCH 0/5 V2] Bug fixes and network tests

V1 -> V2: - Remove bridged network test as we can not assume any interface is good to create one Aline Manera (5): Add message to KCHNET0010E code Bug fix: Allow deleting VLAN tagging bridged network Network API: Update docs/API.md Move rollback_wrapper function to a common place Reorganize the network tests docs/API.md | 11 ++-- src/kimchi/i18n.py | 1 + src/kimchi/model/networks.py | 6 +- tests/test_model.py | 86 ++------------------------- tests/test_network.py | 137 +++++++++++++++++++++++++++++++++++++++++++ tests/test_rest.py | 64 -------------------- tests/utils.py | 15 ++++- 7 files changed, 165 insertions(+), 155 deletions(-) create mode 100644 tests/test_network.py -- 2.1.0

Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/i18n.py | 1 + src/kimchi/model/networks.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index e3a051c..e8693da 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -223,6 +223,7 @@ messages = { "KCHNET0007E": _("Interface should be bare NIC, bonding or bridge device."), "KCHNET0008E": _("Unable to create network %(name)s. Details: %(err)s"), "KCHNET0009E": _("Unable to find a free IP address for network '%(name)s'"), + "KCHNET0010E": _("The interface %(iface)s already exist."), "KCHNET0011E": _("Network name must be a string"), "KCHNET0012E": _("Supported network types are isolated, NAT and bridge"), "KCHNET0013E": _("Network subnet must be a string with IP address and prefix or netmask"), diff --git a/src/kimchi/model/networks.py b/src/kimchi/model/networks.py index 1e94fd2..44f1297 100644 --- a/src/kimchi/model/networks.py +++ b/src/kimchi/model/networks.py @@ -211,9 +211,7 @@ class NetworksModel(object): conn = self.conn.get() if br_name in [net.bridgeName() for net in conn.listAllNetworks()]: - error_msg = 'The interface %s already exist' % br_name - raise InvalidOperation("KCHNET0010E", {'iface': br_name, - 'err': error_msg}) + raise InvalidOperation("KCHNET0010E", {'iface': br_name}) with RollbackContext() as rollback: try: -- 2.1.0

When trying to delete a VLAN tagging bridged network I got the following error because of the VLAN bridge was not active. [26/Dec/2014:16:28:42] HTTP Traceback (most recent call last): File "/usr/lib/python2.7/dist-packages/cherrypy/_cprequest.py", line 670, in respond response.body = self.handler() File "/usr/lib/python2.7/dist-packages/cherrypy/lib/encoding.py", line 217, in __call__ self.body = self.oldhandler(*args, **kwargs) File "/usr/lib/python2.7/dist-packages/cherrypy/_cpdispatch.py", line 61, in __call__ return self.callable(*self.args, **self.kwargs) File "/home/alinefm/kimchi/src/kimchi/control/base.py", line 158, in index 'PUT': self.update}[method]() File "/home/alinefm/kimchi/src/kimchi/control/base.py", line 135, in delete fn(*self.model_args) File "/home/alinefm/kimchi/src/kimchi/model/networks.py", line 327, in delete self._remove_vlan_tagged_bridge(network) File "/home/alinefm/kimchi/src/kimchi/model/networks.py", line 372, in _remove_vlan_tagged_bridge iface.destroy(0) File "/home/alinefm/kimchi/src/kimchi/model/libvirtconnection.py", line 66, in wrapper ret = f(*args, **kwargs) File "/usr/lib/python2.7/dist-packages/libvirt.py", line 2894, in destroy if ret == -1: raise libvirtError ('virInterfaceDestroy() failed', net=self) libvirtError: Requested operation is not valid: interface is not running Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/model/networks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kimchi/model/networks.py b/src/kimchi/model/networks.py index 44f1297..dad31c7 100644 --- a/src/kimchi/model/networks.py +++ b/src/kimchi/model/networks.py @@ -369,5 +369,5 @@ class NetworkModel(object): if bridge.startswith(KIMCHI_BRIDGE_PREFIX): conn = self.conn.get() iface = conn.interfaceLookupByName(bridge) - iface.destroy(0) + iface.isActive() and iface.destroy(0) iface.undefine() -- 2.1.0

Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- docs/API.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/API.md b/docs/API.md index 210a036..94a1001 100644 --- a/docs/API.md +++ b/docs/API.md @@ -565,19 +565,18 @@ A interface represents available interface on host. * **GET**: Retrieve a summarized list of all defined Networks * **POST**: Create a new Network * name: The name of the Network - * subnet *(optional)*: Network segment in slash-separated format with ip address and - prefix or netmask. It is always ignored for bridge network. * connection: Specifies how this network should be connected to the other networks visible to this host. * isolated: Create a private, isolated virtual network. * nat: Outgoing traffic will be routed through the host. * bridge: All traffic on this network will be bridged through the indicated interface. - * interface: The name of a network interface on the host. + * subnet *(optional)*: Network segment in slash-separated format with ip address and + prefix or netmask used to create nat network. + * interface *(optional)*: The name of a network interface on the host. For bridge network, the interface can be a bridge or nic/bonding - device. For isolated or NAT network, the interface is ignored. - * in_use: True if network is in use by a template or virtual machine; - False, otherwise. + device. + * vlan_id *(optional)*: VLAN tagging ID for the bridge network. ### Resource: Network -- 2.1.0

The rollback_wrapper function is used to avoid a NotFoundError when the tests finishes correctly. Move it to a common place to allow reuse. Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- tests/test_model.py | 23 +++++++---------------- tests/utils.py | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/tests/test_model.py b/tests/test_model.py index 5984906..a8d35dc 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -964,10 +964,10 @@ class ModelTests(unittest.TestCase): params_1 = {'name': 'kimchi-vm1', 'template': '/templates/test'} params_2 = {'name': 'kimchi-vm2', 'template': '/templates/test'} inst.vms_create(params_1) - rollback.prependDefer(self._rollback_wrapper, inst.vm_delete, + rollback.prependDefer(utils.rollback_wrapper, inst.vm_delete, 'kimchi-vm1') inst.vms_create(params_2) - rollback.prependDefer(self._rollback_wrapper, inst.vm_delete, + rollback.prependDefer(utils.rollback_wrapper, inst.vm_delete, 'kimchi-vm2') vms = inst.vms_get_list() @@ -978,7 +978,7 @@ class ModelTests(unittest.TestCase): {"graphics": {"passwd": "123456"}}) inst.vm_start('kimchi-vm1') - rollback.prependDefer(self._rollback_wrapper, inst.vm_poweroff, + rollback.prependDefer(utils.rollback_wrapper, inst.vm_poweroff, 'kimchi-vm1') vm_info = inst.vm_lookup(u'kimchi-vm1') @@ -1019,7 +1019,7 @@ class ModelTests(unittest.TestCase): params = {'name': u'пeω-∨м', 'cpus': 4, 'memory': 2048} inst.vm_update('kimchi-vm1', params) - rollback.prependDefer(self._rollback_wrapper, inst.vm_delete, + rollback.prependDefer(utils.rollback_wrapper, inst.vm_delete, u'пeω-∨м') self.assertEquals(info['uuid'], inst.vm_lookup(u'пeω-∨м')['uuid']) info = inst.vm_lookup(u'пeω-∨м') @@ -1296,16 +1296,7 @@ class ModelTests(unittest.TestCase): inst.task_wait(taskid, timeout=10) self.assertEquals('finished', inst.task_lookup(taskid)['status']) - # This wrapper function is needed due to the new backend messaging in - # vm model. vm_poweroff and vm_delete raise exception if vm is not found. - # These functions are called after vm has been deleted if test finishes - # correctly, then NofFoundError exception is raised and rollback breaks - def _rollback_wrapper(self, func, vmname): - try: - func(vmname) - except NotFoundError: - # VM has been deleted already - return + @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_delete_running_vm(self): @@ -1318,11 +1309,11 @@ class ModelTests(unittest.TestCase): params = {'name': u'kīмсhī-∨м', 'template': u'/templates/test'} inst.vms_create(params) - rollback.prependDefer(self._rollback_wrapper, inst.vm_delete, + rollback.prependDefer(utils.rollback_wrapper, inst.vm_delete, u'kīмсhī-∨м') inst.vm_start(u'kīмсhī-∨м') - rollback.prependDefer(self._rollback_wrapper, inst.vm_poweroff, + rollback.prependDefer(utils.rollback_wrapper, inst.vm_poweroff, u'kīмсhī-∨м') inst.vm_delete(u'kīмсhī-∨м') diff --git a/tests/utils.py b/tests/utils.py index c692041..3677851 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -38,7 +38,7 @@ import kimchi.mockmodel import kimchi.server from kimchi.config import config, paths from kimchi.auth import User, USER_NAME, USER_GROUPS, USER_ROLES, tabs -from kimchi.exception import OperationFailed +from kimchi.exception import NotFoundError, OperationFailed from kimchi.utils import kimchi_log _ports = {} @@ -226,3 +226,16 @@ def wait_task(task_lookup, taskid, timeout=10): return kimchi_log.error("Timeout while process long-run task, " "try to increase timeout value.") + + +# The action functions in model backend raise NotFoundError exception if the +# element is not found. But in some tests, these functions are called after +# the element has been deleted if test finishes correctly, then NofFoundError +# exception is raised and rollback breaks. To avoid it, this wrapper ignores +# the NotFoundError. +def rollback_wrapper(func, resource): + try: + func(resource) + except NotFoundError: + # VM has been deleted already + return -- 2.1.0

It creates a new file (test_network.py) with the network test cases. As MockModel does not override any Model function related to network, only Model tests were added using the REST API. Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- tests/test_model.py | 65 ------------------------ tests/test_network.py | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/test_rest.py | 64 ----------------------- 3 files changed, 137 insertions(+), 129 deletions(-) create mode 100644 tests/test_network.py diff --git a/tests/test_model.py b/tests/test_model.py index a8d35dc..7e28aed 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -1064,69 +1064,6 @@ class ModelTests(unittest.TestCase): self.assertEquals([], inst.vm_lookup(u'пeω-∨м')['users']) self.assertEquals([], inst.vm_lookup(u'пeω-∨м')['groups']) - @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') - def test_network(self): - inst = model.Model(None, self.tmp_store) - - with RollbackContext() as rollback: - - # Regression test: - # Kimchi fails creating new network #318 - name = 'test-network-no-subnet' - - networks = inst.networks_get_list() - num = len(networks) + 1 - args = {'name': name, - 'connection': 'nat', - 'subnet': ''} - inst.networks_create(args) - rollback.prependDefer(inst.network_delete, name) - - networks = inst.networks_get_list() - self.assertEquals(num, len(networks)) - networkinfo = inst.network_lookup(name) - self.assertNotEqual(args['subnet'], networkinfo['subnet']) - self.assertEqual(args['connection'], networkinfo['connection']) - self.assertEquals('inactive', networkinfo['state']) - self.assertEquals([], networkinfo['vms']) - self.assertTrue(networkinfo['autostart']) - self.assertTrue(networkinfo['persistent']) - - inst.network_activate(name) - rollback.prependDefer(inst.network_deactivate, name) - - networkinfo = inst.network_lookup(name) - self.assertEquals('active', networkinfo['state']) - - # test network creation with subnet passed - name = 'test-network-subnet' - - networks = inst.networks_get_list() - num = len(networks) + 1 - args = {'name': name, - 'connection': 'nat', - 'subnet': '127.0.100.0/24'} - inst.networks_create(args) - rollback.prependDefer(inst.network_delete, name) - - networks = inst.networks_get_list() - self.assertEquals(num, len(networks)) - networkinfo = inst.network_lookup(name) - self.assertEqual(args['subnet'], networkinfo['subnet']) - self.assertEqual(args['connection'], networkinfo['connection']) - self.assertEquals('inactive', networkinfo['state']) - self.assertEquals([], networkinfo['vms']) - self.assertTrue(networkinfo['autostart']) - - inst.network_activate(name) - rollback.prependDefer(inst.network_deactivate, name) - - networkinfo = inst.network_lookup(name) - self.assertEquals('active', networkinfo['state']) - - networks = inst.networks_get_list() - self.assertEquals((num - 2), len(networks)) - def test_multithreaded_connection(self): def worker(): for i in xrange(100): @@ -1296,8 +1233,6 @@ class ModelTests(unittest.TestCase): inst.task_wait(taskid, timeout=10) self.assertEquals('finished', inst.task_lookup(taskid)['status']) - - @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_delete_running_vm(self): inst = model.Model(objstore_loc=self.tmp_store) diff --git a/tests/test_network.py b/tests/test_network.py new file mode 100644 index 0000000..f38975a --- /dev/null +++ b/tests/test_network.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +# +# Project Kimchi +# +# Copyright IBM, Corp. 2014 +# +# 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 json +import os +import unittest + +from functools import partial + +from kimchi.model.model import Model +from kimchi.rollbackcontext import RollbackContext +from utils import get_free_port, patch_auth, request, rollback_wrapper +from utils import run_server + + +model = None +test_server = None +host = None +port = None +ssl_port = None +cherrypy_port = None + + +def setUpModule(): + global test_server, model, host, port, ssl_port, cherrypy_port + + patch_auth() + model = Model(None, '/tmp/obj-store-test') + host = '127.0.0.1' + port = get_free_port('http') + ssl_port = get_free_port('https') + cherrypy_port = get_free_port('cherrypy_port') + test_server = run_server(host, port, ssl_port, test_mode=True, + cherrypy_port=cherrypy_port, model=model) + + +def tearDownModule(): + test_server.stop() + os.unlink('/tmp/obj-store-test') + + +class NetworkTests(unittest.TestCase): + def setUp(self): + self.request = partial(request, host, ssl_port) + + def test_get_networks(self): + networks = json.loads(self.request('/networks').read()) + self.assertIn('default', [net['name'] for net in networks]) + + with RollbackContext() as rollback: + # Now add a couple of Networks to the mock model + for i in xrange(5): + name = 'network-%i' % i + req = json.dumps({'name': name, + 'connection': 'nat', + 'subnet': '127.0.10%i.0/24' % i}) + + resp = self.request('/networks', req, 'POST') + rollback.prependDefer(model.network_delete, name) + self.assertEquals(201, resp.status) + network = json.loads(resp.read()) + self.assertEquals([], network["vms"]) + + nets = json.loads(self.request('/networks').read()) + self.assertEquals(len(networks) + 5, len(nets)) + + network = json.loads(self.request('/networks/network-1').read()) + keys = [u'name', u'connection', u'interface', u'subnet', u'dhcp', + u'vms', u'in_use', u'autostart', u'state', u'persistent'] + self.assertEquals(sorted(keys), sorted(network.keys())) + + def test_network_lifecycle(self): + def _do_test(params): + with RollbackContext() as rollback: + net_name = params['name'] + uri = '/networks/%s' % net_name.encode('utf-8') + + # Create a network + req = json.dumps(params) + resp = self.request('/networks', req, 'POST') + rollback.prependDefer(rollback_wrapper, model.network_delete, + net_name) + self.assertEquals(201, resp.status) + + # Verify the network + resp = self.request(uri) + network = json.loads(resp.read()) + self.assertEquals('inactive', network['state']) + self.assertTrue(network['persistent']) + + # activate the network + resp = self.request(uri + '/activate', '{}', 'POST') + rollback.prependDefer(rollback_wrapper, + model.network_deactivate, net_name) + self.assertEquals(200, resp.status) + + resp = self.request(uri) + network = json.loads(resp.read()) + self.assertEquals('active', network['state']) + + # Deactivate the network + resp = self.request(uri + '/deactivate', '{}', 'POST') + self.assertEquals(200, resp.status) + + resp = self.request(uri) + network = json.loads(resp.read()) + self.assertEquals('inactive', network['state']) + + # Delete the network + resp = self.request(uri, '{}', 'DELETE') + self.assertEquals(204, resp.status) + + # Verify all the supported network type + networks = [{'name': u'kīмсhī-пet', 'connection': 'isolated'}, + {'name': u'nat-network', 'connection': 'nat'}, + {'name': u'subnet-network', 'connection': 'nat', + 'subnet': '127.0.100.0/24'}] + + for net in networks: + _do_test(net) diff --git a/tests/test_rest.py b/tests/test_rest.py index fa15ef6..8eac0f9 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -1606,70 +1606,6 @@ class RestTests(unittest.TestCase): self.assertEquals(interface['netmask'], "255.255.255.0") self.assertEquals(interface['status'], "active") - def test_get_networks(self): - networks = json.loads(request(host, ssl_port, '/networks').read()) - self.assertEquals(1, len(networks)) - self.assertEquals('default', networks[0]['name']) - self.assertEquals([], networks[0]['vms']) - - # Now add a couple of Networks to the mock model - for i in xrange(5): - name = 'network-%i' % i - req = json.dumps({'name': name, - 'connection': 'nat', - 'subnet': '127.0.10%i.0/24' % i}) - - resp = request(host, ssl_port, '/networks', req, 'POST') - self.assertEquals(201, resp.status) - network = json.loads(resp.read()) - self.assertEquals([], network["vms"]) - - networks = json.loads(request(host, ssl_port, '/networks').read()) - self.assertEquals(6, len(networks)) - - network = json.loads(request(host, ssl_port, - '/networks/network-1').read()) - self.assertEquals('network-1', network['name']) - self.assertEquals('inactive', network['state']) - # Delete the network - for i in xrange(5): - resp = request(host, ssl_port, '/networks/network-%i' % i, - '{}', 'DELETE') - self.assertEquals(204, resp.status) - - def test_network_action(self): - # Create a network - req = json.dumps({'name': 'test-network', - 'connection': 'nat', - 'net': '127.0.1.0/24'}) - resp = request(host, ssl_port, '/networks', req, 'POST') - self.assertEquals(201, resp.status) - - # Verify the network - network = json.loads(request(host, ssl_port, - '/networks/test-network').read()) - self.assertEquals('inactive', network['state']) - self.assertTrue(network['persistent']) - - # activate the network - resp = request(host, ssl_port, - '/networks/test-network/activate', '{}', 'POST') - network = json.loads(request(host, ssl_port, - '/networks/test-network').read()) - self.assertEquals('active', network['state']) - - # Deactivate the network - resp = request(host, ssl_port, - '/networks/test-network/deactivate', '{}', 'POST') - network = json.loads(request(host, ssl_port, - '/networks/test-network').read()) - self.assertEquals('inactive', network['state']) - - # Delete the network - resp = request(host, ssl_port, '/networks/test-network', '{}', - 'DELETE') - self.assertEquals(204, resp.status) - def _task_lookup(self, taskid): return json.loads(self.request('/tasks/%s' % taskid).read()) -- 2.1.0

Please, ignore it. On 06/01/2015 14:07, Aline Manera wrote:
V1 -> V2: - Remove bridged network test as we can not assume any interface is good to create one
Aline Manera (5): Add message to KCHNET0010E code Bug fix: Allow deleting VLAN tagging bridged network Network API: Update docs/API.md Move rollback_wrapper function to a common place Reorganize the network tests
docs/API.md | 11 ++-- src/kimchi/i18n.py | 1 + src/kimchi/model/networks.py | 6 +- tests/test_model.py | 86 ++------------------------- tests/test_network.py | 137 +++++++++++++++++++++++++++++++++++++++++++ tests/test_rest.py | 64 -------------------- tests/utils.py | 15 ++++- 7 files changed, 165 insertions(+), 155 deletions(-) create mode 100644 tests/test_network.py
participants (1)
-
Aline Manera