blob: 0ccd4b6e03efc7305c432c3eecbf9eba9a4c3f26 [file] [log] [blame]
Jay Pipes3f981df2012-03-27 18:59:44 -04001# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2012 OpenStack, LLC
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
Daryl Walleck1465d612011-11-02 02:22:15 -050018import ConfigParser
Jay Pipes7f757632011-12-02 15:53:32 -050019import logging
20import os
Jay Pipes3f981df2012-03-27 18:59:44 -040021
kavan-patil4ea2efb2011-12-09 08:58:50 +000022from tempest.common.utils import data_utils
Jay Pipes7f757632011-12-02 15:53:32 -050023
24LOG = logging.getLogger(__name__)
Daryl Walleck1465d612011-11-02 02:22:15 -050025
26
Jay Pipes3f981df2012-03-27 18:59:44 -040027class BaseConfig(object):
28
29 SECTION_NAME = None
Daryl Walleck1465d612011-11-02 02:22:15 -050030
31 def __init__(self, conf):
Daryl Walleck1465d612011-11-02 02:22:15 -050032 self.conf = conf
33
donald-ngo7fb1efa2011-12-13 17:17:36 -080034 def get(self, item_name, default_value=None):
Daryl Walleck1465d612011-11-02 02:22:15 -050035 try:
Rohit Karajgicfc0c022012-07-17 23:11:38 -070036 return self.conf.get(self.SECTION_NAME, item_name, raw=True)
Daryl Walleck1465d612011-11-02 02:22:15 -050037 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
38 return default_value
39
Attila Fazekasa23f5002012-10-23 19:32:45 +020040 def getboolean(self, item_name, default_value=None):
41 try:
42 return self.conf.getboolean(self.SECTION_NAME, item_name)
43 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
44 return default_value
45
Jay Pipes3f981df2012-03-27 18:59:44 -040046
47class IdentityConfig(BaseConfig):
48
49 """Provides configuration information for authenticating with Keystone."""
50
51 SECTION_NAME = "identity"
52
Daryl Walleck1465d612011-11-02 02:22:15 -050053 @property
chris fattarsi8ed39ac2012-04-30 14:11:27 -070054 def catalog_type(self):
55 """Catalog type of the Identity service."""
56 return self.get("catalog_type", 'identity')
57
58 @property
Rohit Karajgie1b050d2011-12-02 16:13:18 -080059 def host(self):
Daryl Walleck587385b2012-03-03 13:00:26 -060060 """Host IP for making Identity API requests."""
donald-ngo7fb1efa2011-12-13 17:17:36 -080061 return self.get("host", "127.0.0.1")
Rohit Karajgie1b050d2011-12-02 16:13:18 -080062
63 @property
64 def port(self):
Daryl Walleck587385b2012-03-03 13:00:26 -060065 """Port for the Identity service."""
Rohit Karajgie1b050d2011-12-02 16:13:18 -080066 return self.get("port", "8773")
67
68 @property
Daryl Walleck587385b2012-03-03 13:00:26 -060069 def api_version(self):
70 """Version of the Identity API"""
71 return self.get("api_version", "v1.1")
Rohit Karajgie1b050d2011-12-02 16:13:18 -080072
73 @property
74 def path(self):
75 """Path of API request"""
76 return self.get("path", "/")
77
kavan-patil4ea2efb2011-12-09 08:58:50 +000078 @property
79 def auth_url(self):
Daryl Walleck587385b2012-03-03 13:00:26 -060080 """The Identity URL (derived)"""
kavan-patil4ea2efb2011-12-09 08:58:50 +000081 auth_url = data_utils.build_url(self.host,
82 self.port,
Daryl Walleck587385b2012-03-03 13:00:26 -060083 self.api_version,
donald-ngo7fb1efa2011-12-13 17:17:36 -080084 self.path,
85 use_ssl=self.use_ssl)
kavan-patil4ea2efb2011-12-09 08:58:50 +000086 return auth_url
87
Daryl Walleck1465d612011-11-02 02:22:15 -050088 @property
donald-ngo7fb1efa2011-12-13 17:17:36 -080089 def use_ssl(self):
90 """Specifies if we are using https."""
Dan Prince95195922012-03-12 15:14:51 -040091 return self.get("use_ssl", 'false').lower() != 'false'
donald-ngo7fb1efa2011-12-13 17:17:36 -080092
93 @property
Daryl Walleck587385b2012-03-03 13:00:26 -060094 def strategy(self):
95 """Which auth method does the environment use? (basic|keystone)"""
96 return self.get("strategy", 'keystone')
97
98
Jay Pipesf38eaac2012-06-21 13:37:35 -040099class IdentityAdminConfig(BaseConfig):
100
101 SECTION_NAME = "identity-admin"
102
103 @property
104 def username(self):
105 """Username to use for Identity Admin API requests"""
106 return self.get("username", "admin")
107
108 @property
109 def tenant_name(self):
110 """Tenant name to use for Identity Admin API requests"""
111 return self.get("tenant_name", "admin")
112
113 @property
114 def password(self):
115 """API key to use for Identity Admin API requests"""
116 return self.get("password", "pass")
117
118
Jay Pipes3f981df2012-03-27 18:59:44 -0400119class ComputeConfig(BaseConfig):
Daryl Walleck587385b2012-03-03 13:00:26 -0600120
Jay Pipes3f981df2012-03-27 18:59:44 -0400121 SECTION_NAME = "compute"
122
123 @property
Jay Pipesf38eaac2012-06-21 13:37:35 -0400124 def allow_tenant_isolation(self):
125 """
126 Allows test cases to create/destroy tenants and users. This option
127 enables isolated test cases and better parallel execution,
128 but also requires that OpenStack Identity API admin credentials
129 are known.
130 """
131 return self.get("allow_tenant_isolation", 'false').lower() != 'false'
132
133 @property
Dan Smithd6ff6b72012-08-23 10:29:41 -0700134 def allow_tenant_reuse(self):
135 """
136 If allow_tenant_isolation is True and a tenant that would be created
137 for a given test already exists (such as from a previously-failed run),
138 re-use that tenant instead of failing because of the conflict. Note
139 that this would result in the tenant being deleted at the end of a
140 subsequent successful run.
141 """
142 return self.get("allow_tenant_reuse", 'true').lower() != 'false'
143
144 @property
Jay Pipes3f981df2012-03-27 18:59:44 -0400145 def username(self):
146 """Username to use for Nova API requests."""
147 return self.get("username", "demo")
148
149 @property
150 def tenant_name(self):
151 """Tenant name to use for Nova API requests."""
152 return self.get("tenant_name", "demo")
153
154 @property
155 def password(self):
156 """API key to use when authenticating."""
157 return self.get("password", "pass")
158
159 @property
160 def alt_username(self):
161 """Username of alternate user to use for Nova API requests."""
David Kranz7490e952012-04-02 13:28:27 -0400162 return self.get("alt_username")
Jay Pipes3f981df2012-03-27 18:59:44 -0400163
164 @property
165 def alt_tenant_name(self):
166 """Alternate user's Tenant name to use for Nova API requests."""
David Kranz7490e952012-04-02 13:28:27 -0400167 return self.get("alt_tenant_name")
Jay Pipes3f981df2012-03-27 18:59:44 -0400168
169 @property
170 def alt_password(self):
171 """API key to use when authenticating as alternate user."""
David Kranz7490e952012-04-02 13:28:27 -0400172 return self.get("alt_password")
Daryl Walleck587385b2012-03-03 13:00:26 -0600173
174 @property
175 def image_ref(self):
176 """Valid primary image to use in tests."""
Jay Pipes3f981df2012-03-27 18:59:44 -0400177 return self.get("image_ref", "{$IMAGE_ID}")
Daryl Walleck587385b2012-03-03 13:00:26 -0600178
179 @property
180 def image_ref_alt(self):
181 """Valid secondary image reference to be used in tests."""
Jay Pipes3f981df2012-03-27 18:59:44 -0400182 return self.get("image_ref_alt", "{$IMAGE_ID_ALT}")
Daryl Walleck587385b2012-03-03 13:00:26 -0600183
184 @property
185 def flavor_ref(self):
186 """Valid primary flavor to use in tests."""
187 return self.get("flavor_ref", 1)
188
189 @property
190 def flavor_ref_alt(self):
191 """Valid secondary flavor to be used in tests."""
192 return self.get("flavor_ref_alt", 2)
193
194 @property
195 def resize_available(self):
196 """Does the test environment support resizing?"""
Dan Prince95195922012-03-12 15:14:51 -0400197 return self.get("resize_available", 'false').lower() != 'false'
Daryl Walleck587385b2012-03-03 13:00:26 -0600198
199 @property
Mate Lakat99ee9142012-09-14 12:34:46 +0100200 def live_migration_available(self):
201 return self.get(
202 "live_migration_available", 'false').lower() == 'true'
203
204 @property
205 def use_block_migration_for_live_migration(self):
206 return self.get(
207 "use_block_migration_for_live_migration", 'false'
208 ).lower() == 'true'
209
210 @property
David Kranzf97d5fd2012-07-30 13:46:45 -0400211 def change_password_available(self):
212 """Does the test environment support changing the admin password?"""
213 return self.get("change_password_available", 'false').lower() != \
214 'false'
215
216 @property
Daryl Walleck587385b2012-03-03 13:00:26 -0600217 def create_image_enabled(self):
218 """Does the test environment support snapshots?"""
Dan Prince95195922012-03-12 15:14:51 -0400219 return self.get("create_image_enabled", 'false').lower() != 'false'
Daryl Walleck587385b2012-03-03 13:00:26 -0600220
221 @property
Daryl Walleck1465d612011-11-02 02:22:15 -0500222 def build_interval(self):
223 """Time in seconds between build status checks."""
224 return float(self.get("build_interval", 10))
225
226 @property
Daryl Walleck1465d612011-11-02 02:22:15 -0500227 def build_timeout(self):
Rohit Karajgidd47d7e2012-07-31 04:11:01 -0700228 """Timeout in seconds to wait for an instance to build."""
Daryl Walleck1465d612011-11-02 02:22:15 -0500229 return float(self.get("build_timeout", 300))
230
Daryl Walleck4aa82a92012-02-14 15:45:46 -0600231 @property
Daryl Walleck6b9b2882012-04-08 21:43:39 -0500232 def run_ssh(self):
233 """Does the test environment support snapshots?"""
234 return self.get("run_ssh", 'false').lower() != 'false'
235
236 @property
237 def ssh_user(self):
238 """User name used to authenticate to an instance."""
239 return self.get("ssh_user", "root")
240
241 @property
242 def ssh_timeout(self):
243 """Timeout in seconds to wait for authentcation to succeed."""
244 return float(self.get("ssh_timeout", 300))
245
246 @property
247 def network_for_ssh(self):
248 """Network used for SSH connections."""
249 return self.get("network_for_ssh", "public")
250
251 @property
252 def ip_version_for_ssh(self):
253 """IP version used for SSH connections."""
254 return int(self.get("ip_version_for_ssh", 4))
255
256 @property
Daryl Walleckb90a1a62012-02-27 11:23:10 -0600257 def catalog_type(self):
Daryl Walleck587385b2012-03-03 13:00:26 -0600258 """Catalog type of the Compute service."""
Daryl Walleckb90a1a62012-02-27 11:23:10 -0600259 return self.get("catalog_type", 'compute')
Daryl Walleck4aa82a92012-02-14 15:45:46 -0600260
David Kranz180fed12012-03-27 14:31:29 -0400261 @property
262 def log_level(self):
263 """Level for logging compute API calls."""
264 return self.get("log_level", 'ERROR')
265
Jay Pipes051075a2012-04-28 17:39:37 -0400266 @property
267 def whitebox_enabled(self):
268 """Does the test environment support whitebox tests for Compute?"""
269 return self.get("whitebox_enabled", 'false').lower() != 'false'
270
271 @property
272 def db_uri(self):
273 """Connection string to the database of Compute service"""
274 return self.get("db_uri", None)
275
276 @property
277 def source_dir(self):
278 """Path of nova source directory"""
279 return self.get("source_dir", "/opt/stack/nova")
280
281 @property
282 def config_path(self):
283 """Path of nova configuration file"""
284 return self.get("config_path", "/etc/nova/nova.conf")
285
286 @property
287 def bin_dir(self):
288 """Directory containing nova binaries such as nova-manage"""
289 return self.get("bin_dir", "/usr/local/bin/")
290
291 @property
292 def path_to_private_key(self):
293 """Path to a private key file for SSH access to remote hosts"""
294 return self.get("path_to_private_key")
295
Daryl Walleck1465d612011-11-02 02:22:15 -0500296
Jay Pipesff10d552012-04-06 14:18:50 -0400297class ComputeAdminConfig(BaseConfig):
298
299 SECTION_NAME = "compute-admin"
300
301 @property
302 def username(self):
303 """Administrative Username to use for Nova API requests."""
304 return self.get("username", "admin")
305
306 @property
307 def tenant_name(self):
308 """Administrative Tenant name to use for Nova API requests."""
309 return self.get("tenant_name", "admin")
310
311 @property
312 def password(self):
313 """API key to use when authenticating as admin."""
314 return self.get("password", "pass")
315
316
Jay Pipes3f981df2012-03-27 18:59:44 -0400317class ImagesConfig(BaseConfig):
318
Jay Pipes50677282012-01-06 15:39:20 -0500319 """
320 Provides configuration information for connecting to an
321 OpenStack Images service.
322 """
323
Jay Pipes3f981df2012-03-27 18:59:44 -0400324 SECTION_NAME = "image"
Jay Pipes50677282012-01-06 15:39:20 -0500325
326 @property
327 def host(self):
328 """Host IP for making Images API requests. Defaults to '127.0.0.1'."""
329 return self.get("host", "127.0.0.1")
330
331 @property
332 def port(self):
333 """Listen port of the Images service."""
334 return int(self.get("port", "9292"))
335
336 @property
337 def api_version(self):
338 """Version of the API"""
339 return self.get("api_version", "1")
340
341 @property
342 def username(self):
Jay Pipes3f981df2012-03-27 18:59:44 -0400343 """Username to use for Images API requests. Defaults to 'demo'."""
Julien Danjou75a677e2012-04-11 15:49:15 +0200344 return self.get("username", "demo")
Jay Pipes50677282012-01-06 15:39:20 -0500345
346 @property
347 def password(self):
348 """Password for user"""
Jay Pipes3f981df2012-03-27 18:59:44 -0400349 return self.get("password", "pass")
Jay Pipes50677282012-01-06 15:39:20 -0500350
351 @property
Jay Pipes3f981df2012-03-27 18:59:44 -0400352 def tenant_name(self):
353 """Tenant to use for Images API requests. Defaults to 'demo'."""
354 return self.get("tenant_name", "demo")
Jay Pipes50677282012-01-06 15:39:20 -0500355
356
Unmesh Gurjar44986832012-05-08 19:57:10 +0530357class NetworkConfig(BaseConfig):
358 """Provides configuration information for connecting to an OpenStack
359 Network Service.
360 """
361
362 SECTION_NAME = "network"
363
364 @property
365 def catalog_type(self):
366 """Catalog type of the Quantum service."""
367 return self.get("catalog_type", 'network')
368
369 @property
370 def api_version(self):
371 """Version of Quantum API"""
372 return self.get("api_version", "v1.1")
373
374
Rohit Karajgidd47d7e2012-07-31 04:11:01 -0700375class VolumeConfig(BaseConfig):
376 """Provides configuration information for connecting to an OpenStack Block
377 Storage Service.
378 """
379
380 SECTION_NAME = "volume"
381
382 @property
383 def build_interval(self):
384 """Time in seconds between volume availability checks."""
385 return float(self.get("build_interval", 10))
386
387 @property
388 def build_timeout(self):
389 """Timeout in seconds to wait for a volume to become available."""
390 return float(self.get("build_timeout", 300))
391
392 @property
393 def catalog_type(self):
394 """Catalog type of the Volume Service"""
395 return self.get("catalog_type", 'volume')
396
397
dwalleck5d734432012-10-04 01:11:47 -0500398class ObjectStorageConfig(BaseConfig):
399
400 SECTION_NAME = "object-storage"
401
402 @property
403 def username(self):
404 """Username to use for Object-Storage API requests."""
405 return self.get("username", "admin")
406
407 @property
408 def tenant_name(self):
409 """Tenant name to use for Object-Storage API requests."""
410 return self.get("tenant_name", "admin")
411
412 @property
413 def password(self):
414 """API key to use when authenticating."""
415 return self.get("password", "password")
416
417 @property
418 def catalog_type(self):
419 """Catalog type of the Object-Storage service."""
420 return self.get("catalog_type", 'object-store')
421
422
Attila Fazekasa23f5002012-10-23 19:32:45 +0200423class BotoConfig(BaseConfig):
424 """Provides configuration information for connecting to EC2/S3."""
425 SECTION_NAME = "boto"
426
427 @property
428 def ec2_url(self):
429 """EC2 URL"""
430 return self.get("ec2_url", "http://localhost:8773/services/Cloud")
431
432 @property
433 def s3_url(self):
434 """S3 URL"""
435 return self.get("s3_url", "http://localhost:8080")
436
437 @property
438 def aws_secret(self):
439 """AWS Secret Key"""
440 return self.get("aws_secret")
441
442 @property
443 def aws_access(self):
444 """AWS Access Key"""
445 return self.get("aws_access")
446
447 @property
448 def aws_region(self):
449 """AWS Region"""
450 return self.get("aws_region", "RegionOne")
451
452 @property
453 def s3_materials_path(self):
454 return self.get("s3_materials_path",
455 "/opt/stack/devstack/files/images/"
456 "s3-materials/cirros-0.3.0")
457
458 @property
459 def ari_manifest(self):
460 """ARI Ramdisk Image manifest"""
461 return self.get("ari_manifest",
462 "cirros-0.3.0-x86_64-initrd.manifest.xml")
463
464 @property
465 def ami_manifest(self):
466 """AMI Machine Image manifest"""
467 return self.get("ami_manifest",
468 "cirros-0.3.0-x86_64-blank.img.manifest.xml")
469
470 @property
471 def aki_manifest(self):
472 """AKI Kernel Image manifest"""
473 return self.get("aki_manifest",
474 "cirros-0.3.0-x86_64-vmlinuz.manifest.xml")
475
476 @property
477 def instance_type(self):
478 """Instance type"""
479 return self.get("Instance type", "m1.tiny")
480
481 @property
482 def http_socket_timeout(self):
483 """boto Http socket timeout"""
484 return self.get("http_socket_timeout", "3")
485
486 @property
487 def build_timeout(self):
488 """status change timeout"""
489 return float(self.get("build_timeout", "60"))
490
491 @property
492 def build_interval(self):
493 """status change test interval"""
494 return float(self.get("build_interval", 1))
495
496
Rohit Karajgidd47d7e2012-07-31 04:11:01 -0700497# TODO(jaypipes): Move this to a common utils (not data_utils...)
Jay Pipes3f981df2012-03-27 18:59:44 -0400498def singleton(cls):
499 """Simple wrapper for classes that should only have a single instance"""
500 instances = {}
501
502 def getinstance():
503 if cls not in instances:
504 instances[cls] = cls()
505 return instances[cls]
506 return getinstance
507
508
509@singleton
510class TempestConfig:
Daryl Walleck1465d612011-11-02 02:22:15 -0500511 """Provides OpenStack configuration information."""
512
Brian Waldon738cd632011-12-12 18:45:09 -0500513 DEFAULT_CONFIG_DIR = os.path.join(
514 os.path.abspath(
515 os.path.dirname(
516 os.path.dirname(__file__))),
517 "etc")
Daryl Walleck1465d612011-11-02 02:22:15 -0500518
Brian Waldon738cd632011-12-12 18:45:09 -0500519 DEFAULT_CONFIG_FILE = "tempest.conf"
520
521 def __init__(self):
522 """Initialize a configuration from a conf directory and conf file."""
523
524 # Environment variables override defaults...
525 conf_dir = os.environ.get('TEMPEST_CONFIG_DIR',
Zhongyue Luo79d8d362012-09-25 13:49:27 +0800526 self.DEFAULT_CONFIG_DIR)
527 conf_file = os.environ.get('TEMPEST_CONFIG', self.DEFAULT_CONFIG_FILE)
Brian Waldon738cd632011-12-12 18:45:09 -0500528
Jay Pipes7f757632011-12-02 15:53:32 -0500529 path = os.path.join(conf_dir, conf_file)
530
Jay Pipes3f981df2012-03-27 18:59:44 -0400531 LOG.info("Using tempest config file %s" % path)
532
Jay Pipes7f757632011-12-02 15:53:32 -0500533 if not os.path.exists(path):
534 msg = "Config file %(path)s not found" % locals()
535 raise RuntimeError(msg)
536
537 self._conf = self.load_config(path)
Daryl Walleck587385b2012-03-03 13:00:26 -0600538 self.compute = ComputeConfig(self._conf)
Jay Pipesff10d552012-04-06 14:18:50 -0400539 self.compute_admin = ComputeAdminConfig(self._conf)
Daryl Walleck587385b2012-03-03 13:00:26 -0600540 self.identity = IdentityConfig(self._conf)
Jay Pipesf38eaac2012-06-21 13:37:35 -0400541 self.identity_admin = IdentityAdminConfig(self._conf)
Jay Pipes50677282012-01-06 15:39:20 -0500542 self.images = ImagesConfig(self._conf)
Unmesh Gurjar44986832012-05-08 19:57:10 +0530543 self.network = NetworkConfig(self._conf)
Rohit Karajgidd47d7e2012-07-31 04:11:01 -0700544 self.volume = VolumeConfig(self._conf)
dwalleck5d734432012-10-04 01:11:47 -0500545 self.object_storage = ObjectStorageConfig(self._conf)
Attila Fazekasa23f5002012-10-23 19:32:45 +0200546 self.boto = BotoConfig(self._conf)
Daryl Walleck1465d612011-11-02 02:22:15 -0500547
Jay Pipes7f757632011-12-02 15:53:32 -0500548 def load_config(self, path):
Daryl Walleck1465d612011-11-02 02:22:15 -0500549 """Read configuration from given path and return a config object."""
550 config = ConfigParser.SafeConfigParser()
551 config.read(path)
552 return config