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