blob: 38833cd4cbb361c1b12d74c23461c33449e7d592 [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
215 @decorators.idempotent_id('a663aeee-dd81-492b-a207-354fd6284dbe')
216 def test_floatingip_port_details(self):
217 """Tests the following:
218
219 1. Create a port with floating ip in Neutron.
220 2. Create two servers in Nova.
221 3. Attach the port to the server.
222 4. Detach the port from the server.
223 5. Attach the port to the second server.
224 6. Detach the port from the second server.
225 """
226 port = self.create_port(self.network)
227 fip = self.create_and_associate_floatingip(port['id'])
228 server1 = self._create_server(create_floating_ip=False)
229 server2 = self._create_server(create_floating_ip=False)
230
231 for server in [server1, server2]:
232 # attach the port to the server
233 self.create_interface(
234 server['server']['id'], port_id=port['id'])
235 waiters.wait_for_interface_status(
236 self.os_primary.interfaces_client, server['server']['id'],
Brian Haleyaf347da2018-09-14 11:24:00 -0600237 port['id'], lib_constants.PORT_STATUS_ACTIVE)
Hongbin Lu965b03d2018-04-25 22:32:30 +0000238 fip = self.client.show_floatingip(fip['id'])['floatingip']
239 self._check_port_details(
Brian Haleyaf347da2018-09-14 11:24:00 -0600240 fip, port, status=lib_constants.PORT_STATUS_ACTIVE,
Hongbin Lu965b03d2018-04-25 22:32:30 +0000241 device_id=server['server']['id'], device_owner='compute:nova')
242
243 # detach the port from the server; this is a cast in the compute
244 # API so we have to poll the port until the device_id is unset.
245 self.delete_interface(server['server']['id'], port['id'])
Brian Haleyaf347da2018-09-14 11:24:00 -0600246 port = self._wait_for_port_detach(port['id'])
247 fip = self._wait_for_fip_port_down(fip['id'])
Hongbin Lu965b03d2018-04-25 22:32:30 +0000248 self._check_port_details(
Brian Haleyaf347da2018-09-14 11:24:00 -0600249 fip, port, status=lib_constants.PORT_STATUS_DOWN,
250 device_id='', device_owner='')
Hongbin Lu965b03d2018-04-25 22:32:30 +0000251
252 def _check_port_details(self, fip, port, status, device_id, device_owner):
253 self.assertIn('port_details', fip)
254 port_details = fip['port_details']
255 self.assertEqual(port['name'], port_details['name'])
256 self.assertEqual(port['network_id'], port_details['network_id'])
257 self.assertEqual(port['mac_address'], port_details['mac_address'])
258 self.assertEqual(port['admin_state_up'],
259 port_details['admin_state_up'])
260 self.assertEqual(status, port_details['status'])
261 self.assertEqual(device_id, port_details['device_id'])
262 self.assertEqual(device_owner, port_details['device_owner'])
263
264 def _wait_for_port_detach(self, port_id, timeout=120, interval=10):
265 """Waits for the port's device_id to be unset.
266
267 :param port_id: The id of the port being detached.
268 :returns: The final port dict from the show_port response.
269 """
270 port = self.client.show_port(port_id)['port']
271 device_id = port['device_id']
272 start = int(time.time())
273
274 # NOTE(mriedem): Nova updates the port's device_id to '' rather than
275 # None, but it's not contractual so handle Falsey either way.
276 while device_id:
277 time.sleep(interval)
278 port = self.client.show_port(port_id)['port']
279 device_id = port['device_id']
280
281 timed_out = int(time.time()) - start >= timeout
282
283 if device_id and timed_out:
284 message = ('Port %s failed to detach (device_id %s) within '
285 'the required time (%s s).' %
286 (port_id, device_id, timeout))
287 raise exceptions.TimeoutException(message)
288
289 return port
290
Brian Haleyaf347da2018-09-14 11:24:00 -0600291 def _wait_for_fip_port_down(self, fip_id, timeout=120, interval=10):
292 """Waits for the fip's attached port status to be 'DOWN'.
293
294 :param fip_id: The id of the floating IP.
295 :returns: The final fip dict from the show_floatingip response.
296 """
297 fip = self.client.show_floatingip(fip_id)['floatingip']
298 self.assertIn('port_details', fip)
299 port_details = fip['port_details']
300 status = port_details['status']
301 start = int(time.time())
302
303 while status != lib_constants.PORT_STATUS_DOWN:
304 time.sleep(interval)
305 fip = self.client.show_floatingip(fip_id)['floatingip']
306 self.assertIn('port_details', fip)
307 port_details = fip['port_details']
308 status = port_details['status']
309
310 timed_out = int(time.time()) - start >= timeout
311
312 if status != lib_constants.PORT_STATUS_DOWN and timed_out:
313 message = ('Floating IP %s attached port status failed to '
314 'transition to DOWN (current status %s) within '
315 'the required time (%s s).' %
316 (fip_id, status, timeout))
317 raise exceptions.TimeoutException(message)
318
319 return fip
320
Hongbin Lu965b03d2018-04-25 22:32:30 +0000321
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800322class FloatingIPQosTest(FloatingIpTestCasesMixin,
YAMAMOTO Takashia2cc2e52018-07-31 18:54:02 +0900323 test_qos.QoSTestMixin,
324 base.BaseTempestTestCase):
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800325
326 same_network = True
327
328 @classmethod
329 @utils.requires_ext(extension="router", service="network")
330 @utils.requires_ext(extension="qos", service="network")
YAMAMOTO Takashi9c072a02018-03-22 22:49:09 +0900331 @utils.requires_ext(extension="qos-fip", service="network")
LIU Yulong5ba88ef2017-12-22 10:50:15 +0800332 @base_api.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
333 def resource_setup(cls):
334 super(FloatingIPQosTest, cls).resource_setup()
335
336 @decorators.idempotent_id('5eb48aea-eaba-4c20-8a6f-7740070a0aa3')
337 def test_qos(self):
338 """Test floating IP is binding to a QoS policy with
339
340 ingress and egress bandwidth limit rules. And it applied correctly
341 by sending a file from the instance to the test node.
342 Then calculating the bandwidth every ~1 sec by the number of bits
343 received / elapsed time.
344 """
345
346 self._test_basic_resources()
347 policy_id = self._create_qos_policy()
348 ssh_client = self._create_ssh_client()
349 self.os_admin.network_client.create_bandwidth_limit_rule(
350 policy_id, max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
351 max_burst_kbps=constants.LIMIT_KILO_BYTES,
352 direction=lib_constants.INGRESS_DIRECTION)
353 self.os_admin.network_client.create_bandwidth_limit_rule(
354 policy_id, max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
355 max_burst_kbps=constants.LIMIT_KILO_BYTES,
356 direction=lib_constants.EGRESS_DIRECTION)
357
358 rules = self.os_admin.network_client.list_bandwidth_limit_rules(
359 policy_id)
360 self.assertEqual(2, len(rules['bandwidth_limit_rules']))
361
362 fip = self.os_admin.network_client.get_floatingip(
363 self.fip['id'])['floatingip']
364 self.assertEqual(self.port['id'], fip['port_id'])
365
366 self.os_admin.network_client.update_floatingip(
367 self.fip['id'],
368 qos_policy_id=policy_id)
369
370 fip = self.os_admin.network_client.get_floatingip(
371 self.fip['id'])['floatingip']
372 self.assertEqual(policy_id, fip['qos_policy_id'])
373
374 self._create_file_for_bw_tests(ssh_client)
375 common_utils.wait_until_true(lambda: self._check_bw(
376 ssh_client,
377 self.fip['floating_ip_address'],
378 port=self.NC_PORT),
379 timeout=120,
380 sleep=1)