blob: 73a66934b0ace047e0627f0a2743327cce21a6e7 [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
21from oslo_serialization import jsonutils as json
22from oslo_utils import netutils
23import six
24
25from tempest.common import compute
26from tempest.common import image as common_image
Andrea Frittolif4510a12017-03-07 19:17:11 +000027from 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
Ken'ichi Ohmichi02d1f242017-03-12 18:56:27 -070032from tempest.lib.common.utils import data_utils
Andrea Frittolif4510a12017-03-07 19:17:11 +000033from 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 Tuandb2abab2017-06-21 20:38:39 +070051 cls.flavors_client = cls.os_primary.flavors_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000052 cls.compute_floating_ips_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070053 cls.os_primary.compute_floating_ips_client)
Andrea Frittolif4510a12017-03-07 19:17:11 +000054 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 Tuandb2abab2017-06-21 20:38:39 +070057 cls.image_client = cls.os_primary.image_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000058 elif CONF.image_feature_enabled.api_v2:
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070059 cls.image_client = cls.os_primary.image_client_v2
Andrea Frittolif4510a12017-03-07 19:17:11 +000060 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 Tuandb2abab2017-06-21 20:38:39 +070065 cls.compute_images_client = cls.os_primary.compute_images_client
66 cls.keypairs_client = cls.os_primary.keypairs_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000067 # Nova security groups client
68 cls.compute_security_groups_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070069 cls.os_primary.compute_security_groups_client)
Andrea Frittolif4510a12017-03-07 19:17:11 +000070 cls.compute_security_group_rules_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +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
Andrea Frittolif4510a12017-03-07 19:17:11 +000074 # Neutron network client
Vu Cong Tuandb2abab2017-06-21 20:38:39 +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
Andrea Frittolif4510a12017-03-07 19:17:11 +000081 cls.security_group_rules_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070082 cls.os_primary.security_group_rules_client)
Andrea Frittolif4510a12017-03-07 19:17:11 +000083
84 if CONF.volume_feature_enabled.api_v2:
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070085 cls.volumes_client = cls.os_primary.volumes_v2_client
86 cls.snapshots_client = cls.os_primary.snapshots_v2_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000087 else:
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070088 cls.volumes_client = cls.os_primary.volumes_client
89 cls.snapshots_client = cls.os_primary.snapshots_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000090
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 Tuandb2abab2017-06-21 20:38:39 +0700142 clients = self.os_primary
Andrea Frittolif4510a12017-03-07 19:17:11 +0000143
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 = {s['name']: s['id'] for s in security_groups}
164
165 sec_groups_names = [s['name'] for s in kwargs.pop(
166 'security_groups')]
167 security_groups_ids = [sec_dict[s]
168 for s in sec_groups_names]
169
170 if security_groups_ids:
171 create_port_body[
172 'security_groups'] = security_groups_ids
173 networks = kwargs.pop('networks', [])
174 else:
175 networks = []
176
177 # If there are no networks passed to us we look up
178 # for the project's private networks and create a port.
179 # The same behaviour as we would expect when passing
180 # the call to the clients with no networks
181 if not networks:
182 networks = clients.networks_client.list_networks(
183 **{'router:external': False, 'fields': 'id'})['networks']
184
185 # It's net['uuid'] if networks come from kwargs
186 # and net['id'] if they come from
187 # clients.networks_client.list_networks
188 for net in networks:
189 net_id = net.get('uuid', net.get('id'))
190 if 'port' not in net:
191 port = self._create_port(network_id=net_id,
192 client=clients.ports_client,
193 **create_port_body)
194 ports.append({'port': port['id']})
195 else:
196 ports.append({'port': net['port']})
197 if ports:
198 kwargs['networks'] = ports
199 self.ports = ports
200
201 tenant_network = self.get_tenant_network()
202
203 body, servers = compute.create_test_server(
204 clients,
205 tenant_network=tenant_network,
206 wait_until=wait_until,
207 name=name, flavor=flavor,
208 image_id=image_id, **kwargs)
209
210 self.addCleanup(waiters.wait_for_server_termination,
211 clients.servers_client, body['id'])
212 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
213 clients.servers_client.delete_server, body['id'])
214 server = clients.servers_client.show_server(body['id'])['server']
215 return server
216
217 def create_volume(self, size=None, name=None, snapshot_id=None,
218 imageRef=None, volume_type=None):
219 if size is None:
220 size = CONF.volume.volume_size
221 if imageRef:
222 image = self.compute_images_client.show_image(imageRef)['image']
223 min_disk = image.get('minDisk')
224 size = max(size, min_disk)
225 if name is None:
226 name = data_utils.rand_name(self.__class__.__name__ + "-volume")
227 kwargs = {'display_name': name,
228 'snapshot_id': snapshot_id,
229 'imageRef': imageRef,
230 'volume_type': volume_type,
231 'size': size}
232 volume = self.volumes_client.create_volume(**kwargs)['volume']
233
234 self.addCleanup(self.volumes_client.wait_for_resource_deletion,
235 volume['id'])
236 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
237 self.volumes_client.delete_volume, volume['id'])
238
239 # NOTE(e0ne): Cinder API v2 uses name instead of display_name
240 if 'display_name' in volume:
241 self.assertEqual(name, volume['display_name'])
242 else:
243 self.assertEqual(name, volume['name'])
244 waiters.wait_for_volume_resource_status(self.volumes_client,
245 volume['id'], 'available')
246 # The volume retrieved on creation has a non-up-to-date status.
247 # Retrieval after it becomes active ensures correct details.
248 volume = self.volumes_client.show_volume(volume['id'])['volume']
249 return volume
250
251 def create_volume_type(self, client=None, name=None, backend_name=None):
252 if not client:
253 client = self.admin_volume_types_client
254 if not name:
255 class_name = self.__class__.__name__
256 name = data_utils.rand_name(class_name + '-volume-type')
257 randomized_name = data_utils.rand_name('scenario-type-' + name)
258
259 LOG.debug("Creating a volume type: %s on backend %s",
260 randomized_name, backend_name)
261 extra_specs = {}
262 if backend_name:
263 extra_specs = {"volume_backend_name": backend_name}
264
265 body = client.create_volume_type(name=randomized_name,
266 extra_specs=extra_specs)
267 volume_type = body['volume_type']
268 self.assertIn('id', volume_type)
269 self.addCleanup(client.delete_volume_type, volume_type['id'])
270 return volume_type
271
272 def _create_loginable_secgroup_rule(self, secgroup_id=None):
273 _client = self.compute_security_groups_client
274 _client_rules = self.compute_security_group_rules_client
275 if secgroup_id is None:
276 sgs = _client.list_security_groups()['security_groups']
277 for sg in sgs:
278 if sg['name'] == 'default':
279 secgroup_id = sg['id']
280
281 # These rules are intended to permit inbound ssh and icmp
282 # traffic from all sources, so no group_id is provided.
283 # Setting a group_id would only permit traffic from ports
284 # belonging to the same security group.
285 rulesets = [
286 {
287 # ssh
288 'ip_protocol': 'tcp',
289 'from_port': 22,
290 'to_port': 22,
291 'cidr': '0.0.0.0/0',
292 },
293 {
294 # ping
295 'ip_protocol': 'icmp',
296 'from_port': -1,
297 'to_port': -1,
298 'cidr': '0.0.0.0/0',
299 }
300 ]
301 rules = list()
302 for ruleset in rulesets:
303 sg_rule = _client_rules.create_security_group_rule(
304 parent_group_id=secgroup_id, **ruleset)['security_group_rule']
305 rules.append(sg_rule)
306 return rules
307
308 def _create_security_group(self):
309 # Create security group
310 sg_name = data_utils.rand_name(self.__class__.__name__)
311 sg_desc = sg_name + " description"
312 secgroup = self.compute_security_groups_client.create_security_group(
313 name=sg_name, description=sg_desc)['security_group']
314 self.assertEqual(secgroup['name'], sg_name)
315 self.assertEqual(secgroup['description'], sg_desc)
316 self.addCleanup(
317 test_utils.call_and_ignore_notfound_exc,
318 self.compute_security_groups_client.delete_security_group,
319 secgroup['id'])
320
321 # Add rules to the security group
322 self._create_loginable_secgroup_rule(secgroup['id'])
323
324 return secgroup
325
326 def get_remote_client(self, ip_address, username=None, private_key=None):
327 """Get a SSH client to a remote server
328
329 @param ip_address the server floating or fixed IP address to use
330 for ssh validation
331 @param username name of the Linux account on the remote server
332 @param private_key the SSH private key to use
333 @return a RemoteClient object
334 """
335
336 if username is None:
337 username = CONF.validation.image_ssh_user
338 # Set this with 'keypair' or others to log in with keypair or
339 # username/password.
340 if CONF.validation.auth_method == 'keypair':
341 password = None
342 if private_key is None:
343 private_key = self.keypair['private_key']
344 else:
345 password = CONF.validation.image_ssh_password
346 private_key = None
347 linux_client = remote_client.RemoteClient(ip_address, username,
348 pkey=private_key,
349 password=password)
350 try:
351 linux_client.validate_authentication()
352 except Exception as e:
353 message = ('Initializing SSH connection to %(ip)s failed. '
354 'Error: %(error)s' % {'ip': ip_address,
355 'error': e})
356 caller = test_utils.find_test_caller()
357 if caller:
358 message = '(%s) %s' % (caller, message)
359 LOG.exception(message)
360 self._log_console_output()
361 raise
362
363 return linux_client
364
365 def _image_create(self, name, fmt, path,
366 disk_format=None, properties=None):
367 if properties is None:
368 properties = {}
369 name = data_utils.rand_name('%s-' % name)
370 params = {
371 'name': name,
372 'container_format': fmt,
373 'disk_format': disk_format or fmt,
374 }
375 if CONF.image_feature_enabled.api_v1:
376 params['is_public'] = 'False'
377 params['properties'] = properties
378 params = {'headers': common_image.image_meta_to_headers(**params)}
379 else:
380 params['visibility'] = 'private'
381 # Additional properties are flattened out in the v2 API.
382 params.update(properties)
383 body = self.image_client.create_image(**params)
384 image = body['image'] if 'image' in body else body
385 self.addCleanup(self.image_client.delete_image, image['id'])
386 self.assertEqual("queued", image['status'])
387 with open(path, 'rb') as image_file:
388 if CONF.image_feature_enabled.api_v1:
389 self.image_client.update_image(image['id'], data=image_file)
390 else:
391 self.image_client.store_image_file(image['id'], image_file)
392 return image['id']
393
394 def glance_image_create(self):
395 img_path = CONF.scenario.img_dir + "/" + CONF.scenario.img_file
396 aki_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.aki_img_file
397 ari_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ari_img_file
398 ami_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ami_img_file
399 img_container_format = CONF.scenario.img_container_format
400 img_disk_format = CONF.scenario.img_disk_format
401 img_properties = CONF.scenario.img_properties
402 LOG.debug("paths: img: %s, container_format: %s, disk_format: %s, "
403 "properties: %s, ami: %s, ari: %s, aki: %s",
404 img_path, img_container_format, img_disk_format,
405 img_properties, ami_img_path, ari_img_path, aki_img_path)
406 try:
407 image = self._image_create('scenario-img',
408 img_container_format,
409 img_path,
410 disk_format=img_disk_format,
411 properties=img_properties)
412 except IOError:
413 LOG.debug("A qcow2 image was not found. Try to get a uec image.")
414 kernel = self._image_create('scenario-aki', 'aki', aki_img_path)
415 ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path)
416 properties = {'kernel_id': kernel, 'ramdisk_id': ramdisk}
417 image = self._image_create('scenario-ami', 'ami',
418 path=ami_img_path,
419 properties=properties)
420 LOG.debug("image:%s", image)
421
422 return image
423
424 def _log_console_output(self, servers=None):
425 if not CONF.compute_feature_enabled.console_output:
426 LOG.debug('Console output not supported, cannot log')
427 return
428 if not servers:
429 servers = self.servers_client.list_servers()
430 servers = servers['servers']
431 for server in servers:
432 try:
433 console_output = self.servers_client.get_console_output(
434 server['id'])['output']
435 LOG.debug('Console output for %s\nbody=\n%s',
436 server['id'], console_output)
437 except lib_exc.NotFound:
438 LOG.debug("Server %s disappeared(deleted) while looking "
439 "for the console log", server['id'])
440
441 def _log_net_info(self, exc):
442 # network debug is called as part of ssh init
443 if not isinstance(exc, lib_exc.SSHTimeout):
444 LOG.debug('Network information on a devstack host')
445
446 def create_server_snapshot(self, server, name=None):
447 # Glance client
448 _image_client = self.image_client
449 # Compute client
450 _images_client = self.compute_images_client
451 if name is None:
452 name = data_utils.rand_name(self.__class__.__name__ + 'snapshot')
453 LOG.debug("Creating a snapshot image for server: %s", server['name'])
454 image = _images_client.create_image(server['id'], name=name)
455 image_id = image.response['location'].split('images/')[1]
456 waiters.wait_for_image_status(_image_client, image_id, 'active')
457
458 self.addCleanup(_image_client.wait_for_resource_deletion,
459 image_id)
460 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
461 _image_client.delete_image, image_id)
462
463 if CONF.image_feature_enabled.api_v1:
464 # In glance v1 the additional properties are stored in the headers.
465 resp = _image_client.check_image(image_id)
466 snapshot_image = common_image.get_image_meta_from_headers(resp)
467 image_props = snapshot_image.get('properties', {})
468 else:
469 # In glance v2 the additional properties are flattened.
470 snapshot_image = _image_client.show_image(image_id)
471 image_props = snapshot_image
472
473 bdm = image_props.get('block_device_mapping')
474 if bdm:
475 bdm = json.loads(bdm)
476 if bdm and 'snapshot_id' in bdm[0]:
477 snapshot_id = bdm[0]['snapshot_id']
478 self.addCleanup(
479 self.snapshots_client.wait_for_resource_deletion,
480 snapshot_id)
481 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
482 self.snapshots_client.delete_snapshot,
483 snapshot_id)
484 waiters.wait_for_volume_resource_status(self.snapshots_client,
485 snapshot_id,
486 'available')
487 image_name = snapshot_image['name']
488 self.assertEqual(name, image_name)
489 LOG.debug("Created snapshot image %s for server %s",
490 image_name, server['name'])
491 return snapshot_image
492
493 def nova_volume_attach(self, server, volume_to_attach):
494 volume = self.servers_client.attach_volume(
495 server['id'], volumeId=volume_to_attach['id'], device='/dev/%s'
496 % CONF.compute.volume_device_name)['volumeAttachment']
497 self.assertEqual(volume_to_attach['id'], volume['id'])
498 waiters.wait_for_volume_resource_status(self.volumes_client,
499 volume['id'], 'in-use')
500
501 # Return the updated volume after the attachment
502 return self.volumes_client.show_volume(volume['id'])['volume']
503
504 def nova_volume_detach(self, server, volume):
505 self.servers_client.detach_volume(server['id'], volume['id'])
506 waiters.wait_for_volume_resource_status(self.volumes_client,
507 volume['id'], 'available')
508
509 volume = self.volumes_client.show_volume(volume['id'])['volume']
510 self.assertEqual('available', volume['status'])
511
512 def rebuild_server(self, server_id, image=None,
513 preserve_ephemeral=False, wait=True,
514 rebuild_kwargs=None):
515 if image is None:
516 image = CONF.compute.image_ref
517
518 rebuild_kwargs = rebuild_kwargs or {}
519
520 LOG.debug("Rebuilding server (id: %s, image: %s, preserve eph: %s)",
521 server_id, image, preserve_ephemeral)
522 self.servers_client.rebuild_server(
523 server_id=server_id, image_ref=image,
524 preserve_ephemeral=preserve_ephemeral,
525 **rebuild_kwargs)
526 if wait:
527 waiters.wait_for_server_status(self.servers_client,
528 server_id, 'ACTIVE')
529
530 def ping_ip_address(self, ip_address, should_succeed=True,
531 ping_timeout=None, mtu=None):
532 timeout = ping_timeout or CONF.validation.ping_timeout
533 cmd = ['ping', '-c1', '-w1']
534
535 if mtu:
536 cmd += [
537 # don't fragment
538 '-M', 'do',
539 # ping receives just the size of ICMP payload
540 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
541 ]
542 cmd.append(ip_address)
543
544 def ping():
545 proc = subprocess.Popen(cmd,
546 stdout=subprocess.PIPE,
547 stderr=subprocess.PIPE)
548 proc.communicate()
549
550 return (proc.returncode == 0) == should_succeed
551
552 caller = test_utils.find_test_caller()
553 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
554 ' expected result is %(should_succeed)s', {
555 'caller': caller, 'ip': ip_address, 'timeout': timeout,
556 'should_succeed':
557 'reachable' if should_succeed else 'unreachable'
558 })
559 result = test_utils.call_until_true(ping, timeout, 1)
560 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
561 'ping result is %(result)s', {
562 'caller': caller, 'ip': ip_address, 'timeout': timeout,
563 'result': 'expected' if result else 'unexpected'
564 })
565 return result
566
567 def check_vm_connectivity(self, ip_address,
568 username=None,
569 private_key=None,
570 should_connect=True,
571 mtu=None):
572 """Check server connectivity
573
574 :param ip_address: server to test against
575 :param username: server's ssh username
576 :param private_key: server's ssh private key to be used
577 :param should_connect: True/False indicates positive/negative test
578 positive - attempt ping and ssh
579 negative - attempt ping and fail if succeed
580 :param mtu: network MTU to use for connectivity validation
581
582 :raises: AssertError if the result of the connectivity check does
583 not match the value of the should_connect param
584 """
585 if should_connect:
586 msg = "Timed out waiting for %s to become reachable" % ip_address
587 else:
588 msg = "ip address %s is reachable" % ip_address
589 self.assertTrue(self.ping_ip_address(ip_address,
590 should_succeed=should_connect,
591 mtu=mtu),
592 msg=msg)
593 if should_connect:
594 # no need to check ssh for negative connectivity
595 self.get_remote_client(ip_address, username, private_key)
596
597 def check_public_network_connectivity(self, ip_address, username,
598 private_key, should_connect=True,
599 msg=None, servers=None, mtu=None):
600 # The target login is assumed to have been configured for
601 # key-based authentication by cloud-init.
602 LOG.debug('checking network connections to IP %s with user: %s',
603 ip_address, username)
604 try:
605 self.check_vm_connectivity(ip_address,
606 username,
607 private_key,
608 should_connect=should_connect,
609 mtu=mtu)
610 except Exception:
611 ex_msg = 'Public network connectivity check failed'
612 if msg:
613 ex_msg += ": " + msg
614 LOG.exception(ex_msg)
615 self._log_console_output(servers)
616 raise
617
618 def create_floating_ip(self, thing, pool_name=None):
619 """Create a floating IP and associates to a server on Nova"""
620
621 if not pool_name:
622 pool_name = CONF.network.floating_network_name
623 floating_ip = (self.compute_floating_ips_client.
624 create_floating_ip(pool=pool_name)['floating_ip'])
625 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
626 self.compute_floating_ips_client.delete_floating_ip,
627 floating_ip['id'])
628 self.compute_floating_ips_client.associate_floating_ip_to_server(
629 floating_ip['ip'], thing['id'])
630 return floating_ip
631
632 def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
633 private_key=None):
634 ssh_client = self.get_remote_client(ip_address,
635 private_key=private_key)
636 if dev_name is not None:
637 ssh_client.make_fs(dev_name)
638 ssh_client.mount(dev_name, mount_path)
639 cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % mount_path
640 ssh_client.exec_command(cmd_timestamp)
641 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
642 % mount_path)
643 if dev_name is not None:
644 ssh_client.umount(mount_path)
645 return timestamp
646
647 def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
648 private_key=None):
649 ssh_client = self.get_remote_client(ip_address,
650 private_key=private_key)
651 if dev_name is not None:
652 ssh_client.mount(dev_name, mount_path)
653 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
654 % mount_path)
655 if dev_name is not None:
656 ssh_client.umount(mount_path)
657 return timestamp
658
659 def get_server_ip(self, server):
660 """Get the server fixed or floating IP.
661
662 Based on the configuration we're in, return a correct ip
663 address for validating that a guest is up.
664 """
665 if CONF.validation.connect_method == 'floating':
666 # The tests calling this method don't have a floating IP
667 # and can't make use of the validation resources. So the
668 # method is creating the floating IP there.
669 return self.create_floating_ip(server)['ip']
670 elif CONF.validation.connect_method == 'fixed':
671 # Determine the network name to look for based on config or creds
672 # provider network resources.
673 if CONF.validation.network_for_ssh:
674 addresses = server['addresses'][
675 CONF.validation.network_for_ssh]
676 else:
677 creds_provider = self._get_credentials_provider()
678 net_creds = creds_provider.get_primary_creds()
679 network = getattr(net_creds, 'network', None)
680 addresses = (server['addresses'][network['name']]
681 if network else [])
682 for address in addresses:
683 if (address['version'] == CONF.validation.ip_version_for_ssh
684 and address['OS-EXT-IPS:type'] == 'fixed'):
685 return address['addr']
686 raise exceptions.ServerUnreachable(server_id=server['id'])
687 else:
688 raise lib_exc.InvalidConfiguration()
689
690
691class NetworkScenarioTest(ScenarioTest):
692 """Base class for network scenario tests.
693
694 This class provide helpers for network scenario tests, using the neutron
695 API. Helpers from ancestor which use the nova network API are overridden
696 with the neutron API.
697
698 This Class also enforces using Neutron instead of novanetwork.
699 Subclassed tests will be skipped if Neutron is not enabled
700
701 """
702
703 credentials = ['primary', 'admin']
704
705 @classmethod
706 def skip_checks(cls):
707 super(NetworkScenarioTest, cls).skip_checks()
708 if not CONF.service_available.neutron:
709 raise cls.skipException('Neutron not available')
710
711 def _create_network(self, networks_client=None,
712 tenant_id=None,
713 namestart='network-smoke-',
714 port_security_enabled=True):
715 if not networks_client:
716 networks_client = self.networks_client
717 if not tenant_id:
718 tenant_id = networks_client.tenant_id
719 name = data_utils.rand_name(namestart)
720 network_kwargs = dict(name=name, tenant_id=tenant_id)
721 # Neutron disables port security by default so we have to check the
722 # config before trying to create the network with port_security_enabled
723 if CONF.network_feature_enabled.port_security:
724 network_kwargs['port_security_enabled'] = port_security_enabled
725 result = networks_client.create_network(**network_kwargs)
726 network = result['network']
727
728 self.assertEqual(network['name'], name)
729 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
730 networks_client.delete_network,
731 network['id'])
732 return network
733
734 def _create_subnet(self, network, subnets_client=None,
735 routers_client=None, namestart='subnet-smoke',
736 **kwargs):
737 """Create a subnet for the given network
738
739 within the cidr block configured for tenant networks.
740 """
741 if not subnets_client:
742 subnets_client = self.subnets_client
743 if not routers_client:
744 routers_client = self.routers_client
745
746 def cidr_in_use(cidr, tenant_id):
747 """Check cidr existence
748
749 :returns: True if subnet with cidr already exist in tenant
750 False else
751 """
Vu Cong Tuan99751862017-06-23 19:46:40 +0700752 cidr_in_use = self.os_admin.subnets_client.list_subnets(
Andrea Frittolif4510a12017-03-07 19:17:11 +0000753 tenant_id=tenant_id, cidr=cidr)['subnets']
754 return len(cidr_in_use) != 0
755
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200756 def _make_create_subnet_request(namestart, network,
757 ip_version, subnets_client, **kwargs):
Andrea Frittolif4510a12017-03-07 19:17:11 +0000758
759 subnet = dict(
760 name=data_utils.rand_name(namestart),
761 network_id=network['id'],
762 tenant_id=network['tenant_id'],
Andrea Frittolif4510a12017-03-07 19:17:11 +0000763 ip_version=ip_version,
764 **kwargs
765 )
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200766
767 if ip_version == 6:
768 subnet['ipv6_address_mode'] = 'slaac'
769 subnet['ipv6_ra_mode'] = 'slaac'
770
Andrea Frittolif4510a12017-03-07 19:17:11 +0000771 try:
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200772 return subnets_client.create_subnet(**subnet)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000773 except lib_exc.Conflict as e:
774 if 'overlaps with another subnet' not in six.text_type(e):
775 raise
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200776
777 result = None
778 str_cidr = None
779
780 use_default_subnetpool = kwargs.get('use_default_subnetpool', False)
781
782 ip_version = kwargs.pop('ip_version', 4)
783
784 if not use_default_subnetpool:
785
786 if ip_version == 6:
787 tenant_cidr = netaddr.IPNetwork(
788 CONF.network.project_network_v6_cidr)
789 num_bits = CONF.network.project_network_v6_mask_bits
790 else:
791 tenant_cidr = netaddr.IPNetwork(
792 CONF.network.project_network_cidr)
793 num_bits = CONF.network.project_network_mask_bits
794
795 # Repeatedly attempt subnet creation with sequential cidr
796 # blocks until an unallocated block is found.
797 for subnet_cidr in tenant_cidr.subnet(num_bits):
798 str_cidr = str(subnet_cidr)
799 if cidr_in_use(str_cidr, tenant_id=network['tenant_id']):
800 continue
801
802 result = _make_create_subnet_request(
803 namestart, network, ip_version, subnets_client,
804 cidr=str_cidr, **kwargs)
805 if result is not None:
806 break
807 else:
808 result = _make_create_subnet_request(
809 namestart, network, ip_version, subnets_client,
810 **kwargs)
811
812 self.assertIsNotNone(result)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000813
814 subnet = result['subnet']
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200815 if str_cidr is not None:
816 self.assertEqual(subnet['cidr'], str_cidr)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000817
818 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
819 subnets_client.delete_subnet, subnet['id'])
820
821 return subnet
822
823 def _get_server_port_id_and_ip4(self, server, ip_addr=None):
Vu Cong Tuan99751862017-06-23 19:46:40 +0700824 ports = self.os_admin.ports_client.list_ports(
Andrea Frittolif4510a12017-03-07 19:17:11 +0000825 device_id=server['id'], fixed_ip=ip_addr)['ports']
826 # A port can have more than one IP address in some cases.
827 # If the network is dual-stack (IPv4 + IPv6), this port is associated
828 # with 2 subnets
829 p_status = ['ACTIVE']
830 # NOTE(vsaienko) With Ironic, instances live on separate hardware
831 # servers. Neutron does not bind ports for Ironic instances, as a
832 # result the port remains in the DOWN state.
833 # TODO(vsaienko) remove once bug: #1599836 is resolved.
834 if getattr(CONF.service_available, 'ironic', False):
835 p_status.append('DOWN')
836 port_map = [(p["id"], fxip["ip_address"])
837 for p in ports
838 for fxip in p["fixed_ips"]
839 if netutils.is_valid_ipv4(fxip["ip_address"])
840 and p['status'] in p_status]
841 inactive = [p for p in ports if p['status'] != 'ACTIVE']
842 if inactive:
843 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
844
845 self.assertNotEqual(0, len(port_map),
846 "No IPv4 addresses found in: %s" % ports)
847 self.assertEqual(len(port_map), 1,
848 "Found multiple IPv4 addresses: %s. "
849 "Unable to determine which port to target."
850 % port_map)
851 return port_map[0]
852
853 def _get_network_by_name(self, network_name):
Vu Cong Tuan99751862017-06-23 19:46:40 +0700854 net = self.os_admin.networks_client.list_networks(
Andrea Frittolif4510a12017-03-07 19:17:11 +0000855 name=network_name)['networks']
856 self.assertNotEqual(len(net), 0,
857 "Unable to get network by name: %s" % network_name)
858 return net[0]
859
860 def create_floating_ip(self, thing, external_network_id=None,
861 port_id=None, client=None):
862 """Create a floating IP and associates to a resource/port on Neutron"""
863 if not external_network_id:
864 external_network_id = CONF.network.public_network_id
865 if not client:
866 client = self.floating_ips_client
867 if not port_id:
868 port_id, ip4 = self._get_server_port_id_and_ip4(thing)
869 else:
870 ip4 = None
871 result = client.create_floatingip(
872 floating_network_id=external_network_id,
873 port_id=port_id,
874 tenant_id=thing['tenant_id'],
875 fixed_ip_address=ip4
876 )
877 floating_ip = result['floatingip']
878 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
879 client.delete_floatingip,
880 floating_ip['id'])
881 return floating_ip
882
883 def _associate_floating_ip(self, floating_ip, server):
884 port_id, _ = self._get_server_port_id_and_ip4(server)
885 kwargs = dict(port_id=port_id)
886 floating_ip = self.floating_ips_client.update_floatingip(
887 floating_ip['id'], **kwargs)['floatingip']
888 self.assertEqual(port_id, floating_ip['port_id'])
889 return floating_ip
890
891 def _disassociate_floating_ip(self, floating_ip):
892 """:param floating_ip: floating_ips_client.create_floatingip"""
893 kwargs = dict(port_id=None)
894 floating_ip = self.floating_ips_client.update_floatingip(
895 floating_ip['id'], **kwargs)['floatingip']
896 self.assertIsNone(floating_ip['port_id'])
897 return floating_ip
898
899 def check_floating_ip_status(self, floating_ip, status):
900 """Verifies floatingip reaches the given status
901
902 :param dict floating_ip: floating IP dict to check status
903 :param status: target status
904 :raises: AssertionError if status doesn't match
905 """
906 floatingip_id = floating_ip['id']
907
908 def refresh():
909 result = (self.floating_ips_client.
910 show_floatingip(floatingip_id)['floatingip'])
911 return status == result['status']
912
913 test_utils.call_until_true(refresh,
914 CONF.network.build_timeout,
915 CONF.network.build_interval)
916 floating_ip = self.floating_ips_client.show_floatingip(
917 floatingip_id)['floatingip']
918 self.assertEqual(status, floating_ip['status'],
919 message="FloatingIP: {fp} is at status: {cst}. "
920 "failed to reach status: {st}"
921 .format(fp=floating_ip, cst=floating_ip['status'],
922 st=status))
923 LOG.info("FloatingIP: {fp} is at status: {st}"
924 .format(fp=floating_ip, st=status))
925
926 def _check_tenant_network_connectivity(self, server,
927 username,
928 private_key,
929 should_connect=True,
930 servers_for_debug=None):
931 if not CONF.network.project_networks_reachable:
932 msg = 'Tenant networks not configured to be reachable.'
933 LOG.info(msg)
934 return
935 # The target login is assumed to have been configured for
936 # key-based authentication by cloud-init.
937 try:
938 for ip_addresses in server['addresses'].values():
939 for ip_address in ip_addresses:
940 self.check_vm_connectivity(ip_address['addr'],
941 username,
942 private_key,
943 should_connect=should_connect)
944 except Exception as e:
945 LOG.exception('Tenant network connectivity check failed')
946 self._log_console_output(servers_for_debug)
947 self._log_net_info(e)
948 raise
949
950 def _check_remote_connectivity(self, source, dest, should_succeed=True,
951 nic=None):
952 """assert ping server via source ssh connection
953
954 Note: This is an internal method. Use check_remote_connectivity
955 instead.
956
957 :param source: RemoteClient: an ssh connection from which to ping
958 :param dest: and IP to ping against
959 :param should_succeed: boolean should ping succeed or not
960 :param nic: specific network interface to ping from
961 """
962 def ping_remote():
963 try:
964 source.ping_host(dest, nic=nic)
965 except lib_exc.SSHExecCommandFailed:
966 LOG.warning('Failed to ping IP: %s via a ssh connection '
967 'from: %s.', dest, source.ssh_client.host)
968 return not should_succeed
969 return should_succeed
970
971 return test_utils.call_until_true(ping_remote,
972 CONF.validation.ping_timeout,
973 1)
974
975 def check_remote_connectivity(self, source, dest, should_succeed=True,
976 nic=None):
977 """assert ping server via source ssh connection
978
979 :param source: RemoteClient: an ssh connection from which to ping
980 :param dest: and IP to ping against
981 :param should_succeed: boolean should ping succeed or not
982 :param nic: specific network interface to ping from
983 """
984 result = self._check_remote_connectivity(source, dest, should_succeed,
985 nic)
986 source_host = source.ssh_client.host
987 if should_succeed:
zhongjun39e9c582017-06-21 15:17:11 +0800988 msg = ("Timed out waiting for %s to become reachable from %s"
989 % (dest, source_host))
Andrea Frittolif4510a12017-03-07 19:17:11 +0000990 else:
991 msg = "%s is reachable from %s" % (dest, source_host)
992 self.assertTrue(result, msg)
993
994 def _create_security_group(self, security_group_rules_client=None,
995 tenant_id=None,
996 namestart='secgroup-smoke',
997 security_groups_client=None):
998 if security_group_rules_client is None:
999 security_group_rules_client = self.security_group_rules_client
1000 if security_groups_client is None:
1001 security_groups_client = self.security_groups_client
1002 if tenant_id is None:
1003 tenant_id = security_groups_client.tenant_id
1004 secgroup = self._create_empty_security_group(
1005 namestart=namestart, client=security_groups_client,
1006 tenant_id=tenant_id)
1007
1008 # Add rules to the security group
1009 rules = self._create_loginable_secgroup_rule(
1010 security_group_rules_client=security_group_rules_client,
1011 secgroup=secgroup,
1012 security_groups_client=security_groups_client)
1013 for rule in rules:
1014 self.assertEqual(tenant_id, rule['tenant_id'])
1015 self.assertEqual(secgroup['id'], rule['security_group_id'])
1016 return secgroup
1017
1018 def _create_empty_security_group(self, client=None, tenant_id=None,
1019 namestart='secgroup-smoke'):
1020 """Create a security group without rules.
1021
1022 Default rules will be created:
1023 - IPv4 egress to any
1024 - IPv6 egress to any
1025
1026 :param tenant_id: secgroup will be created in this tenant
1027 :returns: the created security group
1028 """
1029 if client is None:
1030 client = self.security_groups_client
1031 if not tenant_id:
1032 tenant_id = client.tenant_id
1033 sg_name = data_utils.rand_name(namestart)
1034 sg_desc = sg_name + " description"
1035 sg_dict = dict(name=sg_name,
1036 description=sg_desc)
1037 sg_dict['tenant_id'] = tenant_id
1038 result = client.create_security_group(**sg_dict)
1039
1040 secgroup = result['security_group']
1041 self.assertEqual(secgroup['name'], sg_name)
1042 self.assertEqual(tenant_id, secgroup['tenant_id'])
1043 self.assertEqual(secgroup['description'], sg_desc)
1044
1045 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1046 client.delete_security_group, secgroup['id'])
1047 return secgroup
1048
1049 def _default_security_group(self, client=None, tenant_id=None):
1050 """Get default secgroup for given tenant_id.
1051
1052 :returns: default secgroup for given tenant
1053 """
1054 if client is None:
1055 client = self.security_groups_client
1056 if not tenant_id:
1057 tenant_id = client.tenant_id
1058 sgs = [
1059 sg for sg in list(client.list_security_groups().values())[0]
1060 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
1061 ]
1062 msg = "No default security group for tenant %s." % (tenant_id)
1063 self.assertGreater(len(sgs), 0, msg)
1064 return sgs[0]
1065
1066 def _create_security_group_rule(self, secgroup=None,
1067 sec_group_rules_client=None,
1068 tenant_id=None,
1069 security_groups_client=None, **kwargs):
1070 """Create a rule from a dictionary of rule parameters.
1071
1072 Create a rule in a secgroup. if secgroup not defined will search for
1073 default secgroup in tenant_id.
1074
1075 :param secgroup: the security group.
1076 :param tenant_id: if secgroup not passed -- the tenant in which to
1077 search for default secgroup
1078 :param kwargs: a dictionary containing rule parameters:
1079 for example, to allow incoming ssh:
1080 rule = {
1081 direction: 'ingress'
1082 protocol:'tcp',
1083 port_range_min: 22,
1084 port_range_max: 22
1085 }
1086 """
1087 if sec_group_rules_client is None:
1088 sec_group_rules_client = self.security_group_rules_client
1089 if security_groups_client is None:
1090 security_groups_client = self.security_groups_client
1091 if not tenant_id:
1092 tenant_id = security_groups_client.tenant_id
1093 if secgroup is None:
1094 secgroup = self._default_security_group(
1095 client=security_groups_client, tenant_id=tenant_id)
1096
1097 ruleset = dict(security_group_id=secgroup['id'],
1098 tenant_id=secgroup['tenant_id'])
1099 ruleset.update(kwargs)
1100
1101 sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
1102 sg_rule = sg_rule['security_group_rule']
1103
1104 self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
1105 self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
1106
1107 return sg_rule
1108
1109 def _create_loginable_secgroup_rule(self, security_group_rules_client=None,
1110 secgroup=None,
1111 security_groups_client=None):
1112 """Create loginable security group rule
1113
1114 This function will create:
1115 1. egress and ingress tcp port 22 allow rule in order to allow ssh
1116 access for ipv4.
1117 2. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
1118 3. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
1119 """
1120
1121 if security_group_rules_client is None:
1122 security_group_rules_client = self.security_group_rules_client
1123 if security_groups_client is None:
1124 security_groups_client = self.security_groups_client
1125 rules = []
1126 rulesets = [
1127 dict(
1128 # ssh
1129 protocol='tcp',
1130 port_range_min=22,
1131 port_range_max=22,
1132 ),
1133 dict(
Rodrigo Barbieri797257e2017-11-21 11:00:45 -02001134 # ipv6-ssh
1135 protocol='tcp',
1136 port_range_min=22,
1137 port_range_max=22,
1138 ethertype='IPv6',
1139 ),
1140 dict(
Andrea Frittolif4510a12017-03-07 19:17:11 +00001141 # ping
1142 protocol='icmp',
1143 ),
1144 dict(
1145 # ipv6-icmp for ping6
1146 protocol='icmp',
1147 ethertype='IPv6',
1148 )
1149 ]
1150 sec_group_rules_client = security_group_rules_client
1151 for ruleset in rulesets:
1152 for r_direction in ['ingress', 'egress']:
1153 ruleset['direction'] = r_direction
1154 try:
1155 sg_rule = self._create_security_group_rule(
1156 sec_group_rules_client=sec_group_rules_client,
1157 secgroup=secgroup,
1158 security_groups_client=security_groups_client,
1159 **ruleset)
1160 except lib_exc.Conflict as ex:
1161 # if rule already exist - skip rule and continue
1162 msg = 'Security group rule already exists'
1163 if msg not in ex._error_string:
1164 raise ex
1165 else:
1166 self.assertEqual(r_direction, sg_rule['direction'])
1167 rules.append(sg_rule)
1168
1169 return rules
1170
1171 def _get_router(self, client=None, tenant_id=None):
1172 """Retrieve a router for the given tenant id.
1173
1174 If a public router has been configured, it will be returned.
1175
1176 If a public router has not been configured, but a public
1177 network has, a tenant router will be created and returned that
1178 routes traffic to the public network.
1179 """
1180 if not client:
1181 client = self.routers_client
1182 if not tenant_id:
1183 tenant_id = client.tenant_id
1184 router_id = CONF.network.public_router_id
1185 network_id = CONF.network.public_network_id
1186 if router_id:
1187 body = client.show_router(router_id)
1188 return body['router']
1189 elif network_id:
1190 router = self._create_router(client, tenant_id)
1191 kwargs = {'external_gateway_info': dict(network_id=network_id)}
1192 router = client.update_router(router['id'], **kwargs)['router']
1193 return router
1194 else:
1195 raise Exception("Neither of 'public_router_id' or "
1196 "'public_network_id' has been defined.")
1197
1198 def _create_router(self, client=None, tenant_id=None,
1199 namestart='router-smoke'):
1200 if not client:
1201 client = self.routers_client
1202 if not tenant_id:
1203 tenant_id = client.tenant_id
1204 name = data_utils.rand_name(namestart)
1205 result = client.create_router(name=name,
1206 admin_state_up=True,
1207 tenant_id=tenant_id)
1208 router = result['router']
1209 self.assertEqual(router['name'], name)
1210 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1211 client.delete_router,
1212 router['id'])
1213 return router
1214
1215 def _update_router_admin_state(self, router, admin_state_up):
1216 kwargs = dict(admin_state_up=admin_state_up)
1217 router = self.routers_client.update_router(
1218 router['id'], **kwargs)['router']
1219 self.assertEqual(admin_state_up, router['admin_state_up'])
1220
1221 def create_networks(self, networks_client=None,
1222 routers_client=None, subnets_client=None,
1223 tenant_id=None, dns_nameservers=None,
1224 port_security_enabled=True):
1225 """Create a network with a subnet connected to a router.
1226
1227 The baremetal driver is a special case since all nodes are
1228 on the same shared network.
1229
1230 :param tenant_id: id of tenant to create resources in.
1231 :param dns_nameservers: list of dns servers to send to subnet.
1232 :returns: network, subnet, router
1233 """
1234 if CONF.network.shared_physical_network:
1235 # NOTE(Shrews): This exception is for environments where tenant
1236 # credential isolation is available, but network separation is
1237 # not (the current baremetal case). Likely can be removed when
1238 # test account mgmt is reworked:
1239 # https://blueprints.launchpad.net/tempest/+spec/test-accounts
1240 if not CONF.compute.fixed_network_name:
1241 m = 'fixed_network_name must be specified in config'
1242 raise lib_exc.InvalidConfiguration(m)
1243 network = self._get_network_by_name(
1244 CONF.compute.fixed_network_name)
1245 router = None
1246 subnet = None
1247 else:
1248 network = self._create_network(
1249 networks_client=networks_client,
1250 tenant_id=tenant_id,
1251 port_security_enabled=port_security_enabled)
1252 router = self._get_router(client=routers_client,
1253 tenant_id=tenant_id)
1254 subnet_kwargs = dict(network=network,
1255 subnets_client=subnets_client,
1256 routers_client=routers_client)
1257 # use explicit check because empty list is a valid option
1258 if dns_nameservers is not None:
1259 subnet_kwargs['dns_nameservers'] = dns_nameservers
1260 subnet = self._create_subnet(**subnet_kwargs)
1261 if not routers_client:
1262 routers_client = self.routers_client
1263 router_id = router['id']
1264 routers_client.add_router_interface(router_id,
1265 subnet_id=subnet['id'])
1266
1267 # save a cleanup job to remove this association between
1268 # router and subnet
1269 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1270 routers_client.remove_router_interface, router_id,
1271 subnet_id=subnet['id'])
1272 return network, subnet, router