blob: 4c68daed328b66b3a7fa12847bdff73d524f17b7 [file] [log] [blame]
YAMAMOTO Takashi25935722017-01-23 15:34:11 +09001# Copyright (c) 2017 Midokura SARL
2# All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15
Hongbin Lu965b03d2018-04-25 22:32:30 +000016import time
17
Slawek Kaplonskiec162e02024-03-06 12:21:24 +010018import ddt
LIU Yulong5ba88ef2017-12-22 10:50:15 +080019from neutron_lib import constants as lib_constants
20from neutron_lib.services.qos import constants as qos_consts
Rodolfo Alonso Hernandez4dea8062020-01-16 16:32:59 +000021from neutron_lib.utils import test
elajkat38d90512021-12-13 14:21:30 +010022from oslo_log import log
Chandan Kumarc125fd12017-11-15 19:41:01 +053023from tempest.common import utils
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090024from tempest.common import waiters
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090025from tempest.lib.common.utils import data_utils
Sławek Kapłońskic0caa2e2017-02-25 10:11:32 +000026from tempest.lib import decorators
Slawek Kaplonski168e5012018-10-04 14:31:19 +020027from tempest.lib import exceptions
Roman Safronov29c2dff2019-04-02 22:01:23 +030028import testtools
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090029
LIU Yulong5ba88ef2017-12-22 10:50:15 +080030from neutron_tempest_plugin.api import base as base_api
Chandan Kumar667d3d32017-09-22 12:24:06 +053031from neutron_tempest_plugin.common import ssh
Brian Haleyba800452017-12-14 10:30:48 -050032from neutron_tempest_plugin.common import utils as common_utils
Chandan Kumar667d3d32017-09-22 12:24:06 +053033from neutron_tempest_plugin import config
34from neutron_tempest_plugin.scenario import base
35from neutron_tempest_plugin.scenario import constants
LIU Yulong5ba88ef2017-12-22 10:50:15 +080036from neutron_tempest_plugin.scenario import test_qos
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090037
38
39CONF = config.CONF
elajkat38d90512021-12-13 14:21:30 +010040LOG = log.getLogger(__name__)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090041
42
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090043class FloatingIpTestCasesMixin(object):
44 credentials = ['primary', 'admin']
45
46 @classmethod
Chandan Kumarc125fd12017-11-15 19:41:01 +053047 @utils.requires_ext(extension="router", service="network")
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090048 def resource_setup(cls):
49 super(FloatingIpTestCasesMixin, cls).resource_setup()
50 cls.network = cls.create_network()
51 cls.subnet = cls.create_subnet(cls.network)
52 cls.router = cls.create_router_by_client()
53 cls.create_router_interface(cls.router['id'], cls.subnet['id'])
54 cls.keypair = cls.create_keypair()
55
rajat294495c042017-06-28 15:37:16 +053056 cls.secgroup = cls.os_primary.network_client.create_security_group(
Chandan Kumarc125fd12017-11-15 19:41:01 +053057 name=data_utils.rand_name('secgroup'))['security_group']
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090058 cls.security_groups.append(cls.secgroup)
59 cls.create_loginable_secgroup_rule(secgroup_id=cls.secgroup['id'])
60 cls.create_pingable_secgroup_rule(secgroup_id=cls.secgroup['id'])
61
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090062 if cls.same_network:
63 cls._dest_network = cls.network
64 else:
65 cls._dest_network = cls._create_dest_network()
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090066
67 @classmethod
Itzik Browna31510f2018-01-19 11:09:48 +020068 def _get_external_gateway(cls):
69 if CONF.network.public_network_id:
70 subnets = cls.os_admin.network_client.list_subnets(
71 network_id=CONF.network.public_network_id)
72
73 for subnet in subnets['subnets']:
Brian Haley33ef4602018-04-26 14:37:49 -040074 if (subnet['gateway_ip'] and
75 subnet['ip_version'] == lib_constants.IP_VERSION_4):
Itzik Browna31510f2018-01-19 11:09:48 +020076 return subnet['gateway_ip']
77
78 @classmethod
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090079 def _create_dest_network(cls):
80 network = cls.create_network()
Federico Ressi0ddc93b2018-04-09 12:01:48 +020081 subnet = cls.create_subnet(network)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090082 cls.create_router_interface(cls.router['id'], subnet['id'])
83 return network
84
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000085 def _create_server(self, create_floating_ip=True, network=None):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090086 if network is None:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000087 network = self.network
88 port = self.create_port(network, security_groups=[self.secgroup['id']])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090089 if create_floating_ip:
Federico Ressi3dfa94c2018-07-06 09:46:39 +020090 fip = self.create_floatingip(port=port)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090091 else:
92 fip = None
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000093 server = self.create_server(
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090094 flavor_ref=CONF.compute.flavor_ref,
95 image_ref=CONF.compute.image_ref,
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000096 key_name=self.keypair['name'],
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090097 networks=[{'port': port['id']}])['server']
rajat294495c042017-06-28 15:37:16 +053098 waiters.wait_for_server_status(self.os_primary.servers_client,
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090099 server['id'],
100 constants.SERVER_STATUS_ACTIVE)
101 return {'port': port, 'fip': fip, 'server': server}
102
Slawek Kaplonskiec162e02024-03-06 12:21:24 +0100103 def _test_east_west(self, src_has_fip, dest_has_fip):
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900104 # The proxy VM is used to control the source VM when it doesn't
105 # have a floating-ip.
Slawek Kaplonskiec162e02024-03-06 12:21:24 +0100106 if src_has_fip:
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900107 proxy = None
108 proxy_client = None
109 else:
110 proxy = self._create_server()
111 proxy_client = ssh.Client(proxy['fip']['floating_ip_address'],
112 CONF.validation.image_ssh_user,
113 pkey=self.keypair['private_key'])
114
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900115 # Source VM
Slawek Kaplonskiec162e02024-03-06 12:21:24 +0100116 if src_has_fip:
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900117 src_server = self._create_server()
118 src_server_ip = src_server['fip']['floating_ip_address']
119 else:
120 src_server = self._create_server(create_floating_ip=False)
121 src_server_ip = src_server['port']['fixed_ips'][0]['ip_address']
122 ssh_client = ssh.Client(src_server_ip,
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900123 CONF.validation.image_ssh_user,
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900124 pkey=self.keypair['private_key'],
125 proxy_client=proxy_client)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900126
127 # Destination VM
Slawek Kaplonskiec162e02024-03-06 12:21:24 +0100128 if dest_has_fip:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000129 dest_server = self._create_server(network=self._dest_network)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900130 else:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000131 dest_server = self._create_server(create_floating_ip=False,
132 network=self._dest_network)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900133
134 # Check connectivity
135 self.check_remote_connectivity(ssh_client,
Slawek Kaplonskie58219b2019-12-09 12:10:55 +0100136 dest_server['port']['fixed_ips'][0]['ip_address'],
137 servers=[src_server, dest_server])
Slawek Kaplonskiec162e02024-03-06 12:21:24 +0100138 if dest_has_fip:
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900139 self.check_remote_connectivity(ssh_client,
Slawek Kaplonskie58219b2019-12-09 12:10:55 +0100140 dest_server['fip']['floating_ip_address'],
141 servers=[src_server, dest_server])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900142
143
Slawek Kaplonskiec162e02024-03-06 12:21:24 +0100144@ddt.ddt
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900145class FloatingIpSameNetwork(FloatingIpTestCasesMixin,
146 base.BaseTempestTestCase):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900147
148 same_network = True
149
Rodolfo Alonso Hernandez4dea8062020-01-16 16:32:59 +0000150 @test.unstable_test("bug 1717302")
Sławek Kapłońskic0caa2e2017-02-25 10:11:32 +0000151 @decorators.idempotent_id('05c4e3b3-7319-4052-90ad-e8916436c23b')
Slawek Kaplonskiec162e02024-03-06 12:21:24 +0100152 @ddt.unpack
153 @ddt.data({'src_has_fip': True, 'dest_has_fip': True},
154 {'src_has_fip': True, 'dest_has_fip': False},
155 {'src_has_fip': False, 'dest_has_fip': True},
156 {'src_has_fip': True, 'dest_has_fip': False})
157 def test_east_west(self, src_has_fip, dest_has_fip):
158 self._test_east_west(src_has_fip=src_has_fip,
159 dest_has_fip=dest_has_fip)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900160
161
Slawek Kaplonskiec162e02024-03-06 12:21:24 +0100162@ddt.ddt
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900163class FloatingIpSeparateNetwork(FloatingIpTestCasesMixin,
164 base.BaseTempestTestCase):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900165
166 same_network = False
167
Rodolfo Alonso Hernandez4dea8062020-01-16 16:32:59 +0000168 @test.unstable_test("bug 1717302")
Sławek Kapłońskic0caa2e2017-02-25 10:11:32 +0000169 @decorators.idempotent_id('f18f0090-3289-4783-b956-a0f8ac511e8b')
Slawek Kaplonskiec162e02024-03-06 12:21:24 +0100170 @ddt.unpack
171 @ddt.data({'src_has_fip': True, 'dest_has_fip': True},
172 {'src_has_fip': True, 'dest_has_fip': False},
173 {'src_has_fip': False, 'dest_has_fip': True},
174 {'src_has_fip': True, 'dest_has_fip': False})
175 def test_east_west(self, src_has_fip, dest_has_fip):
176 self._test_east_west(src_has_fip=src_has_fip,
177 dest_has_fip=dest_has_fip)
Itzik Browna31510f2018-01-19 11:09:48 +0200178
179
180class DefaultSnatToExternal(FloatingIpTestCasesMixin,
181 base.BaseTempestTestCase):
182 same_network = True
183
184 @decorators.idempotent_id('3d73ea1a-27c6-45a9-b0f8-04a283d9d764')
185 def test_snat_external_ip(self):
186 """Check connectivity to an external IP"""
187 gateway_external_ip = self._get_external_gateway()
188
189 if not gateway_external_ip:
190 raise self.skipTest("IPv4 gateway is not configured for public "
191 "network or public_network_id is not "
192 "configured")
193 proxy = self._create_server()
194 proxy_client = ssh.Client(proxy['fip']['floating_ip_address'],
195 CONF.validation.image_ssh_user,
196 pkey=self.keypair['private_key'])
197 src_server = self._create_server(create_floating_ip=False)
198 src_server_ip = src_server['port']['fixed_ips'][0]['ip_address']
199 ssh_client = ssh.Client(src_server_ip,
200 CONF.validation.image_ssh_user,
201 pkey=self.keypair['private_key'],
202 proxy_client=proxy_client)
203 self.check_remote_connectivity(ssh_client,
Slawek Kaplonskie58219b2019-12-09 12:10:55 +0100204 gateway_external_ip,
205 servers=[proxy, src_server])
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800206
207
Hongbin Lu965b03d2018-04-25 22:32:30 +0000208class FloatingIPPortDetailsTest(FloatingIpTestCasesMixin,
209 base.BaseTempestTestCase):
210 same_network = True
211
212 @classmethod
213 @utils.requires_ext(extension="router", service="network")
214 @utils.requires_ext(extension="fip-port-details", service="network")
215 def resource_setup(cls):
216 super(FloatingIPPortDetailsTest, cls).resource_setup()
217
218 @decorators.idempotent_id('a663aeee-dd81-492b-a207-354fd6284dbe')
219 def test_floatingip_port_details(self):
220 """Tests the following:
221
222 1. Create a port with floating ip in Neutron.
223 2. Create two servers in Nova.
224 3. Attach the port to the server.
225 4. Detach the port from the server.
226 5. Attach the port to the second server.
227 6. Detach the port from the second server.
228 """
229 port = self.create_port(self.network)
230 fip = self.create_and_associate_floatingip(port['id'])
231 server1 = self._create_server(create_floating_ip=False)
232 server2 = self._create_server(create_floating_ip=False)
233
234 for server in [server1, server2]:
235 # attach the port to the server
236 self.create_interface(
237 server['server']['id'], port_id=port['id'])
238 waiters.wait_for_interface_status(
239 self.os_primary.interfaces_client, server['server']['id'],
Brian Haleyaf347da2018-09-14 11:24:00 -0600240 port['id'], lib_constants.PORT_STATUS_ACTIVE)
Hongbin Lu965b03d2018-04-25 22:32:30 +0000241 fip = self.client.show_floatingip(fip['id'])['floatingip']
242 self._check_port_details(
Brian Haleyaf347da2018-09-14 11:24:00 -0600243 fip, port, status=lib_constants.PORT_STATUS_ACTIVE,
Hongbin Lu965b03d2018-04-25 22:32:30 +0000244 device_id=server['server']['id'], device_owner='compute:nova')
elajkat38d90512021-12-13 14:21:30 +0100245 LOG.debug('Port check for server %s and FIP %s finished, '
246 'lets detach port %s from server!',
247 server['server']['id'], fip['id'], port['id'])
Hongbin Lu965b03d2018-04-25 22:32:30 +0000248
249 # detach the port from the server; this is a cast in the compute
250 # API so we have to poll the port until the device_id is unset.
251 self.delete_interface(server['server']['id'], port['id'])
Brian Haleyaf347da2018-09-14 11:24:00 -0600252 port = self._wait_for_port_detach(port['id'])
elajkat38d90512021-12-13 14:21:30 +0100253 LOG.debug('Port %s has been detached from server %s, lets check '
254 'the status of port in FIP %s details!',
255 port['id'], server['server']['id'], fip['id'])
Brian Haleyaf347da2018-09-14 11:24:00 -0600256 fip = self._wait_for_fip_port_down(fip['id'])
Hongbin Lu965b03d2018-04-25 22:32:30 +0000257 self._check_port_details(
Brian Haleyaf347da2018-09-14 11:24:00 -0600258 fip, port, status=lib_constants.PORT_STATUS_DOWN,
259 device_id='', device_owner='')
Hongbin Lu965b03d2018-04-25 22:32:30 +0000260
261 def _check_port_details(self, fip, port, status, device_id, device_owner):
262 self.assertIn('port_details', fip)
263 port_details = fip['port_details']
264 self.assertEqual(port['name'], port_details['name'])
265 self.assertEqual(port['network_id'], port_details['network_id'])
266 self.assertEqual(port['mac_address'], port_details['mac_address'])
267 self.assertEqual(port['admin_state_up'],
268 port_details['admin_state_up'])
269 self.assertEqual(status, port_details['status'])
270 self.assertEqual(device_id, port_details['device_id'])
271 self.assertEqual(device_owner, port_details['device_owner'])
272
273 def _wait_for_port_detach(self, port_id, timeout=120, interval=10):
274 """Waits for the port's device_id to be unset.
275
276 :param port_id: The id of the port being detached.
277 :returns: The final port dict from the show_port response.
278 """
279 port = self.client.show_port(port_id)['port']
280 device_id = port['device_id']
281 start = int(time.time())
282
283 # NOTE(mriedem): Nova updates the port's device_id to '' rather than
284 # None, but it's not contractual so handle Falsey either way.
285 while device_id:
286 time.sleep(interval)
287 port = self.client.show_port(port_id)['port']
288 device_id = port['device_id']
289
290 timed_out = int(time.time()) - start >= timeout
291
292 if device_id and timed_out:
293 message = ('Port %s failed to detach (device_id %s) within '
294 'the required time (%s s).' %
295 (port_id, device_id, timeout))
296 raise exceptions.TimeoutException(message)
297
298 return port
299
Brian Haleyaf347da2018-09-14 11:24:00 -0600300 def _wait_for_fip_port_down(self, fip_id, timeout=120, interval=10):
301 """Waits for the fip's attached port status to be 'DOWN'.
302
303 :param fip_id: The id of the floating IP.
304 :returns: The final fip dict from the show_floatingip response.
305 """
306 fip = self.client.show_floatingip(fip_id)['floatingip']
307 self.assertIn('port_details', fip)
308 port_details = fip['port_details']
309 status = port_details['status']
310 start = int(time.time())
311
312 while status != lib_constants.PORT_STATUS_DOWN:
313 time.sleep(interval)
314 fip = self.client.show_floatingip(fip_id)['floatingip']
315 self.assertIn('port_details', fip)
316 port_details = fip['port_details']
317 status = port_details['status']
318
319 timed_out = int(time.time()) - start >= timeout
320
321 if status != lib_constants.PORT_STATUS_DOWN and timed_out:
Slawek Kaplonski7451ad72019-02-25 12:02:49 +0100322 port_id = fip.get("port_id")
323 port = self.os_admin.network_client.show_port(port_id)['port']
Brian Haleyaf347da2018-09-14 11:24:00 -0600324 message = ('Floating IP %s attached port status failed to '
325 'transition to DOWN (current status %s) within '
Slawek Kaplonski7451ad72019-02-25 12:02:49 +0100326 'the required time (%s s). Port details: %s' %
327 (fip_id, status, timeout, port))
Brian Haleyaf347da2018-09-14 11:24:00 -0600328 raise exceptions.TimeoutException(message)
329
elajkat38d90512021-12-13 14:21:30 +0100330 LOG.debug('Port %s attached to FIP %s is down after %s!',
331 fip.get("port_id"), fip_id, int(time.time()) - start)
Brian Haleyaf347da2018-09-14 11:24:00 -0600332 return fip
333
Hongbin Lu965b03d2018-04-25 22:32:30 +0000334
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800335class FloatingIPQosTest(FloatingIpTestCasesMixin,
YAMAMOTO Takashia2cc2e52018-07-31 18:54:02 +0900336 test_qos.QoSTestMixin,
337 base.BaseTempestTestCase):
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800338
339 same_network = True
340
341 @classmethod
342 @utils.requires_ext(extension="router", service="network")
343 @utils.requires_ext(extension="qos", service="network")
YAMAMOTO Takashi9c072a02018-03-22 22:49:09 +0900344 @utils.requires_ext(extension="qos-fip", service="network")
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800345 @base_api.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
346 def resource_setup(cls):
347 super(FloatingIPQosTest, cls).resource_setup()
348
Eduardo Olivares98772912021-02-19 12:36:21 +0100349 @classmethod
350 def setup_clients(cls):
351 super(FloatingIPQosTest, cls).setup_clients()
352 cls.admin_client = cls.os_admin.network_client
zahlabut7ebb66e2021-09-01 22:39:49 +0300353 cls.qos_bw_limit_rule_client = \
354 cls.os_admin.qos_limit_bandwidth_rules_client
Eduardo Olivares98772912021-02-19 12:36:21 +0100355
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800356 @decorators.idempotent_id('5eb48aea-eaba-4c20-8a6f-7740070a0aa3')
357 def test_qos(self):
358 """Test floating IP is binding to a QoS policy with
359
360 ingress and egress bandwidth limit rules. And it applied correctly
361 by sending a file from the instance to the test node.
362 Then calculating the bandwidth every ~1 sec by the number of bits
363 received / elapsed time.
364 """
365
Slawek Kaplonski71a462b2020-10-21 12:59:18 +0200366 self.skip_if_no_extension_enabled_in_l3_agents("fip_qos")
367
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800368 self._test_basic_resources()
Eduardo Olivaresf5a40d92021-02-23 09:17:49 +0100369
370 # Create a new QoS policy
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800371 policy_id = self._create_qos_policy()
372 ssh_client = self._create_ssh_client()
Eduardo Olivaresf5a40d92021-02-23 09:17:49 +0100373
374 # As admin user create a new QoS rules
zahlabut7ebb66e2021-09-01 22:39:49 +0300375 rule_data = {'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
376 'max_burst_kbps': constants.LIMIT_KILO_BYTES,
377 'direction': lib_constants.INGRESS_DIRECTION}
378 self.qos_bw_limit_rule_client.create_limit_bandwidth_rule(
379 qos_policy_id=policy_id, **rule_data)
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800380
zahlabut7ebb66e2021-09-01 22:39:49 +0300381 rule_data = {'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND,
382 'max_burst_kbps': constants.LIMIT_KILO_BYTES,
383 'direction': lib_constants.EGRESS_DIRECTION}
384 self.qos_bw_limit_rule_client.create_limit_bandwidth_rule(
385 qos_policy_id=policy_id, **rule_data)
386
387 rules = self.qos_bw_limit_rule_client.list_limit_bandwidth_rules(
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800388 policy_id)
389 self.assertEqual(2, len(rules['bandwidth_limit_rules']))
390
391 fip = self.os_admin.network_client.get_floatingip(
392 self.fip['id'])['floatingip']
393 self.assertEqual(self.port['id'], fip['port_id'])
394
Eduardo Olivaresf5a40d92021-02-23 09:17:49 +0100395 # Associate QoS to the FIP
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800396 self.os_admin.network_client.update_floatingip(
397 self.fip['id'],
398 qos_policy_id=policy_id)
399
400 fip = self.os_admin.network_client.get_floatingip(
401 self.fip['id'])['floatingip']
402 self.assertEqual(policy_id, fip['qos_policy_id'])
403
Eduardo Olivaresf5a40d92021-02-23 09:17:49 +0100404 # Basic test, Check that actual BW while downloading file
405 # is as expected (Original BW)
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800406 common_utils.wait_until_true(lambda: self._check_bw(
407 ssh_client,
408 self.fip['floating_ip_address'],
409 port=self.NC_PORT),
410 timeout=120,
Eduardo Olivaresf5a40d92021-02-23 09:17:49 +0100411 sleep=1,
412 exception=RuntimeError(
413 'Failed scenario: "Create a QoS policy associated with FIP" '
414 'Actual BW is not as expected!'))
415
416 # As admin user update QoS rules
417 for rule in rules['bandwidth_limit_rules']:
zahlabut7ebb66e2021-09-01 22:39:49 +0300418 self.qos_bw_limit_rule_client.update_limit_bandwidth_rule(
419 policy_id, rule['id'],
420 **{'max_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 2,
421 'max_burst_kbps': constants.LIMIT_KILO_BITS_PER_SECOND * 2})
Eduardo Olivaresf5a40d92021-02-23 09:17:49 +0100422
423 # Check that actual BW while downloading file
424 # is as expected (Update BW)
425 common_utils.wait_until_true(lambda: self._check_bw(
426 ssh_client,
427 self.fip['floating_ip_address'],
428 port=self.NC_PORT,
429 expected_bw=test_qos.QoSTestMixin.LIMIT_BYTES_SEC * 2),
430 timeout=120,
431 sleep=1,
432 exception=RuntimeError(
433 'Failed scenario: "Update QoS policy associated with FIP" '
434 'Actual BW is not as expected!'))
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000435
436
437class TestFloatingIPUpdate(FloatingIpTestCasesMixin,
438 base.BaseTempestTestCase):
439
440 same_network = None
441
442 @decorators.idempotent_id('1bdd849b-03dd-4b8f-994f-457cf8a36f93')
443 def test_floating_ip_update(self):
444 """Test updating FIP with another port.
445
446 The test creates two servers and attaches floating ip to first server.
447 Then it checks server is accesible using the FIP. FIP is then
448 associated with the second server and connectivity is checked again.
449 """
450 ports = [self.create_port(
451 self.network, security_groups=[self.secgroup['id']])
452 for i in range(2)]
453
454 servers = []
455 for port in ports:
456 name = data_utils.rand_name("server-%s" % port['id'][:8])
457 server = self.create_server(
458 name=name,
459 flavor_ref=CONF.compute.flavor_ref,
460 key_name=self.keypair['name'],
461 image_ref=CONF.compute.image_ref,
462 networks=[{'port': port['id']}])['server']
463 server['name'] = name
464 servers.append(server)
465 for server in servers:
466 self.wait_for_server_active(server)
Slawek Kaplonski2211eab2020-10-20 16:43:53 +0200467 self.wait_for_guest_os_ready(server)
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000468
469 self.fip = self.create_floatingip(port=ports[0])
470 self.check_connectivity(self.fip['floating_ip_address'],
471 CONF.validation.image_ssh_user,
Slawek Kaplonskie58219b2019-12-09 12:10:55 +0100472 self.keypair['private_key'],
473 servers=servers)
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000474 self.client.update_floatingip(self.fip['id'], port_id=ports[1]['id'])
475
476 def _wait_for_fip_associated():
477 try:
478 self.check_servers_hostnames(servers[-1:], log_errors=False)
Vasyl Saienko7f2887c2021-12-22 11:54:04 +0200479 # NOTE(vsaienko): it might take some time by neutron to update VIP
480 # retry on any exception here.
481 except Exception:
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000482 return False
483 return True
484
485 # The FIP is now associated with the port of the second server.
486 try:
Slawek Kaplonski0e9edc52020-09-30 16:54:13 +0200487 common_utils.wait_until_true(_wait_for_fip_associated, sleep=3)
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000488 except common_utils.WaitTimeout:
489 self._log_console_output(servers[-1:])
490 self.fail(
491 "Server %s is not accessible via its floating ip %s" % (
492 servers[-1]['id'], self.fip['id']))
Roman Safronov29c2dff2019-04-02 22:01:23 +0300493
494
495class FloatingIpMultipleRoutersTest(base.BaseTempestTestCase):
496 credentials = ['primary', 'admin']
497
498 @classmethod
499 @utils.requires_ext(extension="router", service="network")
500 def skip_checks(cls):
501 super(FloatingIpMultipleRoutersTest, cls).skip_checks()
502
503 def _create_keypair_and_secgroup(self):
504 self.keypair = self.create_keypair()
505 self.secgroup = self.create_security_group()
506 self.create_loginable_secgroup_rule(
507 secgroup_id=self.secgroup['id'])
508 self.create_pingable_secgroup_rule(
509 secgroup_id=self.secgroup['id'])
510
Rodolfo Alonso Hernandez71583982020-05-27 16:25:59 +0000511 def _delete_floating_ip(self, fip_address):
512 ip_address = fip_address['floating_ip_address']
513
514 def _fip_is_free():
515 fips = self.os_admin.network_client.list_floatingips()
516 for fip in fips['floatingips']:
517 if ip_address == fip['floating_ip_address']:
518 return False
519 return True
520
521 self.delete_floatingip(fip_address)
522 try:
523 common_utils.wait_until_true(_fip_is_free, timeout=30, sleep=5)
524 except common_utils.WaitTimeout:
525 self.fail("Can't reuse IP address %s because it is not free" %
526 ip_address)
527
528 def _create_network_and_servers(self, servers_num=1, fip_addresses=None,
529 delete_fip_ids=None):
530 delete_fip_ids = delete_fip_ids or []
Roman Safronov29c2dff2019-04-02 22:01:23 +0300531 if fip_addresses:
532 self.assertEqual(servers_num, len(fip_addresses),
533 ('Number of specified fip addresses '
534 'does not match the number of servers'))
535 network = self.create_network()
536 subnet = self.create_subnet(network)
537 router = self.create_router_by_client()
538 self.create_router_interface(router['id'], subnet['id'])
539
540 fips = []
541 for server in range(servers_num):
542 fip = fip_addresses[server] if fip_addresses else None
Rodolfo Alonso Hernandez71583982020-05-27 16:25:59 +0000543 delete_fip = fip['id'] in delete_fip_ids if fip else False
Roman Safronov29c2dff2019-04-02 22:01:23 +0300544 fips.append(
Rodolfo Alonso Hernandez71583982020-05-27 16:25:59 +0000545 self._create_server_and_fip(network=network,
546 fip_address=fip,
547 delete_fip_address=delete_fip))
Roman Safronov29c2dff2019-04-02 22:01:23 +0300548 return fips
549
Rodolfo Alonso Hernandez71583982020-05-27 16:25:59 +0000550 def _create_server_and_fip(self, network, fip_address=None,
551 delete_fip_address=False):
Roman Safronov29c2dff2019-04-02 22:01:23 +0300552 server = self.create_server(
553 flavor_ref=CONF.compute.flavor_ref,
554 image_ref=CONF.compute.image_ref,
555 key_name=self.keypair['name'],
556 networks=[{'uuid': network['id']}],
557 security_groups=[{'name': self.secgroup['name']}])
558 waiters.wait_for_server_status(self.os_primary.servers_client,
559 server['server']['id'],
560 constants.SERVER_STATUS_ACTIVE)
561 port = self.client.list_ports(
562 network_id=network['id'],
563 device_id=server['server']['id'])['ports'][0]
564
565 if fip_address:
Rodolfo Alonso Hernandez71583982020-05-27 16:25:59 +0000566 if delete_fip_address:
567 self._delete_floating_ip(fip_address)
Roman Safronov29c2dff2019-04-02 22:01:23 +0300568 fip = self.create_floatingip(
Rodolfo Alonso Hernandez71583982020-05-27 16:25:59 +0000569 floating_ip_address=fip_address['floating_ip_address'],
Roman Safronov29c2dff2019-04-02 22:01:23 +0300570 client=self.os_admin.network_client,
571 port=port)
572 self.addCleanup(
573 self.delete_floatingip, fip, self.os_admin.network_client)
574 else:
575 fip = self.create_floatingip(port=port)
576 return fip
577
578 def _check_fips_connectivity(self, mutable_fip, permanent_fip):
579 for fip in [mutable_fip, permanent_fip]:
580 fip['ssh_client'] = ssh.Client(fip['floating_ip_address'],
581 CONF.validation.image_ssh_user,
582 pkey=self.keypair['private_key'])
583 self.check_remote_connectivity(
584 permanent_fip['ssh_client'], mutable_fip['floating_ip_address'])
585 self.check_remote_connectivity(
586 mutable_fip['ssh_client'], permanent_fip['floating_ip_address'])
587
588 @testtools.skipUnless(CONF.network.public_network_id,
589 'The public_network_id option must be specified.')
590 @decorators.idempotent_id('b0382ab3-3c86-4415-84e3-649a8b040dab')
591 def test_reuse_ip_address_with_other_fip_on_other_router(self):
592 """Reuse IP address by another floating IP on another router
593
594 Scenario:
595 1. Create and connect a router to the external network.
596 2. Create and connect an internal network to the router.
597 3. Create and connect 2 VMs to the internal network.
598 4. Create FIPs in the external network for the VMs.
599 5. Make sure that VM1 can ping VM2 FIP address.
Rodolfo Alonso Hernandez71583982020-05-27 16:25:59 +0000600 6. Create and connect one more router to the external network.
601 7. Create and connect an internal network to the second router.
602 8. Create and connect a VM (VM3) to the internal network of
Roman Safronov29c2dff2019-04-02 22:01:23 +0300603 the second router.
Rodolfo Alonso Hernandez71583982020-05-27 16:25:59 +0000604 9. Delete VM2 FIP but save IP address that it used. The FIP is
605 deleted just before the creation of the new IP to "reserve" the
606 IP address associated (see LP#1880976).
Roman Safronov29c2dff2019-04-02 22:01:23 +0300607 10. Create a FIP for the VM3 in the external network with
Elod Illesf2e985e2023-11-06 19:30:29 +0100608 the same IP address that was used for VM2.
Roman Safronov29c2dff2019-04-02 22:01:23 +0300609 11. Make sure that now VM1 is able to reach VM3 using the FIP.
610
611 Note, the scenario passes only in case corresponding
612 ARP update was sent to the external network when reusing same IP
613 address for another FIP.
614 """
615
616 self._create_keypair_and_secgroup()
617 [mutable_fip, permanent_fip] = (
618 self._create_network_and_servers(servers_num=2))
619 self._check_fips_connectivity(mutable_fip, permanent_fip)
Roman Safronov29c2dff2019-04-02 22:01:23 +0300620 [mutable_fip] = self._create_network_and_servers(
Rodolfo Alonso Hernandez71583982020-05-27 16:25:59 +0000621 servers_num=1, fip_addresses=[mutable_fip],
622 delete_fip_ids=[mutable_fip['id']])
Roman Safronov29c2dff2019-04-02 22:01:23 +0300623 self._check_fips_connectivity(mutable_fip, permanent_fip)