diff --git a/rally-jobs/rally-neutron-extensions.yaml b/rally-jobs/rally-neutron-extensions.yaml new file mode 100644 index 00000000..e5b431cb --- /dev/null +++ b/rally-jobs/rally-neutron-extensions.yaml @@ -0,0 +1,17 @@ +--- + NeutronLoadbalancerV2.create_and_list_loadbalancers: + - + args: + lb_create_args: {} + runner: + type: "constant" + times: 5 + concurrency: 2 + context: + users: + tenants: 2 + users_per_tenant: 2 + network: {} + sla: + failure_rate: + max: 0 diff --git a/rally/plugins/openstack/cleanup/resources.py b/rally/plugins/openstack/cleanup/resources.py old mode 100644 new mode 100755 index 0981510b..df27a538 --- a/rally/plugins/openstack/cleanup/resources.py +++ b/rally/plugins/openstack/cleanup/resources.py @@ -386,6 +386,27 @@ class NeutronV1Pool(NeutronLbaasV1Mixin): pass +class NeutronLbaasV2Mixin(NeutronMixin): + + def list(self): + if self.supports_extension("lbaasv2"): + return super(NeutronLbaasV2Mixin, self).list() + return [] + + +@base.resource("neutron", "loadbalancer", order=next(_neutron_order), + tenant_resource=True) +class NeutronV2Loadbalancer(NeutronLbaasV2Mixin): + + def is_deleted(self): + try: + self._manager().show_loadbalancer(self.id()) + except Exception as e: + return getattr(e, "status_code", 400) == 404 + + return False + + @base.resource("neutron", "port", order=next(_neutron_order), tenant_resource=True) class NeutronPort(NeutronMixin): diff --git a/rally/plugins/openstack/scenarios/neutron/loadbalancer_v2.py b/rally/plugins/openstack/scenarios/neutron/loadbalancer_v2.py new file mode 100755 index 00000000..08eb9630 --- /dev/null +++ b/rally/plugins/openstack/scenarios/neutron/loadbalancer_v2.py @@ -0,0 +1,47 @@ +# 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 import consts +from rally.plugins.openstack import scenario +from rally.plugins.openstack.scenarios.neutron import utils +from rally.task import validation + + +"""Scenarios for Neutron Loadbalancer v2.""" + + +@validation.required_neutron_extensions("lbaasv2") +@validation.required_services(consts.Service.NEUTRON) +@validation.required_openstack(users=True) +@validation.required_contexts("network") +@scenario.configure(context={"cleanup": ["neutron"]}, + name="NeutronLoadbalancerV2.create_and_list_loadbalancers") +class CreateAndListLoadbalancers(utils.NeutronScenario): + + def run(self, lb_create_args=None): + """Create a loadbalancer(v2) and then list loadbalancers(v2). + + Measure the "neutron lbaas-loadbalancer-list" command performance. + The scenario creates a loadbalancer for every subnet and then lists + loadbalancers. + + :param loadbalancer_create_args: dict, POST /lbaas/loadbalancers + request options + """ + lb_create_args = lb_create_args or {} + subnets = [] + networks = self.context.get("tenant", {}).get("networks", []) + for network in networks: + subnets.extend(network.get("subnets", [])) + for subnet_id in subnets: + self._create_lbaasv2_loadbalancer(subnet_id, **lb_create_args) + self._list_lbaasv2_loadbalancers() diff --git a/rally/plugins/openstack/scenarios/neutron/utils.py b/rally/plugins/openstack/scenarios/neutron/utils.py old mode 100644 new mode 100755 index ca1d2662..fab4def8 --- a/rally/plugins/openstack/scenarios/neutron/utils.py +++ b/rally/plugins/openstack/scenarios/neutron/utils.py @@ -15,12 +15,32 @@ import random +from oslo_config import cfg + from rally.common.i18n import _ from rally.common import logging from rally import exceptions from rally.plugins.openstack import scenario from rally.plugins.openstack.wrappers import network as network_wrapper from rally.task import atomic +from rally.task import utils + +NEUTRON_BENCHMARK_OPTS = [ + cfg.FloatOpt( + "neutron_create_loadbalancer_timeout", + default=float(500), + help="Neutron create loadbalancer timeout"), + cfg.FloatOpt( + "neutron_create_loadbalancer_poll_interval", + default=float(2), + help="Neutron create loadbalancer poll interval") +] + +CONF = cfg.CONF +benchmark_group = cfg.OptGroup(name="benchmark", + title="benchmark options") +CONF.register_group(benchmark_group) +CONF.register_opts(NEUTRON_BENCHMARK_OPTS, group=benchmark_group) LOG = logging.getLogger(__name__) @@ -588,3 +608,50 @@ class NeutronScenario(scenario.OpenStackScenario): body = {"security_group": security_group_update_args} return self.clients("neutron").update_security_group( security_group["security_group"]["id"], body) + + def update_loadbalancer_resource(self, lb): + try: + new_lb = self.clients("neutron").show_loadbalancer(lb["id"]) + except Exception as e: + if getattr(e, "status_code", 400) == 404: + raise exceptions.GetResourceNotFound(resource=lb) + raise exceptions.GetResourceFailure(resource=lb, err=e) + return new_lb["loadbalancer"] + + @atomic.optional_action_timer("neutron.create_lbaasv2_loadbalancer") + def _create_lbaasv2_loadbalancer(self, subnet_id, **lb_create_args): + """Create LB loadbalancer(v2) + + :param subnet_id: str, neutron subnet-id + :param lb_create_args: dict, POST /lbaas/loadbalancers request options + :param atomic_action: True if this is an atomic action. added + and handled by the + optional_action_timer() decorator + :returns: dict, neutron lb + """ + args = {"name": self.generate_random_name(), + "vip_subnet_id": subnet_id} + args.update(lb_create_args) + neutronclient = self.clients("neutron") + lb = neutronclient.create_loadbalancer({"loadbalancer": args}) + lb = lb["loadbalancer"] + lb = utils.wait_for_status( + lb, + ready_statuses=["ACTIVE"], + status_attr="provisioning_status", + update_resource=self.update_loadbalancer_resource, + timeout=CONF.benchmark.neutron_create_loadbalancer_timeout, + check_interval=( + CONF.benchmark.neutron_create_loadbalancer_poll_interval) + ) + return lb + + @atomic.action_timer("neutron.list_lbaasv2_loadbalancers") + def _list_lbaasv2_loadbalancers(self, retrieve_all=True, **lb_list_args): + """List LB loadbalancers(v2) + + :param lb_list_args: dict, POST /lbaas/loadbalancers request options + :returns: dict, neutron lb loadbalancers(v2) + """ + return self.clients("neutron").list_loadbalancers(retrieve_all, + **lb_list_args) diff --git a/samples/tasks/scenarios/neutron/create-and-list-loadbalancers.json b/samples/tasks/scenarios/neutron/create-and-list-loadbalancers.json new file mode 100644 index 00000000..2bf67baa --- /dev/null +++ b/samples/tasks/scenarios/neutron/create-and-list-loadbalancers.json @@ -0,0 +1,26 @@ +{ + "NeutronLoadbalancerV2.create_and_list_loadbalancers": [ + { + "args": { + "lb_create_args": {} + }, + "runner": { + "type": "constant", + "times": 5, + "concurrency": 2 + }, + "context": { + "users": { + "tenants": 2, + "users_per_tenant": 2 + }, + "network": {} + }, + "sla": { + "failure_rate": { + "max": 0 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/neutron/create-and-list-loadbalancers.yaml b/samples/tasks/scenarios/neutron/create-and-list-loadbalancers.yaml new file mode 100644 index 00000000..e5b431cb --- /dev/null +++ b/samples/tasks/scenarios/neutron/create-and-list-loadbalancers.yaml @@ -0,0 +1,17 @@ +--- + NeutronLoadbalancerV2.create_and_list_loadbalancers: + - + args: + lb_create_args: {} + runner: + type: "constant" + times: 5 + concurrency: 2 + context: + users: + tenants: 2 + users_per_tenant: 2 + network: {} + sla: + failure_rate: + max: 0 diff --git a/tests/unit/plugins/openstack/cleanup/test_resources.py b/tests/unit/plugins/openstack/cleanup/test_resources.py old mode 100644 new mode 100755 index ac772ce7..922b84ca --- a/tests/unit/plugins/openstack/cleanup/test_resources.py +++ b/tests/unit/plugins/openstack/cleanup/test_resources.py @@ -434,6 +434,79 @@ class NeutronLbaasV1MixinTestCase(test.TestCase): self.assertFalse(neut._manager().list_some_resources.called) +class NeutronLbaasV2MixinTestCase(test.TestCase): + + def get_neutron_lbaasv2_mixin(self, extensions=None): + if extensions is None: + extensions = [] + neut = resources.NeutronLbaasV2Mixin() + neut._service = "neutron" + neut._resource = "some_resource" + neut._manager = mock.Mock() + neut._manager().list_extensions.return_value = { + "extensions": [{"alias": ext} for ext in extensions] + } + return neut + + def test_list_lbaasv2_available(self): + neut = self.get_neutron_lbaasv2_mixin(extensions=["lbaasv2"]) + neut.tenant_uuid = "user_tenant" + + some_resources = [{"tenant_id": neut.tenant_uuid}, {"tenant_id": "a"}] + neut._manager().list_some_resources.return_value = { + "some_resources": some_resources + } + + self.assertEqual([some_resources[0]], list(neut.list())) + neut._manager().list_some_resources.assert_called_once_with( + tenant_id=neut.tenant_uuid) + + def test_list_lbaasv2_unavailable(self): + neut = self.get_neutron_lbaasv2_mixin() + + self.assertEqual([], list(neut.list())) + self.assertFalse(neut._manager().list_some_resources.called) + + +class NeutronV2LoadbalancerTestCase(test.TestCase): + + def get_neutron_lbaasv2_lb(self): + neutron_lb = resources.NeutronV2Loadbalancer() + neutron_lb.raw_resource = {"id": "1", "name": "s_rally"} + neutron_lb._manager = mock.Mock() + return neutron_lb + + def test_is_deleted_true(self): + from neutronclient.common import exceptions as n_exceptions + neutron_lb = self.get_neutron_lbaasv2_lb() + neutron_lb._manager().show_loadbalancer.side_effect = ( + n_exceptions.NotFound) + + self.assertTrue(neutron_lb.is_deleted()) + + neutron_lb._manager().show_loadbalancer.assert_called_once_with( + neutron_lb.id()) + + def test_is_deleted_false(self): + from neutronclient.common import exceptions as n_exceptions + neutron_lb = self.get_neutron_lbaasv2_lb() + neutron_lb._manager().show_loadbalancer.return_value = ( + neutron_lb.raw_resource) + + self.assertFalse(neutron_lb.is_deleted()) + neutron_lb._manager().show_loadbalancer.assert_called_once_with( + neutron_lb.id()) + + neutron_lb._manager().show_loadbalancer.reset_mock() + + neutron_lb._manager().show_loadbalancer.side_effect = ( + n_exceptions.Forbidden) + + self.assertFalse(neutron_lb.is_deleted()) + neutron_lb._manager().show_loadbalancer.assert_called_once_with( + neutron_lb.id()) + + class NeutronPortTestCase(test.TestCase): def test_delete(self): diff --git a/tests/unit/plugins/openstack/scenarios/neutron/test_loadbalancer_v2.py b/tests/unit/plugins/openstack/scenarios/neutron/test_loadbalancer_v2.py new file mode 100755 index 00000000..565f8595 --- /dev/null +++ b/tests/unit/plugins/openstack/scenarios/neutron/test_loadbalancer_v2.py @@ -0,0 +1,59 @@ +# 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 ddt +import mock + +from rally.plugins.openstack.scenarios.neutron import loadbalancer_v2 +from tests.unit import test + + +@ddt.ddt +class NeutronLoadbalancerv2TestCase(test.TestCase): + + def _get_context(self): + context = test.get_test_context() + context.update({ + "user": { + "id": "fake_user", + "tenant_id": "fake_tenant", + "credential": mock.MagicMock() + }, + "tenant": {"id": "fake_tenant", + "networks": [{"id": "fake_net", + "subnets": ["fake_subnet"]}]}}) + return context + + @ddt.data( + {}, + {"lb_create_args": None}, + {"lb_create_args": {}}, + {"lb_create_args": {"name": "given-name"}}, + ) + @ddt.unpack + def test_create_and_list_load_balancers(self, lb_create_args=None): + context = self._get_context() + scenario = loadbalancer_v2.CreateAndListLoadbalancers(context) + lb_create_args = lb_create_args or {} + networks = context["tenant"]["networks"] + scenario._create_lbaasv2_loadbalancer = mock.Mock() + scenario._list_lbaasv2_loadbalancers = mock.Mock() + scenario.run(lb_create_args=lb_create_args) + + subnets = [] + mock_has_calls = [] + for network in networks: + subnets.extend(network.get("subnets", [])) + for subnet in subnets: + mock_has_calls.append(mock.call(subnet, **lb_create_args)) + scenario._create_lbaasv2_loadbalancer.assert_has_calls(mock_has_calls) + scenario._list_lbaasv2_loadbalancers.assert_called_once_with() diff --git a/tests/unit/plugins/openstack/scenarios/neutron/test_utils.py b/tests/unit/plugins/openstack/scenarios/neutron/test_utils.py old mode 100644 new mode 100755 index 05f238f0..d41db8ec --- a/tests/unit/plugins/openstack/scenarios/neutron/test_utils.py +++ b/tests/unit/plugins/openstack/scenarios/neutron/test_utils.py @@ -20,7 +20,6 @@ from rally import exceptions from rally.plugins.openstack.scenarios.neutron import utils from tests.unit import test - NEUTRON_UTILS = "rally.plugins.openstack.scenarios.neutron.utils." @@ -880,6 +879,74 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.update_healthmonitor") + def test_update_loadbalancer_resource(self): + lb = {"id": "1", "provisioning_status": "READY"} + new_lb = {"id": "1", "provisioning_status": "ACTIVE"} + self.clients("neutron").show_loadbalancer.return_value = { + "loadbalancer": new_lb} + + return_lb = self.scenario.update_loadbalancer_resource(lb) + + self.clients("neutron").show_loadbalancer.assert_called_once_with( + lb["id"]) + self.assertEqual(new_lb, return_lb) + + def test_update_loadbalancer_resource_not_found(self): + from neutronclient.common import exceptions as n_exceptions + lb = {"id": "1", "provisioning_status": "READY"} + self.clients("neutron").show_loadbalancer.side_effect = ( + n_exceptions.NotFound) + + self.assertRaises(exceptions.GetResourceNotFound, + self.scenario.update_loadbalancer_resource, + lb) + self.clients("neutron").show_loadbalancer.assert_called_once_with( + lb["id"]) + + def test_update_loadbalancer_resource_failure(self): + from neutronclient.common import exceptions as n_exceptions + lb = {"id": "1", "provisioning_status": "READY"} + self.clients("neutron").show_loadbalancer.side_effect = ( + n_exceptions.Forbidden) + + self.assertRaises(exceptions.GetResourceFailure, + self.scenario.update_loadbalancer_resource, + lb) + self.clients("neutron").show_loadbalancer.assert_called_once_with( + lb["id"]) + + def test__create_lbaasv2_loadbalancer(self): + neutronclient = self.clients("neutron") + create_args = {"name": "s_rally", "vip_subnet_id": "1", + "fake": "fake"} + new_lb = {"id": "1", "provisioning_status": "ACTIVE"} + + self.scenario.generate_random_name = mock.Mock( + return_value="s_rally") + self.mock_wait_for_status.mock.return_value = new_lb + + return_lb = self.scenario._create_lbaasv2_loadbalancer( + "1", fake="fake") + + neutronclient.create_loadbalancer.assert_called_once_with( + {"loadbalancer": create_args}) + self.assertEqual(new_lb, return_lb) + self._test_atomic_action_timer(self.scenario.atomic_actions(), + "neutron.create_lbaasv2_loadbalancer") + + def test__list_lbaasv2_loadbalancers(self): + value = {"loadbalancer": [{"id": "1", "name": "s_rally"}]} + self.clients("neutron").list_loadbalancers.return_value = value + + return_value = self.scenario._list_lbaasv2_loadbalancers( + True, fake="fake") + + (self.clients("neutron").list_loadbalancers + .assert_called_once_with(True, fake="fake")) + self.assertEqual(value, return_value) + self._test_atomic_action_timer(self.scenario.atomic_actions(), + "neutron.list_lbaasv2_loadbalancers") + class NeutronScenarioFunctionalTestCase(test.FakeClientsScenarioTestCase):