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