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

V1 -> V2: - Add a MockModel test case to test VLAN tagging bridge 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 | 3 +- src/kimchi/mockmodel.py | 22 +------ src/kimchi/model/networks.py | 8 +-- tests/test_mock_network.py | 71 +++++++++++++++++++++ tests/test_model.py | 88 +++----------------------- tests/test_network.py | 144 +++++++++++++++++++++++++++++++++++++++++++ tests/test_rest.py | 82 +----------------------- tests/utils.py | 17 ++++- 9 files changed, 251 insertions(+), 195 deletions(-) create mode 100644 tests/test_mock_network.py create mode 100644 tests/test_network.py -- 2.1.0

Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/i18n.py | 3 ++- src/kimchi/model/networks.py | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/kimchi/i18n.py b/src/kimchi/i18n.py index e3a051c..adbd071 100644 --- a/src/kimchi/i18n.py +++ b/src/kimchi/i18n.py @@ -1,7 +1,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2014 +# Copyright IBM, Corp. 2014-2015 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -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..7a52a19 100644 --- a/src/kimchi/model/networks.py +++ b/src/kimchi/model/networks.py @@ -1,7 +1,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2014 +# Copyright IBM, Corp. 2014-2015 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -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 7a52a19..6785e44 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 | 25 ++++++++----------------- tests/utils.py | 17 +++++++++++++++-- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/tests/test_model.py b/tests/test_model.py index 5984906..2d818ae 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -2,7 +2,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2013-2014 +# Copyright IBM, Corp. 2013-2015 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -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..72078cc 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,7 +1,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2013-2014 +# Copyright IBM, Corp. 2013-2015 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -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 2 new files: test_network.py and test_mock_network.py with the network test cases. The only test case on test_mock_network.py is related to VLAN tagging bridge as it is hard to test on Model environment because it needs an active interface to create the VLAN tagging bridge. MockModel was also changed to return the system interfaces as the libvirt Test driver needs a valid one to create the VLAN tagging bridge but it does not need to be active. Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/mockmodel.py | 22 +------ tests/test_mock_network.py | 71 ++++++++++++++++++++++ tests/test_model.py | 65 -------------------- tests/test_network.py | 144 +++++++++++++++++++++++++++++++++++++++++++++ tests/test_rest.py | 82 +------------------------- 5 files changed, 219 insertions(+), 165 deletions(-) create mode 100644 tests/test_mock_network.py create mode 100644 tests/test_network.py diff --git a/src/kimchi/mockmodel.py b/src/kimchi/mockmodel.py index 34c7b22..f8e317a 100644 --- a/src/kimchi/mockmodel.py +++ b/src/kimchi/mockmodel.py @@ -1,7 +1,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2013-2014 +# Copyright IBM, Corp. 2013-2015 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -64,7 +64,6 @@ class MockModel(Model): osinfo.defaults = dict(defaults) self._mock_devices = MockDevices() - self._mock_interfaces = MockInterfaces() self._mock_storagevolumes = MockStorageVolumes() self._mock_swupdate = MockSoftwareUpdate() self._mock_repositories = MockRepositories() @@ -284,12 +283,6 @@ class MockModel(Model): return self._model_storagevolume_lookup(pool, vol) - def _mock_interfaces_get_list(self): - return self._mock_interfaces.ifaces.keys() - - def _mock_interface_lookup(self, name): - return self._mock_interfaces.ifaces[name] - def _mock_devices_get_list(self, _cap=None, _passthrough=None, _passthrough_affected_by=None): if _cap is None: @@ -429,19 +422,6 @@ class MockStorageVolumes(object): 'ref_cnt': 0}} -class MockInterfaces(object): - def __init__(self): - self.ifaces = {} - params = {"eth1": "nic", "bond0": "bonding", - "eth1.10": "vlan", "bridge0": "bridge"} - for i, name in enumerate(params.iterkeys()): - info = {'name': name, 'type': params[name], - 'ipaddr': '192.168.%s.101' % (i + 1), - 'netmask': '255.255.255.0', 'status': 'active'} - self.ifaces[name] = info - self.ifaces['eth1']['ipaddr'] = '192.168.0.101' - - class MockDevices(object): def __init__(self): self.devices = { diff --git a/tests/test_mock_network.py b/tests/test_mock_network.py new file mode 100644 index 0000000..e018d11 --- /dev/null +++ b/tests/test_mock_network.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# +# Project Kimchi +# +# Copyright IBM, Corp. 2015 +# +# 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.mockmodel import MockModel +from test_network import _do_network_test +from utils import get_free_port, patch_auth, request, 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 = MockModel('/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 MockNetworkTests(unittest.TestCase): + def setUp(self): + self.request = partial(request, host, ssl_port) + model.reset() + + def test_vlan_tag_bridge(self): + # Verify the current system has at least one interface to create a + # bridged network + interfaces = json.loads(self.request('/interfaces?type=nic').read()) + if len(interfaces) > 0: + iface = interfaces[0]['name'] + _do_network_test(self, model, {'name': u'bridge-network', + 'connection': 'bridge', + 'interface': iface, 'vlan_id': 987}) diff --git a/tests/test_model.py b/tests/test_model.py index 2d818ae..8181f7c 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..5dbe54d --- /dev/null +++ b/tests/test_network.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +# +# Project Kimchi +# +# Copyright IBM, Corp. 2015 +# +# 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') + + +def _do_network_test(self, model, 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) + + +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): + # 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'}] + + # Verify the current system has at least one interface to create a + # bridged network + interfaces = json.loads(self.request('/interfaces?type=nic').read()) + if len(interfaces) > 0: + iface = interfaces[0]['name'] + networks.append({'name': u'bridge-network', 'connection': 'bridge', + 'interface': iface}) + + for net in networks: + _do_network_test(self, model, net) diff --git a/tests/test_rest.py b/tests/test_rest.py index fa15ef6..0c36efb 100644 --- a/tests/test_rest.py +++ b/tests/test_rest.py @@ -2,7 +2,7 @@ # # Project Kimchi # -# Copyright IBM, Corp. 2013-2014 +# Copyright IBM, Corp. 2013-2015 # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -1590,85 +1590,9 @@ class RestTests(unittest.TestCase): resp = self.request('/interfaces').read() self.assertIn('name', resp) interfaces = json.loads(resp) + keys = ['name', 'type', 'ipaddr', 'netmask', 'status'] for interface in interfaces: - self.assertIn('name', interface) - self.assertIn('type', interface) - self.assertIn('ipaddr', interface) - self.assertIn('netmask', interface) - self.assertIn('status', interface) - - ident = "eth1" - resp = self.request('/interfaces/%s' % ident).read() - interface = json.loads(resp) - self.assertEquals(interface['name'], ident) - self.assertEquals(interface['type'], "nic") - self.assertEquals(interface['ipaddr'], "192.168.0.101") - 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) + self.assertEquals(sorted(keys), sorted(interface.keys())) def _task_lookup(self, taskid): return json.loads(self.request('/tasks/%s' % taskid).read()) -- 2.1.0

Reviewed-by: Daniel Barboza <dhbarboza82@gmail.com> Tested-by: Daniel Barboza <dhbarboza82@gmail.com> Using branch: https://github.com/alinefm/kimchi/tree/net-tests On 01/07/2015 05:04 PM, Aline Manera wrote:
V1 -> V2: - Add a MockModel test case to test VLAN tagging bridge
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 | 3 +- src/kimchi/mockmodel.py | 22 +------ src/kimchi/model/networks.py | 8 +-- tests/test_mock_network.py | 71 +++++++++++++++++++++ tests/test_model.py | 88 +++----------------------- tests/test_network.py | 144 +++++++++++++++++++++++++++++++++++++++++++ tests/test_rest.py | 82 +----------------------- tests/utils.py | 17 ++++- 9 files changed, 251 insertions(+), 195 deletions(-) create mode 100644 tests/test_mock_network.py create mode 100644 tests/test_network.py

Reviewed-by: Royce Lv<lvroyce@linux.vnet.ibm.com> On 01/07/2015 02:04 PM, Aline Manera wrote:
V1 -> V2: - Add a MockModel test case to test VLAN tagging bridge
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 | 3 +- src/kimchi/mockmodel.py | 22 +------ src/kimchi/model/networks.py | 8 +-- tests/test_mock_network.py | 71 +++++++++++++++++++++ tests/test_model.py | 88 +++----------------------- tests/test_network.py | 144 +++++++++++++++++++++++++++++++++++++++++++ tests/test_rest.py | 82 +----------------------- tests/utils.py | 17 ++++- 9 files changed, 251 insertions(+), 195 deletions(-) create mode 100644 tests/test_mock_network.py create mode 100644 tests/test_network.py
participants (3)
-
Aline Manera
-
Daniel Henrique Barboza
-
Royce Lv