blob: aefbb263cf6526a5270b89ec2a61e43158b190a4 [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 +000023from tempest.common import image as common_image
Andrea Frittolif4510a12017-03-07 19:17:11 +000024from tempest.common.utils.linux import remote_client
25from tempest.common.utils import net_utils
Andrea Frittolif4510a12017-03-07 19:17:11 +000026from tempest import config
27from tempest import exceptions
Ken'ichi Ohmichi02d1f242017-03-12 18:56:27 -070028from tempest.lib.common.utils import data_utils
Andrea Frittolif4510a12017-03-07 19:17:11 +000029from tempest.lib.common.utils import test_utils
30from tempest.lib import exceptions as lib_exc
Roman Popelka290ef292022-02-28 10:41:04 +010031from tempest.scenario import manager
Andrea Frittolif4510a12017-03-07 19:17:11 +000032
33CONF = config.CONF
34
35LOG = log.getLogger(__name__)
36
37
Roman Popelka290ef292022-02-28 10:41:04 +010038class ScenarioTest(manager.ScenarioTest):
Andrea Frittolif4510a12017-03-07 19:17:11 +000039 """Base class for scenario tests. Uses tempest own clients. """
40
41 credentials = ['primary']
42
43 @classmethod
44 def setup_clients(cls):
45 super(ScenarioTest, cls).setup_clients()
46 # Clients (in alphabetical order)
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070047 cls.flavors_client = cls.os_primary.flavors_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000048 cls.compute_floating_ips_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070049 cls.os_primary.compute_floating_ips_client)
Andrea Frittolif4510a12017-03-07 19:17:11 +000050 if CONF.service_available.glance:
51 # Check if glance v1 is available to determine which client to use.
52 if CONF.image_feature_enabled.api_v1:
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070053 cls.image_client = cls.os_primary.image_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000054 elif CONF.image_feature_enabled.api_v2:
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070055 cls.image_client = cls.os_primary.image_client_v2
Andrea Frittolif4510a12017-03-07 19:17:11 +000056 else:
57 raise lib_exc.InvalidConfiguration(
58 'Either api_v1 or api_v2 must be True in '
59 '[image-feature-enabled].')
60 # Compute image client
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070061 cls.compute_images_client = cls.os_primary.compute_images_client
62 cls.keypairs_client = cls.os_primary.keypairs_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000063 # Nova security groups client
64 cls.compute_security_groups_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070065 cls.os_primary.compute_security_groups_client)
Andrea Frittolif4510a12017-03-07 19:17:11 +000066 cls.compute_security_group_rules_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070067 cls.os_primary.compute_security_group_rules_client)
68 cls.servers_client = cls.os_primary.servers_client
69 cls.interface_client = cls.os_primary.interfaces_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000070 # Neutron network client
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070071 cls.networks_client = cls.os_primary.networks_client
72 cls.ports_client = cls.os_primary.ports_client
73 cls.routers_client = cls.os_primary.routers_client
74 cls.subnets_client = cls.os_primary.subnets_client
75 cls.floating_ips_client = cls.os_primary.floating_ips_client
76 cls.security_groups_client = cls.os_primary.security_groups_client
Andrea Frittolif4510a12017-03-07 19:17:11 +000077 cls.security_group_rules_client = (
Vu Cong Tuandb2abab2017-06-21 20:38:39 +070078 cls.os_primary.security_group_rules_client)
Andrea Frittolif4510a12017-03-07 19:17:11 +000079
Andrea Frittolif4510a12017-03-07 19:17:11 +000080 # ## Test functions library
81 #
82 # The create_[resource] functions only return body and discard the
83 # resp part which is not used in scenario tests
84
Andrea Frittolif4510a12017-03-07 19:17:11 +000085 def _create_loginable_secgroup_rule(self, secgroup_id=None):
86 _client = self.compute_security_groups_client
87 _client_rules = self.compute_security_group_rules_client
88 if secgroup_id is None:
89 sgs = _client.list_security_groups()['security_groups']
90 for sg in sgs:
91 if sg['name'] == 'default':
92 secgroup_id = sg['id']
93
94 # These rules are intended to permit inbound ssh and icmp
95 # traffic from all sources, so no group_id is provided.
96 # Setting a group_id would only permit traffic from ports
97 # belonging to the same security group.
98 rulesets = [
99 {
100 # ssh
101 'ip_protocol': 'tcp',
102 'from_port': 22,
103 'to_port': 22,
104 'cidr': '0.0.0.0/0',
105 },
106 {
107 # ping
108 'ip_protocol': 'icmp',
109 'from_port': -1,
110 'to_port': -1,
111 'cidr': '0.0.0.0/0',
112 }
113 ]
114 rules = list()
115 for ruleset in rulesets:
116 sg_rule = _client_rules.create_security_group_rule(
117 parent_group_id=secgroup_id, **ruleset)['security_group_rule']
118 rules.append(sg_rule)
119 return rules
120
121 def _create_security_group(self):
122 # Create security group
123 sg_name = data_utils.rand_name(self.__class__.__name__)
124 sg_desc = sg_name + " description"
125 secgroup = self.compute_security_groups_client.create_security_group(
126 name=sg_name, description=sg_desc)['security_group']
127 self.assertEqual(secgroup['name'], sg_name)
128 self.assertEqual(secgroup['description'], sg_desc)
129 self.addCleanup(
130 test_utils.call_and_ignore_notfound_exc,
131 self.compute_security_groups_client.delete_security_group,
132 secgroup['id'])
133
134 # Add rules to the security group
135 self._create_loginable_secgroup_rule(secgroup['id'])
136
137 return secgroup
138
139 def get_remote_client(self, ip_address, username=None, private_key=None):
140 """Get a SSH client to a remote server
141
142 @param ip_address the server floating or fixed IP address to use
143 for ssh validation
144 @param username name of the Linux account on the remote server
145 @param private_key the SSH private key to use
146 @return a RemoteClient object
147 """
148
149 if username is None:
150 username = CONF.validation.image_ssh_user
151 # Set this with 'keypair' or others to log in with keypair or
152 # username/password.
153 if CONF.validation.auth_method == 'keypair':
154 password = None
155 if private_key is None:
156 private_key = self.keypair['private_key']
157 else:
158 password = CONF.validation.image_ssh_password
159 private_key = None
160 linux_client = remote_client.RemoteClient(ip_address, username,
161 pkey=private_key,
162 password=password)
163 try:
164 linux_client.validate_authentication()
165 except Exception as e:
166 message = ('Initializing SSH connection to %(ip)s failed. '
167 'Error: %(error)s' % {'ip': ip_address,
168 'error': e})
169 caller = test_utils.find_test_caller()
170 if caller:
171 message = '(%s) %s' % (caller, message)
172 LOG.exception(message)
173 self._log_console_output()
174 raise
175
176 return linux_client
177
178 def _image_create(self, name, fmt, path,
179 disk_format=None, properties=None):
180 if properties is None:
181 properties = {}
182 name = data_utils.rand_name('%s-' % name)
183 params = {
184 'name': name,
185 'container_format': fmt,
186 'disk_format': disk_format or fmt,
187 }
188 if CONF.image_feature_enabled.api_v1:
189 params['is_public'] = 'False'
190 params['properties'] = properties
191 params = {'headers': common_image.image_meta_to_headers(**params)}
192 else:
193 params['visibility'] = 'private'
194 # Additional properties are flattened out in the v2 API.
195 params.update(properties)
196 body = self.image_client.create_image(**params)
197 image = body['image'] if 'image' in body else body
198 self.addCleanup(self.image_client.delete_image, image['id'])
199 self.assertEqual("queued", image['status'])
200 with open(path, 'rb') as image_file:
201 if CONF.image_feature_enabled.api_v1:
202 self.image_client.update_image(image['id'], data=image_file)
203 else:
204 self.image_client.store_image_file(image['id'], image_file)
205 return image['id']
206
207 def glance_image_create(self):
Martin Kopec258cc6c2020-04-15 22:55:25 +0000208 img_path = CONF.scenario.img_file
Andrea Frittolif4510a12017-03-07 19:17:11 +0000209 img_container_format = CONF.scenario.img_container_format
210 img_disk_format = CONF.scenario.img_disk_format
211 img_properties = CONF.scenario.img_properties
212 LOG.debug("paths: img: %s, container_format: %s, disk_format: %s, "
Martin Kopec258cc6c2020-04-15 22:55:25 +0000213 "properties: %s",
Andrea Frittolif4510a12017-03-07 19:17:11 +0000214 img_path, img_container_format, img_disk_format,
Martin Kopec258cc6c2020-04-15 22:55:25 +0000215 img_properties)
216 image = self._image_create('scenario-img',
217 img_container_format,
218 img_path,
219 disk_format=img_disk_format,
220 properties=img_properties)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000221 LOG.debug("image:%s", image)
222
223 return image
224
225 def _log_console_output(self, servers=None):
226 if not CONF.compute_feature_enabled.console_output:
227 LOG.debug('Console output not supported, cannot log')
228 return
229 if not servers:
230 servers = self.servers_client.list_servers()
231 servers = servers['servers']
232 for server in servers:
233 try:
234 console_output = self.servers_client.get_console_output(
235 server['id'])['output']
236 LOG.debug('Console output for %s\nbody=\n%s',
237 server['id'], console_output)
238 except lib_exc.NotFound:
239 LOG.debug("Server %s disappeared(deleted) while looking "
240 "for the console log", server['id'])
241
242 def _log_net_info(self, exc):
243 # network debug is called as part of ssh init
244 if not isinstance(exc, lib_exc.SSHTimeout):
245 LOG.debug('Network information on a devstack host')
246
Andrea Frittolif4510a12017-03-07 19:17:11 +0000247 def ping_ip_address(self, ip_address, should_succeed=True,
248 ping_timeout=None, mtu=None):
249 timeout = ping_timeout or CONF.validation.ping_timeout
250 cmd = ['ping', '-c1', '-w1']
251
252 if mtu:
253 cmd += [
254 # don't fragment
255 '-M', 'do',
256 # ping receives just the size of ICMP payload
257 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
258 ]
259 cmd.append(ip_address)
260
261 def ping():
262 proc = subprocess.Popen(cmd,
263 stdout=subprocess.PIPE,
264 stderr=subprocess.PIPE)
265 proc.communicate()
266
267 return (proc.returncode == 0) == should_succeed
268
269 caller = test_utils.find_test_caller()
270 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
271 ' expected result is %(should_succeed)s', {
272 'caller': caller, 'ip': ip_address, 'timeout': timeout,
273 'should_succeed':
274 'reachable' if should_succeed else 'unreachable'
275 })
276 result = test_utils.call_until_true(ping, timeout, 1)
277 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
278 'ping result is %(result)s', {
279 'caller': caller, 'ip': ip_address, 'timeout': timeout,
280 'result': 'expected' if result else 'unexpected'
281 })
282 return result
283
284 def check_vm_connectivity(self, ip_address,
285 username=None,
286 private_key=None,
287 should_connect=True,
288 mtu=None):
289 """Check server connectivity
290
291 :param ip_address: server to test against
292 :param username: server's ssh username
293 :param private_key: server's ssh private key to be used
294 :param should_connect: True/False indicates positive/negative test
295 positive - attempt ping and ssh
296 negative - attempt ping and fail if succeed
297 :param mtu: network MTU to use for connectivity validation
298
299 :raises: AssertError if the result of the connectivity check does
300 not match the value of the should_connect param
301 """
302 if should_connect:
303 msg = "Timed out waiting for %s to become reachable" % ip_address
304 else:
305 msg = "ip address %s is reachable" % ip_address
306 self.assertTrue(self.ping_ip_address(ip_address,
307 should_succeed=should_connect,
308 mtu=mtu),
309 msg=msg)
310 if should_connect:
311 # no need to check ssh for negative connectivity
312 self.get_remote_client(ip_address, username, private_key)
313
314 def check_public_network_connectivity(self, ip_address, username,
315 private_key, should_connect=True,
316 msg=None, servers=None, mtu=None):
317 # The target login is assumed to have been configured for
318 # key-based authentication by cloud-init.
319 LOG.debug('checking network connections to IP %s with user: %s',
320 ip_address, username)
321 try:
322 self.check_vm_connectivity(ip_address,
323 username,
324 private_key,
325 should_connect=should_connect,
326 mtu=mtu)
327 except Exception:
328 ex_msg = 'Public network connectivity check failed'
329 if msg:
330 ex_msg += ": " + msg
331 LOG.exception(ex_msg)
332 self._log_console_output(servers)
333 raise
334
335 def create_floating_ip(self, thing, pool_name=None):
336 """Create a floating IP and associates to a server on Nova"""
337
338 if not pool_name:
339 pool_name = CONF.network.floating_network_name
340 floating_ip = (self.compute_floating_ips_client.
341 create_floating_ip(pool=pool_name)['floating_ip'])
342 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
343 self.compute_floating_ips_client.delete_floating_ip,
344 floating_ip['id'])
345 self.compute_floating_ips_client.associate_floating_ip_to_server(
346 floating_ip['ip'], thing['id'])
347 return floating_ip
348
349 def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
350 private_key=None):
351 ssh_client = self.get_remote_client(ip_address,
352 private_key=private_key)
353 if dev_name is not None:
354 ssh_client.make_fs(dev_name)
355 ssh_client.mount(dev_name, mount_path)
356 cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % mount_path
357 ssh_client.exec_command(cmd_timestamp)
358 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
359 % mount_path)
360 if dev_name is not None:
361 ssh_client.umount(mount_path)
362 return timestamp
363
364 def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
365 private_key=None):
366 ssh_client = self.get_remote_client(ip_address,
367 private_key=private_key)
368 if dev_name is not None:
369 ssh_client.mount(dev_name, mount_path)
370 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
371 % mount_path)
372 if dev_name is not None:
373 ssh_client.umount(mount_path)
374 return timestamp
375
376 def get_server_ip(self, server):
377 """Get the server fixed or floating IP.
378
379 Based on the configuration we're in, return a correct ip
380 address for validating that a guest is up.
381 """
382 if CONF.validation.connect_method == 'floating':
383 # The tests calling this method don't have a floating IP
384 # and can't make use of the validation resources. So the
385 # method is creating the floating IP there.
386 return self.create_floating_ip(server)['ip']
387 elif CONF.validation.connect_method == 'fixed':
388 # Determine the network name to look for based on config or creds
389 # provider network resources.
390 if CONF.validation.network_for_ssh:
391 addresses = server['addresses'][
392 CONF.validation.network_for_ssh]
393 else:
394 creds_provider = self._get_credentials_provider()
395 net_creds = creds_provider.get_primary_creds()
396 network = getattr(net_creds, 'network', None)
397 addresses = (server['addresses'][network['name']]
398 if network else [])
399 for address in addresses:
400 if (address['version'] == CONF.validation.ip_version_for_ssh
401 and address['OS-EXT-IPS:type'] == 'fixed'):
402 return address['addr']
403 raise exceptions.ServerUnreachable(server_id=server['id'])
404 else:
405 raise lib_exc.InvalidConfiguration()
406
407
408class NetworkScenarioTest(ScenarioTest):
409 """Base class for network scenario tests.
410
411 This class provide helpers for network scenario tests, using the neutron
412 API. Helpers from ancestor which use the nova network API are overridden
413 with the neutron API.
414
415 This Class also enforces using Neutron instead of novanetwork.
416 Subclassed tests will be skipped if Neutron is not enabled
417
418 """
419
420 credentials = ['primary', 'admin']
421
422 @classmethod
423 def skip_checks(cls):
424 super(NetworkScenarioTest, cls).skip_checks()
425 if not CONF.service_available.neutron:
426 raise cls.skipException('Neutron not available')
427
428 def _create_network(self, networks_client=None,
429 tenant_id=None,
430 namestart='network-smoke-',
431 port_security_enabled=True):
432 if not networks_client:
433 networks_client = self.networks_client
434 if not tenant_id:
435 tenant_id = networks_client.tenant_id
436 name = data_utils.rand_name(namestart)
437 network_kwargs = dict(name=name, tenant_id=tenant_id)
438 # Neutron disables port security by default so we have to check the
439 # config before trying to create the network with port_security_enabled
440 if CONF.network_feature_enabled.port_security:
441 network_kwargs['port_security_enabled'] = port_security_enabled
442 result = networks_client.create_network(**network_kwargs)
443 network = result['network']
444
445 self.assertEqual(network['name'], name)
446 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
447 networks_client.delete_network,
448 network['id'])
449 return network
450
451 def _create_subnet(self, network, subnets_client=None,
452 routers_client=None, namestart='subnet-smoke',
453 **kwargs):
454 """Create a subnet for the given network
455
456 within the cidr block configured for tenant networks.
457 """
458 if not subnets_client:
459 subnets_client = self.subnets_client
460 if not routers_client:
461 routers_client = self.routers_client
462
463 def cidr_in_use(cidr, tenant_id):
464 """Check cidr existence
465
466 :returns: True if subnet with cidr already exist in tenant
467 False else
468 """
Vu Cong Tuan99751862017-06-23 19:46:40 +0700469 cidr_in_use = self.os_admin.subnets_client.list_subnets(
Andrea Frittolif4510a12017-03-07 19:17:11 +0000470 tenant_id=tenant_id, cidr=cidr)['subnets']
471 return len(cidr_in_use) != 0
472
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200473 def _make_create_subnet_request(namestart, network,
474 ip_version, subnets_client, **kwargs):
Andrea Frittolif4510a12017-03-07 19:17:11 +0000475
476 subnet = dict(
477 name=data_utils.rand_name(namestart),
478 network_id=network['id'],
479 tenant_id=network['tenant_id'],
Andrea Frittolif4510a12017-03-07 19:17:11 +0000480 ip_version=ip_version,
481 **kwargs
482 )
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200483
484 if ip_version == 6:
485 subnet['ipv6_address_mode'] = 'slaac'
486 subnet['ipv6_ra_mode'] = 'slaac'
487
Andrea Frittolif4510a12017-03-07 19:17:11 +0000488 try:
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200489 return subnets_client.create_subnet(**subnet)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000490 except lib_exc.Conflict as e:
haixin48895812020-09-30 13:50:37 +0800491 if 'overlaps with another subnet' not in str(e):
Andrea Frittolif4510a12017-03-07 19:17:11 +0000492 raise
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200493
494 result = None
495 str_cidr = None
496
497 use_default_subnetpool = kwargs.get('use_default_subnetpool', False)
498
499 ip_version = kwargs.pop('ip_version', 4)
500
501 if not use_default_subnetpool:
502
503 if ip_version == 6:
504 tenant_cidr = netaddr.IPNetwork(
505 CONF.network.project_network_v6_cidr)
506 num_bits = CONF.network.project_network_v6_mask_bits
507 else:
508 tenant_cidr = netaddr.IPNetwork(
509 CONF.network.project_network_cidr)
510 num_bits = CONF.network.project_network_mask_bits
511
512 # Repeatedly attempt subnet creation with sequential cidr
513 # blocks until an unallocated block is found.
514 for subnet_cidr in tenant_cidr.subnet(num_bits):
515 str_cidr = str(subnet_cidr)
516 if cidr_in_use(str_cidr, tenant_id=network['tenant_id']):
517 continue
518
519 result = _make_create_subnet_request(
520 namestart, network, ip_version, subnets_client,
521 cidr=str_cidr, **kwargs)
522 if result is not None:
523 break
524 else:
525 result = _make_create_subnet_request(
526 namestart, network, ip_version, subnets_client,
527 **kwargs)
528
529 self.assertIsNotNone(result)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000530
531 subnet = result['subnet']
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200532 if str_cidr is not None:
533 self.assertEqual(subnet['cidr'], str_cidr)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000534
535 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
536 subnets_client.delete_subnet, subnet['id'])
537
538 return subnet
539
540 def _get_server_port_id_and_ip4(self, server, ip_addr=None):
Rockyd3432662019-07-02 14:58:30 +1000541 if ip_addr:
542 ports = self.os_admin.ports_client.list_ports(
543 device_id=server['id'],
544 fixed_ips='ip_address=%s' % ip_addr)['ports']
545 else:
546 ports = self.os_admin.ports_client.list_ports(
547 device_id=server['id'])['ports']
Andrea Frittolif4510a12017-03-07 19:17:11 +0000548 # A port can have more than one IP address in some cases.
549 # If the network is dual-stack (IPv4 + IPv6), this port is associated
550 # with 2 subnets
Rockyd3432662019-07-02 14:58:30 +1000551
552 def _is_active(port):
553 # NOTE(vsaienko) With Ironic, instances live on separate hardware
554 # servers. Neutron does not bind ports for Ironic instances, as a
555 # result the port remains in the DOWN state. This has been fixed
556 # with the introduction of the networking-baremetal plugin but
557 # it's not mandatory (and is not used on all stable branches).
558 return (port['status'] == 'ACTIVE' or
559 port.get('binding:vnic_type') == 'baremetal')
560
Andrea Frittolif4510a12017-03-07 19:17:11 +0000561 port_map = [(p["id"], fxip["ip_address"])
562 for p in ports
563 for fxip in p["fixed_ips"]
Rockyd3432662019-07-02 14:58:30 +1000564 if (netutils.is_valid_ipv4(fxip["ip_address"]) and
565 _is_active(p))]
Andrea Frittolif4510a12017-03-07 19:17:11 +0000566 inactive = [p for p in ports if p['status'] != 'ACTIVE']
567 if inactive:
568 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
569
Rockyd3432662019-07-02 14:58:30 +1000570 self.assertNotEmpty(port_map,
Andrea Frittolif4510a12017-03-07 19:17:11 +0000571 "No IPv4 addresses found in: %s" % ports)
572 self.assertEqual(len(port_map), 1,
573 "Found multiple IPv4 addresses: %s. "
574 "Unable to determine which port to target."
575 % port_map)
576 return port_map[0]
577
Goutham Pacha Ravi37ee6772019-10-18 12:53:22 -0700578 def _get_network_by_name_or_id(self, identifier):
579
580 if uuidutils.is_uuid_like(identifier):
581 return self.os_admin.networks_client.show_network(
582 identifier)['network']
583
584 networks = self.os_admin.networks_client.list_networks(
585 name=identifier)['networks']
586 self.assertNotEqual(len(networks), 0,
587 "Unable to get network by name: %s" % identifier)
588 return networks[0]
589
590 def get_networks(self):
591 return self.os_admin.networks_client.list_networks()['networks']
Andrea Frittolif4510a12017-03-07 19:17:11 +0000592
593 def create_floating_ip(self, thing, external_network_id=None,
lkuchlan7636a1f2020-04-30 16:13:13 +0300594 port_id=None, ip_addr=None, client=None):
Andrea Frittolif4510a12017-03-07 19:17:11 +0000595 """Create a floating IP and associates to a resource/port on Neutron"""
596 if not external_network_id:
597 external_network_id = CONF.network.public_network_id
598 if not client:
599 client = self.floating_ips_client
600 if not port_id:
lkuchlan7636a1f2020-04-30 16:13:13 +0300601 port_id, ip4 = self._get_server_port_id_and_ip4(thing,
602 ip_addr=ip_addr)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000603 else:
604 ip4 = None
605 result = client.create_floatingip(
606 floating_network_id=external_network_id,
607 port_id=port_id,
608 tenant_id=thing['tenant_id'],
609 fixed_ip_address=ip4
610 )
611 floating_ip = result['floatingip']
612 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
613 client.delete_floatingip,
614 floating_ip['id'])
615 return floating_ip
616
617 def _associate_floating_ip(self, floating_ip, server):
618 port_id, _ = self._get_server_port_id_and_ip4(server)
619 kwargs = dict(port_id=port_id)
620 floating_ip = self.floating_ips_client.update_floatingip(
621 floating_ip['id'], **kwargs)['floatingip']
622 self.assertEqual(port_id, floating_ip['port_id'])
623 return floating_ip
624
625 def _disassociate_floating_ip(self, floating_ip):
626 """:param floating_ip: floating_ips_client.create_floatingip"""
627 kwargs = dict(port_id=None)
628 floating_ip = self.floating_ips_client.update_floatingip(
629 floating_ip['id'], **kwargs)['floatingip']
630 self.assertIsNone(floating_ip['port_id'])
631 return floating_ip
632
633 def check_floating_ip_status(self, floating_ip, status):
634 """Verifies floatingip reaches the given status
635
636 :param dict floating_ip: floating IP dict to check status
637 :param status: target status
638 :raises: AssertionError if status doesn't match
639 """
640 floatingip_id = floating_ip['id']
641
642 def refresh():
643 result = (self.floating_ips_client.
644 show_floatingip(floatingip_id)['floatingip'])
645 return status == result['status']
646
647 test_utils.call_until_true(refresh,
648 CONF.network.build_timeout,
649 CONF.network.build_interval)
650 floating_ip = self.floating_ips_client.show_floatingip(
651 floatingip_id)['floatingip']
652 self.assertEqual(status, floating_ip['status'],
653 message="FloatingIP: {fp} is at status: {cst}. "
654 "failed to reach status: {st}"
655 .format(fp=floating_ip, cst=floating_ip['status'],
656 st=status))
657 LOG.info("FloatingIP: {fp} is at status: {st}"
658 .format(fp=floating_ip, st=status))
659
660 def _check_tenant_network_connectivity(self, server,
661 username,
662 private_key,
663 should_connect=True,
664 servers_for_debug=None):
665 if not CONF.network.project_networks_reachable:
666 msg = 'Tenant networks not configured to be reachable.'
667 LOG.info(msg)
668 return
669 # The target login is assumed to have been configured for
670 # key-based authentication by cloud-init.
671 try:
672 for ip_addresses in server['addresses'].values():
673 for ip_address in ip_addresses:
674 self.check_vm_connectivity(ip_address['addr'],
675 username,
676 private_key,
677 should_connect=should_connect)
678 except Exception as e:
679 LOG.exception('Tenant network connectivity check failed')
680 self._log_console_output(servers_for_debug)
681 self._log_net_info(e)
682 raise
683
684 def _check_remote_connectivity(self, source, dest, should_succeed=True,
685 nic=None):
686 """assert ping server via source ssh connection
687
688 Note: This is an internal method. Use check_remote_connectivity
689 instead.
690
691 :param source: RemoteClient: an ssh connection from which to ping
692 :param dest: and IP to ping against
693 :param should_succeed: boolean should ping succeed or not
694 :param nic: specific network interface to ping from
695 """
696 def ping_remote():
697 try:
698 source.ping_host(dest, nic=nic)
699 except lib_exc.SSHExecCommandFailed:
700 LOG.warning('Failed to ping IP: %s via a ssh connection '
701 'from: %s.', dest, source.ssh_client.host)
702 return not should_succeed
703 return should_succeed
704
705 return test_utils.call_until_true(ping_remote,
706 CONF.validation.ping_timeout,
707 1)
708
709 def check_remote_connectivity(self, source, dest, should_succeed=True,
710 nic=None):
711 """assert ping server via source ssh connection
712
713 :param source: RemoteClient: an ssh connection from which to ping
714 :param dest: and IP to ping against
715 :param should_succeed: boolean should ping succeed or not
716 :param nic: specific network interface to ping from
717 """
718 result = self._check_remote_connectivity(source, dest, should_succeed,
719 nic)
720 source_host = source.ssh_client.host
721 if should_succeed:
zhongjun39e9c582017-06-21 15:17:11 +0800722 msg = ("Timed out waiting for %s to become reachable from %s"
723 % (dest, source_host))
Andrea Frittolif4510a12017-03-07 19:17:11 +0000724 else:
725 msg = "%s is reachable from %s" % (dest, source_host)
726 self.assertTrue(result, msg)
727
728 def _create_security_group(self, security_group_rules_client=None,
729 tenant_id=None,
730 namestart='secgroup-smoke',
731 security_groups_client=None):
732 if security_group_rules_client is None:
733 security_group_rules_client = self.security_group_rules_client
734 if security_groups_client is None:
735 security_groups_client = self.security_groups_client
736 if tenant_id is None:
737 tenant_id = security_groups_client.tenant_id
738 secgroup = self._create_empty_security_group(
739 namestart=namestart, client=security_groups_client,
740 tenant_id=tenant_id)
741
742 # Add rules to the security group
743 rules = self._create_loginable_secgroup_rule(
744 security_group_rules_client=security_group_rules_client,
745 secgroup=secgroup,
746 security_groups_client=security_groups_client)
747 for rule in rules:
748 self.assertEqual(tenant_id, rule['tenant_id'])
749 self.assertEqual(secgroup['id'], rule['security_group_id'])
750 return secgroup
751
752 def _create_empty_security_group(self, client=None, tenant_id=None,
753 namestart='secgroup-smoke'):
754 """Create a security group without rules.
755
756 Default rules will be created:
757 - IPv4 egress to any
758 - IPv6 egress to any
759
760 :param tenant_id: secgroup will be created in this tenant
761 :returns: the created security group
762 """
763 if client is None:
764 client = self.security_groups_client
765 if not tenant_id:
766 tenant_id = client.tenant_id
767 sg_name = data_utils.rand_name(namestart)
768 sg_desc = sg_name + " description"
769 sg_dict = dict(name=sg_name,
770 description=sg_desc)
771 sg_dict['tenant_id'] = tenant_id
772 result = client.create_security_group(**sg_dict)
773
774 secgroup = result['security_group']
775 self.assertEqual(secgroup['name'], sg_name)
776 self.assertEqual(tenant_id, secgroup['tenant_id'])
777 self.assertEqual(secgroup['description'], sg_desc)
778
779 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
780 client.delete_security_group, secgroup['id'])
781 return secgroup
782
783 def _default_security_group(self, client=None, tenant_id=None):
784 """Get default secgroup for given tenant_id.
785
786 :returns: default secgroup for given tenant
787 """
788 if client is None:
789 client = self.security_groups_client
790 if not tenant_id:
791 tenant_id = client.tenant_id
792 sgs = [
793 sg for sg in list(client.list_security_groups().values())[0]
794 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
795 ]
796 msg = "No default security group for tenant %s." % (tenant_id)
797 self.assertGreater(len(sgs), 0, msg)
798 return sgs[0]
799
800 def _create_security_group_rule(self, secgroup=None,
801 sec_group_rules_client=None,
802 tenant_id=None,
803 security_groups_client=None, **kwargs):
804 """Create a rule from a dictionary of rule parameters.
805
806 Create a rule in a secgroup. if secgroup not defined will search for
807 default secgroup in tenant_id.
808
809 :param secgroup: the security group.
810 :param tenant_id: if secgroup not passed -- the tenant in which to
811 search for default secgroup
812 :param kwargs: a dictionary containing rule parameters:
813 for example, to allow incoming ssh:
814 rule = {
815 direction: 'ingress'
816 protocol:'tcp',
817 port_range_min: 22,
818 port_range_max: 22
819 }
820 """
821 if sec_group_rules_client is None:
822 sec_group_rules_client = self.security_group_rules_client
823 if security_groups_client is None:
824 security_groups_client = self.security_groups_client
825 if not tenant_id:
826 tenant_id = security_groups_client.tenant_id
827 if secgroup is None:
828 secgroup = self._default_security_group(
829 client=security_groups_client, tenant_id=tenant_id)
830
831 ruleset = dict(security_group_id=secgroup['id'],
832 tenant_id=secgroup['tenant_id'])
833 ruleset.update(kwargs)
834
835 sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
836 sg_rule = sg_rule['security_group_rule']
837
838 self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
839 self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
840
841 return sg_rule
842
843 def _create_loginable_secgroup_rule(self, security_group_rules_client=None,
844 secgroup=None,
845 security_groups_client=None):
846 """Create loginable security group rule
847
848 This function will create:
849 1. egress and ingress tcp port 22 allow rule in order to allow ssh
850 access for ipv4.
851 2. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
852 3. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
853 """
854
855 if security_group_rules_client is None:
856 security_group_rules_client = self.security_group_rules_client
857 if security_groups_client is None:
858 security_groups_client = self.security_groups_client
859 rules = []
860 rulesets = [
861 dict(
862 # ssh
863 protocol='tcp',
864 port_range_min=22,
865 port_range_max=22,
866 ),
867 dict(
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200868 # ipv6-ssh
869 protocol='tcp',
870 port_range_min=22,
871 port_range_max=22,
872 ethertype='IPv6',
873 ),
874 dict(
Andrea Frittolif4510a12017-03-07 19:17:11 +0000875 # ping
876 protocol='icmp',
877 ),
878 dict(
879 # ipv6-icmp for ping6
880 protocol='icmp',
881 ethertype='IPv6',
882 )
883 ]
884 sec_group_rules_client = security_group_rules_client
885 for ruleset in rulesets:
886 for r_direction in ['ingress', 'egress']:
887 ruleset['direction'] = r_direction
888 try:
889 sg_rule = self._create_security_group_rule(
890 sec_group_rules_client=sec_group_rules_client,
891 secgroup=secgroup,
892 security_groups_client=security_groups_client,
893 **ruleset)
894 except lib_exc.Conflict as ex:
895 # if rule already exist - skip rule and continue
896 msg = 'Security group rule already exists'
897 if msg not in ex._error_string:
898 raise ex
899 else:
900 self.assertEqual(r_direction, sg_rule['direction'])
901 rules.append(sg_rule)
902
903 return rules
904
905 def _get_router(self, client=None, tenant_id=None):
906 """Retrieve a router for the given tenant id.
907
908 If a public router has been configured, it will be returned.
909
910 If a public router has not been configured, but a public
911 network has, a tenant router will be created and returned that
912 routes traffic to the public network.
913 """
914 if not client:
915 client = self.routers_client
916 if not tenant_id:
917 tenant_id = client.tenant_id
918 router_id = CONF.network.public_router_id
919 network_id = CONF.network.public_network_id
920 if router_id:
921 body = client.show_router(router_id)
922 return body['router']
923 elif network_id:
924 router = self._create_router(client, tenant_id)
925 kwargs = {'external_gateway_info': dict(network_id=network_id)}
926 router = client.update_router(router['id'], **kwargs)['router']
927 return router
928 else:
929 raise Exception("Neither of 'public_router_id' or "
930 "'public_network_id' has been defined.")
931
932 def _create_router(self, client=None, tenant_id=None,
933 namestart='router-smoke'):
934 if not client:
935 client = self.routers_client
936 if not tenant_id:
937 tenant_id = client.tenant_id
938 name = data_utils.rand_name(namestart)
939 result = client.create_router(name=name,
940 admin_state_up=True,
941 tenant_id=tenant_id)
942 router = result['router']
943 self.assertEqual(router['name'], name)
944 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
945 client.delete_router,
946 router['id'])
947 return router
948
949 def _update_router_admin_state(self, router, admin_state_up):
950 kwargs = dict(admin_state_up=admin_state_up)
951 router = self.routers_client.update_router(
952 router['id'], **kwargs)['router']
953 self.assertEqual(admin_state_up, router['admin_state_up'])
954
955 def create_networks(self, networks_client=None,
956 routers_client=None, subnets_client=None,
957 tenant_id=None, dns_nameservers=None,
958 port_security_enabled=True):
959 """Create a network with a subnet connected to a router.
960
961 The baremetal driver is a special case since all nodes are
962 on the same shared network.
963
964 :param tenant_id: id of tenant to create resources in.
965 :param dns_nameservers: list of dns servers to send to subnet.
966 :returns: network, subnet, router
967 """
968 if CONF.network.shared_physical_network:
969 # NOTE(Shrews): This exception is for environments where tenant
970 # credential isolation is available, but network separation is
971 # not (the current baremetal case). Likely can be removed when
972 # test account mgmt is reworked:
973 # https://blueprints.launchpad.net/tempest/+spec/test-accounts
974 if not CONF.compute.fixed_network_name:
975 m = 'fixed_network_name must be specified in config'
976 raise lib_exc.InvalidConfiguration(m)
Goutham Pacha Ravi37ee6772019-10-18 12:53:22 -0700977 network = self._get_network_by_name_or_id(
Andrea Frittolif4510a12017-03-07 19:17:11 +0000978 CONF.compute.fixed_network_name)
979 router = None
980 subnet = None
981 else:
982 network = self._create_network(
983 networks_client=networks_client,
984 tenant_id=tenant_id,
985 port_security_enabled=port_security_enabled)
986 router = self._get_router(client=routers_client,
987 tenant_id=tenant_id)
988 subnet_kwargs = dict(network=network,
989 subnets_client=subnets_client,
990 routers_client=routers_client)
991 # use explicit check because empty list is a valid option
992 if dns_nameservers is not None:
993 subnet_kwargs['dns_nameservers'] = dns_nameservers
994 subnet = self._create_subnet(**subnet_kwargs)
995 if not routers_client:
996 routers_client = self.routers_client
997 router_id = router['id']
998 routers_client.add_router_interface(router_id,
999 subnet_id=subnet['id'])
1000
1001 # save a cleanup job to remove this association between
1002 # router and subnet
1003 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1004 routers_client.remove_router_interface, router_id,
1005 subnet_id=subnet['id'])
1006 return network, subnet, router