diff --git a/rally-jobs/neutron.yaml b/rally-jobs/neutron.yaml index 13799791..f88bd4c2 100644 --- a/rally-jobs/neutron.yaml +++ b/rally-jobs/neutron.yaml @@ -290,6 +290,29 @@ failure_rate: max: 0 + NeutronNetworks.associate_and_dissociate_floating_ips: + - + args: + floating_network: "public" + runner: + type: "constant" + times: {{smoke or 8}} + concurrency: {{smoke or 4}} + context: + users: + tenants: {{smoke or 2}} + users_per_tenant: {{smoke or 1}} + quotas: + neutron: + floatingip: -1 + network: -1 + port: -1 + router: -1 + subnet: -1 + sla: + failure_rate: + max: 0 + NeutronNetworks.create_and_list_routers: - args: diff --git a/rally_openstack/scenarios/neutron/network.py b/rally_openstack/scenarios/neutron/network.py index 7ec89b9b..d21baf4d 100644 --- a/rally_openstack/scenarios/neutron/network.py +++ b/rally_openstack/scenarios/neutron/network.py @@ -557,6 +557,61 @@ class CreateAndDeleteFloatingIps(utils.NeutronScenario): self._delete_floating_ip(floating_ip["floatingip"]) +@validation.add("required_services", + services=[consts.Service.NEUTRON]) +@validation.add("required_platform", platform="openstack", users=True) +@validation.add("external_network_exists", param_name="floating_network") +@scenario.configure( + context={"cleanup@openstack": ["neutron"]}, + name="NeutronNetworks.associate_and_dissociate_floating_ips", + platform="openstack") +class AssociateAndDissociateFloatingIps(utils.NeutronScenario): + + def run(self, floating_network=None): + """Associate and dissociate floating IPs. + + Measure the "openstack floating ip set" and + "openstack floating ip unset" commands performance. + Because of the prerequisites for "floating ip set/unset" we also + measure the performance of the following commands: + * "openstack network create" + * "openstack subnet create" + * "openstack port create" + * "openstack router create" + * "openstack router set --external-gateway" + * "openstack router add subnet" + + :param floating_network: str, external network for floating IP creation + """ + floating_ip = self._create_floatingip( + floating_network) + + private_network = self._create_network( + network_create_args={}) + subnet = self._create_subnet( + network=private_network, + subnet_create_args={}) + port = self._create_port( + network=private_network, + port_create_args={}) + + router = self._create_router( + router_create_args={}) + floating_network_id = self._get_network_id(floating_network) + self._add_gateway_router( + router, + {"network": {"id": floating_network_id}}) + self._add_interface_router( + subnet["subnet"], + router["router"]) + + self._associate_floating_ip( + floatingip=floating_ip["floatingip"], + port=port["port"]) + self._dissociate_floating_ip( + floatingip=floating_ip["floatingip"]) + + @validation.add("required_services", services=[consts.Service.NEUTRON]) @validation.add("required_platform", platform="openstack", users=True) diff --git a/rally_openstack/scenarios/neutron/utils.py b/rally_openstack/scenarios/neutron/utils.py index 5e7756c5..bd30afbf 100644 --- a/rally_openstack/scenarios/neutron/utils.py +++ b/rally_openstack/scenarios/neutron/utils.py @@ -410,16 +410,17 @@ class NeutronScenario(scenario.OpenStackScenario): router["id"], {"subnet_id": subnet["id"]}) @atomic.action_timer("neutron.add_gateway_router") - def _add_gateway_router(self, router, ext_net, enable_snat): + def _add_gateway_router(self, router, ext_net, enable_snat=None): """Set the external network gateway for a router. :param router: dict, neutron router :param ext_net: external network for the gateway - :param enable_snat: True if enable snat + :param enable_snat: True if enable snat, None to avoid update """ gw_info = {"network_id": ext_net["network"]["id"]} - if self._ext_gw_mode_enabled: - gw_info["enable_snat"] = enable_snat + if enable_snat is not None: + if self._ext_gw_mode_enabled: + gw_info["enable_snat"] = enable_snat self.clients("neutron").add_gateway_router( router["router"]["id"], gw_info) @@ -573,6 +574,29 @@ class NeutronScenario(scenario.OpenStackScenario): """ return self.clients("neutron").delete_floatingip(floating_ip["id"]) + @atomic.action_timer("neutron.associate_floating_ip") + def _associate_floating_ip(self, floatingip, port): + """Associate floating IP with port. + + :param floatingip: floating IP dict + :param port: port dict + :returns: updated floating IP dict + """ + return self.clients("neutron").update_floatingip( + floatingip["id"], + {"floatingip": {"port_id": port["id"]}})["floatingip"] + + @atomic.action_timer("neutron.dissociate_floating_ip") + def _dissociate_floating_ip(self, floatingip): + """Dissociate floating IP from ports. + + :param floatingip: floating IP dict + :returns: updated floating IP dict + """ + return self.clients("neutron").update_floatingip( + floatingip["id"], + {"floatingip": {"port_id": None}})["floatingip"] + @atomic.action_timer("neutron.create_healthmonitor") def _create_v1_healthmonitor(self, **healthmonitor_create_args): """Create LB healthmonitor. diff --git a/samples/tasks/scenarios/neutron/associate-and-dissociate-floating-ips.json b/samples/tasks/scenarios/neutron/associate-and-dissociate-floating-ips.json new file mode 100644 index 00000000..7768f203 --- /dev/null +++ b/samples/tasks/scenarios/neutron/associate-and-dissociate-floating-ips.json @@ -0,0 +1,34 @@ +{ + "NeutronNetworks.associate_and_dissociate_floating_ips": [ + { + "args": { + "floating_network": "public" + }, + "runner": { + "type": "constant", + "times": 10, + "concurrency": 5 + }, + "context": { + "users": { + "tenants": 2, + "users_per_tenant": 3 + }, + "quotas": { + "neutron": { + "floatingip": -1, + "network": -1, + "port": -1, + "router": -1, + "subnet": -1 + } + } + }, + "sla": { + "failure_rate": { + "max": 0 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/neutron/associate-and-dissociate-floating-ips.yaml b/samples/tasks/scenarios/neutron/associate-and-dissociate-floating-ips.yaml new file mode 100644 index 00000000..d0111902 --- /dev/null +++ b/samples/tasks/scenarios/neutron/associate-and-dissociate-floating-ips.yaml @@ -0,0 +1,23 @@ +--- + NeutronNetworks.associate_and_dissociate_floating_ips: + - + args: + floating_network: "public" + runner: + type: "constant" + times: 10 + concurrency: 5 + context: + users: + tenants: 2 + users_per_tenant: 3 + quotas: + neutron: + floatingip: -1 + network: -1 + port: -1 + router: -1 + subnet: -1 + sla: + failure_rate: + max: 0 diff --git a/tests/unit/scenarios/neutron/test_network.py b/tests/unit/scenarios/neutron/test_network.py index 5affc7ce..e9ea752f 100644 --- a/tests/unit/scenarios/neutron/test_network.py +++ b/tests/unit/scenarios/neutron/test_network.py @@ -550,6 +550,33 @@ class NeutronNetworksTestCase(test.ScenarioTestCase): scenario._delete_floating_ip.assert_called_once_with( scenario._create_floatingip.return_value["floatingip"]) + def test_associate_and_dissociate_floating_ips(self): + scenario = network.AssociateAndDissociateFloatingIps(self.context) + + fip = {"floatingip": {"id": "floating-ip-id"}} + subnet = {"subnet": {}} + port = {"port": {"id": "port-id"}} + router = {"router": {}} + + scenario._create_floatingip = mock.Mock(return_value=fip) + scenario._create_network = mock.Mock() + scenario._create_subnet = mock.Mock(return_value=subnet) + scenario._create_port = mock.Mock(return_value=port) + scenario._create_router = mock.Mock(return_value=router) + scenario._get_network_id = mock.Mock() + scenario._add_gateway_router = mock.Mock() + scenario._add_interface_router = mock.Mock() + + scenario._associate_floating_ip = mock.Mock() + scenario._dissociate_floating_ip = mock.Mock() + + scenario.run(floating_network="public") + + scenario._associate_floating_ip.assert_called_once_with( + floatingip=fip["floatingip"], port=port["port"]) + scenario._dissociate_floating_ip.assert_called_once_with( + floatingip=fip["floatingip"]) + @mock.patch("%s.DeleteSubnets._delete_subnet" % BASE) def test_delete_subnets(self, mock__delete_subnet): # do not guess what user will be used diff --git a/tests/unit/scenarios/neutron/test_utils.py b/tests/unit/scenarios/neutron/test_utils.py index 643752b1..62807ec3 100644 --- a/tests/unit/scenarios/neutron/test_utils.py +++ b/tests/unit/scenarios/neutron/test_utils.py @@ -413,6 +413,29 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.add_gateway_router") + def test_add_gateway_router_no_snat_update(self): + ext_net = { + "network": { + "name": "extnet-name", + "id": "extnet-id" + } + } + router = { + "router": { + "name": "router-name", + "id": "router-id" + } + } + gw_info = {"network_id": ext_net["network"]["id"]} + self.clients("neutron").list_extensions.return_value = { + "extensions": [{"alias": "ext-gw-mode"}]} + + self.scenario._add_gateway_router(router, ext_net) + self.clients("neutron").add_gateway_router.assert_called_once_with( + router["router"]["id"], gw_info) + self._test_atomic_action_timer(self.scenario.atomic_actions(), + "neutron.add_gateway_router") + def test_add_gateway_router_without_ext_gw_mode_extension(self): ext_net = { "network": { @@ -627,6 +650,23 @@ class NeutronScenarioTestCase(test.ScenarioTestCase): self._test_atomic_action_timer(self.scenario.atomic_actions(), "neutron.delete_floating_ip") + def test_associate_floating_ip(self): + fip = {"id": "fip-id"} + port = {"id": "port-id"} + self.scenario._associate_floating_ip(fip, port) + self.clients("neutron").update_floatingip.assert_called_once_with( + "fip-id", {"floatingip": {"port_id": "port-id"}}) + self._test_atomic_action_timer(self.scenario.atomic_actions(), + "neutron.associate_floating_ip") + + def test_dissociate_floating_ip(self): + fip = {"id": "fip-id"} + self.scenario._dissociate_floating_ip(fip) + self.clients("neutron").update_floatingip.assert_called_once_with( + "fip-id", {"floatingip": {"port_id": None}}) + self._test_atomic_action_timer(self.scenario.atomic_actions(), + "neutron.dissociate_floating_ip") + @ddt.data( {}, {"router_create_args": {"admin_state_up": False}},