Brianna Poulos | 011292a | 2017-03-15 16:24:38 -0400 | [diff] [blame] | 1 | # 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 | |
| 17 | from oslo_log import log |
| 18 | |
| 19 | from tempest.common import compute |
| 20 | from tempest.common import image as common_image |
| 21 | from tempest.common import waiters |
| 22 | from tempest import config |
| 23 | from tempest.lib.common.utils import data_utils |
| 24 | from tempest.lib.common.utils import test_utils |
| 25 | from tempest.lib import exceptions as lib_exc |
| 26 | import tempest.test |
| 27 | |
| 28 | CONF = config.CONF |
| 29 | |
| 30 | LOG = log.getLogger(__name__) |
| 31 | |
| 32 | |
| 33 | class ScenarioTest(tempest.test.BaseTestCase): |
| 34 | """Base class for scenario tests. Uses tempest own clients. """ |
| 35 | |
| 36 | credentials = ['primary'] |
| 37 | |
| 38 | @classmethod |
| 39 | def setup_clients(cls): |
| 40 | super(ScenarioTest, cls).setup_clients() |
| 41 | # Clients (in alphabetical order) |
| 42 | cls.flavors_client = cls.manager.flavors_client |
| 43 | cls.compute_floating_ips_client = ( |
| 44 | cls.manager.compute_floating_ips_client) |
| 45 | if CONF.service_available.glance: |
| 46 | # Check if glance v1 is available to determine which client to use. |
| 47 | if CONF.image_feature_enabled.api_v1: |
| 48 | cls.image_client = cls.manager.image_client |
| 49 | elif CONF.image_feature_enabled.api_v2: |
| 50 | cls.image_client = cls.manager.image_client_v2 |
| 51 | else: |
| 52 | raise lib_exc.InvalidConfiguration( |
| 53 | 'Either api_v1 or api_v2 must be True in ' |
| 54 | '[image-feature-enabled].') |
| 55 | # Compute image client |
| 56 | cls.compute_images_client = cls.manager.compute_images_client |
| 57 | cls.keypairs_client = cls.manager.keypairs_client |
| 58 | # Nova security groups client |
| 59 | cls.compute_security_groups_client = ( |
| 60 | cls.manager.compute_security_groups_client) |
| 61 | cls.compute_security_group_rules_client = ( |
| 62 | cls.manager.compute_security_group_rules_client) |
| 63 | cls.servers_client = cls.manager.servers_client |
| 64 | # Neutron network client |
| 65 | cls.networks_client = cls.manager.networks_client |
| 66 | cls.ports_client = cls.manager.ports_client |
| 67 | cls.routers_client = cls.manager.routers_client |
| 68 | cls.subnets_client = cls.manager.subnets_client |
| 69 | cls.floating_ips_client = cls.manager.floating_ips_client |
| 70 | cls.security_groups_client = cls.manager.security_groups_client |
| 71 | cls.security_group_rules_client = ( |
| 72 | cls.manager.security_group_rules_client) |
| 73 | |
| 74 | if CONF.volume_feature_enabled.api_v2: |
| 75 | cls.volumes_client = cls.manager.volumes_v2_client |
| 76 | cls.snapshots_client = cls.manager.snapshots_v2_client |
| 77 | else: |
| 78 | cls.volumes_client = cls.manager.volumes_client |
| 79 | cls.snapshots_client = cls.manager.snapshots_client |
| 80 | |
| 81 | # ## Test functions library |
| 82 | # |
| 83 | # The create_[resource] functions only return body and discard the |
| 84 | # resp part which is not used in scenario tests |
| 85 | |
| 86 | def _create_port(self, network_id, client=None, namestart='port-quotatest', |
| 87 | **kwargs): |
| 88 | if not client: |
| 89 | client = self.ports_client |
| 90 | name = data_utils.rand_name(namestart) |
| 91 | result = client.create_port( |
| 92 | name=name, |
| 93 | network_id=network_id, |
| 94 | **kwargs) |
| 95 | self.assertIsNotNone(result, 'Unable to allocate port') |
| 96 | port = result['port'] |
| 97 | self.addCleanup(test_utils.call_and_ignore_notfound_exc, |
| 98 | client.delete_port, port['id']) |
| 99 | return port |
| 100 | |
| 101 | def create_keypair(self, client=None): |
| 102 | if not client: |
| 103 | client = self.keypairs_client |
| 104 | name = data_utils.rand_name(self.__class__.__name__) |
| 105 | # We don't need to create a keypair by pubkey in scenario |
| 106 | body = client.create_keypair(name=name) |
| 107 | self.addCleanup(client.delete_keypair, name) |
| 108 | return body['keypair'] |
| 109 | |
| 110 | def create_server(self, name=None, image_id=None, flavor=None, |
| 111 | validatable=False, wait_until='ACTIVE', |
| 112 | clients=None, **kwargs): |
| 113 | """Wrapper utility that returns a test server. |
| 114 | |
| 115 | This wrapper utility calls the common create test server and |
| 116 | returns a test server. The purpose of this wrapper is to minimize |
| 117 | the impact on the code of the tests already using this |
| 118 | function. |
| 119 | """ |
| 120 | |
| 121 | # NOTE(jlanoux): As a first step, ssh checks in the scenario |
| 122 | # tests need to be run regardless of the run_validation and |
| 123 | # validatable parameters and thus until the ssh validation job |
| 124 | # becomes voting in CI. The test resources management and IP |
| 125 | # association are taken care of in the scenario tests. |
| 126 | # Therefore, the validatable parameter is set to false in all |
| 127 | # those tests. In this way create_server just return a standard |
| 128 | # server and the scenario tests always perform ssh checks. |
| 129 | |
| 130 | # Needed for the cross_tenant_traffic test: |
| 131 | if clients is None: |
| 132 | clients = self.manager |
| 133 | |
| 134 | if name is None: |
| 135 | name = data_utils.rand_name(self.__class__.__name__ + "-server") |
| 136 | |
| 137 | vnic_type = CONF.network.port_vnic_type |
| 138 | |
| 139 | # If vnic_type is configured create port for |
| 140 | # every network |
| 141 | if vnic_type: |
| 142 | ports = [] |
| 143 | |
| 144 | create_port_body = {'binding:vnic_type': vnic_type, |
| 145 | 'namestart': 'port-smoke'} |
| 146 | if kwargs: |
| 147 | # Convert security group names to security group ids |
| 148 | # to pass to create_port |
| 149 | if 'security_groups' in kwargs: |
| 150 | security_groups = \ |
| 151 | clients.security_groups_client.list_security_groups( |
| 152 | ).get('security_groups') |
| 153 | sec_dict = dict([(s['name'], s['id']) |
| 154 | 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 | |
| 208 | def create_volume(self, size=None, name=None, snapshot_id=None, |
| 209 | imageRef=None, volume_type=None): |
| 210 | if size is None: |
| 211 | size = CONF.volume.volume_size |
| 212 | if imageRef: |
| 213 | image = self.compute_images_client.show_image(imageRef)['image'] |
| 214 | min_disk = image.get('minDisk') |
| 215 | size = max(size, min_disk) |
| 216 | if name is None: |
| 217 | name = data_utils.rand_name(self.__class__.__name__ + "-volume") |
| 218 | kwargs = {'display_name': name, |
| 219 | 'snapshot_id': snapshot_id, |
| 220 | 'imageRef': imageRef, |
| 221 | 'volume_type': volume_type, |
| 222 | 'size': size} |
| 223 | volume = self.volumes_client.create_volume(**kwargs)['volume'] |
| 224 | |
| 225 | self.addCleanup(self.volumes_client.wait_for_resource_deletion, |
| 226 | volume['id']) |
| 227 | self.addCleanup(test_utils.call_and_ignore_notfound_exc, |
| 228 | self.volumes_client.delete_volume, volume['id']) |
| 229 | |
| 230 | # NOTE(e0ne): Cinder API v2 uses name instead of display_name |
| 231 | if 'display_name' in volume: |
| 232 | self.assertEqual(name, volume['display_name']) |
| 233 | else: |
| 234 | self.assertEqual(name, volume['name']) |
| 235 | waiters.wait_for_volume_resource_status(self.volumes_client, |
| 236 | volume['id'], 'available') |
| 237 | # The volume retrieved on creation has a non-up-to-date status. |
| 238 | # Retrieval after it becomes active ensures correct details. |
| 239 | volume = self.volumes_client.show_volume(volume['id'])['volume'] |
| 240 | return volume |
| 241 | |
| 242 | def create_volume_type(self, client=None, name=None, backend_name=None): |
| 243 | if not client: |
| 244 | client = self.admin_volume_types_client |
| 245 | if not name: |
| 246 | class_name = self.__class__.__name__ |
| 247 | name = data_utils.rand_name(class_name + '-volume-type') |
| 248 | randomized_name = data_utils.rand_name('scenario-type-' + name) |
| 249 | |
| 250 | LOG.debug("Creating a volume type: %s on backend %s", |
| 251 | randomized_name, backend_name) |
| 252 | extra_specs = {} |
| 253 | if backend_name: |
| 254 | extra_specs = {"volume_backend_name": backend_name} |
| 255 | |
| 256 | body = client.create_volume_type(name=randomized_name, |
| 257 | extra_specs=extra_specs) |
| 258 | volume_type = body['volume_type'] |
| 259 | self.assertIn('id', volume_type) |
| 260 | self.addCleanup(client.delete_volume_type, volume_type['id']) |
| 261 | return volume_type |
| 262 | |
| 263 | def _image_create(self, name, fmt, path, |
| 264 | disk_format=None, properties=None): |
| 265 | if properties is None: |
| 266 | properties = {} |
| 267 | name = data_utils.rand_name('%s-' % name) |
| 268 | params = { |
| 269 | 'name': name, |
| 270 | 'container_format': fmt, |
| 271 | 'disk_format': disk_format or fmt, |
| 272 | } |
| 273 | if CONF.image_feature_enabled.api_v1: |
| 274 | params['is_public'] = 'False' |
| 275 | params['properties'] = properties |
| 276 | params = {'headers': common_image.image_meta_to_headers(**params)} |
| 277 | else: |
| 278 | params['visibility'] = 'private' |
| 279 | # Additional properties are flattened out in the v2 API. |
| 280 | params.update(properties) |
| 281 | body = self.image_client.create_image(**params) |
| 282 | image = body['image'] if 'image' in body else body |
| 283 | self.addCleanup(self.image_client.delete_image, image['id']) |
| 284 | self.assertEqual("queued", image['status']) |
| 285 | with open(path, 'rb') as image_file: |
| 286 | if CONF.image_feature_enabled.api_v1: |
| 287 | self.image_client.update_image(image['id'], data=image_file) |
| 288 | else: |
| 289 | self.image_client.store_image_file(image['id'], image_file) |
| 290 | return image['id'] |
| 291 | |
| 292 | def rebuild_server(self, server_id, image=None, |
| 293 | preserve_ephemeral=False, wait=True, |
| 294 | rebuild_kwargs=None): |
| 295 | if image is None: |
| 296 | image = CONF.compute.image_ref |
| 297 | |
| 298 | rebuild_kwargs = rebuild_kwargs or {} |
| 299 | |
| 300 | LOG.debug("Rebuilding server (id: %s, image: %s, preserve eph: %s)", |
| 301 | server_id, image, preserve_ephemeral) |
| 302 | self.servers_client.rebuild_server( |
| 303 | server_id=server_id, image_ref=image, |
| 304 | preserve_ephemeral=preserve_ephemeral, |
| 305 | **rebuild_kwargs) |
| 306 | if wait: |
| 307 | waiters.wait_for_server_status(self.servers_client, |
| 308 | server_id, 'ACTIVE') |
| 309 | |
| 310 | def create_floating_ip(self, thing, pool_name=None): |
| 311 | """Create a floating IP and associates to a server on Nova""" |
| 312 | |
| 313 | if not pool_name: |
| 314 | pool_name = CONF.network.floating_network_name |
| 315 | floating_ip = (self.compute_floating_ips_client. |
| 316 | create_floating_ip(pool=pool_name)['floating_ip']) |
| 317 | self.addCleanup(test_utils.call_and_ignore_notfound_exc, |
| 318 | self.compute_floating_ips_client.delete_floating_ip, |
| 319 | floating_ip['id']) |
| 320 | self.compute_floating_ips_client.associate_floating_ip_to_server( |
| 321 | floating_ip['ip'], thing['id']) |
| 322 | return floating_ip |