one of the problems we've got with tempest is the fact that config loading is tied into the class hierarchy. However there is no reason why it should be. If we instead create a config proxy object we can lazy load the actual config when we are executing, and not do it at import time. This could use future iteration, but it does a huge job in removing config from the object inheritance tree which massively simplifies our ability to use config variables throughout the code. Change-Id: I9b1bbfe231c85c01938bd68be4e5974bd24130d6
173 lines
6.6 KiB
Python
173 lines
6.6 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2012 OpenStack Foundation
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import random
|
|
import string
|
|
|
|
import testtools
|
|
|
|
from tempest.api.compute import base
|
|
from tempest import config
|
|
from tempest import exceptions
|
|
from tempest.test import attr
|
|
|
|
|
|
class LiveBlockMigrationTestJSON(base.BaseV2ComputeAdminTest):
|
|
_host_key = 'OS-EXT-SRV-ATTR:host'
|
|
_interface = 'json'
|
|
|
|
CONF = config.CONF
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(LiveBlockMigrationTestJSON, cls).setUpClass()
|
|
|
|
cls.admin_hosts_client = cls.os_adm.hosts_client
|
|
cls.admin_servers_client = cls.os_adm.servers_client
|
|
|
|
cls.created_server_ids = []
|
|
|
|
def _get_compute_hostnames(self):
|
|
_resp, body = self.admin_hosts_client.list_hosts()
|
|
return [
|
|
host_record['host_name']
|
|
for host_record in body
|
|
if host_record['service'] == 'compute'
|
|
]
|
|
|
|
def _get_server_details(self, server_id):
|
|
_resp, body = self.admin_servers_client.get_server(server_id)
|
|
return body
|
|
|
|
def _get_host_for_server(self, server_id):
|
|
return self._get_server_details(server_id)[self._host_key]
|
|
|
|
def _migrate_server_to(self, server_id, dest_host):
|
|
_resp, body = self.admin_servers_client.live_migrate_server(
|
|
server_id, dest_host,
|
|
self.config.compute_feature_enabled.
|
|
block_migration_for_live_migration)
|
|
return body
|
|
|
|
def _get_host_other_than(self, host):
|
|
for target_host in self._get_compute_hostnames():
|
|
if host != target_host:
|
|
return target_host
|
|
|
|
def _get_non_existing_host_name(self):
|
|
random_name = ''.join(
|
|
random.choice(string.ascii_uppercase) for x in range(20))
|
|
|
|
self.assertNotIn(random_name, self._get_compute_hostnames())
|
|
|
|
return random_name
|
|
|
|
def _get_server_status(self, server_id):
|
|
return self._get_server_details(server_id)['status']
|
|
|
|
def _get_an_active_server(self):
|
|
for server_id in self.created_server_ids:
|
|
if 'ACTIVE' == self._get_server_status(server_id):
|
|
return server_id
|
|
else:
|
|
_, server = self.create_test_server(wait_until="ACTIVE")
|
|
server_id = server['id']
|
|
self.password = server['adminPass']
|
|
self.password = 'password'
|
|
self.created_server_ids.append(server_id)
|
|
return server_id
|
|
|
|
def _volume_clean_up(self, server_id, volume_id):
|
|
resp, body = self.volumes_client.get_volume(volume_id)
|
|
if body['status'] == 'in-use':
|
|
self.servers_client.detach_volume(server_id, volume_id)
|
|
self.volumes_client.wait_for_volume_status(volume_id, 'available')
|
|
self.volumes_client.delete_volume(volume_id)
|
|
|
|
@testtools.skipIf(not CONF.compute_feature_enabled.live_migration,
|
|
'Live migration not available')
|
|
@attr(type='gate')
|
|
def test_live_block_migration(self):
|
|
# Live block migrate an instance to another host
|
|
if len(self._get_compute_hostnames()) < 2:
|
|
raise self.skipTest(
|
|
"Less than 2 compute nodes, skipping migration test.")
|
|
server_id = self._get_an_active_server()
|
|
actual_host = self._get_host_for_server(server_id)
|
|
target_host = self._get_host_other_than(actual_host)
|
|
self._migrate_server_to(server_id, target_host)
|
|
self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
|
|
self.assertEqual(target_host, self._get_host_for_server(server_id))
|
|
|
|
@testtools.skipIf(not CONF.compute_feature_enabled.live_migration,
|
|
'Live migration not available')
|
|
@attr(type='gate')
|
|
def test_invalid_host_for_migration(self):
|
|
# Migrating to an invalid host should not change the status
|
|
server_id = self._get_an_active_server()
|
|
target_host = self._get_non_existing_host_name()
|
|
|
|
self.assertRaises(exceptions.BadRequest, self._migrate_server_to,
|
|
server_id, target_host)
|
|
self.assertEqual('ACTIVE', self._get_server_status(server_id))
|
|
|
|
@testtools.skipIf(not CONF.compute_feature_enabled.live_migration or not
|
|
CONF.compute_feature_enabled.
|
|
block_migration_for_live_migration,
|
|
'Block Live migration not available')
|
|
@testtools.skipIf(not CONF.compute_feature_enabled.
|
|
block_migrate_cinder_iscsi,
|
|
'Block Live migration not configured for iSCSI')
|
|
@attr(type='gate')
|
|
def test_iscsi_volume(self):
|
|
# Live block migrate an instance to another host
|
|
if len(self._get_compute_hostnames()) < 2:
|
|
raise self.skipTest(
|
|
"Less than 2 compute nodes, skipping migration test.")
|
|
server_id = self._get_an_active_server()
|
|
actual_host = self._get_host_for_server(server_id)
|
|
target_host = self._get_host_other_than(actual_host)
|
|
|
|
resp, volume = self.volumes_client.create_volume(1,
|
|
display_name='test')
|
|
|
|
self.volumes_client.wait_for_volume_status(volume['id'],
|
|
'available')
|
|
self.addCleanup(self._volume_clean_up, server_id, volume['id'])
|
|
|
|
# Attach the volume to the server
|
|
self.servers_client.attach_volume(server_id, volume['id'],
|
|
device='/dev/xvdb')
|
|
self.volumes_client.wait_for_volume_status(volume['id'], 'in-use')
|
|
|
|
self._migrate_server_to(server_id, target_host)
|
|
self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
|
|
self.assertEqual(target_host, self._get_host_for_server(server_id))
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
for server_id in cls.created_server_ids:
|
|
cls.servers_client.delete_server(server_id)
|
|
|
|
super(LiveBlockMigrationTestJSON, cls).tearDownClass()
|
|
|
|
|
|
class LiveBlockMigrationTestXML(LiveBlockMigrationTestJSON):
|
|
_host_key = (
|
|
'{http://docs.openstack.org/compute/ext/extended_status/api/v1.1}host')
|
|
_interface = 'xml'
|