diff --git a/rally/plugins/openstack/context/ec2/__init__.py b/rally/plugins/openstack/context/ec2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/rally/plugins/openstack/context/ec2/servers.py b/rally/plugins/openstack/context/ec2/servers.py new file mode 100644 index 00000000..ac8952f3 --- /dev/null +++ b/rally/plugins/openstack/context/ec2/servers.py @@ -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", [])) diff --git a/rally/plugins/openstack/scenarios/ec2/servers.py b/rally/plugins/openstack/scenarios/ec2/servers.py index 288ba775..0a5cd283 100644 --- a/rally/plugins/openstack/scenarios/ec2/servers.py +++ b/rally/plugins/openstack/scenarios/ec2/servers.py @@ -41,4 +41,4 @@ class EC2Servers(utils.EC2Scenario): :param flavor: flavor to be used to boot an instance :param kwargs: optional additional arguments for server creation """ - self._boot_server(image, flavor, **kwargs) + self._boot_servers(image, flavor, **kwargs) diff --git a/rally/plugins/openstack/scenarios/ec2/utils.py b/rally/plugins/openstack/scenarios/ec2/utils.py index f55da3ab..c36307bd 100644 --- a/rally/plugins/openstack/scenarios/ec2/utils.py +++ b/rally/plugins/openstack/scenarios/ec2/utils.py @@ -48,33 +48,38 @@ CONF.register_opts(EC2_BENCHMARK_OPTS, group=benchmark_group) class EC2Scenario(scenario.OpenStackScenario): """Base class for EC2 scenarios with basic atomic actions.""" - RESOURCE_NAME_PREFIX = "rally_ec2server_" - RESOURCE_NAME_LENGTH = 16 + @base.atomic_action_timer("ec2.boot_servers") + def _boot_servers(self, image_id, flavor_name, + instance_num=1, **kwargs): + """Boot multiple servers. - @base.atomic_action_timer("ec2.boot_server") - def _boot_server(self, image_id, flavor_name, **kwargs): - """Boot a server. - - Returns when the server is actually booted and in "Running" state. + Returns when all the servers are actually booted and are in the + "Running" state. :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 kwargs: other optional parameters to initialize the server - :returns: EC2 Server instance + :param instance_num: Number of instances to boot + :param kwargs: Other optional parameters to boot servers + + :returns: List of created server objects """ reservation = self.clients("ec2").run_instances( - image_id=image_id, instance_type=flavor_name, **kwargs) - server = reservation.instances[0] + image_id=image_id, + 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) - server = utils.wait_for( + servers = [utils.wait_for( server, is_ready=utils.resource_is("RUNNING"), update_resource=self._update_resource, timeout=CONF.benchmark.ec2_server_boot_timeout, check_interval=CONF.benchmark.ec2_server_boot_poll_interval - ) - return server + ) for server in servers] + return servers def _update_resource(self, resource): resource.update() diff --git a/tests/unit/plugins/openstack/context/ec2/__init__.py b/tests/unit/plugins/openstack/context/ec2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/plugins/openstack/context/ec2/test_servers.py b/tests/unit/plugins/openstack/context/ec2/test_servers.py new file mode 100644 index 00000000..2dfdfc68 --- /dev/null +++ b/tests/unit/plugins/openstack/context/ec2/test_servers.py @@ -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"]) diff --git a/tests/unit/plugins/openstack/scenarios/ec2/test_servers.py b/tests/unit/plugins/openstack/scenarios/ec2/test_servers.py index 2197a73b..87ab8258 100644 --- a/tests/unit/plugins/openstack/scenarios/ec2/test_servers.py +++ b/tests/unit/plugins/openstack/scenarios/ec2/test_servers.py @@ -22,7 +22,7 @@ class EC2ServersTestCase(test.ScenarioTestCase): def test_boot_server(self): 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.assert_called_once_with( + scenario._boot_servers.assert_called_once_with( "foo_image", "foo_flavor", foo="bar") diff --git a/tests/unit/plugins/openstack/scenarios/ec2/test_utils.py b/tests/unit/plugins/openstack/scenarios/ec2/test_utils.py index e2c3d71a..373f8cbb 100644 --- a/tests/unit/plugins/openstack/scenarios/ec2/test_utils.py +++ b/tests/unit/plugins/openstack/scenarios/ec2/test_utils.py @@ -18,7 +18,6 @@ from oslo_config import cfg from rally.plugins.openstack.scenarios.ec2 import utils from tests.unit import test -EC2_UTILS = "rally.plugins.openstack.scenarios.ec2.utils" CONF = cfg.CONF @@ -26,27 +25,39 @@ class EC2ScenarioTestCase(test.ScenarioTestCase): def setUp(self): super(EC2ScenarioTestCase, self).setUp() - self.server = mock.MagicMock() - self.reservation = mock.MagicMock(instances=[self.server]) - - def test__boot_server(self): - 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") + self.server1 = mock.MagicMock() + self.server2 = mock.MagicMock() + self.reservations = mock.MagicMock(instances=[self.server1, + self.server2]) def test__update_resource(self): resource = mock.MagicMock() scenario = utils.EC2Scenario() self.assertEqual(scenario._update_resource(resource), resource) 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")