blob: 1aaf8ce8f1cf65c596572b9d5387de6249bdc3d8 [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
43 All the parameters are the ones used in Compute API
Roee Agiman6a0a18a2017-11-16 11:51:56 +020044 * - Kwargs that require admin privileges
Itzik Brownbac51dc2016-10-31 12:25:04 +000045
46 Args:
47 flavor_ref(str): The flavor of the server to be provisioned.
48 image_ref(str): The image of the server to be provisioned.
49 key_name(str): SSH key to to be used to connect to the
50 provisioned server.
51 networks(list): List of dictionaries where each represent
52 an interface to be attached to the server. For network
53 it should be {'uuid': network_uuid} and for port it should
54 be {'port': port_uuid}
Roee Agiman6a0a18a2017-11-16 11:51:56 +020055 kwargs:
Itzik Brownbac51dc2016-10-31 12:25:04 +000056 name(str): Name of the server to be provisioned.
57 security_groups(list): List of dictionaries where
58 the keys is 'name' and the value is the name of
59 the security group. If it's not passed the default
60 security group will be used.
Roee Agiman6a0a18a2017-11-16 11:51:56 +020061 availability_zone(str)*: The availability zone that
62 the instance will be in.
63 You can request a specific az without actually creating one,
64 Just pass 'X:Y' where X is the default availability
65 zone, and Y is the compute host name.
Itzik Brownbac51dc2016-10-31 12:25:04 +000066 """
67
Jakub Libosvarffd9b912017-11-16 09:54:14 +000068 kwargs.setdefault('name', data_utils.rand_name('server-test'))
Itzik Brownbac51dc2016-10-31 12:25:04 +000069
Jakub Libosvarffd9b912017-11-16 09:54:14 +000070 # We cannot use setdefault() here because caller could have passed
71 # security_groups=None and we don't want to pass None to
72 # client.create_server()
73 if not kwargs.get('security_groups'):
74 kwargs['security_groups'] = [{'name': 'default'}]
Roee Agiman6a0a18a2017-11-16 11:51:56 +020075
Jakub Libosvarffd9b912017-11-16 09:54:14 +000076 client = self.os_primary.servers_client
77 if kwargs.get('availability_zone'):
Roee Agiman6a0a18a2017-11-16 11:51:56 +020078 client = self.os_admin.servers_client
Roee Agiman6a0a18a2017-11-16 11:51:56 +020079
Jakub Libosvarffd9b912017-11-16 09:54:14 +000080 server = client.create_server(
81 flavorRef=flavor_ref,
82 imageRef=image_ref,
83 key_name=key_name,
84 networks=networks,
85 **kwargs)
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000086
87 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020088 waiters.wait_for_server_termination,
89 client,
90 server['server']['id'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000091 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020092 client.delete_server,
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000093 server['server']['id'])
Itzik Browne67ebb52016-05-15 05:34:41 +000094 return server
95
96 @classmethod
Jens Harbott54357632017-11-21 11:47:06 +000097 def create_secgroup_rules(cls, rule_list, secgroup_id=None,
98 client=None):
Roee Agiman6a0a18a2017-11-16 11:51:56 +020099 client = client or cls.os_primary.network_client
Itzik Browne67ebb52016-05-15 05:34:41 +0000100 if not secgroup_id:
101 sgs = client.list_security_groups()['security_groups']
102 for sg in sgs:
103 if sg['name'] == constants.DEFAULT_SECURITY_GROUP:
104 secgroup_id = sg['id']
105 break
106
Itzik Brown1ef813a2016-06-06 12:56:21 +0000107 for rule in rule_list:
108 direction = rule.pop('direction')
109 client.create_security_group_rule(
110 direction=direction,
111 security_group_id=secgroup_id,
112 **rule)
113
114 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200115 def create_loginable_secgroup_rule(cls, secgroup_id=None,
116 client=None):
Itzik Brown1ef813a2016-06-06 12:56:21 +0000117 """This rule is intended to permit inbound ssh
118
119 Allowing ssh traffic traffic from all sources, so no group_id is
120 provided.
121 Setting a group_id would only permit traffic from ports
122 belonging to the same security group.
123 """
124
125 rule_list = [{'protocol': 'tcp',
126 'direction': 'ingress',
127 'port_range_min': 22,
128 'port_range_max': 22,
129 'remote_ip_prefix': '0.0.0.0/0'}]
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200130 client = client or cls.os_primary.network_client
Jens Harbott54357632017-11-21 11:47:06 +0000131 cls.create_secgroup_rules(rule_list, client=client,
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200132 secgroup_id=secgroup_id)
Itzik Browne67ebb52016-05-15 05:34:41 +0000133
134 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200135 def create_pingable_secgroup_rule(cls, secgroup_id=None,
136 client=None):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900137 """This rule is intended to permit inbound ping
138 """
139
140 rule_list = [{'protocol': 'icmp',
141 'direction': 'ingress',
142 'port_range_min': 8, # type
143 'port_range_max': 0, # code
144 'remote_ip_prefix': '0.0.0.0/0'}]
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200145 client = client or cls.os_primary.network_client
Jens Harbott54357632017-11-21 11:47:06 +0000146 cls.create_secgroup_rules(rule_list, client=client,
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200147 secgroup_id=secgroup_id)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900148
149 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300150 def create_router_by_client(cls, is_admin=False, **kwargs):
151 kwargs.update({'router_name': data_utils.rand_name('router'),
152 'admin_state_up': True,
153 'external_network_id': CONF.network.public_network_id})
154 if not is_admin:
155 router = cls.create_router(**kwargs)
156 else:
157 router = cls.create_admin_router(**kwargs)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500158 LOG.debug("Created router %s", router['name'])
Itzik Browne67ebb52016-05-15 05:34:41 +0000159 cls.routers.append(router)
160 return router
161
Federico Ressibf877c82018-08-22 08:36:37 +0200162 @removals.remove(version='Stein',
163 message="Please use create_floatingip method instead of "
164 "create_and_associate_floatingip.")
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200165 def create_and_associate_floatingip(self, port_id, client=None):
166 client = client or self.os_primary.network_client
Federico Ressibf877c82018-08-22 08:36:37 +0200167 return self.create_floatingip(port_id=port_id, client=client)
Itzik Browne67ebb52016-05-15 05:34:41 +0000168
Hongbin Lu965b03d2018-04-25 22:32:30 +0000169 def create_interface(cls, server_id, port_id, client=None):
170 client = client or cls.os_primary.interfaces_client
171 body = client.create_interface(server_id, port_id=port_id)
172 return body['interfaceAttachment']
173
174 def delete_interface(cls, server_id, port_id, client=None):
175 client = client or cls.os_primary.interfaces_client
176 client.delete_interface(server_id, port_id=port_id)
177
Assaf Muller92fdc782018-05-31 10:32:47 -0400178 def setup_network_and_server(
179 self, router=None, server_name=None, **kwargs):
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300180 """Create network resources and a server.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000181
182 Creating a network, subnet, router, keypair, security group
183 and a server.
184 """
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000185 self.network = self.create_network()
186 LOG.debug("Created network %s", self.network['name'])
187 self.subnet = self.create_subnet(self.network)
188 LOG.debug("Created subnet %s", self.subnet['id'])
Itzik Brown1ef813a2016-06-06 12:56:21 +0000189
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400190 secgroup = self.os_primary.network_client.create_security_group(
Chandan Kumarc125fd12017-11-15 19:41:01 +0530191 name=data_utils.rand_name('secgroup'))
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500192 LOG.debug("Created security group %s",
193 secgroup['security_group']['name'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000194 self.security_groups.append(secgroup['security_group'])
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300195 if not router:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000196 router = self.create_router_by_client(**kwargs)
197 self.create_router_interface(router['id'], self.subnet['id'])
198 self.keypair = self.create_keypair()
199 self.create_loginable_secgroup_rule(
Itzik Brownbac51dc2016-10-31 12:25:04 +0000200 secgroup_id=secgroup['security_group']['id'])
Assaf Muller92fdc782018-05-31 10:32:47 -0400201
202 server_kwargs = {
203 'flavor_ref': CONF.compute.flavor_ref,
204 'image_ref': CONF.compute.image_ref,
205 'key_name': self.keypair['name'],
206 'networks': [{'uuid': self.network['id']}],
207 'security_groups': [{'name': secgroup['security_group']['name']}],
208 }
209 if server_name is not None:
210 server_kwargs['name'] = server_name
211
212 self.server = self.create_server(**server_kwargs)
Federico Ressie7417b72018-05-30 05:50:58 +0200213 self.wait_for_server_active(self.server['server'])
Jakub Libosvar1345d9d2017-06-09 13:59:05 +0000214 self.port = self.client.list_ports(network_id=self.network['id'],
215 device_id=self.server[
216 'server']['id'])['ports'][0]
Federico Ressibf877c82018-08-22 08:36:37 +0200217 self.fip = self.create_floatingip(port=self.port)
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500218
219 def check_connectivity(self, host, ssh_user, ssh_key, servers=None):
220 ssh_client = ssh.Client(host, ssh_user, pkey=ssh_key)
221 try:
222 ssh_client.test_connection_auth()
223 except lib_exc.SSHTimeout as ssh_e:
224 LOG.debug(ssh_e)
225 self._log_console_output(servers)
226 raise
227
228 def _log_console_output(self, servers=None):
229 if not CONF.compute_feature_enabled.console_output:
230 LOG.debug('Console output not supported, cannot log')
231 return
232 if not servers:
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400233 servers = self.os_primary.servers_client.list_servers()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500234 servers = servers['servers']
235 for server in servers:
236 try:
237 console_output = (
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400238 self.os_primary.servers_client.get_console_output(
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500239 server['id'])['output'])
240 LOG.debug('Console output for %s\nbody=\n%s',
241 server['id'], console_output)
242 except lib_exc.NotFound:
243 LOG.debug("Server %s disappeared(deleted) while looking "
244 "for the console log", server['id'])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900245
246 def _check_remote_connectivity(self, source, dest, should_succeed=True,
Assaf Muller92fdc782018-05-31 10:32:47 -0400247 nic=None, mtu=None, fragmentation=True,
248 timeout=None):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900249 """check ping server via source ssh connection
250
251 :param source: RemoteClient: an ssh connection from which to ping
252 :param dest: and IP to ping against
253 :param should_succeed: boolean should ping succeed or not
254 :param nic: specific network interface to ping from
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300255 :param mtu: mtu size for the packet to be sent
256 :param fragmentation: Flag for packet fragmentation
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900257 :returns: boolean -- should_succeed == ping
258 :returns: ping is false if ping failed
259 """
260 def ping_host(source, host, count=CONF.validation.ping_count,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300261 size=CONF.validation.ping_size, nic=None, mtu=None,
262 fragmentation=True):
Assaf Muller92fdc782018-05-31 10:32:47 -0400263 IP_VERSION_4 = neutron_lib_constants.IP_VERSION_4
264 IP_VERSION_6 = neutron_lib_constants.IP_VERSION_6
265
266 # Use 'ping6' for IPv6 addresses, 'ping' for IPv4 and hostnames
267 ip_version = (
268 IP_VERSION_6 if netaddr.valid_ipv6(host) else IP_VERSION_4)
269 cmd = (
270 'ping6' if ip_version == IP_VERSION_6 else 'ping')
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900271 if nic:
272 cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300273 if mtu:
274 if not fragmentation:
275 cmd += ' -M do'
276 size = str(net_utils.get_ping_payload_size(
Assaf Muller92fdc782018-05-31 10:32:47 -0400277 mtu=mtu, ip_version=ip_version))
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900278 cmd += ' -c{0} -w{0} -s{1} {2}'.format(count, size, host)
279 return source.exec_command(cmd)
280
281 def ping_remote():
282 try:
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300283 result = ping_host(source, dest, nic=nic, mtu=mtu,
284 fragmentation=fragmentation)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900285
286 except lib_exc.SSHExecCommandFailed:
287 LOG.warning('Failed to ping IP: %s via a ssh connection '
288 'from: %s.', dest, source.host)
289 return not should_succeed
290 LOG.debug('ping result: %s', result)
Assaf Muller92fdc782018-05-31 10:32:47 -0400291
292 if validators.validate_ip_address(dest) is None:
293 # Assert that the return traffic was from the correct
294 # source address.
295 from_source = 'from %s' % dest
296 self.assertIn(from_source, result)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900297 return should_succeed
298
Assaf Muller92fdc782018-05-31 10:32:47 -0400299 return test_utils.call_until_true(
300 ping_remote, timeout or CONF.validation.ping_timeout, 1)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900301
302 def check_remote_connectivity(self, source, dest, should_succeed=True,
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200303 nic=None, mtu=None, fragmentation=True,
Assaf Muller92fdc782018-05-31 10:32:47 -0400304 servers=None, timeout=None):
Slawek Kaplonskib07251f2018-05-16 12:21:50 +0200305 try:
306 self.assertTrue(self._check_remote_connectivity(
Assaf Muller92fdc782018-05-31 10:32:47 -0400307 source, dest, should_succeed, nic, mtu, fragmentation,
308 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)
348 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
349 'ping result is %(result)s', {
350 'caller': caller, 'ip': ip_address, 'timeout': timeout,
351 'result': 'expected' if result else 'unexpected'
352 })
353 return result
Federico Ressie7417b72018-05-30 05:50:58 +0200354
355 def wait_for_server_status(self, server, status, client=None, **kwargs):
356 """Waits for a server to reach a given status.
357
358 :param server: mapping having schema {'id': <server_id>}
359 :param status: string status to wait for (es: 'ACTIVE')
360 :param clien: servers client (self.os_primary.servers_client as
361 default value)
362 """
363
364 client = client or self.os_primary.servers_client
365 waiters.wait_for_server_status(client, server['id'], status, **kwargs)
366
367 def wait_for_server_active(self, server, client=None):
368 """Waits for a server to reach active status.
369
370 :param server: mapping having schema {'id': <server_id>}
371 :param clien: servers client (self.os_primary.servers_client as
372 default value)
373 """
374 self.wait_for_server_status(
375 server, constants.SERVER_STATUS_ACTIVE, client)