[Kimchi-devel] [PATCH 3/3 v3] Live migration backend: unit tests
dhbarboza82 at gmail.com
dhbarboza82 at gmail.com
Thu Oct 29 19:48:22 UTC 2015
From: Daniel Henrique Barboza <dhbarboza82 at gmail.com>
These unit tests requires an additional variable passed in the
command line, otherwise it won't be possible to test the
migration types. Example:
$ sudo KIMCHI_LIVE_MIGRATION_TEST=1.1.1.1 ./run_tests.sh test_livemigration
It also depends on the common pre-requisites of migration:
- valid remote host
- password-less root login at the remote host
The unit test will silently skip if the KIMCHI_LIVE_MIGRATION_TEST is not
defined to avoid tampering with the common usage of ./run_tests.sh script.
Signed-off-by: Daniel Henrique Barboza <dhbarboza82 at gmail.com>
---
src/wok/plugins/kimchi/tests/test_livemigration.py | 356 +++++++++++++++++++++
1 file changed, 356 insertions(+)
create mode 100644 src/wok/plugins/kimchi/tests/test_livemigration.py
diff --git a/src/wok/plugins/kimchi/tests/test_livemigration.py b/src/wok/plugins/kimchi/tests/test_livemigration.py
new file mode 100644
index 0000000..1fdcd65
--- /dev/null
+++ b/src/wok/plugins/kimchi/tests/test_livemigration.py
@@ -0,0 +1,356 @@
+#
+# 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 libvirt
+import os
+import socket
+import shutil
+import unittest
+from functools import partial
+
+
+from wok.basemodel import Singleton
+from wok.exception import OperationFailed
+from wok.rollbackcontext import RollbackContext
+
+
+from wok.plugins.kimchi.model import model
+from wok.plugins.kimchi.model.libvirtconnection import LibvirtConnection
+from wok.plugins.kimchi.model.vms import VMModel
+
+
+import iso_gen
+import utils
+from utils import get_free_port, patch_auth, request
+from utils import run_server, wait_task
+
+
+TMP_DIR = '/var/lib/kimchi/tests/'
+UBUNTU_ISO = TMP_DIR + 'ubuntu14.04.iso'
+KIMCHI_LIVE_MIGRATION_TEST = None
+
+
+def setUpModule():
+ if not os.path.exists(TMP_DIR):
+ os.makedirs(TMP_DIR)
+ iso_gen.construct_fake_iso(UBUNTU_ISO, True, '14.04', 'ubuntu')
+ # Some FeatureTests functions depend on server to validate their result.
+ # As CapabilitiesModel is a Singleton class it will get the first result
+ # from FeatureTests which may be wrong when using the Model instance
+ # directly - the case of this test_model.py
+ # So clean Singleton instances to make sure to get the right result when
+ # running the following tests.
+ Singleton._instances = {}
+
+
+def tearDownModule():
+ shutil.rmtree(TMP_DIR)
+
+
+def remoteserver_environment_defined():
+ global KIMCHI_LIVE_MIGRATION_TEST
+ KIMCHI_LIVE_MIGRATION_TEST = os.environ.get('KIMCHI_LIVE_MIGRATION_TEST')
+ return KIMCHI_LIVE_MIGRATION_TEST is not None
+
+
+def running_root_and_remoteserver_defined():
+ return utils.running_as_root() and remoteserver_environment_defined()
+
+
+def check_if_vm_migration_test_possible():
+ inst = model.Model(objstore_loc='/tmp/kimchi-store-test')
+ try:
+ inst.vm_migration_pre_check(KIMCHI_LIVE_MIGRATION_TEST, 'root')
+ except:
+ return False
+ return True
+
+
+ at unittest.skipUnless(running_root_and_remoteserver_defined(),
+ 'Must be run as root and with a remote server '
+ 'defined in the KIMCHI_LIVE_MIGRATION_TEST variable')
+class LiveMigrationTests(unittest.TestCase):
+ def setUp(self):
+ self.tmp_store = '/tmp/kimchi-store-test'
+ self.inst = model.Model(objstore_loc=self.tmp_store)
+ params = {'name': u'template_test_vm_migrate',
+ 'disks': [],
+ 'cdrom': UBUNTU_ISO,
+ 'memory': 2048,
+ 'max_memory': 4096*1024}
+ self.inst.templates_create(params)
+
+ def tearDown(self):
+ self.inst.template_delete('template_test_vm_migrate')
+
+ os.unlink(self.tmp_store)
+
+ def create_vm_test(self):
+ params = {
+ 'name': u'test_vm_migrate',
+ 'template': u'/plugins/kimchi/templates/template_test_vm_migrate'
+ }
+ task = self.inst.vms_create(params)
+ self.inst.task_wait(task['id'])
+
+ def test_vm_migrate_fails_if_remote_is_localhost(self):
+ with RollbackContext() as rollback:
+ self.create_vm_test()
+ rollback.prependDefer(utils.rollback_wrapper, self.inst.vm_delete,
+ u'test_vm_migrate')
+
+ self.assertRaises(OperationFailed,
+ self.inst.vm_migrate,
+ 'test_vm_migrate',
+ '127.0.0.1')
+
+ self.assertRaises(OperationFailed,
+ self.inst.vm_migrate,
+ 'test_vm_migrate',
+ 'localhost')
+
+ hostname = socket.gethostname()
+
+ self.assertRaises(OperationFailed,
+ self.inst.vm_migrate,
+ 'test_vm_migrate',
+ hostname)
+
+ def test_vm_migrate_fails_if_remotehost_unreachable(self):
+ with RollbackContext() as rollback:
+ self.create_vm_test()
+ rollback.prependDefer(utils.rollback_wrapper, self.inst.vm_delete,
+ u'test_vm_migrate')
+
+ self.assertRaises(OperationFailed,
+ self.inst.vm_migrate,
+ 'test_vm_migrate',
+ 'test.vm.migrate.host.unreachable')
+
+ def test_vm_migrate_fails_if_not_passwordless_login(self):
+ with RollbackContext() as rollback:
+ self.create_vm_test()
+ rollback.prependDefer(utils.rollback_wrapper, self.inst.vm_delete,
+ u'test_vm_migrate')
+
+ self.assertRaises(OperationFailed,
+ self.inst.vm_migrate,
+ 'test_vm_migrate',
+ KIMCHI_LIVE_MIGRATION_TEST,
+ user='test_vm_migrate_fake_user')
+
+ def get_remote_conn(self):
+ remote_uri = 'qemu+ssh://%s@%s/system' % \
+ ('root', KIMCHI_LIVE_MIGRATION_TEST)
+ remote_conn = libvirt.open(remote_uri)
+ return remote_conn
+
+ def get_remote_vm_list(self):
+ remote_uri = 'qemu+ssh://%s@%s/system' % \
+ ('root', KIMCHI_LIVE_MIGRATION_TEST)
+ remote_conn = libvirt.open(remote_uri)
+ return [vm.name() for vm in remote_conn.listAllDomains()]
+
+ @unittest.skipUnless(check_if_vm_migration_test_possible(),
+ 'not possible to test a live migration')
+ def test_vm_livemigrate_persistent(self):
+ inst = model.Model(libvirt_uri='qemu:///system',
+ objstore_loc=self.tmp_store)
+
+ with RollbackContext() as rollback:
+ self.create_vm_test()
+ rollback.prependDefer(utils.rollback_wrapper, self.inst.vm_delete,
+ u'test_vm_migrate')
+
+ # removing cdrom because it is not shared storage and will make
+ # the migration fail
+ dev_list = self.inst.vmstorages_get_list('test_vm_migrate')
+ self.inst.vmstorage_delete('test_vm_migrate', dev_list[0])
+
+ try:
+ self.inst.vm_start('test_vm_migrate')
+ except Exception, e:
+ self.fail('Failed to start the vm, reason: %s' % e.message)
+ try:
+ task = inst.vm_migrate('test_vm_migrate',
+ KIMCHI_LIVE_MIGRATION_TEST)
+ inst.task_wait(task['id'])
+ self.assertIn('test_vm_migrate', self.get_remote_vm_list())
+
+ remote_conn = self.get_remote_conn()
+ rollback.prependDefer(remote_conn.close)
+
+ remote_vm = remote_conn.lookupByName('test_vm_migrate')
+ self.assertTrue(remote_vm.isPersistent())
+
+ remote_vm.destroy()
+ remote_vm.undefine()
+ except Exception, e:
+ self.fail('Migration test failed: %s' % e.message)
+
+ @unittest.skipUnless(check_if_vm_migration_test_possible(),
+ 'not possible to test a live migration')
+ def test_vm_livemigrate_transient(self):
+ inst = model.Model(libvirt_uri='qemu:///system',
+ objstore_loc=self.tmp_store)
+
+ self.create_vm_test()
+
+ with RollbackContext() as rollback:
+ try:
+ # removing cdrom because it is not shared storage and will make
+ # the migration fail
+ dev_list = self.inst.vmstorages_get_list('test_vm_migrate')
+ self.inst.vmstorage_delete('test_vm_migrate', dev_list[0])
+
+ self.inst.vm_start('test_vm_migrate')
+
+ # to make the VM transient, undefine it while it's running
+ vm = VMModel.get_vm(
+ 'test_vm_migrate',
+ LibvirtConnection('qemu:///system')
+ )
+ vm.undefine()
+
+ task = inst.vm_migrate('test_vm_migrate',
+ KIMCHI_LIVE_MIGRATION_TEST)
+ inst.task_wait(task['id'])
+ self.assertIn('test_vm_migrate', self.get_remote_vm_list())
+
+ remote_conn = self.get_remote_conn()
+ rollback.prependDefer(remote_conn.close)
+
+ remote_vm = remote_conn.lookupByName('test_vm_migrate')
+ self.assertFalse(remote_vm.isPersistent())
+
+ remote_vm.destroy()
+ except Exception, e:
+ # Clean up here instead of rollback because if the
+ # VM was turned transient and shut down it might
+ # not exist already - rollback in this case will cause
+ # a QEMU error
+ vm = VMModel.get_vm(
+ 'test_vm_migrate',
+ LibvirtConnection('qemu:///system')
+ )
+ if vm.isPersistent():
+ vm.undefine()
+ vm.shutdown()
+ self.fail('Migration test failed: %s' % e.message)
+
+ @unittest.skipUnless(check_if_vm_migration_test_possible(),
+ 'not possible to test shutdown migration')
+ def test_vm_coldmigrate(self):
+ inst = model.Model(libvirt_uri='qemu:///system',
+ objstore_loc=self.tmp_store)
+
+ with RollbackContext() as rollback:
+ self.create_vm_test()
+ rollback.prependDefer(utils.rollback_wrapper, self.inst.vm_delete,
+ u'test_vm_migrate')
+
+ # removing cdrom because it is not shared storage and will make
+ # the migration fail
+ dev_list = self.inst.vmstorages_get_list('test_vm_migrate')
+ self.inst.vmstorage_delete('test_vm_migrate', dev_list[0])
+
+ try:
+ task = inst.vm_migrate('test_vm_migrate',
+ KIMCHI_LIVE_MIGRATION_TEST)
+ inst.task_wait(task['id'])
+ self.assertIn('test_vm_migrate', self.get_remote_vm_list())
+
+ remote_conn = self.get_remote_conn()
+ rollback.prependDefer(remote_conn.close)
+
+ remote_vm = remote_conn.lookupByName('test_vm_migrate')
+ self.assertTrue(remote_vm.isPersistent())
+
+ state = remote_vm.info()[0]
+ self.assertEqual(state, libvirt.VIR_DOMAIN_SHUTOFF)
+
+ remote_vm.undefine()
+ except Exception, e:
+ self.fail('Migration test failed: %s' % e.message)
+
+ def _task_lookup(self, taskid):
+ return json.loads(
+ self.request('/plugins/kimchi/tasks/%s' % taskid).read()
+ )
+
+ @unittest.skipUnless(check_if_vm_migration_test_possible(),
+ 'not possible to test a live migration')
+ def test_vm_livemigrate_persistent_API(self):
+ patch_auth()
+
+ inst = model.Model(libvirt_uri='qemu:///system',
+ objstore_loc=self.tmp_store)
+
+ host = '127.0.0.1'
+ port = get_free_port('http')
+ ssl_port = get_free_port('https')
+ cherrypy_port = get_free_port('cherrypy_port')
+
+ with RollbackContext() as rollback:
+ test_server = run_server(host, port, ssl_port, test_mode=True,
+ cherrypy_port=cherrypy_port, model=inst)
+ rollback.prependDefer(test_server.stop)
+
+ self.request = partial(request, host, ssl_port)
+
+ self.create_vm_test()
+ rollback.prependDefer(utils.rollback_wrapper, self.inst.vm_delete,
+ u'test_vm_migrate')
+
+ # removing cdrom because it is not shared storage and will make
+ # the migration fail
+ dev_list = self.inst.vmstorages_get_list('test_vm_migrate')
+ self.inst.vmstorage_delete('test_vm_migrate', dev_list[0])
+
+ try:
+ self.inst.vm_start('test_vm_migrate')
+ except Exception, e:
+ self.fail('Failed to start the vm, reason: %s' % e.message)
+
+ migrate_url = "/plugins/kimchi/vms/%s/migrate" % 'test_vm_migrate'
+
+ req = json.dumps({'remote_host': KIMCHI_LIVE_MIGRATION_TEST,
+ 'user': 'root'})
+ resp = self.request(migrate_url, req, 'POST')
+ self.assertEquals(202, resp.status)
+ task = json.loads(resp.read())
+ wait_task(self._task_lookup, task['id'])
+ task = json.loads(
+ self.request(
+ '/plugins/kimchi/tasks/%s' % task['id'],
+ '{}'
+ ).read()
+ )
+ self.assertEquals('finished', task['status'])
+
+ try:
+ remote_conn = self.get_remote_conn()
+ rollback.prependDefer(remote_conn.close)
+ remote_vm = remote_conn.lookupByName('test_vm_migrate')
+ self.assertTrue(remote_vm.isPersistent())
+ remote_vm.destroy()
+ remote_vm.undefine()
+ except Exception, e:
+ self.fail('Migration test failed: %s' % e.message)
--
2.4.3
More information about the Kimchi-devel
mailing list