blob: 15a28371578a71e0a23ea66d98d3508cd428c685 [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
Chandan Kumarc125fd12017-11-15 19:41:01 +053020from tempest.common import utils
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090021from tempest.common import waiters
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090022from tempest.lib.common.utils import data_utils
Sławek Kapłońskic0caa2e2017-02-25 10:11:32 +000023from tempest.lib import decorators
Slawek Kaplonski168e5012018-10-04 14:31:19 +020024from tempest.lib import exceptions
Ihar Hrachyshkace9c4862018-01-18 18:26:14 +000025import testscenarios
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +090026from testscenarios.scenarios import multiply_scenarios
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090027
LIU Yulong5ba88ef2017-12-22 10:50:15 +080028from neutron_tempest_plugin.api import base as base_api
Chandan Kumar667d3d32017-09-22 12:24:06 +053029from neutron_tempest_plugin.common import ssh
Brian Haleyba800452017-12-14 10:30:48 -050030from neutron_tempest_plugin.common import utils as common_utils
Chandan Kumar667d3d32017-09-22 12:24:06 +053031from neutron_tempest_plugin import config
32from neutron_tempest_plugin.scenario import base
33from neutron_tempest_plugin.scenario import constants
LIU Yulong5ba88ef2017-12-22 10:50:15 +080034from neutron_tempest_plugin.scenario import test_qos
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090035
36
37CONF = config.CONF
38
39
40load_tests = testscenarios.load_tests_apply_scenarios
41
42
43class 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
103 def _test_east_west(self):
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.
106 if self.src_has_fip:
107 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
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900116 if self.src_has_fip:
117 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
128 if self.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,
136 dest_server['port']['fixed_ips'][0]['ip_address'])
137 if self.dest_has_fip:
138 self.check_remote_connectivity(ssh_client,
139 dest_server['fip']['floating_ip_address'])
140
141
142class FloatingIpSameNetwork(FloatingIpTestCasesMixin,
143 base.BaseTempestTestCase):
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900144 scenarios = multiply_scenarios([
145 ('SRC with FIP', dict(src_has_fip=True)),
146 ('SRC without FIP', dict(src_has_fip=False)),
147 ], [
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900148 ('DEST with FIP', dict(dest_has_fip=True)),
149 ('DEST without FIP', dict(dest_has_fip=False)),
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900150 ])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900151
152 same_network = True
153
Brian Haleyba800452017-12-14 10:30:48 -0500154 @common_utils.unstable_test("bug 1717302")
Sławek Kapłońskic0caa2e2017-02-25 10:11:32 +0000155 @decorators.idempotent_id('05c4e3b3-7319-4052-90ad-e8916436c23b')
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900156 def test_east_west(self):
157 self._test_east_west()
158
159
160class FloatingIpSeparateNetwork(FloatingIpTestCasesMixin,
161 base.BaseTempestTestCase):
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900162 scenarios = multiply_scenarios([
163 ('SRC with FIP', dict(src_has_fip=True)),
164 ('SRC without FIP', dict(src_has_fip=False)),
165 ], [
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900166 ('DEST with FIP', dict(dest_has_fip=True)),
167 ('DEST without FIP', dict(dest_has_fip=False)),
YAMAMOTO Takashi60faf4f2017-01-25 08:03:07 +0900168 ])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900169
170 same_network = False
171
Brian Haleyba800452017-12-14 10:30:48 -0500172 @common_utils.unstable_test("bug 1717302")
Sławek Kapłońskic0caa2e2017-02-25 10:11:32 +0000173 @decorators.idempotent_id('f18f0090-3289-4783-b956-a0f8ac511e8b')
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900174 def test_east_west(self):
175 self._test_east_west()
Itzik Browna31510f2018-01-19 11:09:48 +0200176
177
178class DefaultSnatToExternal(FloatingIpTestCasesMixin,
179 base.BaseTempestTestCase):
180 same_network = True
181
182 @decorators.idempotent_id('3d73ea1a-27c6-45a9-b0f8-04a283d9d764')
183 def test_snat_external_ip(self):
184 """Check connectivity to an external IP"""
185 gateway_external_ip = self._get_external_gateway()
186
187 if not gateway_external_ip:
188 raise self.skipTest("IPv4 gateway is not configured for public "
189 "network or public_network_id is not "
190 "configured")
191 proxy = self._create_server()
192 proxy_client = ssh.Client(proxy['fip']['floating_ip_address'],
193 CONF.validation.image_ssh_user,
194 pkey=self.keypair['private_key'])
195 src_server = self._create_server(create_floating_ip=False)
196 src_server_ip = src_server['port']['fixed_ips'][0]['ip_address']
197 ssh_client = ssh.Client(src_server_ip,
198 CONF.validation.image_ssh_user,
199 pkey=self.keypair['private_key'],
200 proxy_client=proxy_client)
201 self.check_remote_connectivity(ssh_client,
202 gateway_external_ip)
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800203
204
Hongbin Lu965b03d2018-04-25 22:32:30 +0000205class FloatingIPPortDetailsTest(FloatingIpTestCasesMixin,
206 base.BaseTempestTestCase):
207 same_network = True
208
209 @classmethod
210 @utils.requires_ext(extension="router", service="network")
211 @utils.requires_ext(extension="fip-port-details", service="network")
212 def resource_setup(cls):
213 super(FloatingIPPortDetailsTest, cls).resource_setup()
214
Slawek Kaplonski5ddcf332019-03-26 18:00:58 +0100215 @common_utils.unstable_test("bug 1815585")
Hongbin Lu965b03d2018-04-25 22:32:30 +0000216 @decorators.idempotent_id('a663aeee-dd81-492b-a207-354fd6284dbe')
217 def test_floatingip_port_details(self):
218 """Tests the following:
219
220 1. Create a port with floating ip in Neutron.
221 2. Create two servers in Nova.
222 3. Attach the port to the server.
223 4. Detach the port from the server.
224 5. Attach the port to the second server.
225 6. Detach the port from the second server.
226 """
227 port = self.create_port(self.network)
228 fip = self.create_and_associate_floatingip(port['id'])
229 server1 = self._create_server(create_floating_ip=False)
230 server2 = self._create_server(create_floating_ip=False)
231
232 for server in [server1, server2]:
233 # attach the port to the server
234 self.create_interface(
235 server['server']['id'], port_id=port['id'])
236 waiters.wait_for_interface_status(
237 self.os_primary.interfaces_client, server['server']['id'],
Brian Haleyaf347da2018-09-14 11:24:00 -0600238 port['id'], lib_constants.PORT_STATUS_ACTIVE)
Hongbin Lu965b03d2018-04-25 22:32:30 +0000239 fip = self.client.show_floatingip(fip['id'])['floatingip']
240 self._check_port_details(
Brian Haleyaf347da2018-09-14 11:24:00 -0600241 fip, port, status=lib_constants.PORT_STATUS_ACTIVE,
Hongbin Lu965b03d2018-04-25 22:32:30 +0000242 device_id=server['server']['id'], device_owner='compute:nova')
243
244 # detach the port from the server; this is a cast in the compute
245 # API so we have to poll the port until the device_id is unset.
246 self.delete_interface(server['server']['id'], port['id'])
Brian Haleyaf347da2018-09-14 11:24:00 -0600247 port = self._wait_for_port_detach(port['id'])
248 fip = self._wait_for_fip_port_down(fip['id'])
Hongbin Lu965b03d2018-04-25 22:32:30 +0000249 self._check_port_details(
Brian Haleyaf347da2018-09-14 11:24:00 -0600250 fip, port, status=lib_constants.PORT_STATUS_DOWN,
251 device_id='', device_owner='')
Hongbin Lu965b03d2018-04-25 22:32:30 +0000252
253 def _check_port_details(self, fip, port, status, device_id, device_owner):
254 self.assertIn('port_details', fip)
255 port_details = fip['port_details']
256 self.assertEqual(port['name'], port_details['name'])
257 self.assertEqual(port['network_id'], port_details['network_id'])
258 self.assertEqual(port['mac_address'], port_details['mac_address'])
259 self.assertEqual(port['admin_state_up'],
260 port_details['admin_state_up'])
261 self.assertEqual(status, port_details['status'])
262 self.assertEqual(device_id, port_details['device_id'])
263 self.assertEqual(device_owner, port_details['device_owner'])
264
265 def _wait_for_port_detach(self, port_id, timeout=120, interval=10):
266 """Waits for the port's device_id to be unset.
267
268 :param port_id: The id of the port being detached.
269 :returns: The final port dict from the show_port response.
270 """
271 port = self.client.show_port(port_id)['port']
272 device_id = port['device_id']
273 start = int(time.time())
274
275 # NOTE(mriedem): Nova updates the port's device_id to '' rather than
276 # None, but it's not contractual so handle Falsey either way.
277 while device_id:
278 time.sleep(interval)
279 port = self.client.show_port(port_id)['port']
280 device_id = port['device_id']
281
282 timed_out = int(time.time()) - start >= timeout
283
284 if device_id and timed_out:
285 message = ('Port %s failed to detach (device_id %s) within '
286 'the required time (%s s).' %
287 (port_id, device_id, timeout))
288 raise exceptions.TimeoutException(message)
289
290 return port
291
Brian Haleyaf347da2018-09-14 11:24:00 -0600292 def _wait_for_fip_port_down(self, fip_id, timeout=120, interval=10):
293 """Waits for the fip's attached port status to be 'DOWN'.
294
295 :param fip_id: The id of the floating IP.
296 :returns: The final fip dict from the show_floatingip response.
297 """
298 fip = self.client.show_floatingip(fip_id)['floatingip']
299 self.assertIn('port_details', fip)
300 port_details = fip['port_details']
301 status = port_details['status']
302 start = int(time.time())
303
304 while status != lib_constants.PORT_STATUS_DOWN:
305 time.sleep(interval)
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
311 timed_out = int(time.time()) - start >= timeout
312
313 if status != lib_constants.PORT_STATUS_DOWN and timed_out:
Slawek Kaplonski7451ad72019-02-25 12:02:49 +0100314 port_id = fip.get("port_id")
315 port = self.os_admin.network_client.show_port(port_id)['port']
Brian Haleyaf347da2018-09-14 11:24:00 -0600316 message = ('Floating IP %s attached port status failed to '
317 'transition to DOWN (current status %s) within '
Slawek Kaplonski7451ad72019-02-25 12:02:49 +0100318 'the required time (%s s). Port details: %s' %
319 (fip_id, status, timeout, port))
Brian Haleyaf347da2018-09-14 11:24:00 -0600320 raise exceptions.TimeoutException(message)
321
322 return fip
323
Hongbin Lu965b03d2018-04-25 22:32:30 +0000324
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800325class FloatingIPQosTest(FloatingIpTestCasesMixin,
YAMAMOTO Takashia2cc2e52018-07-31 18:54:02 +0900326 test_qos.QoSTestMixin,
327 base.BaseTempestTestCase):
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800328
329 same_network = True
330
331 @classmethod
332 @utils.requires_ext(extension="router", service="network")
333 @utils.requires_ext(extension="qos", service="network")
YAMAMOTO Takashi9c072a02018-03-22 22:49:09 +0900334 @utils.requires_ext(extension="qos-fip", service="network")
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800335 @base_api.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
336 def resource_setup(cls):
337 super(FloatingIPQosTest, cls).resource_setup()
338
339 @decorators.idempotent_id('5eb48aea-eaba-4c20-8a6f-7740070a0aa3')
340 def test_qos(self):
341 """Test floating IP is binding to a QoS policy with
342
343 ingress and egress bandwidth limit rules. And it applied correctly
344 by sending a file from the instance to the test node.
345 Then calculating the bandwidth every ~1 sec by the number of bits
346 received / elapsed time.
347 """
348
349 self._test_basic_resources()
350 policy_id = self._create_qos_policy()
351 ssh_client = self._create_ssh_client()
352 self.os_admin.network_client.create_bandwidth_limit_rule(
353 policy_id, max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
354 max_burst_kbps=constants.LIMIT_KILO_BYTES,
355 direction=lib_constants.INGRESS_DIRECTION)
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.EGRESS_DIRECTION)
360
361 rules = self.os_admin.network_client.list_bandwidth_limit_rules(
362 policy_id)
363 self.assertEqual(2, len(rules['bandwidth_limit_rules']))
364
365 fip = self.os_admin.network_client.get_floatingip(
366 self.fip['id'])['floatingip']
367 self.assertEqual(self.port['id'], fip['port_id'])
368
369 self.os_admin.network_client.update_floatingip(
370 self.fip['id'],
371 qos_policy_id=policy_id)
372
373 fip = self.os_admin.network_client.get_floatingip(
374 self.fip['id'])['floatingip']
375 self.assertEqual(policy_id, fip['qos_policy_id'])
376
377 self._create_file_for_bw_tests(ssh_client)
378 common_utils.wait_until_true(lambda: self._check_bw(
379 ssh_client,
380 self.fip['floating_ip_address'],
381 port=self.NC_PORT),
382 timeout=120,
383 sleep=1)