blob: d98fd44cad336da7a0effd0e8fbd2e21c7a4eb00 [file] [log] [blame]
ZhiQiang Fan39f97222013-09-20 04:49:44 +08001# Copyright 2012 OpenStack Foundation
Sean Dague6dbc6da2013-05-08 17:49:46 -04002# 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
Attila Fazekasfb7552a2013-08-27 13:02:26 +020017import logging
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120018import os
Sean Dague6dbc6da2013-05-08 17:49:46 -040019import subprocess
20
21# Default client libs
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +090022import cinderclient.client
Sean Dague6dbc6da2013-05-08 17:49:46 -040023import glanceclient
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120024import heatclient.client
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +000025import keystoneclient.apiclient.exceptions
Sean Dague6dbc6da2013-05-08 17:49:46 -040026import keystoneclient.v2_0.client
27import netaddr
Mark McClainf2982e82013-07-06 17:48:03 -040028from neutronclient.common import exceptions as exc
29import neutronclient.v2_0.client
Sean Dague6dbc6da2013-05-08 17:49:46 -040030import novaclient.client
fujioka yuuichi636f8db2013-08-09 12:05:24 +090031from novaclient import exceptions as nova_exceptions
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +000032import swiftclient
Sean Dague6dbc6da2013-05-08 17:49:46 -040033
Sean Dague1937d092013-05-17 16:36:38 -040034from tempest.api.network import common as net_common
Matthew Treinishb86cda92013-07-29 11:22:23 -040035from tempest.common import isolated_creds
Masayuki Igawa259c1132013-10-31 17:48:44 +090036from tempest.common.utils import data_utils
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +090037from tempest.common.utils.linux.remote_client import RemoteClient
Giulio Fidente92f77192013-08-26 17:13:28 +020038from tempest import exceptions
Sean Dague6dbc6da2013-05-08 17:49:46 -040039import tempest.manager
Attila Fazekasfb7552a2013-08-27 13:02:26 +020040from tempest.openstack.common import log
Sean Dague6dbc6da2013-05-08 17:49:46 -040041import tempest.test
Sean Dague6dbc6da2013-05-08 17:49:46 -040042
43
Attila Fazekasfb7552a2013-08-27 13:02:26 +020044LOG = log.getLogger(__name__)
45
46# NOTE(afazekas): Workaround for the stdout logging
47LOG_nova_client = logging.getLogger('novaclient.client')
48LOG_nova_client.addHandler(log.NullHandler())
49
50LOG_cinder_client = logging.getLogger('cinderclient.client')
51LOG_cinder_client.addHandler(log.NullHandler())
Sean Dague6dbc6da2013-05-08 17:49:46 -040052
53
54class OfficialClientManager(tempest.manager.Manager):
55 """
56 Manager that provides access to the official python clients for
57 calling various OpenStack APIs.
58 """
59
60 NOVACLIENT_VERSION = '2'
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +090061 CINDERCLIENT_VERSION = '1'
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120062 HEATCLIENT_VERSION = '1'
Sean Dague6dbc6da2013-05-08 17:49:46 -040063
Matthew Treinishb86cda92013-07-29 11:22:23 -040064 def __init__(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -040065 super(OfficialClientManager, self).__init__()
Matthew Treinishb86cda92013-07-29 11:22:23 -040066 self.compute_client = self._get_compute_client(username,
67 password,
68 tenant_name)
69 self.identity_client = self._get_identity_client(username,
70 password,
71 tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -040072 self.image_client = self._get_image_client()
Sean Dague6dbc6da2013-05-08 17:49:46 -040073 self.network_client = self._get_network_client()
Matthew Treinishb86cda92013-07-29 11:22:23 -040074 self.volume_client = self._get_volume_client(username,
75 password,
76 tenant_name)
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +000077 self.object_storage_client = self._get_object_storage_client(
78 username,
79 password,
80 tenant_name)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120081 self.orchestration_client = self._get_orchestration_client(
82 username,
83 password,
84 tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -040085
Matthew Treinishb86cda92013-07-29 11:22:23 -040086 def _get_compute_client(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -040087 # Novaclient will not execute operations for anyone but the
88 # identified user, so a new client needs to be created for
89 # each user that operations need to be performed for.
Sean Dague43cd9052013-07-19 12:20:04 -040090 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -040091
92 auth_url = self.config.identity.uri
93 dscv = self.config.identity.disable_ssl_certificate_validation
Russell Sim1fd81ce2013-11-07 17:04:21 +110094 region = self.config.identity.region
Sean Dague6dbc6da2013-05-08 17:49:46 -040095
96 client_args = (username, password, tenant_name, auth_url)
97
98 # Create our default Nova client to use in testing
99 service_type = self.config.compute.catalog_type
100 return novaclient.client.Client(self.NOVACLIENT_VERSION,
101 *client_args,
102 service_type=service_type,
Russell Sim1fd81ce2013-11-07 17:04:21 +1100103 region_name=region,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400104 no_cache=True,
Attila Fazekasfb7552a2013-08-27 13:02:26 +0200105 insecure=dscv,
106 http_log_debug=True)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400107
108 def _get_image_client(self):
Matthew Treinishb86cda92013-07-29 11:22:23 -0400109 token = self.identity_client.auth_token
Russell Sim1fd81ce2013-11-07 17:04:21 +1100110 region = self.config.identity.region
Matthew Treinishb86cda92013-07-29 11:22:23 -0400111 endpoint = self.identity_client.service_catalog.url_for(
Russell Sim1fd81ce2013-11-07 17:04:21 +1100112 attr='region', filter_value=region,
Matthew Treinishb86cda92013-07-29 11:22:23 -0400113 service_type='image', endpoint_type='publicURL')
Sean Dague6dbc6da2013-05-08 17:49:46 -0400114 dscv = self.config.identity.disable_ssl_certificate_validation
115 return glanceclient.Client('1', endpoint=endpoint, token=token,
116 insecure=dscv)
117
Matthew Treinishb86cda92013-07-29 11:22:23 -0400118 def _get_volume_client(self, username, password, tenant_name):
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +0900119 auth_url = self.config.identity.uri
Russell Sim1fd81ce2013-11-07 17:04:21 +1100120 region = self.config.identity.region
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +0900121 return cinderclient.client.Client(self.CINDERCLIENT_VERSION,
122 username,
123 password,
124 tenant_name,
Attila Fazekasfb7552a2013-08-27 13:02:26 +0200125 auth_url,
Russell Sim1fd81ce2013-11-07 17:04:21 +1100126 region_name=region,
Attila Fazekasfb7552a2013-08-27 13:02:26 +0200127 http_log_debug=True)
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +0900128
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +0000129 def _get_object_storage_client(self, username, password, tenant_name):
130 auth_url = self.config.identity.uri
Qiu Hua Qiao5384ef52014-01-10 07:55:19 -0600131 # add current tenant to swift operator role group.
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +0000132 keystone_admin = self._get_identity_client(
133 self.config.identity.admin_username,
134 self.config.identity.admin_password,
135 self.config.identity.admin_tenant_name)
136
Qiu Hua Qiao5384ef52014-01-10 07:55:19 -0600137 # enable test user to operate swift by adding operator role to him.
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +0000138 roles = keystone_admin.roles.list()
Qiu Hua Qiao5384ef52014-01-10 07:55:19 -0600139 operator_role = self.config.object_storage.operator_role
140 member_role = [role for role in roles if role.name == operator_role][0]
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +0000141 # NOTE(maurosr): This is surrounded in the try-except block cause
142 # neutron tests doesn't have tenant isolation.
143 try:
144 keystone_admin.roles.add_user_role(self.identity_client.user_id,
145 member_role.id,
146 self.identity_client.tenant_id)
147 except keystoneclient.apiclient.exceptions.Conflict:
148 pass
149
150 return swiftclient.Connection(auth_url, username, password,
151 tenant_name=tenant_name,
152 auth_version='2')
153
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200154 def _get_orchestration_client(self, username=None, password=None,
155 tenant_name=None):
156 if not username:
157 username = self.config.identity.admin_username
158 if not password:
159 password = self.config.identity.admin_password
160 if not tenant_name:
161 tenant_name = self.config.identity.tenant_name
162
163 self._validate_credentials(username, password, tenant_name)
164
165 keystone = self._get_identity_client(username, password, tenant_name)
Russell Sim1fd81ce2013-11-07 17:04:21 +1100166 region = self.config.identity.region
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200167 token = keystone.auth_token
168 try:
169 endpoint = keystone.service_catalog.url_for(
Russell Sim1fd81ce2013-11-07 17:04:21 +1100170 attr='region',
171 filter_value=region,
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200172 service_type='orchestration',
173 endpoint_type='publicURL')
174 except keystoneclient.exceptions.EndpointNotFound:
175 return None
176 else:
177 return heatclient.client.Client(self.HEATCLIENT_VERSION,
178 endpoint,
179 token=token,
180 username=username,
181 password=password)
182
Matthew Treinishb86cda92013-07-29 11:22:23 -0400183 def _get_identity_client(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400184 # This identity client is not intended to check the security
185 # of the identity service, so use admin credentials by default.
Sean Dague43cd9052013-07-19 12:20:04 -0400186 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400187
188 auth_url = self.config.identity.uri
189 dscv = self.config.identity.disable_ssl_certificate_validation
190
191 return keystoneclient.v2_0.client.Client(username=username,
192 password=password,
193 tenant_name=tenant_name,
194 auth_url=auth_url,
195 insecure=dscv)
196
197 def _get_network_client(self):
198 # The intended configuration is for the network client to have
199 # admin privileges and indicate for whom resources are being
200 # created via a 'tenant_id' parameter. This will often be
201 # preferable to authenticating as a specific user because
202 # working with certain resources (public routers and networks)
203 # often requires admin privileges anyway.
204 username = self.config.identity.admin_username
205 password = self.config.identity.admin_password
206 tenant_name = self.config.identity.admin_tenant_name
207
Sean Dague43cd9052013-07-19 12:20:04 -0400208 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400209
210 auth_url = self.config.identity.uri
211 dscv = self.config.identity.disable_ssl_certificate_validation
212
Mark McClainf2982e82013-07-06 17:48:03 -0400213 return neutronclient.v2_0.client.Client(username=username,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400214 password=password,
215 tenant_name=tenant_name,
216 auth_url=auth_url,
217 insecure=dscv)
218
219
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400220class OfficialClientTest(tempest.test.BaseTestCase):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400221 """
222 Official Client test base class for scenario testing.
223
224 Official Client tests are tests that have the following characteristics:
225
226 * Test basic operations of an API, typically in an order that
227 a regular user would perform those operations
228 * Test only the correct inputs and action paths -- no fuzz or
229 random input data is sent, only valid inputs.
230 * Use only the default client tool for calling an API
231 """
232
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400233 @classmethod
234 def setUpClass(cls):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200235 super(OfficialClientTest, cls).setUpClass()
Matthew Treinishb86cda92013-07-29 11:22:23 -0400236 cls.isolated_creds = isolated_creds.IsolatedCreds(
Matthew Treinish9f756a02014-01-15 10:26:07 -0500237 __name__, tempest_client=False,
238 network_resources=cls.network_resources)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200239
Yair Fried769bbff2013-12-18 16:33:17 +0200240 username, password, tenant_name = cls.credentials()
Matthew Treinishb86cda92013-07-29 11:22:23 -0400241
242 cls.manager = OfficialClientManager(username, password, tenant_name)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400243 cls.compute_client = cls.manager.compute_client
244 cls.image_client = cls.manager.image_client
245 cls.identity_client = cls.manager.identity_client
246 cls.network_client = cls.manager.network_client
247 cls.volume_client = cls.manager.volume_client
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +0000248 cls.object_storage_client = cls.manager.object_storage_client
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200249 cls.orchestration_client = cls.manager.orchestration_client
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400250 cls.resource_keys = {}
251 cls.os_resources = []
Sean Dague6dbc6da2013-05-08 17:49:46 -0400252
253 @classmethod
Yair Frieda71cc442013-12-18 13:32:36 +0200254 def _get_credentials(cls, get_creds, prefix):
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200255 if cls.config.compute.allow_tenant_isolation:
Yair Fried769bbff2013-12-18 16:33:17 +0200256 username, tenant_name, password = get_creds()
257 else:
258 username = getattr(cls.config.identity, prefix + 'username')
259 password = getattr(cls.config.identity, prefix + 'password')
260 tenant_name = getattr(cls.config.identity, prefix + 'tenant_name')
261 return username, password, tenant_name
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200262
263 @classmethod
Yair Frieda71cc442013-12-18 13:32:36 +0200264 def credentials(cls):
265 return cls._get_credentials(cls.isolated_creds.get_primary_creds, '')
266
267 @classmethod
268 def alt_credentials(cls):
269 return cls._get_credentials(cls.isolated_creds.get_alt_creds, 'alt_')
270
271 @classmethod
272 def admin_credentials(cls):
273 return cls._get_credentials(cls.isolated_creds.get_admin_creds,
274 'admin_')
275
276 @classmethod
Sean Dague6dbc6da2013-05-08 17:49:46 -0400277 def tearDownClass(cls):
278 # NOTE(jaypipes): Because scenario tests are typically run in a
279 # specific order, and because test methods in scenario tests
280 # generally create resources in a particular order, we destroy
281 # resources in the reverse order in which resources are added to
282 # the scenario test class object
283 while cls.os_resources:
284 thing = cls.os_resources.pop()
285 LOG.debug("Deleting %r from shared resources of %s" %
286 (thing, cls.__name__))
287
288 try:
289 # OpenStack resources are assumed to have a delete()
290 # method which destroys the resource...
291 thing.delete()
292 except Exception as e:
293 # If the resource is already missing, mission accomplished.
Yair Fried4d7efa62013-11-17 17:12:29 +0200294 # add status code as workaround for bug 1247568
Attila Fazekas09925972013-12-19 16:16:49 +0100295 if (e.__class__.__name__ == 'NotFound' or
296 hasattr(e, 'status_code') and e.status_code == 404):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400297 continue
298 raise
299
300 def is_deletion_complete():
301 # Deletion testing is only required for objects whose
302 # existence cannot be checked via retrieval.
303 if isinstance(thing, dict):
304 return True
305 try:
306 thing.get()
307 except Exception as e:
308 # Clients are expected to return an exception
309 # called 'NotFound' if retrieval fails.
310 if e.__class__.__name__ == 'NotFound':
311 return True
312 raise
313 return False
314
315 # Block until resource deletion has completed or timed-out
316 tempest.test.call_until_true(is_deletion_complete, 10, 1)
Matthew Treinishb86cda92013-07-29 11:22:23 -0400317 cls.isolated_creds.clear_isolated_creds()
318 super(OfficialClientTest, cls).tearDownClass()
Sean Dague6dbc6da2013-05-08 17:49:46 -0400319
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400320 @classmethod
321 def set_resource(cls, key, thing):
322 LOG.debug("Adding %r to shared resources of %s" %
323 (thing, cls.__name__))
324 cls.resource_keys[key] = thing
325 cls.os_resources.append(thing)
326
327 @classmethod
328 def get_resource(cls, key):
329 return cls.resource_keys[key]
330
331 @classmethod
332 def remove_resource(cls, key):
333 thing = cls.resource_keys[key]
334 cls.os_resources.remove(thing)
335 del cls.resource_keys[key]
336
Steve Bakerefde7612013-09-30 11:29:23 +1300337 def status_timeout(self, things, thing_id, expected_status,
338 error_status='ERROR',
339 not_found_exception=nova_exceptions.NotFound):
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400340 """
341 Given a thing and an expected status, do a loop, sleeping
342 for a configurable amount of time, checking for the
343 expected status to show. At any time, if the returned
344 status of the thing is ERROR, fail out.
345 """
Steve Bakerefde7612013-09-30 11:29:23 +1300346 self._status_timeout(things, thing_id,
347 expected_status=expected_status,
348 error_status=error_status,
349 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900350
Steve Bakerefde7612013-09-30 11:29:23 +1300351 def delete_timeout(self, things, thing_id,
352 error_status='ERROR',
353 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900354 """
355 Given a thing, do a loop, sleeping
356 for a configurable amount of time, checking for the
357 deleted status to show. At any time, if the returned
358 status of the thing is ERROR, fail out.
359 """
360 self._status_timeout(things,
361 thing_id,
Steve Bakerefde7612013-09-30 11:29:23 +1300362 allow_notfound=True,
363 error_status=error_status,
364 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900365
366 def _status_timeout(self,
367 things,
368 thing_id,
369 expected_status=None,
Steve Bakerefde7612013-09-30 11:29:23 +1300370 allow_notfound=False,
371 error_status='ERROR',
372 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900373
374 log_status = expected_status if expected_status else ''
375 if allow_notfound:
376 log_status += ' or NotFound' if log_status != '' else 'NotFound'
377
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400378 def check_status():
379 # python-novaclient has resources available to its client
380 # that all implement a get() method taking an identifier
381 # for the singular resource to retrieve.
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900382 try:
383 thing = things.get(thing_id)
Steve Bakerefde7612013-09-30 11:29:23 +1300384 except not_found_exception:
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900385 if allow_notfound:
386 return True
387 else:
388 raise
389
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400390 new_status = thing.status
Brent Eaglesc26d4522013-12-02 13:28:49 -0500391
392 # Some components are reporting error status in lower case
393 # so case sensitive comparisons can really mess things
394 # up.
395 if new_status.lower() == error_status.lower():
Ken'ichi Ohmichiab1496f2013-12-12 22:17:57 +0900396 message = ("%s failed to get to expected status. "
397 "In %s state.") % (thing, new_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200398 raise exceptions.BuildErrorException(message)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900399 elif new_status == expected_status and expected_status is not None:
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400400 return True # All good.
401 LOG.debug("Waiting for %s to get to %s status. "
402 "Currently in %s status",
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900403 thing, log_status, new_status)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400404 if not tempest.test.call_until_true(
405 check_status,
406 self.config.compute.build_timeout,
407 self.config.compute.build_interval):
Ken'ichi Ohmichiab1496f2013-12-12 22:17:57 +0900408 message = ("Timed out waiting for thing %s "
409 "to become %s") % (thing_id, log_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200410 raise exceptions.TimeoutException(message)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400411
Yair Friedeb69f3f2013-10-10 13:18:16 +0300412 def _create_loginable_secgroup_rule_nova(self, client=None,
413 secgroup_id=None):
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900414 if client is None:
415 client = self.compute_client
416 if secgroup_id is None:
417 sgs = client.security_groups.list()
418 for sg in sgs:
419 if sg.name == 'default':
420 secgroup_id = sg.id
421
422 # These rules are intended to permit inbound ssh and icmp
423 # traffic from all sources, so no group_id is provided.
424 # Setting a group_id would only permit traffic from ports
425 # belonging to the same security group.
426 rulesets = [
427 {
428 # ssh
429 'ip_protocol': 'tcp',
430 'from_port': 22,
431 'to_port': 22,
432 'cidr': '0.0.0.0/0',
433 },
434 {
435 # ping
436 'ip_protocol': 'icmp',
437 'from_port': -1,
438 'to_port': -1,
439 'cidr': '0.0.0.0/0',
440 }
441 ]
Yair Friedeb69f3f2013-10-10 13:18:16 +0300442 rules = list()
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900443 for ruleset in rulesets:
444 sg_rule = client.security_group_rules.create(secgroup_id,
445 **ruleset)
446 self.set_resource(sg_rule.id, sg_rule)
Yair Friedeb69f3f2013-10-10 13:18:16 +0300447 rules.append(sg_rule)
448 return rules
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900449
Giulio Fidente61cadca2013-09-24 18:33:37 +0200450 def create_server(self, client=None, name=None, image=None, flavor=None,
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900451 create_kwargs={}):
Giulio Fidente61cadca2013-09-24 18:33:37 +0200452 if client is None:
453 client = self.compute_client
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900454 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900455 name = data_utils.rand_name('scenario-server-')
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900456 if image is None:
457 image = self.config.compute.image_ref
458 if flavor is None:
459 flavor = self.config.compute.flavor_ref
460 LOG.debug("Creating a server (name: %s, image: %s, flavor: %s)",
461 name, image, flavor)
462 server = client.servers.create(name, image, flavor, **create_kwargs)
Giulio Fidente92f77192013-08-26 17:13:28 +0200463 self.assertEqual(server.name, name)
464 self.set_resource(name, server)
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900465 self.status_timeout(client.servers, server.id, 'ACTIVE')
466 # The instance retrieved on creation is missing network
467 # details, necessitating retrieval after it becomes active to
468 # ensure correct details.
469 server = client.servers.get(server.id)
470 self.set_resource(name, server)
471 LOG.debug("Created server: %s", server)
472 return server
473
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900474 def create_volume(self, client=None, size=1, name=None,
475 snapshot_id=None, imageRef=None):
476 if client is None:
477 client = self.volume_client
478 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900479 name = data_utils.rand_name('scenario-volume-')
Eric Windisch2d26f1b2013-09-04 17:52:16 -0700480 LOG.debug("Creating a volume (size: %s, name: %s)", size, name)
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900481 volume = client.volumes.create(size=size, display_name=name,
482 snapshot_id=snapshot_id,
483 imageRef=imageRef)
484 self.set_resource(name, volume)
485 self.assertEqual(name, volume.display_name)
486 self.status_timeout(client.volumes, volume.id, 'available')
487 LOG.debug("Created volume: %s", volume)
488 return volume
489
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900490 def create_server_snapshot(self, server, compute_client=None,
491 image_client=None, name=None):
492 if compute_client is None:
493 compute_client = self.compute_client
494 if image_client is None:
495 image_client = self.image_client
496 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900497 name = data_utils.rand_name('scenario-snapshot-')
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900498 LOG.debug("Creating a snapshot image for server: %s", server.name)
499 image_id = compute_client.servers.create_image(server, name)
500 self.addCleanup(image_client.images.delete, image_id)
501 self.status_timeout(image_client.images, image_id, 'active')
502 snapshot_image = image_client.images.get(image_id)
Chang Bo Guofc77e932013-09-16 17:38:26 -0700503 self.assertEqual(name, snapshot_image.name)
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900504 LOG.debug("Created snapshot image %s for server %s",
505 snapshot_image.name, server.name)
506 return snapshot_image
507
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900508 def create_keypair(self, client=None, name=None):
509 if client is None:
510 client = self.compute_client
511 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900512 name = data_utils.rand_name('scenario-keypair-')
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900513 keypair = client.keypairs.create(name)
514 self.assertEqual(keypair.name, name)
515 self.set_resource(name, keypair)
516 return keypair
517
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900518 def get_remote_client(self, server_or_ip, username=None, private_key=None):
519 if isinstance(server_or_ip, basestring):
520 ip = server_or_ip
521 else:
522 network_name_for_ssh = self.config.compute.network_for_ssh
523 ip = server_or_ip.networks[network_name_for_ssh][0]
524 if username is None:
525 username = self.config.scenario.ssh_user
526 if private_key is None:
527 private_key = self.keypair.private_key
528 return RemoteClient(ip, username, pkey=private_key)
529
Nachi Ueno95b41282014-01-15 06:54:21 -0800530 def _log_console_output(self, servers=None):
531 if not servers:
532 servers = self.compute_client.servers.list()
533 for server in servers:
534 LOG.debug('Console output for %s', server.id)
535 LOG.debug(server.get_console_output())
536
Sean Dague6dbc6da2013-05-08 17:49:46 -0400537
538class NetworkScenarioTest(OfficialClientTest):
539 """
540 Base class for network scenario tests
541 """
542
543 @classmethod
544 def check_preconditions(cls):
Matthew Treinishfaa340d2013-07-19 16:26:21 -0400545 if (cls.config.service_available.neutron):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400546 cls.enabled = True
Attila Fazekasc3a095b2013-08-17 09:15:44 +0200547 # verify that neutron_available is telling the truth
Sean Dague6dbc6da2013-05-08 17:49:46 -0400548 try:
549 cls.network_client.list_networks()
550 except exc.EndpointNotFound:
551 cls.enabled = False
552 raise
553 else:
554 cls.enabled = False
Mark McClainf2982e82013-07-06 17:48:03 -0400555 msg = 'Neutron not available'
Sean Dague6dbc6da2013-05-08 17:49:46 -0400556 raise cls.skipException(msg)
557
558 @classmethod
559 def setUpClass(cls):
560 super(NetworkScenarioTest, cls).setUpClass()
Miguel Lavalleb8fabc52013-08-23 11:19:57 -0500561 if cls.config.compute.allow_tenant_isolation:
562 cls.tenant_id = cls.isolated_creds.get_primary_tenant().id
563 else:
564 cls.tenant_id = cls.manager._get_identity_client(
565 cls.config.identity.username,
566 cls.config.identity.password,
567 cls.config.identity.tenant_name).tenant_id
Sean Dague6dbc6da2013-05-08 17:49:46 -0400568
Sean Dague6dbc6da2013-05-08 17:49:46 -0400569 def _create_network(self, tenant_id, namestart='network-smoke-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900570 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400571 body = dict(
572 network=dict(
573 name=name,
574 tenant_id=tenant_id,
575 ),
576 )
577 result = self.network_client.create_network(body=body)
578 network = net_common.DeletableNetwork(client=self.network_client,
579 **result['network'])
580 self.assertEqual(network.name, name)
581 self.set_resource(name, network)
582 return network
583
584 def _list_networks(self):
585 nets = self.network_client.list_networks()
586 return nets['networks']
587
588 def _list_subnets(self):
589 subnets = self.network_client.list_subnets()
590 return subnets['subnets']
591
592 def _list_routers(self):
593 routers = self.network_client.list_routers()
594 return routers['routers']
595
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000596 def _list_ports(self):
597 ports = self.network_client.list_ports()
598 return ports['ports']
599
600 def _get_tenant_own_network_num(self, tenant_id):
601 nets = self._list_networks()
602 ownnets = [value for value in nets if tenant_id == value['tenant_id']]
603 return len(ownnets)
604
605 def _get_tenant_own_subnet_num(self, tenant_id):
606 subnets = self._list_subnets()
607 ownsubnets = ([value for value in subnets
608 if tenant_id == value['tenant_id']])
609 return len(ownsubnets)
610
611 def _get_tenant_own_port_num(self, tenant_id):
612 ports = self._list_ports()
613 ownports = ([value for value in ports
614 if tenant_id == value['tenant_id']])
615 return len(ownports)
616
Sean Dague6dbc6da2013-05-08 17:49:46 -0400617 def _create_subnet(self, network, namestart='subnet-smoke-'):
618 """
619 Create a subnet for the given network within the cidr block
620 configured for tenant networks.
621 """
622 cfg = self.config.network
623 tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
624 result = None
625 # Repeatedly attempt subnet creation with sequential cidr
626 # blocks until an unallocated block is found.
627 for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
628 body = dict(
629 subnet=dict(
630 ip_version=4,
631 network_id=network.id,
632 tenant_id=network.tenant_id,
633 cidr=str(subnet_cidr),
634 ),
635 )
636 try:
637 result = self.network_client.create_subnet(body=body)
638 break
Mark McClainf2982e82013-07-06 17:48:03 -0400639 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400640 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
641 if not is_overlapping_cidr:
642 raise
643 self.assertIsNotNone(result, 'Unable to allocate tenant network')
644 subnet = net_common.DeletableSubnet(client=self.network_client,
645 **result['subnet'])
646 self.assertEqual(subnet.cidr, str(subnet_cidr))
Masayuki Igawa259c1132013-10-31 17:48:44 +0900647 self.set_resource(data_utils.rand_name(namestart), subnet)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400648 return subnet
649
650 def _create_port(self, network, namestart='port-quotatest-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900651 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400652 body = dict(
653 port=dict(name=name,
654 network_id=network.id,
655 tenant_id=network.tenant_id))
656 result = self.network_client.create_port(body=body)
657 self.assertIsNotNone(result, 'Unable to allocate port')
658 port = net_common.DeletablePort(client=self.network_client,
659 **result['port'])
660 self.set_resource(name, port)
661 return port
662
Yair Fried05db2522013-11-18 11:02:10 +0200663 def _get_server_port_id(self, server):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400664 result = self.network_client.list_ports(device_id=server.id)
665 ports = result.get('ports', [])
666 self.assertEqual(len(ports), 1,
667 "Unable to determine which port to target.")
Yair Fried05db2522013-11-18 11:02:10 +0200668 return ports[0]['id']
669
670 def _create_floating_ip(self, server, external_network_id):
671 port_id = self._get_server_port_id(server)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400672 body = dict(
673 floatingip=dict(
674 floating_network_id=external_network_id,
675 port_id=port_id,
676 tenant_id=server.tenant_id,
677 )
678 )
679 result = self.network_client.create_floatingip(body=body)
680 floating_ip = net_common.DeletableFloatingIp(
681 client=self.network_client,
682 **result['floatingip'])
Masayuki Igawa259c1132013-10-31 17:48:44 +0900683 self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400684 return floating_ip
685
Yair Fried05db2522013-11-18 11:02:10 +0200686 def _associate_floating_ip(self, floating_ip, server):
687 port_id = self._get_server_port_id(server)
688 floating_ip.update(port_id=port_id)
689 self.assertEqual(port_id, floating_ip.port_id)
690 return floating_ip
691
Yair Fried9a551c42013-12-15 14:59:34 +0200692 def _disassociate_floating_ip(self, floating_ip):
693 """
694 :param floating_ip: type DeletableFloatingIp
695 """
696 floating_ip.update(port_id=None)
697 self.assertEqual(None, floating_ip.port_id)
698 return floating_ip
699
700 def _ping_ip_address(self, ip_address, should_succeed=True):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400701 cmd = ['ping', '-c1', '-w1', ip_address]
702
703 def ping():
704 proc = subprocess.Popen(cmd,
705 stdout=subprocess.PIPE,
706 stderr=subprocess.PIPE)
707 proc.wait()
Yair Fried9a551c42013-12-15 14:59:34 +0200708 return (proc.returncode == 0) == should_succeed
Sean Dague6dbc6da2013-05-08 17:49:46 -0400709
Nachi Ueno6d580be2013-07-24 10:58:11 -0700710 return tempest.test.call_until_true(
711 ping, self.config.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000712
Yair Fried9a551c42013-12-15 14:59:34 +0200713 def _check_vm_connectivity(self, ip_address,
714 username=None,
715 private_key=None,
716 should_connect=True):
717 """
718 :param ip_address: server to test against
719 :param username: server's ssh username
720 :param private_key: server's ssh private key to be used
721 :param should_connect: True/False indicates positive/negative test
722 positive - attempt ping and ssh
723 negative - attempt ping and fail if succeed
724
725 :raises: AssertError if the result of the connectivity check does
726 not match the value of the should_connect param
727 """
728 if should_connect:
729 msg = "Timed out waiting for %s to become reachable" % ip_address
730 else:
731 msg = "ip address %s is reachable" % ip_address
732 self.assertTrue(self._ping_ip_address(ip_address,
733 should_succeed=should_connect),
734 msg=msg)
735 if should_connect:
736 # no need to check ssh for negative connectivity
Attila Fazekasad7ef7d2013-11-20 10:12:53 +0100737 linux_client = self.get_remote_client(ip_address, username,
738 private_key)
739 linux_client.validate_authentication()
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200740
Yair Friedeb69f3f2013-10-10 13:18:16 +0300741 def _create_security_group_nova(self, client=None,
742 namestart='secgroup-smoke-',
743 tenant_id=None):
744 if client is None:
745 client = self.compute_client
746 # Create security group
747 sg_name = data_utils.rand_name(namestart)
748 sg_desc = sg_name + " description"
749 secgroup = client.security_groups.create(sg_name, sg_desc)
750 self.assertEqual(secgroup.name, sg_name)
751 self.assertEqual(secgroup.description, sg_desc)
752 self.set_resource(sg_name, secgroup)
753
754 # Add rules to the security group
755 self._create_loginable_secgroup_rule_nova(client, secgroup.id)
756
757 return secgroup
758
759 def _create_security_group_neutron(self, tenant_id, client=None,
760 namestart='secgroup-smoke-'):
761 if client is None:
762 client = self.network_client
763 secgroup = self._create_empty_security_group(namestart=namestart,
764 client=client,
765 tenant_id=tenant_id)
766
767 # Add rules to the security group
768 rules = self._create_loginable_secgroup_rule_neutron(secgroup=secgroup)
769 for rule in rules:
770 self.assertEqual(tenant_id, rule.tenant_id)
771 self.assertEqual(secgroup.id, rule.security_group_id)
772 return secgroup
773
774 def _create_empty_security_group(self, tenant_id, client=None,
775 namestart='secgroup-smoke-'):
776 """Create a security group without rules.
777
778 Default rules will be created:
779 - IPv4 egress to any
780 - IPv6 egress to any
781
782 :param tenant_id: secgroup will be created in this tenant
783 :returns: DeletableSecurityGroup -- containing the secgroup created
784 """
785 if client is None:
786 client = self.network_client
787 sg_name = data_utils.rand_name(namestart)
788 sg_desc = sg_name + " description"
789 sg_dict = dict(name=sg_name,
790 description=sg_desc)
791 sg_dict['tenant_id'] = tenant_id
792 body = dict(security_group=sg_dict)
793 result = client.create_security_group(body=body)
794 secgroup = net_common.DeletableSecurityGroup(
795 client=client,
796 **result['security_group']
797 )
798 self.assertEqual(secgroup.name, sg_name)
799 self.assertEqual(tenant_id, secgroup.tenant_id)
800 self.assertEqual(secgroup.description, sg_desc)
801 self.set_resource(sg_name, secgroup)
802 return secgroup
803
804 def _default_security_group(self, tenant_id, client=None):
805 """Get default secgroup for given tenant_id.
806
807 :returns: DeletableSecurityGroup -- default secgroup for given tenant
808 """
809 if client is None:
810 client = self.network_client
811 sgs = [
812 sg for sg in client.list_security_groups().values()[0]
813 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
814 ]
815 msg = "No default security group for tenant %s." % (tenant_id)
816 self.assertTrue(len(sgs) > 0, msg)
817 if len(sgs) > 1:
818 msg = "Found %d default security groups" % len(sgs)
819 raise exc.NeutronClientNoUniqueMatch(msg=msg)
820 return net_common.DeletableSecurityGroup(client=client,
821 **sgs[0])
822
823 def _create_security_group_rule(self, client=None, secgroup=None,
824 tenant_id=None, **kwargs):
825 """Create a rule from a dictionary of rule parameters.
826
827 Create a rule in a secgroup. if secgroup not defined will search for
828 default secgroup in tenant_id.
829
830 :param secgroup: type DeletableSecurityGroup.
831 :param secgroup_id: search for secgroup by id
832 default -- choose default secgroup for given tenant_id
833 :param tenant_id: if secgroup not passed -- the tenant in which to
834 search for default secgroup
835 :param kwargs: a dictionary containing rule parameters:
836 for example, to allow incoming ssh:
837 rule = {
838 direction: 'ingress'
839 protocol:'tcp',
840 port_range_min: 22,
841 port_range_max: 22
842 }
843 """
844 if client is None:
845 client = self.network_client
846 if secgroup is None:
847 secgroup = self._default_security_group(tenant_id)
848
849 ruleset = dict(security_group_id=secgroup.id,
850 tenant_id=secgroup.tenant_id,
851 )
852 ruleset.update(kwargs)
853
854 body = dict(security_group_rule=dict(ruleset))
855 sg_rule = client.create_security_group_rule(body=body)
856 sg_rule = net_common.DeletableSecurityGroupRule(
857 client=client,
858 **sg_rule['security_group_rule']
859 )
860 self.set_resource(sg_rule.id, sg_rule)
861 self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
862 self.assertEqual(secgroup.id, sg_rule.security_group_id)
863
864 return sg_rule
865
866 def _create_loginable_secgroup_rule_neutron(self, client=None,
867 secgroup=None):
868 """These rules are intended to permit inbound ssh and icmp
869 traffic from all sources, so no group_id is provided.
870 Setting a group_id would only permit traffic from ports
871 belonging to the same security group.
872 """
873
874 if client is None:
875 client = self.network_client
876 rules = []
877 rulesets = [
878 dict(
879 # ssh
880 protocol='tcp',
881 port_range_min=22,
882 port_range_max=22,
883 ),
884 dict(
885 # ping
886 protocol='icmp',
887 )
888 ]
889 for ruleset in rulesets:
890 for r_direction in ['ingress', 'egress']:
891 ruleset['direction'] = r_direction
892 try:
893 sg_rule = self._create_security_group_rule(
894 client=client, secgroup=secgroup, **ruleset)
895 except exc.NeutronClientException as ex:
896 # if rule already exist - skip rule and continue
897 if not (ex.status_code is 409 and 'Security group rule'
898 ' already exists' in ex.message):
899 raise ex
900 else:
901 self.assertEqual(r_direction, sg_rule.direction)
902 rules.append(sg_rule)
903
904 return rules
905
Yair Fried5f670ab2013-12-09 09:26:51 +0200906 def _ssh_to_server(self, server, private_key):
907 ssh_login = self.config.compute.image_ssh_user
908 return self.get_remote_client(server,
909 username=ssh_login,
910 private_key=private_key)
911
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000912 def _show_quota_network(self, tenant_id):
913 quota = self.network_client.show_quota(tenant_id)
914 return quota['quota']['network']
915
916 def _show_quota_subnet(self, tenant_id):
917 quota = self.network_client.show_quota(tenant_id)
918 return quota['quota']['subnet']
919
920 def _show_quota_port(self, tenant_id):
921 quota = self.network_client.show_quota(tenant_id)
922 return quota['quota']['port']
923
Yair Fried4d7efa62013-11-17 17:12:29 +0200924 def _get_router(self, tenant_id):
925 """Retrieve a router for the given tenant id.
926
927 If a public router has been configured, it will be returned.
928
929 If a public router has not been configured, but a public
930 network has, a tenant router will be created and returned that
931 routes traffic to the public network.
932 """
933 router_id = self.config.network.public_router_id
934 network_id = self.config.network.public_network_id
935 if router_id:
936 result = self.network_client.show_router(router_id)
937 return net_common.AttributeDict(**result['router'])
938 elif network_id:
939 router = self._create_router(tenant_id)
940 router.add_gateway(network_id)
941 return router
942 else:
943 raise Exception("Neither of 'public_router_id' or "
944 "'public_network_id' has been defined.")
945
946 def _create_router(self, tenant_id, namestart='router-smoke-'):
947 name = data_utils.rand_name(namestart)
948 body = dict(
949 router=dict(
950 name=name,
951 admin_state_up=True,
952 tenant_id=tenant_id,
953 ),
954 )
955 result = self.network_client.create_router(body=body)
956 router = net_common.DeletableRouter(client=self.network_client,
957 **result['router'])
958 self.assertEqual(router.name, name)
959 self.set_resource(name, router)
960 return router
961
962 def _create_networks(self, tenant_id=None):
963 """Create a network with a subnet connected to a router.
964
965 :returns: network, subnet, router
966 """
967 if tenant_id is None:
968 tenant_id = self.tenant_id
969 network = self._create_network(tenant_id)
970 router = self._get_router(tenant_id)
971 subnet = self._create_subnet(network)
972 subnet.add_to_router(router.id)
973 self.networks.append(network)
974 self.subnets.append(subnet)
975 self.routers.append(router)
976 return network, subnet, router
977
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200978
979class OrchestrationScenarioTest(OfficialClientTest):
980 """
981 Base class for orchestration scenario tests
982 """
983
984 @classmethod
Matt Riedemann11c5b642013-08-24 08:45:38 -0700985 def setUpClass(cls):
986 super(OrchestrationScenarioTest, cls).setUpClass()
987 if not cls.config.service_available.heat:
988 raise cls.skipException("Heat support is required")
989
990 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200991 def credentials(cls):
992 username = cls.config.identity.admin_username
993 password = cls.config.identity.admin_password
994 tenant_name = cls.config.identity.tenant_name
Ryan Hsuee1017c2013-12-20 12:00:34 -0800995 return username, password, tenant_name
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200996
997 def _load_template(self, base_file, file_name):
998 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
999 file_name)
1000 with open(filepath) as f:
1001 return f.read()
1002
1003 @classmethod
1004 def _stack_rand_name(cls):
Masayuki Igawa259c1132013-10-31 17:48:44 +09001005 return data_utils.rand_name(cls.__name__ + '-')
Steve Baker80252da2013-09-25 13:29:10 +12001006
1007 @classmethod
1008 def _get_default_network(cls):
1009 networks = cls.network_client.list_networks()
1010 for net in networks['networks']:
1011 if net['name'] == cls.config.compute.fixed_network_name:
1012 return net