blob: 50b2c206c3572150e8778c48699439f1dfcd2458 [file] [log] [blame]
Solio Sarabia60095ff2017-02-28 18:18:26 -06001# 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
17# NOTE(soliosg) Do not edit this file. It will only stay temporarily
18# in ironic, while QA refactors the tempest.scenario interface. This
19# file was copied from openstack/tempest/tempest/scenario/manager.py,
20# openstack/tempest commit: 82a278e88c9e9f9ba49f81c1f8dba0bca7943daf
21
22import subprocess
23
Solio Sarabia60095ff2017-02-28 18:18:26 -060024from oslo_log import log
Solio Sarabia60095ff2017-02-28 18:18:26 -060025from oslo_utils import netutils
Solio Sarabia60095ff2017-02-28 18:18:26 -060026from tempest.common.utils.linux import remote_client
27from tempest.common.utils import net_utils
Solio Sarabia60095ff2017-02-28 18:18:26 -060028from tempest import config
29from tempest import exceptions
30from tempest.lib.common.utils import data_utils
31from tempest.lib.common.utils import test_utils
32from tempest.lib import exceptions as lib_exc
33import tempest.test
34
35CONF = config.CONF
36
37LOG = log.getLogger(__name__)
38
39
Roman Popelka7e962d62022-02-28 09:10:31 +010040class ScenarioTest(tempest.scenario.manager.ScenarioTest):
Solio Sarabia60095ff2017-02-28 18:18:26 -060041 """Base class for scenario tests. Uses tempest own clients. """
42
Julia Kreger3a07c4d2021-06-22 10:27:56 -070043 credentials = ['primary', 'admin', 'system_admin']
Solio Sarabia60095ff2017-02-28 18:18:26 -060044
45 @classmethod
46 def setup_clients(cls):
47 super(ScenarioTest, cls).setup_clients()
48 # Clients (in alphabetical order)
Vu Cong Tuanf825d192017-06-21 18:32:15 +070049 cls.flavors_client = cls.os_primary.flavors_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060050 cls.compute_floating_ips_client = (
Vu Cong Tuanf825d192017-06-21 18:32:15 +070051 cls.os_primary.compute_floating_ips_client)
Solio Sarabia60095ff2017-02-28 18:18:26 -060052 if CONF.service_available.glance:
53 # Check if glance v1 is available to determine which client to use.
54 if CONF.image_feature_enabled.api_v1:
Vu Cong Tuanf825d192017-06-21 18:32:15 +070055 cls.image_client = cls.os_primary.image_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060056 elif CONF.image_feature_enabled.api_v2:
Vu Cong Tuanf825d192017-06-21 18:32:15 +070057 cls.image_client = cls.os_primary.image_client_v2
Solio Sarabia60095ff2017-02-28 18:18:26 -060058 else:
59 raise lib_exc.InvalidConfiguration(
60 'Either api_v1 or api_v2 must be True in '
61 '[image-feature-enabled].')
62 # Compute image client
Vu Cong Tuanf825d192017-06-21 18:32:15 +070063 cls.compute_images_client = cls.os_primary.compute_images_client
64 cls.keypairs_client = cls.os_primary.keypairs_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060065 # Nova security groups client
66 cls.compute_security_groups_client = (
Vu Cong Tuanf825d192017-06-21 18:32:15 +070067 cls.os_primary.compute_security_groups_client)
Solio Sarabia60095ff2017-02-28 18:18:26 -060068 cls.compute_security_group_rules_client = (
Vu Cong Tuanf825d192017-06-21 18:32:15 +070069 cls.os_primary.compute_security_group_rules_client)
70 cls.servers_client = cls.os_primary.servers_client
71 cls.interface_client = cls.os_primary.interfaces_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060072 # Neutron network client
Vu Cong Tuanf825d192017-06-21 18:32:15 +070073 cls.networks_client = cls.os_primary.networks_client
74 cls.ports_client = cls.os_primary.ports_client
75 cls.routers_client = cls.os_primary.routers_client
76 cls.subnets_client = cls.os_primary.subnets_client
77 cls.floating_ips_client = cls.os_primary.floating_ips_client
78 cls.security_groups_client = cls.os_primary.security_groups_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060079 cls.security_group_rules_client = (
Vu Cong Tuanf825d192017-06-21 18:32:15 +070080 cls.os_primary.security_group_rules_client)
Solio Sarabia60095ff2017-02-28 18:18:26 -060081
Ghanshyam Mann3b663f62019-12-12 17:01:16 +000082 cls.volumes_client = cls.os_primary.volumes_client_latest
83 cls.snapshots_client = cls.os_primary.snapshots_client_latest
Solio Sarabia60095ff2017-02-28 18:18:26 -060084
85 # ## Test functions library
86 #
87 # The create_[resource] functions only return body and discard the
88 # resp part which is not used in scenario tests
89
Solio Sarabia60095ff2017-02-28 18:18:26 -060090 def create_server(self, name=None, image_id=None, flavor=None,
91 validatable=False, wait_until='ACTIVE',
92 clients=None, **kwargs):
Roman Popelka6fdd3742022-02-28 09:29:45 +010093 return super().create_server(name=name,
94 image_id=image_id,
95 flavor=flavor,
96 validatable=validatable,
97 wait_until=wait_until,
98 clients=clients,
99 **kwargs)
Solio Sarabia60095ff2017-02-28 18:18:26 -0600100
Solio Sarabia60095ff2017-02-28 18:18:26 -0600101 def get_remote_client(self, ip_address, username=None, private_key=None):
102 """Get a SSH client to a remote server
103
104 @param ip_address the server floating or fixed IP address to use
105 for ssh validation
106 @param username name of the Linux account on the remote server
107 @param private_key the SSH private key to use
108 @return a RemoteClient object
109 """
110
111 if username is None:
112 username = CONF.validation.image_ssh_user
113 # Set this with 'keypair' or others to log in with keypair or
114 # username/password.
115 if CONF.validation.auth_method == 'keypair':
116 password = None
117 if private_key is None:
118 private_key = self.keypair['private_key']
119 else:
120 password = CONF.validation.image_ssh_password
121 private_key = None
122 linux_client = remote_client.RemoteClient(ip_address, username,
123 pkey=private_key,
124 password=password)
125 try:
126 linux_client.validate_authentication()
127 except Exception as e:
128 message = ('Initializing SSH connection to %(ip)s failed. '
129 'Error: %(error)s' % {'ip': ip_address,
130 'error': e})
131 caller = test_utils.find_test_caller()
132 if caller:
133 message = '(%s) %s' % (caller, message)
134 LOG.exception(message)
135 self._log_console_output()
136 raise
137
138 return linux_client
139
Solio Sarabia60095ff2017-02-28 18:18:26 -0600140 def _log_console_output(self, servers=None):
141 if not CONF.compute_feature_enabled.console_output:
142 LOG.debug('Console output not supported, cannot log')
143 return
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700144 client = self.os_primary.servers_client
Solio Sarabia60095ff2017-02-28 18:18:26 -0600145 if not servers:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700146 servers = client.list_servers()
Solio Sarabia60095ff2017-02-28 18:18:26 -0600147 servers = servers['servers']
148 for server in servers:
149 try:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700150 console_output = client.get_console_output(
Solio Sarabia60095ff2017-02-28 18:18:26 -0600151 server['id'])['output']
152 LOG.debug('Console output for %s\nbody=\n%s',
153 server['id'], console_output)
154 except lib_exc.NotFound:
155 LOG.debug("Server %s disappeared(deleted) while looking "
156 "for the console log", server['id'])
157
Solio Sarabia60095ff2017-02-28 18:18:26 -0600158 def ping_ip_address(self, ip_address, should_succeed=True,
159 ping_timeout=None, mtu=None):
160 timeout = ping_timeout or CONF.validation.ping_timeout
161 cmd = ['ping', '-c1', '-w1']
162
163 if mtu:
164 cmd += [
165 # don't fragment
166 '-M', 'do',
167 # ping receives just the size of ICMP payload
168 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
169 ]
170 cmd.append(ip_address)
171
172 def ping():
173 proc = subprocess.Popen(cmd,
174 stdout=subprocess.PIPE,
175 stderr=subprocess.PIPE)
176 proc.communicate()
177
178 return (proc.returncode == 0) == should_succeed
179
180 caller = test_utils.find_test_caller()
181 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
182 ' expected result is %(should_succeed)s', {
183 'caller': caller, 'ip': ip_address, 'timeout': timeout,
184 'should_succeed':
185 'reachable' if should_succeed else 'unreachable'
186 })
187 result = test_utils.call_until_true(ping, timeout, 1)
188 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
189 'ping result is %(result)s', {
190 'caller': caller, 'ip': ip_address, 'timeout': timeout,
191 'result': 'expected' if result else 'unexpected'
192 })
193 return result
194
195 def check_vm_connectivity(self, ip_address,
196 username=None,
197 private_key=None,
198 should_connect=True,
199 mtu=None):
200 """Check server connectivity
201
202 :param ip_address: server to test against
203 :param username: server's ssh username
204 :param private_key: server's ssh private key to be used
205 :param should_connect: True/False indicates positive/negative test
206 positive - attempt ping and ssh
207 negative - attempt ping and fail if succeed
208 :param mtu: network MTU to use for connectivity validation
209
210 :raises: AssertError if the result of the connectivity check does
211 not match the value of the should_connect param
212 """
213 if should_connect:
214 msg = "Timed out waiting for %s to become reachable" % ip_address
215 else:
216 msg = "ip address %s is reachable" % ip_address
217 self.assertTrue(self.ping_ip_address(ip_address,
218 should_succeed=should_connect,
219 mtu=mtu),
220 msg=msg)
221 if should_connect:
222 # no need to check ssh for negative connectivity
223 self.get_remote_client(ip_address, username, private_key)
224
Solio Sarabia60095ff2017-02-28 18:18:26 -0600225 def create_floating_ip(self, thing, pool_name=None):
226 """Create a floating IP and associates to a server on Nova"""
227
228 if not pool_name:
229 pool_name = CONF.network.floating_network_name
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700230 client = self.os_primary.compute_floating_ips_client
231 floating_ip = (client.
Solio Sarabia60095ff2017-02-28 18:18:26 -0600232 create_floating_ip(pool=pool_name)['floating_ip'])
233 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700234 client.delete_floating_ip,
Solio Sarabia60095ff2017-02-28 18:18:26 -0600235 floating_ip['id'])
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700236 client.associate_floating_ip_to_server(
Solio Sarabia60095ff2017-02-28 18:18:26 -0600237 floating_ip['ip'], thing['id'])
238 return floating_ip
239
240 def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
241 private_key=None):
242 ssh_client = self.get_remote_client(ip_address,
243 private_key=private_key)
244 if dev_name is not None:
245 ssh_client.make_fs(dev_name)
246 ssh_client.exec_command('sudo mount /dev/%s %s' % (dev_name,
247 mount_path))
248 cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % mount_path
249 ssh_client.exec_command(cmd_timestamp)
250 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
251 % mount_path)
252 if dev_name is not None:
253 ssh_client.exec_command('sudo umount %s' % mount_path)
254 return timestamp
255
Solio Sarabia60095ff2017-02-28 18:18:26 -0600256 def get_server_ip(self, server):
257 """Get the server fixed or floating IP.
258
259 Based on the configuration we're in, return a correct ip
260 address for validating that a guest is up.
261 """
262 if CONF.validation.connect_method == 'floating':
263 # The tests calling this method don't have a floating IP
264 # and can't make use of the validation resources. So the
265 # method is creating the floating IP there.
266 return self.create_floating_ip(server)['ip']
267 elif CONF.validation.connect_method == 'fixed':
268 # Determine the network name to look for based on config or creds
269 # provider network resources.
270 if CONF.validation.network_for_ssh:
271 addresses = server['addresses'][
272 CONF.validation.network_for_ssh]
273 else:
274 creds_provider = self._get_credentials_provider()
275 net_creds = creds_provider.get_primary_creds()
276 network = getattr(net_creds, 'network', None)
277 addresses = (server['addresses'][network['name']]
278 if network else [])
279 for address in addresses:
280 if (address['version'] == CONF.validation.ip_version_for_ssh
281 and address['OS-EXT-IPS:type'] == 'fixed'):
282 return address['addr']
283 raise exceptions.ServerUnreachable(server_id=server['id'])
284 else:
285 raise lib_exc.InvalidConfiguration()
286
Pavlo Shchelokovskyy40db0332017-03-21 08:00:17 +0000287 def _get_router(self, client=None, tenant_id=None):
288 """Retrieve a router for the given tenant id.
289
290 If a public router has been configured, it will be returned.
291
292 If a public router has not been configured, but a public
293 network has, a tenant router will be created and returned that
294 routes traffic to the public network.
295 """
296 if not client:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700297 client = self.os_primary.routers_client
Pavlo Shchelokovskyy40db0332017-03-21 08:00:17 +0000298 if not tenant_id:
299 tenant_id = client.tenant_id
300 router_id = CONF.network.public_router_id
301 network_id = CONF.network.public_network_id
302 if router_id:
303 body = client.show_router(router_id)
304 return body['router']
305 elif network_id:
306 router = self._create_router(client, tenant_id)
307 kwargs = {'external_gateway_info': dict(network_id=network_id)}
308 router = client.update_router(router['id'], **kwargs)['router']
309 return router
310 else:
311 raise Exception("Neither of 'public_router_id' or "
312 "'public_network_id' has been defined.")
313
314 def _create_router(self, client=None, tenant_id=None,
315 namestart='router-smoke'):
316 if not client:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700317 client = self.os_primary.routers_client
Pavlo Shchelokovskyy40db0332017-03-21 08:00:17 +0000318 if not tenant_id:
319 tenant_id = client.tenant_id
320 name = data_utils.rand_name(namestart)
321 result = client.create_router(name=name,
322 admin_state_up=True,
323 tenant_id=tenant_id)
324 router = result['router']
325 self.assertEqual(router['name'], name)
326 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
327 client.delete_router,
328 router['id'])
329 return router
330
Solio Sarabia60095ff2017-02-28 18:18:26 -0600331
332class NetworkScenarioTest(ScenarioTest):
333 """Base class for network scenario tests.
334
335 This class provide helpers for network scenario tests, using the neutron
336 API. Helpers from ancestor which use the nova network API are overridden
337 with the neutron API.
338
339 This Class also enforces using Neutron instead of novanetwork.
340 Subclassed tests will be skipped if Neutron is not enabled
341
342 """
343
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700344 credentials = ['primary', 'admin', 'system_admin']
Solio Sarabia60095ff2017-02-28 18:18:26 -0600345
346 @classmethod
347 def skip_checks(cls):
348 super(NetworkScenarioTest, cls).skip_checks()
349 if not CONF.service_available.neutron:
350 raise cls.skipException('Neutron not available')
351
352 def _create_network(self, networks_client=None,
353 tenant_id=None,
354 namestart='network-smoke-',
355 port_security_enabled=True):
356 if not networks_client:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700357 networks_client = self.os_primary.networks_client
Solio Sarabia60095ff2017-02-28 18:18:26 -0600358 if not tenant_id:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700359 tenant_id = self.os_primary.networks_client.tenant_id
Solio Sarabia60095ff2017-02-28 18:18:26 -0600360 name = data_utils.rand_name(namestart)
361 network_kwargs = dict(name=name, tenant_id=tenant_id)
362 # Neutron disables port security by default so we have to check the
363 # config before trying to create the network with port_security_enabled
364 if CONF.network_feature_enabled.port_security:
365 network_kwargs['port_security_enabled'] = port_security_enabled
366 result = networks_client.create_network(**network_kwargs)
367 network = result['network']
368
369 self.assertEqual(network['name'], name)
370 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
371 networks_client.delete_network,
372 network['id'])
373 return network
374
Solio Sarabia60095ff2017-02-28 18:18:26 -0600375 def _get_server_port_id_and_ip4(self, server, ip_addr=None):
Hongbin Lu43015f02018-07-19 15:17:19 +0000376 if ip_addr:
377 ports = self.os_admin.ports_client.list_ports(
378 device_id=server['id'],
379 fixed_ips='ip_address=%s' % ip_addr)['ports']
380 else:
381 ports = self.os_admin.ports_client.list_ports(
382 device_id=server['id'])['ports']
Solio Sarabia60095ff2017-02-28 18:18:26 -0600383 # A port can have more than one IP address in some cases.
384 # If the network is dual-stack (IPv4 + IPv6), this port is associated
385 # with 2 subnets
386 p_status = ['ACTIVE']
387 # NOTE(vsaienko) With Ironic, instances live on separate hardware
388 # servers. Neutron does not bind ports for Ironic instances, as a
389 # result the port remains in the DOWN state.
390 # TODO(vsaienko) remove once bug: #1599836 is resolved.
391 if getattr(CONF.service_available, 'ironic', False):
392 p_status.append('DOWN')
393 port_map = [(p["id"], fxip["ip_address"])
394 for p in ports
395 for fxip in p["fixed_ips"]
396 if netutils.is_valid_ipv4(fxip["ip_address"])
397 and p['status'] in p_status]
398 inactive = [p for p in ports if p['status'] != 'ACTIVE']
399 if inactive:
400 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
401
402 self.assertNotEqual(0, len(port_map),
403 "No IPv4 addresses found in: %s" % ports)
404 self.assertEqual(len(port_map), 1,
405 "Found multiple IPv4 addresses: %s. "
406 "Unable to determine which port to target."
407 % port_map)
408 return port_map[0]
409
Solio Sarabia60095ff2017-02-28 18:18:26 -0600410 def create_floating_ip(self, thing, external_network_id=None,
411 port_id=None, client=None):
412 """Create a floating IP and associates to a resource/port on Neutron"""
413 if not external_network_id:
414 external_network_id = CONF.network.public_network_id
415 if not client:
Julia Kreger3a07c4d2021-06-22 10:27:56 -0700416 client = self.os_primary.floating_ips_client
Solio Sarabia60095ff2017-02-28 18:18:26 -0600417 if not port_id:
418 port_id, ip4 = self._get_server_port_id_and_ip4(thing)
419 else:
420 ip4 = None
421 result = client.create_floatingip(
422 floating_network_id=external_network_id,
423 port_id=port_id,
424 tenant_id=thing['tenant_id'],
425 fixed_ip_address=ip4
426 )
427 floating_ip = result['floatingip']
428 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
429 client.delete_floatingip,
430 floating_ip['id'])
431 return floating_ip