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