blob: 6e8a2f6ed709c2b9291ceaf2d393f5014ff94c5a [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):
Vu Cong Tuan99751862017-06-23 19:46:40 +0700684 ports = self.os_admin.ports_client.list_ports(
Andrea Frittolif4510a12017-03-07 19:17:11 +0000685 device_id=server['id'], fixed_ip=ip_addr)['ports']
686 # A port can have more than one IP address in some cases.
687 # If the network is dual-stack (IPv4 + IPv6), this port is associated
688 # with 2 subnets
689 p_status = ['ACTIVE']
690 # NOTE(vsaienko) With Ironic, instances live on separate hardware
691 # servers. Neutron does not bind ports for Ironic instances, as a
692 # result the port remains in the DOWN state.
693 # TODO(vsaienko) remove once bug: #1599836 is resolved.
694 if getattr(CONF.service_available, 'ironic', False):
695 p_status.append('DOWN')
696 port_map = [(p["id"], fxip["ip_address"])
697 for p in ports
698 for fxip in p["fixed_ips"]
699 if netutils.is_valid_ipv4(fxip["ip_address"])
700 and p['status'] in p_status]
701 inactive = [p for p in ports if p['status'] != 'ACTIVE']
702 if inactive:
703 LOG.warning("Instance has ports that are not ACTIVE: %s", inactive)
704
705 self.assertNotEqual(0, len(port_map),
706 "No IPv4 addresses found in: %s" % ports)
707 self.assertEqual(len(port_map), 1,
708 "Found multiple IPv4 addresses: %s. "
709 "Unable to determine which port to target."
710 % port_map)
711 return port_map[0]
712
Goutham Pacha Ravi37ee6772019-10-18 12:53:22 -0700713 def _get_network_by_name_or_id(self, identifier):
714
715 if uuidutils.is_uuid_like(identifier):
716 return self.os_admin.networks_client.show_network(
717 identifier)['network']
718
719 networks = self.os_admin.networks_client.list_networks(
720 name=identifier)['networks']
721 self.assertNotEqual(len(networks), 0,
722 "Unable to get network by name: %s" % identifier)
723 return networks[0]
724
725 def get_networks(self):
726 return self.os_admin.networks_client.list_networks()['networks']
Andrea Frittolif4510a12017-03-07 19:17:11 +0000727
728 def create_floating_ip(self, thing, external_network_id=None,
729 port_id=None, client=None):
730 """Create a floating IP and associates to a resource/port on Neutron"""
731 if not external_network_id:
732 external_network_id = CONF.network.public_network_id
733 if not client:
734 client = self.floating_ips_client
735 if not port_id:
736 port_id, ip4 = self._get_server_port_id_and_ip4(thing)
737 else:
738 ip4 = None
739 result = client.create_floatingip(
740 floating_network_id=external_network_id,
741 port_id=port_id,
742 tenant_id=thing['tenant_id'],
743 fixed_ip_address=ip4
744 )
745 floating_ip = result['floatingip']
746 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
747 client.delete_floatingip,
748 floating_ip['id'])
749 return floating_ip
750
751 def _associate_floating_ip(self, floating_ip, server):
752 port_id, _ = self._get_server_port_id_and_ip4(server)
753 kwargs = dict(port_id=port_id)
754 floating_ip = self.floating_ips_client.update_floatingip(
755 floating_ip['id'], **kwargs)['floatingip']
756 self.assertEqual(port_id, floating_ip['port_id'])
757 return floating_ip
758
759 def _disassociate_floating_ip(self, floating_ip):
760 """:param floating_ip: floating_ips_client.create_floatingip"""
761 kwargs = dict(port_id=None)
762 floating_ip = self.floating_ips_client.update_floatingip(
763 floating_ip['id'], **kwargs)['floatingip']
764 self.assertIsNone(floating_ip['port_id'])
765 return floating_ip
766
767 def check_floating_ip_status(self, floating_ip, status):
768 """Verifies floatingip reaches the given status
769
770 :param dict floating_ip: floating IP dict to check status
771 :param status: target status
772 :raises: AssertionError if status doesn't match
773 """
774 floatingip_id = floating_ip['id']
775
776 def refresh():
777 result = (self.floating_ips_client.
778 show_floatingip(floatingip_id)['floatingip'])
779 return status == result['status']
780
781 test_utils.call_until_true(refresh,
782 CONF.network.build_timeout,
783 CONF.network.build_interval)
784 floating_ip = self.floating_ips_client.show_floatingip(
785 floatingip_id)['floatingip']
786 self.assertEqual(status, floating_ip['status'],
787 message="FloatingIP: {fp} is at status: {cst}. "
788 "failed to reach status: {st}"
789 .format(fp=floating_ip, cst=floating_ip['status'],
790 st=status))
791 LOG.info("FloatingIP: {fp} is at status: {st}"
792 .format(fp=floating_ip, st=status))
793
794 def _check_tenant_network_connectivity(self, server,
795 username,
796 private_key,
797 should_connect=True,
798 servers_for_debug=None):
799 if not CONF.network.project_networks_reachable:
800 msg = 'Tenant networks not configured to be reachable.'
801 LOG.info(msg)
802 return
803 # The target login is assumed to have been configured for
804 # key-based authentication by cloud-init.
805 try:
806 for ip_addresses in server['addresses'].values():
807 for ip_address in ip_addresses:
808 self.check_vm_connectivity(ip_address['addr'],
809 username,
810 private_key,
811 should_connect=should_connect)
812 except Exception as e:
813 LOG.exception('Tenant network connectivity check failed')
814 self._log_console_output(servers_for_debug)
815 self._log_net_info(e)
816 raise
817
818 def _check_remote_connectivity(self, source, dest, should_succeed=True,
819 nic=None):
820 """assert ping server via source ssh connection
821
822 Note: This is an internal method. Use check_remote_connectivity
823 instead.
824
825 :param source: RemoteClient: an ssh connection from which to ping
826 :param dest: and IP to ping against
827 :param should_succeed: boolean should ping succeed or not
828 :param nic: specific network interface to ping from
829 """
830 def ping_remote():
831 try:
832 source.ping_host(dest, nic=nic)
833 except lib_exc.SSHExecCommandFailed:
834 LOG.warning('Failed to ping IP: %s via a ssh connection '
835 'from: %s.', dest, source.ssh_client.host)
836 return not should_succeed
837 return should_succeed
838
839 return test_utils.call_until_true(ping_remote,
840 CONF.validation.ping_timeout,
841 1)
842
843 def check_remote_connectivity(self, source, dest, should_succeed=True,
844 nic=None):
845 """assert ping server via source ssh connection
846
847 :param source: RemoteClient: an ssh connection from which to ping
848 :param dest: and IP to ping against
849 :param should_succeed: boolean should ping succeed or not
850 :param nic: specific network interface to ping from
851 """
852 result = self._check_remote_connectivity(source, dest, should_succeed,
853 nic)
854 source_host = source.ssh_client.host
855 if should_succeed:
zhongjun39e9c582017-06-21 15:17:11 +0800856 msg = ("Timed out waiting for %s to become reachable from %s"
857 % (dest, source_host))
Andrea Frittolif4510a12017-03-07 19:17:11 +0000858 else:
859 msg = "%s is reachable from %s" % (dest, source_host)
860 self.assertTrue(result, msg)
861
862 def _create_security_group(self, security_group_rules_client=None,
863 tenant_id=None,
864 namestart='secgroup-smoke',
865 security_groups_client=None):
866 if security_group_rules_client is None:
867 security_group_rules_client = self.security_group_rules_client
868 if security_groups_client is None:
869 security_groups_client = self.security_groups_client
870 if tenant_id is None:
871 tenant_id = security_groups_client.tenant_id
872 secgroup = self._create_empty_security_group(
873 namestart=namestart, client=security_groups_client,
874 tenant_id=tenant_id)
875
876 # Add rules to the security group
877 rules = self._create_loginable_secgroup_rule(
878 security_group_rules_client=security_group_rules_client,
879 secgroup=secgroup,
880 security_groups_client=security_groups_client)
881 for rule in rules:
882 self.assertEqual(tenant_id, rule['tenant_id'])
883 self.assertEqual(secgroup['id'], rule['security_group_id'])
884 return secgroup
885
886 def _create_empty_security_group(self, client=None, tenant_id=None,
887 namestart='secgroup-smoke'):
888 """Create a security group without rules.
889
890 Default rules will be created:
891 - IPv4 egress to any
892 - IPv6 egress to any
893
894 :param tenant_id: secgroup will be created in this tenant
895 :returns: the created security group
896 """
897 if client is None:
898 client = self.security_groups_client
899 if not tenant_id:
900 tenant_id = client.tenant_id
901 sg_name = data_utils.rand_name(namestart)
902 sg_desc = sg_name + " description"
903 sg_dict = dict(name=sg_name,
904 description=sg_desc)
905 sg_dict['tenant_id'] = tenant_id
906 result = client.create_security_group(**sg_dict)
907
908 secgroup = result['security_group']
909 self.assertEqual(secgroup['name'], sg_name)
910 self.assertEqual(tenant_id, secgroup['tenant_id'])
911 self.assertEqual(secgroup['description'], sg_desc)
912
913 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
914 client.delete_security_group, secgroup['id'])
915 return secgroup
916
917 def _default_security_group(self, client=None, tenant_id=None):
918 """Get default secgroup for given tenant_id.
919
920 :returns: default secgroup for given tenant
921 """
922 if client is None:
923 client = self.security_groups_client
924 if not tenant_id:
925 tenant_id = client.tenant_id
926 sgs = [
927 sg for sg in list(client.list_security_groups().values())[0]
928 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
929 ]
930 msg = "No default security group for tenant %s." % (tenant_id)
931 self.assertGreater(len(sgs), 0, msg)
932 return sgs[0]
933
934 def _create_security_group_rule(self, secgroup=None,
935 sec_group_rules_client=None,
936 tenant_id=None,
937 security_groups_client=None, **kwargs):
938 """Create a rule from a dictionary of rule parameters.
939
940 Create a rule in a secgroup. if secgroup not defined will search for
941 default secgroup in tenant_id.
942
943 :param secgroup: the security group.
944 :param tenant_id: if secgroup not passed -- the tenant in which to
945 search for default secgroup
946 :param kwargs: a dictionary containing rule parameters:
947 for example, to allow incoming ssh:
948 rule = {
949 direction: 'ingress'
950 protocol:'tcp',
951 port_range_min: 22,
952 port_range_max: 22
953 }
954 """
955 if sec_group_rules_client is None:
956 sec_group_rules_client = self.security_group_rules_client
957 if security_groups_client is None:
958 security_groups_client = self.security_groups_client
959 if not tenant_id:
960 tenant_id = security_groups_client.tenant_id
961 if secgroup is None:
962 secgroup = self._default_security_group(
963 client=security_groups_client, tenant_id=tenant_id)
964
965 ruleset = dict(security_group_id=secgroup['id'],
966 tenant_id=secgroup['tenant_id'])
967 ruleset.update(kwargs)
968
969 sg_rule = sec_group_rules_client.create_security_group_rule(**ruleset)
970 sg_rule = sg_rule['security_group_rule']
971
972 self.assertEqual(secgroup['tenant_id'], sg_rule['tenant_id'])
973 self.assertEqual(secgroup['id'], sg_rule['security_group_id'])
974
975 return sg_rule
976
977 def _create_loginable_secgroup_rule(self, security_group_rules_client=None,
978 secgroup=None,
979 security_groups_client=None):
980 """Create loginable security group rule
981
982 This function will create:
983 1. egress and ingress tcp port 22 allow rule in order to allow ssh
984 access for ipv4.
985 2. egress and ingress ipv6 icmp allow rule, in order to allow icmpv6.
986 3. egress and ingress ipv4 icmp allow rule, in order to allow icmpv4.
987 """
988
989 if security_group_rules_client is None:
990 security_group_rules_client = self.security_group_rules_client
991 if security_groups_client is None:
992 security_groups_client = self.security_groups_client
993 rules = []
994 rulesets = [
995 dict(
996 # ssh
997 protocol='tcp',
998 port_range_min=22,
999 port_range_max=22,
1000 ),
1001 dict(
Rodrigo Barbieri797257e2017-11-21 11:00:45 -02001002 # ipv6-ssh
1003 protocol='tcp',
1004 port_range_min=22,
1005 port_range_max=22,
1006 ethertype='IPv6',
1007 ),
1008 dict(
Andrea Frittolif4510a12017-03-07 19:17:11 +00001009 # ping
1010 protocol='icmp',
1011 ),
1012 dict(
1013 # ipv6-icmp for ping6
1014 protocol='icmp',
1015 ethertype='IPv6',
1016 )
1017 ]
1018 sec_group_rules_client = security_group_rules_client
1019 for ruleset in rulesets:
1020 for r_direction in ['ingress', 'egress']:
1021 ruleset['direction'] = r_direction
1022 try:
1023 sg_rule = self._create_security_group_rule(
1024 sec_group_rules_client=sec_group_rules_client,
1025 secgroup=secgroup,
1026 security_groups_client=security_groups_client,
1027 **ruleset)
1028 except lib_exc.Conflict as ex:
1029 # if rule already exist - skip rule and continue
1030 msg = 'Security group rule already exists'
1031 if msg not in ex._error_string:
1032 raise ex
1033 else:
1034 self.assertEqual(r_direction, sg_rule['direction'])
1035 rules.append(sg_rule)
1036
1037 return rules
1038
1039 def _get_router(self, client=None, tenant_id=None):
1040 """Retrieve a router for the given tenant id.
1041
1042 If a public router has been configured, it will be returned.
1043
1044 If a public router has not been configured, but a public
1045 network has, a tenant router will be created and returned that
1046 routes traffic to the public network.
1047 """
1048 if not client:
1049 client = self.routers_client
1050 if not tenant_id:
1051 tenant_id = client.tenant_id
1052 router_id = CONF.network.public_router_id
1053 network_id = CONF.network.public_network_id
1054 if router_id:
1055 body = client.show_router(router_id)
1056 return body['router']
1057 elif network_id:
1058 router = self._create_router(client, tenant_id)
1059 kwargs = {'external_gateway_info': dict(network_id=network_id)}
1060 router = client.update_router(router['id'], **kwargs)['router']
1061 return router
1062 else:
1063 raise Exception("Neither of 'public_router_id' or "
1064 "'public_network_id' has been defined.")
1065
1066 def _create_router(self, client=None, tenant_id=None,
1067 namestart='router-smoke'):
1068 if not client:
1069 client = self.routers_client
1070 if not tenant_id:
1071 tenant_id = client.tenant_id
1072 name = data_utils.rand_name(namestart)
1073 result = client.create_router(name=name,
1074 admin_state_up=True,
1075 tenant_id=tenant_id)
1076 router = result['router']
1077 self.assertEqual(router['name'], name)
1078 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1079 client.delete_router,
1080 router['id'])
1081 return router
1082
1083 def _update_router_admin_state(self, router, admin_state_up):
1084 kwargs = dict(admin_state_up=admin_state_up)
1085 router = self.routers_client.update_router(
1086 router['id'], **kwargs)['router']
1087 self.assertEqual(admin_state_up, router['admin_state_up'])
1088
1089 def create_networks(self, networks_client=None,
1090 routers_client=None, subnets_client=None,
1091 tenant_id=None, dns_nameservers=None,
1092 port_security_enabled=True):
1093 """Create a network with a subnet connected to a router.
1094
1095 The baremetal driver is a special case since all nodes are
1096 on the same shared network.
1097
1098 :param tenant_id: id of tenant to create resources in.
1099 :param dns_nameservers: list of dns servers to send to subnet.
1100 :returns: network, subnet, router
1101 """
1102 if CONF.network.shared_physical_network:
1103 # NOTE(Shrews): This exception is for environments where tenant
1104 # credential isolation is available, but network separation is
1105 # not (the current baremetal case). Likely can be removed when
1106 # test account mgmt is reworked:
1107 # https://blueprints.launchpad.net/tempest/+spec/test-accounts
1108 if not CONF.compute.fixed_network_name:
1109 m = 'fixed_network_name must be specified in config'
1110 raise lib_exc.InvalidConfiguration(m)
Goutham Pacha Ravi37ee6772019-10-18 12:53:22 -07001111 network = self._get_network_by_name_or_id(
Andrea Frittolif4510a12017-03-07 19:17:11 +00001112 CONF.compute.fixed_network_name)
1113 router = None
1114 subnet = None
1115 else:
1116 network = self._create_network(
1117 networks_client=networks_client,
1118 tenant_id=tenant_id,
1119 port_security_enabled=port_security_enabled)
1120 router = self._get_router(client=routers_client,
1121 tenant_id=tenant_id)
1122 subnet_kwargs = dict(network=network,
1123 subnets_client=subnets_client,
1124 routers_client=routers_client)
1125 # use explicit check because empty list is a valid option
1126 if dns_nameservers is not None:
1127 subnet_kwargs['dns_nameservers'] = dns_nameservers
1128 subnet = self._create_subnet(**subnet_kwargs)
1129 if not routers_client:
1130 routers_client = self.routers_client
1131 router_id = router['id']
1132 routers_client.add_router_interface(router_id,
1133 subnet_id=subnet['id'])
1134
1135 # save a cleanup job to remove this association between
1136 # router and subnet
1137 self.addCleanup(test_utils.call_and_ignore_notfound_exc,
1138 routers_client.remove_router_interface, router_id,
1139 subnet_id=subnet['id'])
1140 return network, subnet, router