blob: ca3a2db107c029bcac9e8fc9cd90584f173e3d43 [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
131 # add current tenant to Member group.
132 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
137 # enable test user to operate swift by adding Member role to him.
138 roles = keystone_admin.roles.list()
139 member_role = [role for role in roles if role.name == 'Member'][0]
140 # NOTE(maurosr): This is surrounded in the try-except block cause
141 # neutron tests doesn't have tenant isolation.
142 try:
143 keystone_admin.roles.add_user_role(self.identity_client.user_id,
144 member_role.id,
145 self.identity_client.tenant_id)
146 except keystoneclient.apiclient.exceptions.Conflict:
147 pass
148
149 return swiftclient.Connection(auth_url, username, password,
150 tenant_name=tenant_name,
151 auth_version='2')
152
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200153 def _get_orchestration_client(self, username=None, password=None,
154 tenant_name=None):
155 if not username:
156 username = self.config.identity.admin_username
157 if not password:
158 password = self.config.identity.admin_password
159 if not tenant_name:
160 tenant_name = self.config.identity.tenant_name
161
162 self._validate_credentials(username, password, tenant_name)
163
164 keystone = self._get_identity_client(username, password, tenant_name)
Russell Sim1fd81ce2013-11-07 17:04:21 +1100165 region = self.config.identity.region
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200166 token = keystone.auth_token
167 try:
168 endpoint = keystone.service_catalog.url_for(
Russell Sim1fd81ce2013-11-07 17:04:21 +1100169 attr='region',
170 filter_value=region,
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200171 service_type='orchestration',
172 endpoint_type='publicURL')
173 except keystoneclient.exceptions.EndpointNotFound:
174 return None
175 else:
176 return heatclient.client.Client(self.HEATCLIENT_VERSION,
177 endpoint,
178 token=token,
179 username=username,
180 password=password)
181
Matthew Treinishb86cda92013-07-29 11:22:23 -0400182 def _get_identity_client(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400183 # This identity client is not intended to check the security
184 # of the identity service, so use admin credentials by default.
Sean Dague43cd9052013-07-19 12:20:04 -0400185 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400186
187 auth_url = self.config.identity.uri
188 dscv = self.config.identity.disable_ssl_certificate_validation
189
190 return keystoneclient.v2_0.client.Client(username=username,
191 password=password,
192 tenant_name=tenant_name,
193 auth_url=auth_url,
194 insecure=dscv)
195
196 def _get_network_client(self):
197 # The intended configuration is for the network client to have
198 # admin privileges and indicate for whom resources are being
199 # created via a 'tenant_id' parameter. This will often be
200 # preferable to authenticating as a specific user because
201 # working with certain resources (public routers and networks)
202 # often requires admin privileges anyway.
203 username = self.config.identity.admin_username
204 password = self.config.identity.admin_password
205 tenant_name = self.config.identity.admin_tenant_name
206
Sean Dague43cd9052013-07-19 12:20:04 -0400207 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400208
209 auth_url = self.config.identity.uri
210 dscv = self.config.identity.disable_ssl_certificate_validation
211
Mark McClainf2982e82013-07-06 17:48:03 -0400212 return neutronclient.v2_0.client.Client(username=username,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400213 password=password,
214 tenant_name=tenant_name,
215 auth_url=auth_url,
216 insecure=dscv)
217
218
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400219class OfficialClientTest(tempest.test.BaseTestCase):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400220 """
221 Official Client test base class for scenario testing.
222
223 Official Client tests are tests that have the following characteristics:
224
225 * Test basic operations of an API, typically in an order that
226 a regular user would perform those operations
227 * Test only the correct inputs and action paths -- no fuzz or
228 random input data is sent, only valid inputs.
229 * Use only the default client tool for calling an API
230 """
231
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400232 @classmethod
233 def setUpClass(cls):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200234 super(OfficialClientTest, cls).setUpClass()
Matthew Treinishb86cda92013-07-29 11:22:23 -0400235 cls.isolated_creds = isolated_creds.IsolatedCreds(
Sean Dague6969b902014-01-28 06:48:37 -0500236 cls.__name__, tempest_client=False,
Matthew Treinish9f756a02014-01-15 10:26:07 -0500237 network_resources=cls.network_resources)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200238
Yair Fried769bbff2013-12-18 16:33:17 +0200239 username, password, tenant_name = cls.credentials()
Matthew Treinishb86cda92013-07-29 11:22:23 -0400240
241 cls.manager = OfficialClientManager(username, password, tenant_name)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400242 cls.compute_client = cls.manager.compute_client
243 cls.image_client = cls.manager.image_client
244 cls.identity_client = cls.manager.identity_client
245 cls.network_client = cls.manager.network_client
246 cls.volume_client = cls.manager.volume_client
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +0000247 cls.object_storage_client = cls.manager.object_storage_client
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200248 cls.orchestration_client = cls.manager.orchestration_client
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400249 cls.resource_keys = {}
250 cls.os_resources = []
Sean Dague6dbc6da2013-05-08 17:49:46 -0400251
252 @classmethod
Yair Frieda71cc442013-12-18 13:32:36 +0200253 def _get_credentials(cls, get_creds, prefix):
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200254 if cls.config.compute.allow_tenant_isolation:
Yair Fried769bbff2013-12-18 16:33:17 +0200255 username, tenant_name, password = get_creds()
256 else:
257 username = getattr(cls.config.identity, prefix + 'username')
258 password = getattr(cls.config.identity, prefix + 'password')
259 tenant_name = getattr(cls.config.identity, prefix + 'tenant_name')
260 return username, password, tenant_name
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200261
262 @classmethod
Yair Frieda71cc442013-12-18 13:32:36 +0200263 def credentials(cls):
264 return cls._get_credentials(cls.isolated_creds.get_primary_creds, '')
265
266 @classmethod
267 def alt_credentials(cls):
268 return cls._get_credentials(cls.isolated_creds.get_alt_creds, 'alt_')
269
270 @classmethod
271 def admin_credentials(cls):
272 return cls._get_credentials(cls.isolated_creds.get_admin_creds,
273 'admin_')
274
275 @classmethod
Sean Dague6dbc6da2013-05-08 17:49:46 -0400276 def tearDownClass(cls):
277 # NOTE(jaypipes): Because scenario tests are typically run in a
278 # specific order, and because test methods in scenario tests
279 # generally create resources in a particular order, we destroy
280 # resources in the reverse order in which resources are added to
281 # the scenario test class object
282 while cls.os_resources:
283 thing = cls.os_resources.pop()
284 LOG.debug("Deleting %r from shared resources of %s" %
285 (thing, cls.__name__))
286
287 try:
288 # OpenStack resources are assumed to have a delete()
289 # method which destroys the resource...
290 thing.delete()
291 except Exception as e:
292 # If the resource is already missing, mission accomplished.
Yair Fried4d7efa62013-11-17 17:12:29 +0200293 # add status code as workaround for bug 1247568
Attila Fazekas09925972013-12-19 16:16:49 +0100294 if (e.__class__.__name__ == 'NotFound' or
295 hasattr(e, 'status_code') and e.status_code == 404):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400296 continue
297 raise
298
299 def is_deletion_complete():
300 # Deletion testing is only required for objects whose
301 # existence cannot be checked via retrieval.
302 if isinstance(thing, dict):
303 return True
304 try:
305 thing.get()
306 except Exception as e:
307 # Clients are expected to return an exception
308 # called 'NotFound' if retrieval fails.
309 if e.__class__.__name__ == 'NotFound':
310 return True
311 raise
312 return False
313
314 # Block until resource deletion has completed or timed-out
315 tempest.test.call_until_true(is_deletion_complete, 10, 1)
Matthew Treinishb86cda92013-07-29 11:22:23 -0400316 cls.isolated_creds.clear_isolated_creds()
317 super(OfficialClientTest, cls).tearDownClass()
Sean Dague6dbc6da2013-05-08 17:49:46 -0400318
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400319 @classmethod
320 def set_resource(cls, key, thing):
321 LOG.debug("Adding %r to shared resources of %s" %
322 (thing, cls.__name__))
323 cls.resource_keys[key] = thing
324 cls.os_resources.append(thing)
325
326 @classmethod
327 def get_resource(cls, key):
328 return cls.resource_keys[key]
329
330 @classmethod
331 def remove_resource(cls, key):
332 thing = cls.resource_keys[key]
333 cls.os_resources.remove(thing)
334 del cls.resource_keys[key]
335
Steve Bakerefde7612013-09-30 11:29:23 +1300336 def status_timeout(self, things, thing_id, expected_status,
337 error_status='ERROR',
338 not_found_exception=nova_exceptions.NotFound):
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400339 """
340 Given a thing and an expected status, do a loop, sleeping
341 for a configurable amount of time, checking for the
342 expected status to show. At any time, if the returned
343 status of the thing is ERROR, fail out.
344 """
Steve Bakerefde7612013-09-30 11:29:23 +1300345 self._status_timeout(things, thing_id,
346 expected_status=expected_status,
347 error_status=error_status,
348 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900349
Steve Bakerefde7612013-09-30 11:29:23 +1300350 def delete_timeout(self, things, thing_id,
351 error_status='ERROR',
352 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900353 """
354 Given a thing, do a loop, sleeping
355 for a configurable amount of time, checking for the
356 deleted status to show. At any time, if the returned
357 status of the thing is ERROR, fail out.
358 """
359 self._status_timeout(things,
360 thing_id,
Steve Bakerefde7612013-09-30 11:29:23 +1300361 allow_notfound=True,
362 error_status=error_status,
363 not_found_exception=not_found_exception)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900364
365 def _status_timeout(self,
366 things,
367 thing_id,
368 expected_status=None,
Steve Bakerefde7612013-09-30 11:29:23 +1300369 allow_notfound=False,
370 error_status='ERROR',
371 not_found_exception=nova_exceptions.NotFound):
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900372
373 log_status = expected_status if expected_status else ''
374 if allow_notfound:
375 log_status += ' or NotFound' if log_status != '' else 'NotFound'
376
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400377 def check_status():
378 # python-novaclient has resources available to its client
379 # that all implement a get() method taking an identifier
380 # for the singular resource to retrieve.
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900381 try:
382 thing = things.get(thing_id)
Steve Bakerefde7612013-09-30 11:29:23 +1300383 except not_found_exception:
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900384 if allow_notfound:
385 return True
386 else:
387 raise
388
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400389 new_status = thing.status
Brent Eaglesc26d4522013-12-02 13:28:49 -0500390
391 # Some components are reporting error status in lower case
392 # so case sensitive comparisons can really mess things
393 # up.
394 if new_status.lower() == error_status.lower():
Ken'ichi Ohmichiab1496f2013-12-12 22:17:57 +0900395 message = ("%s failed to get to expected status. "
396 "In %s state.") % (thing, new_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200397 raise exceptions.BuildErrorException(message)
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900398 elif new_status == expected_status and expected_status is not None:
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400399 return True # All good.
400 LOG.debug("Waiting for %s to get to %s status. "
401 "Currently in %s status",
fujioka yuuichi636f8db2013-08-09 12:05:24 +0900402 thing, log_status, new_status)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400403 if not tempest.test.call_until_true(
404 check_status,
405 self.config.compute.build_timeout,
406 self.config.compute.build_interval):
Ken'ichi Ohmichiab1496f2013-12-12 22:17:57 +0900407 message = ("Timed out waiting for thing %s "
408 "to become %s") % (thing_id, log_status)
Giulio Fidente92f77192013-08-26 17:13:28 +0200409 raise exceptions.TimeoutException(message)
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400410
Yair Friedeb69f3f2013-10-10 13:18:16 +0300411 def _create_loginable_secgroup_rule_nova(self, client=None,
412 secgroup_id=None):
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900413 if client is None:
414 client = self.compute_client
415 if secgroup_id is None:
416 sgs = client.security_groups.list()
417 for sg in sgs:
418 if sg.name == 'default':
419 secgroup_id = sg.id
420
421 # These rules are intended to permit inbound ssh and icmp
422 # traffic from all sources, so no group_id is provided.
423 # Setting a group_id would only permit traffic from ports
424 # belonging to the same security group.
425 rulesets = [
426 {
427 # ssh
428 'ip_protocol': 'tcp',
429 'from_port': 22,
430 'to_port': 22,
431 'cidr': '0.0.0.0/0',
432 },
433 {
434 # ping
435 'ip_protocol': 'icmp',
436 'from_port': -1,
437 'to_port': -1,
438 'cidr': '0.0.0.0/0',
439 }
440 ]
Yair Friedeb69f3f2013-10-10 13:18:16 +0300441 rules = list()
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900442 for ruleset in rulesets:
443 sg_rule = client.security_group_rules.create(secgroup_id,
444 **ruleset)
445 self.set_resource(sg_rule.id, sg_rule)
Yair Friedeb69f3f2013-10-10 13:18:16 +0300446 rules.append(sg_rule)
447 return rules
Ken'ichi Ohmichi3c1f5192013-08-19 19:02:15 +0900448
Giulio Fidente61cadca2013-09-24 18:33:37 +0200449 def create_server(self, client=None, name=None, image=None, flavor=None,
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900450 create_kwargs={}):
Giulio Fidente61cadca2013-09-24 18:33:37 +0200451 if client is None:
452 client = self.compute_client
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900453 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900454 name = data_utils.rand_name('scenario-server-')
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900455 if image is None:
456 image = self.config.compute.image_ref
457 if flavor is None:
458 flavor = self.config.compute.flavor_ref
459 LOG.debug("Creating a server (name: %s, image: %s, flavor: %s)",
460 name, image, flavor)
461 server = client.servers.create(name, image, flavor, **create_kwargs)
Giulio Fidente92f77192013-08-26 17:13:28 +0200462 self.assertEqual(server.name, name)
463 self.set_resource(name, server)
Ken'ichi Ohmichi61f272b2013-08-15 15:58:53 +0900464 self.status_timeout(client.servers, server.id, 'ACTIVE')
465 # The instance retrieved on creation is missing network
466 # details, necessitating retrieval after it becomes active to
467 # ensure correct details.
468 server = client.servers.get(server.id)
469 self.set_resource(name, server)
470 LOG.debug("Created server: %s", server)
471 return server
472
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900473 def create_volume(self, client=None, size=1, name=None,
474 snapshot_id=None, imageRef=None):
475 if client is None:
476 client = self.volume_client
477 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900478 name = data_utils.rand_name('scenario-volume-')
Eric Windisch2d26f1b2013-09-04 17:52:16 -0700479 LOG.debug("Creating a volume (size: %s, name: %s)", size, name)
Ken'ichi Ohmichi70672df2013-08-19 18:35:19 +0900480 volume = client.volumes.create(size=size, display_name=name,
481 snapshot_id=snapshot_id,
482 imageRef=imageRef)
483 self.set_resource(name, volume)
484 self.assertEqual(name, volume.display_name)
485 self.status_timeout(client.volumes, volume.id, 'available')
486 LOG.debug("Created volume: %s", volume)
487 return volume
488
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900489 def create_server_snapshot(self, server, compute_client=None,
490 image_client=None, name=None):
491 if compute_client is None:
492 compute_client = self.compute_client
493 if image_client is None:
494 image_client = self.image_client
495 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900496 name = data_utils.rand_name('scenario-snapshot-')
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900497 LOG.debug("Creating a snapshot image for server: %s", server.name)
498 image_id = compute_client.servers.create_image(server, name)
499 self.addCleanup(image_client.images.delete, image_id)
500 self.status_timeout(image_client.images, image_id, 'active')
501 snapshot_image = image_client.images.get(image_id)
Chang Bo Guofc77e932013-09-16 17:38:26 -0700502 self.assertEqual(name, snapshot_image.name)
Ken'ichi Ohmichia4912232013-08-26 14:03:25 +0900503 LOG.debug("Created snapshot image %s for server %s",
504 snapshot_image.name, server.name)
505 return snapshot_image
506
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900507 def create_keypair(self, client=None, name=None):
508 if client is None:
509 client = self.compute_client
510 if name is None:
Masayuki Igawa259c1132013-10-31 17:48:44 +0900511 name = data_utils.rand_name('scenario-keypair-')
Ken'ichi Ohmichi599d1b82013-08-19 18:48:37 +0900512 keypair = client.keypairs.create(name)
513 self.assertEqual(keypair.name, name)
514 self.set_resource(name, keypair)
515 return keypair
516
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +0900517 def get_remote_client(self, server_or_ip, username=None, private_key=None):
518 if isinstance(server_or_ip, basestring):
519 ip = server_or_ip
520 else:
521 network_name_for_ssh = self.config.compute.network_for_ssh
522 ip = server_or_ip.networks[network_name_for_ssh][0]
523 if username is None:
524 username = self.config.scenario.ssh_user
525 if private_key is None:
526 private_key = self.keypair.private_key
527 return RemoteClient(ip, username, pkey=private_key)
528
Nachi Ueno95b41282014-01-15 06:54:21 -0800529 def _log_console_output(self, servers=None):
530 if not servers:
531 servers = self.compute_client.servers.list()
532 for server in servers:
533 LOG.debug('Console output for %s', server.id)
534 LOG.debug(server.get_console_output())
535
Sean Dague6dbc6da2013-05-08 17:49:46 -0400536
537class NetworkScenarioTest(OfficialClientTest):
538 """
539 Base class for network scenario tests
540 """
541
542 @classmethod
543 def check_preconditions(cls):
Matthew Treinishfaa340d2013-07-19 16:26:21 -0400544 if (cls.config.service_available.neutron):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400545 cls.enabled = True
Attila Fazekasc3a095b2013-08-17 09:15:44 +0200546 # verify that neutron_available is telling the truth
Sean Dague6dbc6da2013-05-08 17:49:46 -0400547 try:
548 cls.network_client.list_networks()
549 except exc.EndpointNotFound:
550 cls.enabled = False
551 raise
552 else:
553 cls.enabled = False
Mark McClainf2982e82013-07-06 17:48:03 -0400554 msg = 'Neutron not available'
Sean Dague6dbc6da2013-05-08 17:49:46 -0400555 raise cls.skipException(msg)
556
557 @classmethod
558 def setUpClass(cls):
559 super(NetworkScenarioTest, cls).setUpClass()
Miguel Lavalleb8fabc52013-08-23 11:19:57 -0500560 if cls.config.compute.allow_tenant_isolation:
561 cls.tenant_id = cls.isolated_creds.get_primary_tenant().id
562 else:
563 cls.tenant_id = cls.manager._get_identity_client(
564 cls.config.identity.username,
565 cls.config.identity.password,
566 cls.config.identity.tenant_name).tenant_id
Sean Dague6dbc6da2013-05-08 17:49:46 -0400567
Sean Dague6dbc6da2013-05-08 17:49:46 -0400568 def _create_network(self, tenant_id, namestart='network-smoke-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900569 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400570 body = dict(
571 network=dict(
572 name=name,
573 tenant_id=tenant_id,
574 ),
575 )
576 result = self.network_client.create_network(body=body)
577 network = net_common.DeletableNetwork(client=self.network_client,
578 **result['network'])
579 self.assertEqual(network.name, name)
580 self.set_resource(name, network)
581 return network
582
583 def _list_networks(self):
584 nets = self.network_client.list_networks()
585 return nets['networks']
586
587 def _list_subnets(self):
588 subnets = self.network_client.list_subnets()
589 return subnets['subnets']
590
591 def _list_routers(self):
592 routers = self.network_client.list_routers()
593 return routers['routers']
594
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000595 def _list_ports(self):
596 ports = self.network_client.list_ports()
597 return ports['ports']
598
599 def _get_tenant_own_network_num(self, tenant_id):
600 nets = self._list_networks()
601 ownnets = [value for value in nets if tenant_id == value['tenant_id']]
602 return len(ownnets)
603
604 def _get_tenant_own_subnet_num(self, tenant_id):
605 subnets = self._list_subnets()
606 ownsubnets = ([value for value in subnets
607 if tenant_id == value['tenant_id']])
608 return len(ownsubnets)
609
610 def _get_tenant_own_port_num(self, tenant_id):
611 ports = self._list_ports()
612 ownports = ([value for value in ports
613 if tenant_id == value['tenant_id']])
614 return len(ownports)
615
Sean Dague6dbc6da2013-05-08 17:49:46 -0400616 def _create_subnet(self, network, namestart='subnet-smoke-'):
617 """
618 Create a subnet for the given network within the cidr block
619 configured for tenant networks.
620 """
621 cfg = self.config.network
622 tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
623 result = None
624 # Repeatedly attempt subnet creation with sequential cidr
625 # blocks until an unallocated block is found.
626 for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
627 body = dict(
628 subnet=dict(
629 ip_version=4,
630 network_id=network.id,
631 tenant_id=network.tenant_id,
632 cidr=str(subnet_cidr),
633 ),
634 )
635 try:
636 result = self.network_client.create_subnet(body=body)
637 break
Mark McClainf2982e82013-07-06 17:48:03 -0400638 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400639 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
640 if not is_overlapping_cidr:
641 raise
642 self.assertIsNotNone(result, 'Unable to allocate tenant network')
643 subnet = net_common.DeletableSubnet(client=self.network_client,
644 **result['subnet'])
645 self.assertEqual(subnet.cidr, str(subnet_cidr))
Masayuki Igawa259c1132013-10-31 17:48:44 +0900646 self.set_resource(data_utils.rand_name(namestart), subnet)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400647 return subnet
648
649 def _create_port(self, network, namestart='port-quotatest-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900650 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400651 body = dict(
652 port=dict(name=name,
653 network_id=network.id,
654 tenant_id=network.tenant_id))
655 result = self.network_client.create_port(body=body)
656 self.assertIsNotNone(result, 'Unable to allocate port')
657 port = net_common.DeletablePort(client=self.network_client,
658 **result['port'])
659 self.set_resource(name, port)
660 return port
661
Yair Fried05db2522013-11-18 11:02:10 +0200662 def _get_server_port_id(self, server):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400663 result = self.network_client.list_ports(device_id=server.id)
664 ports = result.get('ports', [])
665 self.assertEqual(len(ports), 1,
666 "Unable to determine which port to target.")
Yair Fried05db2522013-11-18 11:02:10 +0200667 return ports[0]['id']
668
669 def _create_floating_ip(self, server, external_network_id):
670 port_id = self._get_server_port_id(server)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400671 body = dict(
672 floatingip=dict(
673 floating_network_id=external_network_id,
674 port_id=port_id,
675 tenant_id=server.tenant_id,
676 )
677 )
678 result = self.network_client.create_floatingip(body=body)
679 floating_ip = net_common.DeletableFloatingIp(
680 client=self.network_client,
681 **result['floatingip'])
Masayuki Igawa259c1132013-10-31 17:48:44 +0900682 self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400683 return floating_ip
684
Yair Fried05db2522013-11-18 11:02:10 +0200685 def _associate_floating_ip(self, floating_ip, server):
686 port_id = self._get_server_port_id(server)
687 floating_ip.update(port_id=port_id)
688 self.assertEqual(port_id, floating_ip.port_id)
689 return floating_ip
690
Yair Fried9a551c42013-12-15 14:59:34 +0200691 def _disassociate_floating_ip(self, floating_ip):
692 """
693 :param floating_ip: type DeletableFloatingIp
694 """
695 floating_ip.update(port_id=None)
696 self.assertEqual(None, floating_ip.port_id)
697 return floating_ip
698
699 def _ping_ip_address(self, ip_address, should_succeed=True):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400700 cmd = ['ping', '-c1', '-w1', ip_address]
701
702 def ping():
703 proc = subprocess.Popen(cmd,
704 stdout=subprocess.PIPE,
705 stderr=subprocess.PIPE)
706 proc.wait()
Yair Fried9a551c42013-12-15 14:59:34 +0200707 return (proc.returncode == 0) == should_succeed
Sean Dague6dbc6da2013-05-08 17:49:46 -0400708
Nachi Ueno6d580be2013-07-24 10:58:11 -0700709 return tempest.test.call_until_true(
710 ping, self.config.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000711
Yair Fried9a551c42013-12-15 14:59:34 +0200712 def _check_vm_connectivity(self, ip_address,
713 username=None,
714 private_key=None,
715 should_connect=True):
716 """
717 :param ip_address: server to test against
718 :param username: server's ssh username
719 :param private_key: server's ssh private key to be used
720 :param should_connect: True/False indicates positive/negative test
721 positive - attempt ping and ssh
722 negative - attempt ping and fail if succeed
723
724 :raises: AssertError if the result of the connectivity check does
725 not match the value of the should_connect param
726 """
727 if should_connect:
728 msg = "Timed out waiting for %s to become reachable" % ip_address
729 else:
730 msg = "ip address %s is reachable" % ip_address
731 self.assertTrue(self._ping_ip_address(ip_address,
732 should_succeed=should_connect),
733 msg=msg)
734 if should_connect:
735 # no need to check ssh for negative connectivity
Attila Fazekasad7ef7d2013-11-20 10:12:53 +0100736 linux_client = self.get_remote_client(ip_address, username,
737 private_key)
738 linux_client.validate_authentication()
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200739
Yair Friedeb69f3f2013-10-10 13:18:16 +0300740 def _create_security_group_nova(self, client=None,
741 namestart='secgroup-smoke-',
742 tenant_id=None):
743 if client is None:
744 client = self.compute_client
745 # Create security group
746 sg_name = data_utils.rand_name(namestart)
747 sg_desc = sg_name + " description"
748 secgroup = client.security_groups.create(sg_name, sg_desc)
749 self.assertEqual(secgroup.name, sg_name)
750 self.assertEqual(secgroup.description, sg_desc)
751 self.set_resource(sg_name, secgroup)
752
753 # Add rules to the security group
754 self._create_loginable_secgroup_rule_nova(client, secgroup.id)
755
756 return secgroup
757
758 def _create_security_group_neutron(self, tenant_id, client=None,
759 namestart='secgroup-smoke-'):
760 if client is None:
761 client = self.network_client
762 secgroup = self._create_empty_security_group(namestart=namestart,
763 client=client,
764 tenant_id=tenant_id)
765
766 # Add rules to the security group
767 rules = self._create_loginable_secgroup_rule_neutron(secgroup=secgroup)
768 for rule in rules:
769 self.assertEqual(tenant_id, rule.tenant_id)
770 self.assertEqual(secgroup.id, rule.security_group_id)
771 return secgroup
772
773 def _create_empty_security_group(self, tenant_id, client=None,
774 namestart='secgroup-smoke-'):
775 """Create a security group without rules.
776
777 Default rules will be created:
778 - IPv4 egress to any
779 - IPv6 egress to any
780
781 :param tenant_id: secgroup will be created in this tenant
782 :returns: DeletableSecurityGroup -- containing the secgroup created
783 """
784 if client is None:
785 client = self.network_client
786 sg_name = data_utils.rand_name(namestart)
787 sg_desc = sg_name + " description"
788 sg_dict = dict(name=sg_name,
789 description=sg_desc)
790 sg_dict['tenant_id'] = tenant_id
791 body = dict(security_group=sg_dict)
792 result = client.create_security_group(body=body)
793 secgroup = net_common.DeletableSecurityGroup(
794 client=client,
795 **result['security_group']
796 )
797 self.assertEqual(secgroup.name, sg_name)
798 self.assertEqual(tenant_id, secgroup.tenant_id)
799 self.assertEqual(secgroup.description, sg_desc)
800 self.set_resource(sg_name, secgroup)
801 return secgroup
802
803 def _default_security_group(self, tenant_id, client=None):
804 """Get default secgroup for given tenant_id.
805
806 :returns: DeletableSecurityGroup -- default secgroup for given tenant
807 """
808 if client is None:
809 client = self.network_client
810 sgs = [
811 sg for sg in client.list_security_groups().values()[0]
812 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
813 ]
814 msg = "No default security group for tenant %s." % (tenant_id)
815 self.assertTrue(len(sgs) > 0, msg)
816 if len(sgs) > 1:
817 msg = "Found %d default security groups" % len(sgs)
818 raise exc.NeutronClientNoUniqueMatch(msg=msg)
819 return net_common.DeletableSecurityGroup(client=client,
820 **sgs[0])
821
822 def _create_security_group_rule(self, client=None, secgroup=None,
823 tenant_id=None, **kwargs):
824 """Create a rule from a dictionary of rule parameters.
825
826 Create a rule in a secgroup. if secgroup not defined will search for
827 default secgroup in tenant_id.
828
829 :param secgroup: type DeletableSecurityGroup.
830 :param secgroup_id: search for secgroup by id
831 default -- choose default secgroup for given tenant_id
832 :param tenant_id: if secgroup not passed -- the tenant in which to
833 search for default secgroup
834 :param kwargs: a dictionary containing rule parameters:
835 for example, to allow incoming ssh:
836 rule = {
837 direction: 'ingress'
838 protocol:'tcp',
839 port_range_min: 22,
840 port_range_max: 22
841 }
842 """
843 if client is None:
844 client = self.network_client
845 if secgroup is None:
846 secgroup = self._default_security_group(tenant_id)
847
848 ruleset = dict(security_group_id=secgroup.id,
849 tenant_id=secgroup.tenant_id,
850 )
851 ruleset.update(kwargs)
852
853 body = dict(security_group_rule=dict(ruleset))
854 sg_rule = client.create_security_group_rule(body=body)
855 sg_rule = net_common.DeletableSecurityGroupRule(
856 client=client,
857 **sg_rule['security_group_rule']
858 )
859 self.set_resource(sg_rule.id, sg_rule)
860 self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
861 self.assertEqual(secgroup.id, sg_rule.security_group_id)
862
863 return sg_rule
864
865 def _create_loginable_secgroup_rule_neutron(self, client=None,
866 secgroup=None):
867 """These rules are intended to permit inbound ssh and icmp
868 traffic from all sources, so no group_id is provided.
869 Setting a group_id would only permit traffic from ports
870 belonging to the same security group.
871 """
872
873 if client is None:
874 client = self.network_client
875 rules = []
876 rulesets = [
877 dict(
878 # ssh
879 protocol='tcp',
880 port_range_min=22,
881 port_range_max=22,
882 ),
883 dict(
884 # ping
885 protocol='icmp',
886 )
887 ]
888 for ruleset in rulesets:
889 for r_direction in ['ingress', 'egress']:
890 ruleset['direction'] = r_direction
891 try:
892 sg_rule = self._create_security_group_rule(
893 client=client, secgroup=secgroup, **ruleset)
894 except exc.NeutronClientException as ex:
895 # if rule already exist - skip rule and continue
896 if not (ex.status_code is 409 and 'Security group rule'
897 ' already exists' in ex.message):
898 raise ex
899 else:
900 self.assertEqual(r_direction, sg_rule.direction)
901 rules.append(sg_rule)
902
903 return rules
904
Yair Fried5f670ab2013-12-09 09:26:51 +0200905 def _ssh_to_server(self, server, private_key):
906 ssh_login = self.config.compute.image_ssh_user
907 return self.get_remote_client(server,
908 username=ssh_login,
909 private_key=private_key)
910
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000911 def _show_quota_network(self, tenant_id):
912 quota = self.network_client.show_quota(tenant_id)
913 return quota['quota']['network']
914
915 def _show_quota_subnet(self, tenant_id):
916 quota = self.network_client.show_quota(tenant_id)
917 return quota['quota']['subnet']
918
919 def _show_quota_port(self, tenant_id):
920 quota = self.network_client.show_quota(tenant_id)
921 return quota['quota']['port']
922
Yair Fried4d7efa62013-11-17 17:12:29 +0200923 def _get_router(self, tenant_id):
924 """Retrieve a router for the given tenant id.
925
926 If a public router has been configured, it will be returned.
927
928 If a public router has not been configured, but a public
929 network has, a tenant router will be created and returned that
930 routes traffic to the public network.
931 """
932 router_id = self.config.network.public_router_id
933 network_id = self.config.network.public_network_id
934 if router_id:
935 result = self.network_client.show_router(router_id)
936 return net_common.AttributeDict(**result['router'])
937 elif network_id:
938 router = self._create_router(tenant_id)
939 router.add_gateway(network_id)
940 return router
941 else:
942 raise Exception("Neither of 'public_router_id' or "
943 "'public_network_id' has been defined.")
944
945 def _create_router(self, tenant_id, namestart='router-smoke-'):
946 name = data_utils.rand_name(namestart)
947 body = dict(
948 router=dict(
949 name=name,
950 admin_state_up=True,
951 tenant_id=tenant_id,
952 ),
953 )
954 result = self.network_client.create_router(body=body)
955 router = net_common.DeletableRouter(client=self.network_client,
956 **result['router'])
957 self.assertEqual(router.name, name)
958 self.set_resource(name, router)
959 return router
960
961 def _create_networks(self, tenant_id=None):
962 """Create a network with a subnet connected to a router.
963
964 :returns: network, subnet, router
965 """
966 if tenant_id is None:
967 tenant_id = self.tenant_id
968 network = self._create_network(tenant_id)
969 router = self._get_router(tenant_id)
970 subnet = self._create_subnet(network)
971 subnet.add_to_router(router.id)
972 self.networks.append(network)
973 self.subnets.append(subnet)
974 self.routers.append(router)
975 return network, subnet, router
976
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200977
978class OrchestrationScenarioTest(OfficialClientTest):
979 """
980 Base class for orchestration scenario tests
981 """
982
983 @classmethod
Matt Riedemann11c5b642013-08-24 08:45:38 -0700984 def setUpClass(cls):
985 super(OrchestrationScenarioTest, cls).setUpClass()
986 if not cls.config.service_available.heat:
987 raise cls.skipException("Heat support is required")
988
989 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200990 def credentials(cls):
991 username = cls.config.identity.admin_username
992 password = cls.config.identity.admin_password
993 tenant_name = cls.config.identity.tenant_name
Ryan Hsuee1017c2013-12-20 12:00:34 -0800994 return username, password, tenant_name
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200995
996 def _load_template(self, base_file, file_name):
997 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
998 file_name)
999 with open(filepath) as f:
1000 return f.read()
1001
1002 @classmethod
1003 def _stack_rand_name(cls):
Masayuki Igawa259c1132013-10-31 17:48:44 +09001004 return data_utils.rand_name(cls.__name__ + '-')
Steve Baker80252da2013-09-25 13:29:10 +12001005
1006 @classmethod
1007 def _get_default_network(cls):
1008 networks = cls.network_client.list_networks()
1009 for net in networks['networks']:
1010 if net['name'] == cls.config.compute.fixed_network_name:
1011 return net