Merge "Remove created routers as part of test cleanup"
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index abd36a6..ac43869 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -339,6 +339,7 @@
         cls.hosts_client = cls.os.hosts_v3_client
         cls.quotas_client = cls.os.quotas_v3_client
         cls.version_client = cls.os.version_v3_client
+        cls.migrations_client = cls.os.migrations_v3_client
 
     @classmethod
     def create_image_from_server(cls, server_id, **kwargs):
@@ -412,3 +413,4 @@
         cls.hosts_admin_client = cls.os_adm.hosts_v3_client
         cls.quotas_admin_client = cls.os_adm.quotas_v3_client
         cls.agents_admin_client = cls.os_adm.agents_v3_client
+        cls.migrations_admin_client = cls.os_adm.migrations_v3_client
diff --git a/tempest/api/compute/v3/admin/test_migrations.py b/tempest/api/compute/v3/admin/test_migrations.py
new file mode 100644
index 0000000..e8bd473
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_migrations.py
@@ -0,0 +1,50 @@
+# 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 testtools
+
+from tempest.api.compute import base
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class MigrationsAdminV3Test(base.BaseV3ComputeAdminTest):
+
+    @test.attr(type='gate')
+    def test_list_migrations(self):
+        # Admin can get the migrations list
+        resp, _ = self.migrations_admin_client.list_migrations()
+        self.assertEqual(200, resp.status)
+
+    @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+                          'Resize not available.')
+    @test.attr(type='gate')
+    def test_list_migrations_in_flavor_resize_situation(self):
+        # Admin can get the migrations list which contains the resized server
+        resp, server = self.create_test_server(wait_until="ACTIVE")
+        server_id = server['id']
+
+        resp, _ = self.servers_client.resize(server_id, self.flavor_ref_alt)
+        self.assertEqual(202, resp.status)
+        self.servers_client.wait_for_server_status(server_id, 'VERIFY_RESIZE')
+        self.servers_client.confirm_resize(server_id)
+        self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
+
+        resp, body = self.migrations_admin_client.list_migrations()
+        self.assertEqual(200, resp.status)
+
+        instance_uuids = [x['instance_uuid'] for x in body]
+        self.assertIn(server_id, instance_uuids)
diff --git a/tempest/api_schema/compute/v2/hosts.py b/tempest/api_schema/compute/v2/hosts.py
new file mode 100644
index 0000000..add5bb5
--- /dev/null
+++ b/tempest/api_schema/compute/v2/hosts.py
@@ -0,0 +1,27 @@
+# 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.
+
+body = {
+    'type': 'object',
+    'properties': {
+        'host': {'type': 'string'},
+        'power_action': {'enum': ['startup']}
+    },
+    'required': ['host', 'power_action']
+}
+
+startup_host = {
+    'status_code': [200],
+    'response_body': body
+}
diff --git a/tempest/api_schema/compute/v2/security_groups.py b/tempest/api_schema/compute/v2/security_groups.py
index 68b65b4..6dd44cd 100644
--- a/tempest/api_schema/compute/v2/security_groups.py
+++ b/tempest/api_schema/compute/v2/security_groups.py
@@ -36,3 +36,41 @@
         'required': ['security_groups']
     }
 }
+
+create_security_group_rule = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'security_group_rule': {
+                'type': 'object',
+                'properties': {
+                    'from_port': {'type': 'integer'},
+                    'to_port': {'type': 'integer'},
+                    'group': {'type': 'object'},
+                    'ip_protocol': {'type': 'string'},
+                    # 'parent_group_id' can be UUID so defining it
+                    # as 'string' also.
+                    'parent_group_id': {'type': ['integer', 'string']},
+                    'id': {'type': ['integer', 'string']},
+                    'ip_range': {
+                        'type': 'object',
+                        'properties': {
+                            'cidr': {'type': 'string'}
+                        }
+                        # When optional argument is provided in request body
+                        # like 'group_id' then, attribute 'cidr' does not
+                        # comes in response body. So it is not 'required'.
+                    }
+                },
+                'required': ['from_port', 'to_port', 'group', 'ip_protocol',
+                             'parent_group_id', 'id', 'ip_range']
+            }
+        },
+        'required': ['security_group_rule']
+    }
+}
+
+delete_security_group_rule = {
+    'status_code': [202]
+}
diff --git a/tempest/api_schema/compute/v3/hosts.py b/tempest/api_schema/compute/v3/hosts.py
new file mode 100644
index 0000000..9731d4b
--- /dev/null
+++ b/tempest/api_schema/compute/v3/hosts.py
@@ -0,0 +1,26 @@
+# 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_schema.compute.v2 import hosts
+
+startup_host = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'host': hosts.body
+        },
+        'required': ['host']
+    }
+}
diff --git a/tempest/cli/simple_read_only/test_sahara.py b/tempest/cli/simple_read_only/test_sahara.py
index cd819a4..5af8dae 100644
--- a/tempest/cli/simple_read_only/test_sahara.py
+++ b/tempest/cli/simple_read_only/test_sahara.py
@@ -48,23 +48,48 @@
 
     def test_sahara_plugins_list(self):
         plugins = self.parser.listing(self.sahara('plugin-list'))
-        self.assertTableStruct(plugins, ['name', 'versions', 'title'])
+        self.assertTableStruct(plugins, [
+            'name',
+            'versions',
+            'title'
+        ])
 
     def test_sahara_plugins_show(self):
-        plugin = self.parser.listing(self.sahara('plugin-show',
-                                                 params='--name vanilla'))
-        self.assertTableStruct(plugin, ['Property', 'Value'])
+        result = self.sahara('plugin-show', params='--name vanilla')
+        plugin = self.parser.listing(result)
+        self.assertTableStruct(plugin, [
+            'Property',
+            'Value'
+        ])
 
     def test_sahara_node_group_template_list(self):
-        plugins = self.parser.listing(self.sahara('node-group-template-list'))
-        self.assertTableStruct(plugins, ['name', 'id', 'plugin_name',
-                                         'node_processes', 'description'])
+        result = self.sahara('node-group-template-list')
+        node_group_templates = self.parser.listing(result)
+        self.assertTableStruct(node_group_templates, [
+            'name',
+            'id',
+            'plugin_name',
+            'node_processes',
+            'description'
+        ])
 
     def test_sahara_cluster_template_list(self):
-        plugins = self.parser.listing(self.sahara('cluster-template-list'))
-        self.assertTableStruct(plugins, ['name', 'id', 'plugin_name',
-                                         'node_groups', 'description'])
+        result = self.sahara('cluster-template-list')
+        cluster_templates = self.parser.listing(result)
+        self.assertTableStruct(cluster_templates, [
+            'name',
+            'id',
+            'plugin_name',
+            'node_groups',
+            'description'
+        ])
 
     def test_sahara_cluster_list(self):
-        plugins = self.parser.listing(self.sahara('cluster-list'))
-        self.assertTableStruct(plugins, ['name', 'id', 'status', 'node_count'])
+        result = self.sahara('cluster-list')
+        clusters = self.parser.listing(result)
+        self.assertTableStruct(clusters, [
+            'name',
+            'id',
+            'status',
+            'node_count'
+        ])
diff --git a/tempest/clients.py b/tempest/clients.py
index 7ebd983..6d27417 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -78,6 +78,8 @@
     InterfacesV3ClientJSON
 from tempest.services.compute.v3.json.keypairs_client import \
     KeyPairsV3ClientJSON
+from tempest.services.compute.v3.json.migration_client import \
+    MigrationsV3ClientJSON
 from tempest.services.compute.v3.json.quotas_client import \
     QuotasV3ClientJSON
 from tempest.services.compute.v3.json.servers_client import \
@@ -325,6 +327,8 @@
             self.tenant_usages_client = TenantUsagesClientJSON(
                 self.auth_provider)
             self.version_v3_client = VersionV3ClientJSON(self.auth_provider)
+            self.migrations_v3_client = MigrationsV3ClientJSON(
+                self.auth_provider)
             self.policy_client = PolicyClientJSON(self.auth_provider)
             self.hosts_client = HostsClientJSON(self.auth_provider)
             self.hypervisor_v3_client = HypervisorV3ClientJSON(
diff --git a/tempest/common/generator/base_generator.py b/tempest/common/generator/base_generator.py
index 95d50e2..87d65d0 100644
--- a/tempest/common/generator/base_generator.py
+++ b/tempest/common/generator/base_generator.py
@@ -87,12 +87,6 @@
         "additionalProperties": False,
     }
 
-    def __new__(cls, *args, **kwargs):
-        if not cls._instance:
-            cls._instance = super(BasicGeneratorSet, cls).__new__(cls, *args,
-                                                                  **kwargs)
-        return cls._instance
-
     def __init__(self):
         self.types_dict = {}
         for m in dir(self):
diff --git a/tempest/common/glance_http.py b/tempest/common/glance_http.py
index b4ba933..9358851 100644
--- a/tempest/common/glance_http.py
+++ b/tempest/common/glance_http.py
@@ -19,6 +19,7 @@
 import hashlib
 import httplib
 import json
+import OpenSSL
 import posixpath
 import re
 from six import moves
@@ -27,14 +28,6 @@
 import struct
 import urlparse
 
-
-# Python 2.5 compat fix
-if not hasattr(urlparse, 'parse_qsl'):
-    import cgi
-    urlparse.parse_qsl = cgi.parse_qsl
-
-import OpenSSL
-
 from tempest import exceptions as exc
 from tempest.openstack.common import log as logging
 
@@ -50,7 +43,7 @@
         self.auth_provider = auth_provider
         self.filters = filters
         self.endpoint = auth_provider.base_url(filters)
-        endpoint_parts = self.parse_endpoint(self.endpoint)
+        endpoint_parts = urlparse.urlparse(self.endpoint)
         self.endpoint_scheme = endpoint_parts.scheme
         self.endpoint_hostname = endpoint_parts.hostname
         self.endpoint_port = endpoint_parts.port
@@ -61,10 +54,6 @@
             self.endpoint_scheme, **kwargs)
 
     @staticmethod
-    def parse_endpoint(endpoint):
-        return urlparse.urlparse(endpoint)
-
-    @staticmethod
     def get_connection_class(scheme):
         if scheme == 'https':
             return VerifiedHTTPSConnection
@@ -107,7 +96,7 @@
         conn = self.get_connection()
 
         try:
-            url_parts = self.parse_endpoint(url)
+            url_parts = urlparse.urlparse(url)
             conn_url = posixpath.normpath(url_parts.path)
             LOG.debug('Actual Path: {path}'.format(path=conn_url))
             if kwargs['headers'].get('Transfer-Encoding') == 'chunked':
@@ -134,7 +123,6 @@
             raise exc.TimeoutException(message)
 
         body_iter = ResponseBodyIterator(resp)
-
         # Read body into string if it isn't obviously image data
         if resp.getheader('content-type', None) != 'application/octet-stream':
             body_str = ''.join([body_chunk for body_chunk in body_iter])
@@ -178,7 +166,7 @@
 
         resp, body_iter = self._http_request(url, method, **kwargs)
 
-        if 'application/json' in resp.getheader('content-type', None):
+        if 'application/json' in resp.getheader('content-type', ''):
             body = ''.join([chunk for chunk in body_iter])
             try:
                 body = json.loads(body)
diff --git a/tempest/services/compute/json/hosts_client.py b/tempest/services/compute/json/hosts_client.py
index eeb417a..a000e56 100644
--- a/tempest/services/compute/json/hosts_client.py
+++ b/tempest/services/compute/json/hosts_client.py
@@ -16,6 +16,7 @@
 import urllib
 
 from tempest.api_schema.compute import hosts as schema
+from tempest.api_schema.compute.v2 import hosts as v2_schema
 from tempest.common import rest_client
 from tempest import config
 
@@ -67,6 +68,7 @@
 
         resp, body = self.get("os-hosts/%s/startup" % str(hostname))
         body = json.loads(body)
+        self.validate_response(v2_schema.startup_host, resp, body)
         return resp, body['host']
 
     def shutdown_host(self, hostname):
diff --git a/tempest/services/compute/json/security_groups_client.py b/tempest/services/compute/json/security_groups_client.py
index 9267be7..7411fb7 100644
--- a/tempest/services/compute/json/security_groups_client.py
+++ b/tempest/services/compute/json/security_groups_client.py
@@ -111,11 +111,15 @@
         url = 'os-security-group-rules'
         resp, body = self.post(url, post_body)
         body = json.loads(body)
+        self.validate_response(schema.create_security_group_rule, resp, body)
         return resp, body['security_group_rule']
 
     def delete_security_group_rule(self, group_rule_id):
         """Deletes the provided Security Group rule."""
-        return self.delete('os-security-group-rules/%s' % str(group_rule_id))
+        resp, body = self.delete('os-security-group-rules/%s' %
+                                 str(group_rule_id))
+        self.validate_response(schema.delete_security_group_rule, resp, body)
+        return resp, body
 
     def list_security_group_rules(self, security_group_id):
         """List all rules for a security group."""
diff --git a/tempest/services/compute/v3/json/hosts_client.py b/tempest/services/compute/v3/json/hosts_client.py
index db7134c..558d4f7 100644
--- a/tempest/services/compute/v3/json/hosts_client.py
+++ b/tempest/services/compute/v3/json/hosts_client.py
@@ -16,6 +16,7 @@
 import urllib
 
 from tempest.api_schema.compute import hosts as schema
+from tempest.api_schema.compute.v3 import hosts as v3_schema
 from tempest.common import rest_client
 from tempest import config
 
@@ -67,6 +68,7 @@
 
         resp, body = self.get("os-hosts/%s/startup" % str(hostname))
         body = json.loads(body)
+        self.validate_response(v3_schema.startup_host, resp, body)
         return resp, body['host']
 
     def shutdown_host(self, hostname):
diff --git a/tempest/services/compute/v3/json/migration_client.py b/tempest/services/compute/v3/json/migration_client.py
new file mode 100644
index 0000000..efd39b7
--- /dev/null
+++ b/tempest/services/compute/v3/json/migration_client.py
@@ -0,0 +1,37 @@
+# Copyright 2014 NEC Corporation.
+#
+#    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 urllib
+
+from tempest.common import rest_client
+from tempest import config
+
+CONF = config.CONF
+
+
+class MigrationsV3ClientJSON(rest_client.RestClient):
+
+    def __init__(self, auth_provider):
+        super(MigrationsV3ClientJSON, self).__init__(auth_provider)
+        self.service = CONF.compute.catalog_v3_type
+
+    def list_migrations(self, params=None):
+        """Lists all migrations."""
+
+        url = 'os-migrations'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+
+        resp, body = self.get(url)
+        return resp, self._parse_resp(body)
diff --git a/tox.ini b/tox.ini
index 4a625f8..5e8d283 100644
--- a/tox.ini
+++ b/tox.ini
@@ -54,7 +54,7 @@
 setenv = OS_TEST_TIMEOUT=1200
 # The regex below is used to select heat api/scenario tests tagged as slow.
 commands =
-  bash tools/pretty_tox_serial.sh '(?=.*\[.*\bslow\b.*\])(^tempest\.(api|scenario)\.orchestration) {posargs}'
+  bash tools/pretty_tox.sh '(?=.*\[.*\bslow\b.*\])(^tempest\.(api|scenario)\.orchestration) {posargs}'
 
 [testenv:large-ops]
 sitepackages = True