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