From f62456a221c87b2a5df296e9b1510d734028d3ca Mon Sep 17 00:00:00 2001 From: Andrey Kurilin Date: Tue, 6 Mar 2018 20:50:25 +0200 Subject: [PATCH] Rework novaclient floating ips actions Methods for association floating ips and dissociation were deprecated in novaclient a year ago and latest major release (python-novaclient 10) doesn't include them[*]. These actions should be performed via neutronclient now. It requires more work, but there is no other options. As well, we can turn neutron job voting. [*] https://github.com/openstack/python-novaclient/blob/master/releasenotes/notes/remove-virt-interfaces-add-rm-fixed-floating-398c905d9c91cca8.yaml Change-Id: I6c019fe84fb3d6dfcef5dfadbbccff01bde4a102 --- .zuul.d/zuul.yaml | 5 +- rally_openstack/scenarios/nova/utils.py | 67 ++++++++++-- rally_openstack/scenarios/vm/utils.py | 4 +- tests/unit/scenarios/nova/test_utils.py | 138 +++++++++++++++++++----- tests/unit/scenarios/vm/test_utils.py | 12 +-- 5 files changed, 182 insertions(+), 44 deletions(-) diff --git a/.zuul.d/zuul.yaml b/.zuul.d/zuul.yaml index 547c2e8b..a5efecb1 100644 --- a/.zuul.d/zuul.yaml +++ b/.zuul.d/zuul.yaml @@ -57,9 +57,7 @@ - rally-task-murano: # some workloads began fail. need more time to investigate voting: false - - rally-task-neutron: - # should fail due to novaclient release - voting: false + - rally-task-neutron - rally-task-neutron-with-extensions: # more services should be enabled voting: false @@ -83,5 +81,6 @@ - rally-task-ironic - rally-task-keystone-glance-swift - rally-task-mistral + - rally-task-neutron - rally-task-telemetry - rally-task-zaqar diff --git a/rally_openstack/scenarios/nova/utils.py b/rally_openstack/scenarios/nova/utils.py index 8d81f42c..75ea7f07 100644 --- a/rally_openstack/scenarios/nova/utils.py +++ b/rally_openstack/scenarios/nova/utils.py @@ -608,13 +608,42 @@ class NovaScenario(scenario.OpenStackScenario): """Add floating IP to an instance :param server: The :class:`Server` to add an IP to. - :param address: The ip address or FloatingIP to add to the instance + :param address: The dict-like representation of FloatingIP to add + to the instance :param fixed_address: The fixedIP address the FloatingIP is to be associated with (optional) """ - server.add_floating_ip(address, fixed_address=fixed_address) + with atomic.ActionTimer(self, "neutron.list_ports"): + ports = self.clients("neutron").list_ports(device_id=server.id) + port = ports["ports"][0] + + fip = address + if not isinstance(address, dict): + LOG.warning( + "The argument 'address' of " + "NovaScenario._associate_floating_ip method accepts a " + "dict-like representation of floating ip. Transmitting a " + "string with just an IP is deprecated.") + with atomic.ActionTimer(self, "neutron.list_floating_ips"): + all_fips = self.clients("neutron").list_floatingips( + tenant_id=self.context["tenant"]["id"]) + filtered_fip = [f for f in all_fips["floatingips"] + if f["floating_ip_address"] == address] + if not filtered_fip: + raise exceptions.NotFoundException( + "There is no floating ip with '%s' address." % address) + fip = filtered_fip[0] + # the first case: fip object is returned from network wrapper + # the second case: from neutronclient directly + fip_ip = fip.get("ip", fip.get("floating_ip_address", None)) + fip_update_dict = {"port_id": port["id"]} + if fixed_address: + fip_update_dict["fixed_ip_address"] = fixed_address + self.clients("neutron").update_floatingip( + fip["id"], {"floatingip": fip_update_dict} + ) utils.wait_for(server, - is_ready=self.check_ip_address(address), + is_ready=self.check_ip_address(fip_ip), update_resource=utils.get_from_manager()) # Update server data server.addresses = server.manager.get(server.id).addresses @@ -624,12 +653,34 @@ class NovaScenario(scenario.OpenStackScenario): """Remove floating IP from an instance :param server: The :class:`Server` to add an IP to. - :param address: The ip address or FloatingIP to remove + :param address: The dict-like representation of FloatingIP to remove """ - server.remove_floating_ip(address) + fip = address + if not isinstance(fip, dict): + LOG.warning( + "The argument 'address' of " + "NovaScenario._dissociate_floating_ip method accepts a " + "dict-like representation of floating ip. Transmitting a " + "string with just an IP is deprecated.") + with atomic.ActionTimer(self, "neutron.list_floating_ips"): + all_fips = self.clients("neutron").list_floatingips( + tenant_id=self.context["tenant"]["id"] + ) + filtered_fip = [f for f in all_fips["floatingips"] + if f["floating_ip_address"] == address] + if not filtered_fip: + raise exceptions.NotFoundException( + "There is no floating ip with '%s' address." % address) + fip = filtered_fip[0] + self.clients("neutron").update_floatingip( + fip["id"], {"floatingip": {"port_id": None}} + ) + # the first case: fip object is returned from network wrapper + # the second case: from neutronclient directly + fip_ip = fip.get("ip", fip.get("floating_ip_address", None)) utils.wait_for( server, - is_ready=self.check_ip_address(address, must_exist=False), + is_ready=self.check_ip_address(fip_ip, must_exist=False), update_resource=utils.get_from_manager() ) # Update server data @@ -642,8 +693,8 @@ class NovaScenario(scenario.OpenStackScenario): def _check_addr(resource): for network, addr_list in resource.addresses.items(): for addr in addr_list: - if ip_to_check == addr["addr"]: - return must_exist + if ip_to_check == addr["addr"]: + return must_exist return not must_exist return _check_addr diff --git a/rally_openstack/scenarios/vm/utils.py b/rally_openstack/scenarios/vm/utils.py index 9d37d7f8..f0bf7702 100644 --- a/rally_openstack/scenarios/vm/utils.py +++ b/rally_openstack/scenarios/vm/utils.py @@ -170,7 +170,7 @@ class VMScenario(nova_utils.NovaScenario): ext_network=floating_network, tenant_id=server.tenant_id, fixed_ip=fixed_ip) - self._associate_floating_ip(server, fip["ip"], fixed_address=fixed_ip) + self._associate_floating_ip(server, fip, fixed_address=fixed_ip) return fip @@ -179,7 +179,7 @@ class VMScenario(nova_utils.NovaScenario): with logging.ExceptionLogger( LOG, "Unable to delete IP: %s" % fip["ip"]): if self.check_ip_address(fip["ip"])(server): - self._dissociate_floating_ip(server, fip["ip"]) + self._dissociate_floating_ip(server, fip) with atomic.ActionTimer(self, "neutron.delete_floating_ip"): network_wrapper.wrap(self.clients, self).delete_floating_ip( diff --git a/tests/unit/scenarios/nova/test_utils.py b/tests/unit/scenarios/nova/test_utils.py index 4211dc5d..eeb8d0fc 100644 --- a/tests/unit/scenarios/nova/test_utils.py +++ b/tests/unit/scenarios/nova/test_utils.py @@ -38,12 +38,8 @@ class NovaScenarioTestCase(test.ScenarioTestCase): self.floating_ip = mock.Mock() self.image = mock.Mock() self.context.update( - {"user": {"id": "fake_user_id", "credential": mock.MagicMock()}}) - - def _context_with_networks(self, networks): - retval = {"tenant": {"networks": networks}} - retval.update(self.context) - return retval + {"user": {"id": "fake_user_id", "credential": mock.MagicMock()}, + "tenant": {"id": "fake_tenant"}}) def _context_with_secgroup(self, secgroup): retval = {"user": {"secgroup": secgroup, @@ -140,9 +136,12 @@ class NovaScenarioTestCase(test.ScenarioTestCase): "nova.boot_server") def test__boot_server_with_network_exception(self): + self.context.update({"tenant": {"networks": None}}) + self.clients("nova").servers.create.return_value = self.server + nova_scenario = utils.NovaScenario( - context=self._context_with_networks(None)) + context=self.context) self.assertRaises(TypeError, nova_scenario._boot_server, "image_id", "flavor_id", auto_assign_nic=True) @@ -506,32 +505,121 @@ class NovaScenarioTestCase(test.ScenarioTestCase): def test__associate_floating_ip(self): nova_scenario = utils.NovaScenario(context=self.context) - nova_scenario._associate_floating_ip(self.server, self.floating_ip) - self.server.add_floating_ip.assert_called_once_with(self.floating_ip, - fixed_address=None) + neutronclient = nova_scenario.clients("neutron") + neutronclient.list_ports.return_value = {"ports": [{"id": "p1"}, + {"id": "p2"}]} + + fip_ip = "172.168.0.1" + fip_id = "some" + # case #1- an object from neutronclient + floating_ip = {"floating_ip_address": fip_ip, "id": fip_id} + + nova_scenario._associate_floating_ip(self.server, floating_ip) + + neutronclient.update_floatingip.assert_called_once_with( + fip_id, {"floatingip": {"port_id": "p1"}} + ) + # case #2 - an object from network wrapper + neutronclient.update_floatingip.reset_mock() + floating_ip = {"ip": fip_ip, "id": fip_id} + + nova_scenario._associate_floating_ip(self.server, floating_ip) + + neutronclient.update_floatingip.assert_called_once_with( + fip_id, {"floatingip": {"port_id": "p1"}} + ) + + # these should not be called in both cases + self.assertFalse(neutronclient.list_floatingips.called) + # it is an old behavior. let's check that it was not called + self.assertFalse(self.server.add_floating_ip.called) + + self._test_atomic_action_timer(nova_scenario.atomic_actions(), + "nova.associate_floating_ip", count=2) + + def test__associate_floating_ip_deprecated_behavior(self): + nova_scenario = utils.NovaScenario(context=self.context) + neutronclient = nova_scenario.clients("neutron") + neutronclient.list_ports.return_value = {"ports": [{"id": "p1"}, + {"id": "p2"}]} + + fip_id = "fip1" + fip_ip = "172.168.0.1" + neutronclient.list_floatingips.return_value = { + "floatingips": [ + {"id": fip_id, "floating_ip_address": fip_ip}, + {"id": "fip2", "floating_ip_address": "127.0.0.1"}]} + + nova_scenario._associate_floating_ip(self.server, fip_ip) + + neutronclient.update_floatingip.assert_called_once_with( + fip_id, {"floatingip": {"port_id": "p1"}} + ) + + neutronclient.list_floatingips.assert_called_once_with( + tenant_id="fake_tenant") + + # it is an old behavior. let's check that it was not called + self.assertFalse(self.server.add_floating_ip.called) + self._test_atomic_action_timer(nova_scenario.atomic_actions(), "nova.associate_floating_ip") - def test__associate_floating_ip_with_no_atomic_action(self): - nova_scenario = utils.NovaScenario(context=self.context) - nova_scenario._associate_floating_ip(self.server, self.floating_ip) - self.server.add_floating_ip.assert_called_once_with(self.floating_ip, - fixed_address=None) - def test__dissociate_floating_ip(self): nova_scenario = utils.NovaScenario(context=self.context) - nova_scenario._dissociate_floating_ip(self.server, self.floating_ip) - self.server.remove_floating_ip.assert_called_once_with( - self.floating_ip) + neutronclient = nova_scenario.clients("neutron") + + fip_ip = "172.168.0.1" + fip_id = "some" + # case #1- an object from neutronclient + floating_ip = {"floating_ip_address": fip_ip, "id": fip_id} + + nova_scenario._dissociate_floating_ip(self.server, floating_ip) + + neutronclient.update_floatingip.assert_called_once_with( + fip_id, {"floatingip": {"port_id": None}} + ) + # case #2 - an object from network wrapper + neutronclient.update_floatingip.reset_mock() + floating_ip = {"ip": fip_ip, "id": fip_id} + + nova_scenario._dissociate_floating_ip(self.server, floating_ip) + + neutronclient.update_floatingip.assert_called_once_with( + fip_id, {"floatingip": {"port_id": None}} + ) + + # these should not be called in both cases + self.assertFalse(neutronclient.list_floatingips.called) + # it is an old behavior. let's check that it was not called + self.assertFalse(self.server.add_floating_ip.called) + + self._test_atomic_action_timer(nova_scenario.atomic_actions(), + "nova.dissociate_floating_ip", count=2) + + def test__disassociate_floating_ip_deprecated_behavior(self): + nova_scenario = utils.NovaScenario(context=self.context) + neutronclient = nova_scenario.clients("neutron") + + fip_id = "fip1" + fip_ip = "172.168.0.1" + neutronclient.list_floatingips.return_value = { + "floatingips": [ + {"id": fip_id, "floating_ip_address": fip_ip}, + {"id": "fip2", "floating_ip_address": "127.0.0.1"}]} + + nova_scenario._dissociate_floating_ip(self.server, fip_ip) + + neutronclient.update_floatingip.assert_called_once_with( + fip_id, {"floatingip": {"port_id": None}} + ) + + neutronclient.list_floatingips.assert_called_once_with( + tenant_id="fake_tenant") + self._test_atomic_action_timer(nova_scenario.atomic_actions(), "nova.dissociate_floating_ip") - def test__dissociate_floating_ip_with_no_atomic_action(self): - nova_scenario = utils.NovaScenario(context=self.context) - nova_scenario._dissociate_floating_ip(self.server, self.floating_ip) - self.server.remove_floating_ip.assert_called_once_with( - self.floating_ip) - def test__check_ip_address(self): nova_scenario = utils.NovaScenario(context=self.context) fake_server = fakes.FakeServerManager().create("test_server", diff --git a/tests/unit/scenarios/vm/test_utils.py b/tests/unit/scenarios/vm/test_utils.py index 0b503ce5..333350b2 100644 --- a/tests/unit/scenarios/vm/test_utils.py +++ b/tests/unit/scenarios/vm/test_utils.py @@ -209,8 +209,8 @@ class VMScenarioTestCase(test.ScenarioTestCase): scenario, server = self.get_scenario() netwrap = mock_wrap.return_value - netwrap.create_floating_ip.return_value = { - "id": "foo_id", "ip": "foo_ip"} + fip = {"id": "foo_id", "ip": "foo_ip"} + netwrap.create_floating_ip.return_value = fip scenario._attach_floating_ip( server, floating_network="bar_network") @@ -221,7 +221,7 @@ class VMScenarioTestCase(test.ScenarioTestCase): tenant_id="foo_tenant", fixed_ip="foo_ip") scenario._associate_floating_ip.assert_called_once_with( - server, "foo_ip", fixed_address="foo_ip") + server, fip, fixed_address="foo_ip") @mock.patch(VMTASKS_UTILS + ".network_wrapper.wrap") def test__delete_floating_ip(self, mock_wrap): @@ -231,14 +231,14 @@ class VMScenarioTestCase(test.ScenarioTestCase): scenario.check_ip_address = mock.Mock(return_value=_check_addr) scenario._dissociate_floating_ip = mock.Mock() - scenario._delete_floating_ip( - server, fip={"id": "foo_id", "ip": "foo_ip"}) + fip = {"id": "foo_id", "ip": "foo_ip"} + scenario._delete_floating_ip(server, fip=fip) scenario.check_ip_address.assert_called_once_with( "foo_ip") _check_addr.assert_called_once_with(server) scenario._dissociate_floating_ip.assert_called_once_with( - server, "foo_ip") + server, fip) mock_wrap.assert_called_once_with(scenario.clients, scenario) mock_wrap.return_value.delete_floating_ip.assert_called_once_with( "foo_id", wait=True)