blob: 0aa1b9e18b62ad502f2468435bf6275e47d74938 [file] [log] [blame]
Andrea Frittolif4510a12017-03-07 19:17:11 +00001# 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
17import subprocess
18
19import netaddr
20from oslo_log import log
Andrea Frittolif4510a12017-03-07 19:17:11 +000021from oslo_utils import netutils
22import six
23
24from tempest.common import compute
25from tempest.common import image as common_image
Andrea Frittolif4510a12017-03-07 19:17:11 +000026from tempest.common.utils.linux import remote_client
27from tempest.common.utils import net_utils
28from tempest.common import waiters
29from tempest import config
30from tempest import exceptions
Ken'ichi Ohmichi02d1f242017-03-12 18:56:27 -070031from tempest.lib.common.utils import data_utils
Andrea Frittolif4510a12017-03-07 19:17:11 +000032from tempest.lib.common.utils import test_utils
33from tempest.lib import exceptions as lib_exc
34import tempest.test
35
36CONF = config.CONF
37
38LOG = log.getLogger(__name__)
39
40
41class ScenarioTest(tempest.test.BaseTestCase):
42 """Base class for scenario tests. Uses tempest own clients. """
43
44 credentials = ['primary']
45
46 @classmethod
47 def setup_clients(cls):
48 super(ScenarioTest, cls).setup_clients()
49 # Clients (in alphabetical order)
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070050 cls.flavors_client = cls.os_primary.flavors_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000051 cls.compute_floating_ips_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070052 cls.os_primary.compute_floating_ips_client)
Andrea Frittolif4510a12017-03-07 19:17:11 +000053 if CONF.service_available.glance:
54 # Check if glance v1 is available to determine which client to use.
55 if CONF.image_feature_enabled.api_v1:
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070056 cls.image_client = cls.os_primary.image_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000057 elif CONF.image_feature_enabled.api_v2:
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070058 cls.image_client = cls.os_primary.image_client_v2
Andrea Frittolif4510a12017-03-07 19:17:11 +000059 else:
60 raise lib_exc.InvalidConfiguration(
61 'Either api_v1 or api_v2 must be True in '
62 '[image-feature-enabled].')
63 # Compute image client
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070064 cls.compute_images_client = cls.os_primary.compute_images_client
65 cls.keypairs_client = cls.os_primary.keypairs_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000066 # Nova security groups client
67 cls.compute_security_groups_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070068 cls.os_primary.compute_security_groups_client)
Andrea Frittolif4510a12017-03-07 19:17:11 +000069 cls.compute_security_group_rules_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070070 cls.os_primary.compute_security_group_rules_client)
71 cls.servers_client = cls.os_primary.servers_client
72 cls.interface_client = cls.os_primary.interfaces_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000073 # Neutron network client
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070074 cls.networks_client = cls.os_primary.networks_client
75 cls.ports_client = cls.os_primary.ports_client
76 cls.routers_client = cls.os_primary.routers_client
77 cls.subnets_client = cls.os_primary.subnets_client
78 cls.floating_ips_client = cls.os_primary.floating_ips_client
79 cls.security_groups_client = cls.os_primary.security_groups_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000080 cls.security_group_rules_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070081 cls.os_primary.security_group_rules_client)
Andrea Frittolif4510a12017-03-07 19:17:11 +000082
Andrea Frittolif4510a12017-03-07 19:17:11 +000083 # ## Test functions library
84 #
85 # The create_[resource] functions only return body and discard the
86 # resp part which is not used in scenario tests
87
88 def _create_port(self, network_id, client=None, namestart='port-quotatest',
89 **kwargs):
90 if not client:
91 client = self.ports_client
92 name = data_utils.rand_name(namestart)
93 result = client.create_port(
94 name=name,
95 network_id=network_id,
96 **kwargs)
97 self.assertIsNotNone(result, 'Unable to allocate port')
98 port = result['port']
99 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
100 client.delete_port, port['id'])
101 return port
102
103 def create_keypair(self, client=None):
104 if not client:
105 client = self.keypairs_client
106 name = data_utils.rand_name(self.__class__.__name__)
107 # We don't need to create a keypair by pubkey in scenario
108 body = client.create_keypair(name=name)
109 self.addCleanup(client.delete_keypair, name)
110 return body['keypair']
111
112 def create_server(self, name=None, image_id=None, flavor=None,
113 validatable=False, wait_until='ACTIVE',
114 clients=None, **kwargs):
115 """Wrapper utility that returns a test server.
116
117 This wrapper utility calls the common create test server and
118 returns a test server. The purpose of this wrapper is to minimize
119 the impact on the code of the tests already using this
120 function.
121 """
122
123 # NOTE(jlanoux): As a first step, ssh checks in the scenario
124 # tests need to be run regardless of the run_validation and
125 # validatable parameters and thus until the ssh validation job
126 # becomes voting in CI. The test resources management and IP
127 # association are taken care of in the scenario tests.
128 # Therefore, the validatable parameter is set to false in all
129 # those tests. In this way create_server just return a standard
130 # server and the scenario tests always perform ssh checks.
131
132 # Needed for the cross_tenant_traffic test:
133 if clients is None:
Vu Cong Tuandb2abab2017-06-21 20:38:39 +0700134 clients = self.os_primary
Andrea Frittolif4510a12017-03-07 19:17:11 +0000135
136 if name is None:
137 name = data_utils.rand_name(self.__class__.__name__ + "-server")
138
139 vnic_type = CONF.network.port_vnic_type
140
141 # If vnic_type is configured create port for
142 # every network
143 if vnic_type:
144 ports = []
145
146 create_port_body = {'binding:vnic_type': vnic_type,
147 'namestart': 'port-smoke'}
148 if kwargs:
149 # Convert security group names to security group ids
150 # to pass to create_port
151 if 'security_groups' in kwargs:
152 security_groups = (
153 clients.security_groups_client.list_security_groups(
154 ).get('security_groups'))
155 sec_dict = {s['name']: s['id'] for s in security_groups}
156
157 sec_groups_names = [s['name'] for s in kwargs.pop(
158 'security_groups')]
159 security_groups_ids = [sec_dict[s]
160 for s in sec_groups_names]
161
162 if security_groups_ids:
163 create_port_body[
164 'security_groups'] = security_groups_ids
165 networks = kwargs.pop('networks', [])
166 else:
167 networks = []
168
169 # If there are no networks passed to us we look up
170 # for the project's private networks and create a port.
171 # The same behaviour as we would expect when passing
172 # the call to the clients with no networks
173 if not networks:
174 networks = clients.networks_client.list_networks(
175 **{'router:external': False, 'fields': 'id'})['networks']
176
177 # It's net['uuid'] if networks come from kwargs
178 # and net['id'] if they come from
179 # clients.networks_client.list_networks
180 for net in networks:
181 net_id = net.get('uuid', net.get('id'))
182 if 'port' not in net:
183 port = self._create_port(network_id=net_id,
184 client=clients.ports_client,
185 **create_port_body)
186 ports.append({'port': port['id']})
187 else:
188 ports.append({'port': net['port']})
189 if ports:
190 kwargs['networks'] = ports
191 self.ports = ports
192
193 tenant_network = self.get_tenant_network()
194
195 body, servers = compute.create_test_server(
196 clients,
197 tenant_network=tenant_network,
198 wait_until=wait_until,
199 name=name, flavor=flavor,
200 image_id=image_id, **kwargs)
201
202 self.addCleanup(waiters.wait_for_server_termination,
203 clients.servers_client, body['id'])
204 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
205 clients.servers_client.delete_server, body['id'])
206 server = clients.servers_client.show_server(body['id'])['server']
207 return server
208
Andrea Frittolif4510a12017-03-07 19:17:11 +0000209 def _create_loginable_secgroup_rule(self, secgroup_id=None):
210 _client = self.compute_security_groups_client
211 _client_rules = self.compute_security_group_rules_client
212 if secgroup_id is None:
213 sgs = _client.list_security_groups()['security_groups']
214 for sg in sgs:
215 if sg['name'] == 'default':
216 secgroup_id = sg['id']
217
218 # These rules are intended to permit inbound ssh and icmp
219 # traffic from all sources, so no group_id is provided.
220 # Setting a group_id would only permit traffic from ports
221 # belonging to the same security group.
222 rulesets = [
223 {
224 # ssh
225 'ip_protocol': 'tcp',
226 'from_port': 22,
227 'to_port': 22,
228 'cidr': '0.0.0.0/0',
229 },
230 {
231 # ping
232 'ip_protocol': 'icmp',
233 'from_port': -1,
234 'to_port': -1,
235 'cidr': '0.0.0.0/0',
236 }
237 ]
238 rules = list()
239 for ruleset in rulesets:
240 sg_rule = _client_rules.create_security_group_rule(
241 parent_group_id=secgroup_id, **ruleset)['security_group_rule']
242 rules.append(sg_rule)
243 return rules
244
245 def _create_security_group(self):
246 # Create security group
247 sg_name = data_utils.rand_name(self.__class__.__name__)
248 sg_desc = sg_name + " description"
249 secgroup = self.compute_security_groups_client.create_security_group(
250 name=sg_name, description=sg_desc)['security_group']
251 self.assertEqual(secgroup['name'], sg_name)
252 self.assertEqual(secgroup['description'], sg_desc)
253 self.addCleanup(
254 test_utils.call_and_ignore_notfound_exc,
255 self.compute_security_groups_client.delete_security_group,
256 secgroup['id'])
257
258 # Add rules to the security group
259 self._create_loginable_secgroup_rule(secgroup['id'])
260
261 return secgroup
262
263 def get_remote_client(self, ip_address, username=None, private_key=None):
264 """Get a SSH client to a remote server
265
266 @param ip_address the server floating or fixed IP address to use
267 for ssh validation
268 @param username name of the Linux account on the remote server
269 @param private_key the SSH private key to use
270 @return a RemoteClient object
271 """
272
273 if username is None:
274 username = CONF.validation.image_ssh_user
275 # Set this with 'keypair' or others to log in with keypair or
276 # username/password.
277 if CONF.validation.auth_method == 'keypair':
278 password = None
279 if private_key is None:
280 private_key = self.keypair['private_key']
281 else:
282 password = CONF.validation.image_ssh_password
283 private_key = None
284 linux_client = remote_client.RemoteClient(ip_address, username,
285 pkey=private_key,
286 password=password)
287 try:
288 linux_client.validate_authentication()
289 except Exception as e:
290 message = ('Initializing SSH connection to %(ip)s failed. '
291 'Error: %(error)s' % {'ip': ip_address,
292 'error': e})
293 caller = test_utils.find_test_caller()
294 if caller:
295 message = '(%s) %s' % (caller, message)
296 LOG.exception(message)
297 self._log_console_output()
298 raise
299
300 return linux_client
301
302 def _image_create(self, name, fmt, path,
303 disk_format=None, properties=None):
304 if properties is None:
305 properties = {}
306 name = data_utils.rand_name('%s-' % name)
307 params = {
308 'name': name,
309 'container_format': fmt,
310 'disk_format': disk_format or fmt,
311 }
312 if CONF.image_feature_enabled.api_v1:
313 params['is_public'] = 'False'
314 params['properties'] = properties
315 params = {'headers': common_image.image_meta_to_headers(**params)}
316 else:
317 params['visibility'] = 'private'
318 # Additional properties are flattened out in the v2 API.
319 params.update(properties)
320 body = self.image_client.create_image(**params)
321 image = body['image'] if 'image' in body else body
322 self.addCleanup(self.image_client.delete_image, image['id'])
323 self.assertEqual("queued", image['status'])
324 with open(path, 'rb') as image_file:
325 if CONF.image_feature_enabled.api_v1:
326 self.image_client.update_image(image['id'], data=image_file)
327 else:
328 self.image_client.store_image_file(image['id'], image_file)
329 return image['id']
330
331 def glance_image_create(self):
332 img_path = CONF.scenario.img_dir + "/" + CONF.scenario.img_file
333 aki_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.aki_img_file
334 ari_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ari_img_file
335 ami_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ami_img_file
336 img_container_format = CONF.scenario.img_container_format
337 img_disk_format = CONF.scenario.img_disk_format
338 img_properties = CONF.scenario.img_properties
339 LOG.debug("paths: img: %s, container_format: %s, disk_format: %s, "
340 "properties: %s, ami: %s, ari: %s, aki: %s",
341 img_path, img_container_format, img_disk_format,
342 img_properties, ami_img_path, ari_img_path, aki_img_path)
343 try:
344 image = self._image_create('scenario-img',
345 img_container_format,
346 img_path,
347 disk_format=img_disk_format,
348 properties=img_properties)
349 except IOError:
350 LOG.debug("A qcow2 image was not found. Try to get a uec image.")
351 kernel = self._image_create('scenario-aki', 'aki', aki_img_path)
352 ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path)
353 properties = {'kernel_id': kernel, 'ramdisk_id': ramdisk}
354 image = self._image_create('scenario-ami', 'ami',
355 path=ami_img_path,
356 properties=properties)
357 LOG.debug("image:%s", image)
358
359 return image
360
361 def _log_console_output(self, servers=None):
362 if not CONF.compute_feature_enabled.console_output:
363 LOG.debug('Console output not supported, cannot log')
364 return
365 if not servers:
366 servers = self.servers_client.list_servers()
367 servers = servers['servers']
368 for server in servers:
369 try:
370 console_output = self.servers_client.get_console_output(
371 server['id'])['output']
372 LOG.debug('Console output for %s\nbody=\n%s',
373 server['id'], console_output)
374 except lib_exc.NotFound:
375 LOG.debug("Server %s disappeared(deleted) while looking "
376 "for the console log", server['id'])
377
378 def _log_net_info(self, exc):
379 # network debug is called as part of ssh init
380 if not isinstance(exc, lib_exc.SSHTimeout):
381 LOG.debug('Network information on a devstack host')
382
Andrea Frittolif4510a12017-03-07 19:17:11 +0000383 def rebuild_server(self, server_id, image=None,
384 preserve_ephemeral=False, wait=True,
385 rebuild_kwargs=None):
386 if image is None:
387 image = CONF.compute.image_ref
388
389 rebuild_kwargs = rebuild_kwargs or {}
390
391 LOG.debug("Rebuilding server (id: %s, image: %s, preserve eph: %s)",
392 server_id, image, preserve_ephemeral)
393 self.servers_client.rebuild_server(
394 server_id=server_id, image_ref=image,
395 preserve_ephemeral=preserve_ephemeral,
396 **rebuild_kwargs)
397 if wait:
398 waiters.wait_for_server_status(self.servers_client,
399 server_id, 'ACTIVE')
400
401 def ping_ip_address(self, ip_address, should_succeed=True,
402 ping_timeout=None, mtu=None):
403 timeout = ping_timeout or CONF.validation.ping_timeout
404 cmd = ['ping', '-c1', '-w1']
405
406 if mtu:
407 cmd += [
408 # don't fragment
409 '-M', 'do',
410 # ping receives just the size of ICMP payload
411 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
412 ]
413 cmd.append(ip_address)
414
415 def ping():
416 proc = subprocess.Popen(cmd,
417 stdout=subprocess.PIPE,
418 stderr=subprocess.PIPE)
419 proc.communicate()
420
421 return (proc.returncode == 0) == should_succeed
422
423 caller = test_utils.find_test_caller()
424 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
425 ' expected result is %(should_succeed)s', {
426 'caller': caller, 'ip': ip_address, 'timeout': timeout,
427 'should_succeed':
428 'reachable' if should_succeed else 'unreachable'
429 })
430 result = test_utils.call_until_true(ping, timeout, 1)
431 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
432 'ping result is %(result)s', {
433 'caller': caller, 'ip': ip_address, 'timeout': timeout,
434 'result': 'expected' if result else 'unexpected'
435 })
436 return result
437
438 def check_vm_connectivity(self, ip_address,
439 username=None,
440 private_key=None,
441 should_connect=True,
442 mtu=None):
443 """Check server connectivity
444
445 :param ip_address: server to test against
446 :param username: server's ssh username
447 :param private_key: server's ssh private key to be used
448 :param should_connect: True/False indicates positive/negative test
449 positive - attempt ping and ssh
450 negative - attempt ping and fail if succeed
451 :param mtu: network MTU to use for connectivity validation
452
453 :raises: AssertError if the result of the connectivity check does
454 not match the value of the should_connect param
455 """
456 if should_connect:
457 msg = "Timed out waiting for %s to become reachable" % ip_address
458 else:
459 msg = "ip address %s is reachable" % ip_address
460 self.assertTrue(self.ping_ip_address(ip_address,
461 should_succeed=should_connect,
462 mtu=mtu),
463 msg=msg)
464 if should_connect:
465 # no need to check ssh for negative connectivity
466 self.get_remote_client(ip_address, username, private_key)
467
468 def check_public_network_connectivity(self, ip_address, username,
469 private_key, should_connect=True,
470 msg=None, servers=None, mtu=None):
471 # The target login is assumed to have been configured for
472 # key-based authentication by cloud-init.
473 LOG.debug('checking network connections to IP %s with user: %s',
474 ip_address, username)
475 try:
476 self.check_vm_connectivity(ip_address,
477 username,
478 private_key,
479 should_connect=should_connect,
480 mtu=mtu)
481 except Exception:
482 ex_msg = 'Public network connectivity check failed'
483 if msg:
484 ex_msg += ": " + msg
485 LOG.exception(ex_msg)
486 self._log_console_output(servers)
487 raise
488
489 def create_floating_ip(self, thing, pool_name=None):
490 """Create a floating IP and associates to a server on Nova"""
491
492 if not pool_name:
493 pool_name = CONF.network.floating_network_name
494 floating_ip = (self.compute_floating_ips_client.
495 create_floating_ip(pool=pool_name)['floating_ip'])
496 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
497 self.compute_floating_ips_client.delete_floating_ip,
498 floating_ip['id'])
499 self.compute_floating_ips_client.associate_floating_ip_to_server(
500 floating_ip['ip'], thing['id'])
501 return floating_ip
502
503 def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
504 private_key=None):
505 ssh_client = self.get_remote_client(ip_address,
506 private_key=private_key)
507 if dev_name is not None:
508 ssh_client.make_fs(dev_name)
509 ssh_client.mount(dev_name, mount_path)
510 cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % mount_path
511 ssh_client.exec_command(cmd_timestamp)
512 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
513 % mount_path)
514 if dev_name is not None:
515 ssh_client.umount(mount_path)
516 return timestamp
517
518 def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
519 private_key=None):
520 ssh_client = self.get_remote_client(ip_address,
521 private_key=private_key)
522 if dev_name is not None:
523 ssh_client.mount(dev_name, mount_path)
524 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
525 % mount_path)
526 if dev_name is not None:
527 ssh_client.umount(mount_path)
528 return timestamp
529
530 def get_server_ip(self, server):
531 """Get the server fixed or floating IP.
532
533 Based on the configuration we're in, return a correct ip
534 address for validating that a guest is up.
535 """
536 if CONF.validation.connect_method == 'floating':
537 # The tests calling this method don't have a floating IP
538 # and can't make use of the validation resources. So the
539 # method is creating the floating IP there.
540 return self.create_floating_ip(server)['ip']
541 elif CONF.validation.connect_method == 'fixed':
542 # Determine the network name to look for based on config or creds
543 # provider network resources.
544 if CONF.validation.network_for_ssh:
545 addresses = server['addresses'][
546 CONF.validation.network_for_ssh]
547 else:
548 creds_provider = self._get_credentials_provider()
549 net_creds = creds_provider.get_primary_creds()
550 network = getattr(net_creds, 'network', None)
551 addresses = (server['addresses'][network['name']]
552 if network else [])
553 for address in addresses:
554 if (address['version'] == CONF.validation.ip_version_for_ssh
555 and address['OS-EXT-IPS:type'] == 'fixed'):
556 return address['addr']
557 raise exceptions.ServerUnreachable(server_id=server['id'])
558 else:
559 raise lib_exc.InvalidConfiguration()
560
561
562class NetworkScenarioTest(ScenarioTest):
563 """Base class for network scenario tests.
564
565 This class provide helpers for network scenario tests, using the neutron
566 API. Helpers from ancestor which use the nova network API are overridden
567 with the neutron API.
568
569 This Class also enforces using Neutron instead of novanetwork.
570 Subclassed tests will be skipped if Neutron is not enabled
571
572 """
573
574 credentials = ['primary', 'admin']
575
576 @classmethod
577 def skip_checks(cls):
578 super(NetworkScenarioTest, cls).skip_checks()
579 if not CONF.service_available.neutron:
580 raise cls.skipException('Neutron not available')
581
582 def _create_network(self, networks_client=None,
583 tenant_id=None,
584 namestart='network-smoke-',
585 port_security_enabled=True):
586 if not networks_client:
587 networks_client = self.networks_client
588 if not tenant_id:
589 tenant_id = networks_client.tenant_id
590 name = data_utils.rand_name(namestart)
591 network_kwargs = dict(name=name, tenant_id=tenant_id)
592 # Neutron disables port security by default so we have to check the
593 # config before trying to create the network with port_security_enabled
594 if CONF.network_feature_enabled.port_security:
595 network_kwargs['port_security_enabled'] = port_security_enabled
596 result = networks_client.create_network(**network_kwargs)
597 network = result['network']
598
599 self.assertEqual(network['name'], name)
600 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
601 networks_client.delete_network,
602 network['id'])
603 return network
604
605 def _create_subnet(self, network, subnets_client=None,
606 routers_client=None, namestart='subnet-smoke',
607 **kwargs):
608 """Create a subnet for the given network
609
610 within the cidr block configured for tenant networks.
611 """
612 if not subnets_client:
613 subnets_client = self.subnets_client
614 if not routers_client:
615 routers_client = self.routers_client
616
617 def cidr_in_use(cidr, tenant_id):
618 """Check cidr existence
619
620 :returns: True if subnet with cidr already exist in tenant
621 False else
622 """
Vu Cong Tuan99751862017-06-23 19:46:40 +0700623 cidr_in_use = self.os_admin.subnets_client.list_subnets(
Andrea Frittolif4510a12017-03-07 19:17:11 +0000624 tenant_id=tenant_id, cidr=cidr)['subnets']
625 return len(cidr_in_use) != 0
626
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200627 def _make_create_subnet_request(namestart, network,
628 ip_version, subnets_client, **kwargs):
Andrea Frittolif4510a12017-03-07 19:17:11 +0000629
630 subnet = dict(
631 name=data_utils.rand_name(namestart),
632 network_id=network['id'],
633 tenant_id=network['tenant_id'],
Andrea Frittolif4510a12017-03-07 19:17:11 +0000634 ip_version=ip_version,
635 **kwargs
636 )
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200637
638 if ip_version == 6:
639 subnet['ipv6_address_mode'] = 'slaac'
640 subnet['ipv6_ra_mode'] = 'slaac'
641
Andrea Frittolif4510a12017-03-07 19:17:11 +0000642 try:
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200643 return subnets_client.create_subnet(**subnet)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000644 except lib_exc.Conflict as e:
645 if 'overlaps with another subnet' not in six.text_type(e):
646 raise
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200647
648 result = None
649 str_cidr = None
650
651 use_default_subnetpool = kwargs.get('use_default_subnetpool', False)
652
653 ip_version = kwargs.pop('ip_version', 4)
654
655 if not use_default_subnetpool:
656
657 if ip_version == 6:
658 tenant_cidr = netaddr.IPNetwork(
659 CONF.network.project_network_v6_cidr)
660 num_bits = CONF.network.project_network_v6_mask_bits
661 else:
662 tenant_cidr = netaddr.IPNetwork(
663 CONF.network.project_network_cidr)
664 num_bits = CONF.network.project_network_mask_bits
665
666 # Repeatedly attempt subnet creation with sequential cidr
667 # blocks until an unallocated block is found.
668 for subnet_cidr in tenant_cidr.subnet(num_bits):
669 str_cidr = str(subnet_cidr)
670 if cidr_in_use(str_cidr, tenant_id=network['tenant_id']):
671 continue
672
673 result = _make_create_subnet_request(
674 namestart, network, ip_version, subnets_client,
675 cidr=str_cidr, **kwargs)
676 if result is not None:
677 break
678 else:
679 result = _make_create_subnet_request(
680 namestart, network, ip_version, subnets_client,
681 **kwargs)
682
683 self.assertIsNotNone(result)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000684
685 subnet = result['subnet']
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200686 if str_cidr is not None:
687 self.assertEqual(subnet['cidr'], str_cidr)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000688
689 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
690 subnets_client.delete_subnet, subnet['id'])
691
692 return subnet
693
694 def _get_server_port_id_and_ip4(self, server, ip_addr=None):
Vu Cong Tuan99751862017-06-23 19:46:40 +0700695 ports = self.os_admin.ports_client.list_ports(
Andrea Frittolif4510a12017-03-07 19:17:11 +0000696 device_id=server['id'], fixed_ip=ip_addr)['ports']
697 # A port can have more than one IP address in some cases.
698 # If the network is dual-stack (IPv4 + IPv6), this port is associated
699 # with 2 subnets
700 p_status = ['ACTIVE']
701 # NOTE(vsaienko) With Ironic, instances live on separate hardware
702 # servers. Neutron does not bind ports for Ironic instances, as a
703 # result the port remains in the DOWN state.
704 # TODO(vsaienko) remove once bug: #1599836 is resolved.
705 if getattr(CONF.service_available, 'ironic', False):
706 p_status.append('DOWN')
707 port_map = [(p["id"], fxip["ip_address"])
708 for p in ports
709 for fxip in p["fixed_ips"]
710 if netutils.is_valid_ipv4(fxip["ip_address"])
711 and p['status'] in p_status]
712 inactive = [p for p in ports if p['status'] != 'ACTIVE']
713 if inactive:
714 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
715
716 self.assertNotEqual(0, len(port_map),
717 "No IPv4 addresses found in: %s" % ports)
718 self.assertEqual(len(port_map), 1,
719 "Found multiple IPv4 addresses: %s. "
720 "Unable to determine which port to target."
721 % port_map)
722 return port_map[0]
723
724 def _get_network_by_name(self, network_name):
Vu Cong Tuan99751862017-06-23 19:46:40 +0700725 net = self.os_admin.networks_client.list_networks(
Andrea Frittolif4510a12017-03-07 19:17:11 +0000726 name=network_name)['networks']
727 self.assertNotEqual(len(net), 0,
728 "Unable to get network by name: %s" % network_name)
729 return net[0]
730
731 def create_floating_ip(self, thing, external_network_id=None,
732 port_id=None, client=None):
733 """Create a floating IP and associates to a resource/port on Neutron"""
734 if not external_network_id:
735 external_network_id = CONF.network.public_network_id
736 if not client:
737 client = self.floating_ips_client
738 if not port_id:
739 port_id, ip4 = self._get_server_port_id_and_ip4(thing)
740 else:
741 ip4 = None
742 result = client.create_floatingip(
743 floating_network_id=external_network_id,
744 port_id=port_id,
745 tenant_id=thing['tenant_id'],
746 fixed_ip_address=ip4
747 )
748 floating_ip = result['floatingip']
749 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
750 client.delete_floatingip,
751 floating_ip['id'])
752 return floating_ip
753
754 def _associate_floating_ip(self, floating_ip, server):
755 port_id, _ = self._get_server_port_id_and_ip4(server)
756 kwargs = dict(port_id=port_id)
757 floating_ip = self.floating_ips_client.update_floatingip(
758 floating_ip['id'], **kwargs)['floatingip']
759 self.assertEqual(port_id, floating_ip['port_id'])
760 return floating_ip
761
762 def _disassociate_floating_ip(self, floating_ip):
763 """:param floating_ip: floating_ips_client.create_floatingip"""
764 kwargs = dict(port_id=None)
765 floating_ip = self.floating_ips_client.update_floatingip(
766 floating_ip['id'], **kwargs)['floatingip']
767 self.assertIsNone(floating_ip['port_id'])
768 return floating_ip
769
770 def check_floating_ip_status(self, floating_ip, status):
771 """Verifies floatingip reaches the given status
772
773 :param dict floating_ip: floating IP dict to check status
774 :param status: target status
775 :raises: AssertionError if status doesn't match
776 """
777 floatingip_id = floating_ip['id']
778
779 def refresh():
780 result = (self.floating_ips_client.
781 show_floatingip(floatingip_id)['floatingip'])
782 return status == result['status']
783
784 test_utils.call_until_true(refresh,
785 CONF.network.build_timeout,
786 CONF.network.build_interval)
787 floating_ip = self.floating_ips_client.show_floatingip(
788 floatingip_id)['floatingip']
789 self.assertEqual(status, floating_ip['status'],
790 message="FloatingIP: {fp} is at status: {cst}. "
791 "failed to reach status: {st}"
792 .format(fp=floating_ip, cst=floating_ip['status'],
793 st=status))
794 LOG.info("FloatingIP: {fp} is at status: {st}"
795 .format(fp=floating_ip, st=status))
796
797 def _check_tenant_network_connectivity(self, server,
798 username,
799 private_key,
800 should_connect=True,
801 servers_for_debug=None):
802 if not CONF.network.project_networks_reachable:
803 msg = 'Tenant networks not configured to be reachable.'
804 LOG.info(msg)
805 return
806 # The target login is assumed to have been configured for
807 # key-based authentication by cloud-init.
808 try:
809 for ip_addresses in server['addresses'].values():
810 for ip_address in ip_addresses:
811 self.check_vm_connectivity(ip_address['addr'],
812 username,
813 private_key,
814 should_connect=should_connect)
815 except Exception as e:
816 LOG.exception('Tenant network connectivity check failed')
817 self._log_console_output(servers_for_debug)
818 self._log_net_info(e)
819 raise
820
821 def _check_remote_connectivity(self, source, dest, should_succeed=True,
822 nic=None):
823 """assert ping server via source ssh connection
824
825 Note: This is an internal method. Use check_remote_connectivity
826 instead.
827
828 :param source: RemoteClient: an ssh connection from which to ping
829 :param dest: and IP to ping against
830 :param should_succeed: boolean should ping succeed or not
831 :param nic: specific network interface to ping from
832 """
833 def ping_remote():
834 try:
835 source.ping_host(dest, nic=nic)
836 except lib_exc.SSHExecCommandFailed:
837 LOG.warning('Failed to ping IP: %s via a ssh connection '
838 'from: %s.', dest, source.ssh_client.host)
839 return not should_succeed
840 return should_succeed
841
842 return test_utils.call_until_true(ping_remote,
843 CONF.validation.ping_timeout,
844 1)
845
846 def check_remote_connectivity(self, source, dest, should_succeed=True,
847 nic=None):
848 """assert ping server via source ssh connection
849
850 :param source: RemoteClient: an ssh connection from which to ping
851 :param dest: and IP to ping against
852 :param should_succeed: boolean should ping succeed or not
853 :param nic: specific network interface to ping from
854 """
855 result = self._check_remote_connectivity(source, dest, should_succeed,
856 nic)
857 source_host = source.ssh_client.host
858 if should_succeed:
zhongjun39e9c582017-06-21 15:17:11 +0800859 msg = ("Timed out waiting for %s to become reachable from %s"
860 % (dest, source_host))
Andrea Frittolif4510a12017-03-07 19:17:11 +0000861 else:
862 msg = "%s is reachable from %s" % (dest, source_host)
863 self.assertTrue(result, msg)
864
865 def _create_security_group(self, security_group_rules_client=None,
866 tenant_id=None,
867 namestart='secgroup-smoke',
868 security_groups_client=None):
869 if security_group_rules_client is None:
870 security_group_rules_client = self.security_group_rules_client
871 if security_groups_client is None:
872 security_groups_client = self.security_groups_client
873 if tenant_id is None:
874 tenant_id = security_groups_client.tenant_id
875 secgroup = self._create_empty_security_group(
876 namestart=namestart, client=security_groups_client,
877 tenant_id=tenant_id)
878
879 # Add rules to the security group
880 rules = self._create_loginable_secgroup_rule(
881 security_group_rules_client=security_group_rules_client,
882 secgroup=secgroup,
883 security_groups_client=security_groups_client)
884 for rule in rules:
885 self.assertEqual(tenant_id, rule['tenant_id'])
886 self.assertEqual(secgroup['id'], rule['security_group_id'])
887 return secgroup
888
889 def _create_empty_security_group(self, client=None, tenant_id=None,
890 namestart='secgroup-smoke'):
891 """Create a security group without rules.
892
893 Default rules will be created:
894 - IPv4 egress to any
895 - IPv6 egress to any
896
897 :param tenant_id: secgroup will be created in this tenant
898 :returns: the created security group
899 """
900 if client is None:
901 client = self.security_groups_client
902 if not tenant_id:
903 tenant_id = client.tenant_id
904 sg_name = data_utils.rand_name(namestart)
905 sg_desc = sg_name + " description"
906 sg_dict = dict(name=sg_name,
907 description=sg_desc)
908 sg_dict['tenant_id'] = tenant_id
909 result = client.create_security_group(**sg_dict)
910
911 secgroup = result['security_group']
912 self.assertEqual(secgroup['name'], sg_name)
913 self.assertEqual(tenant_id, secgroup['tenant_id'])
914 self.assertEqual(secgroup['description'], sg_desc)
915
916 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
917 client.delete_security_group, secgroup['id'])
918 return secgroup
919
920 def _default_security_group(self, client=None, tenant_id=None):
921 """Get default secgroup for given tenant_id.
922
923 :returns: default secgroup for given tenant
924 """
925 if client is None:
926 client = self.security_groups_client
927 if not tenant_id:
928 tenant_id = client.tenant_id
929 sgs = [
930 sg for sg in list(client.list_security_groups().values())[0]
931 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
932 ]
933 msg = "No default security group for tenant %s." % (tenant_id)
934 self.assertGreater(len(sgs), 0, msg)
935 return sgs[0]
936
937 def _create_security_group_rule(self, secgroup=None,
938 sec_group_rules_client=None,
939 tenant_id=None,
940 security_groups_client=None, **kwargs):
941 """Create a rule from a dictionary of rule parameters.
942
943 Create a rule in a secgroup. if secgroup not defined will search for
944 default secgroup in tenant_id.
945
946 :param secgroup: the security group.
947 :param tenant_id: if secgroup not passed -- the tenant in which to
948 search for default secgroup
949 :param kwargs: a dictionary containing rule parameters:
950 for example, to allow incoming ssh:
951 rule = {
952 direction: 'ingress'
953 protocol:'tcp',
954 port_range_min: 22,
955 port_range_max: 22
956 }
957 """
958 if sec_group_rules_client is None:
959 sec_group_rules_client = self.security_group_rules_client
960 if security_groups_client is None:
961 security_groups_client = self.security_groups_client
962 if not tenant_id:
963 tenant_id = security_groups_client.tenant_id
964 if secgroup is None:
965 secgroup = self._default_security_group(
966 client=security_groups_client, tenant_id=tenant_id)
967
968 ruleset = dict(security_group_id=secgroup['id'],
969 tenant_id=secgroup['tenant_id'])
970 ruleset.update(kwargs)
971
972 sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
973 sg_rule = sg_rule['security_group_rule']
974
975 self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
976 self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
977
978 return sg_rule
979
980 def _create_loginable_secgroup_rule(self, security_group_rules_client=None,
981 secgroup=None,
982 security_groups_client=None):
983 """Create loginable security group rule
984
985 This function will create:
986 1. egress and ingress tcp port 22 allow rule in order to allow ssh
987 access for ipv4.
988 2. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
989 3. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
990 """
991
992 if security_group_rules_client is None:
993 security_group_rules_client = self.security_group_rules_client
994 if security_groups_client is None:
995 security_groups_client = self.security_groups_client
996 rules = []
997 rulesets = [
998 dict(
999 # ssh
1000 protocol='tcp',
1001 port_range_min=22,
1002 port_range_max=22,
1003 ),
1004 dict(
Rodrigo Barbieri797257e2017-11-21 11:00:45 -02001005 # ipv6-ssh
1006 protocol='tcp',
1007 port_range_min=22,
1008 port_range_max=22,
1009 ethertype='IPv6',
1010 ),
1011 dict(
Andrea Frittolif4510a12017-03-07 19:17:11 +00001012 # ping
1013 protocol='icmp',
1014 ),
1015 dict(
1016 # ipv6-icmp for ping6
1017 protocol='icmp',
1018 ethertype='IPv6',
1019 )
1020 ]
1021 sec_group_rules_client = security_group_rules_client
1022 for ruleset in rulesets:
1023 for r_direction in ['ingress', 'egress']:
1024 ruleset['direction'] = r_direction
1025 try:
1026 sg_rule = self._create_security_group_rule(
1027 sec_group_rules_client=sec_group_rules_client,
1028 secgroup=secgroup,
1029 security_groups_client=security_groups_client,
1030 **ruleset)
1031 except lib_exc.Conflict as ex:
1032 # if rule already exist - skip rule and continue
1033 msg = 'Security group rule already exists'
1034 if msg not in ex._error_string:
1035 raise ex
1036 else:
1037 self.assertEqual(r_direction, sg_rule['direction'])
1038 rules.append(sg_rule)
1039
1040 return rules
1041
1042 def _get_router(self, client=None, tenant_id=None):
1043 """Retrieve a router for the given tenant id.
1044
1045 If a public router has been configured, it will be returned.
1046
1047 If a public router has not been configured, but a public
1048 network has, a tenant router will be created and returned that
1049 routes traffic to the public network.
1050 """
1051 if not client:
1052 client = self.routers_client
1053 if not tenant_id:
1054 tenant_id = client.tenant_id
1055 router_id = CONF.network.public_router_id
1056 network_id = CONF.network.public_network_id
1057 if router_id:
1058 body = client.show_router(router_id)
1059 return body['router']
1060 elif network_id:
1061 router = self._create_router(client, tenant_id)
1062 kwargs = {'external_gateway_info': dict(network_id=network_id)}
1063 router = client.update_router(router['id'], **kwargs)['router']
1064 return router
1065 else:
1066 raise Exception("Neither of 'public_router_id' or "
1067 "'public_network_id' has been defined.")
1068
1069 def _create_router(self, client=None, tenant_id=None,
1070 namestart='router-smoke'):
1071 if not client:
1072 client = self.routers_client
1073 if not tenant_id:
1074 tenant_id = client.tenant_id
1075 name = data_utils.rand_name(namestart)
1076 result = client.create_router(name=name,
1077 admin_state_up=True,
1078 tenant_id=tenant_id)
1079 router = result['router']
1080 self.assertEqual(router['name'], name)
1081 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1082 client.delete_router,
1083 router['id'])
1084 return router
1085
1086 def _update_router_admin_state(self, router, admin_state_up):
1087 kwargs = dict(admin_state_up=admin_state_up)
1088 router = self.routers_client.update_router(
1089 router['id'], **kwargs)['router']
1090 self.assertEqual(admin_state_up, router['admin_state_up'])
1091
1092 def create_networks(self, networks_client=None,
1093 routers_client=None, subnets_client=None,
1094 tenant_id=None, dns_nameservers=None,
1095 port_security_enabled=True):
1096 """Create a network with a subnet connected to a router.
1097
1098 The baremetal driver is a special case since all nodes are
1099 on the same shared network.
1100
1101 :param tenant_id: id of tenant to create resources in.
1102 :param dns_nameservers: list of dns servers to send to subnet.
1103 :returns: network, subnet, router
1104 """
1105 if CONF.network.shared_physical_network:
1106 # NOTE(Shrews): This exception is for environments where tenant
1107 # credential isolation is available, but network separation is
1108 # not (the current baremetal case). Likely can be removed when
1109 # test account mgmt is reworked:
1110 # https://blueprints.launchpad.net/tempest/+spec/test-accounts
1111 if not CONF.compute.fixed_network_name:
1112 m = 'fixed_network_name must be specified in config'
1113 raise lib_exc.InvalidConfiguration(m)
1114 network = self._get_network_by_name(
1115 CONF.compute.fixed_network_name)
1116 router = None
1117 subnet = None
1118 else:
1119 network = self._create_network(
1120 networks_client=networks_client,
1121 tenant_id=tenant_id,
1122 port_security_enabled=port_security_enabled)
1123 router = self._get_router(client=routers_client,
1124 tenant_id=tenant_id)
1125 subnet_kwargs = dict(network=network,
1126 subnets_client=subnets_client,
1127 routers_client=routers_client)
1128 # use explicit check because empty list is a valid option
1129 if dns_nameservers is not None:
1130 subnet_kwargs['dns_nameservers'] = dns_nameservers
1131 subnet = self._create_subnet(**subnet_kwargs)
1132 if not routers_client:
1133 routers_client = self.routers_client
1134 router_id = router['id']
1135 routers_client.add_router_interface(router_id,
1136 subnet_id=subnet['id'])
1137
1138 # save a cleanup job to remove this association between
1139 # router and subnet
1140 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1141 routers_client.remove_router_interface, router_id,
1142 subnet_id=subnet['id'])
1143 return network, subnet, router