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