[PATCH V2] [Kimchi 0/3] Kimchi testcases fixes

From: Paulo Vital <pvital@linux.vnet.ibm.com> This patchset fixes some Kimchi testcases were failling or finishing with errors in Fedora 23 host. There're 3 tests failling at this moment: 2 tests in test_edit_vm() and test_vlan_tag_bridge(). Paulo Vital (3): Fix test_image_based_template testcase. Fix test_vm_lifecycle testcase. fix 2 test_vm_lifecycle testcase tests/test_model.py | 66 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 22 deletions(-) -- 2.5.0

From: Paulo Vital <pvital@linux.vnet.ibm.com> Libvirt of the Fedora 23 is not able to delete the storage volume in the testcase test_image_based_template when testing ModelTests. This happens because Libvirt is not able to unlink/rmdir returning an error (-1) and setting errno to ENOENT, causing the error: "cannot unlink file '/var/lib/libvirt/images/base-vol.img': Success" This patch fixes the testcase by manually remove the volume file from the storage pool directory if the wok.plugins.kimchi.model.storagevolume_delete() was not capable to delete the volume. Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- tests/test_model.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/test_model.py b/tests/test_model.py index 762f0f8..4aa19bd 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -96,6 +96,16 @@ class ModelTests(unittest.TestCase): os.unlink(self.tmp_store) + def temporary_storagevolume_delete(inst, pool, vol): + # Quick fix to passby the Libvirt error: + # "error: cannot unlink file '%(vol)': Success" + # in Fedora <= 23. The Libvirt fix will be present only on Fedora 24 + try: + inst.storagevolume_delete(pool, vol) + except OperationFailed as e: + path = inst.storagevolume_lookup(pool, vol)['path'] + os.remove(path) + def test_vm_info(self): inst = model.Model('test:///default', self.tmp_store) vms = inst.vms_get_list() @@ -250,19 +260,22 @@ class ModelTests(unittest.TestCase): @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_image_based_template(self): + inst = model.Model(objstore_loc=self.tmp_store) with RollbackContext() as rollback: + pool = 'default' vol = 'base-vol.img' params = {'name': vol, 'capacity': 1073741824, # 1 GiB 'allocation': 1048576, # 1 MiB 'format': 'qcow2'} - task_id = inst.storagevolumes_create('default', params)['id'] - rollback.prependDefer(inst.storagevolume_delete, 'default', vol) + task_id = inst.storagevolumes_create(pool, params)['id'] + rollback.prependDefer(temporary_storagevolume_delete, inst, + pool, vol) inst.task_wait(task_id) self.assertEquals('finished', inst.task_lookup(task_id)['status']) - vol_path = inst.storagevolume_lookup('default', vol)['path'] + vol_path = inst.storagevolume_lookup(pool, vol)['path'] # Create template based on IMG file tmpl_name = "img-tmpl" -- 2.5.0

On 15/12/2015 10:11, pvital@linux.vnet.ibm.com wrote:
From: Paulo Vital <pvital@linux.vnet.ibm.com>
Libvirt of the Fedora 23 is not able to delete the storage volume in the testcase test_image_based_template when testing ModelTests. This happens because Libvirt is not able to unlink/rmdir returning an error (-1) and setting errno to ENOENT, causing the error:
"cannot unlink file '/var/lib/libvirt/images/base-vol.img': Success"
This patch fixes the testcase by manually remove the volume file from the storage pool directory if the wok.plugins.kimchi.model.storagevolume_delete() was not capable to delete the volume.
Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- tests/test_model.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/tests/test_model.py b/tests/test_model.py index 762f0f8..4aa19bd 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -96,6 +96,16 @@ class ModelTests(unittest.TestCase):
os.unlink(self.tmp_store)
+ def temporary_storagevolume_delete(inst, pool, vol): + # Quick fix to passby the Libvirt error: + # "error: cannot unlink file '%(vol)': Success" + # in Fedora <= 23. The Libvirt fix will be present only on Fedora 24 + try: + inst.storagevolume_delete(pool, vol) + except OperationFailed as e: + path = inst.storagevolume_lookup(pool, vol)['path'] + os.remove(path) + def test_vm_info(self): inst = model.Model('test:///default', self.tmp_store) vms = inst.vms_get_list() @@ -250,19 +260,22 @@ class ModelTests(unittest.TestCase):
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_image_based_template(self): + inst = model.Model(objstore_loc=self.tmp_store)
with RollbackContext() as rollback: + pool = 'default' vol = 'base-vol.img' params = {'name': vol, 'capacity': 1073741824, # 1 GiB 'allocation': 1048576, # 1 MiB 'format': 'qcow2'}
- task_id = inst.storagevolumes_create('default', params)['id'] - rollback.prependDefer(inst.storagevolume_delete, 'default', vol)
Does that mean the user will not be able to delete a volume through Kimchi API? DELETE /plugins/kimchi/storagepools/default/<vol> If so, I suggest to move the manual removal to the API. So even though libvirt fails, Kimchi will be able to remove the volume.
+ task_id = inst.storagevolumes_create(pool, params)['id'] + rollback.prependDefer(temporary_storagevolume_delete, inst, + pool, vol) inst.task_wait(task_id) self.assertEquals('finished', inst.task_lookup(task_id)['status']) - vol_path = inst.storagevolume_lookup('default', vol)['path'] + vol_path = inst.storagevolume_lookup(pool, vol)['path']
# Create template based on IMG file tmpl_name = "img-tmpl"

On 12/16/2015 12:24 PM, Aline Manera wrote:
On 15/12/2015 10:11, pvital@linux.vnet.ibm.com wrote:
From: Paulo Vital <pvital@linux.vnet.ibm.com>
Libvirt of the Fedora 23 is not able to delete the storage volume in the testcase test_image_based_template when testing ModelTests. This happens because Libvirt is not able to unlink/rmdir returning an error (-1) and setting errno to ENOENT, causing the error:
"cannot unlink file '/var/lib/libvirt/images/base-vol.img': Success"
This patch fixes the testcase by manually remove the volume file from the storage pool directory if the wok.plugins.kimchi.model.storagevolume_delete() was not capable to delete the volume.
Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- tests/test_model.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/tests/test_model.py b/tests/test_model.py index 762f0f8..4aa19bd 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -96,6 +96,16 @@ class ModelTests(unittest.TestCase):
os.unlink(self.tmp_store)
+ def temporary_storagevolume_delete(inst, pool, vol): + # Quick fix to passby the Libvirt error: + # "error: cannot unlink file '%(vol)': Success" + # in Fedora <= 23. The Libvirt fix will be present only on Fedora 24 + try: + inst.storagevolume_delete(pool, vol) + except OperationFailed as e: + path = inst.storagevolume_lookup(pool, vol)['path'] + os.remove(path) + def test_vm_info(self): inst = model.Model('test:///default', self.tmp_store) vms = inst.vms_get_list() @@ -250,19 +260,22 @@ class ModelTests(unittest.TestCase):
@unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_image_based_template(self): + inst = model.Model(objstore_loc=self.tmp_store)
with RollbackContext() as rollback: + pool = 'default' vol = 'base-vol.img' params = {'name': vol, 'capacity': 1073741824, # 1 GiB 'allocation': 1048576, # 1 MiB 'format': 'qcow2'}
- task_id = inst.storagevolumes_create('default', params)['id'] - rollback.prependDefer(inst.storagevolume_delete, 'default', vol)
Does that mean the user will not be able to delete a volume through Kimchi API?
DELETE /plugins/kimchi/storagepools/default/<vol>
If so, I suggest to move the manual removal to the API. So even though libvirt fails, Kimchi will be able to remove the volume.
This happens only on Kimchi testcases. Look the ouput of my test: [pvital@blah.br.ibm.com] [Thu Dec 17 19:01:13] [~/Projects/wok+all] $ curl -k -u test -H 'Content-type: application/json' -H 'Accept: application/json' 'https://localhost:8001/plugins/kimchi/storagepools/default/storagevolumes' -X POST -d '{"name": "base-vol.img", "capacity": 1073741824, "format": "qcow2"}' Enter host password for user 'test': { "status":"running", "message":"OK", "id":"1", "target_uri":"/plugins/kimchi/storagepools/default/storagevolumes/base-vol.img" }------------------------------------------------------------------------------- [pvital@blah.br.ibm.com] [Thu Dec 17 19:02:33] [~/Projects/wok+all] $ curl -k -u test -H 'Content-type: application/json' -H 'Accept: application/json' 'https://localhost:8001/plugins/kimchi/storagepools/default/storagevolumes/ba...' -X GET Enter host password for user 'test': { "allocation":200704, "isvalid":true, "capacity":1073741824, "name":"base-vol.img", "format":"qcow2", "path":"/var/lib/libvirt/images/base-vol.img", "used_by":[], "type":"file" }------------------------------------------------------------------------------- [pvital@blah.br.ibm.com] [Thu Dec 17 19:03:01] [~/Projects/wok+all] $ curl -k -u test -H 'Content-type: application/json' -H 'Accept: application/json' 'https://localhost:8001/plugins/kimchi/storagepools/default/storagevolumes/ba...' -X DELETE Enter host password for user 'test': ------------------------------------------------------------------------------- [pvital@blah.br.ibm.com] [Thu Dec 17 19:03:47] [~/Projects/wok+all] $ curl -k -u test -H 'Content-type: application/json' -H 'Accept: application/json' 'https://localhost:8001/plugins/kimchi/storagepools/default/storagevolumes/ba...' -X GET Enter host password for user 'test': { "reason":"KCHVOL0002E: Storage volume base-vol.img does not exist in storage pool default", "code":"404 Not Found", "call_stack":"Traceback (most recent call last):\n File \"/usr/lib/python2.7/site-packages/cherrypy/_cprequest.py\", line 670, in respond\n response.body = self.handler()\n File \"/usr/lib/python2.7/site-packages/cherrypy/lib/encoding.py\", line 217, in __call__\n self.body = self.oldhandler(*args, **kwargs)\n File \"/usr/lib/python2.7/site-packages/cherrypy/_cpdispatch.py\", line 61, in __call__\n return self.callable(*self.args, **self.kwargs)\n File \"/home/pvital/Projects/wok+all/src/wok/control/base.py\", line 184, in index\n raise cherrypy.HTTPError(404, e.message)\nHTTPError: (404, u'KCHVOL0002E: Storage volume base-vol.img does not exist in storage pool default')\n" }------------------------------------------------------------------------------- [pvital@blah.br.ibm.com] [Thu Dec 17 19:03:57] [~/Projects/wok+all] $
+ task_id = inst.storagevolumes_create(pool, params)['id'] + rollback.prependDefer(temporary_storagevolume_delete, inst, + pool, vol) inst.task_wait(task_id) self.assertEquals('finished', inst.task_lookup(task_id)['status']) - vol_path = inst.storagevolume_lookup('default', vol)['path'] + vol_path = inst.storagevolume_lookup(pool, vol)['path']
# Create template based on IMG file tmpl_name = "img-tmpl"

From: Paulo Vital <pvital@linux.vnet.ibm.com> Libvirt of the Fedora 23 is not able to delete the storage volume in the testcase test_vm_lifecycle when testing ModelTests. This happens because Libvirt is not able to unlink/rmdir returning an error (-1) and setting errno to ENOENT, causing the error: "cannot unlink file '/var/lib/libvirt/images/test-vol': Success" This patch fixes the testcase by manually remove the volume file from the storage pool directory if the wok.plugins.kimchi.model.storagevolume_delete() was not capable to delete the volume. Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- tests/test_model.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_model.py b/tests/test_model.py index 4aa19bd..ff63182 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -96,7 +96,7 @@ class ModelTests(unittest.TestCase): os.unlink(self.tmp_store) - def temporary_storagevolume_delete(inst, pool, vol): + def temporary_storagevolume_delete(self, inst, pool, vol): # Quick fix to passby the Libvirt error: # "error: cannot unlink file '%(vol)': Success" # in Fedora <= 23. The Libvirt fix will be present only on Fedora 24 @@ -134,13 +134,14 @@ class ModelTests(unittest.TestCase): @unittest.skipUnless(utils.running_as_root(), 'Must be run as root') def test_vm_lifecycle(self): + inst = model.Model(objstore_loc=self.tmp_store) with RollbackContext() as rollback: vol_params = {'name': u'test-vol', 'capacity': 1024} task = inst.storagevolumes_create(u'default', vol_params) - rollback.prependDefer(inst.storagevolume_delete, u'default', - vol_params['name']) + rollback.prependDefer(self.temporary_storagevolume_delete, inst, + u'default', vol_params['name']) inst.task_wait(task['id']) task = inst.task_lookup(task['id']) self.assertEquals('finished', task['status']) @@ -271,7 +272,7 @@ class ModelTests(unittest.TestCase): 'allocation': 1048576, # 1 MiB 'format': 'qcow2'} task_id = inst.storagevolumes_create(pool, params)['id'] - rollback.prependDefer(temporary_storagevolume_delete, inst, + rollback.prependDefer(self.temporary_storagevolume_delete, inst, pool, vol) inst.task_wait(task_id) self.assertEquals('finished', inst.task_lookup(task_id)['status']) -- 2.5.0

From: Paulo Vital <pvital@linux.vnet.ibm.com> The vmsnapshot_revert() updates the name in the VM XML to the previous VM name (kimchi-vm), but the domain name used by libvirt to lookup this VM/domain, is the updated name (kimchi-vm-new) as proved here: https://paste.fedoraproject.org/296617/49064913/. In addition, if libvirtd is restarted, the name displayed in the 'virsh list --all' command is the updated name. This patch changed the name of the VM used to test the lifecycle. Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- tests/test_model.py | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/tests/test_model.py b/tests/test_model.py index ff63182..aebd787 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -157,7 +157,7 @@ class ModelTests(unittest.TestCase): params = {'name': 'kimchi-vm', 'template': '/plugins/kimchi/templates/test'} task = inst.vms_create(params) - rollback.prependDefer(inst.vm_delete, 'kimchi-vm') + rollback.prependDefer(inst.vm_delete, 'kimchi-vm-new') inst.task_wait(task['id'], 10) task = inst.task_lookup(task['id']) self.assertEquals('finished', task['status']) @@ -204,7 +204,7 @@ class ModelTests(unittest.TestCase): task = inst.vmsnapshots_create(u'kimchi-vm') snap_name = task['target_uri'].split('/')[-1] rollback.prependDefer(inst.vmsnapshot_delete, - u'kimchi-vm', snap_name) + u'kimchi-vm-new', snap_name) inst.task_wait(task['id']) task = inst.task_lookup(task['id']) self.assertEquals('finished', task['status']) @@ -227,34 +227,42 @@ class ModelTests(unittest.TestCase): result = inst.vmsnapshot_revert(u'kimchi-vm-new', params['name']) self.assertEquals(result, [u'kimchi-vm', snap['name']]) - vm = inst.vm_lookup(u'kimchi-vm') + # The vmsnapshot_revert() updates the name in the VM XML to the + # previous VM name (kimchi-vm), but the domain name used by libvirt + # to lookup this VM/domain, is the updated name (kimchi-vm-new) as + # proved here: https://paste.fedoraproject.org/296617/49064913/. + # In addition, if libvirtd is restarted, the name displayed in the + # 'virsh list --all' cmd is the updated name. + vm = inst.vm_lookup(u'kimchi-vm-new') self.assertEquals(vm['state'], snap['state']) - current_snap = inst.currentvmsnapshot_lookup(u'kimchi-vm') + current_snap = inst.currentvmsnapshot_lookup(u'kimchi-vm-new') self.assertEquals(params['name'], current_snap['name']) self.assertRaises(NotFoundError, inst.vmsnapshot_delete, - u'kimchi-vm', u'foobar') + u'kimchi-vm-new', u'foobar') # suspend and resume the VM - info = inst.vm_lookup(u'kimchi-vm') + info = inst.vm_lookup(u'kimchi-vm-new') self.assertEquals(info['state'], 'shutoff') - self.assertRaises(InvalidOperation, inst.vm_suspend, u'kimchi-vm') - inst.vm_start(u'kimchi-vm') - info = inst.vm_lookup(u'kimchi-vm') + self.assertRaises(InvalidOperation, inst.vm_suspend, + u'kimchi-vm-new') + inst.vm_start(u'kimchi-vm-new') + info = inst.vm_lookup(u'kimchi-vm-new') self.assertEquals(info['state'], 'running') - inst.vm_suspend(u'kimchi-vm') - info = inst.vm_lookup(u'kimchi-vm') + inst.vm_suspend(u'kimchi-vm-new') + info = inst.vm_lookup(u'kimchi-vm-new') self.assertEquals(info['state'], 'paused') - self.assertRaises(InvalidParameter, inst.vm_update, u'kimchi-vm', - {'name': 'foo'}) - inst.vm_resume(u'kimchi-vm') - info = inst.vm_lookup(u'kimchi-vm') + self.assertRaises(InvalidParameter, inst.vm_update, + u'kimchi-vm-new', {'name': 'foo'}) + inst.vm_resume(u'kimchi-vm-new') + info = inst.vm_lookup(u'kimchi-vm-new') self.assertEquals(info['state'], 'running') - self.assertRaises(InvalidOperation, inst.vm_resume, u'kimchi-vm') + self.assertRaises(InvalidOperation, inst.vm_resume, + u'kimchi-vm-new') # leave the VM suspended to make sure a paused VM can be # deleted correctly - inst.vm_suspend(u'kimchi-vm') + inst.vm_suspend(u'kimchi-vm-new') vms = inst.vms_get_list() self.assertFalse('kimchi-vm' in vms) -- 2.5.0

On 15/12/2015 10:11, pvital@linux.vnet.ibm.com wrote:
From: Paulo Vital <pvital@linux.vnet.ibm.com>
The vmsnapshot_revert() updates the name in the VM XML to the previous VM name (kimchi-vm), but the domain name used by libvirt to lookup this VM/domain, is the updated name (kimchi-vm-new) as proved here: https://paste.fedoraproject.org/296617/49064913/. In addition, if libvirtd is restarted, the name displayed in the 'virsh list --all' command is the updated name.
This patch changed the name of the VM used to test the lifecycle.
Signed-off-by: Paulo Vital <pvital@linux.vnet.ibm.com> --- tests/test_model.py | 42 +++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 17 deletions(-)
diff --git a/tests/test_model.py b/tests/test_model.py index ff63182..aebd787 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -157,7 +157,7 @@ class ModelTests(unittest.TestCase): params = {'name': 'kimchi-vm', 'template': '/plugins/kimchi/templates/test'} task = inst.vms_create(params) - rollback.prependDefer(inst.vm_delete, 'kimchi-vm') + rollback.prependDefer(inst.vm_delete, 'kimchi-vm-new') inst.task_wait(task['id'], 10) task = inst.task_lookup(task['id']) self.assertEquals('finished', task['status']) @@ -204,7 +204,7 @@ class ModelTests(unittest.TestCase): task = inst.vmsnapshots_create(u'kimchi-vm') snap_name = task['target_uri'].split('/')[-1] rollback.prependDefer(inst.vmsnapshot_delete, - u'kimchi-vm', snap_name) + u'kimchi-vm-new', snap_name) inst.task_wait(task['id']) task = inst.task_lookup(task['id']) self.assertEquals('finished', task['status']) @@ -227,34 +227,42 @@ class ModelTests(unittest.TestCase):
result = inst.vmsnapshot_revert(u'kimchi-vm-new', params['name']) self.assertEquals(result, [u'kimchi-vm', snap['name']])
Usually we don't know when reverting a snapshot will lead on a VM name update. For that, the revert() function returns a list which contains the new VM name: def revert(self, vm_name, name): try: vir_dom = VMModel.get_vm(vm_name, self.conn) vir_snap = self.get_vmsnapshot(vm_name, name) vir_dom.revertToSnapshot(vir_snap, 0) # get vm name recorded in the snapshot and return new uri params vm_new_name = xpath_get_text(vir_snap.getXMLDesc(0), 'domain/name')[0] * return [vm_new_name, name] * except libvirt.libvirtError, e: raise OperationFailed('KCHSNAP0009E', {'name': name, 'vm': vm_name, 'err': e.message}) I strongly recommend to get the 'result' value and use the expected name in the operations below. That way we make sure the flow is also working when doing it via UI.
- vm = inst.vm_lookup(u'kimchi-vm') + # The vmsnapshot_revert() updates the name in the VM XML to the + # previous VM name (kimchi-vm), but the domain name used by libvirt + # to lookup this VM/domain, is the updated name (kimchi-vm-new) as + # proved here: https://paste.fedoraproject.org/296617/49064913/.
What do we need to do that to have libvirt working in the real VM name after reverting the snapshot?
+ # In addition, if libvirtd is restarted, the name displayed in the + # 'virsh list --all' cmd is the updated name.
So the conclusion is "Reverting a snapshot WILL NEVER update the vm name". Is that correct? What is the actual Kimchi behavior while performing those action via UI?
+ vm = inst.vm_lookup(u'kimchi-vm-new') self.assertEquals(vm['state'], snap['state'])
- current_snap = inst.currentvmsnapshot_lookup(u'kimchi-vm') + current_snap = inst.currentvmsnapshot_lookup(u'kimchi-vm-new') self.assertEquals(params['name'], current_snap['name'])
self.assertRaises(NotFoundError, inst.vmsnapshot_delete, - u'kimchi-vm', u'foobar') + u'kimchi-vm-new', u'foobar')
# suspend and resume the VM - info = inst.vm_lookup(u'kimchi-vm') + info = inst.vm_lookup(u'kimchi-vm-new') self.assertEquals(info['state'], 'shutoff') - self.assertRaises(InvalidOperation, inst.vm_suspend, u'kimchi-vm') - inst.vm_start(u'kimchi-vm') - info = inst.vm_lookup(u'kimchi-vm') + self.assertRaises(InvalidOperation, inst.vm_suspend, + u'kimchi-vm-new') + inst.vm_start(u'kimchi-vm-new') + info = inst.vm_lookup(u'kimchi-vm-new') self.assertEquals(info['state'], 'running') - inst.vm_suspend(u'kimchi-vm') - info = inst.vm_lookup(u'kimchi-vm') + inst.vm_suspend(u'kimchi-vm-new') + info = inst.vm_lookup(u'kimchi-vm-new') self.assertEquals(info['state'], 'paused') - self.assertRaises(InvalidParameter, inst.vm_update, u'kimchi-vm', - {'name': 'foo'}) - inst.vm_resume(u'kimchi-vm') - info = inst.vm_lookup(u'kimchi-vm') + self.assertRaises(InvalidParameter, inst.vm_update, + u'kimchi-vm-new', {'name': 'foo'}) + inst.vm_resume(u'kimchi-vm-new') + info = inst.vm_lookup(u'kimchi-vm-new') self.assertEquals(info['state'], 'running') - self.assertRaises(InvalidOperation, inst.vm_resume, u'kimchi-vm') + self.assertRaises(InvalidOperation, inst.vm_resume, + u'kimchi-vm-new') # leave the VM suspended to make sure a paused VM can be # deleted correctly - inst.vm_suspend(u'kimchi-vm') + inst.vm_suspend(u'kimchi-vm-new')
vms = inst.vms_get_list() self.assertFalse('kimchi-vm' in vms)
participants (3)
-
Aline Manera
-
Paulo Ricardo Paz Vital
-
pvital@linux.vnet.ibm.com