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