blob: b76a81a946a9335f8cfc7afca68da3af1acbb625 [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
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050018from oslo_log import log
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +030019from tempest.common.utils import net_utils
Itzik Browne67ebb52016-05-15 05:34:41 +000020from tempest.common import waiters
Itzik Browne67ebb52016-05-15 05:34:41 +000021from tempest.lib.common.utils import data_utils
YAMAMOTO Takashi25935722017-01-23 15:34:11 +090022from tempest.lib.common.utils import test_utils
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -050023from tempest.lib import exceptions as lib_exc
Itzik Browne67ebb52016-05-15 05:34:41 +000024
Chandan Kumar667d3d32017-09-22 12:24:06 +053025from neutron_tempest_plugin.api import base as base_api
26from neutron_tempest_plugin.common import ssh
27from neutron_tempest_plugin import config
28from neutron_tempest_plugin.scenario import constants
Itzik Browne67ebb52016-05-15 05:34:41 +000029
30CONF = config.CONF
Itzik Browne67ebb52016-05-15 05:34:41 +000031
Alex Stafeyevc4d9c352016-12-12 04:13:33 -050032LOG = log.getLogger(__name__)
33
Itzik Browne67ebb52016-05-15 05:34:41 +000034
35class BaseTempestTestCase(base_api.BaseNetworkTest):
36 @classmethod
37 def resource_setup(cls):
38 super(BaseTempestTestCase, cls).resource_setup()
39
Itzik Browne67ebb52016-05-15 05:34:41 +000040 cls.keypairs = []
41
42 @classmethod
43 def resource_cleanup(cls):
Itzik Browne67ebb52016-05-15 05:34:41 +000044 for keypair in cls.keypairs:
Roee Agiman6a0a18a2017-11-16 11:51:56 +020045 client = keypair['client']
46 client.delete_keypair(
47 keypair_name=keypair['keypair']['name'])
Itzik Browne67ebb52016-05-15 05:34:41 +000048
49 super(BaseTempestTestCase, cls).resource_cleanup()
50
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000051 def create_server(self, flavor_ref, image_ref, key_name, networks,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020052 **kwargs):
Itzik Brownbac51dc2016-10-31 12:25:04 +000053 """Create a server using tempest lib
54 All the parameters are the ones used in Compute API
Roee Agiman6a0a18a2017-11-16 11:51:56 +020055 * - Kwargs that require admin privileges
Itzik Brownbac51dc2016-10-31 12:25:04 +000056
57 Args:
58 flavor_ref(str): The flavor of the server to be provisioned.
59 image_ref(str): The image of the server to be provisioned.
60 key_name(str): SSH key to to be used to connect to the
61 provisioned server.
62 networks(list): List of dictionaries where each represent
63 an interface to be attached to the server. For network
64 it should be {'uuid': network_uuid} and for port it should
65 be {'port': port_uuid}
Roee Agiman6a0a18a2017-11-16 11:51:56 +020066 kwargs:
Itzik Brownbac51dc2016-10-31 12:25:04 +000067 name(str): Name of the server to be provisioned.
68 security_groups(list): List of dictionaries where
69 the keys is 'name' and the value is the name of
70 the security group. If it's not passed the default
71 security group will be used.
Roee Agiman6a0a18a2017-11-16 11:51:56 +020072 availability_zone(str)*: The availability zone that
73 the instance will be in.
74 You can request a specific az without actually creating one,
75 Just pass 'X:Y' where X is the default availability
76 zone, and Y is the compute host name.
Itzik Brownbac51dc2016-10-31 12:25:04 +000077 """
78
Jakub Libosvarffd9b912017-11-16 09:54:14 +000079 kwargs.setdefault('name', data_utils.rand_name('server-test'))
Itzik Brownbac51dc2016-10-31 12:25:04 +000080
Jakub Libosvarffd9b912017-11-16 09:54:14 +000081 # We cannot use setdefault() here because caller could have passed
82 # security_groups=None and we don't want to pass None to
83 # client.create_server()
84 if not kwargs.get('security_groups'):
85 kwargs['security_groups'] = [{'name': 'default'}]
Roee Agiman6a0a18a2017-11-16 11:51:56 +020086
Jakub Libosvarffd9b912017-11-16 09:54:14 +000087 client = self.os_primary.servers_client
88 if kwargs.get('availability_zone'):
Roee Agiman6a0a18a2017-11-16 11:51:56 +020089 client = self.os_admin.servers_client
Roee Agiman6a0a18a2017-11-16 11:51:56 +020090
Jakub Libosvarffd9b912017-11-16 09:54:14 +000091 server = client.create_server(
92 flavorRef=flavor_ref,
93 imageRef=image_ref,
94 key_name=key_name,
95 networks=networks,
96 **kwargs)
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000097
98 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +020099 waiters.wait_for_server_termination,
100 client,
101 server['server']['id'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000102 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200103 client.delete_server,
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000104 server['server']['id'])
Itzik Browne67ebb52016-05-15 05:34:41 +0000105 return server
106
107 @classmethod
108 def create_keypair(cls, client=None):
rajat294495c042017-06-28 15:37:16 +0530109 client = client or cls.os_primary.keypairs_client
Itzik Browne67ebb52016-05-15 05:34:41 +0000110 name = data_utils.rand_name('keypair-test')
111 body = client.create_keypair(name=name)
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200112 body.update(client=client)
113 if client is cls.os_primary.keypairs_client:
114 cls.keypairs.append(body)
115
Itzik Browne67ebb52016-05-15 05:34:41 +0000116 return body['keypair']
117
118 @classmethod
Jens Harbott54357632017-11-21 11:47:06 +0000119 def create_secgroup_rules(cls, rule_list, secgroup_id=None,
120 client=None):
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200121 client = client or cls.os_primary.network_client
Itzik Browne67ebb52016-05-15 05:34:41 +0000122 if not secgroup_id:
123 sgs = client.list_security_groups()['security_groups']
124 for sg in sgs:
125 if sg['name'] == constants.DEFAULT_SECURITY_GROUP:
126 secgroup_id = sg['id']
127 break
128
Itzik Brown1ef813a2016-06-06 12:56:21 +0000129 for rule in rule_list:
130 direction = rule.pop('direction')
131 client.create_security_group_rule(
132 direction=direction,
133 security_group_id=secgroup_id,
134 **rule)
135
136 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200137 def create_loginable_secgroup_rule(cls, secgroup_id=None,
138 client=None):
Itzik Brown1ef813a2016-06-06 12:56:21 +0000139 """This rule is intended to permit inbound ssh
140
141 Allowing ssh traffic traffic from all sources, so no group_id is
142 provided.
143 Setting a group_id would only permit traffic from ports
144 belonging to the same security group.
145 """
146
147 rule_list = [{'protocol': 'tcp',
148 'direction': 'ingress',
149 'port_range_min': 22,
150 'port_range_max': 22,
151 'remote_ip_prefix': '0.0.0.0/0'}]
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200152 client = client or cls.os_primary.network_client
Jens Harbott54357632017-11-21 11:47:06 +0000153 cls.create_secgroup_rules(rule_list, client=client,
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200154 secgroup_id=secgroup_id)
Itzik Browne67ebb52016-05-15 05:34:41 +0000155
156 @classmethod
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200157 def create_pingable_secgroup_rule(cls, secgroup_id=None,
158 client=None):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900159 """This rule is intended to permit inbound ping
160 """
161
162 rule_list = [{'protocol': 'icmp',
163 'direction': 'ingress',
164 'port_range_min': 8, # type
165 'port_range_max': 0, # code
166 'remote_ip_prefix': '0.0.0.0/0'}]
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200167 client = client or cls.os_primary.network_client
Jens Harbott54357632017-11-21 11:47:06 +0000168 cls.create_secgroup_rules(rule_list, client=client,
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200169 secgroup_id=secgroup_id)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900170
171 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300172 def create_router_by_client(cls, is_admin=False, **kwargs):
173 kwargs.update({'router_name': data_utils.rand_name('router'),
174 'admin_state_up': True,
175 'external_network_id': CONF.network.public_network_id})
176 if not is_admin:
177 router = cls.create_router(**kwargs)
178 else:
179 router = cls.create_admin_router(**kwargs)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500180 LOG.debug("Created router %s", router['name'])
Itzik Browne67ebb52016-05-15 05:34:41 +0000181 cls.routers.append(router)
182 return router
183
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200184 def create_and_associate_floatingip(self, port_id, client=None):
185 client = client or self.os_primary.network_client
186 fip = client.create_floatingip(
Itzik Browne67ebb52016-05-15 05:34:41 +0000187 CONF.network.public_network_id,
188 port_id=port_id)['floatingip']
Roee Agiman6a0a18a2017-11-16 11:51:56 +0200189 if client is self.os_primary.network_client:
190 self.floating_ips.append(fip)
Itzik Browne67ebb52016-05-15 05:34:41 +0000191 return fip
192
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000193 def setup_network_and_server(self, router=None, **kwargs):
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300194 """Create network resources and a server.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000195
196 Creating a network, subnet, router, keypair, security group
197 and a server.
198 """
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000199 self.network = self.create_network()
200 LOG.debug("Created network %s", self.network['name'])
201 self.subnet = self.create_subnet(self.network)
202 LOG.debug("Created subnet %s", self.subnet['id'])
Itzik Brown1ef813a2016-06-06 12:56:21 +0000203
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400204 secgroup = self.os_primary.network_client.create_security_group(
Chandan Kumarc125fd12017-11-15 19:41:01 +0530205 name=data_utils.rand_name('secgroup'))
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500206 LOG.debug("Created security group %s",
207 secgroup['security_group']['name'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000208 self.security_groups.append(secgroup['security_group'])
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300209 if not router:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000210 router = self.create_router_by_client(**kwargs)
211 self.create_router_interface(router['id'], self.subnet['id'])
212 self.keypair = self.create_keypair()
213 self.create_loginable_secgroup_rule(
Itzik Brownbac51dc2016-10-31 12:25:04 +0000214 secgroup_id=secgroup['security_group']['id'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000215 self.server = self.create_server(
Itzik Brown1ef813a2016-06-06 12:56:21 +0000216 flavor_ref=CONF.compute.flavor_ref,
217 image_ref=CONF.compute.image_ref,
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000218 key_name=self.keypair['name'],
219 networks=[{'uuid': self.network['id']}],
Itzik Brownbac51dc2016-10-31 12:25:04 +0000220 security_groups=[{'name': secgroup['security_group']['name']}])
Federico Ressie7417b72018-05-30 05:50:58 +0200221 self.wait_for_server_active(self.server['server'])
Jakub Libosvar1345d9d2017-06-09 13:59:05 +0000222 self.port = self.client.list_ports(network_id=self.network['id'],
223 device_id=self.server[
224 'server']['id'])['ports'][0]
225 self.fip = self.create_and_associate_floatingip(self.port['id'])
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500226
227 def check_connectivity(self, host, ssh_user, ssh_key, servers=None):
228 ssh_client = ssh.Client(host, ssh_user, pkey=ssh_key)
229 try:
230 ssh_client.test_connection_auth()
231 except lib_exc.SSHTimeout as ssh_e:
232 LOG.debug(ssh_e)
233 self._log_console_output(servers)
234 raise
235
236 def _log_console_output(self, servers=None):
237 if not CONF.compute_feature_enabled.console_output:
238 LOG.debug('Console output not supported, cannot log')
239 return
240 if not servers:
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400241 servers = self.os_primary.servers_client.list_servers()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500242 servers = servers['servers']
243 for server in servers:
244 try:
245 console_output = (
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400246 self.os_primary.servers_client.get_console_output(
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500247 server['id'])['output'])
248 LOG.debug('Console output for %s\nbody=\n%s',
249 server['id'], console_output)
250 except lib_exc.NotFound:
251 LOG.debug("Server %s disappeared(deleted) while looking "
252 "for the console log", server['id'])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900253
254 def _check_remote_connectivity(self, source, dest, should_succeed=True,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300255 nic=None, mtu=None, fragmentation=True):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900256 """check ping server via source ssh connection
257
258 :param source: RemoteClient: an ssh connection from which to ping
259 :param dest: and IP to ping against
260 :param should_succeed: boolean should ping succeed or not
261 :param nic: specific network interface to ping from
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300262 :param mtu: mtu size for the packet to be sent
263 :param fragmentation: Flag for packet fragmentation
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900264 :returns: boolean -- should_succeed == ping
265 :returns: ping is false if ping failed
266 """
267 def ping_host(source, host, count=CONF.validation.ping_count,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300268 size=CONF.validation.ping_size, nic=None, mtu=None,
269 fragmentation=True):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900270 addr = netaddr.IPAddress(host)
271 cmd = 'ping6' if addr.version == 6 else 'ping'
272 if nic:
273 cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300274 if mtu:
275 if not fragmentation:
276 cmd += ' -M do'
277 size = str(net_utils.get_ping_payload_size(
278 mtu=mtu, ip_version=addr.version))
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900279 cmd += ' -c{0} -w{0} -s{1} {2}'.format(count, size, host)
280 return source.exec_command(cmd)
281
282 def ping_remote():
283 try:
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300284 result = ping_host(source, dest, nic=nic, mtu=mtu,
285 fragmentation=fragmentation)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900286
287 except lib_exc.SSHExecCommandFailed:
288 LOG.warning('Failed to ping IP: %s via a ssh connection '
289 'from: %s.', dest, source.host)
290 return not should_succeed
291 LOG.debug('ping result: %s', result)
292 # Assert that the return traffic was from the correct
293 # source address.
294 from_source = 'from %s' % dest
295 self.assertIn(from_source, result)
296 return should_succeed
297
298 return test_utils.call_until_true(ping_remote,
299 CONF.validation.ping_timeout,
300 1)
301
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,
304 servers=None):
305 try:
306 self.assertTrue(self._check_remote_connectivity(
307 source, dest, should_succeed, nic, mtu, fragmentation))
308 except lib_exc.SSHTimeout as ssh_e:
309 LOG.debug(ssh_e)
310 self._log_console_output(servers)
311 raise
312 except AssertionError:
313 self._log_console_output(servers)
314 raise
Chandan Kumarc125fd12017-11-15 19:41:01 +0530315
316 def ping_ip_address(self, ip_address, should_succeed=True,
317 ping_timeout=None, mtu=None):
318 # the code is taken from tempest/scenario/manager.py in tempest git
319 timeout = ping_timeout or CONF.validation.ping_timeout
320 cmd = ['ping', '-c1', '-w1']
321
322 if mtu:
323 cmd += [
324 # don't fragment
325 '-M', 'do',
326 # ping receives just the size of ICMP payload
327 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
328 ]
329 cmd.append(ip_address)
330
331 def ping():
332 proc = subprocess.Popen(cmd,
333 stdout=subprocess.PIPE,
334 stderr=subprocess.PIPE)
335 proc.communicate()
336
337 return (proc.returncode == 0) == should_succeed
338
339 caller = test_utils.find_test_caller()
340 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
341 ' expected result is %(should_succeed)s', {
342 'caller': caller, 'ip': ip_address, 'timeout': timeout,
343 'should_succeed':
344 'reachable' if should_succeed else 'unreachable'
345 })
346 result = test_utils.call_until_true(ping, timeout, 1)
347 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
348 'ping result is %(result)s', {
349 'caller': caller, 'ip': ip_address, 'timeout': timeout,
350 'result': 'expected' if result else 'unexpected'
351 })
352 return result
Federico Ressie7417b72018-05-30 05:50:58 +0200353
354 def wait_for_server_status(self, server, status, client=None, **kwargs):
355 """Waits for a server to reach a given status.
356
357 :param server: mapping having schema {'id': <server_id>}
358 :param status: string status to wait for (es: 'ACTIVE')
359 :param clien: servers client (self.os_primary.servers_client as
360 default value)
361 """
362
363 client = client or self.os_primary.servers_client
364 waiters.wait_for_server_status(client, server['id'], status, **kwargs)
365
366 def wait_for_server_active(self, server, client=None):
367 """Waits for a server to reach active status.
368
369 :param server: mapping having schema {'id': <server_id>}
370 :param clien: servers client (self.os_primary.servers_client as
371 default value)
372 """
373 self.wait_for_server_status(
374 server, constants.SERVER_STATUS_ACTIVE, client)