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):
|
class TimeoutException(RallyException):
|
||||||
msg_fmt = _("Timeout exceeded.")
|
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