Merge "Add Marconi Smoke Tests"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index ac96a4a..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
@@ -984,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/setup.cfg b/setup.cfg
index f4aa3e1..14e1913 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
name = tempest
-version = 2014.1
+version = 2
summary = OpenStack Integration Testing
description-file =
README.rst
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index 85ad29d..a077943 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -30,29 +30,31 @@
def test_security_groups_create_list_delete(self):
# Positive test:Should return the list of Security Groups
# Create 3 Security Groups
+ security_group_list = []
for i in range(3):
- resp, securitygroup = self.create_security_group()
+ resp, body = self.create_security_group()
self.assertEqual(200, resp.status)
+ security_group_list.append(body)
# Fetch all Security Groups and verify the list
# has all created Security Groups
resp, fetched_list = self.client.list_security_groups()
self.assertEqual(200, resp.status)
# Now check if all the created Security Groups are in fetched list
missing_sgs = \
- [sg for sg in self.security_groups if sg not in fetched_list]
+ [sg for sg in security_group_list if sg not in fetched_list]
self.assertFalse(missing_sgs,
"Failed to find Security Group %s in fetched "
"list" % ', '.join(m_group['name']
for m_group in missing_sgs))
# Delete all security groups
- for sg in self.security_groups:
+ for sg in security_group_list:
resp, _ = self.client.delete_security_group(sg['id'])
self.assertEqual(202, resp.status)
self.client.wait_for_resource_deletion(sg['id'])
# Now check if all the created Security Groups are deleted
resp, fetched_list = self.client.list_security_groups()
deleted_sgs = \
- [sg for sg in self.security_groups if sg in fetched_list]
+ [sg for sg in security_group_list if sg in fetched_list]
self.assertFalse(deleted_sgs,
"Failed to delete Security Group %s "
"list" % ', '.join(m_group['name']
@@ -78,6 +80,9 @@
self.assertEqual(securitygroup, fetched_group,
"The fetched Security Group is different "
"from the created Group")
+ resp, _ = self.client.delete_security_group(securitygroup['id'])
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_resource_deletion(securitygroup['id'])
@test.attr(type='smoke')
def test_server_security_groups(self):
diff --git a/tempest/api/identity/admin/v3/test_certificates.py b/tempest/api/identity/admin/v3/test_certificates.py
deleted file mode 100644
index a53ee0a..0000000
--- a/tempest/api/identity/admin/v3/test_certificates.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright 2014 OpenStack Foundation
-# 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.identity import base
-from tempest import test
-
-
-class CertificatesV3TestJSON(base.BaseIdentityV3AdminTest):
- _interface = 'json'
-
- def _verify_response(self, expected, actual):
- missing_tags = [t for t in expected if t not in actual]
- self.assertEqual(0, len(missing_tags),
- "Failed to fetch expected tag"
- "in the certificate: %s" % ','.join(missing_tags))
-
- @test.attr(type='smoke')
- def test_get_ca_certificate(self):
- # Verify ca certificate chain
- expected_tags = ['BEGIN CERTIFICATE', 'END CERTIFICATE']
- resp, certificate = self.client.get_ca_certificate()
- self.assertEqual(200, resp.status)
- self._verify_response(expected_tags, certificate)
-
- @test.attr(type='smoke')
- def test_get_certificates(self):
- # Verify signing certificates
- expected_tags = ['Certificate', 'BEGIN CERTIFICATE', 'END CERTIFICATE']
- resp, certificates = self.client.get_certificates()
- self.assertEqual(200, resp.status)
- self._verify_response(expected_tags, certificates)
diff --git a/tempest/api/network/test_allowed_address_pair.py b/tempest/api/network/test_allowed_address_pair.py
new file mode 100644
index 0000000..e0e26da
--- /dev/null
+++ b/tempest/api/network/test_allowed_address_pair.py
@@ -0,0 +1,83 @@
+# Copyright 2014 OpenStack Foundation
+# 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.network import base
+from tempest import test
+
+
+class AllowedAddressPairTestJSON(base.BaseNetworkTest):
+ _interface = 'json'
+
+ """
+ Tests the Neutron Allowed Address Pair API extension using the Tempest
+ ReST client. The following API operations are tested with this extension:
+
+ create port
+ list ports
+ update port
+ show port
+
+ v2.0 of the Neutron API is assumed. It is also assumed that the following
+ options are defined in the [network-feature-enabled] section of
+ etc/tempest.conf
+
+ api_extensions
+ """
+
+ @classmethod
+ @test.safe_setup
+ def setUpClass(cls):
+ super(AllowedAddressPairTestJSON, cls).setUpClass()
+ if not test.is_extension_enabled('allowed-address-pairs', 'network'):
+ msg = "Allowed Address Pairs extension not enabled."
+ raise cls.skipException(msg)
+ cls.network = cls.create_network()
+ cls.create_subnet(cls.network)
+ port = cls.create_port(cls.network)
+ cls.ip_address = port['fixed_ips'][0]['ip_address']
+ cls.mac_address = port['mac_address']
+
+ @test.attr(type='smoke')
+ def test_create_list_port_with_address_pair(self):
+ # Create port with allowed address pair attribute
+ allowed_address_pairs = [{'ip_address': self.ip_address,
+ 'mac_address': self.mac_address}]
+ resp, body = self.client.create_port(
+ network_id=self.network['id'],
+ allowed_address_pairs=allowed_address_pairs)
+ self.assertEqual('201', resp['status'])
+ port_id = body['port']['id']
+ self.addCleanup(self.client.delete_port, port_id)
+
+ # Confirm port was created with allowed address pair attribute
+ resp, body = self.client.list_ports()
+ self.assertEqual('200', resp['status'])
+ ports = body['ports']
+ port = [p for p in ports if p['id'] == port_id]
+ msg = 'Created port not found in list of ports returned by Neutron'
+ self.assertTrue(port, msg)
+ self._confirm_allowed_address_pair(port[0], self.ip_address)
+
+ def _confirm_allowed_address_pair(self, port, ip):
+ msg = 'Port allowed address pairs should not be empty'
+ self.assertTrue(port['allowed_address_pairs'], msg)
+ ip_address = port['allowed_address_pairs'][0]['ip_address']
+ mac_address = port['allowed_address_pairs'][0]['mac_address']
+ self.assertEqual(ip_address, ip)
+ self.assertEqual(mac_address, self.mac_address)
+
+
+class AllowedAddressPairTestXML(AllowedAddressPairTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index d0d25ec..2463654 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import netaddr
+
from tempest.api.network import base
from tempest.common.utils import data_utils
from tempest import config
@@ -192,6 +194,36 @@
self.assertEqual('200', resp['status'])
self.assertIsNone(floating_ip['floatingip']['port_id'])
+ @test.attr(type='smoke')
+ def test_create_update_floatingip_with_port_multiple_ip_address(self):
+ # Find out ips that can be used for tests
+ ips = list(netaddr.IPNetwork(self.subnet['cidr']))
+ list_ips = [str(ip) for ip in ips[-3:-1]]
+ fixed_ips = [{'ip_address': list_ips[0]}, {'ip_address': list_ips[1]}]
+ # Create port
+ resp, body = self.client.create_port(network_id=self.network['id'],
+ fixed_ips=fixed_ips)
+ self.assertEqual('201', resp['status'])
+ port = body['port']
+ self.addCleanup(self.client.delete_port, port['id'])
+ # Create floating ip
+ resp, body = self.client.create_floatingip(
+ floating_network_id=self.ext_net_id, port_id=port['id'],
+ fixed_ip_address=list_ips[0])
+ self.assertEqual('201', resp['status'])
+ floating_ip = body['floatingip']
+ self.addCleanup(self.client.delete_floatingip, floating_ip['id'])
+ self.assertIsNotNone(floating_ip['id'])
+ self.assertEqual(floating_ip['fixed_ip_address'], list_ips[0])
+ # Update floating ip
+ resp, body = self.client.update_floatingip(
+ floating_ip['id'], port_id=port['id'],
+ fixed_ip_address=list_ips[1])
+ self.assertEqual('200', resp['status'])
+ update_floating_ip = body['floatingip']
+ self.assertEqual(update_floating_ip['fixed_ip_address'],
+ list_ips[1])
+
class FloatingIPTestXML(FloatingIPTestJSON):
_interface = 'xml'
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
index d4bda19..446f4ab 100644
--- a/tempest/api/orchestration/base.py
+++ b/tempest/api/orchestration/base.py
@@ -42,12 +42,14 @@
cls.keypairs_client = cls.os.keypairs_client
cls.network_client = cls.os.network_client
cls.volumes_client = cls.os.volumes_client
+ cls.images_v2_client = cls.os.image_client_v2
cls.stacks = []
cls.keypairs = []
+ cls.images = []
@classmethod
def _get_default_network(cls):
- resp, networks = cls.network_client.list_networks()
+ __, networks = cls.network_client.list_networks()
for net in networks['networks']:
if net['name'] == CONF.compute.fixed_network_name:
return net
@@ -91,7 +93,7 @@
@classmethod
def _create_keypair(cls, name_start='keypair-heat-'):
kp_name = data_utils.rand_name(name_start)
- resp, body = cls.keypairs_client.create_keypair(kp_name)
+ __, body = cls.keypairs_client.create_keypair(kp_name)
cls.keypairs.append(kp_name)
return body
@@ -104,6 +106,25 @@
pass
@classmethod
+ def _create_image(cls, name_start='image-heat-', container_format='bare',
+ disk_format='iso'):
+ image_name = data_utils.rand_name(name_start)
+ __, body = cls.images_v2_client.create_image(image_name,
+ container_format,
+ disk_format)
+ image_id = body['id']
+ cls.images.append(image_id)
+ return body
+
+ @classmethod
+ def _clear_images(cls):
+ for image_id in cls.images:
+ try:
+ cls.images_v2_client.delete_image(image_id)
+ except exceptions.NotFound:
+ pass
+
+ @classmethod
def load_template(cls, name, ext='yaml'):
loc = ["stacks", "templates", "%s.%s" % (name, ext)]
fullpath = os.path.join(os.path.dirname(__file__), *loc)
@@ -116,6 +137,7 @@
def tearDownClass(cls):
cls._clear_stacks()
cls._clear_keypairs()
+ cls._clear_images()
super(BaseOrchestrationTest, cls).tearDownClass()
@staticmethod
diff --git a/tempest/api/orchestration/stacks/templates/non_empty_stack.yaml b/tempest/api/orchestration/stacks/templates/non_empty_stack.yaml
index 58a934e..8690941 100644
--- a/tempest/api/orchestration/stacks/templates/non_empty_stack.yaml
+++ b/tempest/api/orchestration/stacks/templates/non_empty_stack.yaml
@@ -5,6 +5,8 @@
trigger:
Type: String
Default: not_yet
+ image:
+ Type: String
Resources:
fluffy:
Type: AWS::AutoScaling::LaunchConfiguration
@@ -13,7 +15,7 @@
- Tom
- Stinky
Properties:
- ImageId: not_used
+ ImageId: {Ref: image}
InstanceType: not_used
UserData:
Fn::Replace:
diff --git a/tempest/api/orchestration/stacks/test_non_empty_stack.py b/tempest/api/orchestration/stacks/test_non_empty_stack.py
index 9cf59a5..585c90b 100644
--- a/tempest/api/orchestration/stacks/test_non_empty_stack.py
+++ b/tempest/api/orchestration/stacks/test_non_empty_stack.py
@@ -14,8 +14,10 @@
from tempest.api.orchestration import base
from tempest.common.utils import data_utils
+from tempest import config
from tempest import test
+CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -27,13 +29,15 @@
super(StacksTestJSON, cls).setUpClass()
cls.stack_name = data_utils.rand_name('heat')
template = cls.load_template('non_empty_stack')
-
+ image_id = (CONF.orchestration.image_ref or
+ cls._create_image()['id'])
# create the stack
cls.stack_identifier = cls.create_stack(
cls.stack_name,
template,
parameters={
- 'trigger': 'start'
+ 'trigger': 'start',
+ 'image': image_id
})
cls.stack_id = cls.stack_identifier.split('/')[1]
cls.resource_name = 'fluffy'
diff --git a/tempest/api/orchestration/stacks/test_server_cfn_init.py b/tempest/api/orchestration/stacks/test_server_cfn_init.py
deleted file mode 100644
index 4b845b1..0000000
--- a/tempest/api/orchestration/stacks/test_server_cfn_init.py
+++ /dev/null
@@ -1,121 +0,0 @@
-# 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
-import testtools
-
-from tempest.api.orchestration import base
-from tempest.common.utils import data_utils
-from tempest.common.utils.linux import remote_client
-from tempest import config
-from tempest import exceptions
-from tempest.openstack.common import log as logging
-from tempest import test
-
-CONF = config.CONF
-LOG = logging.getLogger(__name__)
-
-
-class ServerCfnInitTestJSON(base.BaseOrchestrationTest):
- existing_keypair = CONF.orchestration.keypair_name is not None
-
- @classmethod
- @test.safe_setup
- def setUpClass(cls):
- super(ServerCfnInitTestJSON, cls).setUpClass()
- if not CONF.orchestration.image_ref:
- raise cls.skipException("No image available to test")
- template = cls.load_template('cfn_init_signal')
- stack_name = data_utils.rand_name('heat')
- if CONF.orchestration.keypair_name:
- keypair_name = CONF.orchestration.keypair_name
- else:
- cls.keypair = cls._create_keypair()
- keypair_name = cls.keypair['name']
-
- # create the stack
- cls.stack_identifier = cls.create_stack(
- stack_name,
- template,
- parameters={
- 'key_name': keypair_name,
- 'flavor': CONF.orchestration.instance_type,
- 'image': CONF.orchestration.image_ref,
- 'network': cls._get_default_network()['id'],
- 'timeout': CONF.orchestration.build_timeout
- })
-
- @test.attr(type='slow')
- @testtools.skipIf(existing_keypair, 'Server ssh tests are disabled.')
- def test_can_log_into_created_server(self):
-
- sid = self.stack_identifier
- rid = 'SmokeServer'
-
- # wait for create to complete.
- self.client.wait_for_stack_status(sid, 'CREATE_COMPLETE')
-
- resp, body = self.client.get_resource(sid, rid)
- self.assertEqual('CREATE_COMPLETE', body['resource_status'])
-
- # fetch the IP address from servers client, since we can't get it
- # from the stack until stack create is complete
- resp, server = self.servers_client.get_server(
- body['physical_resource_id'])
-
- # Check that the user can authenticate with the generated password
- linux_client = remote_client.RemoteClient(server, 'ec2-user',
- pkey=self.keypair[
- 'private_key'])
- linux_client.validate_authentication()
-
- @test.attr(type='slow')
- def test_all_resources_created(self):
- sid = self.stack_identifier
- self.client.wait_for_resource_status(
- sid, 'WaitHandle', 'CREATE_COMPLETE')
- self.client.wait_for_resource_status(
- sid, 'SmokeSecurityGroup', 'CREATE_COMPLETE')
- self.client.wait_for_resource_status(
- sid, 'SmokeKeys', 'CREATE_COMPLETE')
- self.client.wait_for_resource_status(
- sid, 'CfnUser', 'CREATE_COMPLETE')
- self.client.wait_for_resource_status(
- sid, 'SmokeServer', 'CREATE_COMPLETE')
- try:
- self.client.wait_for_resource_status(
- sid, 'WaitCondition', 'CREATE_COMPLETE')
- except (exceptions.StackResourceBuildErrorException,
- exceptions.TimeoutException) as e:
- # attempt to log the server console to help with debugging
- # the cause of the server not signalling the waitcondition
- # to heat.
- resp, body = self.client.get_resource(sid, 'SmokeServer')
- server_id = body['physical_resource_id']
- LOG.debug('Console output for %s', server_id)
- resp, output = self.servers_client.get_console_output(
- server_id, None)
- LOG.debug(output)
- raise e
-
- # wait for create to complete.
- self.client.wait_for_stack_status(sid, 'CREATE_COMPLETE')
-
- # This is an assert of great significance, as it means the following
- # has happened:
- # - cfn-init read the provided metadata and wrote out a file
- # - a user was created and credentials written to the server
- # - a cfn-signal was built which was signed with provided credentials
- # - the wait condition was fulfilled and the stack has changed state
- wait_status = json.loads(
- self.get_stack_output(sid, 'WaitConditionStatus'))
- self.assertEqual('smoke test complete', wait_status['00000'])
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 e11f047..fb39087 100644
--- a/tempest/api_schema/compute/servers.py
+++ b/tempest/api_schema/compute/servers.py
@@ -139,3 +139,11 @@
'required': ['servers']
}
}
+
+server_actions_common_schema = {
+ 'status_code': [202]
+}
+
+server_actions_delete_password = {
+ 'status_code': [204]
+}
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/compute/v2/servers.py b/tempest/api_schema/compute/v2/servers.py
index e90f436..3da3276 100644
--- a/tempest/api_schema/compute/v2/servers.py
+++ b/tempest/api_schema/compute/v2/servers.py
@@ -128,3 +128,6 @@
'status_code': [200],
'response_body': parameter_types.addresses
}
+
+server_actions_confirm_resize = copy.deepcopy(
+ servers.server_actions_delete_password)
diff --git a/tempest/api_schema/compute/v3/servers.py b/tempest/api_schema/compute/v3/servers.py
index 956b5ad..8f694f7 100644
--- a/tempest/api_schema/compute/v3/servers.py
+++ b/tempest/api_schema/compute/v3/servers.py
@@ -81,3 +81,6 @@
'status_code': [200],
'response_body': addresses_v3
}
+
+server_actions_change_password = copy.deepcopy(
+ servers.server_actions_delete_password)
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 c732fe0..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."),
]
@@ -447,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/scenario/manager.py b/tempest/scenario/manager.py
index e057c74..f83a4b8 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -16,9 +16,12 @@
import logging
import os
+import re
import six
import subprocess
+import time
+from heatclient import exc as heat_exceptions
import netaddr
from neutronclient.common import exceptions as exc
from novaclient import exceptions as nova_exceptions
@@ -32,6 +35,7 @@
from tempest import config
from tempest import exceptions
from tempest.openstack.common import log
+from tempest.openstack.common import timeutils
import tempest.test
CONF = config.CONF
@@ -1069,3 +1073,98 @@
for net in networks['networks']:
if net['name'] == CONF.compute.fixed_network_name:
return net
+
+ @staticmethod
+ def _stack_output(stack, output_key):
+ """Return a stack output value for a given key."""
+ return next((o['output_value'] for o in stack.outputs
+ if o['output_key'] == output_key), None)
+
+ def _ping_ip_address(self, ip_address, should_succeed=True):
+ cmd = ['ping', '-c1', '-w1', ip_address]
+
+ def ping():
+ proc = subprocess.Popen(cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ proc.wait()
+ return (proc.returncode == 0) == should_succeed
+
+ return tempest.test.call_until_true(
+ ping, CONF.orchestration.build_timeout, 1)
+
+ def _wait_for_resource_status(self, stack_identifier, resource_name,
+ status, failure_pattern='^.*_FAILED$'):
+ """Waits for a Resource to reach a given status."""
+ fail_regexp = re.compile(failure_pattern)
+ build_timeout = CONF.orchestration.build_timeout
+ build_interval = CONF.orchestration.build_interval
+
+ start = timeutils.utcnow()
+ while timeutils.delta_seconds(start,
+ timeutils.utcnow()) < build_timeout:
+ try:
+ res = self.client.resources.get(
+ stack_identifier, resource_name)
+ except heat_exceptions.HTTPNotFound:
+ # ignore this, as the resource may not have
+ # been created yet
+ pass
+ else:
+ if res.resource_status == status:
+ return
+ if fail_regexp.search(res.resource_status):
+ raise exceptions.StackResourceBuildErrorException(
+ resource_name=res.resource_name,
+ stack_identifier=stack_identifier,
+ resource_status=res.resource_status,
+ resource_status_reason=res.resource_status_reason)
+ time.sleep(build_interval)
+
+ message = ('Resource %s failed to reach %s status within '
+ 'the required time (%s s).' %
+ (res.resource_name, status, build_timeout))
+ raise exceptions.TimeoutException(message)
+
+ def _wait_for_stack_status(self, stack_identifier, status,
+ failure_pattern='^.*_FAILED$'):
+ """
+ Waits for a Stack to reach a given status.
+
+ Note this compares the full $action_$status, e.g
+ CREATE_COMPLETE, not just COMPLETE which is exposed
+ via the status property of Stack in heatclient
+ """
+ fail_regexp = re.compile(failure_pattern)
+ build_timeout = CONF.orchestration.build_timeout
+ build_interval = CONF.orchestration.build_interval
+
+ start = timeutils.utcnow()
+ while timeutils.delta_seconds(start,
+ timeutils.utcnow()) < build_timeout:
+ try:
+ stack = self.client.stacks.get(stack_identifier)
+ except heat_exceptions.HTTPNotFound:
+ # ignore this, as the stackource may not have
+ # been created yet
+ pass
+ else:
+ if stack.stack_status == status:
+ return
+ if fail_regexp.search(stack.stack_status):
+ raise exceptions.StackBuildErrorException(
+ stack_identifier=stack_identifier,
+ stack_status=stack.stack_status,
+ stack_status_reason=stack.stack_status_reason)
+ time.sleep(build_interval)
+
+ message = ('Stack %s failed to reach %s status within '
+ 'the required time (%s s).' %
+ (stack.stack_name, status, build_timeout))
+ raise exceptions.TimeoutException(message)
+
+ def _stack_delete(self, stack_identifier):
+ try:
+ self.client.stacks.delete(stack_identifier)
+ except heat_exceptions.HTTPNotFound:
+ pass
diff --git a/tempest/api/orchestration/stacks/templates/cfn_init_signal.yaml b/tempest/scenario/orchestration/cfn_init_signal.yaml
similarity index 97%
rename from tempest/api/orchestration/stacks/templates/cfn_init_signal.yaml
rename to tempest/scenario/orchestration/cfn_init_signal.yaml
index fa5345e..c95aabf 100644
--- a/tempest/api/orchestration/stacks/templates/cfn_init_signal.yaml
+++ b/tempest/scenario/orchestration/cfn_init_signal.yaml
@@ -62,7 +62,7 @@
#!/bin/bash -v
/opt/aws/bin/cfn-init
/opt/aws/bin/cfn-signal -e 0 --data "`cat /tmp/smoke-status`" \
- "WaitHandle"
+ --id smoke_status "WaitHandle"
WaitHandle:
Type: AWS::CloudFormation::WaitConditionHandle
WaitCondition:
diff --git a/tempest/scenario/orchestration/test_server_cfn_init.py b/tempest/scenario/orchestration/test_server_cfn_init.py
new file mode 100644
index 0000000..36e6126
--- /dev/null
+++ b/tempest/scenario/orchestration/test_server_cfn_init.py
@@ -0,0 +1,130 @@
+# 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 import config
+from tempest import exceptions
+from tempest.openstack.common import log as logging
+from tempest.scenario import manager
+from tempest import test
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+class CfnInitScenarioTest(manager.OrchestrationScenarioTest):
+
+ def setUp(self):
+ super(CfnInitScenarioTest, self).setUp()
+ if not CONF.orchestration.image_ref:
+ raise self.skipException("No image available to test")
+ self.client = self.orchestration_client
+ self.template_name = 'cfn_init_signal.yaml'
+
+ def assign_keypair(self):
+ self.stack_name = self._stack_rand_name()
+ if CONF.orchestration.keypair_name:
+ self.keypair = None
+ self.keypair_name = CONF.orchestration.keypair_name
+ else:
+ self.keypair = self.create_keypair()
+ self.keypair_name = self.keypair.id
+
+ def launch_stack(self):
+ net = self._get_default_network()
+ self.parameters = {
+ 'key_name': self.keypair_name,
+ 'flavor': CONF.orchestration.instance_type,
+ 'image': CONF.orchestration.image_ref,
+ 'timeout': CONF.orchestration.build_timeout,
+ 'network': net['id'],
+ }
+
+ # create the stack
+ self.template = self._load_template(__file__, self.template_name)
+ self.client.stacks.create(
+ stack_name=self.stack_name,
+ template=self.template,
+ parameters=self.parameters)
+
+ self.stack = self.client.stacks.get(self.stack_name)
+ self.stack_identifier = '%s/%s' % (self.stack_name, self.stack.id)
+ self.addCleanup(self._stack_delete, self.stack_identifier)
+
+ def check_stack(self):
+ sid = self.stack_identifier
+ self._wait_for_resource_status(
+ sid, 'WaitHandle', 'CREATE_COMPLETE')
+ self._wait_for_resource_status(
+ sid, 'SmokeSecurityGroup', 'CREATE_COMPLETE')
+ self._wait_for_resource_status(
+ sid, 'SmokeKeys', 'CREATE_COMPLETE')
+ self._wait_for_resource_status(
+ sid, 'CfnUser', 'CREATE_COMPLETE')
+ self._wait_for_resource_status(
+ sid, 'SmokeServer', 'CREATE_COMPLETE')
+
+ server_resource = self.client.resources.get(sid, 'SmokeServer')
+ server_id = server_resource.physical_resource_id
+ server = self.compute_client.servers.get(server_id)
+ server_ip = server.networks[CONF.compute.network_for_ssh][0]
+
+ if not self._ping_ip_address(server_ip):
+ self._log_console_output(servers=[server])
+ self.fail(
+ "Timed out waiting for %s to become reachable" % server_ip)
+
+ try:
+ self._wait_for_resource_status(
+ sid, 'WaitCondition', 'CREATE_COMPLETE')
+ except (exceptions.StackResourceBuildErrorException,
+ exceptions.TimeoutException) as e:
+ raise e
+ finally:
+ # attempt to log the server console regardless of WaitCondition
+ # going to complete. This allows successful and failed cloud-init
+ # logs to be compared
+ self._log_console_output(servers=[server])
+
+ self._wait_for_stack_status(sid, 'CREATE_COMPLETE')
+
+ stack = self.client.stacks.get(sid)
+
+ # This is an assert of great significance, as it means the following
+ # has happened:
+ # - cfn-init read the provided metadata and wrote out a file
+ # - a user was created and credentials written to the server
+ # - a cfn-signal was built which was signed with provided credentials
+ # - the wait condition was fulfilled and the stack has changed state
+ wait_status = json.loads(
+ self._stack_output(stack, 'WaitConditionStatus'))
+ self.assertEqual('smoke test complete', wait_status['smoke_status'])
+
+ if self.keypair:
+ # Check that the user can authenticate with the generated
+ # keypair
+ try:
+ linux_client = self.get_remote_client(
+ server_ip, username='ec2-user')
+ linux_client.validate_authentication()
+ except (exceptions.ServerUnreachable,
+ exceptions.SSHTimeout) as e:
+ self._log_console_output(servers=[server])
+ raise e
+
+ @test.attr(type='slow')
+ @test.services('orchestration', 'compute')
+ def test_server_cfn_init(self):
+ self.assign_keypair()
+ self.launch_stack()
+ self.check_stack()
diff --git a/tempest/scenario/test_large_ops.py b/tempest/scenario/test_large_ops.py
index 0210c56..ed5743c 100644
--- a/tempest/scenario/test_large_ops.py
+++ b/tempest/scenario/test_large_ops.py
@@ -63,11 +63,20 @@
self.set_resource(server.name, server)
self._wait_for_server_status('ACTIVE')
- @test.services('compute', 'image')
- def test_large_ops_scenario(self):
+ def _large_ops_scenario(self):
if CONF.scenario.large_ops_number < 1:
return
self.glance_image_create()
self.nova_boot()
- self.nova_boot()
- self.nova_boot()
+
+ @test.services('compute', 'image')
+ def test_large_ops_scenario_1(self):
+ self._large_ops_scenario()
+
+ @test.services('compute', 'image')
+ def test_large_ops_scenario_2(self):
+ self._large_ops_scenario()
+
+ @test.services('compute', 'image')
+ def test_large_ops_scenario_3(self):
+ self._large_ops_scenario()
diff --git a/tempest/services/compute/json/interfaces_client.py b/tempest/services/compute/json/interfaces_client.py
index 8d51123..cdac8b7 100644
--- a/tempest/services/compute/json/interfaces_client.py
+++ b/tempest/services/compute/json/interfaces_client.py
@@ -17,6 +17,7 @@
import time
from tempest.api_schema.compute import interfaces as common_schema
+from tempest.api_schema.compute import servers as servers_schema
from tempest.api_schema.compute.v2 import interfaces as schema
from tempest.common import rest_client
from tempest import config
@@ -94,6 +95,8 @@
})
resp, body = self.post('servers/%s/action' % str(server_id),
post_body)
+ self.validate_response(servers_schema.server_actions_common_schema,
+ resp, body)
return resp, body
def remove_fixed_ip(self, server_id, ip_address):
@@ -105,4 +108,6 @@
})
resp, body = self.post('servers/%s/action' % str(server_id),
post_body)
+ self.validate_response(servers_schema.server_actions_common_schema,
+ resp, body)
return resp, body
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 5204cd0..9c7eec6 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -204,7 +204,7 @@
return resp, body
def action(self, server_id, action_name, response_key,
- schema=None, **kwargs):
+ schema=common_schema.server_actions_common_schema, **kwargs):
post_body = json.dumps({action_name: kwargs})
resp, body = self.post('servers/%s/action' % str(server_id),
post_body)
@@ -218,6 +218,8 @@
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
def create_backup(self, server_id, backup_type, rotation, name):
@@ -245,8 +247,11 @@
Note that this does not actually change the instance server
password.
"""
- return self.delete("servers/%s/os-server-password" %
- str(server_id))
+ resp, body = self.delete("servers/%s/os-server-password" %
+ str(server_id))
+ self.validate_response(common_schema.server_actions_delete_password,
+ resp, body)
+ return resp, body
def reboot(self, server_id, reboot_type):
"""Reboots a server."""
@@ -258,7 +263,7 @@
if 'disk_config' in kwargs:
kwargs['OS-DCF:diskConfig'] = 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."""
@@ -270,7 +275,9 @@
def confirm_resize(self, server_id, **kwargs):
"""Confirms the flavor change for a server."""
- return self.action(server_id, 'confirmResize', None, **kwargs)
+ return self.action(server_id, 'confirmResize',
+ None, schema.server_actions_confirm_resize,
+ **kwargs)
def revert_resize(self, server_id, **kwargs):
"""Reverts a server back to its original flavor."""
@@ -370,6 +377,8 @@
req_body = json.dumps({'os-migrateLive': migrate_params})
resp, body = self.post("servers/%s/action" % str(server_id), req_body)
+ self.validate_response(common_schema.server_actions_common_schema,
+ resp, body)
return resp, body
def migrate_server(self, server_id, **kwargs):
@@ -418,7 +427,7 @@
def get_console_output(self, server_id, length):
return self.action(server_id, 'os-getConsoleOutput', 'output',
- length=length)
+ None, length=length)
def list_virtual_interfaces(self, server_id):
"""
@@ -432,7 +441,7 @@
def rescue_server(self, server_id, **kwargs):
"""Rescue the provided server."""
- return self.action(server_id, 'rescue', None, **kwargs)
+ return self.action(server_id, 'rescue', 'adminPass', None, **kwargs)
def unrescue_server(self, server_id):
"""Unrescue the provided server."""
diff --git a/tempest/services/compute/v3/json/interfaces_client.py b/tempest/services/compute/v3/json/interfaces_client.py
index 77b3179..e66ccaa 100644
--- a/tempest/services/compute/v3/json/interfaces_client.py
+++ b/tempest/services/compute/v3/json/interfaces_client.py
@@ -17,6 +17,7 @@
import time
from tempest.api_schema.compute import interfaces as common_schema
+from tempest.api_schema.compute import servers as servers_schema
from tempest.api_schema.compute.v3 import interfaces as schema
from tempest.common import rest_client
from tempest import config
@@ -95,6 +96,8 @@
})
resp, body = self.post('servers/%s/action' % str(server_id),
post_body)
+ self.validate_response(servers_schema.server_actions_common_schema,
+ resp, body)
return resp, body
def remove_fixed_ip(self, server_id, ip_address):
@@ -106,4 +109,6 @@
})
resp, body = self.post('servers/%s/action' % str(server_id),
post_body)
+ self.validate_response(servers_schema.server_actions_common_schema,
+ resp, body)
return resp, body
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index 1990d39..caa0a1d 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -203,12 +203,15 @@
self.validate_response(schema.list_addresses_by_network, resp, body)
return resp, body
- def action(self, server_id, action_name, response_key, **kwargs):
+ def action(self, server_id, action_name, response_key,
+ schema=common_schema.server_actions_common_schema, **kwargs):
post_body = json.dumps({action_name: kwargs})
resp, body = self.post('servers/%s/action' % str(server_id),
post_body)
if response_key is not None:
body = json.loads(body)[response_key]
+ else:
+ self.validate_response(schema, resp, body)
return resp, body
def create_backup(self, server_id, backup_type, rotation, name):
@@ -220,7 +223,8 @@
def change_password(self, server_id, admin_password):
"""Changes the root password for the server."""
- return self.action(server_id, 'change_password', None,
+ return self.action(server_id, 'change_password',
+ None, schema.server_actions_change_password,
admin_password=admin_password)
def get_password(self, server_id):
@@ -236,8 +240,11 @@
Note that this does not actually change the instance server
password.
"""
- return self.delete("servers/%s/os-server-password" %
- str(server_id))
+ resp, body = self.delete("servers/%s/os-server-password" %
+ str(server_id))
+ self.validate_response(common_schema.server_actions_delete_password,
+ resp, body)
+ return resp, body
def reboot(self, server_id, reboot_type):
"""Reboots a server."""
@@ -364,6 +371,8 @@
resp, body = self.post("servers/%s/action" % str(server_id),
req_body)
+ self.validate_response(common_schema.server_actions_common_schema,
+ resp, body)
return resp, body
def migrate_server(self, server_id, **kwargs):
@@ -416,7 +425,7 @@
def rescue_server(self, server_id, **kwargs):
"""Rescue the provided server."""
- return self.action(server_id, 'rescue', None, **kwargs)
+ return self.action(server_id, 'rescue', 'admin_password', **kwargs)
def unrescue_server(self, server_id):
"""Unrescue the provided server."""
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index 73e52a0..6829333 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -450,16 +450,6 @@
% (trust_id, role_id))
return resp, body
- def get_ca_certificate(self):
- """GET ca certificate chain."""
- resp, body = self.get("OS-SIMPLE-CERT/ca")
- return resp, body
-
- def get_certificates(self):
- """GET signing certificates used to sign tokens."""
- resp, body = self.get("OS-SIMPLE-CERT/certificates")
- return resp, body
-
class V3TokenClientJSON(rest_client.RestClient):
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index a9d4880..a7a6b2c 100644
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -24,7 +24,7 @@
# list of plurals used for xml serialization
PLURALS = ['dns_nameservers', 'host_routes', 'allocation_pools',
'fixed_ips', 'extensions', 'extra_dhcp_opts', 'pools',
- 'health_monitors', 'vips', 'members']
+ 'health_monitors', 'vips', 'members', 'allowed_address_pairs']
def get_rest_client(self, auth_provider):
rc = rest_client.RestClient(auth_provider)
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/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