blob: 29777404b57721b61e2d7b718d75a09e27811c53 [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 import compute
Solio Sarabia60095ff2017-02-28 18:18:26 -060027from tempest.common.utils.linux import remote_client
28from tempest.common.utils import net_utils
29from tempest.common import waiters
30from tempest import config
31from tempest import exceptions
32from tempest.lib.common.utils import data_utils
33from tempest.lib.common.utils import test_utils
34from tempest.lib import exceptions as lib_exc
35import tempest.test
36
37CONF = config.CONF
38
39LOG = log.getLogger(__name__)
40
41
42class ScenarioTest(tempest.test.BaseTestCase):
43 """Base class for scenario tests. Uses tempest own clients. """
44
45 credentials = ['primary']
46
47 @classmethod
48 def setup_clients(cls):
49 super(ScenarioTest, cls).setup_clients()
50 # Clients (in alphabetical order)
Vu Cong Tuanf825d192017-06-21 18:32:15 +070051 cls.flavors_client = cls.os_primary.flavors_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060052 cls.compute_floating_ips_client = (
Vu Cong Tuanf825d192017-06-21 18:32:15 +070053 cls.os_primary.compute_floating_ips_client)
Solio Sarabia60095ff2017-02-28 18:18:26 -060054 if CONF.service_available.glance:
55 # Check if glance v1 is available to determine which client to use.
56 if CONF.image_feature_enabled.api_v1:
Vu Cong Tuanf825d192017-06-21 18:32:15 +070057 cls.image_client = cls.os_primary.image_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060058 elif CONF.image_feature_enabled.api_v2:
Vu Cong Tuanf825d192017-06-21 18:32:15 +070059 cls.image_client = cls.os_primary.image_client_v2
Solio Sarabia60095ff2017-02-28 18:18:26 -060060 else:
61 raise lib_exc.InvalidConfiguration(
62 'Either api_v1 or api_v2 must be True in '
63 '[image-feature-enabled].')
64 # Compute image client
Vu Cong Tuanf825d192017-06-21 18:32:15 +070065 cls.compute_images_client = cls.os_primary.compute_images_client
66 cls.keypairs_client = cls.os_primary.keypairs_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060067 # Nova security groups client
68 cls.compute_security_groups_client = (
Vu Cong Tuanf825d192017-06-21 18:32:15 +070069 cls.os_primary.compute_security_groups_client)
Solio Sarabia60095ff2017-02-28 18:18:26 -060070 cls.compute_security_group_rules_client = (
Vu Cong Tuanf825d192017-06-21 18:32:15 +070071 cls.os_primary.compute_security_group_rules_client)
72 cls.servers_client = cls.os_primary.servers_client
73 cls.interface_client = cls.os_primary.interfaces_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060074 # Neutron network client
Vu Cong Tuanf825d192017-06-21 18:32:15 +070075 cls.networks_client = cls.os_primary.networks_client
76 cls.ports_client = cls.os_primary.ports_client
77 cls.routers_client = cls.os_primary.routers_client
78 cls.subnets_client = cls.os_primary.subnets_client
79 cls.floating_ips_client = cls.os_primary.floating_ips_client
80 cls.security_groups_client = cls.os_primary.security_groups_client
Solio Sarabia60095ff2017-02-28 18:18:26 -060081 cls.security_group_rules_client = (
Vu Cong Tuanf825d192017-06-21 18:32:15 +070082 cls.os_primary.security_group_rules_client)
Solio Sarabia60095ff2017-02-28 18:18:26 -060083
Ghanshyam Mann3b663f62019-12-12 17:01:16 +000084 cls.volumes_client = cls.os_primary.volumes_client_latest
85 cls.snapshots_client = cls.os_primary.snapshots_client_latest
Solio Sarabia60095ff2017-02-28 18:18:26 -060086
87 # ## Test functions library
88 #
89 # The create_[resource] functions only return body and discard the
90 # resp part which is not used in scenario tests
91
92 def _create_port(self, network_id, client=None, namestart='port-quotatest',
93 **kwargs):
94 if not client:
95 client = self.ports_client
96 name = data_utils.rand_name(namestart)
97 result = client.create_port(
98 name=name,
99 network_id=network_id,
100 **kwargs)
101 self.assertIsNotNone(result, 'Unable to allocate port')
102 port = result['port']
103 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
104 client.delete_port, port['id'])
105 return port
106
107 def create_keypair(self, client=None):
108 if not client:
109 client = self.keypairs_client
110 name = data_utils.rand_name(self.__class__.__name__)
111 # We don't need to create a keypair by pubkey in scenario
112 body = client.create_keypair(name=name)
113 self.addCleanup(client.delete_keypair, name)
114 return body['keypair']
115
116 def create_server(self, name=None, image_id=None, flavor=None,
117 validatable=False, wait_until='ACTIVE',
118 clients=None, **kwargs):
119 """Wrapper utility that returns a test server.
120
121 This wrapper utility calls the common create test server and
122 returns a test server. The purpose of this wrapper is to minimize
123 the impact on the code of the tests already using this
124 function.
125 """
126
127 # NOTE(jlanoux): As a first step, ssh checks in the scenario
128 # tests need to be run regardless of the run_validation and
129 # validatable parameters and thus until the ssh validation job
130 # becomes voting in CI. The test resources management and IP
131 # association are taken care of in the scenario tests.
132 # Therefore, the validatable parameter is set to false in all
133 # those tests. In this way create_server just return a standard
134 # server and the scenario tests always perform ssh checks.
135
136 # Needed for the cross_tenant_traffic test:
137 if clients is None:
Vu Cong Tuanf825d192017-06-21 18:32:15 +0700138 clients = self.os_primary
Solio Sarabia60095ff2017-02-28 18:18:26 -0600139
140 if name is None:
141 name = data_utils.rand_name(self.__class__.__name__ + "-server")
142
143 vnic_type = CONF.network.port_vnic_type
144
145 # If vnic_type is configured create port for
146 # every network
147 if vnic_type:
148 ports = []
149
150 create_port_body = {'binding:vnic_type': vnic_type,
151 'namestart': 'port-smoke'}
152 if kwargs:
153 # Convert security group names to security group ids
154 # to pass to create_port
155 if 'security_groups' in kwargs:
156 security_groups = \
157 clients.security_groups_client.list_security_groups(
158 ).get('security_groups')
159 sec_dict = dict([(s['name'], s['id'])
160 for s in security_groups])
161
162 sec_groups_names = [s['name'] for s in kwargs.pop(
163 'security_groups')]
164 security_groups_ids = [sec_dict[s]
165 for s in sec_groups_names]
166
167 if security_groups_ids:
168 create_port_body[
169 'security_groups'] = security_groups_ids
170 networks = kwargs.pop('networks', [])
171 else:
172 networks = []
173
174 # If there are no networks passed to us we look up
175 # for the project's private networks and create a port.
176 # The same behaviour as we would expect when passing
177 # the call to the clients with no networks
178 if not networks:
179 networks = clients.networks_client.list_networks(
180 **{'router:external': False, 'fields': 'id'})['networks']
181
182 # It's net['uuid'] if networks come from kwargs
183 # and net['id'] if they come from
184 # clients.networks_client.list_networks
185 for net in networks:
186 net_id = net.get('uuid', net.get('id'))
187 if 'port' not in net:
188 port = self._create_port(network_id=net_id,
189 client=clients.ports_client,
190 **create_port_body)
191 ports.append({'port': port['id']})
192 else:
193 ports.append({'port': net['port']})
194 if ports:
195 kwargs['networks'] = ports
196 self.ports = ports
197
198 tenant_network = self.get_tenant_network()
199
200 body, servers = compute.create_test_server(
201 clients,
202 tenant_network=tenant_network,
203 wait_until=wait_until,
204 name=name, flavor=flavor,
205 image_id=image_id, **kwargs)
206
207 self.addCleanup(waiters.wait_for_server_termination,
208 clients.servers_client, body['id'])
209 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
210 clients.servers_client.delete_server, body['id'])
211 server = clients.servers_client.show_server(body['id'])['server']
212 return server
213
Solio Sarabia60095ff2017-02-28 18:18:26 -0600214 def get_remote_client(self, ip_address, username=None, private_key=None):
215 """Get a SSH client to a remote server
216
217 @param ip_address the server floating or fixed IP address to use
218 for ssh validation
219 @param username name of the Linux account on the remote server
220 @param private_key the SSH private key to use
221 @return a RemoteClient object
222 """
223
224 if username is None:
225 username = CONF.validation.image_ssh_user
226 # Set this with 'keypair' or others to log in with keypair or
227 # username/password.
228 if CONF.validation.auth_method == 'keypair':
229 password = None
230 if private_key is None:
231 private_key = self.keypair['private_key']
232 else:
233 password = CONF.validation.image_ssh_password
234 private_key = None
235 linux_client = remote_client.RemoteClient(ip_address, username,
236 pkey=private_key,
237 password=password)
238 try:
239 linux_client.validate_authentication()
240 except Exception as e:
241 message = ('Initializing SSH connection to %(ip)s failed. '
242 'Error: %(error)s' % {'ip': ip_address,
243 'error': e})
244 caller = test_utils.find_test_caller()
245 if caller:
246 message = '(%s) %s' % (caller, message)
247 LOG.exception(message)
248 self._log_console_output()
249 raise
250
251 return linux_client
252
Solio Sarabia60095ff2017-02-28 18:18:26 -0600253 def _log_console_output(self, servers=None):
254 if not CONF.compute_feature_enabled.console_output:
255 LOG.debug('Console output not supported, cannot log')
256 return
257 if not servers:
258 servers = self.servers_client.list_servers()
259 servers = servers['servers']
260 for server in servers:
261 try:
262 console_output = self.servers_client.get_console_output(
263 server['id'])['output']
264 LOG.debug('Console output for %s\nbody=\n%s',
265 server['id'], console_output)
266 except lib_exc.NotFound:
267 LOG.debug("Server %s disappeared(deleted) while looking "
268 "for the console log", server['id'])
269
Solio Sarabia60095ff2017-02-28 18:18:26 -0600270 def rebuild_server(self, server_id, image=None,
271 preserve_ephemeral=False, wait=True,
272 rebuild_kwargs=None):
273 if image is None:
274 image = CONF.compute.image_ref
275
276 rebuild_kwargs = rebuild_kwargs or {}
277
278 LOG.debug("Rebuilding server (id: %s, image: %s, preserve eph: %s)",
279 server_id, image, preserve_ephemeral)
280 self.servers_client.rebuild_server(
281 server_id=server_id, image_ref=image,
282 preserve_ephemeral=preserve_ephemeral,
283 **rebuild_kwargs)
284 if wait:
285 waiters.wait_for_server_status(self.servers_client,
286 server_id, 'ACTIVE')
287
288 def ping_ip_address(self, ip_address, should_succeed=True,
289 ping_timeout=None, mtu=None):
290 timeout = ping_timeout or CONF.validation.ping_timeout
291 cmd = ['ping', '-c1', '-w1']
292
293 if mtu:
294 cmd += [
295 # don't fragment
296 '-M', 'do',
297 # ping receives just the size of ICMP payload
298 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
299 ]
300 cmd.append(ip_address)
301
302 def ping():
303 proc = subprocess.Popen(cmd,
304 stdout=subprocess.PIPE,
305 stderr=subprocess.PIPE)
306 proc.communicate()
307
308 return (proc.returncode == 0) == should_succeed
309
310 caller = test_utils.find_test_caller()
311 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
312 ' expected result is %(should_succeed)s', {
313 'caller': caller, 'ip': ip_address, 'timeout': timeout,
314 'should_succeed':
315 'reachable' if should_succeed else 'unreachable'
316 })
317 result = test_utils.call_until_true(ping, timeout, 1)
318 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
319 'ping result is %(result)s', {
320 'caller': caller, 'ip': ip_address, 'timeout': timeout,
321 'result': 'expected' if result else 'unexpected'
322 })
323 return result
324
325 def check_vm_connectivity(self, ip_address,
326 username=None,
327 private_key=None,
328 should_connect=True,
329 mtu=None):
330 """Check server connectivity
331
332 :param ip_address: server to test against
333 :param username: server's ssh username
334 :param private_key: server's ssh private key to be used
335 :param should_connect: True/False indicates positive/negative test
336 positive - attempt ping and ssh
337 negative - attempt ping and fail if succeed
338 :param mtu: network MTU to use for connectivity validation
339
340 :raises: AssertError if the result of the connectivity check does
341 not match the value of the should_connect param
342 """
343 if should_connect:
344 msg = "Timed out waiting for %s to become reachable" % ip_address
345 else:
346 msg = "ip address %s is reachable" % ip_address
347 self.assertTrue(self.ping_ip_address(ip_address,
348 should_succeed=should_connect,
349 mtu=mtu),
350 msg=msg)
351 if should_connect:
352 # no need to check ssh for negative connectivity
353 self.get_remote_client(ip_address, username, private_key)
354
Solio Sarabia60095ff2017-02-28 18:18:26 -0600355 def create_floating_ip(self, thing, pool_name=None):
356 """Create a floating IP and associates to a server on Nova"""
357
358 if not pool_name:
359 pool_name = CONF.network.floating_network_name
360 floating_ip = (self.compute_floating_ips_client.
361 create_floating_ip(pool=pool_name)['floating_ip'])
362 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
363 self.compute_floating_ips_client.delete_floating_ip,
364 floating_ip['id'])
365 self.compute_floating_ips_client.associate_floating_ip_to_server(
366 floating_ip['ip'], thing['id'])
367 return floating_ip
368
369 def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
370 private_key=None):
371 ssh_client = self.get_remote_client(ip_address,
372 private_key=private_key)
373 if dev_name is not None:
374 ssh_client.make_fs(dev_name)
375 ssh_client.exec_command('sudo mount /dev/%s %s' % (dev_name,
376 mount_path))
377 cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % mount_path
378 ssh_client.exec_command(cmd_timestamp)
379 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
380 % mount_path)
381 if dev_name is not None:
382 ssh_client.exec_command('sudo umount %s' % mount_path)
383 return timestamp
384
Solio Sarabia60095ff2017-02-28 18:18:26 -0600385 def get_server_ip(self, server):
386 """Get the server fixed or floating IP.
387
388 Based on the configuration we're in, return a correct ip
389 address for validating that a guest is up.
390 """
391 if CONF.validation.connect_method == 'floating':
392 # The tests calling this method don't have a floating IP
393 # and can't make use of the validation resources. So the
394 # method is creating the floating IP there.
395 return self.create_floating_ip(server)['ip']
396 elif CONF.validation.connect_method == 'fixed':
397 # Determine the network name to look for based on config or creds
398 # provider network resources.
399 if CONF.validation.network_for_ssh:
400 addresses = server['addresses'][
401 CONF.validation.network_for_ssh]
402 else:
403 creds_provider = self._get_credentials_provider()
404 net_creds = creds_provider.get_primary_creds()
405 network = getattr(net_creds, 'network', None)
406 addresses = (server['addresses'][network['name']]
407 if network else [])
408 for address in addresses:
409 if (address['version'] == CONF.validation.ip_version_for_ssh
410 and address['OS-EXT-IPS:type'] == 'fixed'):
411 return address['addr']
412 raise exceptions.ServerUnreachable(server_id=server['id'])
413 else:
414 raise lib_exc.InvalidConfiguration()
415
Pavlo Shchelokovskyy40db0332017-03-21 08:00:17 +0000416 def _get_router(self, client=None, tenant_id=None):
417 """Retrieve a router for the given tenant id.
418
419 If a public router has been configured, it will be returned.
420
421 If a public router has not been configured, but a public
422 network has, a tenant router will be created and returned that
423 routes traffic to the public network.
424 """
425 if not client:
426 client = self.routers_client
427 if not tenant_id:
428 tenant_id = client.tenant_id
429 router_id = CONF.network.public_router_id
430 network_id = CONF.network.public_network_id
431 if router_id:
432 body = client.show_router(router_id)
433 return body['router']
434 elif network_id:
435 router = self._create_router(client, tenant_id)
436 kwargs = {'external_gateway_info': dict(network_id=network_id)}
437 router = client.update_router(router['id'], **kwargs)['router']
438 return router
439 else:
440 raise Exception("Neither of 'public_router_id' or "
441 "'public_network_id' has been defined.")
442
443 def _create_router(self, client=None, tenant_id=None,
444 namestart='router-smoke'):
445 if not client:
446 client = self.routers_client
447 if not tenant_id:
448 tenant_id = client.tenant_id
449 name = data_utils.rand_name(namestart)
450 result = client.create_router(name=name,
451 admin_state_up=True,
452 tenant_id=tenant_id)
453 router = result['router']
454 self.assertEqual(router['name'], name)
455 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
456 client.delete_router,
457 router['id'])
458 return router
459
Solio Sarabia60095ff2017-02-28 18:18:26 -0600460
461class NetworkScenarioTest(ScenarioTest):
462 """Base class for network scenario tests.
463
464 This class provide helpers for network scenario tests, using the neutron
465 API. Helpers from ancestor which use the nova network API are overridden
466 with the neutron API.
467
468 This Class also enforces using Neutron instead of novanetwork.
469 Subclassed tests will be skipped if Neutron is not enabled
470
471 """
472
473 credentials = ['primary', 'admin']
474
475 @classmethod
476 def skip_checks(cls):
477 super(NetworkScenarioTest, cls).skip_checks()
478 if not CONF.service_available.neutron:
479 raise cls.skipException('Neutron not available')
480
481 def _create_network(self, networks_client=None,
482 tenant_id=None,
483 namestart='network-smoke-',
484 port_security_enabled=True):
485 if not networks_client:
486 networks_client = self.networks_client
487 if not tenant_id:
488 tenant_id = networks_client.tenant_id
489 name = data_utils.rand_name(namestart)
490 network_kwargs = dict(name=name, tenant_id=tenant_id)
491 # Neutron disables port security by default so we have to check the
492 # config before trying to create the network with port_security_enabled
493 if CONF.network_feature_enabled.port_security:
494 network_kwargs['port_security_enabled'] = port_security_enabled
495 result = networks_client.create_network(**network_kwargs)
496 network = result['network']
497
498 self.assertEqual(network['name'], name)
499 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
500 networks_client.delete_network,
501 network['id'])
502 return network
503
Solio Sarabia60095ff2017-02-28 18:18:26 -0600504 def _get_server_port_id_and_ip4(self, server, ip_addr=None):
Hongbin Lu43015f02018-07-19 15:17:19 +0000505 if ip_addr:
506 ports = self.os_admin.ports_client.list_ports(
507 device_id=server['id'],
508 fixed_ips='ip_address=%s' % ip_addr)['ports']
509 else:
510 ports = self.os_admin.ports_client.list_ports(
511 device_id=server['id'])['ports']
Solio Sarabia60095ff2017-02-28 18:18:26 -0600512 # A port can have more than one IP address in some cases.
513 # If the network is dual-stack (IPv4 + IPv6), this port is associated
514 # with 2 subnets
515 p_status = ['ACTIVE']
516 # NOTE(vsaienko) With Ironic, instances live on separate hardware
517 # servers. Neutron does not bind ports for Ironic instances, as a
518 # result the port remains in the DOWN state.
519 # TODO(vsaienko) remove once bug: #1599836 is resolved.
520 if getattr(CONF.service_available, 'ironic', False):
521 p_status.append('DOWN')
522 port_map = [(p["id"], fxip["ip_address"])
523 for p in ports
524 for fxip in p["fixed_ips"]
525 if netutils.is_valid_ipv4(fxip["ip_address"])
526 and p['status'] in p_status]
527 inactive = [p for p in ports if p['status'] != 'ACTIVE']
528 if inactive:
529 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
530
531 self.assertNotEqual(0, len(port_map),
532 "No IPv4 addresses found in: %s" % ports)
533 self.assertEqual(len(port_map), 1,
534 "Found multiple IPv4 addresses: %s. "
535 "Unable to determine which port to target."
536 % port_map)
537 return port_map[0]
538
Solio Sarabia60095ff2017-02-28 18:18:26 -0600539 def create_floating_ip(self, thing, external_network_id=None,
540 port_id=None, client=None):
541 """Create a floating IP and associates to a resource/port on Neutron"""
542 if not external_network_id:
543 external_network_id = CONF.network.public_network_id
544 if not client:
545 client = self.floating_ips_client
546 if not port_id:
547 port_id, ip4 = self._get_server_port_id_and_ip4(thing)
548 else:
549 ip4 = None
550 result = client.create_floatingip(
551 floating_network_id=external_network_id,
552 port_id=port_id,
553 tenant_id=thing['tenant_id'],
554 fixed_ip_address=ip4
555 )
556 floating_ip = result['floatingip']
557 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
558 client.delete_floatingip,
559 floating_ip['id'])
560 return floating_ip