blob: 93d35bf6bda4d122d2af2d74c5bd8dc6c3da5999 [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
Roman Popelkafd509eb2022-04-07 15:10:08 +020026from tempest.scenario import manager
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020027
28CONF = config.CONF
29
30LOG = log.getLogger(__name__)
31
32
Roman Popelkafd509eb2022-04-07 15:10:08 +020033class ScenarioTest(manager.NetworkScenarioTest):
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020034 """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
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020059 def get_remote_client(self, ip_address, username=None, private_key=None):
60 """Get a SSH client to a remote server
61
62 @param ip_address the server floating or fixed IP address to use
63 for ssh validation
64 @param username name of the Linux account on the remote server
65 @param private_key the SSH private key to use
66 @return a RemoteClient object
67 """
68
69 if username is None:
70 username = CONF.validation.image_ssh_user
71 # Set this with 'keypair' or others to log in with keypair or
72 # username/password.
73 if CONF.validation.auth_method == 'keypair':
74 password = None
75 if private_key is None:
76 private_key = self.keypair['private_key']
77 else:
78 password = CONF.validation.image_ssh_password
79 private_key = None
80 linux_client = remote_client.RemoteClient(ip_address, username,
81 pkey=private_key,
82 password=password)
83 try:
84 linux_client.validate_authentication()
85 except Exception as e:
86 message = ('Initializing SSH connection to %(ip)s failed. '
87 'Error: %(error)s' % {'ip': ip_address,
88 'error': e})
89 caller = test_utils.find_test_caller()
90 if caller:
91 message = '(%s) %s' % (caller, message)
92 LOG.exception(message)
93 self._log_console_output()
94 raise
95
96 return linux_client
97
98 def _log_console_output(self, servers=None):
99 if not CONF.compute_feature_enabled.console_output:
100 LOG.debug('Console output not supported, cannot log')
101 return
102 if not servers:
103 servers = self.servers_client.list_servers()
104 servers = servers['servers']
105 for server in servers:
106 try:
107 console_output = self.servers_client.get_console_output(
108 server['id'])['output']
109 LOG.debug('Console output for %s\nbody=\n%s',
110 server['id'], console_output)
111 except lib_exc.NotFound:
112 LOG.debug("Server %s disappeared(deleted) while looking "
113 "for the console log", server['id'])
114
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200115
116class NetworkScenarioTest(ScenarioTest):
117 """Base class for network scenario tests.
118
119 This class provide helpers for network scenario tests, using the neutron
120 API. Helpers from ancestor which use the nova network API are overridden
121 with the neutron API.
122
123 This Class also enforces using Neutron instead of novanetwork.
124 Subclassed tests will be skipped if Neutron is not enabled
125
126 """
127
128 credentials = ['primary', 'admin']
129
130 @classmethod
131 def skip_checks(cls):
132 super(NetworkScenarioTest, cls).skip_checks()
133 if not CONF.service_available.neutron:
134 raise cls.skipException('Neutron not available')
135 if not utils.is_extension_enabled('bgpvpn', 'network'):
136 msg = "Bgpvpn extension not enabled."
137 raise cls.skipException(msg)
138
139 def _create_network(self, networks_client=None,
140 tenant_id=None,
141 namestart='network-smoke-',
142 port_security_enabled=True):
143 if not networks_client:
144 networks_client = self.networks_client
145 if not tenant_id:
146 tenant_id = networks_client.tenant_id
147 name = data_utils.rand_name(namestart)
148 network_kwargs = dict(name=name, tenant_id=tenant_id)
149 # Neutron disables port security by default so we have to check the
150 # config before trying to create the network with port_security_enabled
151 if CONF.network_feature_enabled.port_security:
152 network_kwargs['port_security_enabled'] = port_security_enabled
153 result = networks_client.create_network(**network_kwargs)
154 network = result['network']
155
156 self.assertEqual(network['name'], name)
157 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
158 networks_client.delete_network,
159 network['id'])
160 return network
161
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200162 def _get_server_port_id_and_ip4(self, server, ip_addr=None):
163 ports = self.os_admin.ports_client.list_ports(
164 device_id=server['id'], fixed_ip=ip_addr)['ports']
165 # A port can have more than one IP address in some cases.
166 # If the network is dual-stack (IPv4 + IPv6), this port is associated
167 # with 2 subnets
168 p_status = ['ACTIVE']
169 # NOTE(vsaienko) With Ironic, instances live on separate hardware
170 # servers. Neutron does not bind ports for Ironic instances, as a
171 # result the port remains in the DOWN state.
172 # TODO(vsaienko) remove once bug: #1599836 is resolved.
173 if getattr(CONF.service_available, 'ironic', False):
174 p_status.append('DOWN')
175 port_map = [(p["id"], fxip["ip_address"])
176 for p in ports
177 for fxip in p["fixed_ips"]
Bernard Cafarellic3bec862020-09-10 13:59:49 +0200178 if netutils.is_valid_ipv4(fxip["ip_address"]) and
179 p['status'] in p_status]
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200180 inactive = [p for p in ports if p['status'] != 'ACTIVE']
181 if inactive:
182 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
183
184 self.assertNotEqual(0, len(port_map),
185 "No IPv4 addresses found in: %s" % ports)
186 self.assertEqual(len(port_map), 1,
187 "Found multiple IPv4 addresses: %s. "
188 "Unable to determine which port to target."
189 % port_map)
190 return port_map[0]
191
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200192 def create_floating_ip(self, thing, external_network_id=None,
193 port_id=None, client=None):
194 """Create a floating IP and associates to a resource/port on Neutron"""
195 if not external_network_id:
196 external_network_id = CONF.network.public_network_id
197 if not client:
198 client = self.floating_ips_client
199 if not port_id:
200 port_id, ip4 = self._get_server_port_id_and_ip4(thing)
201 else:
202 ip4 = None
203 result = client.create_floatingip(
204 floating_network_id=external_network_id,
205 port_id=port_id,
206 tenant_id=thing['tenant_id'],
207 fixed_ip_address=ip4
208 )
209 floating_ip = result['floatingip']
210 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
211 client.delete_floatingip,
212 floating_ip['id'])
213 return floating_ip
214
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200215 def _check_remote_connectivity(self, source, dest, should_succeed=True,
216 nic=None):
217 """check ping server via source ssh connection
218
219 :param source: RemoteClient: an ssh connection from which to ping
220 :param dest: and IP to ping against
221 :param should_succeed: boolean should ping succeed or not
222 :param nic: specific network interface to ping from
223 :returns: boolean -- should_succeed == ping
224 :returns: ping is false if ping failed
225 """
226 def ping_remote():
227 try:
228 source.ping_host(dest, nic=nic)
229 except lib_exc.SSHExecCommandFailed:
230 LOG.warning('Failed to ping IP: %s via a ssh connection '
231 'from: %s.', dest, source.ssh_client.host)
232 return not should_succeed
233 return should_succeed
234
235 return test_utils.call_until_true(ping_remote,
236 CONF.validation.ping_timeout,
237 1)
238
239 def _create_security_group(self, security_group_rules_client=None,
240 tenant_id=None,
241 namestart='secgroup-smoke',
242 security_groups_client=None):
243 if security_group_rules_client is None:
244 security_group_rules_client = self.security_group_rules_client
245 if security_groups_client is None:
246 security_groups_client = self.security_groups_client
247 if tenant_id is None:
248 tenant_id = security_groups_client.tenant_id
249 secgroup = self._create_empty_security_group(
250 namestart=namestart, client=security_groups_client,
251 tenant_id=tenant_id)
252
253 # Add rules to the security group
254 rules = self._create_loginable_secgroup_rule(
255 security_group_rules_client=security_group_rules_client,
256 secgroup=secgroup,
257 security_groups_client=security_groups_client)
258 for rule in rules:
259 self.assertEqual(tenant_id, rule['tenant_id'])
260 self.assertEqual(secgroup['id'], rule['security_group_id'])
261 return secgroup
262
263 def _create_empty_security_group(self, client=None, tenant_id=None,
264 namestart='secgroup-smoke'):
265 """Create a security group without rules.
266
267 Default rules will be created:
268 - IPv4 egress to any
269 - IPv6 egress to any
270
271 :param tenant_id: secgroup will be created in this tenant
272 :returns: the created security group
273 """
274 if client is None:
275 client = self.security_groups_client
276 if not tenant_id:
277 tenant_id = client.tenant_id
278 sg_name = data_utils.rand_name(namestart)
279 sg_desc = sg_name + " description"
280 sg_dict = dict(name=sg_name,
281 description=sg_desc)
282 sg_dict['tenant_id'] = tenant_id
283 result = client.create_security_group(**sg_dict)
284
285 secgroup = result['security_group']
286 self.assertEqual(secgroup['name'], sg_name)
287 self.assertEqual(tenant_id, secgroup['tenant_id'])
288 self.assertEqual(secgroup['description'], sg_desc)
289
290 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
291 client.delete_security_group, secgroup['id'])
292 return secgroup
293
294 def _default_security_group(self, client=None, tenant_id=None):
295 """Get default secgroup for given tenant_id.
296
297 :returns: default secgroup for given tenant
298 """
299 if client is None:
300 client = self.security_groups_client
301 if not tenant_id:
302 tenant_id = client.tenant_id
303 sgs = [
304 sg for sg in list(client.list_security_groups().values())[0]
305 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
306 ]
307 msg = "No default security group for tenant %s." % (tenant_id)
308 self.assertGreater(len(sgs), 0, msg)
309 return sgs[0]
310
311 def _create_security_group_rule(self, secgroup=None,
312 sec_group_rules_client=None,
313 tenant_id=None,
314 security_groups_client=None, **kwargs):
315 """Create a rule from a dictionary of rule parameters.
316
317 Create a rule in a secgroup. if secgroup not defined will search for
318 default secgroup in tenant_id.
319
320 :param secgroup: the security group.
321 :param tenant_id: if secgroup not passed -- the tenant in which to
322 search for default secgroup
323 :param kwargs: a dictionary containing rule parameters:
324 for example, to allow incoming ssh:
325 rule = {
326 direction: 'ingress'
327 protocol:'tcp',
328 port_range_min: 22,
329 port_range_max: 22
330 }
331 """
332 if sec_group_rules_client is None:
333 sec_group_rules_client = self.security_group_rules_client
334 if security_groups_client is None:
335 security_groups_client = self.security_groups_client
336 if not tenant_id:
337 tenant_id = security_groups_client.tenant_id
338 if secgroup is None:
339 secgroup = self._default_security_group(
340 client=security_groups_client, tenant_id=tenant_id)
341
342 ruleset = dict(security_group_id=secgroup['id'],
343 tenant_id=secgroup['tenant_id'])
344 ruleset.update(kwargs)
345
346 sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
347 sg_rule = sg_rule['security_group_rule']
348
349 self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
350 self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
351
352 return sg_rule
353
354 def _create_loginable_secgroup_rule(self, security_group_rules_client=None,
355 secgroup=None,
356 security_groups_client=None):
357 """Create loginable security group rule
358
359 This function will create:
360 1. egress and ingress tcp port 22 allow rule in order to allow ssh
361 access for ipv4.
362 2. egress and ingress tcp port 80 allow rule in order to allow http
363 access for ipv4.
364 3. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
365 4. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
366 """
367
368 if security_group_rules_client is None:
369 security_group_rules_client = self.security_group_rules_client
370 if security_groups_client is None:
371 security_groups_client = self.security_groups_client
372 rules = []
373 rulesets = [
374 dict(
375 # ssh
376 protocol='tcp',
377 port_range_min=22,
378 port_range_max=22,
379 ),
380 dict(
381 # http
382 protocol='tcp',
383 port_range_min=80,
384 port_range_max=80,
385 ),
386 dict(
387 # ping
388 protocol='icmp',
389 ),
390 dict(
391 # ipv6-icmp for ping6
392 protocol='icmp',
393 ethertype='IPv6',
394 )
395 ]
396 sec_group_rules_client = security_group_rules_client
397 for ruleset in rulesets:
398 for r_direction in ['ingress', 'egress']:
399 ruleset['direction'] = r_direction
400 try:
401 sg_rule = self._create_security_group_rule(
402 sec_group_rules_client=sec_group_rules_client,
403 secgroup=secgroup,
404 security_groups_client=security_groups_client,
405 **ruleset)
406 except lib_exc.Conflict as ex:
407 # if rule already exist - skip rule and continue
408 msg = 'Security group rule already exists'
409 if msg not in ex._error_string:
410 raise ex
411 else:
412 self.assertEqual(r_direction, sg_rule['direction'])
413 rules.append(sg_rule)
414
415 return rules
416
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200417 def _create_router(self, client=None, tenant_id=None,
418 namestart='router-smoke'):
419 if not client:
420 client = self.routers_client
421 if not tenant_id:
422 tenant_id = client.tenant_id
423 name = data_utils.rand_name(namestart)
424 result = client.create_router(name=name,
425 admin_state_up=True,
426 tenant_id=tenant_id)
427 router = result['router']
428 self.assertEqual(router['name'], name)
429 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
430 client.delete_router,
431 router['id'])
432 return router