Benchmark scenarios for Rally: Nova utils
This patch contains the base class for Rally benchmark scenarios for Nova. The class provides several helper methods for booting/suspending servers and images. These methods will be of great use in the actual benchmark scenarios for Nova servers, floating_ips, metadata enrties etc. Blueprint benchmark-scenarios Change-Id: I29301d6ee1b1cca2939004acf3d681b01f101b44
This commit is contained in:
0
rally/benchmark/scenarios/nova/__init__.py
Normal file
0
rally/benchmark/scenarios/nova/__init__.py
Normal file
170
rally/benchmark/scenarios/nova/utils.py
Normal file
170
rally/benchmark/scenarios/nova/utils.py
Normal file
@@ -0,0 +1,170 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013: Mirantis Inc.
|
||||
# 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 time
|
||||
|
||||
from novaclient import exceptions
|
||||
|
||||
from rally.benchmark import base
|
||||
from rally import exceptions as rally_exceptions
|
||||
from rally import utils
|
||||
|
||||
|
||||
def _resource_is(status):
|
||||
return lambda resource: resource.status == status
|
||||
|
||||
|
||||
def _get_from_manager(resource):
|
||||
resource = resource.manager.get(resource)
|
||||
if resource.status == "FAILED":
|
||||
raise rally_exceptions.GetResourceFailure()
|
||||
return resource
|
||||
|
||||
|
||||
def _false(resource):
|
||||
return False
|
||||
|
||||
|
||||
class NovaScenario(base.Scenario):
|
||||
|
||||
@classmethod
|
||||
def _boot_server(cls, server_name, image_id, flavor_id, **kwargs):
|
||||
"""Boots one server.
|
||||
|
||||
Returns when the server is actually booted and is in the "Active"
|
||||
state.
|
||||
|
||||
:param server_name: String used to name the server
|
||||
:param image_id: ID of the image to be used for server creation
|
||||
:param flavor_id: ID of the flavor to be used for server creation
|
||||
:param **kwargs: Other optional parameters to initialize the server
|
||||
|
||||
:returns: Created server object
|
||||
"""
|
||||
server = cls.nova.servers.create(server_name, image_id, flavor_id,
|
||||
**kwargs)
|
||||
# NOTE(msdubov): It is reasonable to wait 5 secs before starting to
|
||||
# check whether the server is ready => less API calls.
|
||||
time.sleep(5)
|
||||
server = utils.wait_for(server, is_ready=_resource_is("ACTIVE"),
|
||||
update_resource=_get_from_manager,
|
||||
timeout=600, check_interval=3)
|
||||
return server
|
||||
|
||||
@classmethod
|
||||
def _suspend_server(cls, server):
|
||||
"""Suspends the given server.
|
||||
|
||||
Returns when the server is actually suspended and is in the "Suspended"
|
||||
state.
|
||||
|
||||
:param server: Server object
|
||||
"""
|
||||
server.suspend()
|
||||
time.sleep(2)
|
||||
utils.wait_for(server, is_ready=_resource_is("SUSPENDED"),
|
||||
update_resource=_get_from_manager,
|
||||
timeout=600, check_interval=3)
|
||||
|
||||
@classmethod
|
||||
def _delete_server(cls, server):
|
||||
"""Deletes the given server.
|
||||
|
||||
Returns when the server is actually deleted.
|
||||
|
||||
:param server: Server object
|
||||
"""
|
||||
server.delete()
|
||||
# NOTE(msdubov): When the server gets deleted, the nova.servers.get()
|
||||
# method raises a NotFound exception.
|
||||
try:
|
||||
utils.wait_for(server, is_ready=_false,
|
||||
update_resource=_get_from_manager,
|
||||
timeout=600, check_interval=3)
|
||||
except exceptions.NotFound:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def _delete_image(cls, image):
|
||||
"""Deletes the given image.
|
||||
|
||||
Returns when the image is actually deleted.
|
||||
|
||||
:param image: Image object
|
||||
"""
|
||||
image.delete()
|
||||
utils.wait_for(image, is_ready=_resource_is("DELETED"),
|
||||
update_resource=_get_from_manager,
|
||||
timeout=600, check_interval=3)
|
||||
|
||||
@classmethod
|
||||
def _create_image(cls, server):
|
||||
"""Creates an image of the given server
|
||||
|
||||
Uses the server name to name the created image. Returns when the image
|
||||
is actually created and is in the "Active" state.
|
||||
|
||||
:param server: Server object for which the image will be created
|
||||
|
||||
:returns: Created image object
|
||||
"""
|
||||
image_uuid = cls.nova.servers.create_image(server, server.name)
|
||||
image = cls.nova.images.get(image_uuid)
|
||||
image = utils.wait_for(image, is_ready=_resource_is("ACTIVE"),
|
||||
update_resource=_get_from_manager,
|
||||
timeout=600, check_interval=3)
|
||||
return image
|
||||
|
||||
@classmethod
|
||||
def _boot_servers(cls, name_prefix, image_id, flavor_id,
|
||||
requests, instances_per_request=1, **kwargs):
|
||||
"""Boots multiple servers.
|
||||
|
||||
Returns when all the servers are actually booted and are in the
|
||||
"Active" state.
|
||||
|
||||
:param name_prefix: The prefix to use while naming the created servers.
|
||||
The rest of the server names will be '_No.'
|
||||
:param image_id: ID of the image to be used for server creation
|
||||
:param flavor_id: ID of the flavor to be used for server creation
|
||||
:param requests: Number of booting requests to perform
|
||||
:param instances_per_request: Number of instances to boot
|
||||
per each request
|
||||
|
||||
:returns: List of created server objects
|
||||
"""
|
||||
for i in range(requests):
|
||||
cls.nova.servers.create('%s_%d' % (name_prefix, i), image_id,
|
||||
flavor_id, min_count=instances_per_request,
|
||||
max_count=instances_per_request, **kwargs)
|
||||
# NOTE(msdubov): Nova python client returns only one server even when
|
||||
# min_count > 1, so we have to rediscover all the
|
||||
# created servers manyally.
|
||||
servers = filter(lambda server: server.name.startswith(name_prefix),
|
||||
cls.nova.servers.list())
|
||||
time.sleep(5)
|
||||
servers = [utils.wait_for(server, is_ready=_resource_is("ACTIVE"),
|
||||
update_resource=_get_from_manager,
|
||||
timeout=600, check_interval=3)
|
||||
for server in servers]
|
||||
return servers
|
||||
|
||||
@classmethod
|
||||
def _generate_random_name(cls, length):
|
||||
return ''.join(random.choice(string.lowercase) for i in range(length))
|
@@ -122,3 +122,7 @@ class TaskNotFound(NotFoundException):
|
||||
|
||||
class TimeoutException(RallyException):
|
||||
msg_fmt = _("Timeout exceeded.")
|
||||
|
||||
|
||||
class GetResourceFailure(RallyException):
|
||||
msg_fmt = _("Failed to get the resource.")
|
||||
|
0
tests/benchmark/scenarios/__init__.py
Normal file
0
tests/benchmark/scenarios/__init__.py
Normal file
0
tests/benchmark/scenarios/nova/__init__.py
Normal file
0
tests/benchmark/scenarios/nova/__init__.py
Normal file
165
tests/benchmark/scenarios/nova/test_utils.py
Normal file
165
tests/benchmark/scenarios/nova/test_utils.py
Normal file
@@ -0,0 +1,165 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013: Mirantis Inc.
|
||||
# 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 mock
|
||||
|
||||
from rally.benchmark.scenarios.nova import utils
|
||||
from rally import test
|
||||
|
||||
|
||||
# NOTE(msdubov): A set of 'Fake' classes below is of great use in the test case
|
||||
# for utils and also in the test cases for bechmark scenarios.
|
||||
|
||||
|
||||
class FakeResource(object):
|
||||
|
||||
def __init__(self, manager=None):
|
||||
self.name = "resource"
|
||||
self.status = "ACTIVE"
|
||||
self.manager = manager
|
||||
|
||||
def __getattr__(self, name):
|
||||
# NOTE(msdubov): e.g. server.delete() -> manager.delete(server)
|
||||
def manager_func(*args, **kwargs):
|
||||
getattr(self.manager, name)(self, *args, **kwargs)
|
||||
return manager_func
|
||||
|
||||
|
||||
class FakeServer(FakeResource):
|
||||
|
||||
def suspend(self):
|
||||
self.status = "SUSPENDED"
|
||||
|
||||
|
||||
class FakeImage(FakeResource):
|
||||
pass
|
||||
|
||||
|
||||
class FakeFloatingIP(FakeResource):
|
||||
pass
|
||||
|
||||
|
||||
class FakeManager(object):
|
||||
|
||||
def get(self, resource):
|
||||
return resource
|
||||
|
||||
def delete(self, resource):
|
||||
pass
|
||||
|
||||
|
||||
class FakeServerManager(FakeManager):
|
||||
|
||||
def create(self, name, image_id, flavor_id):
|
||||
return FakeServer(self)
|
||||
|
||||
def create_image(self, server, name):
|
||||
return "img_uuid"
|
||||
|
||||
def add_floating_ip(self, server, fip):
|
||||
pass
|
||||
|
||||
def remove_floating_ip(self, server, fip):
|
||||
pass
|
||||
|
||||
|
||||
class FakeImageManager(FakeManager):
|
||||
|
||||
def create(self):
|
||||
return FakeImage(self)
|
||||
|
||||
|
||||
class FakeFloatingIPsManager(FakeManager):
|
||||
|
||||
def create(self):
|
||||
return FakeFloatingIP(self)
|
||||
|
||||
|
||||
class FakeNovaClient(object):
|
||||
|
||||
def __init__(self):
|
||||
self.servers = FakeServerManager()
|
||||
self.images = FakeImageManager()
|
||||
self.floating_ips = FakeFloatingIPsManager()
|
||||
|
||||
|
||||
class FakeClients(object):
|
||||
|
||||
def get_keystone_client(self):
|
||||
return "keystone"
|
||||
|
||||
def get_nova_client(self):
|
||||
return FakeNovaClient()
|
||||
|
||||
def get_glance_client(self):
|
||||
return "glance"
|
||||
|
||||
def get_cinder_client(self):
|
||||
return "cinder"
|
||||
|
||||
|
||||
class NovaScenarioTestCase(test.NoDBTestCase):
|
||||
|
||||
def test_generate_random_name(self):
|
||||
for length in [8, 16, 32, 64]:
|
||||
name = utils.NovaScenario._generate_random_name(length)
|
||||
self.assertEqual(len(name), length)
|
||||
self.assertTrue(name.isalpha())
|
||||
|
||||
def test_server_helper_methods(self):
|
||||
|
||||
rally_utils = "rally.benchmark.scenarios.nova.utils.utils"
|
||||
utils_resource_is = "rally.benchmark.scenarios.nova.utils._resource_is"
|
||||
osclients = "rally.benchmark.base.osclients"
|
||||
servers_create = ("rally.benchmark.scenarios.nova.utils.NovaScenario."
|
||||
"nova.servers.create")
|
||||
sleep = "rally.benchmark.scenarios.nova.utils.time.sleep"
|
||||
|
||||
with mock.patch(rally_utils) as mock_rally_utils:
|
||||
with mock.patch(utils_resource_is) as mock_resource_is:
|
||||
mock_resource_is.return_value = {}
|
||||
with mock.patch(osclients) as mock_osclients:
|
||||
mock_osclients.Clients.return_value = FakeClients()
|
||||
keys = ["admin_username", "admin_password",
|
||||
"admin_tenant_name", "uri"]
|
||||
kw = dict(zip(keys, keys))
|
||||
utils.NovaScenario.class_init(kw)
|
||||
with mock.patch(servers_create) as mock_create:
|
||||
fake_server = FakeServerManager().create("s1", "i1", 1)
|
||||
mock_create.return_value = fake_server
|
||||
with mock.patch(sleep):
|
||||
utils.NovaScenario._boot_server("s1", "i1", 1)
|
||||
utils.NovaScenario._create_image(fake_server)
|
||||
utils.NovaScenario._suspend_server(fake_server)
|
||||
utils.NovaScenario._delete_server(fake_server)
|
||||
|
||||
expected = [
|
||||
mock.call.wait_for(fake_server, is_ready={},
|
||||
update_resource=utils._get_from_manager,
|
||||
timeout=600, check_interval=3),
|
||||
mock.call.wait_for("img_uuid", is_ready={},
|
||||
update_resource=utils._get_from_manager,
|
||||
timeout=600, check_interval=3),
|
||||
mock.call.wait_for(fake_server, is_ready={},
|
||||
update_resource=utils._get_from_manager,
|
||||
timeout=600, check_interval=3),
|
||||
mock.call.wait_for(fake_server, is_ready=utils._false,
|
||||
update_resource=utils._get_from_manager,
|
||||
timeout=600, check_interval=3)
|
||||
]
|
||||
|
||||
self.assertEqual(mock_rally_utils.mock_calls, expected)
|
Reference in New Issue
Block a user