Merge "Enable boto keyapir test"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index e1196f0..43277d6 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -107,9 +107,6 @@
# Level to log Compute API request/response details.
log_level = ERROR
-# Whitebox options for compute. Whitebox options enable the
-# whitebox test cases, which look at internal Nova database state,
-# SSH into VMs to check instance state, etc.
# Run live migration tests (requires 2 hosts)
live_migration_available = false
@@ -123,6 +120,12 @@
# are forced to skip, regardless of the extension status
disk_config_enabled_override = true
+
+[whitebox]
+# Whitebox options for compute. Whitebox options enable the
+# whitebox test cases, which look at internal Nova database state,
+# SSH into VMs to check instance state, etc.
+
# Should we run whitebox tests for Compute?
whitebox_enabled = true
diff --git a/openstack-common.conf b/openstack-common.conf
index a75279f..0c9e43e 100644
--- a/openstack-common.conf
+++ b/openstack-common.conf
@@ -1,7 +1,7 @@
[DEFAULT]
# The list of modules to copy from openstack-common
-modules=setup,cfg,iniparser
+modules=setup,cfg,iniparser,install_venv_common
# The base module to hold the copy of openstack.common
base=tempest
diff --git a/setup.cfg b/setup.cfg
index c28d129..b522f3a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,7 +1,5 @@
[nosetests]
# NOTE(jkoelker) To run the test suite under nose install the following
# coverage http://pypi.python.org/pypi/coverage
-# tissue http://pypi.python.org/pypi/tissue (pep8 checker)
# openstack-nose https://github.com/openstack-dev/openstack-nose
verbosity=2
-detailed-errors=1
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index cff038d..d649930 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -199,6 +199,34 @@
def _parse_resp(self, body):
return json.loads(body)
+ def response_checker(self, method, url, headers, body, resp, resp_body):
+ if (resp.status in set((204, 205, 304)) or resp.status < 200 or
+ method.upper() == 'HEAD') and resp_body:
+ raise exceptions.ResponseWithNonEmptyBody(status=resp.status)
+ #NOTE(afazekas):
+ # If the HTTP Status Code is 205
+ # 'The response MUST NOT include an entity.'
+ # A HTTP entity has an entity-body and an 'entity-header'.
+ # In the HTTP response specification (Section 6) the 'entity-header'
+ # 'generic-header' and 'response-header' are in OR relation.
+ # All headers not in the above two group are considered as entity
+ # header in every interpretation.
+
+ if (resp.status == 205 and
+ 0 != len(set(resp.keys()) - set(('status',)) -
+ self.response_header_lc - self.general_header_lc)):
+ raise exceptions.ResponseWithEntity()
+ #NOTE(afazekas)
+ # Now the swift sometimes (delete not empty container)
+ # returns with non json error response, we can create new rest class
+ # for swift.
+ # Usually RFC2616 says error responses SHOULD contain an explanation.
+ # The warning is normal for SHOULD/SHOULD NOT case
+
+ # Likely it will cause error
+ if not body and resp.status >= 400:
+ self.log.warning("status >= 400 response with empty body")
+
def request(self, method, url,
headers=None, body=None, depth=0, wait=None):
"""A simple HTTP request interface."""
@@ -215,37 +243,7 @@
req_url = "%s/%s" % (self.base_url, url)
resp, resp_body = self.http_obj.request(req_url, method,
headers=headers, body=body)
-
- #TODO(afazekas): Make sure we can validate all responses, and the
- #http library does not do any action automatically
- if (resp.status in set((204, 205, 304)) or resp.status < 200 or
- method.upper() == 'HEAD') and resp_body:
- raise exceptions.ResponseWithNonEmptyBody(status=resp.status)
-
- #NOTE(afazekas):
- # If the HTTP Status Code is 205
- # 'The response MUST NOT include an entity.'
- # A HTTP entity has an entity-body and an 'entity-header'.
- # In the HTTP response specification (Section 6) the 'entity-header'
- # 'generic-header' and 'response-header' are in OR relation.
- # All headers not in the above two group are considered as entity
- # header in every interpretation.
-
- if (resp.status == 205 and
- 0 != len(set(resp.keys()) - set(('status',)) -
- self.response_header_lc - self.general_header_lc)):
- raise exceptions.ResponseWithEntity()
-
- #NOTE(afazekas)
- # Now the swift sometimes (delete not empty container)
- # returns with non json error response, we can create new rest class
- # for swift.
- # Usually RFC2616 says error responses SHOULD contain an explanation.
- # The warning is normal for SHOULD/SHOULD NOT case
-
- # Likely it will cause error
- if not body and resp.status >= 400:
- self.log.warning("status >= 400 response with empty body")
+ self.response_checker(method, url, headers, body, resp, resp_body)
if resp.status == 401 or resp.status == 403:
self._log(req_url, body, resp, resp_body)
@@ -296,7 +294,7 @@
if resp.status >= 400:
resp_body = self._parse_resp(resp_body)
self._log(req_url, body, resp, resp_body)
- raise exceptions.TempestException(str(resp.status))
+ raise exceptions.RestClientException(str(resp.status))
return resp, resp_body
@@ -344,7 +342,9 @@
"""
Subclasses override with specific deletion detection.
"""
- return False
+ message = ('"%s" does not implement is_resource_deleted'
+ % self.__class__.__name__)
+ raise NotImplementedError(message)
class RestClientXML(RestClient):
diff --git a/tempest/common/utils/misc.py b/tempest/common/utils/misc.py
new file mode 100644
index 0000000..8d945cf
--- /dev/null
+++ b/tempest/common/utils/misc.py
@@ -0,0 +1,27 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+def singleton(cls):
+ """Simple wrapper for classes that should only have a single instance."""
+ instances = {}
+
+ def getinstance():
+ if cls not in instances:
+ instances[cls] = cls()
+ return instances[cls]
+ return getinstance
diff --git a/tempest/config.py b/tempest/config.py
index 89fa2d9..f6fcd90 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -20,6 +20,7 @@
import sys
from tempest.common.utils import data_utils
+from tempest.common.utils.misc import singleton
from tempest.openstack.common import cfg
LOG = logging.getLogger(__name__)
@@ -171,22 +172,6 @@
default=True,
help="If false, skip config tests regardless of the "
"extension status"),
- cfg.BoolOpt('whitebox_enabled',
- default=False,
- help="Does the test environment support whitebox tests for "
- "Compute?"),
- cfg.StrOpt('db_uri',
- default=None,
- help="Connection string to the database of Compute service"),
- cfg.StrOpt('source_dir',
- default="/opt/stack/nova",
- help="Path of nova source directory"),
- cfg.StrOpt('config_path',
- default='/etc/nova/nova.conf',
- help="Path of nova configuration file"),
- cfg.StrOpt('bin_dir',
- default="/usr/local/bin/",
- help="Directory containing nova binaries such as nova-manage"),
]
@@ -219,6 +204,35 @@
conf.register_opt(opt, group='compute-admin')
+whitebox_group = cfg.OptGroup(name='whitebox',
+ title="Whitebox Options")
+
+WhiteboxGroup = [
+ cfg.BoolOpt('whitebox_enabled',
+ default=False,
+ help="Does the test environment support whitebox tests for "
+ "Compute?"),
+ cfg.StrOpt('db_uri',
+ default=None,
+ help="Connection string to the database of Compute service"),
+ cfg.StrOpt('source_dir',
+ default="/opt/stack/nova",
+ help="Path of nova source directory"),
+ cfg.StrOpt('config_path',
+ default='/etc/nova/nova.conf',
+ help="Path of nova configuration file"),
+ cfg.StrOpt('bin_dir',
+ default="/usr/local/bin/",
+ help="Directory containing nova binaries such as nova-manage"),
+]
+
+
+def register_whitebox_opts(conf):
+ conf.register_group(whitebox_group)
+ for opt in WhiteboxGroup:
+ conf.register_opt(opt, group='whitebox')
+
+
image_group = cfg.OptGroup(name='image',
title="Image Service Options")
@@ -370,18 +384,6 @@
conf.register_opt(opt, group='boto')
-# TODO(jaypipes): Move this to a common utils (not data_utils...)
-def singleton(cls):
- """Simple wrapper for classes that should only have a single instance."""
- instances = {}
-
- def getinstance():
- if cls not in instances:
- instances[cls] = cls()
- return instances[cls]
- return getinstance
-
-
@singleton
class TempestConfig:
"""Provides OpenStack configuration information."""
@@ -418,6 +420,7 @@
register_compute_opts(cfg.CONF)
register_identity_opts(cfg.CONF)
+ register_whitebox_opts(cfg.CONF)
register_image_opts(cfg.CONF)
register_network_opts(cfg.CONF)
register_volume_opts(cfg.CONF)
@@ -425,6 +428,7 @@
register_boto_opts(cfg.CONF)
register_compute_admin_opts(cfg.CONF)
self.compute = cfg.CONF.compute
+ self.whitebox = cfg.CONF.whitebox
self.identity = cfg.CONF.identity
self.images = cfg.CONF.image
self.network = cfg.CONF.network
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 178c2f2..de22688 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -15,6 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import unittest2 as unittest
+
class TempestException(Exception):
"""
@@ -27,6 +29,7 @@
message = "An unknown exception occurred"
def __init__(self, *args, **kwargs):
+ super(TempestException, self).__init__()
try:
self._error_string = self.message % kwargs
except Exception:
@@ -49,11 +52,16 @@
message = "Invalid Configuration"
-class NotFound(TempestException):
+class RestClientException(TempestException,
+ unittest.TestCase.failureException):
+ pass
+
+
+class NotFound(RestClientException):
message = "Object not found"
-class Unauthorized(TempestException):
+class Unauthorized(RestClientException):
message = 'Unauthorized'
@@ -78,11 +86,11 @@
message = "Volume %(volume_id)s failed to build and is in ERROR status"
-class BadRequest(TempestException):
+class BadRequest(RestClientException):
message = "Bad request"
-class AuthenticationFailure(TempestException):
+class AuthenticationFailure(RestClientException):
message = ("Authentication with user %(user)s and password "
"%(password)s failed")
@@ -108,7 +116,7 @@
message = "Got identity error"
-class Duplicate(TempestException):
+class Duplicate(RestClientException):
message = "An object with that identifier already exists"
@@ -135,7 +143,7 @@
message = "%(num)d cleanUp operation failed"
-class RFCViolation(TempestException):
+class RFCViolation(RestClientException):
message = "RFC Violation"
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index 9cc30d9..403a3ac 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -197,6 +197,12 @@
body = json.loads(body)
return resp, body['OS-KSADM:service']
+ def list_services(self):
+ """List Service - Returns Services."""
+ resp, body = self.get('/OS-KSADM/services/')
+ body = json.loads(body)
+ return resp, body['OS-KSADM:services']
+
def delete_service(self, service_id):
"""Delete Service."""
url = '/OS-KSADM/services/%s' % service_id
diff --git a/tempest/services/identity/xml/identity_client.py b/tempest/services/identity/xml/identity_client.py
index 02be91e..f79c3d5 100644
--- a/tempest/services/identity/xml/identity_client.py
+++ b/tempest/services/identity/xml/identity_client.py
@@ -226,6 +226,12 @@
body = self._parse_body(etree.fromstring(body))
return resp, body
+ def list_services(self):
+ """Returns services."""
+ resp, body = self.get('OS-KSADM/services', self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
+
def get_service(self, service_id):
"""Get Service."""
url = '/OS-KSADM/services/%s' % service_id
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index b0104e0..440a80e 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -37,7 +37,7 @@
def __init__(self, config, username, password, auth_url, tenant_name=None):
super(VolumesClientXML, self).__init__(config, username, password,
auth_url, tenant_name)
- self.service = self.config.compute.catalog_type
+ self.service = self.config.volume.catalog_type
self.build_interval = self.config.compute.build_interval
self.build_timeout = self.config.compute.build_timeout
@@ -50,10 +50,11 @@
ns, tag = tag.split("}", 1)
if tag == 'metadata':
vol['metadata'] = dict((meta.get('key'),
- meta.text) for meta in list(child))
+ meta.text) for meta in
+ child.getchildren())
else:
vol[tag] = xml_to_json(child)
- return vol
+ return vol
def list_volumes(self, params=None):
"""List all the volumes created."""
@@ -90,25 +91,33 @@
body = etree.fromstring(body)
return resp, self._parse_volume(body)
- def create_volume(self, size, display_name=None, metadata=None):
+ def create_volume(self, size, **kwargs):
"""Creates a new Volume.
:param size: Size of volume in GB. (Required)
:param display_name: Optional Volume Name.
:param metadata: An optional dictionary of values for metadata.
+ :param volume_type: Optional Name of volume_type for the volume
+ :param snapshot_id: When specified the volume is created from
+ this snapshot
"""
volume = Element("volume", xmlns=XMLNS_11, size=size)
- if display_name:
- volume.add_attr('display_name', display_name)
- if metadata:
+ if 'metadata' in kwargs:
_metadata = Element('metadata')
volume.append(_metadata)
- for key, value in metadata.items():
+ for key, value in kwargs['metadata'].items():
meta = Element('meta')
meta.add_attr('key', key)
meta.append(Text(value))
_metadata.append(meta)
+ attr_to_add = kwargs.copy()
+ del attr_to_add['metadata']
+ else:
+ attr_to_add = kwargs
+
+ for key, value in attr_to_add.items():
+ volume.add_attr(key, value)
resp, body = self.post('volumes', str(Document(volume)),
self.headers)
@@ -122,7 +131,6 @@
def wait_for_volume_status(self, volume_id, status):
"""Waits for a Volume to reach a given status."""
resp, body = self.get_volume(volume_id)
- volume_name = body['displayName']
volume_status = body['status']
start = int(time.time())
@@ -135,7 +143,8 @@
if int(time.time()) - start >= self.build_timeout:
message = 'Volume %s failed to reach %s status within '\
- 'the required time (%s s).' % (volume_name, status,
+ 'the required time (%s s).' % (volume_id,
+ status,
self.build_timeout)
raise exceptions.TimeoutException(message)
diff --git a/tempest/testboto.py b/tempest/testboto.py
index 7031fe2..8bcaa33 100644
--- a/tempest/testboto.py
+++ b/tempest/testboto.py
@@ -126,7 +126,7 @@
testresources.ResourcedTestCase):
"""Recommended to use as base class for boto related test."""
- resources = [('boto_init', tempest.tests.boto.BotoResource())]
+ conclusion = tempest.tests.boto.generic_setup_package()
@classmethod
def setUpClass(cls):
@@ -202,8 +202,10 @@
s3_error_code.server = ServerError()
s3_error_code.client = ClientError()
valid_image_state = set(('available', 'pending', 'failed'))
+ #NOTE(afazekas): 'paused' is not valid status in EC2, but it does not have
+ # a good mapping, because it uses memory, but not really a running machine
valid_instance_state = set(('pending', 'running', 'shutting-down',
- 'terminated', 'stopping', 'stopped'))
+ 'terminated', 'stopping', 'stopped', 'paused'))
valid_volume_status = set(('creating', 'available', 'in-use',
'deleting', 'deleted', 'error'))
valid_snapshot_status = set(('pending', 'completed', 'error'))
diff --git a/tempest/tests/boto/__init__.py b/tempest/tests/boto/__init__.py
index 99dd8a9..6d5149e 100644
--- a/tempest/tests/boto/__init__.py
+++ b/tempest/tests/boto/__init__.py
@@ -26,7 +26,6 @@
import tempest.clients
from tempest.common.utils.file_utils import have_effective_read_access
import tempest.config
-from testresources import TestResourceManager
A_I_IMAGES_READY = False # ari,ami,aki
S3_CAN_CONNECT_ERROR = "Unknown Error"
@@ -97,8 +96,3 @@
else:
S3_CAN_CONNECT_ERROR = None
boto_logger.setLevel(level)
-
-
-class BotoResource(TestResourceManager):
- def make(self, dependency_resources=None):
- return generic_setup_package()
diff --git a/tempest/tests/compute/__init__.py b/tempest/tests/compute/__init__.py
index 08e5091..5b59a70 100644
--- a/tempest/tests/compute/__init__.py
+++ b/tempest/tests/compute/__init__.py
@@ -29,7 +29,7 @@
CREATE_IMAGE_ENABLED = CONFIG.compute.create_image_enabled
RESIZE_AVAILABLE = CONFIG.compute.resize_available
CHANGE_PASSWORD_AVAILABLE = CONFIG.compute.change_password_available
-WHITEBOX_ENABLED = CONFIG.compute.whitebox_enabled
+WHITEBOX_ENABLED = CONFIG.whitebox.whitebox_enabled
DISK_CONFIG_ENABLED = False
DISK_CONFIG_ENABLED_OVERRIDE = CONFIG.compute.disk_config_enabled_override
FLAVOR_EXTRA_DATA_ENABLED = False
diff --git a/tempest/tests/compute/servers/test_server_addresses.py b/tempest/tests/compute/servers/test_server_addresses.py
index 6e819a2..6b0f7ae 100644
--- a/tempest/tests/compute/servers/test_server_addresses.py
+++ b/tempest/tests/compute/servers/test_server_addresses.py
@@ -74,9 +74,9 @@
# We do not know the exact network configuration, but an instance
# should at least have a single public or private address
- self.assertGreaterEqual(len(addresses), 1)
+ self.assertTrue(len(addresses) >= 1)
for network_name, network_addresses in addresses.iteritems():
- self.assertGreaterEqual(len(network_addresses), 1)
+ self.assertTrue(len(network_addresses) >= 1)
for address in network_addresses:
self.assertTrue(address['addr'])
self.assertTrue(address['version'])
diff --git a/tempest/tests/identity/admin/test_services.py b/tempest/tests/identity/admin/test_services.py
index 5261b9d..73f4a90 100644
--- a/tempest/tests/identity/admin/test_services.py
+++ b/tempest/tests/identity/admin/test_services.py
@@ -16,6 +16,9 @@
# under the License.
+from nose.plugins.attrib import attr
+import unittest2 as unittest
+
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.tests.identity import base
@@ -30,8 +33,8 @@
name = rand_name('service-')
type = rand_name('type--')
description = rand_name('description-')
- resp, service_data = \
- self.client.create_service(name, type, description=description)
+ resp, service_data = self.client.create_service(
+ name, type, description=description)
self.assertTrue(resp['status'].startswith('2'))
#Verifying response body of create service
self.assertTrue('id' in service_data)
@@ -63,6 +66,32 @@
self.assertRaises(exceptions.NotFound, self.client.get_service,
service_data['id'])
+ def test_list_services(self):
+ # Create, List, Verify and Delete Services
+ services = []
+ for _ in xrange(3):
+ name = rand_name('service-')
+ type = rand_name('type--')
+ description = rand_name('description-')
+ resp, service = self.client.create_service(
+ name, type, description=description)
+ services.append(service)
+ service_ids = map(lambda x: x['id'], services)
+
+ # List and Verify Services
+ resp, body = self.client.list_services()
+ self.assertTrue(resp['status'].startswith('2'))
+ found = [service for service in body if service['id'] in service_ids]
+ self.assertEqual(len(found), len(services), 'Services not found')
+
+ # Delete Services
+ for service in services:
+ resp, body = self.client.delete_service(service['id'])
+ self.assertTrue(resp['status'].startswith('2'))
+ resp, body = self.client.list_services()
+ found = [service for service in body if service['id'] in service_ids]
+ self.assertFalse(any(found), 'Services failed to delete')
+
class ServicesTestJSON(base.BaseIdentityAdminTestJSON, ServicesTestBase):
@classmethod
diff --git a/tempest/tests/network/test_network_basic_ops.py b/tempest/tests/network/test_network_basic_ops.py
index bdebced..d297c98 100644
--- a/tempest/tests/network/test_network_basic_ops.py
+++ b/tempest/tests/network/test_network_basic_ops.py
@@ -186,9 +186,9 @@
cls.check_preconditions()
cfg = cls.config.network
cls.tenant_id = cls.manager._get_identity_client(
- cfg.username,
- cfg.password,
- cfg.tenant_name).tenant_id
+ cls.config.identity.username,
+ cls.config.identity.password,
+ cls.config.identity.tenant_name).tenant_id
# TODO(mnewby) Consider looking up entities as needed instead
# of storing them as collections on the class.
cls.keypairs = {}
diff --git a/tempest/tests/object_storage/test_container_sync.py b/tempest/tests/object_storage/test_container_sync.py
index 597fd86..d612880 100644
--- a/tempest/tests/object_storage/test_container_sync.py
+++ b/tempest/tests/object_storage/test_container_sync.py
@@ -103,7 +103,7 @@
self.assertEqual(resp['status'], '200',
'Error listing the destination container`s'
' "%s" contents' % (self.containers[0]))
- object_list_0 = {obj['name']: obj for obj in object_list_0}
+ object_list_0 = dict((obj['name'], obj) for obj in object_list_0)
# get second container content
resp, object_list_1 = \
cont_client[1].\
@@ -111,7 +111,7 @@
self.assertEqual(resp['status'], '200',
'Error listing the destination container`s'
' "%s" contents' % (self.containers[1]))
- object_list_1 = {obj['name']: obj for obj in object_list_1}
+ object_list_1 = dict((obj['name'], obj) for obj in object_list_1)
# check that containers is not empty and has equal keys()
# or wait for next attepmt
if not object_list_0 or not object_list_1 or \
diff --git a/tempest/tests/volume/admin/test_volume_types.py b/tempest/tests/volume/admin/test_volume_types.py
index cef8428..7184f11 100644
--- a/tempest/tests/volume/admin/test_volume_types.py
+++ b/tempest/tests/volume/admin/test_volume_types.py
@@ -21,6 +21,7 @@
class VolumeTypesTest(BaseVolumeTest):
+ _interface = "json"
@classmethod
def setUpClass(cls):
diff --git a/tempest/tests/volume/admin/test_volume_types_extra_specs.py b/tempest/tests/volume/admin/test_volume_types_extra_specs.py
index d3fd8e5..c743567 100644
--- a/tempest/tests/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/tests/volume/admin/test_volume_types_extra_specs.py
@@ -21,6 +21,7 @@
class VolumeTypesExtraSpecsTest(BaseVolumeTest):
+ _interface = "json"
@classmethod
def setUpClass(cls):
diff --git a/tempest/tests/volume/base.py b/tempest/tests/volume/base.py
index de78c99..32c211e 100644
--- a/tempest/tests/volume/base.py
+++ b/tempest/tests/volume/base.py
@@ -43,9 +43,10 @@
username, tenant_name, password = creds
os = clients.Manager(username=username,
password=password,
- tenant_name=tenant_name)
+ tenant_name=tenant_name,
+ interface=cls._interface)
else:
- os = clients.Manager()
+ os = clients.Manager(interface=cls._interface)
cls.os = os
cls.volumes_client = os.volumes_client
diff --git a/tempest/tests/volume/test_volumes_actions.py b/tempest/tests/volume/test_volumes_actions.py
index 155acb6..dd93b89 100644
--- a/tempest/tests/volume/test_volumes_actions.py
+++ b/tempest/tests/volume/test_volumes_actions.py
@@ -22,6 +22,7 @@
class VolumesActionsTest(BaseVolumeTest):
+ _interface = "json"
@classmethod
def setUpClass(cls):
diff --git a/tempest/whitebox.py b/tempest/whitebox.py
index 03ad63b..b7a1e68 100644
--- a/tempest/whitebox.py
+++ b/tempest/whitebox.py
@@ -63,9 +63,9 @@
super(ComputeWhiteboxTest, cls).setUpClass()
# Add some convenience attributes that tests use...
- cls.nova_dir = cls.config.compute.source_dir
- cls.compute_bin_dir = cls.config.compute.bin_dir
- cls.compute_config_path = cls.config.compute.config_path
+ cls.nova_dir = cls.config.whitebox.source_dir
+ cls.compute_bin_dir = cls.config.whitebox.bin_dir
+ cls.compute_config_path = cls.config.whitebox.config_path
cls.servers_client = cls.manager.servers_client
cls.images_client = cls.manager.images_client
cls.flavors_client = cls.manager.flavors_client
@@ -126,7 +126,7 @@
}
try:
- engine = create_engine(cls.config.compute.db_uri, **engine_args)
+ engine = create_engine(cls.config.whitebox.db_uri, **engine_args)
connection = engine.connect()
meta = MetaData()
meta.reflect(bind=engine)
diff --git a/tools/install_venv.py b/tools/install_venv.py
index 28275ba..52dbe39 100644
--- a/tools/install_venv.py
+++ b/tools/install_venv.py
@@ -5,6 +5,7 @@
# All Rights Reserved.
#
# Copyright 2010 OpenStack, LLC
+# Copyright 2013 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -25,123 +26,10 @@
import subprocess
import sys
-
-ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
-VENV = os.path.join(ROOT, '.venv')
-PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires')
-TEST_REQUIRES = os.path.join(ROOT, 'tools', 'test-requires')
-PY_VERSION = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
+import install_venv_common as install_venv
-def die(message, *args):
- print >> sys.stderr, message % args
- sys.exit(1)
-
-
-def check_python_version():
- if sys.version_info < (2, 6):
- die("Need Python Version >= 2.6")
-
-
-def run_command_with_code(cmd, redirect_output=True, check_exit_code=True):
- """Runs a command in an out-of-process shell.
-
- Returns the output of that command. Working directory is ROOT.
- """
- if redirect_output:
- stdout = subprocess.PIPE
- else:
- stdout = None
-
- proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout)
- output = proc.communicate()[0]
- if check_exit_code and proc.returncode != 0:
- die('Command "%s" failed.\n%s', ' '.join(cmd), output)
- return (output, proc.returncode)
-
-
-def run_command(cmd, redirect_output=True, check_exit_code=True):
- return run_command_with_code(cmd, redirect_output, check_exit_code)[0]
-
-
-class Distro(object):
-
- def check_cmd(self, cmd):
- return bool(run_command(['which', cmd], check_exit_code=False).strip())
-
- def install_virtualenv(self):
- if self.check_cmd('virtualenv'):
- return
-
- if self.check_cmd('easy_install'):
- print 'Installing virtualenv via easy_install...',
- if run_command(['easy_install', 'virtualenv']):
- print 'Succeeded'
- return
- else:
- print 'Failed'
-
- die('ERROR: virtualenv not found.\n\nTempest development'
- ' requires virtualenv, please install it using your'
- ' favorite package management tool')
-
- def post_process(self):
- """Any distribution-specific post-processing gets done here.
-
- In particular, this is useful for applying patches to code inside
- the venv.
- """
- pass
-
-
-class Fedora(Distro):
- """This covers Fedora-based distributions.
-
- Includes: Fedora, RHEL, Scientific Linux"""
-
- def check_pkg(self, pkg):
- return run_command_with_code(['rpm', '-q', pkg],
- check_exit_code=False)[1] == 0
-
- def yum_install(self, pkg, **kwargs):
- print "Attempting to install '%s' via yum" % pkg
- run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs)
-
- def apply_patch(self, originalfile, patchfile):
- run_command(['patch', originalfile, patchfile])
-
- def install_virtualenv(self):
- if self.check_cmd('virtualenv'):
- return
-
- if not self.check_pkg('python-virtualenv'):
- self.yum_install('python-virtualenv', check_exit_code=False)
-
- super(Fedora, self).install_virtualenv()
-
- def post_process(self):
- """Workaround for a bug in eventlet.
-
- This currently affects RHEL6.1, but the fix can safely be
- applied to all RHEL and Fedora distributions.
-
- This can be removed when the fix is applied upstream.
-
- Nova: https://bugs.launchpad.net/nova/+bug/884915
- Upstream: https://bitbucket.org/which_linden/eventlet/issue/89
- """
-
- # Install "patch" program if it's not there
- if not self.check_pkg('patch'):
- self.yum_install('patch')
-
- # Apply the eventlet patch
- self.apply_patch(os.path.join(VENV, 'lib', PY_VERSION, 'site-packages',
- 'eventlet/green/subprocess.py'),
- 'contrib/redhat-eventlet.patch')
-
-
-class CentOS(Fedora):
+class CentOS(install_venv.Fedora):
"""This covers CentOS."""
def post_process(self):
@@ -149,73 +37,6 @@
self.yum.install('openssl-devel', check_exit_code=False)
-def get_distro():
- if os.path.exists('/etc/redhat-release'):
- with open('/etc/redhat-release') as rh_release:
- if 'CentOS' in rh_release.read():
- return CentOS()
- return Fedora()
-
- if os.path.exists('/etc/fedora-release'):
- return Fedora()
-
- return Distro()
-
-
-def check_dependencies():
- get_distro().install_virtualenv()
-
-
-def create_virtualenv(venv=VENV, no_site_packages=True):
- """Creates the virtual environment and installs PIP.
-
- Creates the virtual environment and installs PIP only into the
- virtual environment.
- """
- print 'Creating venv...',
- if no_site_packages:
- run_command(['virtualenv', '-q', '--no-site-packages', VENV])
- else:
- run_command(['virtualenv', '-q', VENV])
- print 'done.'
- print 'Installing pip in virtualenv...',
- if not run_command(['tools/with_venv.sh', 'easy_install',
- 'pip>1.0']).strip():
- die("Failed to install pip.")
- print 'done.'
-
-
-def pip_install(*args):
- run_command(['tools/with_venv.sh',
- 'pip', 'install', '--upgrade'] + list(args),
- redirect_output=False)
-
-
-def install_dependencies(venv=VENV):
- print 'Installing dependencies with pip (this can take a while)...'
-
- # First things first, make sure our venv has the latest pip and distribute.
- # NOTE: we keep pip at version 1.1 since the most recent version causes
- # the .venv creation to fail. See:
- # https://bugs.launchpad.net/nova/+bug/1047120
- pip_install('pip==1.1')
- pip_install('distribute')
-
- # Install greenlet by hand - just listing it in the requires file does not
- # get it in stalled in the right order
- pip_install('greenlet')
-
- pip_install('-r', PIP_REQUIRES)
- pip_install('-r', TEST_REQUIRES)
-
- # Install tempest into the virtual_env. No more path munging!
- run_command([os.path.join(venv, 'bin/python'), 'setup.py', 'develop'])
-
-
-def post_process():
- get_distro().post_process()
-
-
def print_help():
help = """
Tempest development environment setup is complete.
@@ -238,23 +59,25 @@
print help
-def parse_args():
- """Parses command-line arguments."""
- parser = optparse.OptionParser()
- parser.add_option("-n", "--no-site-packages", dest="no_site_packages",
- default=False, action="store_true",
- help="Do not inherit packages from global Python"
- " install")
- return parser.parse_args()
-
-
def main(argv):
- (options, args) = parse_args()
- check_python_version()
- check_dependencies()
- create_virtualenv(no_site_packages=options.no_site_packages)
- install_dependencies()
- post_process()
+ root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+ venv = os.path.join(root, '.venv')
+ pip_requires = os.path.join(root, 'tools', 'pip-requires')
+ test_requires = os.path.join(root, 'tools', 'test-requires')
+ py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
+ project = 'Tempest'
+ install = install_venv.InstallVenv(root, venv, pip_requires, test_requires,
+ py_version, project)
+ if os.path.exists('/etc/redhat-release'):
+ with open('/etc/redhat-release') as rh_release:
+ if 'CentOS' in rh_release.read():
+ install_venv.Fedora = CentOS
+ options = install.parse_args(argv)
+ install.check_python_version()
+ install.check_dependencies()
+ install.create_virtualenv(no_site_packages=options.no_site_packages)
+ install.install_dependencies()
+ install.post_process()
print_help()
if __name__ == '__main__':
diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py
new file mode 100644
index 0000000..3dbcafe
--- /dev/null
+++ b/tools/install_venv_common.py
@@ -0,0 +1,225 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack, LLC
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""Provides methods needed by installation script for OpenStack development
+virtual environments.
+
+Synced in from openstack-common
+"""
+
+import os
+import subprocess
+import sys
+
+from tempest.openstack.common import cfg
+
+
+class InstallVenv(object):
+
+ def __init__(self, root, venv, pip_requires, test_requires, py_version,
+ project):
+ self.root = root
+ self.venv = venv
+ self.pip_requires = pip_requires
+ self.test_requires = test_requires
+ self.py_version = py_version
+ self.project = project
+
+ def die(self, message, *args):
+ print >> sys.stderr, message % args
+ sys.exit(1)
+
+ def check_python_version(self):
+ if sys.version_info < (2, 6):
+ self.die("Need Python Version >= 2.6")
+
+ def run_command_with_code(self, cmd, redirect_output=True,
+ check_exit_code=True):
+ """Runs a command in an out-of-process shell.
+
+ Returns the output of that command. Working directory is ROOT.
+ """
+ if redirect_output:
+ stdout = subprocess.PIPE
+ else:
+ stdout = None
+
+ proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout)
+ output = proc.communicate()[0]
+ if check_exit_code and proc.returncode != 0:
+ self.die('Command "%s" failed.\n%s', ' '.join(cmd), output)
+ return (output, proc.returncode)
+
+ def run_command(self, cmd, redirect_output=True, check_exit_code=True):
+ return self.run_command_with_code(cmd, redirect_output,
+ check_exit_code)[0]
+
+ def get_distro(self):
+ if (os.path.exists('/etc/fedora-release') or
+ os.path.exists('/etc/redhat-release')):
+ return Fedora(self.root, self.venv, self.pip_requires,
+ self.test_requires, self.py_version, self.project)
+ else:
+ return Distro(self.root, self.venv, self.pip_requires,
+ self.test_requires, self.py_version, self.project)
+
+ def check_dependencies(self):
+ self.get_distro().install_virtualenv()
+
+ def create_virtualenv(self, no_site_packages=True):
+ """Creates the virtual environment and installs PIP.
+
+ Creates the virtual environment and installs PIP only into the
+ virtual environment.
+ """
+ if not os.path.isdir(self.venv):
+ print 'Creating venv...',
+ if no_site_packages:
+ self.run_command(['virtualenv', '-q', '--no-site-packages',
+ self.venv])
+ else:
+ self.run_command(['virtualenv', '-q', self.venv])
+ print 'done.'
+ print 'Installing pip in virtualenv...',
+ if not self.run_command(['tools/with_venv.sh', 'easy_install',
+ 'pip>1.0']).strip():
+ self.die("Failed to install pip.")
+ print 'done.'
+ else:
+ print "venv already exists..."
+ pass
+
+ def pip_install(self, *args):
+ self.run_command(['tools/with_venv.sh',
+ 'pip', 'install', '--upgrade'] + list(args),
+ redirect_output=False)
+
+ def install_dependencies(self):
+ print 'Installing dependencies with pip (this can take a while)...'
+
+ # First things first, make sure our venv has the latest pip and
+ # distribute.
+ # NOTE: we keep pip at version 1.1 since the most recent version causes
+ # the .venv creation to fail. See:
+ # https://bugs.launchpad.net/nova/+bug/1047120
+ self.pip_install('pip==1.1')
+ self.pip_install('distribute')
+
+ # Install greenlet by hand - just listing it in the requires file does
+ # not
+ # get it installed in the right order
+ self.pip_install('greenlet')
+
+ self.pip_install('-r', self.pip_requires)
+ self.pip_install('-r', self.test_requires)
+
+ def post_process(self):
+ self.get_distro().post_process()
+
+ def parse_args(self, argv):
+ """Parses command-line arguments."""
+ cli_opts = [
+ cfg.BoolOpt('no-site-packages',
+ default=False,
+ short='n',
+ help="Do not inherit packages from global Python"
+ "install"),
+ ]
+ CLI = cfg.ConfigOpts()
+ CLI.register_cli_opts(cli_opts)
+ CLI(argv[1:])
+ return CLI
+
+
+class Distro(InstallVenv):
+
+ def check_cmd(self, cmd):
+ return bool(self.run_command(['which', cmd],
+ check_exit_code=False).strip())
+
+ def install_virtualenv(self):
+ if self.check_cmd('virtualenv'):
+ return
+
+ if self.check_cmd('easy_install'):
+ print 'Installing virtualenv via easy_install...',
+ if self.run_command(['easy_install', 'virtualenv']):
+ print 'Succeeded'
+ return
+ else:
+ print 'Failed'
+
+ self.die('ERROR: virtualenv not found.\n\n%s development'
+ ' requires virtualenv, please install it using your'
+ ' favorite package management tool' % self.project)
+
+ def post_process(self):
+ """Any distribution-specific post-processing gets done here.
+
+ In particular, this is useful for applying patches to code inside
+ the venv.
+ """
+ pass
+
+
+class Fedora(Distro):
+ """This covers all Fedora-based distributions.
+
+ Includes: Fedora, RHEL, CentOS, Scientific Linux
+ """
+
+ def check_pkg(self, pkg):
+ return self.run_command_with_code(['rpm', '-q', pkg],
+ check_exit_code=False)[1] == 0
+
+ def yum_install(self, pkg, **kwargs):
+ print "Attempting to install '%s' via yum" % pkg
+ self.run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs)
+
+ def apply_patch(self, originalfile, patchfile):
+ self.run_command(['patch', originalfile, patchfile])
+
+ def install_virtualenv(self):
+ if self.check_cmd('virtualenv'):
+ return
+
+ if not self.check_pkg('python-virtualenv'):
+ self.yum_install('python-virtualenv', check_exit_code=False)
+
+ super(Fedora, self).install_virtualenv()
+
+ def post_process(self):
+ """Workaround for a bug in eventlet.
+
+ This currently affects RHEL6.1, but the fix can safely be
+ applied to all RHEL and Fedora distributions.
+
+ This can be removed when the fix is applied upstream.
+
+ Nova: https://bugs.launchpad.net/nova/+bug/884915
+ Upstream: https://bitbucket.org/which_linden/eventlet/issue/89
+ """
+
+ # Install "patch" program if it's not there
+ if not self.check_pkg('patch'):
+ self.yum_install('patch')
+
+ # Apply the eventlet patch
+ self.apply_patch(os.path.join(self.venv, 'lib', self.py_version,
+ 'site-packages',
+ 'eventlet/green/subprocess.py'),
+ 'contrib/redhat-eventlet.patch')
diff --git a/tools/pip-requires b/tools/pip-requires
index dcc859f..504ca24 100644
--- a/tools/pip-requires
+++ b/tools/pip-requires
@@ -11,3 +11,5 @@
python-novaclient>=2.10.0
python-quantumclient>=2.1
testresources
+unittest2
+keyring