blob: d3d34d010889559bc3a66d8a76e5fcfc0d11eac4 [file] [log] [blame]
Sean Dague6dbc6da2013-05-08 17:49:46 -04001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
ZhiQiang Fan39f97222013-09-20 04:49:44 +08003# Copyright 2012 OpenStack Foundation
Sean Dague6dbc6da2013-05-08 17:49:46 -04004# Copyright 2013 IBM Corp.
5# All Rights Reserved.
6#
7# Licensed under the Apache License, Version 2.0 (the "License"); you may
8# not use this file except in compliance with the License. You may obtain
9# a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16# License for the specific language governing permissions and limitations
17# under the License.
18
Attila Fazekasfb7552a2013-08-27 13:02:26 +020019import logging
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120020import os
Sean Dague6dbc6da2013-05-08 17:49:46 -040021import subprocess
22
23# Default client libs
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +090024import cinderclient.client
Sean Dague6dbc6da2013-05-08 17:49:46 -040025import glanceclient
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120026import heatclient.client
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +000027import keystoneclient.apiclient.exceptions
Sean Dague6dbc6da2013-05-08 17:49:46 -040028import keystoneclient.v2_0.client
29import netaddr
Mark McClainf2982e82013-07-06 17:48:03 -040030from neutronclient.common import exceptions as exc
31import neutronclient.v2_0.client
Sean Dague6dbc6da2013-05-08 17:49:46 -040032import novaclient.client
fujioka yuuichi636f8db2013-08-09 12:05:24 +090033from novaclient import exceptions as nova_exceptions
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +000034import swiftclient
Sean Dague6dbc6da2013-05-08 17:49:46 -040035
Sean Dague1937d092013-05-17 16:36:38 -040036from tempest.api.network import common as net_common
Matthew Treinishb86cda92013-07-29 11:22:23 -040037from tempest.common import isolated_creds
Masayuki Igawa259c1132013-10-31 17:48:44 +090038from tempest.common.utils import data_utils
Ken'ichi Ohmichib3aa9122013-08-22 23:27:25 +090039from tempest.common.utils.linux.remote_client import RemoteClient
Giulio Fidente92f77192013-08-26 17:13:28 +020040from tempest import exceptions
Sean Dague6dbc6da2013-05-08 17:49:46 -040041import tempest.manager
Attila Fazekasfb7552a2013-08-27 13:02:26 +020042from tempest.openstack.common import log
Sean Dague6dbc6da2013-05-08 17:49:46 -040043import tempest.test
Sean Dague6dbc6da2013-05-08 17:49:46 -040044
45
Attila Fazekasfb7552a2013-08-27 13:02:26 +020046LOG = log.getLogger(__name__)
47
48# NOTE(afazekas): Workaround for the stdout logging
49LOG_nova_client = logging.getLogger('novaclient.client')
50LOG_nova_client.addHandler(log.NullHandler())
51
52LOG_cinder_client = logging.getLogger('cinderclient.client')
53LOG_cinder_client.addHandler(log.NullHandler())
Sean Dague6dbc6da2013-05-08 17:49:46 -040054
55
56class OfficialClientManager(tempest.manager.Manager):
57 """
58 Manager that provides access to the official python clients for
59 calling various OpenStack APIs.
60 """
61
62 NOVACLIENT_VERSION = '2'
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +090063 CINDERCLIENT_VERSION = '1'
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120064 HEATCLIENT_VERSION = '1'
Sean Dague6dbc6da2013-05-08 17:49:46 -040065
Matthew Treinishb86cda92013-07-29 11:22:23 -040066 def __init__(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -040067 super(OfficialClientManager, self).__init__()
Matthew Treinishb86cda92013-07-29 11:22:23 -040068 self.compute_client = self._get_compute_client(username,
69 password,
70 tenant_name)
71 self.identity_client = self._get_identity_client(username,
72 password,
73 tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -040074 self.image_client = self._get_image_client()
Sean Dague6dbc6da2013-05-08 17:49:46 -040075 self.network_client = self._get_network_client()
Matthew Treinishb86cda92013-07-29 11:22:23 -040076 self.volume_client = self._get_volume_client(username,
77 password,
78 tenant_name)
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +000079 self.object_storage_client = self._get_object_storage_client(
80 username,
81 password,
82 tenant_name)
Steve Bakerdd7c6ce2013-06-24 14:46:47 +120083 self.orchestration_client = self._get_orchestration_client(
84 username,
85 password,
86 tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -040087
Matthew Treinishb86cda92013-07-29 11:22:23 -040088 def _get_compute_client(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -040089 # Novaclient will not execute operations for anyone but the
90 # identified user, so a new client needs to be created for
91 # each user that operations need to be performed for.
Sean Dague43cd9052013-07-19 12:20:04 -040092 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -040093
94 auth_url = self.config.identity.uri
95 dscv = self.config.identity.disable_ssl_certificate_validation
Russell Sim1fd81ce2013-11-07 17:04:21 +110096 region = self.config.identity.region
Sean Dague6dbc6da2013-05-08 17:49:46 -040097
98 client_args = (username, password, tenant_name, auth_url)
99
100 # Create our default Nova client to use in testing
101 service_type = self.config.compute.catalog_type
102 return novaclient.client.Client(self.NOVACLIENT_VERSION,
103 *client_args,
104 service_type=service_type,
Russell Sim1fd81ce2013-11-07 17:04:21 +1100105 region_name=region,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400106 no_cache=True,
Attila Fazekasfb7552a2013-08-27 13:02:26 +0200107 insecure=dscv,
108 http_log_debug=True)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400109
110 def _get_image_client(self):
Matthew Treinishb86cda92013-07-29 11:22:23 -0400111 token = self.identity_client.auth_token
Russell Sim1fd81ce2013-11-07 17:04:21 +1100112 region = self.config.identity.region
Matthew Treinishb86cda92013-07-29 11:22:23 -0400113 endpoint = self.identity_client.service_catalog.url_for(
Russell Sim1fd81ce2013-11-07 17:04:21 +1100114 attr='region', filter_value=region,
Matthew Treinishb86cda92013-07-29 11:22:23 -0400115 service_type='image', endpoint_type='publicURL')
Sean Dague6dbc6da2013-05-08 17:49:46 -0400116 dscv = self.config.identity.disable_ssl_certificate_validation
117 return glanceclient.Client('1', endpoint=endpoint, token=token,
118 insecure=dscv)
119
Matthew Treinishb86cda92013-07-29 11:22:23 -0400120 def _get_volume_client(self, username, password, tenant_name):
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +0900121 auth_url = self.config.identity.uri
Russell Sim1fd81ce2013-11-07 17:04:21 +1100122 region = self.config.identity.region
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +0900123 return cinderclient.client.Client(self.CINDERCLIENT_VERSION,
124 username,
125 password,
126 tenant_name,
Attila Fazekasfb7552a2013-08-27 13:02:26 +0200127 auth_url,
Russell Sim1fd81ce2013-11-07 17:04:21 +1100128 region_name=region,
Attila Fazekasfb7552a2013-08-27 13:02:26 +0200129 http_log_debug=True)
Masayuki Igawa73d9f3a2013-05-24 10:30:01 +0900130
Mauro S. M. Rodriguese86ed042013-12-12 18:56:00 +0000131 def _get_object_storage_client(self, username, password, tenant_name):
132 auth_url = self.config.identity.uri
133 # add current tenant to Member group.
134 keystone_admin = self._get_identity_client(
135 self.config.identity.admin_username,
136 self.config.identity.admin_password,
137 self.config.identity.admin_tenant_name)
138
139 # enable test user to operate swift by adding Member role to him.
140 roles = keystone_admin.roles.list()
141 member_role = [role for role in roles if role.name == 'Member'][0]
142 # NOTE(maurosr): This is surrounded in the try-except block cause
143 # neutron tests doesn't have tenant isolation.
144 try:
145 keystone_admin.roles.add_user_role(self.identity_client.user_id,
146 member_role.id,
147 self.identity_client.tenant_id)
148 except keystoneclient.apiclient.exceptions.Conflict:
149 pass
150
151 return swiftclient.Connection(auth_url, username, password,
152 tenant_name=tenant_name,
153 auth_version='2')
154
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200155 def _get_orchestration_client(self, username=None, password=None,
156 tenant_name=None):
157 if not username:
158 username = self.config.identity.admin_username
159 if not password:
160 password = self.config.identity.admin_password
161 if not tenant_name:
162 tenant_name = self.config.identity.tenant_name
163
164 self._validate_credentials(username, password, tenant_name)
165
166 keystone = self._get_identity_client(username, password, tenant_name)
Russell Sim1fd81ce2013-11-07 17:04:21 +1100167 region = self.config.identity.region
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200168 token = keystone.auth_token
169 try:
170 endpoint = keystone.service_catalog.url_for(
Russell Sim1fd81ce2013-11-07 17:04:21 +1100171 attr='region',
172 filter_value=region,
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200173 service_type='orchestration',
174 endpoint_type='publicURL')
175 except keystoneclient.exceptions.EndpointNotFound:
176 return None
177 else:
178 return heatclient.client.Client(self.HEATCLIENT_VERSION,
179 endpoint,
180 token=token,
181 username=username,
182 password=password)
183
Matthew Treinishb86cda92013-07-29 11:22:23 -0400184 def _get_identity_client(self, username, password, tenant_name):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400185 # This identity client is not intended to check the security
186 # of the identity service, so use admin credentials by default.
Sean Dague43cd9052013-07-19 12:20:04 -0400187 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400188
189 auth_url = self.config.identity.uri
190 dscv = self.config.identity.disable_ssl_certificate_validation
191
192 return keystoneclient.v2_0.client.Client(username=username,
193 password=password,
194 tenant_name=tenant_name,
195 auth_url=auth_url,
196 insecure=dscv)
197
198 def _get_network_client(self):
199 # The intended configuration is for the network client to have
200 # admin privileges and indicate for whom resources are being
201 # created via a 'tenant_id' parameter. This will often be
202 # preferable to authenticating as a specific user because
203 # working with certain resources (public routers and networks)
204 # often requires admin privileges anyway.
205 username = self.config.identity.admin_username
206 password = self.config.identity.admin_password
207 tenant_name = self.config.identity.admin_tenant_name
208
Sean Dague43cd9052013-07-19 12:20:04 -0400209 self._validate_credentials(username, password, tenant_name)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400210
211 auth_url = self.config.identity.uri
212 dscv = self.config.identity.disable_ssl_certificate_validation
213
Mark McClainf2982e82013-07-06 17:48:03 -0400214 return neutronclient.v2_0.client.Client(username=username,
Sean Dague6dbc6da2013-05-08 17:49:46 -0400215 password=password,
216 tenant_name=tenant_name,
217 auth_url=auth_url,
218 insecure=dscv)
219
220
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400221class OfficialClientTest(tempest.test.BaseTestCase):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400222 """
223 Official Client test base class for scenario testing.
224
225 Official Client tests are tests that have the following characteristics:
226
227 * Test basic operations of an API, typically in an order that
228 a regular user would perform those operations
229 * Test only the correct inputs and action paths -- no fuzz or
230 random input data is sent, only valid inputs.
231 * Use only the default client tool for calling an API
232 """
233
Matthew Treinish0ae79ce2013-08-08 14:31:05 -0400234 @classmethod
235 def setUpClass(cls):
Attila Fazekasf86fa312013-07-30 19:56:39 +0200236 super(OfficialClientTest, cls).setUpClass()
Matthew Treinishb86cda92013-07-29 11:22:23 -0400237 cls.isolated_creds = isolated_creds.IsolatedCreds(
238 __name__, tempest_client=False)
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
Sean Dague6dbc6da2013-05-08 17:49:46 -0400530
531class NetworkScenarioTest(OfficialClientTest):
532 """
533 Base class for network scenario tests
534 """
535
536 @classmethod
537 def check_preconditions(cls):
Matthew Treinishfaa340d2013-07-19 16:26:21 -0400538 if (cls.config.service_available.neutron):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400539 cls.enabled = True
Attila Fazekasc3a095b2013-08-17 09:15:44 +0200540 # verify that neutron_available is telling the truth
Sean Dague6dbc6da2013-05-08 17:49:46 -0400541 try:
542 cls.network_client.list_networks()
543 except exc.EndpointNotFound:
544 cls.enabled = False
545 raise
546 else:
547 cls.enabled = False
Mark McClainf2982e82013-07-06 17:48:03 -0400548 msg = 'Neutron not available'
Sean Dague6dbc6da2013-05-08 17:49:46 -0400549 raise cls.skipException(msg)
550
551 @classmethod
552 def setUpClass(cls):
553 super(NetworkScenarioTest, cls).setUpClass()
Miguel Lavalleb8fabc52013-08-23 11:19:57 -0500554 if cls.config.compute.allow_tenant_isolation:
555 cls.tenant_id = cls.isolated_creds.get_primary_tenant().id
556 else:
557 cls.tenant_id = cls.manager._get_identity_client(
558 cls.config.identity.username,
559 cls.config.identity.password,
560 cls.config.identity.tenant_name).tenant_id
Sean Dague6dbc6da2013-05-08 17:49:46 -0400561
Sean Dague6dbc6da2013-05-08 17:49:46 -0400562 def _create_network(self, tenant_id, namestart='network-smoke-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900563 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400564 body = dict(
565 network=dict(
566 name=name,
567 tenant_id=tenant_id,
568 ),
569 )
570 result = self.network_client.create_network(body=body)
571 network = net_common.DeletableNetwork(client=self.network_client,
572 **result['network'])
573 self.assertEqual(network.name, name)
574 self.set_resource(name, network)
575 return network
576
577 def _list_networks(self):
578 nets = self.network_client.list_networks()
579 return nets['networks']
580
581 def _list_subnets(self):
582 subnets = self.network_client.list_subnets()
583 return subnets['subnets']
584
585 def _list_routers(self):
586 routers = self.network_client.list_routers()
587 return routers['routers']
588
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000589 def _list_ports(self):
590 ports = self.network_client.list_ports()
591 return ports['ports']
592
593 def _get_tenant_own_network_num(self, tenant_id):
594 nets = self._list_networks()
595 ownnets = [value for value in nets if tenant_id == value['tenant_id']]
596 return len(ownnets)
597
598 def _get_tenant_own_subnet_num(self, tenant_id):
599 subnets = self._list_subnets()
600 ownsubnets = ([value for value in subnets
601 if tenant_id == value['tenant_id']])
602 return len(ownsubnets)
603
604 def _get_tenant_own_port_num(self, tenant_id):
605 ports = self._list_ports()
606 ownports = ([value for value in ports
607 if tenant_id == value['tenant_id']])
608 return len(ownports)
609
Sean Dague6dbc6da2013-05-08 17:49:46 -0400610 def _create_subnet(self, network, namestart='subnet-smoke-'):
611 """
612 Create a subnet for the given network within the cidr block
613 configured for tenant networks.
614 """
615 cfg = self.config.network
616 tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
617 result = None
618 # Repeatedly attempt subnet creation with sequential cidr
619 # blocks until an unallocated block is found.
620 for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
621 body = dict(
622 subnet=dict(
623 ip_version=4,
624 network_id=network.id,
625 tenant_id=network.tenant_id,
626 cidr=str(subnet_cidr),
627 ),
628 )
629 try:
630 result = self.network_client.create_subnet(body=body)
631 break
Mark McClainf2982e82013-07-06 17:48:03 -0400632 except exc.NeutronClientException as e:
Sean Dague6dbc6da2013-05-08 17:49:46 -0400633 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
634 if not is_overlapping_cidr:
635 raise
636 self.assertIsNotNone(result, 'Unable to allocate tenant network')
637 subnet = net_common.DeletableSubnet(client=self.network_client,
638 **result['subnet'])
639 self.assertEqual(subnet.cidr, str(subnet_cidr))
Masayuki Igawa259c1132013-10-31 17:48:44 +0900640 self.set_resource(data_utils.rand_name(namestart), subnet)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400641 return subnet
642
643 def _create_port(self, network, namestart='port-quotatest-'):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900644 name = data_utils.rand_name(namestart)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400645 body = dict(
646 port=dict(name=name,
647 network_id=network.id,
648 tenant_id=network.tenant_id))
649 result = self.network_client.create_port(body=body)
650 self.assertIsNotNone(result, 'Unable to allocate port')
651 port = net_common.DeletablePort(client=self.network_client,
652 **result['port'])
653 self.set_resource(name, port)
654 return port
655
Yair Fried05db2522013-11-18 11:02:10 +0200656 def _get_server_port_id(self, server):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400657 result = self.network_client.list_ports(device_id=server.id)
658 ports = result.get('ports', [])
659 self.assertEqual(len(ports), 1,
660 "Unable to determine which port to target.")
Yair Fried05db2522013-11-18 11:02:10 +0200661 return ports[0]['id']
662
663 def _create_floating_ip(self, server, external_network_id):
664 port_id = self._get_server_port_id(server)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400665 body = dict(
666 floatingip=dict(
667 floating_network_id=external_network_id,
668 port_id=port_id,
669 tenant_id=server.tenant_id,
670 )
671 )
672 result = self.network_client.create_floatingip(body=body)
673 floating_ip = net_common.DeletableFloatingIp(
674 client=self.network_client,
675 **result['floatingip'])
Masayuki Igawa259c1132013-10-31 17:48:44 +0900676 self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
Sean Dague6dbc6da2013-05-08 17:49:46 -0400677 return floating_ip
678
Yair Fried05db2522013-11-18 11:02:10 +0200679 def _associate_floating_ip(self, floating_ip, server):
680 port_id = self._get_server_port_id(server)
681 floating_ip.update(port_id=port_id)
682 self.assertEqual(port_id, floating_ip.port_id)
683 return floating_ip
684
Yair Fried9a551c42013-12-15 14:59:34 +0200685 def _disassociate_floating_ip(self, floating_ip):
686 """
687 :param floating_ip: type DeletableFloatingIp
688 """
689 floating_ip.update(port_id=None)
690 self.assertEqual(None, floating_ip.port_id)
691 return floating_ip
692
693 def _ping_ip_address(self, ip_address, should_succeed=True):
Sean Dague6dbc6da2013-05-08 17:49:46 -0400694 cmd = ['ping', '-c1', '-w1', ip_address]
695
696 def ping():
697 proc = subprocess.Popen(cmd,
698 stdout=subprocess.PIPE,
699 stderr=subprocess.PIPE)
700 proc.wait()
Yair Fried9a551c42013-12-15 14:59:34 +0200701 return (proc.returncode == 0) == should_succeed
Sean Dague6dbc6da2013-05-08 17:49:46 -0400702
Nachi Ueno6d580be2013-07-24 10:58:11 -0700703 return tempest.test.call_until_true(
704 ping, self.config.compute.ping_timeout, 1)
Maru Newbyaf292e82013-05-20 21:32:28 +0000705
Yair Fried9a551c42013-12-15 14:59:34 +0200706 def _check_vm_connectivity(self, ip_address,
707 username=None,
708 private_key=None,
709 should_connect=True):
710 """
711 :param ip_address: server to test against
712 :param username: server's ssh username
713 :param private_key: server's ssh private key to be used
714 :param should_connect: True/False indicates positive/negative test
715 positive - attempt ping and ssh
716 negative - attempt ping and fail if succeed
717
718 :raises: AssertError if the result of the connectivity check does
719 not match the value of the should_connect param
720 """
721 if should_connect:
722 msg = "Timed out waiting for %s to become reachable" % ip_address
723 else:
724 msg = "ip address %s is reachable" % ip_address
725 self.assertTrue(self._ping_ip_address(ip_address,
726 should_succeed=should_connect),
727 msg=msg)
728 if should_connect:
729 # no need to check ssh for negative connectivity
Attila Fazekasad7ef7d2013-11-20 10:12:53 +0100730 linux_client = self.get_remote_client(ip_address, username,
731 private_key)
732 linux_client.validate_authentication()
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200733
Yair Friedeb69f3f2013-10-10 13:18:16 +0300734 def _create_security_group_nova(self, client=None,
735 namestart='secgroup-smoke-',
736 tenant_id=None):
737 if client is None:
738 client = self.compute_client
739 # Create security group
740 sg_name = data_utils.rand_name(namestart)
741 sg_desc = sg_name + " description"
742 secgroup = client.security_groups.create(sg_name, sg_desc)
743 self.assertEqual(secgroup.name, sg_name)
744 self.assertEqual(secgroup.description, sg_desc)
745 self.set_resource(sg_name, secgroup)
746
747 # Add rules to the security group
748 self._create_loginable_secgroup_rule_nova(client, secgroup.id)
749
750 return secgroup
751
752 def _create_security_group_neutron(self, tenant_id, client=None,
753 namestart='secgroup-smoke-'):
754 if client is None:
755 client = self.network_client
756 secgroup = self._create_empty_security_group(namestart=namestart,
757 client=client,
758 tenant_id=tenant_id)
759
760 # Add rules to the security group
761 rules = self._create_loginable_secgroup_rule_neutron(secgroup=secgroup)
762 for rule in rules:
763 self.assertEqual(tenant_id, rule.tenant_id)
764 self.assertEqual(secgroup.id, rule.security_group_id)
765 return secgroup
766
767 def _create_empty_security_group(self, tenant_id, client=None,
768 namestart='secgroup-smoke-'):
769 """Create a security group without rules.
770
771 Default rules will be created:
772 - IPv4 egress to any
773 - IPv6 egress to any
774
775 :param tenant_id: secgroup will be created in this tenant
776 :returns: DeletableSecurityGroup -- containing the secgroup created
777 """
778 if client is None:
779 client = self.network_client
780 sg_name = data_utils.rand_name(namestart)
781 sg_desc = sg_name + " description"
782 sg_dict = dict(name=sg_name,
783 description=sg_desc)
784 sg_dict['tenant_id'] = tenant_id
785 body = dict(security_group=sg_dict)
786 result = client.create_security_group(body=body)
787 secgroup = net_common.DeletableSecurityGroup(
788 client=client,
789 **result['security_group']
790 )
791 self.assertEqual(secgroup.name, sg_name)
792 self.assertEqual(tenant_id, secgroup.tenant_id)
793 self.assertEqual(secgroup.description, sg_desc)
794 self.set_resource(sg_name, secgroup)
795 return secgroup
796
797 def _default_security_group(self, tenant_id, client=None):
798 """Get default secgroup for given tenant_id.
799
800 :returns: DeletableSecurityGroup -- default secgroup for given tenant
801 """
802 if client is None:
803 client = self.network_client
804 sgs = [
805 sg for sg in client.list_security_groups().values()[0]
806 if sg['tenant_id'] == tenant_id and sg['name'] == 'default'
807 ]
808 msg = "No default security group for tenant %s." % (tenant_id)
809 self.assertTrue(len(sgs) > 0, msg)
810 if len(sgs) > 1:
811 msg = "Found %d default security groups" % len(sgs)
812 raise exc.NeutronClientNoUniqueMatch(msg=msg)
813 return net_common.DeletableSecurityGroup(client=client,
814 **sgs[0])
815
816 def _create_security_group_rule(self, client=None, secgroup=None,
817 tenant_id=None, **kwargs):
818 """Create a rule from a dictionary of rule parameters.
819
820 Create a rule in a secgroup. if secgroup not defined will search for
821 default secgroup in tenant_id.
822
823 :param secgroup: type DeletableSecurityGroup.
824 :param secgroup_id: search for secgroup by id
825 default -- choose default secgroup for given tenant_id
826 :param tenant_id: if secgroup not passed -- the tenant in which to
827 search for default secgroup
828 :param kwargs: a dictionary containing rule parameters:
829 for example, to allow incoming ssh:
830 rule = {
831 direction: 'ingress'
832 protocol:'tcp',
833 port_range_min: 22,
834 port_range_max: 22
835 }
836 """
837 if client is None:
838 client = self.network_client
839 if secgroup is None:
840 secgroup = self._default_security_group(tenant_id)
841
842 ruleset = dict(security_group_id=secgroup.id,
843 tenant_id=secgroup.tenant_id,
844 )
845 ruleset.update(kwargs)
846
847 body = dict(security_group_rule=dict(ruleset))
848 sg_rule = client.create_security_group_rule(body=body)
849 sg_rule = net_common.DeletableSecurityGroupRule(
850 client=client,
851 **sg_rule['security_group_rule']
852 )
853 self.set_resource(sg_rule.id, sg_rule)
854 self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
855 self.assertEqual(secgroup.id, sg_rule.security_group_id)
856
857 return sg_rule
858
859 def _create_loginable_secgroup_rule_neutron(self, client=None,
860 secgroup=None):
861 """These rules are intended to permit inbound ssh and icmp
862 traffic from all sources, so no group_id is provided.
863 Setting a group_id would only permit traffic from ports
864 belonging to the same security group.
865 """
866
867 if client is None:
868 client = self.network_client
869 rules = []
870 rulesets = [
871 dict(
872 # ssh
873 protocol='tcp',
874 port_range_min=22,
875 port_range_max=22,
876 ),
877 dict(
878 # ping
879 protocol='icmp',
880 )
881 ]
882 for ruleset in rulesets:
883 for r_direction in ['ingress', 'egress']:
884 ruleset['direction'] = r_direction
885 try:
886 sg_rule = self._create_security_group_rule(
887 client=client, secgroup=secgroup, **ruleset)
888 except exc.NeutronClientException as ex:
889 # if rule already exist - skip rule and continue
890 if not (ex.status_code is 409 and 'Security group rule'
891 ' already exists' in ex.message):
892 raise ex
893 else:
894 self.assertEqual(r_direction, sg_rule.direction)
895 rules.append(sg_rule)
896
897 return rules
898
Yair Fried5f670ab2013-12-09 09:26:51 +0200899 def _ssh_to_server(self, server, private_key):
900 ssh_login = self.config.compute.image_ssh_user
901 return self.get_remote_client(server,
902 username=ssh_login,
903 private_key=private_key)
904
Yuiko Takada7f4b1b32013-11-20 08:06:26 +0000905 def _show_quota_network(self, tenant_id):
906 quota = self.network_client.show_quota(tenant_id)
907 return quota['quota']['network']
908
909 def _show_quota_subnet(self, tenant_id):
910 quota = self.network_client.show_quota(tenant_id)
911 return quota['quota']['subnet']
912
913 def _show_quota_port(self, tenant_id):
914 quota = self.network_client.show_quota(tenant_id)
915 return quota['quota']['port']
916
Yair Fried4d7efa62013-11-17 17:12:29 +0200917 def _get_router(self, tenant_id):
918 """Retrieve a router for the given tenant id.
919
920 If a public router has been configured, it will be returned.
921
922 If a public router has not been configured, but a public
923 network has, a tenant router will be created and returned that
924 routes traffic to the public network.
925 """
926 router_id = self.config.network.public_router_id
927 network_id = self.config.network.public_network_id
928 if router_id:
929 result = self.network_client.show_router(router_id)
930 return net_common.AttributeDict(**result['router'])
931 elif network_id:
932 router = self._create_router(tenant_id)
933 router.add_gateway(network_id)
934 return router
935 else:
936 raise Exception("Neither of 'public_router_id' or "
937 "'public_network_id' has been defined.")
938
939 def _create_router(self, tenant_id, namestart='router-smoke-'):
940 name = data_utils.rand_name(namestart)
941 body = dict(
942 router=dict(
943 name=name,
944 admin_state_up=True,
945 tenant_id=tenant_id,
946 ),
947 )
948 result = self.network_client.create_router(body=body)
949 router = net_common.DeletableRouter(client=self.network_client,
950 **result['router'])
951 self.assertEqual(router.name, name)
952 self.set_resource(name, router)
953 return router
954
955 def _create_networks(self, tenant_id=None):
956 """Create a network with a subnet connected to a router.
957
958 :returns: network, subnet, router
959 """
960 if tenant_id is None:
961 tenant_id = self.tenant_id
962 network = self._create_network(tenant_id)
963 router = self._get_router(tenant_id)
964 subnet = self._create_subnet(network)
965 subnet.add_to_router(router.id)
966 self.networks.append(network)
967 self.subnets.append(subnet)
968 self.routers.append(router)
969 return network, subnet, router
970
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200971
972class OrchestrationScenarioTest(OfficialClientTest):
973 """
974 Base class for orchestration scenario tests
975 """
976
977 @classmethod
Matt Riedemann11c5b642013-08-24 08:45:38 -0700978 def setUpClass(cls):
979 super(OrchestrationScenarioTest, cls).setUpClass()
980 if not cls.config.service_available.heat:
981 raise cls.skipException("Heat support is required")
982
983 @classmethod
Steve Bakerdd7c6ce2013-06-24 14:46:47 +1200984 def credentials(cls):
985 username = cls.config.identity.admin_username
986 password = cls.config.identity.admin_password
987 tenant_name = cls.config.identity.tenant_name
988 return username, tenant_name, password
989
990 def _load_template(self, base_file, file_name):
991 filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
992 file_name)
993 with open(filepath) as f:
994 return f.read()
995
996 @classmethod
997 def _stack_rand_name(cls):
Masayuki Igawa259c1132013-10-31 17:48:44 +0900998 return data_utils.rand_name(cls.__name__ + '-')
Steve Baker80252da2013-09-25 13:29:10 +1200999
1000 @classmethod
1001 def _get_default_network(cls):
1002 networks = cls.network_client.list_networks()
1003 for net in networks['networks']:
1004 if net['name'] == cls.config.compute.fixed_network_name:
1005 return net