blob: 585af063b7bc688099d9a19f65a1324b87e845dc [file] [log] [blame]
Kevin Bentona305d592016-09-19 04:26:10 -07001# All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
Federico Ressi06ef8542018-10-25 15:23:52 +020015import collections
16
17from neutron_lib import constants
Kevin Benton07c90562017-02-27 01:53:16 -080018from oslo_log import log as logging
Chandan Kumarc125fd12017-11-15 19:41:01 +053019from tempest.common import utils as tutils
Itzik Brownbac51dc2016-10-31 12:25:04 +000020from tempest.lib.common.utils import data_utils
Sławek Kapłońskic0caa2e2017-02-25 10:11:32 +000021from tempest.lib import decorators
Genadi Chereshnyae91b69c2017-07-16 09:51:58 +030022import testtools
Kevin Bentona305d592016-09-19 04:26:10 -070023
Federico Ressi06ef8542018-10-25 15:23:52 +020024from neutron_tempest_plugin.common import ip
Chandan Kumar667d3d32017-09-22 12:24:06 +053025from neutron_tempest_plugin.common import ssh
26from neutron_tempest_plugin.common import utils
27from neutron_tempest_plugin import config
28from neutron_tempest_plugin.scenario import base
Federico Ressi06ef8542018-10-25 15:23:52 +020029
Kevin Bentona305d592016-09-19 04:26:10 -070030
Kevin Benton07c90562017-02-27 01:53:16 -080031LOG = logging.getLogger(__name__)
Kevin Bentona305d592016-09-19 04:26:10 -070032CONF = config.CONF
Kevin Bentona305d592016-09-19 04:26:10 -070033
Federico Ressi06ef8542018-10-25 15:23:52 +020034
35ServerWithTrunkPort = collections.namedtuple(
36 'ServerWithTrunkPort',
37 ['port', 'subport', 'trunk', 'floating_ip', 'server',
38 'ssh_client'])
Jakub Libosvar6d397d32016-12-30 10:57:52 -050039
40
Kevin Bentona305d592016-09-19 04:26:10 -070041class TrunkTest(base.BaseTempestTestCase):
Alex Katzbaf14a52020-03-05 11:31:19 +020042 credentials = ['primary', 'admin']
Kevin Bentona305d592016-09-19 04:26:10 -070043 force_tenant_isolation = False
44
45 @classmethod
Chandan Kumarc125fd12017-11-15 19:41:01 +053046 @tutils.requires_ext(extension="trunk", service="network")
Kevin Bentona305d592016-09-19 04:26:10 -070047 def resource_setup(cls):
48 super(TrunkTest, cls).resource_setup()
49 # setup basic topology for servers we can log into
Federico Ressi06ef8542018-10-25 15:23:52 +020050 cls.rand_name = data_utils.rand_name(
51 cls.__name__.rsplit('.', 1)[-1])
52 cls.network = cls.create_network(name=cls.rand_name)
53 cls.subnet = cls.create_subnet(network=cls.network,
54 name=cls.rand_name)
Huifeng Le1c9f40b2018-11-07 01:14:21 +080055 cls.router = cls.create_router_by_client()
56 cls.create_router_interface(cls.router['id'], cls.subnet['id'])
Federico Ressi06ef8542018-10-25 15:23:52 +020057 cls.keypair = cls.create_keypair(name=cls.rand_name)
Kevin Bentona305d592016-09-19 04:26:10 -070058
Federico Ressi06ef8542018-10-25 15:23:52 +020059 def setUp(self):
60 super(TrunkTest, self).setUp()
61 self.security_group = self.create_security_group(name=self.rand_name)
62 self.create_loginable_secgroup_rule(self.security_group['id'])
Kevin Bentona305d592016-09-19 04:26:10 -070063
Federico Ressi06ef8542018-10-25 15:23:52 +020064 def _create_server_with_network(self, network, use_advanced_image=False):
65 port = self._create_server_port(network=network)
66 floating_ip = self.create_floatingip(port=port)
67 ssh_client = self._create_ssh_client(
68 floating_ip=floating_ip, use_advanced_image=use_advanced_image)
69 server = self._create_server(port=port,
70 use_advanced_image=use_advanced_image)
71 return ServerWithTrunkPort(port=port, subport=None, trunk=None,
72 floating_ip=floating_ip, server=server,
73 ssh_client=ssh_client)
74
75 def _create_server_with_trunk_port(self, subport_network=None,
76 segmentation_id=None,
77 use_advanced_image=False):
78 port = self._create_server_port()
79 floating_ip = self.create_floatingip(port=port)
80 ssh_client = self._create_ssh_client(
81 floating_ip=floating_ip, use_advanced_image=use_advanced_image)
82
83 subport = None
84 subports = None
85 if subport_network:
86 subport = self._create_server_port(
87 network=subport_network, mac_address=port['mac_address'])
88 subports = [{'port_id': subport['id'],
89 'segmentation_type': 'vlan',
90 'segmentation_id': segmentation_id}]
91 trunk = self.create_trunk(port=port, subports=subports)
92
93 server = self._create_server(port=port,
94 use_advanced_image=use_advanced_image)
95 return ServerWithTrunkPort(port=port, subport=subport, trunk=trunk,
96 floating_ip=floating_ip, server=server,
97 ssh_client=ssh_client)
98
99 def _create_server_port(self, network=None, **params):
100 network = network or self.network
101 return self.create_port(network=network, name=self.rand_name,
102 security_groups=[self.security_group['id']],
103 **params)
104
105 def _create_server(self, port, use_advanced_image=False, **params):
Slawek Kaplonskida17f002018-10-11 18:35:23 +0200106 if use_advanced_image:
107 flavor_ref = CONF.neutron_plugin_options.advanced_image_flavor_ref
108 image_ref = CONF.neutron_plugin_options.advanced_image_ref
Federico Ressi06ef8542018-10-25 15:23:52 +0200109 else:
110 flavor_ref = CONF.compute.flavor_ref
111 image_ref = CONF.compute.image_ref
112 return self.create_server(flavor_ref=flavor_ref,
113 image_ref=image_ref,
114 key_name=self.keypair['name'],
115 networks=[{'port': port['id']}],
116 **params)['server']
Jakub Libosvar6d397d32016-12-30 10:57:52 -0500117
Federico Ressi06ef8542018-10-25 15:23:52 +0200118 def _show_port(self, port, update=False):
119 observed = self.client.show_port(port['id'])['port']
120 if update:
121 port.update(observed)
122 return observed
Kevin Bentona305d592016-09-19 04:26:10 -0700123
Federico Ressi06ef8542018-10-25 15:23:52 +0200124 def _show_trunk(self, trunk, update=False):
125 observed = self.client.show_trunk(trunk['id'])['trunk']
126 if update:
127 trunk.update(observed)
128 return observed
Kevin Bentona305d592016-09-19 04:26:10 -0700129
Federico Ressi06ef8542018-10-25 15:23:52 +0200130 def _is_trunk_status(self, trunk, status, update=False):
131 return self._show_trunk(trunk, update)['status'] == status
Kevin Bentona305d592016-09-19 04:26:10 -0700132
Federico Ressi06ef8542018-10-25 15:23:52 +0200133 def _is_port_status(self, port, status, update=False):
134 return self._show_port(port, update)['status'] == status
135
136 def _wait_for_port(self, port, status=constants.ACTIVE):
137 utils.wait_until_true(
138 lambda: self._is_port_status(port, status),
139 exception=RuntimeError(
140 "Timed out waiting for port {!r} to transition to get "
141 "status {!r}.".format(port['id'], status)))
142
143 def _wait_for_trunk(self, trunk, status=constants.ACTIVE):
144 utils.wait_until_true(
145 lambda: self._is_trunk_status(trunk, status),
146 exception=RuntimeError(
147 "Timed out waiting for trunk {!r} to transition to get "
148 "status {!r}.".format(trunk['id'], status)))
149
150 def _create_ssh_client(self, floating_ip, use_advanced_image=False):
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800151 if use_advanced_image:
Federico Ressi06ef8542018-10-25 15:23:52 +0200152 username = CONF.neutron_plugin_options.advanced_image_ssh_user
153 else:
154 username = CONF.validation.image_ssh_user
155 return ssh.Client(host=floating_ip['floating_ip_address'],
156 username=username,
157 pkey=self.keypair['private_key'])
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800158
Federico Ressi06ef8542018-10-25 15:23:52 +0200159 def _assert_has_ssh_connectivity(self, ssh_client):
160 ssh_client.exec_command("true")
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800161
Federico Ressi06ef8542018-10-25 15:23:52 +0200162 def _configure_vlan_subport(self, vm, vlan_tag, vlan_subnet):
163 self.wait_for_server_active(server=vm.server)
164 self._wait_for_trunk(trunk=vm.trunk)
165 self._wait_for_port(port=vm.port)
166 self._wait_for_port(port=vm.subport)
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800167
Federico Ressi06ef8542018-10-25 15:23:52 +0200168 ip_command = ip.IPCommand(ssh_client=vm.ssh_client)
169 for address in ip_command.list_addresses(port=vm.port):
170 port_iface = address.device.name
171 break
172 else:
173 self.fail("Parent port fixed IP not found on server.")
Jakub Libosvar6d397d32016-12-30 10:57:52 -0500174
Federico Ressi06ef8542018-10-25 15:23:52 +0200175 subport_iface = ip_command.configure_vlan_subport(
176 port=vm.port, subport=vm.subport, vlan_tag=vlan_tag,
177 subnets=[vlan_subnet])
178 for address in ip_command.list_addresses(port=vm.subport):
179 self.assertEqual(subport_iface, address.device.name)
180 self.assertEqual(port_iface, address.device.parent)
181 break
182 else:
183 self.fail("Sub-port fixed IP not found on server.")
Jakub Libosvar6d397d32016-12-30 10:57:52 -0500184
Sławek Kapłońskic0caa2e2017-02-25 10:11:32 +0000185 @decorators.idempotent_id('bb13fe28-f152-4000-8131-37890a40c79e')
Kevin Bentona305d592016-09-19 04:26:10 -0700186 def test_trunk_subport_lifecycle(self):
187 """Test trunk creation and subport transition to ACTIVE status.
188
189 This is a basic test for the trunk extension to ensure that we
190 can create a trunk, attach it to a server, add/remove subports,
191 while ensuring the status transitions as appropriate.
192
193 This test does not assert any dataplane behavior for the subports.
194 It's just a high-level check to ensure the agents claim to have
195 wired the port correctly and that the trunk port itself maintains
196 connectivity.
197 """
Federico Ressi06ef8542018-10-25 15:23:52 +0200198 vm1 = self._create_server_with_trunk_port()
199 vm2 = self._create_server_with_trunk_port()
200 for vm in (vm1, vm2):
201 self.wait_for_server_active(server=vm.server)
202 self._wait_for_trunk(vm.trunk)
203 self._assert_has_ssh_connectivity(vm.ssh_client)
204
Kevin Bentona305d592016-09-19 04:26:10 -0700205 # create a few more networks and ports for subports
Yariv Rachmanifed6f862017-12-19 11:55:25 +0200206 # check limit of networks per project
Federico Ressi06ef8542018-10-25 15:23:52 +0200207 segment_ids = range(
208 3, 3 + CONF.neutron_plugin_options.max_networks_per_project)
209 tagged_networks = [self.create_network() for _ in segment_ids]
210 tagged_ports = [self.create_port(network=network)
211 for network in tagged_networks]
212 subports = [{'port_id': tagged_ports[i]['id'],
213 'segmentation_type': 'vlan',
214 'segmentation_id': segment_id}
215 for i, segment_id in enumerate(segment_ids)]
Jakub Libosvar6d397d32016-12-30 10:57:52 -0500216
Federico Ressi06ef8542018-10-25 15:23:52 +0200217 # add all subports to server1
218 self.client.add_subports(vm1.trunk['id'], subports)
219 self._wait_for_trunk(vm1.trunk)
220 for port in tagged_ports:
221 self._wait_for_port(port)
222
223 # ensure main data-plane wasn't interrupted
224 self._assert_has_ssh_connectivity(vm1.ssh_client)
225
226 # move subports over to other server
227 self.client.remove_subports(vm1.trunk['id'], subports)
228 # ensure all subports go down
229 for port in tagged_ports:
230 self._wait_for_port(port, status=constants.DOWN)
231
232 self.client.add_subports(vm2.trunk['id'], subports)
233
234 # wait for both trunks to go back to ACTIVE
235 for vm in [vm1, vm2]:
236 self._wait_for_trunk(vm.trunk)
237
238 # ensure subports come up on other trunk
239 for port in tagged_ports:
240 self._wait_for_port(port)
241
242 # final connectivity check
243 for vm in [vm1, vm2]:
244 self._wait_for_trunk(vm.trunk)
245 self._assert_has_ssh_connectivity(vm1.ssh_client)
246
Yariv Rachmanifa1081a2018-11-21 12:46:57 +0200247 @testtools.skipUnless(
248 (CONF.neutron_plugin_options.advanced_image_ref or
249 CONF.neutron_plugin_options.default_image_is_advanced),
250 "Advanced image is required to run this test.")
Sławek Kapłońskic0caa2e2017-02-25 10:11:32 +0000251 @decorators.idempotent_id('a8a02c9b-b453-49b5-89a2-cce7da66aafb')
Jakub Libosvar6d397d32016-12-30 10:57:52 -0500252 def test_subport_connectivity(self):
253 vlan_tag = 10
Jakub Libosvar6d397d32016-12-30 10:57:52 -0500254 vlan_network = self.create_network()
Federico Ressi06ef8542018-10-25 15:23:52 +0200255 vlan_subnet = self.create_subnet(network=vlan_network, gateway=None)
Jakub Libosvar6d397d32016-12-30 10:57:52 -0500256
Yariv Rachmanifa1081a2018-11-21 12:46:57 +0200257 use_advanced_image = (
258 not CONF.neutron_plugin_options.default_image_is_advanced)
259
260 vm1 = self._create_server_with_trunk_port(
261 subport_network=vlan_network,
262 segmentation_id=vlan_tag,
263 use_advanced_image=use_advanced_image)
264 vm2 = self._create_server_with_trunk_port(
265 subport_network=vlan_network,
266 segmentation_id=vlan_tag,
267 use_advanced_image=use_advanced_image)
Jakub Libosvar6d397d32016-12-30 10:57:52 -0500268
Federico Ressi06ef8542018-10-25 15:23:52 +0200269 for vm in [vm1, vm2]:
270 self._configure_vlan_subport(vm=vm,
271 vlan_tag=vlan_tag,
272 vlan_subnet=vlan_subnet)
Jakub Libosvar6d397d32016-12-30 10:57:52 -0500273
Kevin Benton6f1f9d52017-03-01 09:14:45 -0800274 # Ping from server1 to server2 via VLAN interface should fail because
275 # we haven't allowed ICMP
276 self.check_remote_connectivity(
Federico Ressi06ef8542018-10-25 15:23:52 +0200277 vm1.ssh_client,
278 vm2.subport['fixed_ips'][0]['ip_address'],
279 should_succeed=False)
280
281 # allow intra-security-group traffic
Alex Katzbaf14a52020-03-05 11:31:19 +0200282 sg_rule = self.create_pingable_secgroup_rule(self.security_group['id'])
283 self.addCleanup(
284 self.os_primary.network_client.delete_security_group_rule,
285 sg_rule['id'])
Kevin Benton6f1f9d52017-03-01 09:14:45 -0800286 self.check_remote_connectivity(
Federico Ressi06ef8542018-10-25 15:23:52 +0200287 vm1.ssh_client,
Slawek Kaplonskie58219b2019-12-09 12:10:55 +0100288 vm2.subport['fixed_ips'][0]['ip_address'],
289 servers=[vm1, vm2])
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800290
Alex Katzbaf14a52020-03-05 11:31:19 +0200291 @testtools.skipUnless(CONF.compute_feature_enabled.cold_migration,
292 'Cold migration is not available.')
293 @testtools.skipUnless(CONF.compute.min_compute_nodes > 1,
294 'Less than 2 compute nodes, skipping multinode '
295 'tests.')
296 @testtools.skipUnless(
297 (CONF.neutron_plugin_options.advanced_image_ref or
298 CONF.neutron_plugin_options.default_image_is_advanced),
299 "Advanced image is required to run this test.")
300 @decorators.attr(type='slow')
301 @decorators.idempotent_id('ecd7de30-1c90-4280-b97c-1bed776d5d07')
302 def test_trunk_vm_migration(self):
303 '''Test connectivity after migration of the server with trunk
304
305 A successfully migrated server shows a VERIFY_RESIZE status that
306 requires confirmation. Need to reconfigure VLAN interface on server
307 side after migration is finished as the configuration doesn't survive
308 the reboot.
309 '''
310 vlan_tag = 10
311 vlan_network = self.create_network()
312 vlan_subnet = self.create_subnet(vlan_network)
313 sg_rule = self.create_pingable_secgroup_rule(self.security_group['id'])
314 self.addCleanup(
315 self.os_primary.network_client.delete_security_group_rule,
316 sg_rule['id'])
317
318 use_advanced_image = (
319 not CONF.neutron_plugin_options.default_image_is_advanced)
320 servers = {}
321 for role in ['migrate', 'connection_test']:
322 servers[role] = self._create_server_with_trunk_port(
323 subport_network=vlan_network,
324 segmentation_id=vlan_tag,
325 use_advanced_image=use_advanced_image)
326 for role in ['migrate', 'connection_test']:
327 self.wait_for_server_active(servers[role].server)
328 self._configure_vlan_subport(vm=servers[role],
329 vlan_tag=vlan_tag,
330 vlan_subnet=vlan_subnet)
331
332 self.check_remote_connectivity(
333 servers['connection_test'].ssh_client,
334 servers['migrate'].subport['fixed_ips'][0]['ip_address'])
335
336 client = self.os_admin.compute.ServersClient()
337 client.migrate_server(servers['migrate'].server['id'])
338 self.wait_for_server_status(servers['migrate'].server,
339 'VERIFY_RESIZE')
340 client.confirm_resize_server(servers['migrate'].server['id'])
341 self._configure_vlan_subport(vm=servers['migrate'],
342 vlan_tag=vlan_tag,
343 vlan_subnet=vlan_subnet)
344
345 self.check_remote_connectivity(
346 servers['connection_test'].ssh_client,
347 servers['migrate'].subport['fixed_ips'][0]['ip_address'])
348
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800349 @testtools.skipUnless(
Yariv Rachmanifa1081a2018-11-21 12:46:57 +0200350 (CONF.neutron_plugin_options.advanced_image_ref or
351 CONF.neutron_plugin_options.default_image_is_advanced),
Federico Ressi06ef8542018-10-25 15:23:52 +0200352 "Advanced image is required to run this test.")
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800353 @testtools.skipUnless(
Federico Ressi06ef8542018-10-25 15:23:52 +0200354 CONF.neutron_plugin_options.q_agent == "linuxbridge",
355 "Linux bridge agent is required to run this test.")
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800356 @decorators.idempotent_id('d61cbdf6-1896-491c-b4b4-871caf7fbffe')
357 def test_parent_port_connectivity_after_trunk_deleted_lb(self):
358 vlan_tag = 10
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800359 vlan_network = self.create_network()
360 vlan_subnet = self.create_subnet(vlan_network)
361 self.create_router_interface(self.router['id'], vlan_subnet['id'])
362
Yariv Rachmanifa1081a2018-11-21 12:46:57 +0200363 use_advanced_image = (
364 not CONF.neutron_plugin_options.default_image_is_advanced)
365
Federico Ressi06ef8542018-10-25 15:23:52 +0200366 # Create servers
367 trunk_network_server = self._create_server_with_trunk_port(
368 subport_network=vlan_network,
369 segmentation_id=vlan_tag,
Yariv Rachmanifa1081a2018-11-21 12:46:57 +0200370 use_advanced_image=use_advanced_image)
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800371 normal_network_server = self._create_server_with_network(self.network)
372 vlan_network_server = self._create_server_with_network(vlan_network)
Slawek Kaplonskie58219b2019-12-09 12:10:55 +0100373 vms = [normal_network_server, vlan_network_server]
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800374
Federico Ressi06ef8542018-10-25 15:23:52 +0200375 self._configure_vlan_subport(vm=trunk_network_server,
376 vlan_tag=vlan_tag,
377 vlan_subnet=vlan_subnet)
Slawek Kaplonskie58219b2019-12-09 12:10:55 +0100378 for vm in vms:
Federico Ressi06ef8542018-10-25 15:23:52 +0200379 self.wait_for_server_active(vm.server)
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800380
Federico Ressi06ef8542018-10-25 15:23:52 +0200381 # allow ICMP traffic
Alex Katzbaf14a52020-03-05 11:31:19 +0200382 sg_rule = self.create_pingable_secgroup_rule(self.security_group['id'])
383 self.addCleanup(
384 self.os_primary.network_client.delete_security_group_rule,
385 sg_rule['id'])
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800386
387 # Ping from trunk_network_server to normal_network_server
388 # via parent port
389 self.check_remote_connectivity(
Federico Ressi06ef8542018-10-25 15:23:52 +0200390 trunk_network_server.ssh_client,
391 normal_network_server.port['fixed_ips'][0]['ip_address'],
Slawek Kaplonskie58219b2019-12-09 12:10:55 +0100392 should_succeed=True,
393 servers=vms)
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800394
395 # Ping from trunk_network_server to vlan_network_server via VLAN
396 # interface should success
397 self.check_remote_connectivity(
Federico Ressi06ef8542018-10-25 15:23:52 +0200398 trunk_network_server.ssh_client,
399 vlan_network_server.port['fixed_ips'][0]['ip_address'],
Slawek Kaplonskie58219b2019-12-09 12:10:55 +0100400 should_succeed=True,
401 servers=vms)
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800402
403 # Delete the trunk
Federico Ressi06ef8542018-10-25 15:23:52 +0200404 self.delete_trunk(
405 trunk_network_server.trunk,
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800406 detach_parent_port=False)
Federico Ressi06ef8542018-10-25 15:23:52 +0200407 LOG.debug("Trunk %s is deleted.",
408 trunk_network_server.trunk['id'])
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800409
410 # Ping from trunk_network_server to normal_network_server
411 # via parent port success after trunk deleted
412 self.check_remote_connectivity(
Federico Ressi06ef8542018-10-25 15:23:52 +0200413 trunk_network_server.ssh_client,
414 normal_network_server.port['fixed_ips'][0]['ip_address'],
Slawek Kaplonskie58219b2019-12-09 12:10:55 +0100415 should_succeed=True,
416 servers=vms)
Huifeng Le1c9f40b2018-11-07 01:14:21 +0800417
418 # Ping from trunk_network_server to vlan_network_server via VLAN
419 # interface should fail after trunk deleted
420 self.check_remote_connectivity(
Federico Ressi06ef8542018-10-25 15:23:52 +0200421 trunk_network_server.ssh_client,
422 vlan_network_server.port['fixed_ips'][0]['ip_address'],
423 should_succeed=False)