Merge "Add context that creates servers using EC2 api"
This commit is contained in:
0
rally/plugins/openstack/context/ec2/__init__.py
Normal file
0
rally/plugins/openstack/context/ec2/__init__.py
Normal file
98
rally/plugins/openstack/context/ec2/servers.py
Normal file
98
rally/plugins/openstack/context/ec2/servers.py
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# 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 rally.common.i18n import _
|
||||||
|
from rally.common import log as logging
|
||||||
|
from rally.common import utils as rutils
|
||||||
|
from rally import consts
|
||||||
|
from rally import osclients
|
||||||
|
from rally.plugins.openstack.context.cleanup import manager as resource_manager
|
||||||
|
from rally.plugins.openstack.scenarios.ec2 import utils as ec2_utils
|
||||||
|
from rally.task import context
|
||||||
|
from rally.task import types
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@context.configure(name="ec2_servers", order=460)
|
||||||
|
class EC2ServerGenerator(context.Context):
|
||||||
|
"""Context class for adding temporary servers for benchmarks.
|
||||||
|
|
||||||
|
Servers are added for each tenant.
|
||||||
|
"""
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = {
|
||||||
|
"type": "object",
|
||||||
|
"$schema": consts.JSON_SCHEMA,
|
||||||
|
"properties": {
|
||||||
|
"image": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flavor": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"servers_per_tenant": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["image", "flavor", "servers_per_tenant"],
|
||||||
|
"additionalProperties": False
|
||||||
|
}
|
||||||
|
|
||||||
|
@rutils.log_task_wrapper(LOG.info, _("Enter context: `EC2 Servers`"))
|
||||||
|
def setup(self):
|
||||||
|
image = self.config["image"]
|
||||||
|
flavor = self.config["flavor"]
|
||||||
|
|
||||||
|
clients = osclients.Clients(self.context["users"][0]["endpoint"])
|
||||||
|
image_id = types.EC2ImageResourceType.transform(clients=clients,
|
||||||
|
resource_config=image)
|
||||||
|
|
||||||
|
for user, tenant_id in rutils.iterate_per_tenants(
|
||||||
|
self.context["users"]):
|
||||||
|
LOG.debug("Booting servers for tenant %s "
|
||||||
|
% (user["tenant_id"]))
|
||||||
|
user_clients = osclients.Clients(user["endpoint"])
|
||||||
|
ec2_scenario = ec2_utils.EC2Scenario(clients=user_clients)
|
||||||
|
|
||||||
|
LOG.debug(
|
||||||
|
"Calling _boot_servers with "
|
||||||
|
"image_id={image_id} flavor_name={flavor_name} "
|
||||||
|
"servers_per_tenant={servers_per_tenant}".format(
|
||||||
|
image_id=image_id, flavor_name=flavor["name"],
|
||||||
|
servers_per_tenant=self.config["servers_per_tenant"]))
|
||||||
|
|
||||||
|
servers = ec2_scenario._boot_servers(
|
||||||
|
image_id, flavor["name"], self.config["servers_per_tenant"])
|
||||||
|
|
||||||
|
current_servers = [server.id for server in servers]
|
||||||
|
|
||||||
|
self.context["tenants"][tenant_id]["ec2_servers"] = current_servers
|
||||||
|
|
||||||
|
@rutils.log_task_wrapper(LOG.info, _("Exit context: `EC2 Servers`"))
|
||||||
|
def cleanup(self):
|
||||||
|
resource_manager.cleanup(names=["ec2.servers"],
|
||||||
|
users=self.context.get("users", []))
|
@@ -41,4 +41,4 @@ class EC2Servers(utils.EC2Scenario):
|
|||||||
:param flavor: flavor to be used to boot an instance
|
:param flavor: flavor to be used to boot an instance
|
||||||
:param kwargs: optional additional arguments for server creation
|
:param kwargs: optional additional arguments for server creation
|
||||||
"""
|
"""
|
||||||
self._boot_server(image, flavor, **kwargs)
|
self._boot_servers(image, flavor, **kwargs)
|
||||||
|
@@ -48,33 +48,38 @@ CONF.register_opts(EC2_BENCHMARK_OPTS, group=benchmark_group)
|
|||||||
class EC2Scenario(scenario.OpenStackScenario):
|
class EC2Scenario(scenario.OpenStackScenario):
|
||||||
"""Base class for EC2 scenarios with basic atomic actions."""
|
"""Base class for EC2 scenarios with basic atomic actions."""
|
||||||
|
|
||||||
RESOURCE_NAME_PREFIX = "rally_ec2server_"
|
@base.atomic_action_timer("ec2.boot_servers")
|
||||||
RESOURCE_NAME_LENGTH = 16
|
def _boot_servers(self, image_id, flavor_name,
|
||||||
|
instance_num=1, **kwargs):
|
||||||
|
"""Boot multiple servers.
|
||||||
|
|
||||||
@base.atomic_action_timer("ec2.boot_server")
|
Returns when all the servers are actually booted and are in the
|
||||||
def _boot_server(self, image_id, flavor_name, **kwargs):
|
"Running" state.
|
||||||
"""Boot a server.
|
|
||||||
|
|
||||||
Returns when the server is actually booted and in "Running" state.
|
|
||||||
|
|
||||||
:param image_id: ID of the image to be used for server creation
|
:param image_id: ID of the image to be used for server creation
|
||||||
:param flavor_name: Name of the flavor to be used for server creation
|
:param flavor_name: Name of the flavor to be used for server creation
|
||||||
:param kwargs: other optional parameters to initialize the server
|
:param instance_num: Number of instances to boot
|
||||||
:returns: EC2 Server instance
|
:param kwargs: Other optional parameters to boot servers
|
||||||
|
|
||||||
|
:returns: List of created server objects
|
||||||
"""
|
"""
|
||||||
reservation = self.clients("ec2").run_instances(
|
reservation = self.clients("ec2").run_instances(
|
||||||
image_id=image_id, instance_type=flavor_name, **kwargs)
|
image_id=image_id,
|
||||||
server = reservation.instances[0]
|
instance_type=flavor_name,
|
||||||
|
min_count=instance_num,
|
||||||
|
max_count=instance_num,
|
||||||
|
**kwargs)
|
||||||
|
servers = [instance for instance in reservation.instances]
|
||||||
|
|
||||||
time.sleep(CONF.benchmark.ec2_server_boot_prepoll_delay)
|
time.sleep(CONF.benchmark.ec2_server_boot_prepoll_delay)
|
||||||
server = utils.wait_for(
|
servers = [utils.wait_for(
|
||||||
server,
|
server,
|
||||||
is_ready=utils.resource_is("RUNNING"),
|
is_ready=utils.resource_is("RUNNING"),
|
||||||
update_resource=self._update_resource,
|
update_resource=self._update_resource,
|
||||||
timeout=CONF.benchmark.ec2_server_boot_timeout,
|
timeout=CONF.benchmark.ec2_server_boot_timeout,
|
||||||
check_interval=CONF.benchmark.ec2_server_boot_poll_interval
|
check_interval=CONF.benchmark.ec2_server_boot_poll_interval
|
||||||
)
|
) for server in servers]
|
||||||
return server
|
return servers
|
||||||
|
|
||||||
def _update_resource(self, resource):
|
def _update_resource(self, resource):
|
||||||
resource.update()
|
resource.update()
|
||||||
|
110
tests/unit/plugins/openstack/context/ec2/test_servers.py
Normal file
110
tests/unit/plugins/openstack/context/ec2/test_servers.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# 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 copy
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from rally.plugins.openstack.context.ec2 import servers
|
||||||
|
from tests.unit import fakes
|
||||||
|
from tests.unit import test
|
||||||
|
|
||||||
|
CTX = "rally.plugins.openstack.context.ec2"
|
||||||
|
SCN = "rally.plugins.openstack.scenarios"
|
||||||
|
TYP = "rally.task.types"
|
||||||
|
|
||||||
|
|
||||||
|
class EC2ServerGeneratorTestCase(test.TestCase):
|
||||||
|
|
||||||
|
def _gen_tenants_and_users(self, tenants_count, users_per_tenant):
|
||||||
|
tenants = {}
|
||||||
|
for id in range(tenants_count):
|
||||||
|
tenants[str(id)] = dict(name=str(id))
|
||||||
|
|
||||||
|
users = []
|
||||||
|
for tenant_id in tenants.keys():
|
||||||
|
for i in range(users_per_tenant):
|
||||||
|
users.append({"id": i, "tenant_id": tenant_id,
|
||||||
|
"endpoint": "endpoint"})
|
||||||
|
return tenants, users
|
||||||
|
|
||||||
|
def _get_context(self, users, tenants):
|
||||||
|
return {
|
||||||
|
"config": {
|
||||||
|
"users": {
|
||||||
|
"tenants": 2,
|
||||||
|
"users_per_tenant": 5,
|
||||||
|
"concurrent": 10},
|
||||||
|
"ec2_servers": {
|
||||||
|
"servers_per_tenant": 5,
|
||||||
|
"image": {"name": "foo_image"},
|
||||||
|
"flavor": {"name": "foo_flavor"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"admin": {"endpoint": mock.MagicMock()},
|
||||||
|
"task": mock.MagicMock(),
|
||||||
|
"users": users,
|
||||||
|
"tenants": tenants
|
||||||
|
}
|
||||||
|
|
||||||
|
@mock.patch("%s.ec2.utils.EC2Scenario._boot_servers" % SCN,
|
||||||
|
return_value=[fakes.FakeServer(id=str(i)) for i in range(5)])
|
||||||
|
@mock.patch("%s.EC2ImageResourceType.transform" % TYP,
|
||||||
|
return_value=mock.MagicMock())
|
||||||
|
@mock.patch("%s.servers.osclients" % CTX, return_value=fakes.FakeClients())
|
||||||
|
def test_setup(self, mock_osclients,
|
||||||
|
mock_ec2_image_resource_type_transform,
|
||||||
|
mock_ec2_scenario__boot_servers):
|
||||||
|
|
||||||
|
tenants_count = 2
|
||||||
|
users_per_tenant = 5
|
||||||
|
servers_per_tenant = 5
|
||||||
|
|
||||||
|
tenants, users = self._gen_tenants_and_users(tenants_count,
|
||||||
|
users_per_tenant)
|
||||||
|
|
||||||
|
real_context = self._get_context(users, tenants)
|
||||||
|
|
||||||
|
new_context = copy.deepcopy(real_context)
|
||||||
|
for tenant_id in new_context["tenants"]:
|
||||||
|
new_context["tenants"][tenant_id].setdefault("ec2_servers", [])
|
||||||
|
for i in range(servers_per_tenant):
|
||||||
|
new_context["tenants"][tenant_id]["ec2_servers"].append(str(i))
|
||||||
|
|
||||||
|
servers_ctx = servers.EC2ServerGenerator(real_context)
|
||||||
|
servers_ctx.setup()
|
||||||
|
self.assertEqual(new_context, servers_ctx.context)
|
||||||
|
|
||||||
|
@mock.patch("%s.servers.osclients" % CTX)
|
||||||
|
@mock.patch("%s.servers.resource_manager.cleanup" % CTX)
|
||||||
|
def test_cleanup(self, mock_cleanup, mock_osclients):
|
||||||
|
|
||||||
|
tenants_count = 2
|
||||||
|
users_per_tenant = 5
|
||||||
|
servers_per_tenant = 5
|
||||||
|
|
||||||
|
tenants, users = self._gen_tenants_and_users(tenants_count,
|
||||||
|
users_per_tenant)
|
||||||
|
for tenant_id in tenants.keys():
|
||||||
|
tenants[tenant_id].setdefault("ec2_servers", [])
|
||||||
|
for i in range(servers_per_tenant):
|
||||||
|
tenants[tenant_id]["ec2_servers"].append(str(i))
|
||||||
|
|
||||||
|
context = self._get_context(users, tenants)
|
||||||
|
|
||||||
|
servers_ctx = servers.EC2ServerGenerator(context)
|
||||||
|
servers_ctx.cleanup()
|
||||||
|
|
||||||
|
mock_cleanup.assert_called_once_with(names=["ec2.servers"],
|
||||||
|
users=context["users"])
|
@@ -22,7 +22,7 @@ class EC2ServersTestCase(test.ScenarioTestCase):
|
|||||||
|
|
||||||
def test_boot_server(self):
|
def test_boot_server(self):
|
||||||
scenario = servers.EC2Servers()
|
scenario = servers.EC2Servers()
|
||||||
scenario._boot_server = mock.Mock()
|
scenario._boot_servers = mock.Mock()
|
||||||
scenario.boot_server("foo_image", "foo_flavor", foo="bar")
|
scenario.boot_server("foo_image", "foo_flavor", foo="bar")
|
||||||
scenario._boot_server.assert_called_once_with(
|
scenario._boot_servers.assert_called_once_with(
|
||||||
"foo_image", "foo_flavor", foo="bar")
|
"foo_image", "foo_flavor", foo="bar")
|
||||||
|
@@ -18,7 +18,6 @@ from oslo_config import cfg
|
|||||||
from rally.plugins.openstack.scenarios.ec2 import utils
|
from rally.plugins.openstack.scenarios.ec2 import utils
|
||||||
from tests.unit import test
|
from tests.unit import test
|
||||||
|
|
||||||
EC2_UTILS = "rally.plugins.openstack.scenarios.ec2.utils"
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
|
||||||
@@ -26,27 +25,39 @@ class EC2ScenarioTestCase(test.ScenarioTestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(EC2ScenarioTestCase, self).setUp()
|
super(EC2ScenarioTestCase, self).setUp()
|
||||||
self.server = mock.MagicMock()
|
self.server1 = mock.MagicMock()
|
||||||
self.reservation = mock.MagicMock(instances=[self.server])
|
self.server2 = mock.MagicMock()
|
||||||
|
self.reservations = mock.MagicMock(instances=[self.server1,
|
||||||
def test__boot_server(self):
|
self.server2])
|
||||||
self.clients("ec2").run_instances.return_value = self.reservation
|
|
||||||
ec2_scenario = utils.EC2Scenario(context={})
|
|
||||||
ec2_scenario._update_resource = mock.Mock()
|
|
||||||
return_server = ec2_scenario._boot_server("image", "flavor")
|
|
||||||
self.mock_wait_for.mock.assert_called_once_with(
|
|
||||||
self.server,
|
|
||||||
is_ready=self.mock_resource_is.mock.return_value,
|
|
||||||
update_resource=ec2_scenario._update_resource,
|
|
||||||
check_interval=CONF.benchmark.ec2_server_boot_poll_interval,
|
|
||||||
timeout=CONF.benchmark.ec2_server_boot_timeout)
|
|
||||||
self.mock_resource_is.mock.assert_called_once_with("RUNNING")
|
|
||||||
self.assertEqual(self.mock_wait_for.mock.return_value, return_server)
|
|
||||||
self._test_atomic_action_timer(ec2_scenario.atomic_actions(),
|
|
||||||
"ec2.boot_server")
|
|
||||||
|
|
||||||
def test__update_resource(self):
|
def test__update_resource(self):
|
||||||
resource = mock.MagicMock()
|
resource = mock.MagicMock()
|
||||||
scenario = utils.EC2Scenario()
|
scenario = utils.EC2Scenario()
|
||||||
self.assertEqual(scenario._update_resource(resource), resource)
|
self.assertEqual(scenario._update_resource(resource), resource)
|
||||||
resource.update.assert_called_once_with()
|
resource.update.assert_called_once_with()
|
||||||
|
|
||||||
|
def test__boot_servers(self):
|
||||||
|
self.clients("ec2").run_instances.return_value = self.reservations
|
||||||
|
ec2_scenario = utils.EC2Scenario(context={})
|
||||||
|
ec2_scenario._update_resource = mock.Mock()
|
||||||
|
ec2_scenario._boot_servers("image", "flavor", 2)
|
||||||
|
expected = [
|
||||||
|
mock.call(
|
||||||
|
self.server1,
|
||||||
|
is_ready=self.mock_resource_is.mock.return_value,
|
||||||
|
update_resource=ec2_scenario._update_resource,
|
||||||
|
check_interval=CONF.benchmark.ec2_server_boot_poll_interval,
|
||||||
|
timeout=CONF.benchmark.ec2_server_boot_timeout
|
||||||
|
),
|
||||||
|
mock.call(
|
||||||
|
self.server2,
|
||||||
|
is_ready=self.mock_resource_is.mock.return_value,
|
||||||
|
update_resource=ec2_scenario._update_resource,
|
||||||
|
check_interval=CONF.benchmark.ec2_server_boot_poll_interval,
|
||||||
|
timeout=CONF.benchmark.ec2_server_boot_timeout
|
||||||
|
)
|
||||||
|
]
|
||||||
|
self.mock_wait_for.mock.assert_has_calls(expected)
|
||||||
|
self.mock_resource_is.mock.assert_has_calls([mock.call("RUNNING")])
|
||||||
|
self._test_atomic_action_timer(ec2_scenario.atomic_actions(),
|
||||||
|
"ec2.boot_servers")
|
||||||
|
Reference in New Issue
Block a user