Adds cold resize Mixin

The mixin contains code and tests necessary in order to test
different scenarios / features with cold resize.

The mixins will ensure that the Feature classes they are mixed
into will still behave properly after cold resize.
This commit is contained in:
Claudiu Belu 2017-06-25 12:19:51 -07:00
parent 136ef0ac9d
commit 696da58fde
4 changed files with 171 additions and 1 deletions

View File

@ -0,0 +1,21 @@
# Copyright 2017 Cloudbase Solutions SRL
# 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.
from tempest.lib import exceptions
class ResizeException(exceptions.TempestException):
message = ("Server %(server_id)s failed to resize to the given "
"flavor %(flavor)s")

View File

@ -0,0 +1,108 @@
# Copyright 2017 Cloudbase Solutions SRL
# 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 time
from oslo_log import log as logging
import testtools
from oswin_tempest_plugin import config
from oswin_tempest_plugin import exceptions
CONF = config.CONF
LOG = logging.getLogger(__name__)
class _ResizeUtils(object):
def _get_server_migration(self, server_id):
final_states = ['error', 'confirmed']
for i in range(10):
migrations = (
self.admin_migrations_client.list_migrations()['migrations'])
server_migration = [m for m in migrations
if m['instance_uuid'] == server_id]
if server_migration:
migration_status = server_migration[0]['status']
LOG.debug("Server's %s migration status: %s",
server_id, migration_status)
if migration_status in final_states:
return server_migration[0]
else:
# NOTE(claudiub): the migration might not appear *immediately*
# after the cold resize was requested.
LOG.info("Server's %s migration was not found.", server_id)
time.sleep(1)
return server_migration[0] if server_migration else None
def _resize_server(self, server_tuple, new_flavor):
server = server_tuple.server
self.servers_client.resize_server(server['id'],
flavor_ref=new_flavor['id'])
migration = self._get_server_migration(server['id'])
if migration and migration['status'] == 'error':
# the migration ended up in an error state. Raise an exception.
raise exceptions.ResizeException(server_id=server['id'],
flavor=new_flavor)
self._wait_for_server_status(server, 'VERIFY_RESIZE')
self.servers_client.confirm_resize_server(server['id'])
class _ResizeMixin(_ResizeUtils):
"""Cold resize mixin.
This mixin will add cold resize tests. The tests will create a new
instance, resize it to a new flavor, and check its network connectivity.
The new flavor is based on the configured compute.flavor_ref, with some
updates. For example, if the vNUMA configuration is to be tested, the new
flavor would contain the flavor extra_spec 'hw:numa_nodes=1'.
"""
# NOTE(claudiub): These flavor dicts are updates to the base flavor
# tempest is configured with. For example, _BIGGER_FLAVOR can be:
# _BIGGER_FLAVOR = {'disk': 1}
# which means a flavor having +1 GB disk size will be created, and
# a created instance will be resized to it.
_SMALLER_FLAVOR = {}
_BIGGER_FLAVOR = {}
_BAD_FLAVOR = {}
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize is not available.')
def test_resize(self):
new_flavor = self._create_new_flavor(self._get_flavor_ref(),
self._BIGGER_FLAVOR)
server_tuple = self._create_server()
self._resize_server(server_tuple, new_flavor)
self._check_server_connectivity(server_tuple)
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize is not available.')
def test_resize_negative(self):
new_flavor = self._create_new_flavor(self._get_flavor_ref(),
self._BAD_FLAVOR)
server_tuple = self._create_server()
self.assertRaises(exceptions.ResizeException, self._resize_server,
server_tuple, new_flavor)
# assert that the server is still reachable, even if the resize
# failed.
self._check_server_connectivity(server_tuple)

View File

@ -16,11 +16,13 @@
from oswin_tempest_plugin import config
from oswin_tempest_plugin.tests import test_base
from oswin_tempest_plugin.tests._mixins import migrate
from oswin_tempest_plugin.tests._mixins import resize
CONF = config.CONF
class _BaseDiskTestMixin(migrate._MigrateMixin):
class _BaseDiskTestMixin(migrate._MigrateMixin,
resize._ResizeMixin):
"""Image types / formats test suite.
This test suite will spawn instances with a configured image and will
@ -30,6 +32,9 @@ class _BaseDiskTestMixin(migrate._MigrateMixin):
_CONF_OPTION_NAME = ''
_BIGGER_FLAVOR = {'disk': 1}
_BAD_FLAVOR = {'disk': -1}
@classmethod
def skip_checks(cls):
super(_BaseDiskTestMixin, cls).skip_checks()
@ -48,6 +53,7 @@ class VhdDiskTest(test_base.TestBase, _BaseDiskTestMixin):
_IMAGE_REF = CONF.hyperv.vhd_image_ref
_CONF_OPTION_NAME = 'hyperv.vhd_image_ref'
_FLAVOR_SUFFIX = 'vhd'
# TODO(claudiub): validate that the images really are VHD / VHDX.
@ -56,6 +62,7 @@ class VhdxDiskTest(test_base.TestBase, _BaseDiskTestMixin):
_IMAGE_REF = CONF.hyperv.vhdx_image_ref
_CONF_OPTION_NAME = 'hyperv.vhdx_image_ref'
_FLAVOR_SUFFIX = 'vhdx'
class Generation2DiskTest(test_base.TestBase, _BaseDiskTestMixin):
@ -65,6 +72,7 @@ class Generation2DiskTest(test_base.TestBase, _BaseDiskTestMixin):
_IMAGE_REF = CONF.hyperv.gen2_image_ref
_CONF_OPTION_NAME = 'hyperv.gen2_image_ref'
_FLAVOR_SUFFIX = 'gen2'
# TODO(claudiub): Add validation that the given gen2_image_ref really has
# the 'hw_machine_type=hyperv-gen2' property.

View File

@ -45,6 +45,9 @@ class TestBase(tempest.test.BaseTestCase):
# Inheriting TestCases should change this image ref if needed.
_IMAGE_REF = CONF.compute.image_ref
# suffix to use for the newly created flavors.
_FLAVOR_SUFFIX = ''
@classmethod
def skip_checks(cls):
super(TestBase, cls).skip_checks()
@ -71,6 +74,8 @@ class TestBase(tempest.test.BaseTestCase):
cls.keypairs_client = cls.os_primary.keypairs_client
cls.servers_client = cls.os_primary.servers_client
cls.admin_servers_client = cls.os_admin.servers_client
cls.admin_flavors_client = cls.os_admin.flavors_client
cls.admin_migrations_client = cls.os_admin.migrations_client
# Neutron network client
cls.security_groups_client = (
@ -101,6 +106,34 @@ class TestBase(tempest.test.BaseTestCase):
def _get_image_ref(self):
return self._IMAGE_REF
def _flavor_cleanup(self, flavor_id):
try:
self.admin_flavors_client.delete_flavor(flavor_id)
self.admin_flavors_client.wait_for_resource_deletion(flavor_id)
except exceptions.NotFound:
pass
def _create_new_flavor(self, flavor_ref, flavor_updates):
"""Creates a new flavor based on the given flavor and flavor updates.
:returns: the newly created flavor's ID.
"""
flavor = self.admin_flavors_client.show_flavor(flavor_ref)['flavor']
flavor_name = 'test_resize'
if self._FLAVOR_SUFFIX:
flavor_name += '_%s' % self._FLAVOR_SUFFIX
new_flavor = self.admin_flavors_client.create_flavor(
name=data_utils.rand_name(flavor_name),
ram=flavor['ram'] + flavor_updates.get('ram', 0),
disk=flavor['disk'] + flavor_updates.get('disk', 0),
vcpus=flavor['vcpus'] + flavor_updates.get('vcpus', 0),
)['flavor']
self.addCleanup(self._flavor_cleanup, new_flavor['id'])
return new_flavor
def _get_flavor_ref(self):
return CONF.compute.flavor_ref