blob: c83dfe6bf40db6f1405c49331d776c27ac2da150 [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):
Martin Kopec258cc6c2020-04-15 22:55:25 +0000333 img_path = CONF.scenario.img_file
Andrea Frittolif4510a12017-03-07 19:17:11 +0000334 img_container_format = CONF.scenario.img_container_format
335 img_disk_format = CONF.scenario.img_disk_format
336 img_properties = CONF.scenario.img_properties
337 LOG.debug("paths: img: %s, container_format: %s, disk_format: %s, "
Martin Kopec258cc6c2020-04-15 22:55:25 +0000338 "properties: %s",
Andrea Frittolif4510a12017-03-07 19:17:11 +0000339 img_path, img_container_format, img_disk_format,
Martin Kopec258cc6c2020-04-15 22:55:25 +0000340 img_properties)
341 image = self._image_create('scenario-img',
342 img_container_format,
343 img_path,
344 disk_format=img_disk_format,
345 properties=img_properties)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000346 LOG.debug("image:%s", image)
347
348 return image
349
350 def _log_console_output(self, servers=None):
351 if not CONF.compute_feature_enabled.console_output:
352 LOG.debug('Console output not supported, cannot log')
353 return
354 if not servers:
355 servers = self.servers_client.list_servers()
356 servers = servers['servers']
357 for server in servers:
358 try:
359 console_output = self.servers_client.get_console_output(
360 server['id'])['output']
361 LOG.debug('Console output for %s\nbody=\n%s',
362 server['id'], console_output)
363 except lib_exc.NotFound:
364 LOG.debug("Server %s disappeared(deleted) while looking "
365 "for the console log", server['id'])
366
367 def _log_net_info(self, exc):
368 # network debug is called as part of ssh init
369 if not isinstance(exc, lib_exc.SSHTimeout):
370 LOG.debug('Network information on a devstack host')
371
Andrea Frittolif4510a12017-03-07 19:17:11 +0000372 def rebuild_server(self, server_id, image=None,
373 preserve_ephemeral=False, wait=True,
374 rebuild_kwargs=None):
375 if image is None:
376 image = CONF.compute.image_ref
377
378 rebuild_kwargs = rebuild_kwargs or {}
379
380 LOG.debug("Rebuilding server (id: %s, image: %s, preserve eph: %s)",
381 server_id, image, preserve_ephemeral)
382 self.servers_client.rebuild_server(
383 server_id=server_id, image_ref=image,
384 preserve_ephemeral=preserve_ephemeral,
385 **rebuild_kwargs)
386 if wait:
387 waiters.wait_for_server_status(self.servers_client,
388 server_id, 'ACTIVE')
389
390 def ping_ip_address(self, ip_address, should_succeed=True,
391 ping_timeout=None, mtu=None):
392 timeout = ping_timeout or CONF.validation.ping_timeout
393 cmd = ['ping', '-c1', '-w1']
394
395 if mtu:
396 cmd += [
397 # don't fragment
398 '-M', 'do',
399 # ping receives just the size of ICMP payload
400 '-s', str(net_utils.get_ping_payload_size(mtu, 4))
401 ]
402 cmd.append(ip_address)
403
404 def ping():
405 proc = subprocess.Popen(cmd,
406 stdout=subprocess.PIPE,
407 stderr=subprocess.PIPE)
408 proc.communicate()
409
410 return (proc.returncode == 0) == should_succeed
411
412 caller = test_utils.find_test_caller()
413 LOG.debug('%(caller)s begins to ping %(ip)s in %(timeout)s sec and the'
414 ' expected result is %(should_succeed)s', {
415 'caller': caller, 'ip': ip_address, 'timeout': timeout,
416 'should_succeed':
417 'reachable' if should_succeed else 'unreachable'
418 })
419 result = test_utils.call_until_true(ping, timeout, 1)
420 LOG.debug('%(caller)s finishes ping %(ip)s in %(timeout)s sec and the '
421 'ping result is %(result)s', {
422 'caller': caller, 'ip': ip_address, 'timeout': timeout,
423 'result': 'expected' if result else 'unexpected'
424 })
425 return result
426
427 def check_vm_connectivity(self, ip_address,
428 username=None,
429 private_key=None,
430 should_connect=True,
431 mtu=None):
432 """Check server connectivity
433
434 :param ip_address: server to test against
435 :param username: server's ssh username
436 :param private_key: server's ssh private key to be used
437 :param should_connect: True/False indicates positive/negative test
438 positive - attempt ping and ssh
439 negative - attempt ping and fail if succeed
440 :param mtu: network MTU to use for connectivity validation
441
442 :raises: AssertError if the result of the connectivity check does
443 not match the value of the should_connect param
444 """
445 if should_connect:
446 msg = "Timed out waiting for %s to become reachable" % ip_address
447 else:
448 msg = "ip address %s is reachable" % ip_address
449 self.assertTrue(self.ping_ip_address(ip_address,
450 should_succeed=should_connect,
451 mtu=mtu),
452 msg=msg)
453 if should_connect:
454 # no need to check ssh for negative connectivity
455 self.get_remote_client(ip_address, username, private_key)
456
457 def check_public_network_connectivity(self, ip_address, username,
458 private_key, should_connect=True,
459 msg=None, servers=None, mtu=None):
460 # The target login is assumed to have been configured for
461 # key-based authentication by cloud-init.
462 LOG.debug('checking network connections to IP %s with user: %s',
463 ip_address, username)
464 try:
465 self.check_vm_connectivity(ip_address,
466 username,
467 private_key,
468 should_connect=should_connect,
469 mtu=mtu)
470 except Exception:
471 ex_msg = 'Public network connectivity check failed'
472 if msg:
473 ex_msg += ": " + msg
474 LOG.exception(ex_msg)
475 self._log_console_output(servers)
476 raise
477
478 def create_floating_ip(self, thing, pool_name=None):
479 """Create a floating IP and associates to a server on Nova"""
480
481 if not pool_name:
482 pool_name = CONF.network.floating_network_name
483 floating_ip = (self.compute_floating_ips_client.
484 create_floating_ip(pool=pool_name)['floating_ip'])
485 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
486 self.compute_floating_ips_client.delete_floating_ip,
487 floating_ip['id'])
488 self.compute_floating_ips_client.associate_floating_ip_to_server(
489 floating_ip['ip'], thing['id'])
490 return floating_ip
491
492 def create_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
493 private_key=None):
494 ssh_client = self.get_remote_client(ip_address,
495 private_key=private_key)
496 if dev_name is not None:
497 ssh_client.make_fs(dev_name)
498 ssh_client.mount(dev_name, mount_path)
499 cmd_timestamp = 'sudo sh -c "date > %s/timestamp; sync"' % mount_path
500 ssh_client.exec_command(cmd_timestamp)
501 timestamp = ssh_client.exec_command('sudo cat %s/timestamp'
502 % mount_path)
503 if dev_name is not None:
504 ssh_client.umount(mount_path)
505 return timestamp
506
507 def get_timestamp(self, ip_address, dev_name=None, mount_path='/mnt',
508 private_key=None):
509 ssh_client = self.get_remote_client(ip_address,
510 private_key=private_key)
511 if dev_name is not None:
512 ssh_client.mount(dev_name, mount_path)
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_server_ip(self, server):
520 """Get the server fixed or floating IP.
521
522 Based on the configuration we're in, return a correct ip
523 address for validating that a guest is up.
524 """
525 if CONF.validation.connect_method == 'floating':
526 # The tests calling this method don't have a floating IP
527 # and can't make use of the validation resources. So the
528 # method is creating the floating IP there.
529 return self.create_floating_ip(server)['ip']
530 elif CONF.validation.connect_method == 'fixed':
531 # Determine the network name to look for based on config or creds
532 # provider network resources.
533 if CONF.validation.network_for_ssh:
534 addresses = server['addresses'][
535 CONF.validation.network_for_ssh]
536 else:
537 creds_provider = self._get_credentials_provider()
538 net_creds = creds_provider.get_primary_creds()
539 network = getattr(net_creds, 'network', None)
540 addresses = (server['addresses'][network['name']]
541 if network else [])
542 for address in addresses:
543 if (address['version'] == CONF.validation.ip_version_for_ssh
544 and address['OS-EXT-IPS:type'] == 'fixed'):
545 return address['addr']
546 raise exceptions.ServerUnreachable(server_id=server['id'])
547 else:
548 raise lib_exc.InvalidConfiguration()
549
550
551class NetworkScenarioTest(ScenarioTest):
552 """Base class for network scenario tests.
553
554 This class provide helpers for network scenario tests, using the neutron
555 API. Helpers from ancestor which use the nova network API are overridden
556 with the neutron API.
557
558 This Class also enforces using Neutron instead of novanetwork.
559 Subclassed tests will be skipped if Neutron is not enabled
560
561 """
562
563 credentials = ['primary', 'admin']
564
565 @classmethod
566 def skip_checks(cls):
567 super(NetworkScenarioTest, cls).skip_checks()
568 if not CONF.service_available.neutron:
569 raise cls.skipException('Neutron not available')
570
571 def _create_network(self, networks_client=None,
572 tenant_id=None,
573 namestart='network-smoke-',
574 port_security_enabled=True):
575 if not networks_client:
576 networks_client = self.networks_client
577 if not tenant_id:
578 tenant_id = networks_client.tenant_id
579 name = data_utils.rand_name(namestart)
580 network_kwargs = dict(name=name, tenant_id=tenant_id)
581 # Neutron disables port security by default so we have to check the
582 # config before trying to create the network with port_security_enabled
583 if CONF.network_feature_enabled.port_security:
584 network_kwargs['port_security_enabled'] = port_security_enabled
585 result = networks_client.create_network(**network_kwargs)
586 network = result['network']
587
588 self.assertEqual(network['name'], name)
589 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
590 networks_client.delete_network,
591 network['id'])
592 return network
593
594 def _create_subnet(self, network, subnets_client=None,
595 routers_client=None, namestart='subnet-smoke',
596 **kwargs):
597 """Create a subnet for the given network
598
599 within the cidr block configured for tenant networks.
600 """
601 if not subnets_client:
602 subnets_client = self.subnets_client
603 if not routers_client:
604 routers_client = self.routers_client
605
606 def cidr_in_use(cidr, tenant_id):
607 """Check cidr existence
608
609 :returns: True if subnet with cidr already exist in tenant
610 False else
611 """
Vu Cong Tuan99751862017-06-23 19:46:40 +0700612 cidr_in_use = self.os_admin.subnets_client.list_subnets(
Andrea Frittolif4510a12017-03-07 19:17:11 +0000613 tenant_id=tenant_id, cidr=cidr)['subnets']
614 return len(cidr_in_use) != 0
615
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200616 def _make_create_subnet_request(namestart, network,
617 ip_version, subnets_client, **kwargs):
Andrea Frittolif4510a12017-03-07 19:17:11 +0000618
619 subnet = dict(
620 name=data_utils.rand_name(namestart),
621 network_id=network['id'],
622 tenant_id=network['tenant_id'],
Andrea Frittolif4510a12017-03-07 19:17:11 +0000623 ip_version=ip_version,
624 **kwargs
625 )
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200626
627 if ip_version == 6:
628 subnet['ipv6_address_mode'] = 'slaac'
629 subnet['ipv6_ra_mode'] = 'slaac'
630
Andrea Frittolif4510a12017-03-07 19:17:11 +0000631 try:
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200632 return subnets_client.create_subnet(**subnet)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000633 except lib_exc.Conflict as e:
634 if 'overlaps with another subnet' not in six.text_type(e):
635 raise
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200636
637 result = None
638 str_cidr = None
639
640 use_default_subnetpool = kwargs.get('use_default_subnetpool', False)
641
642 ip_version = kwargs.pop('ip_version', 4)
643
644 if not use_default_subnetpool:
645
646 if ip_version == 6:
647 tenant_cidr = netaddr.IPNetwork(
648 CONF.network.project_network_v6_cidr)
649 num_bits = CONF.network.project_network_v6_mask_bits
650 else:
651 tenant_cidr = netaddr.IPNetwork(
652 CONF.network.project_network_cidr)
653 num_bits = CONF.network.project_network_mask_bits
654
655 # Repeatedly attempt subnet creation with sequential cidr
656 # blocks until an unallocated block is found.
657 for subnet_cidr in tenant_cidr.subnet(num_bits):
658 str_cidr = str(subnet_cidr)
659 if cidr_in_use(str_cidr, tenant_id=network['tenant_id']):
660 continue
661
662 result = _make_create_subnet_request(
663 namestart, network, ip_version, subnets_client,
664 cidr=str_cidr, **kwargs)
665 if result is not None:
666 break
667 else:
668 result = _make_create_subnet_request(
669 namestart, network, ip_version, subnets_client,
670 **kwargs)
671
672 self.assertIsNotNone(result)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000673
674 subnet = result['subnet']
Rodrigo Barbieri797257e2017-11-21 11:00:45 -0200675 if str_cidr is not None:
676 self.assertEqual(subnet['cidr'], str_cidr)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000677
678 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
679 subnets_client.delete_subnet, subnet['id'])
680
681 return subnet
682
683 def _get_server_port_id_and_ip4(self, server, ip_addr=None):
Rockyd3432662019-07-02 14:58:30 +1000684 if ip_addr:
685 ports = self.os_admin.ports_client.list_ports(
686 device_id=server['id'],
687 fixed_ips='ip_address=%s' % ip_addr)['ports']
688 else:
689 ports = self.os_admin.ports_client.list_ports(
690 device_id=server['id'])['ports']
Andrea Frittolif4510a12017-03-07 19:17:11 +0000691 # A port can have more than one IP address in some cases.
692 # If the network is dual-stack (IPv4 + IPv6), this port is associated
693 # with 2 subnets
Rockyd3432662019-07-02 14:58:30 +1000694
695 def _is_active(port):
696 # NOTE(vsaienko) With Ironic, instances live on separate hardware
697 # servers. Neutron does not bind ports for Ironic instances, as a
698 # result the port remains in the DOWN state. This has been fixed
699 # with the introduction of the networking-baremetal plugin but
700 # it's not mandatory (and is not used on all stable branches).
701 return (port['status'] == 'ACTIVE' or
702 port.get('binding:vnic_type') == 'baremetal')
703
Andrea Frittolif4510a12017-03-07 19:17:11 +0000704 port_map = [(p["id"], fxip["ip_address"])
705 for p in ports
706 for fxip in p["fixed_ips"]
Rockyd3432662019-07-02 14:58:30 +1000707 if (netutils.is_valid_ipv4(fxip["ip_address"]) and
708 _is_active(p))]
Andrea Frittolif4510a12017-03-07 19:17:11 +0000709 inactive = [p for p in ports if p['status'] != 'ACTIVE']
710 if inactive:
711 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
712
Rockyd3432662019-07-02 14:58:30 +1000713 self.assertNotEmpty(port_map,
Andrea Frittolif4510a12017-03-07 19:17:11 +0000714 "No IPv4 addresses found in: %s" % ports)
715 self.assertEqual(len(port_map), 1,
716 "Found multiple IPv4 addresses: %s. "
717 "Unable to determine which port to target."
718 % port_map)
719 return port_map[0]
720
Goutham Pacha Ravi37ee6772019-10-18 12:53:22 -0700721 def _get_network_by_name_or_id(self, identifier):
722
723 if uuidutils.is_uuid_like(identifier):
724 return self.os_admin.networks_client.show_network(
725 identifier)['network']
726
727 networks = self.os_admin.networks_client.list_networks(
728 name=identifier)['networks']
729 self.assertNotEqual(len(networks), 0,
730 "Unable to get network by name: %s" % identifier)
731 return networks[0]
732
733 def get_networks(self):
734 return self.os_admin.networks_client.list_networks()['networks']
Andrea Frittolif4510a12017-03-07 19:17:11 +0000735
736 def create_floating_ip(self, thing, external_network_id=None,
lkuchlan7636a1f2020-04-30 16:13:13 +0300737 port_id=None, ip_addr=None, client=None):
Andrea Frittolif4510a12017-03-07 19:17:11 +0000738 """Create a floating IP and associates to a resource/port on Neutron"""
739 if not external_network_id:
740 external_network_id = CONF.network.public_network_id
741 if not client:
742 client = self.floating_ips_client
743 if not port_id:
lkuchlan7636a1f2020-04-30 16:13:13 +0300744 port_id, ip4 = self._get_server_port_id_and_ip4(thing,
745 ip_addr=ip_addr)
Andrea Frittolif4510a12017-03-07 19:17:11 +0000746 else:
747 ip4 = None
748 result = client.create_floatingip(
749 floating_network_id=external_network_id,
750 port_id=port_id,
751 tenant_id=thing['tenant_id'],
752 fixed_ip_address=ip4
753 )
754 floating_ip = result['floatingip']
755 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
756 client.delete_floatingip,
757 floating_ip['id'])
758 return floating_ip
759
760 def _associate_floating_ip(self, floating_ip, server):
761 port_id, _ = self._get_server_port_id_and_ip4(server)
762 kwargs = dict(port_id=port_id)
763 floating_ip = self.floating_ips_client.update_floatingip(
764 floating_ip['id'], **kwargs)['floatingip']
765 self.assertEqual(port_id, floating_ip['port_id'])
766 return floating_ip
767
768 def _disassociate_floating_ip(self, floating_ip):
769 """:param floating_ip: floating_ips_client.create_floatingip"""
770 kwargs = dict(port_id=None)
771 floating_ip = self.floating_ips_client.update_floatingip(
772 floating_ip['id'], **kwargs)['floatingip']
773 self.assertIsNone(floating_ip['port_id'])
774 return floating_ip
775
776 def check_floating_ip_status(self, floating_ip, status):
777 """Verifies floatingip reaches the given status
778
779 :param dict floating_ip: floating IP dict to check status
780 :param status: target status
781 :raises: AssertionError if status doesn't match
782 """
783 floatingip_id = floating_ip['id']
784
785 def refresh():
786 result = (self.floating_ips_client.
787 show_floatingip(floatingip_id)['floatingip'])
788 return status == result['status']
789
790 test_utils.call_until_true(refresh,
791 CONF.network.build_timeout,
792 CONF.network.build_interval)
793 floating_ip = self.floating_ips_client.show_floatingip(
794 floatingip_id)['floatingip']
795 self.assertEqual(status, floating_ip['status'],
796 message="FloatingIP: {fp} is at status: {cst}. "
797 "failed to reach status: {st}"
798 .format(fp=floating_ip, cst=floating_ip['status'],
799 st=status))
800 LOG.info("FloatingIP: {fp} is at status: {st}"
801 .format(fp=floating_ip, st=status))
802
803 def _check_tenant_network_connectivity(self, server,
804 username,
805 private_key,
806 should_connect=True,
807 servers_for_debug=None):
808 if not CONF.network.project_networks_reachable:
809 msg = 'Tenant networks not configured to be reachable.'
810 LOG.info(msg)
811 return
812 # The target login is assumed to have been configured for
813 # key-based authentication by cloud-init.
814 try:
815 for ip_addresses in server['addresses'].values():
816 for ip_address in ip_addresses:
817 self.check_vm_connectivity(ip_address['addr'],
818 username,
819 private_key,
820 should_connect=should_connect)
821 except Exception as e:
822 LOG.exception('Tenant network connectivity check failed')
823 self._log_console_output(servers_for_debug)
824 self._log_net_info(e)
825 raise
826
827 def _check_remote_connectivity(self, source, dest, should_succeed=True,
828 nic=None):
829 """assert ping server via source ssh connection
830
831 Note: This is an internal method. Use check_remote_connectivity
832 instead.
833
834 :param source: RemoteClient: an ssh connection from which to ping
835 :param dest: and IP to ping against
836 :param should_succeed: boolean should ping succeed or not
837 :param nic: specific network interface to ping from
838 """
839 def ping_remote():
840 try:
841 source.ping_host(dest, nic=nic)
842 except lib_exc.SSHExecCommandFailed:
843 LOG.warning('Failed to ping IP: %s via a ssh connection '
844 'from: %s.', dest, source.ssh_client.host)
845 return not should_succeed
846 return should_succeed
847
848 return test_utils.call_until_true(ping_remote,
849 CONF.validation.ping_timeout,
850 1)
851
852 def check_remote_connectivity(self, source, dest, should_succeed=True,
853 nic=None):
854 """assert ping server via source ssh connection
855
856 :param source: RemoteClient: an ssh connection from which to ping
857 :param dest: and IP to ping against
858 :param should_succeed: boolean should ping succeed or not
859 :param nic: specific network interface to ping from
860 """
861 result = self._check_remote_connectivity(source, dest, should_succeed,
862 nic)
863 source_host = source.ssh_client.host
864 if should_succeed:
zhongjun39e9c582017-06-21 15:17:11 +0800865 msg = ("Timed out waiting for %s to become reachable from %s"
866 % (dest, source_host))
Andrea Frittolif4510a12017-03-07 19:17:11 +0000867 else:
868 msg = "%s is reachable from %s" % (dest, source_host)
869 self.assertTrue(result, msg)
870
871 def _create_security_group(self, security_group_rules_client=None,
872 tenant_id=None,
873 namestart='secgroup-smoke',
874 security_groups_client=None):
875 if security_group_rules_client is None:
876 security_group_rules_client = self.security_group_rules_client
877 if security_groups_client is None:
878 security_groups_client = self.security_groups_client
879 if tenant_id is None:
880 tenant_id = security_groups_client.tenant_id
881 secgroup = self._create_empty_security_group(
882 namestart=namestart, client=security_groups_client,
883 tenant_id=tenant_id)
884
885 # Add rules to the security group
886 rules = self._create_loginable_secgroup_rule(
887 security_group_rules_client=security_group_rules_client,
888 secgroup=secgroup,
889 security_groups_client=security_groups_client)
890 for rule in rules:
891 self.assertEqual(tenant_id, rule['tenant_id'])
892 self.assertEqual(secgroup['id'], rule['security_group_id'])
893 return secgroup
894
895 def _create_empty_security_group(self, client=None, tenant_id=None,
896 namestart='secgroup-smoke'):
897 """Create a security group without rules.
898
899 Default rules will be created:
900 - IPv4 egress to any
901 - IPv6 egress to any
902
903 :param tenant_id: secgroup will be created in this tenant
904 :returns: the created security group
905 """
906 if client is None:
907 client = self.security_groups_client
908 if not tenant_id:
909 tenant_id = client.tenant_id
910 sg_name = data_utils.rand_name(namestart)
911 sg_desc = sg_name + " description"
912 sg_dict = dict(name=sg_name,
913 description=sg_desc)
914 sg_dict['tenant_id'] = tenant_id
915 result = client.create_security_group(**sg_dict)
916
917 secgroup = result['security_group']
918 self.assertEqual(secgroup['name'], sg_name)
919 self.assertEqual(tenant_id, secgroup['tenant_id'])
920 self.assertEqual(secgroup['description'], sg_desc)
921
922 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
923 client.delete_security_group, secgroup['id'])
924 return secgroup
925
926 def _default_security_group(self, client=None, tenant_id=None):
927 """Get default secgroup for given tenant_id.
928
929 :returns: default secgroup for given tenant
930 """
931 if client is None:
932 client = self.security_groups_client
933 if not tenant_id:
934 tenant_id = client.tenant_id
935 sgs = [
936 sg for sg in list(client.list_security_groups().values())[0]
937 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
938 ]
939 msg = "No default security group for tenant %s." % (tenant_id)
940 self.assertGreater(len(sgs), 0, msg)
941 return sgs[0]
942
943 def _create_security_group_rule(self, secgroup=None,
944 sec_group_rules_client=None,
945 tenant_id=None,
946 security_groups_client=None, **kwargs):
947 """Create a rule from a dictionary of rule parameters.
948
949 Create a rule in a secgroup. if secgroup not defined will search for
950 default secgroup in tenant_id.
951
952 :param secgroup: the security group.
953 :param tenant_id: if secgroup not passed -- the tenant in which to
954 search for default secgroup
955 :param kwargs: a dictionary containing rule parameters:
956 for example, to allow incoming ssh:
957 rule = {
958 direction: 'ingress'
959 protocol:'tcp',
960 port_range_min: 22,
961 port_range_max: 22
962 }
963 """
964 if sec_group_rules_client is None:
965 sec_group_rules_client = self.security_group_rules_client
966 if security_groups_client is None:
967 security_groups_client = self.security_groups_client
968 if not tenant_id:
969 tenant_id = security_groups_client.tenant_id
970 if secgroup is None:
971 secgroup = self._default_security_group(
972 client=security_groups_client, tenant_id=tenant_id)
973
974 ruleset = dict(security_group_id=secgroup['id'],
975 tenant_id=secgroup['tenant_id'])
976 ruleset.update(kwargs)
977
978 sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
979 sg_rule = sg_rule['security_group_rule']
980
981 self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
982 self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
983
984 return sg_rule
985
986 def _create_loginable_secgroup_rule(self, security_group_rules_client=None,
987 secgroup=None,
988 security_groups_client=None):
989 """Create loginable security group rule
990
991 This function will create:
992 1. egress and ingress tcp port 22 allow rule in order to allow ssh
993 access for ipv4.
994 2. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
995 3. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
996 """
997
998 if security_group_rules_client is None:
999 security_group_rules_client = self.security_group_rules_client
1000 if security_groups_client is None:
1001 security_groups_client = self.security_groups_client
1002 rules = []
1003 rulesets = [
1004 dict(
1005 # ssh
1006 protocol='tcp',
1007 port_range_min=22,
1008 port_range_max=22,
1009 ),
1010 dict(
Rodrigo Barbieri797257e2017-11-21 11:00:45 -02001011 # ipv6-ssh
1012 protocol='tcp',
1013 port_range_min=22,
1014 port_range_max=22,
1015 ethertype='IPv6',
1016 ),
1017 dict(
Andrea Frittolif4510a12017-03-07 19:17:11 +00001018 # ping
1019 protocol='icmp',
1020 ),
1021 dict(
1022 # ipv6-icmp for ping6
1023 protocol='icmp',
1024 ethertype='IPv6',
1025 )
1026 ]
1027 sec_group_rules_client = security_group_rules_client
1028 for ruleset in rulesets:
1029 for r_direction in ['ingress', 'egress']:
1030 ruleset['direction'] = r_direction
1031 try:
1032 sg_rule = self._create_security_group_rule(
1033 sec_group_rules_client=sec_group_rules_client,
1034 secgroup=secgroup,
1035 security_groups_client=security_groups_client,
1036 **ruleset)
1037 except lib_exc.Conflict as ex:
1038 # if rule already exist - skip rule and continue
1039 msg = 'Security group rule already exists'
1040 if msg not in ex._error_string:
1041 raise ex
1042 else:
1043 self.assertEqual(r_direction, sg_rule['direction'])
1044 rules.append(sg_rule)
1045
1046 return rules
1047
1048 def _get_router(self, client=None, tenant_id=None):
1049 """Retrieve a router for the given tenant id.
1050
1051 If a public router has been configured, it will be returned.
1052
1053 If a public router has not been configured, but a public
1054 network has, a tenant router will be created and returned that
1055 routes traffic to the public network.
1056 """
1057 if not client:
1058 client = self.routers_client
1059 if not tenant_id:
1060 tenant_id = client.tenant_id
1061 router_id = CONF.network.public_router_id
1062 network_id = CONF.network.public_network_id
1063 if router_id:
1064 body = client.show_router(router_id)
1065 return body['router']
1066 elif network_id:
1067 router = self._create_router(client, tenant_id)
1068 kwargs = {'external_gateway_info': dict(network_id=network_id)}
1069 router = client.update_router(router['id'], **kwargs)['router']
1070 return router
1071 else:
1072 raise Exception("Neither of 'public_router_id' or "
1073 "'public_network_id' has been defined.")
1074
1075 def _create_router(self, client=None, tenant_id=None,
1076 namestart='router-smoke'):
1077 if not client:
1078 client = self.routers_client
1079 if not tenant_id:
1080 tenant_id = client.tenant_id
1081 name = data_utils.rand_name(namestart)
1082 result = client.create_router(name=name,
1083 admin_state_up=True,
1084 tenant_id=tenant_id)
1085 router = result['router']
1086 self.assertEqual(router['name'], name)
1087 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1088 client.delete_router,
1089 router['id'])
1090 return router
1091
1092 def _update_router_admin_state(self, router, admin_state_up):
1093 kwargs = dict(admin_state_up=admin_state_up)
1094 router = self.routers_client.update_router(
1095 router['id'], **kwargs)['router']
1096 self.assertEqual(admin_state_up, router['admin_state_up'])
1097
1098 def create_networks(self, networks_client=None,
1099 routers_client=None, subnets_client=None,
1100 tenant_id=None, dns_nameservers=None,
1101 port_security_enabled=True):
1102 """Create a network with a subnet connected to a router.
1103
1104 The baremetal driver is a special case since all nodes are
1105 on the same shared network.
1106
1107 :param tenant_id: id of tenant to create resources in.
1108 :param dns_nameservers: list of dns servers to send to subnet.
1109 :returns: network, subnet, router
1110 """
1111 if CONF.network.shared_physical_network:
1112 # NOTE(Shrews): This exception is for environments where tenant
1113 # credential isolation is available, but network separation is
1114 # not (the current baremetal case). Likely can be removed when
1115 # test account mgmt is reworked:
1116 # https://blueprints.launchpad.net/tempest/+spec/test-accounts
1117 if not CONF.compute.fixed_network_name:
1118 m = 'fixed_network_name must be specified in config'
1119 raise lib_exc.InvalidConfiguration(m)
Goutham Pacha Ravi37ee6772019-10-18 12:53:22 -07001120 network = self._get_network_by_name_or_id(
Andrea Frittolif4510a12017-03-07 19:17:11 +00001121 CONF.compute.fixed_network_name)
1122 router = None
1123 subnet = None
1124 else:
1125 network = self._create_network(
1126 networks_client=networks_client,
1127 tenant_id=tenant_id,
1128 port_security_enabled=port_security_enabled)
1129 router = self._get_router(client=routers_client,
1130 tenant_id=tenant_id)
1131 subnet_kwargs = dict(network=network,
1132 subnets_client=subnets_client,
1133 routers_client=routers_client)
1134 # use explicit check because empty list is a valid option
1135 if dns_nameservers is not None:
1136 subnet_kwargs['dns_nameservers'] = dns_nameservers
1137 subnet = self._create_subnet(**subnet_kwargs)
1138 if not routers_client:
1139 routers_client = self.routers_client
1140 router_id = router['id']
1141 routers_client.add_router_interface(router_id,
1142 subnet_id=subnet['id'])
1143
1144 # save a cleanup job to remove this association between
1145 # router and subnet
1146 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1147 routers_client.remove_router_interface, router_id,
1148 subnet_id=subnet['id'])
1149 return network, subnet, router