blob: d902f8f35c132edaa07e7e188bdfadb62266b9a2 [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)
Roman Popelka763567e2022-04-12 09:25:51 +020093 self.log_console_output()
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020094 raise
95
96 return linux_client
97
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +020098
99class NetworkScenarioTest(ScenarioTest):
100 """Base class for network scenario tests.
101
102 This class provide helpers for network scenario tests, using the neutron
103 API. Helpers from ancestor which use the nova network API are overridden
104 with the neutron API.
105
106 This Class also enforces using Neutron instead of novanetwork.
107 Subclassed tests will be skipped if Neutron is not enabled
108
109 """
110
111 credentials = ['primary', 'admin']
112
113 @classmethod
114 def skip_checks(cls):
115 super(NetworkScenarioTest, cls).skip_checks()
116 if not CONF.service_available.neutron:
117 raise cls.skipException('Neutron not available')
118 if not utils.is_extension_enabled('bgpvpn', 'network'):
119 msg = "Bgpvpn extension not enabled."
120 raise cls.skipException(msg)
121
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200122 def _get_server_port_id_and_ip4(self, server, ip_addr=None):
123 ports = self.os_admin.ports_client.list_ports(
124 device_id=server['id'], fixed_ip=ip_addr)['ports']
125 # A port can have more than one IP address in some cases.
126 # If the network is dual-stack (IPv4 + IPv6), this port is associated
127 # with 2 subnets
128 p_status = ['ACTIVE']
129 # NOTE(vsaienko) With Ironic, instances live on separate hardware
130 # servers. Neutron does not bind ports for Ironic instances, as a
131 # result the port remains in the DOWN state.
132 # TODO(vsaienko) remove once bug: #1599836 is resolved.
133 if getattr(CONF.service_available, 'ironic', False):
134 p_status.append('DOWN')
135 port_map = [(p["id"], fxip["ip_address"])
136 for p in ports
137 for fxip in p["fixed_ips"]
Bernard Cafarellic3bec862020-09-10 13:59:49 +0200138 if netutils.is_valid_ipv4(fxip["ip_address"]) and
139 p['status'] in p_status]
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200140 inactive = [p for p in ports if p['status'] != 'ACTIVE']
141 if inactive:
142 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
143
144 self.assertNotEqual(0, len(port_map),
145 "No IPv4 addresses found in: %s" % ports)
146 self.assertEqual(len(port_map), 1,
147 "Found multiple IPv4 addresses: %s. "
148 "Unable to determine which port to target."
149 % port_map)
150 return port_map[0]
151
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200152 def create_floating_ip(self, thing, external_network_id=None,
153 port_id=None, client=None):
154 """Create a floating IP and associates to a resource/port on Neutron"""
155 if not external_network_id:
156 external_network_id = CONF.network.public_network_id
157 if not client:
158 client = self.floating_ips_client
159 if not port_id:
160 port_id, ip4 = self._get_server_port_id_and_ip4(thing)
161 else:
162 ip4 = None
163 result = client.create_floatingip(
164 floating_network_id=external_network_id,
165 port_id=port_id,
166 tenant_id=thing['tenant_id'],
167 fixed_ip_address=ip4
168 )
169 floating_ip = result['floatingip']
170 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
171 client.delete_floatingip,
172 floating_ip['id'])
173 return floating_ip
174
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200175 def _check_remote_connectivity(self, source, dest, should_succeed=True,
176 nic=None):
177 """check ping server via source ssh connection
178
179 :param source: RemoteClient: an ssh connection from which to ping
180 :param dest: and IP to ping against
181 :param should_succeed: boolean should ping succeed or not
182 :param nic: specific network interface to ping from
183 :returns: boolean -- should_succeed == ping
184 :returns: ping is false if ping failed
185 """
186 def ping_remote():
187 try:
188 source.ping_host(dest, nic=nic)
189 except lib_exc.SSHExecCommandFailed:
190 LOG.warning('Failed to ping IP: %s via a ssh connection '
191 'from: %s.', dest, source.ssh_client.host)
192 return not should_succeed
193 return should_succeed
194
195 return test_utils.call_until_true(ping_remote,
196 CONF.validation.ping_timeout,
197 1)
198
199 def _create_security_group(self, security_group_rules_client=None,
200 tenant_id=None,
201 namestart='secgroup-smoke',
202 security_groups_client=None):
203 if security_group_rules_client is None:
204 security_group_rules_client = self.security_group_rules_client
205 if security_groups_client is None:
206 security_groups_client = self.security_groups_client
207 if tenant_id is None:
208 tenant_id = security_groups_client.tenant_id
209 secgroup = self._create_empty_security_group(
210 namestart=namestart, client=security_groups_client,
211 tenant_id=tenant_id)
212
213 # Add rules to the security group
214 rules = self._create_loginable_secgroup_rule(
215 security_group_rules_client=security_group_rules_client,
216 secgroup=secgroup,
217 security_groups_client=security_groups_client)
218 for rule in rules:
219 self.assertEqual(tenant_id, rule['tenant_id'])
220 self.assertEqual(secgroup['id'], rule['security_group_id'])
221 return secgroup
222
223 def _create_empty_security_group(self, client=None, tenant_id=None,
224 namestart='secgroup-smoke'):
225 """Create a security group without rules.
226
227 Default rules will be created:
228 - IPv4 egress to any
229 - IPv6 egress to any
230
231 :param tenant_id: secgroup will be created in this tenant
232 :returns: the created security group
233 """
234 if client is None:
235 client = self.security_groups_client
236 if not tenant_id:
237 tenant_id = client.tenant_id
238 sg_name = data_utils.rand_name(namestart)
239 sg_desc = sg_name + " description"
240 sg_dict = dict(name=sg_name,
241 description=sg_desc)
242 sg_dict['tenant_id'] = tenant_id
243 result = client.create_security_group(**sg_dict)
244
245 secgroup = result['security_group']
246 self.assertEqual(secgroup['name'], sg_name)
247 self.assertEqual(tenant_id, secgroup['tenant_id'])
248 self.assertEqual(secgroup['description'], sg_desc)
249
250 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
251 client.delete_security_group, secgroup['id'])
252 return secgroup
253
254 def _default_security_group(self, client=None, tenant_id=None):
255 """Get default secgroup for given tenant_id.
256
257 :returns: default secgroup for given tenant
258 """
259 if client is None:
260 client = self.security_groups_client
261 if not tenant_id:
262 tenant_id = client.tenant_id
263 sgs = [
264 sg for sg in list(client.list_security_groups().values())[0]
265 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
266 ]
267 msg = "No default security group for tenant %s." % (tenant_id)
268 self.assertGreater(len(sgs), 0, msg)
269 return sgs[0]
270
271 def _create_security_group_rule(self, secgroup=None,
272 sec_group_rules_client=None,
273 tenant_id=None,
274 security_groups_client=None, **kwargs):
275 """Create a rule from a dictionary of rule parameters.
276
277 Create a rule in a secgroup. if secgroup not defined will search for
278 default secgroup in tenant_id.
279
280 :param secgroup: the security group.
281 :param tenant_id: if secgroup not passed -- the tenant in which to
282 search for default secgroup
283 :param kwargs: a dictionary containing rule parameters:
284 for example, to allow incoming ssh:
285 rule = {
286 direction: 'ingress'
287 protocol:'tcp',
288 port_range_min: 22,
289 port_range_max: 22
290 }
291 """
292 if sec_group_rules_client is None:
293 sec_group_rules_client = self.security_group_rules_client
294 if security_groups_client is None:
295 security_groups_client = self.security_groups_client
296 if not tenant_id:
297 tenant_id = security_groups_client.tenant_id
298 if secgroup is None:
299 secgroup = self._default_security_group(
300 client=security_groups_client, tenant_id=tenant_id)
301
302 ruleset = dict(security_group_id=secgroup['id'],
303 tenant_id=secgroup['tenant_id'])
304 ruleset.update(kwargs)
305
306 sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
307 sg_rule = sg_rule['security_group_rule']
308
309 self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
310 self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
311
312 return sg_rule
313
314 def _create_loginable_secgroup_rule(self, security_group_rules_client=None,
315 secgroup=None,
316 security_groups_client=None):
317 """Create loginable security group rule
318
319 This function will create:
320 1. egress and ingress tcp port 22 allow rule in order to allow ssh
321 access for ipv4.
322 2. egress and ingress tcp port 80 allow rule in order to allow http
323 access for ipv4.
324 3. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
325 4. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
326 """
327
328 if security_group_rules_client is None:
329 security_group_rules_client = self.security_group_rules_client
330 if security_groups_client is None:
331 security_groups_client = self.security_groups_client
332 rules = []
333 rulesets = [
334 dict(
335 # ssh
336 protocol='tcp',
337 port_range_min=22,
338 port_range_max=22,
339 ),
340 dict(
341 # http
342 protocol='tcp',
343 port_range_min=80,
344 port_range_max=80,
345 ),
346 dict(
347 # ping
348 protocol='icmp',
349 ),
350 dict(
351 # ipv6-icmp for ping6
352 protocol='icmp',
353 ethertype='IPv6',
354 )
355 ]
356 sec_group_rules_client = security_group_rules_client
357 for ruleset in rulesets:
358 for r_direction in ['ingress', 'egress']:
359 ruleset['direction'] = r_direction
360 try:
361 sg_rule = self._create_security_group_rule(
362 sec_group_rules_client=sec_group_rules_client,
363 secgroup=secgroup,
364 security_groups_client=security_groups_client,
365 **ruleset)
366 except lib_exc.Conflict as ex:
367 # if rule already exist - skip rule and continue
368 msg = 'Security group rule already exists'
369 if msg not in ex._error_string:
370 raise ex
371 else:
372 self.assertEqual(r_direction, sg_rule['direction'])
373 rules.append(sg_rule)
374
375 return rules
376
Slawek Kaplonski8dd49aa2019-04-16 14:47:07 +0200377 def _create_router(self, client=None, tenant_id=None,
378 namestart='router-smoke'):
379 if not client:
380 client = self.routers_client
381 if not tenant_id:
382 tenant_id = client.tenant_id
383 name = data_utils.rand_name(namestart)
384 result = client.create_router(name=name,
385 admin_state_up=True,
386 tenant_id=tenant_id)
387 router = result['router']
388 self.assertEqual(router['name'], name)
389 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
390 client.delete_router,
391 router['id'])
392 return router