blob: dede47a37ff743874b2dc4094ab50426ba6072a2 [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,
737 port_id=None, client=None):
738 """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:
744 port_id, ip4 = self._get_server_port_id_and_ip4(thing)
745 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