From d701c6317a31e0b2a8cab821c7cc0a5921ca2664 Mon Sep 17 00:00:00 2001 From: yanyanhu Date: Thu, 11 Aug 2016 06:00:50 -0400 Subject: [PATCH] [senlin]Add context support for Senlin profile This patch adds context support for Senlin profile. The following changes were made: - Adding Senlin profile context; - Revising 'create_and_delete_profile_cluster' scenario to make it benchmark cluster creating/deleting only; - Revising rally-senlin test job and example jobs accordingly; - Minor fix SenlinMixin cleanup resource to make id query work correctly; - Removing admin requirement from profile util functions since profile creating/deleting doesn't need admin role. Partial-bp: add-support-for-clustering-service-senlin Change-Id: Ib74803d13f4f89152af3a1ff5e7b6ff9fbac2906 --- rally-jobs/rally-senlin.yaml | 20 ++--- rally/plugins/openstack/cleanup/resources.py | 8 +- .../openstack/context/senlin/__init__.py | 0 .../openstack/context/senlin/profiles.py | 76 +++++++++++++++++ .../openstack/scenarios/senlin/clusters.py | 20 ++--- .../openstack/scenarios/senlin/utils.py | 4 +- .../create-and-delete-profile-cluster.json | 28 +++---- .../create-and-delete-profile-cluster.yaml | 20 ++--- .../openstack/cleanup/test_resources.py | 7 +- .../openstack/context/senlin/__init__.py | 0 .../openstack/context/senlin/test_profiles.py | 84 +++++++++++++++++++ .../scenarios/senlin/test_clusters.py | 16 +--- .../openstack/scenarios/senlin/test_utils.py | 6 +- 13 files changed, 223 insertions(+), 66 deletions(-) create mode 100644 rally/plugins/openstack/context/senlin/__init__.py create mode 100644 rally/plugins/openstack/context/senlin/profiles.py create mode 100644 tests/unit/plugins/openstack/context/senlin/__init__.py create mode 100644 tests/unit/plugins/openstack/context/senlin/test_profiles.py diff --git a/rally-jobs/rally-senlin.yaml b/rally-jobs/rally-senlin.yaml index 698a46879d..f2a875d84e 100644 --- a/rally-jobs/rally-senlin.yaml +++ b/rally-jobs/rally-senlin.yaml @@ -1,16 +1,7 @@ --- - SenlinClusters.create_and_delete_profile_cluster: + SenlinClusters.create_and_delete_cluster: - args: - profile_spec: - type: os.nova.server - version: 1.0 - properties: - name: cirros_server - flavor: 1 - image: "cirros-0.3.4-x86_64-uec" - networks: - - network: private desired_capacity: 3 min_size: 0 max_size: 5 @@ -22,6 +13,15 @@ users: tenants: 2 users_per_tenant: 2 + profiles: + type: os.nova.server + version: "1.0" + properties: + name: cirros_server + flavor: 1 + image: "cirros-0.3.4-x86_64-uec" + networks: + - network: private sla: failure_rate: max: 0 diff --git a/rally/plugins/openstack/cleanup/resources.py b/rally/plugins/openstack/cleanup/resources.py index 9f76bb7c1e..fb43c33d7d 100644 --- a/rally/plugins/openstack/cleanup/resources.py +++ b/rally/plugins/openstack/cleanup/resources.py @@ -82,6 +82,9 @@ _senlin_order = get_order(150) @base.resource(service=None, resource=None, admin_required=True) class SenlinMixin(base.ResourceManager): + def id(self): + return self.raw_resource["id"] + def _manager(self): client = self._admin_required and self.admin or self.user return getattr(client, self._service)() @@ -92,7 +95,7 @@ class SenlinMixin(base.ResourceManager): def delete(self): # make singular form of resource name from plural form res_name = self._resource[:-1] - return getattr(self._manager(), "delete_%s" % res_name)(self.id) + return getattr(self._manager(), "delete_%s" % res_name)(self.id()) @base.resource("senlin", "clusters", order=next(_senlin_order)) @@ -100,7 +103,8 @@ class SenlinCluster(SenlinMixin): """Resource class for Senlin Cluster.""" -@base.resource("senlin", "profiles", order=next(_senlin_order)) +@base.resource("senlin", "profiles", order=next(_senlin_order), + admin_required=False, tenant_resource=True) class SenlinProfile(SenlinMixin): """Resource class for Senlin Profile.""" diff --git a/rally/plugins/openstack/context/senlin/__init__.py b/rally/plugins/openstack/context/senlin/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/rally/plugins/openstack/context/senlin/profiles.py b/rally/plugins/openstack/context/senlin/profiles.py new file mode 100644 index 0000000000..04225d1a58 --- /dev/null +++ b/rally/plugins/openstack/context/senlin/profiles.py @@ -0,0 +1,76 @@ +# 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 logging +from rally.common import utils as rutils +from rally import consts +from rally.plugins.openstack.scenarios.senlin import utils as senlin_utils +from rally.task import context + +LOG = logging.getLogger(__name__) + + +@context.configure(name="profiles", order=190) +class ProfilesGenerator(context.Context): + """Context creates a temporary profile for Senlin test.""" + + CONFIG_SCHEMA = { + "type": "object", + "$schema": consts.JSON_SCHEMA, + "properties": { + "type": { + "type": "string", + }, + "version": { + "type": "string", + }, + "properties": { + "type": "object", + } + }, + "additionalProperties": False, + "required": ["type", "version", "properties"] + } + + @logging.log_task_wrapper(LOG.info, _("Enter context: `Senlin profiles`")) + def setup(self): + """Create test profiles.""" + for user, tenant_id in rutils.iterate_per_tenants( + self.context["users"]): + + senlin_scenario = senlin_utils.SenlinScenario({ + "user": user, + "task": self.context["task"], + "config": { + "api_versions": self.context["config"].get( + "api_versions", [])} + }) + profile = senlin_scenario._create_profile(self.config) + + self.context["tenants"][tenant_id]["profile"] = profile.id + + @logging.log_task_wrapper(LOG.info, _("Exit context: `Senlin profiles`")) + def cleanup(self): + """Delete created test profiles.""" + for user, tenant_id in rutils.iterate_per_tenants( + self.context["users"]): + + senlin_scenario = senlin_utils.SenlinScenario({ + "user": user, + "task": self.context["task"], + "config": { + "api_versions": self.context["config"].get( + "api_versions", [])} + }) + senlin_scenario._delete_profile( + self.context["tenants"][tenant_id]["profile"]) diff --git a/rally/plugins/openstack/scenarios/senlin/clusters.py b/rally/plugins/openstack/scenarios/senlin/clusters.py index a848d272a9..7bc452af44 100644 --- a/rally/plugins/openstack/scenarios/senlin/clusters.py +++ b/rally/plugins/openstack/scenarios/senlin/clusters.py @@ -21,18 +21,15 @@ class SenlinClusters(utils.SenlinScenario): @validation.required_openstack(admin=True) @validation.required_services(consts.Service.SENLIN) + @validation.required_contexts("profiles") @scenario.configure(context={"cleanup": ["senlin"]}) - def create_and_delete_profile_cluster(self, profile_spec, - desired_capacity=0, min_size=0, - max_size=-1, timeout=3600, - metadata=None): - """Create a profile and a cluster and then delete them. + def create_and_delete_cluster(self, desired_capacity=0, min_size=0, + max_size=-1, timeout=3600, metadata=None): + """Create a cluster and then delete it. - Measure the "senlin profile-create", "senlin profile-delete", - "senlin cluster-create" and "senlin cluster-delete" commands - performance. + Measure the "senlin cluster-create" and "senlin cluster-delete" + commands performance. - :param profile_spec: spec dictionary used to create profile :param desired_capacity: The capacity or initial number of nodes owned by the cluster :param min_size: The minimum number of nodes owned by the cluster @@ -41,8 +38,7 @@ class SenlinClusters(utils.SenlinScenario): :param timeout: The timeout value in seconds for cluster creation :param metadata: A set of key value pairs to associate with the cluster """ - profile = self._create_profile(profile_spec) - cluster = self._create_cluster(profile.id, desired_capacity, + profile_id = self.context["tenant"]["profile"] + cluster = self._create_cluster(profile_id, desired_capacity, min_size, max_size, timeout, metadata) self._delete_cluster(cluster) - self._delete_profile(profile) diff --git a/rally/plugins/openstack/scenarios/senlin/utils.py b/rally/plugins/openstack/scenarios/senlin/utils.py index f8d9bacaf0..0bcbca6147 100644 --- a/rally/plugins/openstack/scenarios/senlin/utils.py +++ b/rally/plugins/openstack/scenarios/senlin/utils.py @@ -140,7 +140,7 @@ class SenlinScenario(scenario.OpenStackScenario): if metadata: attrs["metadata"] = metadata - return self.admin_clients("senlin").create_profile(**attrs) + return self.clients("senlin").create_profile(**attrs) @atomic.action_timer("senlin.delete_profile") def _delete_profile(self, profile): @@ -150,4 +150,4 @@ class SenlinScenario(scenario.OpenStackScenario): :param profile: profile object to be deleted """ - self.admin_clients("senlin").delete_profile(profile) + self.clients("senlin").delete_profile(profile) diff --git a/samples/tasks/scenarios/senlin/create-and-delete-profile-cluster.json b/samples/tasks/scenarios/senlin/create-and-delete-profile-cluster.json index 29ff697d3d..42ef298564 100644 --- a/samples/tasks/scenarios/senlin/create-and-delete-profile-cluster.json +++ b/samples/tasks/scenarios/senlin/create-and-delete-profile-cluster.json @@ -1,19 +1,7 @@ { - "SenlinClusters.create_and_delete_profile_cluster": [ + "SenlinClusters.create_and_delete_cluster": [ { "args": { - "profile_spec": { - "type": "os.nova.server", - "version": "1.0", - "properties": { - "name": "cirros_server", - "flavor": 1, - "image": "cirros-0.3.4-x86_64-uec", - "networks": [ - { "network": "private" } - ] - } - }, "desired_capacity": 3, "min_size": 0, "max_size": 5 @@ -27,7 +15,19 @@ "users": { "tenants": 1, "users_per_tenant": 1 - } + }, + "profiles": { + "type": "os.nova.server", + "version": "1.0", + "properties": { + "name": "cirros_server", + "flavor": 1, + "image": "cirros-0.3.4-x86_64-uec", + "networks": [ + { "network": "private" } + ] + } + } } } ] diff --git a/samples/tasks/scenarios/senlin/create-and-delete-profile-cluster.yaml b/samples/tasks/scenarios/senlin/create-and-delete-profile-cluster.yaml index 2db234f765..722d79926c 100644 --- a/samples/tasks/scenarios/senlin/create-and-delete-profile-cluster.yaml +++ b/samples/tasks/scenarios/senlin/create-and-delete-profile-cluster.yaml @@ -1,16 +1,7 @@ --- - SenlinClusters.create_and_delete_profile_cluster: + SenlinClusters.create_and_delete_cluster: - args: - profile_spec: - type: os.nova.server - version: "1.0" - properties: - name: cirros_server - flavor: 1 - image: "cirros-0.3.4-x86_64-uec" - networks: - - network: private desired_capacity: 3 min_size: 0 max_size: 5 @@ -22,3 +13,12 @@ users: tenants: 1 users_per_tenant: 1 + profiles: + type: os.nova.server + version: "1.0" + properties: + name: cirros_server + flavor: 1 + image: "cirros-0.3.4-x86_64-uec" + networks: + - network: private diff --git a/tests/unit/plugins/openstack/cleanup/test_resources.py b/tests/unit/plugins/openstack/cleanup/test_resources.py index 03114221d0..be8cd73573 100644 --- a/tests/unit/plugins/openstack/cleanup/test_resources.py +++ b/tests/unit/plugins/openstack/cleanup/test_resources.py @@ -758,6 +758,11 @@ class FuelEnvironmentTestCase(test.TestCase): class SenlinMixinTestCase(test.TestCase): + def test_id(self): + senlin = resources.SenlinMixin() + senlin.raw_resource = {"id": "TEST_ID"} + self.assertEqual("TEST_ID", senlin.id()) + def test__manager(self): senlin = resources.SenlinMixin() senlin._service = "senlin" @@ -781,7 +786,7 @@ class SenlinMixinTestCase(test.TestCase): senlin._service = "senlin" senlin.user = mock.MagicMock() senlin._resource = "some_resources" - senlin.id = "TEST_ID" + senlin.raw_resource = {"id": "TEST_ID"} senlin.user.senlin().delete_some_resource.return_value = None senlin.delete() diff --git a/tests/unit/plugins/openstack/context/senlin/__init__.py b/tests/unit/plugins/openstack/context/senlin/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/plugins/openstack/context/senlin/test_profiles.py b/tests/unit/plugins/openstack/context/senlin/test_profiles.py new file mode 100644 index 0000000000..4cbb7da8f1 --- /dev/null +++ b/tests/unit/plugins/openstack/context/senlin/test_profiles.py @@ -0,0 +1,84 @@ +# 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.plugins.openstack.context.senlin import profiles +from tests.unit import test + + +BASE_CTX = "rally.task.context" +CTX = "rally.plugins.openstack.context" +BASE_SCN = "rally.task.scenarios" +SCN = "rally.plugins.openstack.scenarios" + + +class ProfilesGeneratorTestCase(test.ScenarioTestCase): + """Generate tenants.""" + def _gen_tenants(self, count): + tenants = {} + for _id in range(count): + tenants[str(_id)] = {"id": str(_id)} + return tenants + + def setUp(self): + super(ProfilesGeneratorTestCase, self).setUp() + self.tenants_count = 2 + self.users_per_tenant = 3 + tenants = self._gen_tenants(self.tenants_count) + users = [] + for tenant in tenants: + for i in range(self.users_per_tenant): + users.append({"id": i, "tenant_id": tenant, + "credential": mock.MagicMock()}) + + self.context = { + "config": { + "users": { + "tenants": self.tenants_count, + "users_per_tenant": self.users_per_tenant + }, + "profiles": { + "type": "profile_type_name", + "version": "1.0", + "properties": {"k1": "v1", "k2": "v2"} + }, + }, + "users": users, + "tenants": tenants, + "task": mock.MagicMock() + } + + @mock.patch("%s.senlin.utils.SenlinScenario._create_profile" % SCN, + return_value=mock.MagicMock(id="TEST_PROFILE_ID")) + def test_setup(self, mock_senlin_scenario__create_profile): + profile_ctx = profiles.ProfilesGenerator(self.context) + profile_ctx.setup() + spec = self.context["config"]["profiles"] + + mock_calls = [mock.call(spec) for i in range(self.tenants_count)] + mock_senlin_scenario__create_profile.assert_has_calls(mock_calls) + + for tenant in self.context["tenants"]: + self.assertEqual("TEST_PROFILE_ID", + self.context["tenants"][tenant]["profile"]) + + @mock.patch("%s.senlin.utils.SenlinScenario._delete_profile" % SCN) + def test_cleanup(self, mock_senlin_scenario__delete_profile): + for tenant in self.context["tenants"]: + self.context["tenants"][tenant].update( + {"profile": "TEST_PROFILE_ID"}) + profile_ctx = profiles.ProfilesGenerator(self.context) + profile_ctx.cleanup() + mock_calls = [mock.call("TEST_PROFILE_ID") for i in range( + self.tenants_count)] + mock_senlin_scenario__delete_profile.assert_has_calls(mock_calls) diff --git a/tests/unit/plugins/openstack/scenarios/senlin/test_clusters.py b/tests/unit/plugins/openstack/scenarios/senlin/test_clusters.py index d3497cc7a4..f886c67687 100644 --- a/tests/unit/plugins/openstack/scenarios/senlin/test_clusters.py +++ b/tests/unit/plugins/openstack/scenarios/senlin/test_clusters.py @@ -19,25 +19,17 @@ from tests.unit import test class SenlinClustersTestCase(test.ScenarioTestCase): def test_create_and_delete_cluster(self): - profile_spec = {"k1": "v1"} - mock_profile = mock.Mock(id="fake_profile_id") mock_cluster = mock.Mock() + self.context["tenant"] = {"profile": "fake_profile_id"} scenario = clusters.SenlinClusters(self.context) scenario._create_cluster = mock.Mock(return_value=mock_cluster) - scenario._create_profile = mock.Mock(return_value=mock_profile) scenario._delete_cluster = mock.Mock() - scenario._delete_profile = mock.Mock() - scenario.create_and_delete_profile_cluster(profile_spec, - desired_capacity=1, - min_size=0, - max_size=3, - timeout=60, - metadata={"k2": "v2"}) + scenario.create_and_delete_cluster(desired_capacity=1, min_size=0, + max_size=3, timeout=60, + metadata={"k2": "v2"}) - scenario._create_profile.assert_called_once_with(profile_spec) scenario._create_cluster.assert_called_once_with("fake_profile_id", 1, 0, 3, 60, {"k2": "v2"}) scenario._delete_cluster.assert_called_once_with(mock_cluster) - scenario._delete_profile.assert_called_once_with(mock_profile) diff --git a/tests/unit/plugins/openstack/scenarios/senlin/test_utils.py b/tests/unit/plugins/openstack/scenarios/senlin/test_utils.py index c0f4005471..e03a5c290b 100644 --- a/tests/unit/plugins/openstack/scenarios/senlin/test_utils.py +++ b/tests/unit/plugins/openstack/scenarios/senlin/test_utils.py @@ -134,8 +134,8 @@ class SenlinScenarioTestCase(test.ScenarioTestCase): result = scenario._create_profile(test_spec, metadata={"k2": "v2"}) self.assertEqual( - self.admin_clients("senlin").create_profile.return_value, result) - self.admin_clients("senlin").create_profile.assert_called_once_with( + self.clients("senlin").create_profile.return_value, result) + self.clients("senlin").create_profile.assert_called_once_with( spec=test_spec, name="test_profile", metadata={"k2": "v2"}) mock_generate_random_name.assert_called_once_with() self._test_atomic_action_timer(scenario.atomic_actions(), @@ -146,7 +146,7 @@ class SenlinScenarioTestCase(test.ScenarioTestCase): scenario = utils.SenlinScenario(context=self.context) scenario._delete_profile(fake_profile) - self.admin_clients("senlin").delete_profile.assert_called_once_with( + self.clients("senlin").delete_profile.assert_called_once_with( fake_profile) self._test_atomic_action_timer(scenario.atomic_actions(), "senlin.delete_profile")