blob: f928e16ad7ec26cdf2b364c5ceb42bd5c996eb66 [file] [log] [blame]
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +02001# Copyright 2012 OpenStack Foundation
2# Copyright 2013 IBM Corp.
3# All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020017from oslo_log import log
18from oslo_utils import netutils
19
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020020from tempest.common import utils
21from tempest.common.utils.linux import remote_client
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020022from tempest import config
23from tempest.lib.common.utils import data_utils
24from tempest.lib.common.utils import test_utils
25from tempest.lib import exceptions as lib_exc
26import tempest.test
27
28CONF = config.CONF
29
30LOG = log.getLogger(__name__)
31
32
33class ScenarioTest(tempest.test.BaseTestCase):
34 """Base class for scenario tests. Uses tempest own clients. """
35
36 credentials = ['primary']
37
38 @classmethod
39 def setup_clients(cls):
40 super(ScenarioTest, cls).setup_clients()
41 # Clients (in alphabetical order)
42 cls.keypairs_client = cls.os_primary.keypairs_client
43 cls.servers_client = cls.os_primary.servers_client
44 # Neutron network client
45 cls.networks_client = cls.os_primary.networks_client
46 cls.ports_client = cls.os_primary.ports_client
47 cls.routers_client = cls.os_primary.routers_client
48 cls.subnets_client = cls.os_primary.subnets_client
49 cls.floating_ips_client = cls.os_primary.floating_ips_client
50 cls.security_groups_client = cls.os_primary.security_groups_client
51 cls.security_group_rules_client = (
52 cls.os_primary.security_group_rules_client)
53
54 # ## Test functions library
55 #
56 # The create_[resource] functions only return body and discard the
57 # resp part which is not used in scenario tests
58
59 def _create_port(self, network_id, client=None, namestart='port-quotatest',
60 **kwargs):
61 if not client:
62 client = self.ports_client
63 name = data_utils.rand_name(namestart)
64 result = client.create_port(
65 name=name,
66 network_id=network_id,
67 **kwargs)
68 self.assertIsNotNone(result, 'Unable to allocate port')
69 port = result['port']
70 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
71 client.delete_port, port['id'])
72 return port
73
74 def create_keypair(self, client=None):
75 if not client:
76 client = self.keypairs_client
77 name = data_utils.rand_name(self.__class__.__name__)
78 # We don't need to create a keypair by pubkey in scenario
79 body = client.create_keypair(name=name)
80 self.addCleanup(client.delete_keypair, name)
81 return body['keypair']
82
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020083 def get_remote_client(self, ip_address, username=None, private_key=None):
84 """Get a SSH client to a remote server
85
86 @param ip_address the server floating or fixed IP address to use
87 for ssh validation
88 @param username name of the Linux account on the remote server
89 @param private_key the SSH private key to use
90 @return a RemoteClient object
91 """
92
93 if username is None:
94 username = CONF.validation.image_ssh_user
95 # Set this with 'keypair' or others to log in with keypair or
96 # username/password.
97 if CONF.validation.auth_method == 'keypair':
98 password = None
99 if private_key is None:
100 private_key = self.keypair['private_key']
101 else:
102 password = CONF.validation.image_ssh_password
103 private_key = None
104 linux_client = remote_client.RemoteClient(ip_address, username,
105 pkey=private_key,
106 password=password)
107 try:
108 linux_client.validate_authentication()
109 except Exception as e:
110 message = ('Initializing SSH connection to %(ip)s failed. '
111 'Error: %(error)s' % {'ip': ip_address,
112 'error': e})
113 caller = test_utils.find_test_caller()
114 if caller:
115 message = '(%s) %s' % (caller, message)
116 LOG.exception(message)
117 self._log_console_output()
118 raise
119
120 return linux_client
121
122 def _log_console_output(self, servers=None):
123 if not CONF.compute_feature_enabled.console_output:
124 LOG.debug('Console output not supported, cannot log')
125 return
126 if not servers:
127 servers = self.servers_client.list_servers()
128 servers = servers['servers']
129 for server in servers:
130 try:
131 console_output = self.servers_client.get_console_output(
132 server['id'])['output']
133 LOG.debug('Console output for %s\nbody=\n%s',
134 server['id'], console_output)
135 except lib_exc.NotFound:
136 LOG.debug("Server %s disappeared(deleted) while looking "
137 "for the console log", server['id'])
138
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200139
140class NetworkScenarioTest(ScenarioTest):
141 """Base class for network scenario tests.
142
143 This class provide helpers for network scenario tests, using the neutron
144 API. Helpers from ancestor which use the nova network API are overridden
145 with the neutron API.
146
147 This Class also enforces using Neutron instead of novanetwork.
148 Subclassed tests will be skipped if Neutron is not enabled
149
150 """
151
152 credentials = ['primary', 'admin']
153
154 @classmethod
155 def skip_checks(cls):
156 super(NetworkScenarioTest, cls).skip_checks()
157 if not CONF.service_available.neutron:
158 raise cls.skipException('Neutron not available')
159 if not utils.is_extension_enabled('bgpvpn', 'network'):
160 msg = "Bgpvpn extension not enabled."
161 raise cls.skipException(msg)
162
163 def _create_network(self, networks_client=None,
164 tenant_id=None,
165 namestart='network-smoke-',
166 port_security_enabled=True):
167 if not networks_client:
168 networks_client = self.networks_client
169 if not tenant_id:
170 tenant_id = networks_client.tenant_id
171 name = data_utils.rand_name(namestart)
172 network_kwargs = dict(name=name, tenant_id=tenant_id)
173 # Neutron disables port security by default so we have to check the
174 # config before trying to create the network with port_security_enabled
175 if CONF.network_feature_enabled.port_security:
176 network_kwargs['port_security_enabled'] = port_security_enabled
177 result = networks_client.create_network(**network_kwargs)
178 network = result['network']
179
180 self.assertEqual(network['name'], name)
181 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
182 networks_client.delete_network,
183 network['id'])
184 return network
185
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200186 def _get_server_port_id_and_ip4(self, server, ip_addr=None):
187 ports = self.os_admin.ports_client.list_ports(
188 device_id=server['id'], fixed_ip=ip_addr)['ports']
189 # A port can have more than one IP address in some cases.
190 # If the network is dual-stack (IPv4 + IPv6), this port is associated
191 # with 2 subnets
192 p_status = ['ACTIVE']
193 # NOTE(vsaienko) With Ironic, instances live on separate hardware
194 # servers. Neutron does not bind ports for Ironic instances, as a
195 # result the port remains in the DOWN state.
196 # TODO(vsaienko) remove once bug: #1599836 is resolved.
197 if getattr(CONF.service_available, 'ironic', False):
198 p_status.append('DOWN')
199 port_map = [(p["id"], fxip["ip_address"])
200 for p in ports
201 for fxip in p["fixed_ips"]
Bernard Cafarellic3bec862020-09-10 13:59:49 +0200202 if netutils.is_valid_ipv4(fxip["ip_address"]) and
203 p['status'] in p_status]
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200204 inactive = [p for p in ports if p['status'] != 'ACTIVE']
205 if inactive:
206 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
207
208 self.assertNotEqual(0, len(port_map),
209 "No IPv4 addresses found in: %s" % ports)
210 self.assertEqual(len(port_map), 1,
211 "Found multiple IPv4 addresses: %s. "
212 "Unable to determine which port to target."
213 % port_map)
214 return port_map[0]
215
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200216 def create_floating_ip(self, thing, external_network_id=None,
217 port_id=None, client=None):
218 """Create a floating IP and associates to a resource/port on Neutron"""
219 if not external_network_id:
220 external_network_id = CONF.network.public_network_id
221 if not client:
222 client = self.floating_ips_client
223 if not port_id:
224 port_id, ip4 = self._get_server_port_id_and_ip4(thing)
225 else:
226 ip4 = None
227 result = client.create_floatingip(
228 floating_network_id=external_network_id,
229 port_id=port_id,
230 tenant_id=thing['tenant_id'],
231 fixed_ip_address=ip4
232 )
233 floating_ip = result['floatingip']
234 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
235 client.delete_floatingip,
236 floating_ip['id'])
237 return floating_ip
238
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200239 def _check_remote_connectivity(self, source, dest, should_succeed=True,
240 nic=None):
241 """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
247 :returns: boolean -- should_succeed == ping
248 :returns: ping is false if ping failed
249 """
250 def ping_remote():
251 try:
252 source.ping_host(dest, nic=nic)
253 except lib_exc.SSHExecCommandFailed:
254 LOG.warning('Failed to ping IP: %s via a ssh connection '
255 'from: %s.', dest, source.ssh_client.host)
256 return not should_succeed
257 return should_succeed
258
259 return test_utils.call_until_true(ping_remote,
260 CONF.validation.ping_timeout,
261 1)
262
263 def _create_security_group(self, security_group_rules_client=None,
264 tenant_id=None,
265 namestart='secgroup-smoke',
266 security_groups_client=None):
267 if security_group_rules_client is None:
268 security_group_rules_client = self.security_group_rules_client
269 if security_groups_client is None:
270 security_groups_client = self.security_groups_client
271 if tenant_id is None:
272 tenant_id = security_groups_client.tenant_id
273 secgroup = self._create_empty_security_group(
274 namestart=namestart, client=security_groups_client,
275 tenant_id=tenant_id)
276
277 # Add rules to the security group
278 rules = self._create_loginable_secgroup_rule(
279 security_group_rules_client=security_group_rules_client,
280 secgroup=secgroup,
281 security_groups_client=security_groups_client)
282 for rule in rules:
283 self.assertEqual(tenant_id, rule['tenant_id'])
284 self.assertEqual(secgroup['id'], rule['security_group_id'])
285 return secgroup
286
287 def _create_empty_security_group(self, client=None, tenant_id=None,
288 namestart='secgroup-smoke'):
289 """Create a security group without rules.
290
291 Default rules will be created:
292 - IPv4 egress to any
293 - IPv6 egress to any
294
295 :param tenant_id: secgroup will be created in this tenant
296 :returns: the created security group
297 """
298 if client is None:
299 client = self.security_groups_client
300 if not tenant_id:
301 tenant_id = client.tenant_id
302 sg_name = data_utils.rand_name(namestart)
303 sg_desc = sg_name + " description"
304 sg_dict = dict(name=sg_name,
305 description=sg_desc)
306 sg_dict['tenant_id'] = tenant_id
307 result = client.create_security_group(**sg_dict)
308
309 secgroup = result['security_group']
310 self.assertEqual(secgroup['name'], sg_name)
311 self.assertEqual(tenant_id, secgroup['tenant_id'])
312 self.assertEqual(secgroup['description'], sg_desc)
313
314 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
315 client.delete_security_group, secgroup['id'])
316 return secgroup
317
318 def _default_security_group(self, client=None, tenant_id=None):
319 """Get default secgroup for given tenant_id.
320
321 :returns: default secgroup for given tenant
322 """
323 if client is None:
324 client = self.security_groups_client
325 if not tenant_id:
326 tenant_id = client.tenant_id
327 sgs = [
328 sg for sg in list(client.list_security_groups().values())[0]
329 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
330 ]
331 msg = "No default security group for tenant %s." % (tenant_id)
332 self.assertGreater(len(sgs), 0, msg)
333 return sgs[0]
334
335 def _create_security_group_rule(self, secgroup=None,
336 sec_group_rules_client=None,
337 tenant_id=None,
338 security_groups_client=None, **kwargs):
339 """Create a rule from a dictionary of rule parameters.
340
341 Create a rule in a secgroup. if secgroup not defined will search for
342 default secgroup in tenant_id.
343
344 :param secgroup: the security group.
345 :param tenant_id: if secgroup not passed -- the tenant in which to
346 search for default secgroup
347 :param kwargs: a dictionary containing rule parameters:
348 for example, to allow incoming ssh:
349 rule = {
350 direction: 'ingress'
351 protocol:'tcp',
352 port_range_min: 22,
353 port_range_max: 22
354 }
355 """
356 if sec_group_rules_client is None:
357 sec_group_rules_client = self.security_group_rules_client
358 if security_groups_client is None:
359 security_groups_client = self.security_groups_client
360 if not tenant_id:
361 tenant_id = security_groups_client.tenant_id
362 if secgroup is None:
363 secgroup = self._default_security_group(
364 client=security_groups_client, tenant_id=tenant_id)
365
366 ruleset = dict(security_group_id=secgroup['id'],
367 tenant_id=secgroup['tenant_id'])
368 ruleset.update(kwargs)
369
370 sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
371 sg_rule = sg_rule['security_group_rule']
372
373 self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
374 self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
375
376 return sg_rule
377
378 def _create_loginable_secgroup_rule(self, security_group_rules_client=None,
379 secgroup=None,
380 security_groups_client=None):
381 """Create loginable security group rule
382
383 This function will create:
384 1. egress and ingress tcp port 22 allow rule in order to allow ssh
385 access for ipv4.
386 2. egress and ingress tcp port 80 allow rule in order to allow http
387 access for ipv4.
388 3. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
389 4. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
390 """
391
392 if security_group_rules_client is None:
393 security_group_rules_client = self.security_group_rules_client
394 if security_groups_client is None:
395 security_groups_client = self.security_groups_client
396 rules = []
397 rulesets = [
398 dict(
399 # ssh
400 protocol='tcp',
401 port_range_min=22,
402 port_range_max=22,
403 ),
404 dict(
405 # http
406 protocol='tcp',
407 port_range_min=80,
408 port_range_max=80,
409 ),
410 dict(
411 # ping
412 protocol='icmp',
413 ),
414 dict(
415 # ipv6-icmp for ping6
416 protocol='icmp',
417 ethertype='IPv6',
418 )
419 ]
420 sec_group_rules_client = security_group_rules_client
421 for ruleset in rulesets:
422 for r_direction in ['ingress', 'egress']:
423 ruleset['direction'] = r_direction
424 try:
425 sg_rule = self._create_security_group_rule(
426 sec_group_rules_client=sec_group_rules_client,
427 secgroup=secgroup,
428 security_groups_client=security_groups_client,
429 **ruleset)
430 except lib_exc.Conflict as ex:
431 # if rule already exist - skip rule and continue
432 msg = 'Security group rule already exists'
433 if msg not in ex._error_string:
434 raise ex
435 else:
436 self.assertEqual(r_direction, sg_rule['direction'])
437 rules.append(sg_rule)
438
439 return rules
440
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200441 def _create_router(self, client=None, tenant_id=None,
442 namestart='router-smoke'):
443 if not client:
444 client = self.routers_client
445 if not tenant_id:
446 tenant_id = client.tenant_id
447 name = data_utils.rand_name(namestart)
448 result = client.create_router(name=name,
449 admin_state_up=True,
450 tenant_id=tenant_id)
451 router = result['router']
452 self.assertEqual(router['name'], name)
453 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
454 client.delete_router,
455 router['id'])
456 return router