diff --git a/rally-jobs/rally-neutron.yaml b/rally-jobs/rally-neutron.yaml index 3960ce69..0249d0c2 100644 --- a/rally-jobs/rally-neutron.yaml +++ b/rally-jobs/rally-neutron.yaml @@ -91,6 +91,28 @@ failure_rate: max: 20 + NeutronLoadbalancerV1.create_and_list_pools: + - + args: + pool_create_args: {} + runner: + type: "constant" + times: 20 + concurrency: 10 + context: + users: + tenants: 1 + users_per_tenant: 1 + network: {} + quotas: + neutron: + network: -1 + subnet: -1 + pool: -1 + sla: + failure_rate: + max: 0 + NeutronNetworks.create_and_update_networks: - args: diff --git a/rally/plugins/openstack/context/quotas/neutron_quotas.py b/rally/plugins/openstack/context/quotas/neutron_quotas.py index 67728099..ff91bc0a 100644 --- a/rally/plugins/openstack/context/quotas/neutron_quotas.py +++ b/rally/plugins/openstack/context/quotas/neutron_quotas.py @@ -52,6 +52,10 @@ class NeutronQuotas(object): "security_group_rule": { "type": "integer", "minimum": -1 + }, + "pool": { + "type": "integer", + "minimum": -1 } } } diff --git a/rally/plugins/openstack/scenarios/neutron/loadbalancer_v1.py b/rally/plugins/openstack/scenarios/neutron/loadbalancer_v1.py new file mode 100644 index 00000000..a0de8426 --- /dev/null +++ b/rally/plugins/openstack/scenarios/neutron/loadbalancer_v1.py @@ -0,0 +1,38 @@ +# 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.benchmark.scenarios import base +from rally.benchmark import validation +from rally import consts +from rally.plugins.openstack.scenarios.neutron import utils + + +class NeutronLoadbalancerV1(utils.NeutronScenario): + """Benchmark scenarios for Neutron Loadbalancer v1.""" + + @validation.restricted_parameters("subnet_id", subdict="pool_create_args") + @validation.required_services(consts.Service.NEUTRON) + @validation.required_openstack(users=True) + @validation.required_contexts("network") + @base.scenario(context={"cleanup": ["neutron"]}) + def create_and_list_pools(self, pool_create_args=None): + """Create a pool(v1) and then list pools(v1). + + Measure the "neutron lb-pool-list" command performance. + The scenario creates a pool for every subnet and then lists pools. + + :param pool_create_args: dict, POST /lb/pools request options + """ + for net in self.context.get("tenant", {}).get("networks", []): + for subnet_id in net["subnets"]: + self._create_v1_pool(subnet_id, **pool_create_args) + self._list_v1_pools() diff --git a/rally/plugins/openstack/scenarios/neutron/utils.py b/rally/plugins/openstack/scenarios/neutron/utils.py index 0fe7fab7..ef5f5951 100644 --- a/rally/plugins/openstack/scenarios/neutron/utils.py +++ b/rally/plugins/openstack/scenarios/neutron/utils.py @@ -28,6 +28,9 @@ class NeutronScenario(base.Scenario): RESOURCE_NAME_PREFIX = "rally_net_" RESOURCE_NAME_LENGTH = 16 SUBNET_IP_VERSION = 4 + # TODO(rkiran): modify in case LBaaS-v2 requires + LB_METHOD = "ROUND_ROBIN" + LB_PROTOCOL = "HTTP" @base.atomic_action_timer("neutron.create_network") def _create_network(self, network_create_args): @@ -287,3 +290,22 @@ class NeutronScenario(base.Scenario): """ self.clients("neutron").remove_interface_router( router["id"], {"subnet_id": subnet["id"]}) + + @base.atomic_action_timer("neutron.create_pool") + def _create_v1_pool(self, subnet_id, **pool_create_args): + """Create pool(v1) + + :parm subnet_id: str, neutron subnet-id + :parm pool_create_args: dict, POST /lb/pools request options + :returns: obj, neutron lb pool + """ + args = {"lb_method": self.LB_METHOD, "protocol": self.LB_PROTOCOL, + "name": self._generate_random_name("rally_pool_"), + "subnet_id": subnet_id} + args.update(pool_create_args) + return self.clients("neutron").create_pool({"pool": args}) + + @base.atomic_action_timer("neutron.list_pools") + def _list_v1_pools(self, **kwargs): + """Return user lb pool list(v1).""" + return self.clients("neutron").list_pools() diff --git a/tests/unit/fakes.py b/tests/unit/fakes.py index 779eebbe..d88d1a11 100644 --- a/tests/unit/fakes.py +++ b/tests/unit/fakes.py @@ -1064,6 +1064,7 @@ class FakeNeutronClient(object): self.__subnets = {} self.__routers = {} self.__ports = {} + self.__pools = {} self.__tenant_id = kwargs.get("tenant_id", generate_uuid()) self.format = "json" @@ -1112,6 +1113,21 @@ class FakeNeutronClient(object): self.__networks[network_id] = network return {"network": network} + def create_pool(self, data): + pool = setup_dict(data["pool"], + required=["lb_method", "protocol", "subnet_id"], + defaults={"name": generate_name("pool_"), + "admin_state_up": True}) + if pool["subnet_id"] not in self.__subnets: + raise neutron_exceptions.NeutronClientException + pool_id = generate_uuid() + + pool.update({"id": pool_id, + "status": "PENDING_CREATE", + "tenant_id": self.__tenant_id}) + self.__pools[pool_id] = pool + return {"pool": pool} + def create_port(self, data): port = setup_dict(data["port"], required=["network_id"], @@ -1238,6 +1254,10 @@ class FakeNeutronClient(object): nets = self._filter(self.__networks.values(), search_opts) return {"networks": nets} + def list_pools(self, **search_opts): + pools = self._filter(self.__pools.values(), search_opts) + return {"pools": pools} + def list_ports(self, **search_opts): ports = self._filter(self.__ports.values(), search_opts) return {"ports": ports} diff --git a/tests/unit/plugins/openstack/scenarios/neutron/test_loadbalancer_v1.py b/tests/unit/plugins/openstack/scenarios/neutron/test_loadbalancer_v1.py new file mode 100644 index 00000000..c88997bb --- /dev/null +++ b/tests/unit/plugins/openstack/scenarios/neutron/test_loadbalancer_v1.py @@ -0,0 +1,45 @@ +# 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.scenarios.neutron import loadbalancer_v1 +from tests.unit import test + + +class NeutronLoadbalancerv1TestCase(test.TestCase): + + def _get_context(self): + return { + "user": {"id": "fake_user", "tenant_id": "fake_tenant"}, + "tenant": {"id": "fake_tenant", + "networks": [{"id": "fake_net", + "subnets": ["fake_subnet"]}]}} + + def _validate_scenario(self, pool_create_args): + neutron_scenario = loadbalancer_v1.NeutronLoadbalancerV1( + self._get_context()) + neutron_scenario._create_v1_pool = mock.Mock() + neutron_scenario._list_v1_pools = mock.Mock() + neutron_scenario.create_and_list_pools( + pool_create_args=pool_create_args) + for net in self._get_context()["tenant"]["networks"]: + for subnet_id in net["subnets"]: + neutron_scenario._create_v1_pool.assert_called_once_with( + subnet_id, **pool_create_args) + neutron_scenario._list_v1_pools.assert_called_once_with() + + def test_create_and_list_pools_default(self): + self._validate_scenario(pool_create_args={}) + + def test_create_and_list_pools_explicit(self): + self._validate_scenario(pool_create_args={"name": "given-name"}) diff --git a/tests/unit/plugins/openstack/scenarios/neutron/test_utils.py b/tests/unit/plugins/openstack/scenarios/neutron/test_utils.py index 4ab5449e..032bdcb7 100644 --- a/tests/unit/plugins/openstack/scenarios/neutron/test_utils.py +++ b/tests/unit/plugins/openstack/scenarios/neutron/test_utils.py @@ -391,6 +391,56 @@ class NeutronScenarioTestCase(test.ClientsTestCase): {"allocation_pools": []}, "10.10.10.0/24")] * subnets_per_network) + def test_create_v1_pool_explicit(self): + neutron_scenario = utils.NeutronScenario() + lb_method = "LEAST_CONNECTIONS" + subnet = "fake-id" + pool = mock.Mock() + self.clients("neutron").create_pool.return_value = pool + # Explicit options + pool_data = {"lb_method": lb_method, "name": "explicit-name"} + args = {"lb_method": "ROUND_ROBIN", "protocol": "HTTP", + "name": "random_name", "subnet_id": subnet} + args.update(pool_data) + expected_pool_data = {"pool": args} + pool = neutron_scenario._create_v1_pool( + subnet_id=subnet, **pool_data) + self.clients("neutron").create_pool.assert_called_once_with( + expected_pool_data) + self._test_atomic_action_timer( + neutron_scenario.atomic_actions(), "neutron.create_pool") + + @mock.patch(NEUTRON_UTILS + "NeutronScenario._generate_random_name") + def test_create_v1_pool_default(self, mock_random_name): + neutron_scenario = utils.NeutronScenario() + random_name = "random_name" + subnet = "fake-id" + pool = mock.Mock() + self.clients("neutron").create_pool.return_value = pool + mock_random_name.return_value = random_name + # Random pool name + pool_data = {} + args = {"lb_method": "ROUND_ROBIN", "protocol": "HTTP", + "name": "random_name", "subnet_id": subnet} + args.update(pool_data) + expected_pool_data = {"pool": args} + pool = neutron_scenario._create_v1_pool( + subnet_id=subnet, **pool_data) + self.clients("neutron").create_pool.assert_called_once_with( + expected_pool_data) + self._test_atomic_action_timer( + neutron_scenario.atomic_actions(), "neutron.create_pool") + + def test_list_v1_pools(self): + scenario = utils.NeutronScenario() + pools_list = [] + pools_dict = {"pools": pools_list} + self.clients("neutron").list_pools.return_value = pools_dict + return_pools_dict = scenario._list_v1_pools() + self.assertEqual(pools_dict, return_pools_dict) + self._test_atomic_action_timer(scenario.atomic_actions(), + "neutron.list_pools") + class NeutronScenarioFunctionalTestCase(test.FakeClientsTestCase):