blob: 5cc085fef26a3f87930a6cfca334ced21a54da7d [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:
rajat294495c042017-06-28 15:37:16 +053045 cls.os_primary.keypairs_client.delete_keypair(
Itzik Browne67ebb52016-05-15 05:34:41 +000046 keypair_name=keypair['name'])
47
48 super(BaseTempestTestCase, cls).resource_cleanup()
49
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000050 def create_server(self, flavor_ref, image_ref, key_name, networks,
Itzik Brownbac51dc2016-10-31 12:25:04 +000051 name=None, security_groups=None):
52 """Create a server using tempest lib
53 All the parameters are the ones used in Compute API
54
55 Args:
56 flavor_ref(str): The flavor of the server to be provisioned.
57 image_ref(str): The image of the server to be provisioned.
58 key_name(str): SSH key to to be used to connect to the
59 provisioned server.
60 networks(list): List of dictionaries where each represent
61 an interface to be attached to the server. For network
62 it should be {'uuid': network_uuid} and for port it should
63 be {'port': port_uuid}
64 name(str): Name of the server to be provisioned.
65 security_groups(list): List of dictionaries where
66 the keys is 'name' and the value is the name of
67 the security group. If it's not passed the default
68 security group will be used.
69 """
70
Itzik Browne67ebb52016-05-15 05:34:41 +000071 name = name or data_utils.rand_name('server-test')
Itzik Brownbac51dc2016-10-31 12:25:04 +000072 if not security_groups:
73 security_groups = [{'name': 'default'}]
74
Brian Haleyf86ac2e2017-06-21 10:43:50 -040075 server = self.os_primary.servers_client.create_server(
Itzik Brownbac51dc2016-10-31 12:25:04 +000076 name=name,
77 flavorRef=flavor_ref,
Itzik Browne67ebb52016-05-15 05:34:41 +000078 imageRef=image_ref,
79 key_name=key_name,
Itzik Brownbac51dc2016-10-31 12:25:04 +000080 networks=networks,
81 security_groups=security_groups)
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000082
83 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
84 waiters.wait_for_server_termination,
Brian Haleyf86ac2e2017-06-21 10:43:50 -040085 self.os_primary.servers_client, server['server']['id'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000086 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Brian Haleyf86ac2e2017-06-21 10:43:50 -040087 self.os_primary.servers_client.delete_server,
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +000088 server['server']['id'])
Itzik Browne67ebb52016-05-15 05:34:41 +000089 return server
90
91 @classmethod
92 def create_keypair(cls, client=None):
rajat294495c042017-06-28 15:37:16 +053093 client = client or cls.os_primary.keypairs_client
Itzik Browne67ebb52016-05-15 05:34:41 +000094 name = data_utils.rand_name('keypair-test')
95 body = client.create_keypair(name=name)
96 cls.keypairs.append(body['keypair'])
97 return body['keypair']
98
99 @classmethod
Itzik Brown1ef813a2016-06-06 12:56:21 +0000100 def create_secgroup_rules(cls, rule_list, secgroup_id=None):
rajat294495c042017-06-28 15:37:16 +0530101 client = cls.os_primary.network_client
Itzik Browne67ebb52016-05-15 05:34:41 +0000102 if not secgroup_id:
103 sgs = client.list_security_groups()['security_groups']
104 for sg in sgs:
105 if sg['name'] == constants.DEFAULT_SECURITY_GROUP:
106 secgroup_id = sg['id']
107 break
108
Itzik Brown1ef813a2016-06-06 12:56:21 +0000109 for rule in rule_list:
110 direction = rule.pop('direction')
111 client.create_security_group_rule(
112 direction=direction,
113 security_group_id=secgroup_id,
114 **rule)
115
116 @classmethod
117 def create_loginable_secgroup_rule(cls, secgroup_id=None):
118 """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 """
125
126 rule_list = [{'protocol': 'tcp',
127 'direction': 'ingress',
128 'port_range_min': 22,
129 'port_range_max': 22,
130 'remote_ip_prefix': '0.0.0.0/0'}]
131 cls.create_secgroup_rules(rule_list, secgroup_id=secgroup_id)
Itzik Browne67ebb52016-05-15 05:34:41 +0000132
133 @classmethod
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900134 def create_pingable_secgroup_rule(cls, secgroup_id=None):
135 """This rule is intended to permit inbound ping
136 """
137
138 rule_list = [{'protocol': 'icmp',
139 'direction': 'ingress',
140 'port_range_min': 8, # type
141 'port_range_max': 0, # code
142 'remote_ip_prefix': '0.0.0.0/0'}]
143 cls.create_secgroup_rules(rule_list, secgroup_id=secgroup_id)
144
145 @classmethod
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300146 def create_router_by_client(cls, is_admin=False, **kwargs):
147 kwargs.update({'router_name': data_utils.rand_name('router'),
148 'admin_state_up': True,
149 'external_network_id': CONF.network.public_network_id})
150 if not is_admin:
151 router = cls.create_router(**kwargs)
152 else:
153 router = cls.create_admin_router(**kwargs)
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500154 LOG.debug("Created router %s", router['name'])
Itzik Browne67ebb52016-05-15 05:34:41 +0000155 cls.routers.append(router)
156 return router
157
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000158 def create_and_associate_floatingip(self, port_id):
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400159 fip = self.os_primary.network_client.create_floatingip(
Itzik Browne67ebb52016-05-15 05:34:41 +0000160 CONF.network.public_network_id,
161 port_id=port_id)['floatingip']
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000162 self.floating_ips.append(fip)
Itzik Browne67ebb52016-05-15 05:34:41 +0000163 return fip
164
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000165 def setup_network_and_server(self, router=None, **kwargs):
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300166 """Create network resources and a server.
Itzik Brownbac51dc2016-10-31 12:25:04 +0000167
168 Creating a network, subnet, router, keypair, security group
169 and a server.
170 """
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000171 self.network = self.create_network()
172 LOG.debug("Created network %s", self.network['name'])
173 self.subnet = self.create_subnet(self.network)
174 LOG.debug("Created subnet %s", self.subnet['id'])
Itzik Brown1ef813a2016-06-06 12:56:21 +0000175
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400176 secgroup = self.os_primary.network_client.create_security_group(
Chandan Kumarc125fd12017-11-15 19:41:01 +0530177 name=data_utils.rand_name('secgroup'))
Alex Stafeyevc4d9c352016-12-12 04:13:33 -0500178 LOG.debug("Created security group %s",
179 secgroup['security_group']['name'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000180 self.security_groups.append(secgroup['security_group'])
Genadi Chereshnyac0411e92016-07-11 16:59:42 +0300181 if not router:
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000182 router = self.create_router_by_client(**kwargs)
183 self.create_router_interface(router['id'], self.subnet['id'])
184 self.keypair = self.create_keypair()
185 self.create_loginable_secgroup_rule(
Itzik Brownbac51dc2016-10-31 12:25:04 +0000186 secgroup_id=secgroup['security_group']['id'])
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000187 self.server = self.create_server(
Itzik Brown1ef813a2016-06-06 12:56:21 +0000188 flavor_ref=CONF.compute.flavor_ref,
189 image_ref=CONF.compute.image_ref,
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000190 key_name=self.keypair['name'],
191 networks=[{'uuid': self.network['id']}],
Itzik Brownbac51dc2016-10-31 12:25:04 +0000192 security_groups=[{'name': secgroup['security_group']['name']}])
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400193 waiters.wait_for_server_status(self.os_primary.servers_client,
Genadi Chereshnya918dd0b2017-05-17 13:02:20 +0000194 self.server['server']['id'],
Itzik Brown1ef813a2016-06-06 12:56:21 +0000195 constants.SERVER_STATUS_ACTIVE)
Jakub Libosvar1345d9d2017-06-09 13:59:05 +0000196 self.port = self.client.list_ports(network_id=self.network['id'],
197 device_id=self.server[
198 'server']['id'])['ports'][0]
199 self.fip = self.create_and_associate_floatingip(self.port['id'])
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500200
201 def check_connectivity(self, host, ssh_user, ssh_key, servers=None):
202 ssh_client = ssh.Client(host, ssh_user, pkey=ssh_key)
203 try:
204 ssh_client.test_connection_auth()
205 except lib_exc.SSHTimeout as ssh_e:
206 LOG.debug(ssh_e)
207 self._log_console_output(servers)
208 raise
209
210 def _log_console_output(self, servers=None):
211 if not CONF.compute_feature_enabled.console_output:
212 LOG.debug('Console output not supported, cannot log')
213 return
214 if not servers:
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400215 servers = self.os_primary.servers_client.list_servers()
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500216 servers = servers['servers']
217 for server in servers:
218 try:
219 console_output = (
Brian Haleyf86ac2e2017-06-21 10:43:50 -0400220 self.os_primary.servers_client.get_console_output(
Jakub Libosvarc0c2f1d2017-01-31 12:12:21 -0500221 server['id'])['output'])
222 LOG.debug('Console output for %s\nbody=\n%s',
223 server['id'], console_output)
224 except lib_exc.NotFound:
225 LOG.debug("Server %s disappeared(deleted) while looking "
226 "for the console log", server['id'])
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900227
228 def _check_remote_connectivity(self, source, dest, should_succeed=True,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300229 nic=None, mtu=None, fragmentation=True):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900230 """check ping server via source ssh connection
231
232 :param source: RemoteClient: an ssh connection from which to ping
233 :param dest: and IP to ping against
234 :param should_succeed: boolean should ping succeed or not
235 :param nic: specific network interface to ping from
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300236 :param mtu: mtu size for the packet to be sent
237 :param fragmentation: Flag for packet fragmentation
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900238 :returns: boolean -- should_succeed == ping
239 :returns: ping is false if ping failed
240 """
241 def ping_host(source, host, count=CONF.validation.ping_count,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300242 size=CONF.validation.ping_size, nic=None, mtu=None,
243 fragmentation=True):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900244 addr = netaddr.IPAddress(host)
245 cmd = 'ping6' if addr.version == 6 else 'ping'
246 if nic:
247 cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300248 if mtu:
249 if not fragmentation:
250 cmd += ' -M do'
251 size = str(net_utils.get_ping_payload_size(
252 mtu=mtu, ip_version=addr.version))
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900253 cmd += ' -c{0} -w{0} -s{1} {2}'.format(count, size, host)
254 return source.exec_command(cmd)
255
256 def ping_remote():
257 try:
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300258 result = ping_host(source, dest, nic=nic, mtu=mtu,
259 fragmentation=fragmentation)
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900260
261 except lib_exc.SSHExecCommandFailed:
262 LOG.warning('Failed to ping IP: %s via a ssh connection '
263 'from: %s.', dest, source.host)
264 return not should_succeed
265 LOG.debug('ping result: %s', result)
266 # Assert that the return traffic was from the correct
267 # source address.
268 from_source = 'from %s' % dest
269 self.assertIn(from_source, result)
270 return should_succeed
271
272 return test_utils.call_until_true(ping_remote,
273 CONF.validation.ping_timeout,
274 1)
275
276 def check_remote_connectivity(self, source, dest, should_succeed=True,
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300277 nic=None, mtu=None, fragmentation=True):
YAMAMOTO Takashi25935722017-01-23 15:34:11 +0900278 self.assertTrue(self._check_remote_connectivity(
Genadi Chereshnya6d10c6e2017-07-05 11:34:20 +0300279 source, dest, should_succeed, nic, mtu, fragmentation))
Chandan Kumarc125fd12017-11-15 19:41:01 +0530280
281 def ping_ip_address(self, ip_address, should_succeed=True,
282 ping_timeout=None, mtu=None):
283 # the code is taken from tempest/scenario/manager.py in tempest git
284 timeout = ping_timeout or CONF.validation.ping_timeout
285 cmd = ['ping', '-c1', '-w1']
286
287 if mtu:
288 cmd += [
289 # don't fragment
290 '-M', 'do',
291 # ping receives just the size of ICMP payload
292 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
293 ]
294 cmd.append(ip_address)
295
296 def ping():
297 proc = subprocess.Popen(cmd,
298 stdout=subprocess.PIPE,
299 stderr=subprocess.PIPE)
300 proc.communicate()
301
302 return (proc.returncode == 0) == should_succeed
303
304 caller = test_utils.find_test_caller()
305 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
306 ' expected result is %(should_succeed)s', {
307 'caller': caller, 'ip': ip_address, 'timeout': timeout,
308 'should_succeed':
309 'reachable' if should_succeed else 'unreachable'
310 })
311 result = test_utils.call_until_true(ping, timeout, 1)
312 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
313 'ping result is %(result)s', {
314 'caller': caller, 'ip': ip_address, 'timeout': timeout,
315 'result': 'expected' if result else 'unexpected'
316 })
317 return result