Merge "javelin 2"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 28a4d1c..37d4d53 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -211,13 +211,14 @@
# admin credentials are known. (boolean value)
#allow_tenant_isolation=false
-# Valid primary image reference to be used in tests. (string
-# value)
-#image_ref={$IMAGE_ID}
+# Valid primary image reference to be used in tests. This is a
+# required option (string value)
+#image_ref=<None>
-# Valid secondary image reference to be used in tests. (string
-# value)
-#image_ref_alt={$IMAGE_ID_ALT}
+# Valid secondary image reference to be used in tests. This is
+# a required option, but if only one image is available
+# duplicate the value of image_ref above (string value)
+#image_ref_alt=<None>
# Valid primary flavor to use in tests. (string value)
#flavor_ref=1
@@ -241,7 +242,7 @@
#image_alt_ssh_password=password
# Time in seconds between build status checks. (integer value)
-#build_interval=10
+#build_interval=1
# Timeout in seconds to wait for an instance to build.
# (integer value)
@@ -332,6 +333,9 @@
# admin credentials are known. (boolean value)
#allow_tenant_isolation=false
+# Time in seconds between build status checks. (integer value)
+#build_interval=1
+
[compute-admin]
@@ -705,7 +709,7 @@
# Time in seconds between network operation status checks.
# (integer value)
-#build_interval=10
+#build_interval=1
[network-feature-enabled]
@@ -788,9 +792,6 @@
# (string value)
#endpoint_type=publicURL
-# Time in seconds between build status checks. (integer value)
-#build_interval=1
-
# Timeout in seconds to wait for a stack to build. (integer
# value)
#build_timeout=1200
@@ -825,6 +826,10 @@
# Catalog type of the Queuing service. (string value)
#catalog_type=queuing
+# The maximum number of queue records per page when listing
+# queues (integer value)
+#max_queues_per_page=20
+
[scenario]
@@ -980,7 +985,7 @@
# Time in seconds between volume availability checks. (integer
# value)
-#build_interval=10
+#build_interval=1
# Timeout in seconds to wait for a volume to becomeavailable.
# (integer value)
diff --git a/requirements.txt b/requirements.txt
index 75a61e7..4bf2bcf 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,14 +5,14 @@
testtools>=0.9.34
lxml>=2.3
boto>=2.12.0,!=2.13.0
-paramiko>=1.9.0
+paramiko>=1.13.0
netaddr>=0.7.6
python-glanceclient>=0.9.0
python-keystoneclient>=0.8.0
python-novaclient>=2.17.0
python-neutronclient>=2.3.4,<3
python-cinderclient>=1.0.6
-python-heatclient>=0.2.3
+python-heatclient>=0.2.9
python-ironicclient
python-saharaclient>=0.6.0
python-swiftclient>=2.0.2
diff --git a/tempest/api/data_processing/base.py b/tempest/api/data_processing/base.py
index 8d5e223..cc76880 100644
--- a/tempest/api/data_processing/base.py
+++ b/tempest/api/data_processing/base.py
@@ -38,6 +38,7 @@
cls._node_group_templates = []
cls._cluster_templates = []
cls._data_sources = []
+ cls._job_binary_internals = []
@classmethod
def tearDownClass(cls):
@@ -47,6 +48,8 @@
cls.client.delete_node_group_template)
cls.cleanup_resources(getattr(cls, '_data_sources', []),
cls.client.delete_data_source)
+ cls.cleanup_resources(getattr(cls, '_job_binary_internals', []),
+ cls.client.delete_job_binary_internal)
cls.clear_isolated_creds()
super(BaseDataProcessingTest, cls).tearDownClass()
@@ -112,3 +115,16 @@
cls._data_sources.append(body['id'])
return resp, body
+
+ @classmethod
+ def create_job_binary_internal(cls, name, data):
+ """Creates watched job binary internal with specified params.
+
+ It returns created object. All resources created in this method will
+ be automatically removed in tearDownClass method.
+ """
+ resp, body = cls.client.create_job_binary_internal(name, data)
+ # store id of created job binary internal
+ cls._job_binary_internals.append(body['id'])
+
+ return resp, body
diff --git a/tempest/api/data_processing/test_data_sources.py b/tempest/api/data_processing/test_data_sources.py
new file mode 100644
index 0000000..c72e828
--- /dev/null
+++ b/tempest/api/data_processing/test_data_sources.py
@@ -0,0 +1,154 @@
+# Copyright (c) 2014 Mirantis Inc.
+#
+# 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.
+
+from tempest.api.data_processing import base as dp_base
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class DataSourceTest(dp_base.BaseDataProcessingTest):
+ @classmethod
+ def setUpClass(cls):
+ super(DataSourceTest, cls).setUpClass()
+ cls.swift_data_source_with_creds = {
+ 'url': 'swift://sahara-container.sahara/input-source',
+ 'description': 'Test data source',
+ 'credentials': {
+ 'user': CONF.identity.username,
+ 'password': CONF.identity.password
+ },
+ 'type': 'swift'
+ }
+ cls.swift_data_source = cls.swift_data_source_with_creds.copy()
+ del cls.swift_data_source['credentials']
+
+ cls.local_hdfs_data_source = {
+ 'url': 'input-source',
+ 'description': 'Test data source',
+ 'type': 'hdfs'
+ }
+
+ cls.external_hdfs_data_source = {
+ 'url': 'hdfs://172.18.168.2:8020/usr/hadoop/input-source',
+ 'description': 'Test data source',
+ 'type': 'hdfs'
+ }
+
+ def _create_data_source(self, source_body, source_name=None):
+ """Creates Data Source with optional name specified.
+
+ It creates a link to input-source file (it may not exist) and ensures
+ response status and source name. Returns id and name of created source.
+ """
+ if not source_name:
+ # generate random name if it's not specified
+ source_name = data_utils.rand_name('sahara-data-source')
+
+ # create data source
+ resp, body = self.create_data_source(source_name, **source_body)
+
+ # ensure that source created successfully
+ self.assertEqual(202, resp.status)
+ self.assertEqual(source_name, body['name'])
+ if source_body['type'] == 'swift':
+ source_body = self.swift_data_source
+ self.assertDictContainsSubset(source_body, body)
+
+ return body['id'], source_name
+
+ def _list_data_sources(self, source_info):
+ # check for data source in list
+ resp, sources = self.client.list_data_sources()
+ self.assertEqual(200, resp.status)
+ sources_info = [(source['id'], source['name']) for source in sources]
+ self.assertIn(source_info, sources_info)
+
+ def _get_data_source(self, source_id, source_name, source_body):
+ # check data source fetch by id
+ resp, source = self.client.get_data_source(source_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(source_name, source['name'])
+ self.assertDictContainsSubset(source_body, source)
+
+ def _delete_data_source(self, source_id):
+ # delete the data source by id
+ resp = self.client.delete_data_source(source_id)[0]
+ self.assertEqual(204, resp.status)
+
+ @test.attr(type='smoke')
+ def test_swift_data_source_create(self):
+ self._create_data_source(self.swift_data_source_with_creds)
+
+ @test.attr(type='smoke')
+ def test_swift_data_source_list(self):
+ source_info = self._create_data_source(
+ self.swift_data_source_with_creds)
+ self._list_data_sources(source_info)
+
+ @test.attr(type='smoke')
+ def test_swift_data_source_get(self):
+ source_id, source_name = self._create_data_source(
+ self.swift_data_source_with_creds)
+ self._get_data_source(source_id, source_name, self.swift_data_source)
+
+ @test.attr(type='smoke')
+ def test_swift_data_source_delete(self):
+ source_id = self._create_data_source(
+ self.swift_data_source_with_creds)[0]
+ self._delete_data_source(source_id)
+
+ @test.attr(type='smoke')
+ def test_local_hdfs_data_source_create(self):
+ self._create_data_source(self.local_hdfs_data_source)
+
+ @test.attr(type='smoke')
+ def test_local_hdfs_data_source_list(self):
+ source_info = self._create_data_source(self.local_hdfs_data_source)
+ self._list_data_sources(source_info)
+
+ @test.attr(type='smoke')
+ def test_local_hdfs_data_source_get(self):
+ source_id, source_name = self._create_data_source(
+ self.local_hdfs_data_source)
+ self._get_data_source(
+ source_id, source_name, self.local_hdfs_data_source)
+
+ @test.attr(type='smoke')
+ def test_local_hdfs_data_source_delete(self):
+ source_id = self._create_data_source(self.local_hdfs_data_source)[0]
+ self._delete_data_source(source_id)
+
+ @test.attr(type='smoke')
+ def test_external_hdfs_data_source_create(self):
+ self._create_data_source(self.external_hdfs_data_source)
+
+ @test.attr(type='smoke')
+ def test_external_hdfs_data_source_list(self):
+ source_info = self._create_data_source(self.external_hdfs_data_source)
+ self._list_data_sources(source_info)
+
+ @test.attr(type='smoke')
+ def test_external_hdfs_data_source_get(self):
+ source_id, source_name = self._create_data_source(
+ self.external_hdfs_data_source)
+ self._get_data_source(
+ source_id, source_name, self.external_hdfs_data_source)
+
+ @test.attr(type='smoke')
+ def test_external_hdfs_data_source_delete(self):
+ source_id = self._create_data_source(self.external_hdfs_data_source)[0]
+ self._delete_data_source(source_id)
diff --git a/tempest/api/queuing/base.py b/tempest/api/queuing/base.py
index 6c22719..5649619 100644
--- a/tempest/api/queuing/base.py
+++ b/tempest/api/queuing/base.py
@@ -50,6 +50,42 @@
@classmethod
def delete_queue(cls, queue_name):
- """Wrapper utility that returns a test queue."""
+ """Wrapper utility that deletes a test queue."""
resp, body = cls.client.delete_queue(queue_name)
return resp, body
+
+ @classmethod
+ def check_queue_exists(cls, queue_name):
+ """Wrapper utility that checks the existence of a test queue."""
+ resp, body = cls.client.get_queue(queue_name)
+ return resp, body
+
+ @classmethod
+ def check_queue_exists_head(cls, queue_name):
+ """Wrapper utility checks the head of a queue via http HEAD."""
+ resp, body = cls.client.head_queue(queue_name)
+ return resp, body
+
+ @classmethod
+ def list_queues(cls):
+ """Wrapper utility that lists queues."""
+ resp, body = cls.client.list_queues()
+ return resp, body
+
+ @classmethod
+ def get_queue_stats(cls, queue_name):
+ """Wrapper utility that returns the queue stats."""
+ resp, body = cls.client.get_queue_stats(queue_name)
+ return resp, body
+
+ @classmethod
+ def get_queue_metadata(cls, queue_name):
+ """Wrapper utility that gets a queue metadata."""
+ resp, body = cls.client.get_queue_metadata(queue_name)
+ return resp, body
+
+ @classmethod
+ def set_queue_metadata(cls, queue_name, rbody):
+ """Wrapper utility that sets the metadata of a queue."""
+ resp, body = cls.client.set_queue_metadata(queue_name, rbody)
+ return resp, body
diff --git a/tempest/api/queuing/test_queues.py b/tempest/api/queuing/test_queues.py
index 4d03f7e..e43178a 100644
--- a/tempest/api/queuing/test_queues.py
+++ b/tempest/api/queuing/test_queues.py
@@ -14,6 +14,8 @@
# limitations under the License.
import logging
+from six import moves
+from testtools import matchers
from tempest.api.queuing import base
from tempest.common.utils import data_utils
@@ -43,18 +45,86 @@
@classmethod
def setUpClass(cls):
super(TestManageQueue, cls).setUpClass()
- cls.queue_name = data_utils.rand_name('Queues-Test')
- # Create Queue
- cls.client.create_queue(cls.queue_name)
+ cls.queues = list()
+ for _ in moves.xrange(5):
+ queue_name = data_utils.rand_name('Queues-Test')
+ cls.queues.append(queue_name)
+ # Create Queue
+ cls.client.create_queue(queue_name)
@test.attr(type='smoke')
def test_delete_queue(self):
# Delete Queue
- resp, body = self.delete_queue(self.queue_name)
+ queue_name = self.queues.pop()
+ resp, body = self.delete_queue(queue_name)
self.assertEqual('204', resp['status'])
self.assertEqual('', body)
+ @test.attr(type='smoke')
+ def test_check_queue_existence(self):
+ # Checking Queue Existence
+ for queue_name in self.queues:
+ resp, body = self.check_queue_exists(queue_name)
+ self.assertEqual('204', resp['status'])
+ self.assertEqual('', body)
+
+ @test.attr(type='smoke')
+ def test_check_queue_head(self):
+ # Checking Queue Existence by calling HEAD
+ for queue_name in self.queues:
+ resp, body = self.check_queue_exists_head(queue_name)
+ self.assertEqual('204', resp['status'])
+ self.assertEqual('', body)
+
+ @test.attr(type='smoke')
+ def test_list_queues(self):
+ # Listing queues
+ resp, body = self.list_queues()
+ self.assertEqual(len(body['queues']), len(self.queues))
+ for item in body['queues']:
+ self.assertIn(item['name'], self.queues)
+
+ @test.attr(type='smoke')
+ def test_get_queue_stats(self):
+ # Retrieve random queue
+ queue_name = self.queues[data_utils.rand_int_id(0,
+ len(self.queues) - 1)]
+ # Get Queue Stats for a newly created Queue
+ resp, body = self.get_queue_stats(queue_name)
+ msgs = body['messages']
+ for element in ('free', 'claimed', 'total'):
+ self.assertEqual(0, msgs[element])
+ for element in ('oldest', 'newest'):
+ self.assertNotIn(element, msgs)
+
+ @test.attr(type='smoke')
+ def test_set_and_get_queue_metadata(self):
+ # Retrieve random queue
+ queue_name = self.queues[data_utils.rand_int_id(0,
+ len(self.queues) - 1)]
+ # Check the Queue has no metadata
+ resp, body = self.get_queue_metadata(queue_name)
+ self.assertEqual('200', resp['status'])
+ self.assertThat(body, matchers.HasLength(0))
+ # Create metadata
+ key3 = [0, 1, 2, 3, 4]
+ key2 = data_utils.rand_name('value')
+ req_body1 = dict()
+ req_body1[data_utils.rand_name('key3')] = key3
+ req_body1[data_utils.rand_name('key2')] = key2
+ req_body = dict()
+ req_body[data_utils.rand_name('key1')] = req_body1
+ # Set Queue Metadata
+ resp, body = self.set_queue_metadata(queue_name, req_body)
+ self.assertEqual('204', resp['status'])
+ self.assertEqual('', body)
+ # Get Queue Metadata
+ resp, body = self.get_queue_metadata(queue_name)
+ self.assertEqual('200', resp['status'])
+ self.assertThat(body, matchers.Equals(req_body))
+
@classmethod
def tearDownClass(cls):
- cls.client.delete_queue(cls.queue_name)
+ for queue_name in cls.queues:
+ cls.client.delete_queue(queue_name)
super(TestManageQueue, cls).tearDownClass()
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index 531e145..ecd8836 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -15,6 +15,7 @@
# under the License.
from tempest.api.volume import base
+from tempest.common.utils import data_utils
from tempest import test
QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes']
@@ -99,6 +100,27 @@
self.assertEqual(quota_usage['gigabytes']['in_use'] + 1,
new_quota_usage['gigabytes']['in_use'])
+ @test.attr(type='gate')
+ def test_delete_quota(self):
+ # Admin can delete the resource quota set for a tenant
+ tenant_name = data_utils.rand_name('quota_tenant_')
+ identity_client = self.os_adm.identity_client
+ tenant = identity_client.create_tenant(tenant_name)[1]
+ tenant_id = tenant['id']
+ self.addCleanup(identity_client.delete_tenant, tenant_id)
+ _, quota_set_default = self.quotas_client.get_default_quota_set(
+ tenant_id)
+ volume_default = quota_set_default['volumes']
+
+ self.quotas_client.update_quota_set(tenant_id,
+ volumes=(int(volume_default) + 5))
+
+ resp, _ = self.quotas_client.delete_quota_set(tenant_id)
+ self.assertEqual(200, resp.status)
+
+ _, quota_set_new = self.quotas_client.get_quota_set(tenant_id)
+ self.assertEqual(volume_default, quota_set_new['volumes'])
+
class VolumeQuotasAdminTestXML(VolumeQuotasAdminTestJSON):
_interface = "xml"
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 4d11d24..67d0203 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -109,6 +109,7 @@
cls.backups_client = cls.os.backups_client
cls.volume_services_client = cls.os.volume_services_client
cls.volumes_extension_client = cls.os.volumes_extension_client
+ cls.availability_zone_client = cls.os.volume_availability_zone_client
@classmethod
def create_volume(cls, size=1, **kwargs):
diff --git a/tempest/api/volume/test_availability_zone.py b/tempest/api/volume/test_availability_zone.py
new file mode 100644
index 0000000..1db7b7b
--- /dev/null
+++ b/tempest/api/volume/test_availability_zone.py
@@ -0,0 +1,40 @@
+# Copyright 2014 NEC Corporation
+# 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.
+
+from tempest.api.volume import base
+from tempest import test
+
+
+class AvailabilityZoneTestJSON(base.BaseVolumeV1Test):
+
+ """
+ Tests Availability Zone API List
+ """
+
+ @classmethod
+ def setUpClass(cls):
+ super(AvailabilityZoneTestJSON, cls).setUpClass()
+ cls.client = cls.availability_zone_client
+
+ @test.attr(type='gate')
+ def test_get_availability_zone_list(self):
+ # List of availability zone
+ resp, availability_zone = self.client.get_availability_zone_list()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(availability_zone) > 0)
+
+
+class AvailabilityZoneTestXML(AvailabilityZoneTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api_schema/compute/servers.py b/tempest/api_schema/compute/servers.py
index fb39087..2002927 100644
--- a/tempest/api_schema/compute/servers.py
+++ b/tempest/api_schema/compute/servers.py
@@ -147,3 +147,14 @@
server_actions_delete_password = {
'status_code': [204]
}
+
+get_console_output = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'output': {'type': 'string'}
+ },
+ 'required': ['output']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/security_groups.py b/tempest/api_schema/compute/v2/security_groups.py
index 8b4bead..9a852e5 100644
--- a/tempest/api_schema/compute/v2/security_groups.py
+++ b/tempest/api_schema/compute/v2/security_groups.py
@@ -80,6 +80,10 @@
}
}
+delete_security_group = {
+ 'status_code': [202]
+}
+
create_security_group_rule = {
'status_code': [200],
'response_body': {
diff --git a/tempest/api_schema/queuing/__init__.py b/tempest/api_schema/queuing/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api_schema/queuing/__init__.py
diff --git a/tempest/api_schema/queuing/v1/__init__.py b/tempest/api_schema/queuing/v1/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api_schema/queuing/v1/__init__.py
diff --git a/tempest/api_schema/queuing/v1/queues.py b/tempest/api_schema/queuing/v1/queues.py
new file mode 100644
index 0000000..4630e1c
--- /dev/null
+++ b/tempest/api_schema/queuing/v1/queues.py
@@ -0,0 +1,98 @@
+
+# Copyright (c) 2014 Rackspace, Inc.
+#
+# 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.
+
+list_link = {
+ 'type': 'object',
+ 'properties': {
+ 'rel': {'type': 'string'},
+ 'href': {
+ 'type': 'string',
+ 'format': 'uri'
+ }
+ },
+ 'required': ['href', 'rel']
+}
+
+list_queue = {
+ 'type': 'object',
+ 'properties': {
+ 'name': {'type': 'string'},
+ 'href': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'metadata': {'type': 'object'}
+ },
+ 'required': ['name', 'href']
+}
+
+list_queues = {
+ 'status_code': [200, 204],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'links': {
+ 'type': 'array',
+ 'items': list_link,
+ 'maxItems': 1
+ },
+ 'queues': {
+ 'type': 'array',
+ 'items': list_queue
+ }
+ },
+ 'required': ['links', 'queues']
+ }
+}
+
+message_link = {
+ 'type': 'object',
+ 'properties': {
+ 'href': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'age': {'type': 'number'},
+ 'created': {
+ 'type': 'string',
+ 'format': 'date-time'
+ }
+ },
+ 'required': ['href', 'age', 'created']
+}
+
+messages = {
+ 'type': 'object',
+ 'properties': {
+ 'free': {'type': 'number'},
+ 'claimed': {'type': 'number'},
+ 'total': {'type': 'number'},
+ 'oldest': message_link,
+ 'newest': message_link
+ },
+ 'required': ['free', 'claimed', 'total']
+}
+
+queue_stats = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'messages': messages
+ },
+ 'required': ['messages']
+ }
+}
diff --git a/tempest/clients.py b/tempest/clients.py
index 37049d6..7745d54 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -168,6 +168,8 @@
VolumesServicesClientJSON
from tempest.services.volume.json.admin.volume_types_client import \
VolumeTypesClientJSON
+from tempest.services.volume.json.availability_zone_client import \
+ VolumeAvailabilityZoneClientJSON
from tempest.services.volume.json.backups_client import BackupsClientJSON
from tempest.services.volume.json.extensions_client import \
ExtensionsClientJSON as VolumeExtensionClientJSON
@@ -183,6 +185,8 @@
VolumesServicesClientXML
from tempest.services.volume.xml.admin.volume_types_client import \
VolumeTypesClientXML
+from tempest.services.volume.xml.availability_zone_client import \
+ VolumeAvailabilityZoneClientXML
from tempest.services.volume.xml.backups_client import BackupsClientXML
from tempest.services.volume.xml.extensions_client import \
ExtensionsClientXML as VolumeExtensionClientXML
@@ -262,6 +266,8 @@
self.auth_provider)
self.token_client = TokenClientXML()
self.token_v3_client = V3TokenClientXML()
+ self.volume_availability_zone_client = \
+ VolumeAvailabilityZoneClientXML(self.auth_provider)
elif self.interface == 'json':
self.certificates_client = CertificatesClientJSON(
@@ -358,6 +364,8 @@
self.negative_client = rest_client.NegativeRestClient(
self.auth_provider)
self.negative_client.service = service
+ self.volume_availability_zone_client = \
+ VolumeAvailabilityZoneClientJSON(self.auth_provider)
else:
msg = "Unsupported interface type `%s'" % interface
diff --git a/tempest/common/commands.py b/tempest/common/commands.py
index c31a038..6580c65 100644
--- a/tempest/common/commands.py
+++ b/tempest/common/commands.py
@@ -28,15 +28,13 @@
args = shlex.split(cmd)
subprocess_args = {'stdout': subprocess.PIPE,
'stderr': subprocess.STDOUT}
- try:
- proc = subprocess.Popen(['/usr/bin/sudo', '-n'] + args,
- **subprocess_args)
- return proc.communicate()[0]
- if proc.returncode != 0:
- LOG.error(cmd + "returned with: " +
- proc.returncode + "exit status")
- except subprocess.CalledProcessError as e:
- LOG.error("command output:\n%s" % e.output)
+ proc = subprocess.Popen(['/usr/bin/sudo', '-n'] + args,
+ **subprocess_args)
+ stdout = proc.communicate()[0]
+ if proc.returncode != 0:
+ LOG.error(("Command {0} returned with exit status {1},"
+ "output {2}").format(cmd, proc.returncode, stdout))
+ return stdout
def ip_addr_raw():
diff --git a/tempest/config.py b/tempest/config.py
index f9be90d..1049f67 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -137,11 +137,12 @@
"better parallel execution, but also requires that "
"OpenStack Identity API admin credentials are known."),
cfg.StrOpt('image_ref',
- default="{$IMAGE_ID}",
- help="Valid primary image reference to be used in tests."),
+ help="Valid primary image reference to be used in tests. "
+ "This is a required option"),
cfg.StrOpt('image_ref_alt',
- default="{$IMAGE_ID_ALT}",
- help="Valid secondary image reference to be used in tests."),
+ help="Valid secondary image reference to be used in tests. "
+ "This is a required option, but if only one image is "
+ "available duplicate the value of image_ref above"),
cfg.StrOpt('flavor_ref',
default="1",
help="Valid primary flavor to use in tests."),
@@ -163,7 +164,7 @@
help="Password used to authenticate to an instance using "
"the alternate image."),
cfg.IntOpt('build_interval',
- default=10,
+ default=1,
help="Time in seconds between build status checks."),
cfg.IntOpt('build_timeout',
default=300,
@@ -411,7 +412,7 @@
help="Timeout in seconds to wait for network operation to "
"complete."),
cfg.IntOpt('build_interval',
- default=10,
+ default=1,
help="Time in seconds between network operation status "
"checks."),
]
@@ -436,6 +437,10 @@
cfg.StrOpt('catalog_type',
default='queuing',
help='Catalog type of the Queuing service.'),
+ cfg.IntOpt('max_queues_per_page',
+ default=20,
+ help='The maximum number of queue records per page when '
+ 'listing queues'),
]
volume_group = cfg.OptGroup(name='volume',
@@ -443,7 +448,7 @@
VolumeGroup = [
cfg.IntOpt('build_interval',
- default=10,
+ default=1,
help='Time in seconds between volume availability checks.'),
cfg.IntOpt('build_timeout',
default=300,
diff --git a/tempest/services/compute/json/security_groups_client.py b/tempest/services/compute/json/security_groups_client.py
index c19baf3..a86f3df 100644
--- a/tempest/services/compute/json/security_groups_client.py
+++ b/tempest/services/compute/json/security_groups_client.py
@@ -88,7 +88,10 @@
def delete_security_group(self, security_group_id):
"""Deletes the provided Security Group."""
- return self.delete('os-security-groups/%s' % str(security_group_id))
+ resp, body = self.delete(
+ 'os-security-groups/%s' % str(security_group_id))
+ self.validate_response(schema.delete_security_group, resp, body)
+ return resp, body
def create_security_group_rule(self, parent_group_id, ip_proto, from_port,
to_port, **kwargs):
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 9c7eec6..77c73df 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -210,11 +210,13 @@
post_body)
if response_key is not None:
body = json.loads(body)
- # Check for Schema as 'None' because if we donot have any server
+ # Check for Schema as 'None' because if we do not have any server
# action schema implemented yet then they can pass 'None' to skip
# the validation.Once all server action has their schema
# implemented then, this check can be removed if every actions are
# supposed to validate their response.
+ # TODO(GMann): Remove the below 'if' check once all server actions
+ # schema are implemented.
if schema is not None:
self.validate_response(schema, resp, body)
body = body[response_key]
@@ -427,7 +429,7 @@
def get_console_output(self, server_id, length):
return self.action(server_id, 'os-getConsoleOutput', 'output',
- None, length=length)
+ common_schema.get_console_output, length=length)
def list_virtual_interfaces(self, server_id):
"""
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index caa0a1d..2a83f88 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -209,7 +209,17 @@
resp, body = self.post('servers/%s/action' % str(server_id),
post_body)
if response_key is not None:
- body = json.loads(body)[response_key]
+ body = json.loads(body)
+ # Check for Schema as 'None' because if we do not have any server
+ # action schema implemented yet then they can pass 'None' to skip
+ # the validation.Once all server action has their schema
+ # implemented then, this check can be removed if every actions are
+ # supposed to validate their response.
+ # TODO(GMann): Remove the below 'if' check once all server actions
+ # schema are implemented.
+ if schema is not None:
+ self.validate_response(schema, resp, body)
+ body = body[response_key]
else:
self.validate_response(schema, resp, body)
return resp, body
@@ -256,7 +266,7 @@
if 'disk_config' in kwargs:
kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
del kwargs['disk_config']
- return self.action(server_id, 'rebuild', 'server', **kwargs)
+ return self.action(server_id, 'rebuild', 'server', None, **kwargs)
def resize(self, server_id, flavor_ref, **kwargs):
"""Changes the flavor of a server."""
@@ -421,11 +431,12 @@
def get_console_output(self, server_id, length):
return self.action(server_id, 'get_console_output', 'output',
- length=length)
+ common_schema.get_console_output, length=length)
def rescue_server(self, server_id, **kwargs):
"""Rescue the provided server."""
- return self.action(server_id, 'rescue', 'admin_password', **kwargs)
+ return self.action(server_id, 'rescue', 'admin_password',
+ None, **kwargs)
def unrescue_server(self, server_id):
"""Unrescue the provided server."""
@@ -483,9 +494,9 @@
def get_spice_console(self, server_id, console_type):
"""Get URL of Spice console."""
return self.action(server_id, "get_spice_console"
- "console", type=console_type)
+ "console", None, type=console_type)
def get_rdp_console(self, server_id, console_type):
"""Get URL of RDP console."""
return self.action(server_id, "get_rdp_console"
- "console", type=console_type)
+ "console", None, type=console_type)
diff --git a/tempest/services/data_processing/v1_1/client.py b/tempest/services/data_processing/v1_1/client.py
index b07e663..73e67c3 100644
--- a/tempest/services/data_processing/v1_1/client.py
+++ b/tempest/services/data_processing/v1_1/client.py
@@ -161,3 +161,28 @@
uri = 'data-sources/%s' % source_id
return self.delete(uri)
+
+ def list_job_binary_internals(self):
+ """List all job binary internals for a user."""
+
+ uri = 'job-binary-internals'
+ return self._request_and_parse(self.get, uri, 'binaries')
+
+ def get_job_binary_internal(self, job_binary_id):
+ """Returns the details of a single job binary internal."""
+
+ uri = 'job-binary-internals/%s' % job_binary_id
+ return self._request_and_parse(self.get, uri, 'job_binary_internal')
+
+ def create_job_binary_internal(self, name, data):
+ """Creates job binary internal with specified params."""
+
+ uri = 'job-binary-internals/%s' % name
+ return self._request_and_parse(self.put, uri, 'job_binary_internal',
+ data)
+
+ def delete_job_binary_internal(self, job_binary_id):
+ """Deletes the specified job binary internal by id."""
+
+ uri = 'job-binary-internals/%s' % job_binary_id
+ return self.delete(uri)
diff --git a/tempest/services/queuing/json/queuing_client.py b/tempest/services/queuing/json/queuing_client.py
index 4a0c495..e5978f5 100644
--- a/tempest/services/queuing/json/queuing_client.py
+++ b/tempest/services/queuing/json/queuing_client.py
@@ -15,6 +15,7 @@
import json
+from tempest.api_schema.queuing.v1 import queues as queues_schema
from tempest.common import rest_client
from tempest import config
@@ -33,6 +34,7 @@
uri = '{0}/queues'.format(self.uri_prefix)
resp, body = self.get(uri)
body = json.loads(body)
+ self.validate_response(queues_schema.list_queues, resp, body)
return resp, body
def create_queue(self, queue_name):
@@ -43,16 +45,32 @@
def get_queue(self, queue_name):
uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
resp, body = self.get(uri)
- body = json.loads(body)
return resp, body
def head_queue(self, queue_name):
uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
resp, body = self.head(uri)
- body = json.loads(body)
return resp, body
def delete_queue(self, queue_name):
uri = '{0}/queues/{1}'.format(self.uri_prefix, queue_name)
resp = self.delete(uri)
return resp
+
+ def get_queue_stats(self, queue_name):
+ uri = '{0}/queues/{1}/stats'.format(self.uri_prefix, queue_name)
+ resp, body = self.get(uri)
+ body = json.loads(body)
+ self.validate_response(queues_schema.queue_stats, resp, body)
+ return resp, body
+
+ def get_queue_metadata(self, queue_name):
+ uri = '{0}/queues/{1}/metadata'.format(self.uri_prefix, queue_name)
+ resp, body = self.get(uri)
+ body = json.loads(body)
+ return resp, body
+
+ def set_queue_metadata(self, queue_name, rbody):
+ uri = '{0}/queues/{1}/metadata'.format(self.uri_prefix, queue_name)
+ resp, body = self.put(uri, body=json.dumps(rbody))
+ return resp, body
diff --git a/tempest/services/volume/json/admin/volume_quotas_client.py b/tempest/services/volume/json/admin/volume_quotas_client.py
index ea9c92e..961c7da 100644
--- a/tempest/services/volume/json/admin/volume_quotas_client.py
+++ b/tempest/services/volume/json/admin/volume_quotas_client.py
@@ -77,3 +77,7 @@
post_body = jsonutils.dumps({'quota_set': post_body})
resp, body = self.put('os-quota-sets/%s' % tenant_id, post_body)
return resp, self._parse_resp(body)
+
+ def delete_quota_set(self, tenant_id):
+ """Delete the tenant's quota set."""
+ return self.delete('os-quota-sets/%s' % tenant_id)
diff --git a/tempest/services/volume/json/availability_zone_client.py b/tempest/services/volume/json/availability_zone_client.py
new file mode 100644
index 0000000..6839d3a
--- /dev/null
+++ b/tempest/services/volume/json/availability_zone_client.py
@@ -0,0 +1,34 @@
+# Copyright 2014 NEC Corporation.
+# 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.
+
+import json
+
+from tempest.common import rest_client
+from tempest import config
+
+CONF = config.CONF
+
+
+class VolumeAvailabilityZoneClientJSON(rest_client.RestClient):
+
+ def __init__(self, auth_provider):
+ super(VolumeAvailabilityZoneClientJSON, self).__init__(
+ auth_provider)
+ self.service = CONF.volume.catalog_type
+
+ def get_availability_zone_list(self):
+ resp, body = self.get('os-availability-zone')
+ body = json.loads(body)
+ return resp, body['availabilityZoneInfo']
diff --git a/tempest/services/volume/xml/admin/volume_quotas_client.py b/tempest/services/volume/xml/admin/volume_quotas_client.py
index dd1423f..a38410b 100644
--- a/tempest/services/volume/xml/admin/volume_quotas_client.py
+++ b/tempest/services/volume/xml/admin/volume_quotas_client.py
@@ -68,3 +68,7 @@
str(xml.Document(element)))
body = xml.xml_to_json(etree.fromstring(body))
return resp, self._format_quota(body)
+
+ def delete_quota_set(self, tenant_id):
+ """Delete the tenant's quota set."""
+ return self.delete('os-quota-sets/%s' % tenant_id)
diff --git a/tempest/services/volume/xml/availability_zone_client.py b/tempest/services/volume/xml/availability_zone_client.py
new file mode 100644
index 0000000..e4a004a
--- /dev/null
+++ b/tempest/services/volume/xml/availability_zone_client.py
@@ -0,0 +1,39 @@
+# Copyright 2014 NEC Corporation
+# 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.
+
+from lxml import etree
+
+from tempest.common import rest_client
+from tempest.common import xml_utils
+from tempest import config
+
+CONF = config.CONF
+
+
+class VolumeAvailabilityZoneClientXML(rest_client.RestClient):
+ TYPE = "xml"
+
+ def __init__(self, auth_provider):
+ super(VolumeAvailabilityZoneClientXML, self).__init__(
+ auth_provider)
+ self.service = CONF.volume.catalog_type
+
+ def _parse_array(self, node):
+ return [xml_utils.xml_to_json(x) for x in node]
+
+ def get_availability_zone_list(self):
+ resp, body = self.get('os-availability-zone')
+ availability_zone = self._parse_array(etree.fromstring(body))
+ return resp, availability_zone
diff --git a/tempest/thirdparty/boto/test_ec2_instance_run.py b/tempest/thirdparty/boto/test_ec2_instance_run.py
index 33b8d6e..b2eb18d 100644
--- a/tempest/thirdparty/boto/test_ec2_instance_run.py
+++ b/tempest/thirdparty/boto/test_ec2_instance_run.py
@@ -193,7 +193,6 @@
instance.terminate()
self.cancelResourceCleanUp(rcuk)
- @test.skip_because(bug="1098891")
@test.attr(type='smoke')
def test_run_terminate_instance(self):
# EC2 run, terminate immediately
@@ -211,7 +210,7 @@
pass
except exception.EC2ResponseError as exc:
if self.ec2_error_code.\
- client.InvalidInstanceID.NotFound.match(exc):
+ client.InvalidInstanceID.NotFound.match(exc) is None:
pass
else:
raise
diff --git a/test-requirements.txt b/test-requirements.txt
index 8d64167..942a7c3 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,7 +1,7 @@
hacking>=0.8.0,<0.9
# needed for doc build
docutils==0.9.1
-sphinx>=1.1.2,<1.2
+sphinx>=1.2.1,<1.3
python-subunit>=0.0.18
oslosphinx
mox>=0.5.3