blob: d87a365a05c0844a2d1296f43626d17e1872e1cf [file] [log] [blame]
Itzik Browne67ebb52016-05-15 05:34:41 +00001# Copyright 2016 Red Hat, Inc.
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.
Chandan Kumarc125fd12017-11-15 19:41:01 +053015import subprocess
Itzik Browne67ebb52016-05-15 05:34:41 +000016
Federico Ressibf877c82018-08-22 08:36:37 +020017from debtcollector import removals
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090018import netaddr
Assaf Muller92fdc782018-05-31 10:32:47 -040019from neutron_lib.api import validators
20from neutron_lib import constants as neutron_lib_constants
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050021from oslo_log import log
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +030022from tempest.common.utils import net_utils
Itzik Browne67ebb52016-05-15 05:34:41 +000023from tempest.common import waiters
Itzik Browne67ebb52016-05-15 05:34:41 +000024from tempest.lib.common.utils import data_utils
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090025from tempest.lib.common.utils import test_utils
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -050026from tempest.lib import exceptions as lib_exc
Itzik Browne67ebb52016-05-15 05:34:41 +000027
Chandan Kumar667d3d32017-09-22 12:24:06 +053028from neutron_tempest_plugin.api import base as base_api
29from neutron_tempest_plugin.common import ssh
30from neutron_tempest_plugin import config
31from neutron_tempest_plugin.scenario import constants
Itzik Browne67ebb52016-05-15 05:34:41 +000032
33CONF = config.CONF
Itzik Browne67ebb52016-05-15 05:34:41 +000034
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050035LOG = log.getLogger(__name__)
36
Itzik Browne67ebb52016-05-15 05:34:41 +000037
38class BaseTempestTestCase(base_api.BaseNetworkTest):
Itzik Browne67ebb52016-05-15 05:34:41 +000039
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000040 def create_server(self, flavor_ref, image_ref, key_name, networks,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020041 **kwargs):
Itzik Brownbac51dc2016-10-31 12:25:04 +000042 """Create a server using tempest lib
Brian Haleyaee61ac2018-10-09 20:00:27 -040043
Itzik Brownbac51dc2016-10-31 12:25:04 +000044 All the parameters are the ones used in Compute API
Roee Agiman6a0a18a2017-11-16 11:51:56 +020045 * - Kwargs that require admin privileges
Itzik Brownbac51dc2016-10-31 12:25:04 +000046
47 Args:
48 flavor_ref(str): The flavor of the server to be provisioned.
49 image_ref(str): The image of the server to be provisioned.
50 key_name(str): SSH key to to be used to connect to the
51 provisioned server.
52 networks(list): List of dictionaries where each represent
53 an interface to be attached to the server. For network
54 it should be {'uuid': network_uuid} and for port it should
55 be {'port': port_uuid}
Roee Agiman6a0a18a2017-11-16 11:51:56 +020056 kwargs:
Itzik Brownbac51dc2016-10-31 12:25:04 +000057 name(str): Name of the server to be provisioned.
58 security_groups(list): List of dictionaries where
59 the keys is 'name' and the value is the name of
60 the security group. If it's not passed the default
61 security group will be used.
Roee Agiman6a0a18a2017-11-16 11:51:56 +020062 availability_zone(str)*: The availability zone that
63 the instance will be in.
64 You can request a specific az without actually creating one,
65 Just pass 'X:Y' where X is the default availability
66 zone, and Y is the compute host name.
Itzik Brownbac51dc2016-10-31 12:25:04 +000067 """
68
Jakub Libosvarffd9b912017-11-16 09:54:14 +000069 kwargs.setdefault('name', data_utils.rand_name('server-test'))
Itzik Brownbac51dc2016-10-31 12:25:04 +000070
Jakub Libosvarffd9b912017-11-16 09:54:14 +000071 # We cannot use setdefault() here because caller could have passed
72 # security_groups=None and we don't want to pass None to
73 # client.create_server()
74 if not kwargs.get('security_groups'):
75 kwargs['security_groups'] = [{'name': 'default'}]
Roee Agiman6a0a18a2017-11-16 11:51:56 +020076
Jakub Libosvarffd9b912017-11-16 09:54:14 +000077 client = self.os_primary.servers_client
78 if kwargs.get('availability_zone'):
Roee Agiman6a0a18a2017-11-16 11:51:56 +020079 client = self.os_admin.servers_client
Roee Agiman6a0a18a2017-11-16 11:51:56 +020080
Jakub Libosvarffd9b912017-11-16 09:54:14 +000081 server = client.create_server(
82 flavorRef=flavor_ref,
83 imageRef=image_ref,
84 key_name=key_name,
85 networks=networks,
86 **kwargs)
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000087
88 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020089 waiters.wait_for_server_termination,
90 client,
91 server['server']['id'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000092 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020093 client.delete_server,
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000094 server['server']['id'])
Itzik Browne67ebb52016-05-15 05:34:41 +000095 return server
96
97 @classmethod
Jens Harbott54357632017-11-21 11:47:06 +000098 def create_secgroup_rules(cls, rule_list, secgroup_id=None,
99 client=None):
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200100 client = client or cls.os_primary.network_client
Itzik Browne67ebb52016-05-15 05:34:41 +0000101 if not secgroup_id:
102 sgs = client.list_security_groups()['security_groups']
103 for sg in sgs:
104 if sg['name'] == constants.DEFAULT_SECURITY_GROUP:
105 secgroup_id = sg['id']
106 break
107
Itzik Brown1ef813a2016-06-06 12:56:21 +0000108 for rule in rule_list:
109 direction = rule.pop('direction')
110 client.create_security_group_rule(
111 direction=direction,
112 security_group_id=secgroup_id,
113 **rule)
114
115 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200116 def create_loginable_secgroup_rule(cls, secgroup_id=None,
117 client=None):
Itzik Brown1ef813a2016-06-06 12:56:21 +0000118 """This rule is intended to permit inbound ssh
119
120 Allowing ssh traffic traffic from all sources, so no group_id is
121 provided.
122 Setting a group_id would only permit traffic from ports
123 belonging to the same security group.
124 """
Federico Ressi4c590d72018-10-10 14:01:08 +0200125 return cls.create_security_group_rule(
126 security_group_id=secgroup_id,
127 client=client,
128 protocol=neutron_lib_constants.PROTO_NAME_TCP,
129 direction=neutron_lib_constants.INGRESS_DIRECTION,
130 port_range_min=22,
131 port_range_max=22)
Itzik Browne67ebb52016-05-15 05:34:41 +0000132
133 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200134 def create_pingable_secgroup_rule(cls, secgroup_id=None,
135 client=None):
Federico Ressi4c590d72018-10-10 14:01:08 +0200136 """This rule is intended to permit inbound ping
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900137
Federico Ressi4c590d72018-10-10 14:01:08 +0200138 """
139 return cls.create_security_group_rule(
140 security_group_id=secgroup_id, client=client,
141 protocol=neutron_lib_constants.PROTO_NAME_ICMP,
142 direction=neutron_lib_constants.INGRESS_DIRECTION)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900143
144 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300145 def create_router_by_client(cls, is_admin=False, **kwargs):
146 kwargs.update({'router_name': data_utils.rand_name('router'),
147 'admin_state_up': True,
148 'external_network_id': CONF.network.public_network_id})
149 if not is_admin:
150 router = cls.create_router(**kwargs)
151 else:
152 router = cls.create_admin_router(**kwargs)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500153 LOG.debug("Created router %s", router['name'])
Itzik Browne67ebb52016-05-15 05:34:41 +0000154 cls.routers.append(router)
155 return router
156
Federico Ressibf877c82018-08-22 08:36:37 +0200157 @removals.remove(version='Stein',
158 message="Please use create_floatingip method instead of "
159 "create_and_associate_floatingip.")
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200160 def create_and_associate_floatingip(self, port_id, client=None):
161 client = client or self.os_primary.network_client
Federico Ressibf877c82018-08-22 08:36:37 +0200162 return self.create_floatingip(port_id=port_id, client=client)
Itzik Browne67ebb52016-05-15 05:34:41 +0000163
Hongbin Lu965b03d2018-04-25 22:32:30 +0000164 def create_interface(cls, server_id, port_id, client=None):
165 client = client or cls.os_primary.interfaces_client
166 body = client.create_interface(server_id, port_id=port_id)
167 return body['interfaceAttachment']
168
169 def delete_interface(cls, server_id, port_id, client=None):
170 client = client or cls.os_primary.interfaces_client
171 client.delete_interface(server_id, port_id=port_id)
172
Assaf Muller92fdc782018-05-31 10:32:47 -0400173 def setup_network_and_server(
Assaf Mullerd54ae6c2018-05-31 11:38:00 -0400174 self, router=None, server_name=None, network=None, **kwargs):
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300175 """Create network resources and a server.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000176
177 Creating a network, subnet, router, keypair, security group
178 and a server.
179 """
Assaf Mullerd54ae6c2018-05-31 11:38:00 -0400180 self.network = network or self.create_network()
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000181 LOG.debug("Created network %s", self.network['name'])
182 self.subnet = self.create_subnet(self.network)
183 LOG.debug("Created subnet %s", self.subnet['id'])
Itzik Brown1ef813a2016-06-06 12:56:21 +0000184
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400185 secgroup = self.os_primary.network_client.create_security_group(
Chandan Kumarc125fd12017-11-15 19:41:01 +0530186 name=data_utils.rand_name('secgroup'))
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500187 LOG.debug("Created security group %s",
188 secgroup['security_group']['name'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000189 self.security_groups.append(secgroup['security_group'])
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300190 if not router:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000191 router = self.create_router_by_client(**kwargs)
192 self.create_router_interface(router['id'], self.subnet['id'])
193 self.keypair = self.create_keypair()
194 self.create_loginable_secgroup_rule(
Itzik Brownbac51dc2016-10-31 12:25:04 +0000195 secgroup_id=secgroup['security_group']['id'])
Assaf Muller92fdc782018-05-31 10:32:47 -0400196
197 server_kwargs = {
198 'flavor_ref': CONF.compute.flavor_ref,
199 'image_ref': CONF.compute.image_ref,
200 'key_name': self.keypair['name'],
201 'networks': [{'uuid': self.network['id']}],
202 'security_groups': [{'name': secgroup['security_group']['name']}],
203 }
204 if server_name is not None:
205 server_kwargs['name'] = server_name
206
207 self.server = self.create_server(**server_kwargs)
Federico Ressie7417b72018-05-30 05:50:58 +0200208 self.wait_for_server_active(self.server['server'])
Jakub Libosvar1345d9d2017-06-09 13:59:05 +0000209 self.port = self.client.list_ports(network_id=self.network['id'],
210 device_id=self.server[
211 'server']['id'])['ports'][0]
Federico Ressibf877c82018-08-22 08:36:37 +0200212 self.fip = self.create_floatingip(port=self.port)
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500213
214 def check_connectivity(self, host, ssh_user, ssh_key, servers=None):
215 ssh_client = ssh.Client(host, ssh_user, pkey=ssh_key)
216 try:
217 ssh_client.test_connection_auth()
218 except lib_exc.SSHTimeout as ssh_e:
219 LOG.debug(ssh_e)
220 self._log_console_output(servers)
221 raise
222
223 def _log_console_output(self, servers=None):
224 if not CONF.compute_feature_enabled.console_output:
225 LOG.debug('Console output not supported, cannot log')
226 return
227 if not servers:
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400228 servers = self.os_primary.servers_client.list_servers()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500229 servers = servers['servers']
230 for server in servers:
231 try:
232 console_output = (
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400233 self.os_primary.servers_client.get_console_output(
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500234 server['id'])['output'])
235 LOG.debug('Console output for %s\nbody=\n%s',
236 server['id'], console_output)
237 except lib_exc.NotFound:
238 LOG.debug("Server %s disappeared(deleted) while looking "
239 "for the console log", server['id'])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900240
LIU Yulong68ab2452019-05-18 10:19:49 +0800241 def _check_remote_connectivity(self, source, dest, count,
242 should_succeed=True,
Assaf Muller92fdc782018-05-31 10:32:47 -0400243 nic=None, mtu=None, fragmentation=True,
244 timeout=None):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900245 """check ping server via source ssh connection
246
247 :param source: RemoteClient: an ssh connection from which to ping
248 :param dest: and IP to ping against
LIU Yulong68ab2452019-05-18 10:19:49 +0800249 :param count: Number of ping packet(s) to send
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900250 :param should_succeed: boolean should ping succeed or not
251 :param nic: specific network interface to ping from
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300252 :param mtu: mtu size for the packet to be sent
253 :param fragmentation: Flag for packet fragmentation
LIU Yulong68ab2452019-05-18 10:19:49 +0800254 :param timeout: Timeout for all ping packet(s) to succeed
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900255 :returns: boolean -- should_succeed == ping
256 :returns: ping is false if ping failed
257 """
LIU Yulong68ab2452019-05-18 10:19:49 +0800258 def ping_host(source, host, count,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300259 size=CONF.validation.ping_size, nic=None, mtu=None,
260 fragmentation=True):
Assaf Muller92fdc782018-05-31 10:32:47 -0400261 IP_VERSION_4 = neutron_lib_constants.IP_VERSION_4
262 IP_VERSION_6 = neutron_lib_constants.IP_VERSION_6
263
264 # Use 'ping6' for IPv6 addresses, 'ping' for IPv4 and hostnames
265 ip_version = (
266 IP_VERSION_6 if netaddr.valid_ipv6(host) else IP_VERSION_4)
267 cmd = (
268 'ping6' if ip_version == IP_VERSION_6 else 'ping')
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900269 if nic:
270 cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300271 if mtu:
272 if not fragmentation:
273 cmd += ' -M do'
274 size = str(net_utils.get_ping_payload_size(
Assaf Muller92fdc782018-05-31 10:32:47 -0400275 mtu=mtu, ip_version=ip_version))
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900276 cmd += ' -c{0} -w{0} -s{1} {2}'.format(count, size, host)
277 return source.exec_command(cmd)
278
279 def ping_remote():
280 try:
LIU Yulong68ab2452019-05-18 10:19:49 +0800281 result = ping_host(source, dest, count, nic=nic, mtu=mtu,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300282 fragmentation=fragmentation)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900283
284 except lib_exc.SSHExecCommandFailed:
285 LOG.warning('Failed to ping IP: %s via a ssh connection '
286 'from: %s.', dest, source.host)
287 return not should_succeed
288 LOG.debug('ping result: %s', result)
Assaf Muller92fdc782018-05-31 10:32:47 -0400289
290 if validators.validate_ip_address(dest) is None:
291 # Assert that the return traffic was from the correct
292 # source address.
293 from_source = 'from %s' % dest
294 self.assertIn(from_source, result)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900295 return should_succeed
296
Assaf Muller92fdc782018-05-31 10:32:47 -0400297 return test_utils.call_until_true(
298 ping_remote, timeout or CONF.validation.ping_timeout, 1)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900299
300 def check_remote_connectivity(self, source, dest, should_succeed=True,
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200301 nic=None, mtu=None, fragmentation=True,
LIU Yulong68ab2452019-05-18 10:19:49 +0800302 servers=None, timeout=None,
303 ping_count=CONF.validation.ping_count):
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200304 try:
305 self.assertTrue(self._check_remote_connectivity(
LIU Yulong68ab2452019-05-18 10:19:49 +0800306 source, dest, ping_count, should_succeed, nic, mtu,
307 fragmentation,
Assaf Muller92fdc782018-05-31 10:32:47 -0400308 timeout=timeout))
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200309 except lib_exc.SSHTimeout as ssh_e:
310 LOG.debug(ssh_e)
311 self._log_console_output(servers)
312 raise
313 except AssertionError:
314 self._log_console_output(servers)
315 raise
Chandan Kumarc125fd12017-11-15 19:41:01 +0530316
317 def ping_ip_address(self, ip_address, should_succeed=True,
318 ping_timeout=None, mtu=None):
319 # the code is taken from tempest/scenario/manager.py in tempest git
320 timeout = ping_timeout or CONF.validation.ping_timeout
321 cmd = ['ping', '-c1', '-w1']
322
323 if mtu:
324 cmd += [
325 # don't fragment
326 '-M', 'do',
327 # ping receives just the size of ICMP payload
328 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
329 ]
330 cmd.append(ip_address)
331
332 def ping():
333 proc = subprocess.Popen(cmd,
334 stdout=subprocess.PIPE,
335 stderr=subprocess.PIPE)
336 proc.communicate()
337
338 return (proc.returncode == 0) == should_succeed
339
340 caller = test_utils.find_test_caller()
341 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
342 ' expected result is %(should_succeed)s', {
343 'caller': caller, 'ip': ip_address, 'timeout': timeout,
344 'should_succeed':
345 'reachable' if should_succeed else 'unreachable'
346 })
347 result = test_utils.call_until_true(ping, timeout, 1)
Manjeet Singh Bhatia8bbf8992019-03-04 11:59:57 -0800348
349 # To make sure ping_ip_address called by test works
350 # as expected.
351 self.assertTrue(result)
352
Chandan Kumarc125fd12017-11-15 19:41:01 +0530353 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
354 'ping result is %(result)s', {
355 'caller': caller, 'ip': ip_address, 'timeout': timeout,
356 'result': 'expected' if result else 'unexpected'
357 })
358 return result
Federico Ressie7417b72018-05-30 05:50:58 +0200359
360 def wait_for_server_status(self, server, status, client=None, **kwargs):
361 """Waits for a server to reach a given status.
362
363 :param server: mapping having schema {'id': <server_id>}
364 :param status: string status to wait for (es: 'ACTIVE')
365 :param clien: servers client (self.os_primary.servers_client as
366 default value)
367 """
368
369 client = client or self.os_primary.servers_client
370 waiters.wait_for_server_status(client, server['id'], status, **kwargs)
371
372 def wait_for_server_active(self, server, client=None):
373 """Waits for a server to reach active status.
374
375 :param server: mapping having schema {'id': <server_id>}
376 :param clien: servers client (self.os_primary.servers_client as
377 default value)
378 """
379 self.wait_for_server_status(
380 server, constants.SERVER_STATUS_ACTIVE, client)
Jakub Libosvar031fd5a2019-07-15 16:10:07 +0000381
382 def check_servers_hostnames(self, servers, log_errors=True):
383 """Compare hostnames of given servers with their names."""
384 try:
385 for server in servers:
386 kwargs = {}
387 try:
388 kwargs['port'] = server['port_forwarding']['external_port']
389 except KeyError:
390 pass
391 ssh_client = ssh.Client(
392 self.fip['floating_ip_address'],
393 CONF.validation.image_ssh_user,
394 pkey=self.keypair['private_key'],
395 **kwargs)
396 self.assertIn(server['name'],
397 ssh_client.exec_command('hostname'))
398 except lib_exc.SSHTimeout as ssh_e:
399 LOG.debug(ssh_e)
400 if log_errors:
401 self._log_console_output(servers)
402 raise
403 except AssertionError as assert_e:
404 LOG.debug(assert_e)
405 if log_errors:
406 self._log_console_output(servers)
407 raise