blob: 37df5be59165b918c847029c44a70f900f2acfd6 [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
LIU Yulong5ba88ef2017-12-22 10:50:15 +080018from neutron_lib import constants as lib_constants
19from neutron_lib.services.qos import constants as qos_consts
Rodolfo Alonso Hernandez4dea8062020-01-16 16:32:59 +000020from neutron_lib.utils import test
Chandan Kumarc125fd12017-11-15 19:41:01 +053021from tempest.common import utils
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090022from tempest.common import waiters
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090023from tempest.lib.common.utils import data_utils
Sławek Kapłońskic0caa2e2017-02-25 10:11:32 +000024from tempest.lib import decorators
Slawek Kaplonski168e5012018-10-04 14:31:19 +020025from tempest.lib import exceptions
Ihar Hrachyshkace9c4862018-01-18 18:26:14 +000026import testscenarios
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +090027from testscenarios.scenarios import multiply_scenarios
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090028
LIU Yulong5ba88ef2017-12-22 10:50:15 +080029from neutron_tempest_plugin.api import base as base_api
Chandan Kumar667d3d32017-09-22 12:24:06 +053030from neutron_tempest_plugin.common import ssh
Brian Haleyba800452017-12-14 10:30:48 -050031from neutron_tempest_plugin.common import utils as common_utils
Chandan Kumar667d3d32017-09-22 12:24:06 +053032from neutron_tempest_plugin import config
33from neutron_tempest_plugin.scenario import base
34from neutron_tempest_plugin.scenario import constants
LIU Yulong5ba88ef2017-12-22 10:50:15 +080035from neutron_tempest_plugin.scenario import test_qos
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090036
37
38CONF = config.CONF
39
40
41load_tests = testscenarios.load_tests_apply_scenarios
42
43
44class FloatingIpTestCasesMixin(object):
45 credentials = ['primary', 'admin']
46
47 @classmethod
Chandan Kumarc125fd12017-11-15 19:41:01 +053048 @utils.requires_ext(extension="router", service="network")
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090049 def resource_setup(cls):
50 super(FloatingIpTestCasesMixin, cls).resource_setup()
51 cls.network = cls.create_network()
52 cls.subnet = cls.create_subnet(cls.network)
53 cls.router = cls.create_router_by_client()
54 cls.create_router_interface(cls.router['id'], cls.subnet['id'])
55 cls.keypair = cls.create_keypair()
56
rajat294495c042017-06-28 15:37:16 +053057 cls.secgroup = cls.os_primary.network_client.create_security_group(
Chandan Kumarc125fd12017-11-15 19:41:01 +053058 name=data_utils.rand_name('secgroup'))['security_group']
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090059 cls.security_groups.append(cls.secgroup)
60 cls.create_loginable_secgroup_rule(secgroup_id=cls.secgroup['id'])
61 cls.create_pingable_secgroup_rule(secgroup_id=cls.secgroup['id'])
62
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090063 if cls.same_network:
64 cls._dest_network = cls.network
65 else:
66 cls._dest_network = cls._create_dest_network()
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090067
68 @classmethod
Itzik Browna31510f2018-01-19 11:09:48 +020069 def _get_external_gateway(cls):
70 if CONF.network.public_network_id:
71 subnets = cls.os_admin.network_client.list_subnets(
72 network_id=CONF.network.public_network_id)
73
74 for subnet in subnets['subnets']:
Brian Haley33ef4602018-04-26 14:37:49 -040075 if (subnet['gateway_ip'] and
76 subnet['ip_version'] == lib_constants.IP_VERSION_4):
Itzik Browna31510f2018-01-19 11:09:48 +020077 return subnet['gateway_ip']
78
79 @classmethod
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090080 def _create_dest_network(cls):
81 network = cls.create_network()
Federico Ressi0ddc93b2018-04-09 12:01:48 +020082 subnet = cls.create_subnet(network)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090083 cls.create_router_interface(cls.router['id'], subnet['id'])
84 return network
85
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000086 def _create_server(self, create_floating_ip=True, network=None):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090087 if network is None:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000088 network = self.network
89 port = self.create_port(network, security_groups=[self.secgroup['id']])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090090 if create_floating_ip:
Federico Ressi3dfa94c2018-07-06 09:46:39 +020091 fip = self.create_floatingip(port=port)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090092 else:
93 fip = None
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000094 server = self.create_server(
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090095 flavor_ref=CONF.compute.flavor_ref,
96 image_ref=CONF.compute.image_ref,
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000097 key_name=self.keypair['name'],
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090098 networks=[{'port': port['id']}])['server']
rajat294495c042017-06-28 15:37:16 +053099 waiters.wait_for_server_status(self.os_primary.servers_client,
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900100 server['id'],
101 constants.SERVER_STATUS_ACTIVE)
102 return {'port': port, 'fip': fip, 'server': server}
103
104 def _test_east_west(self):
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900105 # The proxy VM is used to control the source VM when it doesn't
106 # have a floating-ip.
107 if self.src_has_fip:
108 proxy = None
109 proxy_client = None
110 else:
111 proxy = self._create_server()
112 proxy_client = ssh.Client(proxy['fip']['floating_ip_address'],
113 CONF.validation.image_ssh_user,
114 pkey=self.keypair['private_key'])
115
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900116 # Source VM
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900117 if self.src_has_fip:
118 src_server = self._create_server()
119 src_server_ip = src_server['fip']['floating_ip_address']
120 else:
121 src_server = self._create_server(create_floating_ip=False)
122 src_server_ip = src_server['port']['fixed_ips'][0]['ip_address']
123 ssh_client = ssh.Client(src_server_ip,
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900124 CONF.validation.image_ssh_user,
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900125 pkey=self.keypair['private_key'],
126 proxy_client=proxy_client)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900127
128 # Destination VM
129 if self.dest_has_fip:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000130 dest_server = self._create_server(network=self._dest_network)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900131 else:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000132 dest_server = self._create_server(create_floating_ip=False,
133 network=self._dest_network)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900134
135 # Check connectivity
136 self.check_remote_connectivity(ssh_client,
Slawek Kaplonskie58219b2019-12-09 12:10:55 +0100137 dest_server['port']['fixed_ips'][0]['ip_address'],
138 servers=[src_server, dest_server])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900139 if self.dest_has_fip:
140 self.check_remote_connectivity(ssh_client,
Slawek Kaplonskie58219b2019-12-09 12:10:55 +0100141 dest_server['fip']['floating_ip_address'],
142 servers=[src_server, dest_server])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900143
144
145class FloatingIpSameNetwork(FloatingIpTestCasesMixin,
146 base.BaseTempestTestCase):
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900147 scenarios = multiply_scenarios([
148 ('SRC with FIP', dict(src_has_fip=True)),
149 ('SRC without FIP', dict(src_has_fip=False)),
150 ], [
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900151 ('DEST with FIP', dict(dest_has_fip=True)),
152 ('DEST without FIP', dict(dest_has_fip=False)),
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900153 ])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900154
155 same_network = True
156
Rodolfo Alonso Hernandez4dea8062020-01-16 16:32:59 +0000157 @test.unstable_test("bug 1717302")
Sławek Kapłońskic0caa2e2017-02-25 10:11:32 +0000158 @decorators.idempotent_id('05c4e3b3-7319-4052-90ad-e8916436c23b')
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900159 def test_east_west(self):
160 self._test_east_west()
161
162
163class FloatingIpSeparateNetwork(FloatingIpTestCasesMixin,
164 base.BaseTempestTestCase):
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900165 scenarios = multiply_scenarios([
166 ('SRC with FIP', dict(src_has_fip=True)),
167 ('SRC without FIP', dict(src_has_fip=False)),
168 ], [
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900169 ('DEST with FIP', dict(dest_has_fip=True)),
170 ('DEST without FIP', dict(dest_has_fip=False)),
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900171 ])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900172
173 same_network = False
174
Rodolfo Alonso Hernandez4dea8062020-01-16 16:32:59 +0000175 @test.unstable_test("bug 1717302")
Sławek Kapłońskic0caa2e2017-02-25 10:11:32 +0000176 @decorators.idempotent_id('f18f0090-3289-4783-b956-a0f8ac511e8b')
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900177 def test_east_west(self):
178 self._test_east_west()
Itzik Browna31510f2018-01-19 11:09:48 +0200179
180
181class DefaultSnatToExternal(FloatingIpTestCasesMixin,
182 base.BaseTempestTestCase):
183 same_network = True
184
185 @decorators.idempotent_id('3d73ea1a-27c6-45a9-b0f8-04a283d9d764')
186 def test_snat_external_ip(self):
187 """Check connectivity to an external IP"""
188 gateway_external_ip = self._get_external_gateway()
189
190 if not gateway_external_ip:
191 raise self.skipTest("IPv4 gateway is not configured for public "
192 "network or public_network_id is not "
193 "configured")
194 proxy = self._create_server()
195 proxy_client = ssh.Client(proxy['fip']['floating_ip_address'],
196 CONF.validation.image_ssh_user,
197 pkey=self.keypair['private_key'])
198 src_server = self._create_server(create_floating_ip=False)
199 src_server_ip = src_server['port']['fixed_ips'][0]['ip_address']
200 ssh_client = ssh.Client(src_server_ip,
201 CONF.validation.image_ssh_user,
202 pkey=self.keypair['private_key'],
203 proxy_client=proxy_client)
204 self.check_remote_connectivity(ssh_client,
Slawek Kaplonskie58219b2019-12-09 12:10:55 +0100205 gateway_external_ip,
206 servers=[proxy, src_server])
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800207
208
Hongbin Lu965b03d2018-04-25 22:32:30 +0000209class FloatingIPPortDetailsTest(FloatingIpTestCasesMixin,
210 base.BaseTempestTestCase):
211 same_network = True
212
213 @classmethod
214 @utils.requires_ext(extension="router", service="network")
215 @utils.requires_ext(extension="fip-port-details", service="network")
216 def resource_setup(cls):
217 super(FloatingIPPortDetailsTest, cls).resource_setup()
218
Rodolfo Alonso Hernandez4dea8062020-01-16 16:32:59 +0000219 @test.unstable_test("bug 1815585")
Hongbin Lu965b03d2018-04-25 22:32:30 +0000220 @decorators.idempotent_id('a663aeee-dd81-492b-a207-354fd6284dbe')
221 def test_floatingip_port_details(self):
222 """Tests the following:
223
224 1. Create a port with floating ip in Neutron.
225 2. Create two servers in Nova.
226 3. Attach the port to the server.
227 4. Detach the port from the server.
228 5. Attach the port to the second server.
229 6. Detach the port from the second server.
230 """
231 port = self.create_port(self.network)
232 fip = self.create_and_associate_floatingip(port['id'])
233 server1 = self._create_server(create_floating_ip=False)
234 server2 = self._create_server(create_floating_ip=False)
235
236 for server in [server1, server2]:
237 # attach the port to the server
238 self.create_interface(
239 server['server']['id'], port_id=port['id'])
240 waiters.wait_for_interface_status(
241 self.os_primary.interfaces_client, server['server']['id'],
Brian Haleyaf347da2018-09-14 11:24:00 -0600242 port['id'], lib_constants.PORT_STATUS_ACTIVE)
Hongbin Lu965b03d2018-04-25 22:32:30 +0000243 fip = self.client.show_floatingip(fip['id'])['floatingip']
244 self._check_port_details(
Brian Haleyaf347da2018-09-14 11:24:00 -0600245 fip, port, status=lib_constants.PORT_STATUS_ACTIVE,
Hongbin Lu965b03d2018-04-25 22:32:30 +0000246 device_id=server['server']['id'], device_owner='compute:nova')
247
248 # detach the port from the server; this is a cast in the compute
249 # API so we have to poll the port until the device_id is unset.
250 self.delete_interface(server['server']['id'], port['id'])
Brian Haleyaf347da2018-09-14 11:24:00 -0600251 port = self._wait_for_port_detach(port['id'])
252 fip = self._wait_for_fip_port_down(fip['id'])
Hongbin Lu965b03d2018-04-25 22:32:30 +0000253 self._check_port_details(
Brian Haleyaf347da2018-09-14 11:24:00 -0600254 fip, port, status=lib_constants.PORT_STATUS_DOWN,
255 device_id='', device_owner='')
Hongbin Lu965b03d2018-04-25 22:32:30 +0000256
257 def _check_port_details(self, fip, port, status, device_id, device_owner):
258 self.assertIn('port_details', fip)
259 port_details = fip['port_details']
260 self.assertEqual(port['name'], port_details['name'])
261 self.assertEqual(port['network_id'], port_details['network_id'])
262 self.assertEqual(port['mac_address'], port_details['mac_address'])
263 self.assertEqual(port['admin_state_up'],
264 port_details['admin_state_up'])
265 self.assertEqual(status, port_details['status'])
266 self.assertEqual(device_id, port_details['device_id'])
267 self.assertEqual(device_owner, port_details['device_owner'])
268
269 def _wait_for_port_detach(self, port_id, timeout=120, interval=10):
270 """Waits for the port's device_id to be unset.
271
272 :param port_id: The id of the port being detached.
273 :returns: The final port dict from the show_port response.
274 """
275 port = self.client.show_port(port_id)['port']
276 device_id = port['device_id']
277 start = int(time.time())
278
279 # NOTE(mriedem): Nova updates the port's device_id to '' rather than
280 # None, but it's not contractual so handle Falsey either way.
281 while device_id:
282 time.sleep(interval)
283 port = self.client.show_port(port_id)['port']
284 device_id = port['device_id']
285
286 timed_out = int(time.time()) - start >= timeout
287
288 if device_id and timed_out:
289 message = ('Port %s failed to detach (device_id %s) within '
290 'the required time (%s s).' %
291 (port_id, device_id, timeout))
292 raise exceptions.TimeoutException(message)
293
294 return port
295
Brian Haleyaf347da2018-09-14 11:24:00 -0600296 def _wait_for_fip_port_down(self, fip_id, timeout=120, interval=10):
297 """Waits for the fip's attached port status to be 'DOWN'.
298
299 :param fip_id: The id of the floating IP.
300 :returns: The final fip dict from the show_floatingip response.
301 """
302 fip = self.client.show_floatingip(fip_id)['floatingip']
303 self.assertIn('port_details', fip)
304 port_details = fip['port_details']
305 status = port_details['status']
306 start = int(time.time())
307
308 while status != lib_constants.PORT_STATUS_DOWN:
309 time.sleep(interval)
310 fip = self.client.show_floatingip(fip_id)['floatingip']
311 self.assertIn('port_details', fip)
312 port_details = fip['port_details']
313 status = port_details['status']
314
315 timed_out = int(time.time()) - start >= timeout
316
317 if status != lib_constants.PORT_STATUS_DOWN and timed_out:
Slawek Kaplonski7451ad72019-02-25 12:02:49 +0100318 port_id = fip.get("port_id")
319 port = self.os_admin.network_client.show_port(port_id)['port']
Brian Haleyaf347da2018-09-14 11:24:00 -0600320 message = ('Floating IP %s attached port status failed to '
321 'transition to DOWN (current status %s) within '
Slawek Kaplonski7451ad72019-02-25 12:02:49 +0100322 'the required time (%s s). Port details: %s' %
323 (fip_id, status, timeout, port))
Brian Haleyaf347da2018-09-14 11:24:00 -0600324 raise exceptions.TimeoutException(message)
325
326 return fip
327
Hongbin Lu965b03d2018-04-25 22:32:30 +0000328
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800329class FloatingIPQosTest(FloatingIpTestCasesMixin,
YAMAMOTO Takashia2cc2e52018-07-31 18:54:02 +0900330 test_qos.QoSTestMixin,
331 base.BaseTempestTestCase):
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800332
333 same_network = True
334
335 @classmethod
336 @utils.requires_ext(extension="router", service="network")
337 @utils.requires_ext(extension="qos", service="network")
YAMAMOTO Takashi9c072a02018-03-22 22:49:09 +0900338 @utils.requires_ext(extension="qos-fip", service="network")
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800339 @base_api.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
340 def resource_setup(cls):
341 super(FloatingIPQosTest, cls).resource_setup()
342
343 @decorators.idempotent_id('5eb48aea-eaba-4c20-8a6f-7740070a0aa3')
344 def test_qos(self):
345 """Test floating IP is binding to a QoS policy with
346
347 ingress and egress bandwidth limit rules. And it applied correctly
348 by sending a file from the instance to the test node.
349 Then calculating the bandwidth every ~1 sec by the number of bits
350 received / elapsed time.
351 """
352
353 self._test_basic_resources()
354 policy_id = self._create_qos_policy()
355 ssh_client = self._create_ssh_client()
356 self.os_admin.network_client.create_bandwidth_limit_rule(
357 policy_id, max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
358 max_burst_kbps=constants.LIMIT_KILO_BYTES,
359 direction=lib_constants.INGRESS_DIRECTION)
360 self.os_admin.network_client.create_bandwidth_limit_rule(
361 policy_id, max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
362 max_burst_kbps=constants.LIMIT_KILO_BYTES,
363 direction=lib_constants.EGRESS_DIRECTION)
364
365 rules = self.os_admin.network_client.list_bandwidth_limit_rules(
366 policy_id)
367 self.assertEqual(2, len(rules['bandwidth_limit_rules']))
368
369 fip = self.os_admin.network_client.get_floatingip(
370 self.fip['id'])['floatingip']
371 self.assertEqual(self.port['id'], fip['port_id'])
372
373 self.os_admin.network_client.update_floatingip(
374 self.fip['id'],
375 qos_policy_id=policy_id)
376
377 fip = self.os_admin.network_client.get_floatingip(
378 self.fip['id'])['floatingip']
379 self.assertEqual(policy_id, fip['qos_policy_id'])
380
381 self._create_file_for_bw_tests(ssh_client)
382 common_utils.wait_until_true(lambda: self._check_bw(
383 ssh_client,
384 self.fip['floating_ip_address'],
385 port=self.NC_PORT),
386 timeout=120,
387 sleep=1)
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000388
389
390class TestFloatingIPUpdate(FloatingIpTestCasesMixin,
391 base.BaseTempestTestCase):
392
393 same_network = None
394
395 @decorators.idempotent_id('1bdd849b-03dd-4b8f-994f-457cf8a36f93')
396 def test_floating_ip_update(self):
397 """Test updating FIP with another port.
398
399 The test creates two servers and attaches floating ip to first server.
400 Then it checks server is accesible using the FIP. FIP is then
401 associated with the second server and connectivity is checked again.
402 """
403 ports = [self.create_port(
404 self.network, security_groups=[self.secgroup['id']])
405 for i in range(2)]
406
407 servers = []
408 for port in ports:
409 name = data_utils.rand_name("server-%s" % port['id'][:8])
410 server = self.create_server(
411 name=name,
412 flavor_ref=CONF.compute.flavor_ref,
413 key_name=self.keypair['name'],
414 image_ref=CONF.compute.image_ref,
415 networks=[{'port': port['id']}])['server']
416 server['name'] = name
417 servers.append(server)
418 for server in servers:
419 self.wait_for_server_active(server)
420
421 self.fip = self.create_floatingip(port=ports[0])
422 self.check_connectivity(self.fip['floating_ip_address'],
423 CONF.validation.image_ssh_user,
Slawek Kaplonskie58219b2019-12-09 12:10:55 +0100424 self.keypair['private_key'],
425 servers=servers)
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000426 self.client.update_floatingip(self.fip['id'], port_id=ports[1]['id'])
427
428 def _wait_for_fip_associated():
429 try:
430 self.check_servers_hostnames(servers[-1:], log_errors=False)
431 except (AssertionError, exceptions.SSHTimeout):
432 return False
433 return True
434
435 # The FIP is now associated with the port of the second server.
436 try:
437 common_utils.wait_until_true(_wait_for_fip_associated,
438 timeout=15, sleep=3)
439 except common_utils.WaitTimeout:
440 self._log_console_output(servers[-1:])
441 self.fail(
442 "Server %s is not accessible via its floating ip %s" % (
443 servers[-1]['id'], self.fip['id']))