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