Merge "Missing image-del func in test_create_delete_image"
diff --git a/cli/__init__.py b/cli/__init__.py
index 7a92260..a3038d2 100644
--- a/cli/__init__.py
+++ b/cli/__init__.py
@@ -87,6 +87,15 @@
         flags = creds + ' ' + flags
         return self.cmd(cmd, action, flags, params, fail_ok)
 
+    def check_output(self, cmd, **kwargs):
+        # substitutes subprocess.check_output which is not in python2.6
+        kwargs['stdout'] = subprocess.PIPE
+        proc = subprocess.Popen(cmd, **kwargs)
+        output = proc.communicate()[0]
+        if proc.returncode != 0:
+            raise CommandFailed(proc.returncode, cmd, output)
+        return output
+
     def cmd(self, cmd, action, flags='', params='', fail_ok=False,
             merge_stderr=False):
         """Executes specified command for the given action."""
@@ -96,10 +105,10 @@
         cmd = shlex.split(cmd)
         try:
             if merge_stderr:
-                result = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+                result = self.check_output(cmd, stderr=subprocess.STDOUT)
             else:
-                devnull = open('/dev/null', 'w')
-                result = subprocess.check_output(cmd, stderr=devnull)
+                with open('/dev/null', 'w') as devnull:
+                    result = self.check_output(cmd, stderr=devnull)
         except subprocess.CalledProcessError, e:
             LOG.error("command output:\n%s" % e.output)
             raise
@@ -110,3 +119,10 @@
         for item in items:
             for field in field_names:
                 self.assertIn(field, item)
+
+
+class CommandFailed(subprocess.CalledProcessError):
+    # adds output attribute for python2.6
+    def __init__(self, returncode, cmd, output):
+        super(CommandFailed, self).__init__(returncode, cmd)
+        self.output = output
diff --git a/tempest/clients.py b/tempest/clients.py
index 8f8d5e6..732a982 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -22,6 +22,8 @@
 from tempest.services import botoclients
 from tempest.services.compute.json.aggregates_client import \
     AggregatesClientJSON
+from tempest.services.compute.json.availability_zone_client import \
+    AvailabilityZoneClientJSON
 from tempest.services.compute.json.extensions_client import \
     ExtensionsClientJSON
 from tempest.services.compute.json.fixed_ips_client import FixedIPsClientJSON
@@ -40,6 +42,8 @@
 from tempest.services.compute.json.servers_client import ServersClientJSON
 from tempest.services.compute.json.volumes_extensions_client import \
     VolumesExtensionsClientJSON
+from tempest.services.compute.xml.availability_zone_client import \
+    AvailabilityZoneClientXML
 from tempest.services.compute.xml.extensions_client import ExtensionsClientXML
 from tempest.services.compute.xml.fixed_ips_client import FixedIPsClientXML
 from tempest.services.compute.xml.flavors_client import FlavorsClientXML
@@ -174,6 +178,11 @@
     "xml": FixedIPsClientXML
 }
 
+AVAILABILITY_ZONE_CLIENT = {
+    "json": AvailabilityZoneClientJSON,
+    "xml": AvailabilityZoneClientXML,
+}
+
 
 class Manager(object):
 
@@ -238,6 +247,8 @@
             self.interfaces_client = INTERFACES_CLIENT[interface](*client_args)
             self.endpoints_client = ENDPOINT_CLIENT[interface](*client_args)
             self.fixed_ips_client = FIXED_IPS_CLIENT[interface](*client_args)
+            self.availability_zone_client = \
+                AVAILABILITY_ZONE_CLIENT[interface](*client_args)
         except KeyError:
             msg = "Unsupported interface type `%s'" % interface
             raise exceptions.InvalidConfiguration(msg)
diff --git a/tempest/services/compute/json/availability_zone_client.py b/tempest/services/compute/json/availability_zone_client.py
new file mode 100644
index 0000000..b11871b
--- /dev/null
+++ b/tempest/services/compute/json/availability_zone_client.py
@@ -0,0 +1,39 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 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.rest_client import RestClient
+
+
+class AvailabilityZoneClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(AvailabilityZoneClientJSON, self).__init__(config, username,
+                                                         password, auth_url,
+                                                         tenant_name)
+        self.service = self.config.compute.catalog_type
+
+    def get_availability_zone_list(self):
+        resp, body = self.get('os-availability-zone')
+        body = json.loads(body)
+        return resp, body['availabilityZoneInfo']
+
+    def get_availability_zone_list_detail(self):
+        resp, body = self.get('os-availability-zone/detail')
+        body = json.loads(body)
+        return resp, body['availabilityZoneInfo']
diff --git a/tempest/services/compute/xml/availability_zone_client.py b/tempest/services/compute/xml/availability_zone_client.py
new file mode 100644
index 0000000..ae93774
--- /dev/null
+++ b/tempest/services/compute/xml/availability_zone_client.py
@@ -0,0 +1,43 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 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.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class AvailabilityZoneClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(AvailabilityZoneClientXML, self).__init__(config, username,
+                                                        password, auth_url,
+                                                        tenant_name)
+        self.service = self.config.compute.catalog_type
+
+    def _parse_array(self, node):
+        return [xml_to_json(x) for x in node]
+
+    def get_availability_zone_list(self):
+        resp, body = self.get('os-availability-zone', self.headers)
+        availability_zone = self._parse_array(etree.fromstring(body))
+        return resp, availability_zone
+
+    def get_availability_zone_list_detail(self):
+        resp, body = self.get('os-availability-zone/detail', self.headers)
+        availability_zone = self._parse_array(etree.fromstring(body))
+        return resp, availability_zone
diff --git a/tempest/test.py b/tempest/test.py
index ccb2251..b0038e0 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -56,6 +56,11 @@
         #NOTE(afazekas): inspection workaround
         BaseTestCase.config = config.TempestConfig()
 
+    @classmethod
+    def setUpClass(cls):
+        if hasattr(super(BaseTestCase, cls), 'setUpClass'):
+            super(BaseTestCase, cls).setUpClass()
+
 
 class TestCase(BaseTestCase):
     """Base test case class for all Tempest tests
diff --git a/tempest/tests/compute/admin/test_availability_zone.py b/tempest/tests/compute/admin/test_availability_zone.py
new file mode 100644
index 0000000..98ad49c
--- /dev/null
+++ b/tempest/tests/compute/admin/test_availability_zone.py
@@ -0,0 +1,69 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 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 import exceptions
+from tempest.test import attr
+from tempest.tests.compute import base
+
+
+class AvailabilityZoneAdminTestJSON(base.BaseComputeAdminTest):
+
+    """
+    Tests Availability Zone API List that require admin privileges
+    """
+
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(AvailabilityZoneAdminTestJSON, cls).setUpClass()
+        cls.client = cls.os_adm.availability_zone_client
+        cls.non_adm_client = cls.availability_zone_client
+
+    @attr('positive')
+    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)
+
+    @attr('positive')
+    def test_get_availability_zone_list_detail(self):
+        # List of availability zones and available services
+        resp, availability_zone = \
+            self.client.get_availability_zone_list_detail()
+        self.assertEqual(200, resp.status)
+        self.assertTrue(len(availability_zone) > 0)
+
+    @attr('positive')
+    def test_get_availability_zone_list_with_non_admin_user(self):
+        # List of availability zone with non admin user
+        resp, availability_zone = \
+            self.non_adm_client.get_availability_zone_list()
+        self.assertEqual(200, resp.status)
+        self.assertTrue(len(availability_zone) > 0)
+
+    @attr('negative')
+    def test_get_availability_zone_list_detail_with_non_admin_user(self):
+        # List of availability zones and available services with non admin user
+        self.assertRaises(
+            exceptions.Unauthorized,
+            self.non_adm_client.get_availability_zone_list_detail)
+
+
+class AvailabilityZoneAdminTestXML(AvailabilityZoneAdminTestJSON):
+    _interface = 'xml'
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index 7716922..221cfb6 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -61,6 +61,7 @@
         cls.volumes_client = os.volumes_client
         cls.interfaces_client = os.interfaces_client
         cls.fixed_ips_client = os.fixed_ips_client
+        cls.availability_zone_client = os.availability_zone_client
         cls.build_interval = cls.config.compute.build_interval
         cls.build_timeout = cls.config.compute.build_timeout
         cls.ssh_user = cls.config.compute.ssh_user
diff --git a/tempest/tests/compute/images/test_images_oneserver.py b/tempest/tests/compute/images/test_images_oneserver.py
index bcd09f6..dfc16f4 100644
--- a/tempest/tests/compute/images/test_images_oneserver.py
+++ b/tempest/tests/compute/images/test_images_oneserver.py
@@ -72,7 +72,6 @@
                           snapshot_name)
 
     @attr(type='negative')
-    @testtools.skip("Until Bug #1005423 is fixed")
     def test_create_image_specify_invalid_metadata(self):
         # Return an error when creating image with invalid metadata
         snapshot_name = rand_name('test-snap-')
@@ -81,12 +80,11 @@
                           self.server['id'], snapshot_name, meta)
 
     @attr(type='negative')
-    @testtools.skip("Until Bug #1005423 is fixed")
     def test_create_image_specify_metadata_over_limits(self):
         # Return an error when creating image with meta data over 256 chars
         snapshot_name = rand_name('test-snap-')
         meta = {'a' * 260: 'b' * 260}
-        self.assertRaises(exceptions.OverLimit, self.client.create_image,
+        self.assertRaises(exceptions.BadRequest, self.client.create_image,
                           self.server['id'], snapshot_name, meta)
 
     @attr(type='negative')
diff --git a/tempest/tests/compute/limits/test_absolute_limits.py b/tempest/tests/compute/limits/test_absolute_limits.py
index 2b31680..6933fd7 100644
--- a/tempest/tests/compute/limits/test_absolute_limits.py
+++ b/tempest/tests/compute/limits/test_absolute_limits.py
@@ -15,6 +15,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest import exceptions
+from tempest.test import attr
 from tempest.tests.compute import base
 
 
@@ -25,6 +27,7 @@
     def setUpClass(cls):
         super(AbsoluteLimitsTestJSON, cls).setUpClass()
         cls.client = cls.limits_client
+        cls.server_client = cls.servers_client
 
     def test_absLimits_get(self):
         # To check if all limits are present in the response
@@ -45,6 +48,24 @@
                          "Failed to find element %s in absolute limits list"
                          % ', '.join(ele for ele in missing_elements))
 
+    @attr(type='negative')
+    def test_max_image_meta_exceed_limit(self):
+        #We should not create vm with image meta over maxImageMeta limit
+        # Get max limit value
+        max_meta = self.client.get_specific_absolute_limit('maxImageMeta')
+
+        #Create server should fail, since we are passing > metadata Limit!
+        max_meta_data = int(max_meta) + 1
+
+        meta_data = {}
+        for xx in range(max_meta_data):
+            meta_data[str(xx)] = str(xx)
+
+        self.assertRaises(exceptions.OverLimit,
+                          self.server_client.create_server,
+                          name='test', meta=meta_data, flavor_ref='84',
+                          image_ref='9e6a2e3b-1601-42a5-985f-c3a2f93a5ec3')
+
 
 class AbsoluteLimitsTestXML(AbsoluteLimitsTestJSON):
     _interface = 'xml'
diff --git a/tempest/tests/compute/servers/test_multiple_create.py b/tempest/tests/compute/servers/test_multiple_create.py
index ad5d604..47a38b1 100644
--- a/tempest/tests/compute/servers/test_multiple_create.py
+++ b/tempest/tests/compute/servers/test_multiple_create.py
@@ -48,7 +48,7 @@
         created_servers = self._get_created_servers(kwargs['name'])
         # NOTE(maurosr): append it to cls.servers list from base.BaseCompute
         # class.
-        self.servers.append(created_servers)
+        self.servers.extend(created_servers)
         # NOTE(maurosr): get a server list, check status of the ones with names
         # that match and wait for them become active. At a first look, since
         # they are building in parallel, wait inside the for doesn't seem be
diff --git a/tempest/tests/compute/servers/test_server_rescue.py b/tempest/tests/compute/servers/test_server_rescue.py
index 7015d60..04c5b27 100644
--- a/tempest/tests/compute/servers/test_server_rescue.py
+++ b/tempest/tests/compute/servers/test_server_rescue.py
@@ -15,8 +15,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testtools
-
 from tempest.common.utils.data_utils import rand_name
 import tempest.config
 from tempest import exceptions
@@ -192,8 +190,12 @@
         self.assertEqual(202, resp.status)
 
     @attr(type='positive')
-    @testtools.skip("Skipped until Bug #1126257 is resolved")
     def test_rescued_vm_add_remove_security_group(self):
+        # Rescue the server
+        self.servers_client.rescue_server(
+            self.server_id, self.password)
+        self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
+
         #Add Security group
         resp, body = self.servers_client.add_security_group(self.server_id,
                                                             self.sg_name)
@@ -201,7 +203,7 @@
 
         #Delete Security group
         resp, body = self.servers_client.remove_security_group(self.server_id,
-                                                               self.sg_id)
+                                                               self.sg_name)
         self.assertEqual(202, resp.status)
 
         # Unrescue the server