blob: 10cdaf199b8c7fd61432d779ffe6406d776c9088 [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
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090017import netaddr
Assaf Muller92fdc782018-05-31 10:32:47 -040018from neutron_lib.api import validators
19from neutron_lib import constants as neutron_lib_constants
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050020from oslo_log import log
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +030021from tempest.common.utils import net_utils
Itzik Browne67ebb52016-05-15 05:34:41 +000022from tempest.common import waiters
Itzik Browne67ebb52016-05-15 05:34:41 +000023from tempest.lib.common.utils import data_utils
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090024from tempest.lib.common.utils import test_utils
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -050025from tempest.lib import exceptions as lib_exc
Itzik Browne67ebb52016-05-15 05:34:41 +000026
Chandan Kumar667d3d32017-09-22 12:24:06 +053027from neutron_tempest_plugin.api import base as base_api
28from neutron_tempest_plugin.common import ssh
29from neutron_tempest_plugin import config
30from neutron_tempest_plugin.scenario import constants
Itzik Browne67ebb52016-05-15 05:34:41 +000031
32CONF = config.CONF
Itzik Browne67ebb52016-05-15 05:34:41 +000033
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050034LOG = log.getLogger(__name__)
35
Itzik Browne67ebb52016-05-15 05:34:41 +000036
37class BaseTempestTestCase(base_api.BaseNetworkTest):
Itzik Browne67ebb52016-05-15 05:34:41 +000038
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000039 def create_server(self, flavor_ref, image_ref, key_name, networks,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020040 **kwargs):
Itzik Brownbac51dc2016-10-31 12:25:04 +000041 """Create a server using tempest lib
42 All the parameters are the ones used in Compute API
Roee Agiman6a0a18a2017-11-16 11:51:56 +020043 * - Kwargs that require admin privileges
Itzik Brownbac51dc2016-10-31 12:25:04 +000044
45 Args:
46 flavor_ref(str): The flavor of the server to be provisioned.
47 image_ref(str): The image of the server to be provisioned.
48 key_name(str): SSH key to to be used to connect to the
49 provisioned server.
50 networks(list): List of dictionaries where each represent
51 an interface to be attached to the server. For network
52 it should be {'uuid': network_uuid} and for port it should
53 be {'port': port_uuid}
Roee Agiman6a0a18a2017-11-16 11:51:56 +020054 kwargs:
Itzik Brownbac51dc2016-10-31 12:25:04 +000055 name(str): Name of the server to be provisioned.
56 security_groups(list): List of dictionaries where
57 the keys is 'name' and the value is the name of
58 the security group. If it's not passed the default
59 security group will be used.
Roee Agiman6a0a18a2017-11-16 11:51:56 +020060 availability_zone(str)*: The availability zone that
61 the instance will be in.
62 You can request a specific az without actually creating one,
63 Just pass 'X:Y' where X is the default availability
64 zone, and Y is the compute host name.
Itzik Brownbac51dc2016-10-31 12:25:04 +000065 """
66
Jakub Libosvarffd9b912017-11-16 09:54:14 +000067 kwargs.setdefault('name', data_utils.rand_name('server-test'))
Itzik Brownbac51dc2016-10-31 12:25:04 +000068
Jakub Libosvarffd9b912017-11-16 09:54:14 +000069 # We cannot use setdefault() here because caller could have passed
70 # security_groups=None and we don't want to pass None to
71 # client.create_server()
72 if not kwargs.get('security_groups'):
73 kwargs['security_groups'] = [{'name': 'default'}]
Roee Agiman6a0a18a2017-11-16 11:51:56 +020074
Jakub Libosvarffd9b912017-11-16 09:54:14 +000075 client = self.os_primary.servers_client
76 if kwargs.get('availability_zone'):
Roee Agiman6a0a18a2017-11-16 11:51:56 +020077 client = self.os_admin.servers_client
Roee Agiman6a0a18a2017-11-16 11:51:56 +020078
Jakub Libosvarffd9b912017-11-16 09:54:14 +000079 server = client.create_server(
80 flavorRef=flavor_ref,
81 imageRef=image_ref,
82 key_name=key_name,
83 networks=networks,
84 **kwargs)
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000085
86 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020087 waiters.wait_for_server_termination,
88 client,
89 server['server']['id'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000090 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020091 client.delete_server,
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000092 server['server']['id'])
Itzik Browne67ebb52016-05-15 05:34:41 +000093 return server
94
95 @classmethod
Jens Harbott54357632017-11-21 11:47:06 +000096 def create_secgroup_rules(cls, rule_list, secgroup_id=None,
97 client=None):
Roee Agiman6a0a18a2017-11-16 11:51:56 +020098 client = client or cls.os_primary.network_client
Itzik Browne67ebb52016-05-15 05:34:41 +000099 if not secgroup_id:
100 sgs = client.list_security_groups()['security_groups']
101 for sg in sgs:
102 if sg['name'] == constants.DEFAULT_SECURITY_GROUP:
103 secgroup_id = sg['id']
104 break
105
Itzik Brown1ef813a2016-06-06 12:56:21 +0000106 for rule in rule_list:
107 direction = rule.pop('direction')
108 client.create_security_group_rule(
109 direction=direction,
110 security_group_id=secgroup_id,
111 **rule)
112
113 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200114 def create_loginable_secgroup_rule(cls, secgroup_id=None,
115 client=None):
Itzik Brown1ef813a2016-06-06 12:56:21 +0000116 """This rule is intended to permit inbound ssh
117
118 Allowing ssh traffic traffic from all sources, so no group_id is
119 provided.
120 Setting a group_id would only permit traffic from ports
121 belonging to the same security group.
122 """
123
124 rule_list = [{'protocol': 'tcp',
125 'direction': 'ingress',
126 'port_range_min': 22,
127 'port_range_max': 22,
128 'remote_ip_prefix': '0.0.0.0/0'}]
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200129 client = client or cls.os_primary.network_client
Jens Harbott54357632017-11-21 11:47:06 +0000130 cls.create_secgroup_rules(rule_list, client=client,
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200131 secgroup_id=secgroup_id)
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):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900136 """This rule is intended to permit inbound ping
137 """
138
139 rule_list = [{'protocol': 'icmp',
140 'direction': 'ingress',
141 'port_range_min': 8, # type
142 'port_range_max': 0, # code
143 'remote_ip_prefix': '0.0.0.0/0'}]
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200144 client = client or cls.os_primary.network_client
Jens Harbott54357632017-11-21 11:47:06 +0000145 cls.create_secgroup_rules(rule_list, client=client,
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200146 secgroup_id=secgroup_id)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900147
148 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300149 def create_router_by_client(cls, is_admin=False, **kwargs):
150 kwargs.update({'router_name': data_utils.rand_name('router'),
151 'admin_state_up': True,
152 'external_network_id': CONF.network.public_network_id})
153 if not is_admin:
154 router = cls.create_router(**kwargs)
155 else:
156 router = cls.create_admin_router(**kwargs)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500157 LOG.debug("Created router %s", router['name'])
Itzik Browne67ebb52016-05-15 05:34:41 +0000158 cls.routers.append(router)
159 return router
160
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200161 def create_and_associate_floatingip(self, port_id, client=None):
162 client = client or self.os_primary.network_client
163 fip = client.create_floatingip(
Itzik Browne67ebb52016-05-15 05:34:41 +0000164 CONF.network.public_network_id,
165 port_id=port_id)['floatingip']
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200166 if client is self.os_primary.network_client:
167 self.floating_ips.append(fip)
Itzik Browne67ebb52016-05-15 05:34:41 +0000168 return fip
169
Assaf Muller92fdc782018-05-31 10:32:47 -0400170 def setup_network_and_server(
171 self, router=None, server_name=None, **kwargs):
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300172 """Create network resources and a server.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000173
174 Creating a network, subnet, router, keypair, security group
175 and a server.
176 """
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000177 self.network = self.create_network()
178 LOG.debug("Created network %s", self.network['name'])
179 self.subnet = self.create_subnet(self.network)
180 LOG.debug("Created subnet %s", self.subnet['id'])
Itzik Brown1ef813a2016-06-06 12:56:21 +0000181
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400182 secgroup = self.os_primary.network_client.create_security_group(
Chandan Kumarc125fd12017-11-15 19:41:01 +0530183 name=data_utils.rand_name('secgroup'))
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500184 LOG.debug("Created security group %s",
185 secgroup['security_group']['name'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000186 self.security_groups.append(secgroup['security_group'])
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300187 if not router:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000188 router = self.create_router_by_client(**kwargs)
189 self.create_router_interface(router['id'], self.subnet['id'])
190 self.keypair = self.create_keypair()
191 self.create_loginable_secgroup_rule(
Itzik Brownbac51dc2016-10-31 12:25:04 +0000192 secgroup_id=secgroup['security_group']['id'])
Assaf Muller92fdc782018-05-31 10:32:47 -0400193
194 server_kwargs = {
195 'flavor_ref': CONF.compute.flavor_ref,
196 'image_ref': CONF.compute.image_ref,
197 'key_name': self.keypair['name'],
198 'networks': [{'uuid': self.network['id']}],
199 'security_groups': [{'name': secgroup['security_group']['name']}],
200 }
201 if server_name is not None:
202 server_kwargs['name'] = server_name
203
204 self.server = self.create_server(**server_kwargs)
Federico Ressie7417b72018-05-30 05:50:58 +0200205 self.wait_for_server_active(self.server['server'])
Jakub Libosvar1345d9d2017-06-09 13:59:05 +0000206 self.port = self.client.list_ports(network_id=self.network['id'],
207 device_id=self.server[
208 'server']['id'])['ports'][0]
209 self.fip = self.create_and_associate_floatingip(self.port['id'])
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500210
211 def check_connectivity(self, host, ssh_user, ssh_key, servers=None):
212 ssh_client = ssh.Client(host, ssh_user, pkey=ssh_key)
213 try:
214 ssh_client.test_connection_auth()
215 except lib_exc.SSHTimeout as ssh_e:
216 LOG.debug(ssh_e)
217 self._log_console_output(servers)
218 raise
219
220 def _log_console_output(self, servers=None):
221 if not CONF.compute_feature_enabled.console_output:
222 LOG.debug('Console output not supported, cannot log')
223 return
224 if not servers:
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400225 servers = self.os_primary.servers_client.list_servers()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500226 servers = servers['servers']
227 for server in servers:
228 try:
229 console_output = (
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400230 self.os_primary.servers_client.get_console_output(
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500231 server['id'])['output'])
232 LOG.debug('Console output for %s\nbody=\n%s',
233 server['id'], console_output)
234 except lib_exc.NotFound:
235 LOG.debug("Server %s disappeared(deleted) while looking "
236 "for the console log", server['id'])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900237
238 def _check_remote_connectivity(self, source, dest, should_succeed=True,
Assaf Muller92fdc782018-05-31 10:32:47 -0400239 nic=None, mtu=None, fragmentation=True,
240 timeout=None):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900241 """check ping server via source ssh connection
242
243 :param source: RemoteClient: an ssh connection from which to ping
244 :param dest: and IP to ping against
245 :param should_succeed: boolean should ping succeed or not
246 :param nic: specific network interface to ping from
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300247 :param mtu: mtu size for the packet to be sent
248 :param fragmentation: Flag for packet fragmentation
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900249 :returns: boolean -- should_succeed == ping
250 :returns: ping is false if ping failed
251 """
252 def ping_host(source, host, count=CONF.validation.ping_count,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300253 size=CONF.validation.ping_size, nic=None, mtu=None,
254 fragmentation=True):
Assaf Muller92fdc782018-05-31 10:32:47 -0400255 IP_VERSION_4 = neutron_lib_constants.IP_VERSION_4
256 IP_VERSION_6 = neutron_lib_constants.IP_VERSION_6
257
258 # Use 'ping6' for IPv6 addresses, 'ping' for IPv4 and hostnames
259 ip_version = (
260 IP_VERSION_6 if netaddr.valid_ipv6(host) else IP_VERSION_4)
261 cmd = (
262 'ping6' if ip_version == IP_VERSION_6 else 'ping')
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900263 if nic:
264 cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300265 if mtu:
266 if not fragmentation:
267 cmd += ' -M do'
268 size = str(net_utils.get_ping_payload_size(
Assaf Muller92fdc782018-05-31 10:32:47 -0400269 mtu=mtu, ip_version=ip_version))
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900270 cmd += ' -c{0} -w{0} -s{1} {2}'.format(count, size, host)
271 return source.exec_command(cmd)
272
273 def ping_remote():
274 try:
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300275 result = ping_host(source, dest, nic=nic, mtu=mtu,
276 fragmentation=fragmentation)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900277
278 except lib_exc.SSHExecCommandFailed:
279 LOG.warning('Failed to ping IP: %s via a ssh connection '
280 'from: %s.', dest, source.host)
281 return not should_succeed
282 LOG.debug('ping result: %s', result)
Assaf Muller92fdc782018-05-31 10:32:47 -0400283
284 if validators.validate_ip_address(dest) is None:
285 # Assert that the return traffic was from the correct
286 # source address.
287 from_source = 'from %s' % dest
288 self.assertIn(from_source, result)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900289 return should_succeed
290
Assaf Muller92fdc782018-05-31 10:32:47 -0400291 return test_utils.call_until_true(
292 ping_remote, timeout or CONF.validation.ping_timeout, 1)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900293
294 def check_remote_connectivity(self, source, dest, should_succeed=True,
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200295 nic=None, mtu=None, fragmentation=True,
Assaf Muller92fdc782018-05-31 10:32:47 -0400296 servers=None, timeout=None):
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200297 try:
298 self.assertTrue(self._check_remote_connectivity(
Assaf Muller92fdc782018-05-31 10:32:47 -0400299 source, dest, should_succeed, nic, mtu, fragmentation,
300 timeout=timeout))
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200301 except lib_exc.SSHTimeout as ssh_e:
302 LOG.debug(ssh_e)
303 self._log_console_output(servers)
304 raise
305 except AssertionError:
306 self._log_console_output(servers)
307 raise
Chandan Kumarc125fd12017-11-15 19:41:01 +0530308
309 def ping_ip_address(self, ip_address, should_succeed=True,
310 ping_timeout=None, mtu=None):
311 # the code is taken from tempest/scenario/manager.py in tempest git
312 timeout = ping_timeout or CONF.validation.ping_timeout
313 cmd = ['ping', '-c1', '-w1']
314
315 if mtu:
316 cmd += [
317 # don't fragment
318 '-M', 'do',
319 # ping receives just the size of ICMP payload
320 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
321 ]
322 cmd.append(ip_address)
323
324 def ping():
325 proc = subprocess.Popen(cmd,
326 stdout=subprocess.PIPE,
327 stderr=subprocess.PIPE)
328 proc.communicate()
329
330 return (proc.returncode == 0) == should_succeed
331
332 caller = test_utils.find_test_caller()
333 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
334 ' expected result is %(should_succeed)s', {
335 'caller': caller, 'ip': ip_address, 'timeout': timeout,
336 'should_succeed':
337 'reachable' if should_succeed else 'unreachable'
338 })
339 result = test_utils.call_until_true(ping, timeout, 1)
340 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
341 'ping result is %(result)s', {
342 'caller': caller, 'ip': ip_address, 'timeout': timeout,
343 'result': 'expected' if result else 'unexpected'
344 })
345 return result
Federico Ressie7417b72018-05-30 05:50:58 +0200346
347 def wait_for_server_status(self, server, status, client=None, **kwargs):
348 """Waits for a server to reach a given status.
349
350 :param server: mapping having schema {'id': <server_id>}
351 :param status: string status to wait for (es: 'ACTIVE')
352 :param clien: servers client (self.os_primary.servers_client as
353 default value)
354 """
355
356 client = client or self.os_primary.servers_client
357 waiters.wait_for_server_status(client, server['id'], status, **kwargs)
358
359 def wait_for_server_active(self, server, client=None):
360 """Waits for a server to reach active status.
361
362 :param server: mapping having schema {'id': <server_id>}
363 :param clien: servers client (self.os_primary.servers_client as
364 default value)
365 """
366 self.wait_for_server_status(
367 server, constants.SERVER_STATUS_ACTIVE, client)