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:
Mikhail Dubov
2013-09-17 10:04:03 +04:00
parent 51ae9447c9
commit 4dd519a4df
6 changed files with 339 additions and 0 deletions

View 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))

View File

@@ -122,3 +122,7 @@ class TaskNotFound(NotFoundException):
class TimeoutException(RallyException):
msg_fmt = _("Timeout exceeded.")
class GetResourceFailure(RallyException):
msg_fmt = _("Failed to get the resource.")

View File

View 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)