Merge "add positive tests for volume"
diff --git a/.mailmap b/.mailmap
index 9a22c71..5c37a5e 100644
--- a/.mailmap
+++ b/.mailmap
@@ -2,6 +2,7 @@
 Ravikumar Venkatesan <ravikumar.venkatesan@hp.com> ravikumar venkatesan <ravikumar.venkatesan@hp.com>
 Rohit Karajgi <rohit.karajgi@nttdata.com> Rohit Karajgi <rohit.karajgi@vertex.co.in>
 Jay Pipes <jaypipes@gmail.com> Jay Pipes <jpipes@librebox.gateway.2wire.net>
+Joe Gordon <joe.gordon0@gmail.com> <jogo@cloudscaling.com>
 <brian.waldon@rackspace.com> <bcwaldon@gmail.com>
 Daryl Walleck <daryl.walleck@rackspace.com> dwalleck <daryl.walleck@rackspace.com>
 <jeblair@hp.com> <corvus@inaugust.com>
diff --git a/README.rst b/README.rst
index 4161cc6..0996022 100644
--- a/README.rst
+++ b/README.rst
@@ -6,6 +6,33 @@
 Scenarios, and other specific tests useful in validating an OpenStack
 deployment.
 
+Design Principles
+----------
+Tempest Design Principles that we strive to live by.
+
+- Tempest should be able to run against any OpenStack cloud, be it a
+  one node devstack install, a 20 node lxc cloud, or a 1000 node kvm
+  cloud.
+- Tempest should be explicit in testing features. It is easy to auto
+  discover features of a cloud incorrectly, and give people an
+  incorrect assessment of their cloud. Explicit is always better.
+- Tempest uses OpenStack public interfaces. Tests in Tempest should
+  only touch public interfaces, API calls (native or 3rd party),
+  public CLI or libraries.
+- Tempest should not touch private or implementation specific
+  interfaces. This means not directly going to the database, not
+  directly hitting the hypervisors, not testing extensions not
+  included in the OpenStack base. If there is some feature of
+  OpenStack that is not verifiable through standard interfaces, this
+  should be considered a possible enhancement.
+- Tempest strives for complete coverage of the OpenStack API and
+  common scenarios that demonstrate a working cloud.
+- Tempest drives load in an OpenStack cloud. By including a broad
+  array of API and scenario tests Tempest can be reused in whole or in
+  parts as load generation for an OpenStack cloud.
+- Tempest should attempt to clean up after itself, whenever possible
+  we should tear down resources when done.
+- Tempest should be self testing.
 
 Quickstart
 ----------
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index 75b8dad..05bb457 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -80,13 +80,7 @@
         self.assertEqual(flavor['rxtx_factor'], self.rxtx)
         self.assertEqual(flavor['OS-FLV-EXT-DATA:ephemeral'],
                          self.ephemeral)
-        if self._interface == "xml":
-            XMLNS_OS_FLV_ACCESS = "http://docs.openstack.org/compute/ext/"\
-                "flavor_access/api/v2"
-            key = "{" + XMLNS_OS_FLV_ACCESS + "}is_public"
-            self.assertEqual(flavor[key], "True")
-        if self._interface == "json":
-            self.assertEqual(flavor['os-flavor-access:is_public'], True)
+        self.assertEqual(flavor['os-flavor-access:is_public'], True)
 
         # Verify flavor is retrieved
         resp, flavor = self.client.get_flavor_details(new_flavor_id)
@@ -156,6 +150,14 @@
     def test_create_list_flavor_without_extra_data(self):
         # Create a flavor and ensure it is listed
         # This operation requires the user to have 'admin' role
+
+        def verify_flavor_response_extension(flavor):
+            # check some extensions for the flavor create/show/detail response
+            self.assertEqual(flavor['swap'], '')
+            self.assertEqual(int(flavor['rxtx_factor']), 1)
+            self.assertEqual(int(flavor['OS-FLV-EXT-DATA:ephemeral']), 0)
+            self.assertEqual(flavor['os-flavor-access:is_public'], True)
+
         flavor_name = rand_name(self.flavor_name_prefix)
         new_flavor_id = rand_int_id(start=1000)
 
@@ -171,26 +173,20 @@
         self.assertEqual(flavor['vcpus'], self.vcpus)
         self.assertEqual(flavor['disk'], self.disk)
         self.assertEqual(int(flavor['id']), new_flavor_id)
-        self.assertEqual(flavor['swap'], '')
-        self.assertEqual(int(flavor['rxtx_factor']), 1)
-        self.assertEqual(int(flavor['OS-FLV-EXT-DATA:ephemeral']), 0)
-        if self._interface == "xml":
-            XMLNS_OS_FLV_ACCESS = "http://docs.openstack.org/compute/ext/"\
-                "flavor_access/api/v2"
-            key = "{" + XMLNS_OS_FLV_ACCESS + "}is_public"
-            self.assertEqual(flavor[key], "True")
-        if self._interface == "json":
-            self.assertEqual(flavor['os-flavor-access:is_public'], True)
+        verify_flavor_response_extension(flavor)
 
         # Verify flavor is retrieved
         resp, flavor = self.client.get_flavor_details(new_flavor_id)
         self.assertEqual(resp.status, 200)
         self.assertEqual(flavor['name'], flavor_name)
+        verify_flavor_response_extension(flavor)
+
         # Check if flavor is present in list
-        resp, flavors = self.client.list_flavors_with_detail()
+        resp, flavors = self.user_client.list_flavors_with_detail()
         self.assertEqual(resp.status, 200)
         for flavor in flavors:
             if flavor['name'] == flavor_name:
+                verify_flavor_response_extension(flavor)
                 flag = True
         self.assertTrue(flag)
 
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs.py b/tempest/api/compute/admin/test_flavors_extra_specs.py
index 0fd4d11..403a946 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -19,7 +19,6 @@
 from tempest.api.compute import base
 from tempest.common.utils.data_utils import rand_int_id
 from tempest.common.utils.data_utils import rand_name
-from tempest import exceptions
 from tempest.test import attr
 
 
@@ -64,9 +63,9 @@
         super(FlavorsExtraSpecsTestJSON, cls).tearDownClass()
 
     @attr(type='gate')
-    def test_flavor_set_get_unset_keys(self):
-        # Test to SET, GET UNSET flavor extra spec as a user
-        # with admin privileges.
+    def test_flavor_set_get_update_show_unset_keys(self):
+        # Test to SET, GET, UPDATE, SHOW, UNSET flavor extra
+        # spec as a user with admin privileges.
         # Assigning extra specs values that are to be set
         specs = {"key1": "value1", "key2": "value2"}
         # SET extra specs to the flavor created in setUp
@@ -79,55 +78,55 @@
             self.client.get_flavor_extra_spec(self.flavor['id'])
         self.assertEqual(get_resp.status, 200)
         self.assertEqual(get_body, specs)
+
+        # UPDATE the value of the extra specs key1
+        update_resp, update_body = \
+            self.client.update_flavor_extra_spec(self.flavor['id'],
+                                                 "key1",
+                                                 key1="value")
+        self.assertEqual(update_resp.status, 200)
+        self.assertEqual({"key1": "value"}, update_body)
+
         # GET a key value and verify
-        get_resp, get_body = \
+        show_resp, get_body = \
             self.client.get_flavor_extra_spec_with_key(self.flavor['id'],
-                                                       "key2")
+                                                       "key1")
+        self.assertEqual(show_resp.status, 200)
+        self.assertEqual(get_body, 'value')
+
+        # GET extra specs and verify the value of the key2
+        # is the same as before
+        get_resp, get_body = \
+            self.client.get_flavor_extra_spec(self.flavor['id'])
         self.assertEqual(get_resp.status, 200)
-        self.assertEqual(get_body, specs['key2'])
+        self.assertEqual(get_body, {"key1": "value", "key2": "value2"})
+
         # UNSET extra specs that were set in this test
         unset_resp, _ = \
             self.client.unset_flavor_extra_spec(self.flavor['id'], "key1")
         self.assertEqual(unset_resp.status, 200)
-
-    @attr(type=['negative', 'gate'])
-    def test_flavor_non_admin_set_keys(self):
-        # Test to SET flavor extra spec as a user without admin privileges.
-        specs = {"key1": "value1", "key2": "value2"}
-        self.assertRaises(exceptions.Unauthorized,
-                          self.flavors_client.set_flavor_extra_spec,
-                          self.flavor['id'],
-                          specs)
+        unset_resp, _ = \
+            self.client.unset_flavor_extra_spec(self.flavor['id'], "key2")
+        self.assertEqual(unset_resp.status, 200)
 
     @attr(type='gate')
-    def test_flavor_non_admin_get_keys(self):
+    def test_flavor_non_admin_get_all_keys_and_specified_key(self):
         specs = {"key1": "value1", "key2": "value2"}
         set_resp, set_body = self.client.set_flavor_extra_spec(
             self.flavor['id'], specs)
         resp, body = self.flavors_client.get_flavor_extra_spec(
             self.flavor['id'])
         self.assertEqual(resp.status, 200)
+
         for key in specs:
             self.assertEqual(body[key], specs[key])
 
-    @attr(type=['negative', 'gate'])
-    def test_flavor_non_admin_unset_keys(self):
-        specs = {"key1": "value1", "key2": "value2"}
-        set_resp, set_body = self.client.set_flavor_extra_spec(
-            self.flavor['id'], specs)
-
-        self.assertRaises(exceptions.Unauthorized,
-                          self.flavors_client.unset_flavor_extra_spec,
-                          self.flavor['id'],
-                          'key1')
-
-    @attr(type=['negative', 'gate'])
-    def test_flavor_unset_nonexistent_key(self):
-        nonexistent_key = rand_name('flavor_key')
-        self.assertRaises(exceptions.NotFound,
-                          self.client.unset_flavor_extra_spec,
-                          self.flavor['id'],
-                          nonexistent_key)
+        get_resp, get_body = \
+            self.flavors_client.get_flavor_extra_spec_with_key(
+                self.flavor['id'],
+                "key1")
+        self.assertEqual(get_resp.status, 200)
+        self.assertEqual("value1", get_body)
 
 
 class FlavorsExtraSpecsTestXML(FlavorsExtraSpecsTestJSON):
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
new file mode 100644
index 0000000..8d62a2a
--- /dev/null
+++ b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
@@ -0,0 +1,118 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+# Copyright 2013 IBM Corp.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest.common.utils.data_utils import rand_int_id
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+
+
+class FlavorsExtraSpecsNegativeTestJSON(base.BaseV2ComputeAdminTest):
+    """the Negative tests for FlavorsExtraSpecs."""
+
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(FlavorsExtraSpecsNegativeTestJSON, cls).setUpClass()
+        if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+            msg = "FlavorExtraData extension not enabled."
+            raise cls.skipException(msg)
+
+        cls.client = cls.os_adm.flavors_client
+        flavor_name = rand_name('test_flavor')
+        ram = 512
+        vcpus = 1
+        disk = 10
+        ephemeral = 10
+        cls.new_flavor_id = rand_int_id(start=1000)
+        swap = 1024
+        rxtx = 1
+        # Create a flavor
+        resp, cls.flavor = cls.client.create_flavor(flavor_name,
+                                                    ram, vcpus,
+                                                    disk,
+                                                    cls.new_flavor_id,
+                                                    ephemeral=ephemeral,
+                                                    swap=swap, rxtx=rxtx)
+
+    @classmethod
+    def tearDownClass(cls):
+        resp, body = cls.client.delete_flavor(cls.flavor['id'])
+        cls.client.wait_for_resource_deletion(cls.flavor['id'])
+        super(FlavorsExtraSpecsNegativeTestJSON, cls).tearDownClass()
+
+    @attr(type=['negative', 'gate'])
+    def test_flavor_non_admin_set_keys(self):
+        # Test to SET flavor extra spec as a user without admin privileges.
+        specs = {"key1": "value1", "key2": "value2"}
+        self.assertRaises(exceptions.Unauthorized,
+                          self.flavors_client.set_flavor_extra_spec,
+                          self.flavor['id'],
+                          specs)
+
+    @attr(type=['negative', 'gate'])
+    def test_flavor_non_admin_unset_keys(self):
+        specs = {"key1": "value1", "key2": "value2"}
+        set_resp, set_body = self.client.set_flavor_extra_spec(
+            self.flavor['id'], specs)
+
+        self.assertRaises(exceptions.Unauthorized,
+                          self.flavors_client.unset_flavor_extra_spec,
+                          self.flavor['id'],
+                          'key1')
+
+    @attr(type=['negative', 'gate'])
+    def test_flavor_unset_nonexistent_key(self):
+        nonexistent_key = rand_name('flavor_key')
+        self.assertRaises(exceptions.NotFound,
+                          self.client.unset_flavor_extra_spec,
+                          self.flavor['id'],
+                          nonexistent_key)
+
+    @attr(type=['negative', 'gate'])
+    def test_flavor_get_nonexistent_key(self):
+        self.assertRaises(exceptions.NotFound,
+                          self.flavors_client.get_flavor_extra_spec_with_key,
+                          self.flavor['id'],
+                          "nonexistent_key")
+
+    @attr(type=['negative', 'gate'])
+    def test_flavor_update_mismatch_key(self):
+        # the key will be updated should be match the key in the body
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.update_flavor_extra_spec,
+                          self.flavor['id'],
+                          "key2",
+                          key1="value")
+
+    @attr(type=['negative', 'gate'])
+    def test_flavor_update_more_key(self):
+        # there should be just one item in the request body
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.update_flavor_extra_spec,
+                          self.flavor['id'],
+                          "key1",
+                          key1="value",
+                          key2="value")
+
+
+class FlavorsExtraSpecsNegativeTestXML(FlavorsExtraSpecsNegativeTestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index bef45a7..088d3ae 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -188,7 +188,9 @@
         # changes-since returns all instances, including deleted.
         num_expected = (len(self.existing_fixtures) +
                         len(self.deleted_fixtures))
-        self.assertEqual(num_expected, len(body['servers']))
+        self.assertEqual(num_expected, len(body['servers']),
+                         "Number of servers %d is wrong in %s" %
+                         (num_expected, body['servers']))
 
     @attr(type=['negative', 'gate'])
     def test_list_servers_by_changes_since_invalid_date(self):
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index fc4a5e0..961737a 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -253,7 +253,6 @@
         self.assertEqual(202, resp.status)
         self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
 
-    @skip_because(bug="1233026")
     @attr(type='gate')
     def test_lock_unlock_server(self):
         # Lock the server,try server stop(exceptions throw),unlock it and retry
@@ -263,7 +262,7 @@
         self.assertEqual(200, resp.status)
         self.assertEqual(server['status'], 'ACTIVE')
         # Locked server is not allowed to be stopped by non-admin user
-        self.assertRaises(exceptions.BadRequest,
+        self.assertRaises(exceptions.Conflict,
                           self.servers_client.stop, self.server_id)
         resp, server = self.servers_client.unlock_server(self.server_id)
         self.assertEqual(202, resp.status)
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 159c4f5..ed915c1 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -215,3 +215,19 @@
         vpnservice = body['vpnservice']
         cls.vpnservices.append(vpnservice)
         return vpnservice
+
+
+class BaseAdminNetworkTest(BaseNetworkTest):
+
+    @classmethod
+    def setUpClass(cls):
+        super(BaseAdminNetworkTest, cls).setUpClass()
+        admin_username = cls.config.compute_admin.username
+        admin_password = cls.config.compute_admin.password
+        admin_tenant = cls.config.compute_admin.tenant_name
+        if not (admin_username and admin_password and admin_tenant):
+            msg = ("Missing Administrative Network API credentials "
+                   "in configuration.")
+            raise cls.skipException(msg)
+        cls.admin_manager = clients.AdminManager(interface=cls._interface)
+        cls.admin_client = cls.admin_manager.network_client
diff --git a/tempest/api/network/base_security_groups.py b/tempest/api/network/base_security_groups.py
new file mode 100644
index 0000000..5ab1748
--- /dev/null
+++ b/tempest/api/network/base_security_groups.py
@@ -0,0 +1,60 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 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.common.utils import data_utils
+
+
+class BaseSecGroupTest(base.BaseNetworkTest):
+
+    @classmethod
+    def setUpClass(cls):
+        super(BaseSecGroupTest, cls).setUpClass()
+
+    def _create_security_group(self):
+        # Create a security group
+        name = data_utils.rand_name('secgroup-')
+        resp, group_create_body = self.client.create_security_group(name)
+        self.assertEqual('201', resp['status'])
+        self.addCleanup(self._delete_security_group,
+                        group_create_body['security_group']['id'])
+        self.assertEqual(group_create_body['security_group']['name'], name)
+        return group_create_body, name
+
+    def _delete_security_group(self, secgroup_id):
+        resp, _ = self.client.delete_security_group(secgroup_id)
+        self.assertEqual(204, resp.status)
+        # Asserting that the security group is not found in the list
+        # after deletion
+        resp, list_body = self.client.list_security_groups()
+        self.assertEqual('200', resp['status'])
+        secgroup_list = list()
+        for secgroup in list_body['security_groups']:
+            secgroup_list.append(secgroup['id'])
+        self.assertNotIn(secgroup_id, secgroup_list)
+
+    def _delete_security_group_rule(self, rule_id):
+        resp, _ = self.client.delete_security_group_rule(rule_id)
+        self.assertEqual(204, resp.status)
+        # Asserting that the security group is not found in the list
+        # after deletion
+        resp, list_body = self.client.list_security_group_rules()
+        self.assertEqual('200', resp['status'])
+        rules_list = list()
+        for rule in list_body['security_group_rules']:
+            rules_list.append(rule['id'])
+        self.assertNotIn(rule_id, rules_list)
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 2cfbf61..512d065 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -20,7 +20,10 @@
 from tempest.test import attr
 
 
-class RoutersTest(base.BaseNetworkTest):
+class RoutersTest(base.BaseAdminNetworkTest):
+    # NOTE(salv-orlando): This class inherits from BaseAdminNetworkTest
+    # as some router operations, such as enabling or disabling SNAT
+    # require admin credentials by default
     _interface = 'json'
 
     @classmethod
@@ -130,3 +133,99 @@
             interface['port_id'])
         self.assertEqual(show_port_body['port']['device_id'],
                          router['id'])
+
+    def _verify_router_gateway(self, router_id, exp_ext_gw_info=None):
+        resp, show_body = self.client.show_router(router_id)
+        self.assertEqual('200', resp['status'])
+        actual_ext_gw_info = show_body['router']['external_gateway_info']
+        if exp_ext_gw_info is None:
+            self.assertIsNone(actual_ext_gw_info)
+            return
+        # Verify only keys passed in exp_ext_gw_info
+        for k, v in exp_ext_gw_info.iteritems():
+            self.assertEqual(v, actual_ext_gw_info[k])
+
+    def _verify_gateway_port(self, router_id):
+        resp, list_body = self.admin_client.list_ports(
+            network_id=self.network_cfg.public_network_id,
+            device_id=router_id)
+        self.assertEqual(len(list_body['ports']), 1)
+        gw_port = list_body['ports'][0]
+        fixed_ips = gw_port['fixed_ips']
+        self.assertEqual(len(fixed_ips), 1)
+        resp, public_net_body = self.admin_client.show_network(
+            self.network_cfg.public_network_id)
+        public_subnet_id = public_net_body['network']['subnets'][0]
+        self.assertEqual(fixed_ips[0]['subnet_id'], public_subnet_id)
+
+    @attr(type='smoke')
+    def test_update_router_set_gateway(self):
+        router = self.create_router(rand_name('router-'))
+        self.client.update_router(
+            router['id'],
+            external_gateway_info={
+                'network_id': self.network_cfg.public_network_id})
+        # Verify operation - router
+        resp, show_body = self.client.show_router(router['id'])
+        self.assertEqual('200', resp['status'])
+        self._verify_router_gateway(
+            router['id'],
+            {'network_id': self.network_cfg.public_network_id})
+        self._verify_gateway_port(router['id'])
+
+    @attr(type='smoke')
+    def test_update_router_set_gateway_with_snat_explicit(self):
+        router = self.create_router(rand_name('router-'))
+        self.admin_client.update_router_with_snat_gw_info(
+            router['id'],
+            external_gateway_info={
+                'network_id': self.network_cfg.public_network_id,
+                'enable_snat': True})
+        self._verify_router_gateway(
+            router['id'],
+            {'network_id': self.network_cfg.public_network_id,
+             'enable_snat': True})
+        self._verify_gateway_port(router['id'])
+
+    @attr(type='smoke')
+    def test_update_router_set_gateway_without_snat(self):
+        router = self.create_router(rand_name('router-'))
+        self.admin_client.update_router_with_snat_gw_info(
+            router['id'],
+            external_gateway_info={
+                'network_id': self.network_cfg.public_network_id,
+                'enable_snat': False})
+        self._verify_router_gateway(
+            router['id'],
+            {'network_id': self.network_cfg.public_network_id,
+             'enable_snat': False})
+        self._verify_gateway_port(router['id'])
+
+    @attr(type='smoke')
+    def test_update_router_unset_gateway(self):
+        router = self.create_router(
+            rand_name('router-'),
+            external_network_id=self.network_cfg.public_network_id)
+        self.client.update_router(router['id'], external_gateway_info={})
+        self._verify_router_gateway(router['id'])
+        # No gateway port expected
+        resp, list_body = self.admin_client.list_ports(
+            network_id=self.network_cfg.public_network_id,
+            device_id=router['id'])
+        self.assertFalse(list_body['ports'])
+
+    @attr(type='smoke')
+    def test_update_router_reset_gateway_without_snat(self):
+        router = self.create_router(
+            rand_name('router-'),
+            external_network_id=self.network_cfg.public_network_id)
+        self.admin_client.update_router_with_snat_gw_info(
+            router['id'],
+            external_gateway_info={
+                'network_id': self.network_cfg.public_network_id,
+                'enable_snat': False})
+        self._verify_router_gateway(
+            router['id'],
+            {'network_id': self.network_cfg.public_network_id,
+             'enable_snat': False})
+        self._verify_gateway_port(router['id'])
diff --git a/tempest/api/network/test_security_groups.py b/tempest/api/network/test_security_groups.py
index 9218f0c..9b0a3de 100644
--- a/tempest/api/network/test_security_groups.py
+++ b/tempest/api/network/test_security_groups.py
@@ -15,42 +15,13 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api.network import base
-from tempest.common.utils import data_utils
+from tempest.api.network import base_security_groups as base
 from tempest.test import attr
 
 
-class SecGroupTest(base.BaseNetworkTest):
+class SecGroupTest(base.BaseSecGroupTest):
     _interface = 'json'
 
-    @classmethod
-    def setUpClass(cls):
-        super(SecGroupTest, cls).setUpClass()
-
-    def _delete_security_group(self, secgroup_id):
-        resp, _ = self.client.delete_security_group(secgroup_id)
-        self.assertEqual(204, resp.status)
-        # Asserting that the security group is not found in the list
-        # after deletion
-        resp, list_body = self.client.list_security_groups()
-        self.assertEqual('200', resp['status'])
-        secgroup_list = list()
-        for secgroup in list_body['security_groups']:
-            secgroup_list.append(secgroup['id'])
-        self.assertNotIn(secgroup_id, secgroup_list)
-
-    def _delete_security_group_rule(self, rule_id):
-        resp, _ = self.client.delete_security_group_rule(rule_id)
-        self.assertEqual(204, resp.status)
-        # Asserting that the security group is not found in the list
-        # after deletion
-        resp, list_body = self.client.list_security_group_rules()
-        self.assertEqual('200', resp['status'])
-        rules_list = list()
-        for rule in list_body['security_group_rules']:
-            rules_list.append(rule['id'])
-        self.assertNotIn(rule_id, rules_list)
-
     @attr(type='smoke')
     def test_list_security_groups(self):
         # Verify the that security group belonging to tenant exist in list
@@ -66,13 +37,7 @@
 
     @attr(type='smoke')
     def test_create_show_delete_security_group(self):
-        # Create a security group
-        name = data_utils.rand_name('secgroup-')
-        resp, group_create_body = self.client.create_security_group(name)
-        self.assertEqual('201', resp['status'])
-        self.addCleanup(self._delete_security_group,
-                        group_create_body['security_group']['id'])
-        self.assertEqual(group_create_body['security_group']['name'], name)
+        group_create_body, name = self._create_security_group()
 
         # Show details of the created security group
         resp, show_body = self.client.show_security_group(
@@ -90,13 +55,7 @@
 
     @attr(type='smoke')
     def test_create_show_delete_security_group_rule(self):
-        # Create a security group
-        name = data_utils.rand_name('secgroup-')
-        resp, group_create_body = self.client.create_security_group(name)
-        self.assertEqual('201', resp['status'])
-        self.addCleanup(self._delete_security_group,
-                        group_create_body['security_group']['id'])
-        self.assertEqual(group_create_body['security_group']['name'], name)
+        group_create_body, _ = self._create_security_group()
 
         # Create rules for each protocol
         protocols = ['tcp', 'udp', 'icmp']
diff --git a/tempest/api/network/test_security_groups_negative.py b/tempest/api/network/test_security_groups_negative.py
index daeb89f..cb0c247 100644
--- a/tempest/api/network/test_security_groups_negative.py
+++ b/tempest/api/network/test_security_groups_negative.py
@@ -15,46 +15,39 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api.network import test_security_groups as base
-from tempest.common.utils import data_utils
+from tempest.api.network import base_security_groups as base
 from tempest import exceptions
 from tempest.test import attr
 import uuid
 
 
-class NegativeSecGroupTest(base.SecGroupTest):
+class NegativeSecGroupTest(base.BaseSecGroupTest):
     _interface = 'json'
 
-    @attr(type=['negative', 'smoke'])
+    @attr(type=['negative', 'gate'])
     def test_show_non_existent_security_group(self):
         non_exist_id = str(uuid.uuid4())
         self.assertRaises(exceptions.NotFound, self.client.show_security_group,
                           non_exist_id)
 
-    @attr(type=['negative', 'smoke'])
+    @attr(type=['negative', 'gate'])
     def test_show_non_existent_security_group_rule(self):
         non_exist_id = str(uuid.uuid4())
         self.assertRaises(exceptions.NotFound,
                           self.client.show_security_group_rule,
                           non_exist_id)
 
-    @attr(type=['negative', 'smoke'])
+    @attr(type=['negative', 'gate'])
     def test_delete_non_existent_security_group(self):
-        non_exist_id = 'fictional-id'
+        non_exist_id = str(uuid.uuid4())
         self.assertRaises(exceptions.NotFound,
                           self.client.delete_security_group,
                           non_exist_id
                           )
 
-    @attr(type=['negative', 'smoke'])
+    @attr(type=['negative', 'gate'])
     def test_create_security_group_rule_with_bad_protocol(self):
-        # Create a security group
-        name = data_utils.rand_name('secgroup-')
-        resp, group_create_body = self.client.create_security_group(name)
-        self.assertEqual('201', resp['status'])
-        self.addCleanup(self._delete_security_group,
-                        group_create_body['security_group']['id'])
-        self.assertEqual(group_create_body['security_group']['name'], name)
+        group_create_body, _ = self._create_security_group()
 
         #Create rule with bad protocol name
         pname = 'bad_protocol_name'
@@ -63,20 +56,15 @@
                           group_create_body['security_group']['id'],
                           protocol=pname)
 
-    @attr(type=['negative', 'smoke'])
+    @attr(type=['negative', 'gate'])
     def test_create_security_group_rule_with_invalid_ports(self):
-        # Create a security group
-        name = data_utils.rand_name('secgroup-')
-        resp, group_create_body = self.client.create_security_group(name)
-        self.assertEqual('201', resp['status'])
-        self.addCleanup(self._delete_security_group,
-                        group_create_body['security_group']['id'])
-        self.assertEqual(group_create_body['security_group']['name'], name)
+        group_create_body, _ = self._create_security_group()
 
         #Create rule with invalid ports
         states = [(-16, 80, 'Invalid value for port -16'),
                   (80, 79, 'port_range_min must be <= port_range_max'),
-                  (80, 65536, 'Invalid value for port 65536')]
+                  (80, 65536, 'Invalid value for port 65536'),
+                  (-16, 65536, 'Invalid value for port')]
         for pmin, pmax, msg in states:
             ex = self.assertRaises(exceptions.BadRequest,
                                    self.client.create_security_group_rule,
diff --git a/tempest/cli/simple_read_only/test_nova_manage.py b/tempest/cli/simple_read_only/test_nova_manage.py
index 9a33556..524db5d 100644
--- a/tempest/cli/simple_read_only/test_nova_manage.py
+++ b/tempest/cli/simple_read_only/test_nova_manage.py
@@ -55,11 +55,11 @@
                          self.nova_manage('', '--version', merge_stderr=True))
 
     def test_debug_flag(self):
-        self.assertNotEqual("", self.nova_manage('instance_type list',
+        self.assertNotEqual("", self.nova_manage('flavor list',
                             '--debug'))
 
     def test_verbose_flag(self):
-        self.assertNotEqual("", self.nova_manage('instance_type list',
+        self.assertNotEqual("", self.nova_manage('flavor list',
                             '--verbose'))
 
     # test actions
@@ -68,8 +68,6 @@
 
     def test_flavor_list(self):
         self.assertNotEqual("", self.nova_manage('flavor list'))
-        self.assertEqual(self.nova_manage('instance_type list'),
-                         self.nova_manage('flavor list'))
 
     def test_db_archive_deleted_rows(self):
         # make sure command doesn't error out
diff --git a/tempest/services/compute/json/flavors_client.py b/tempest/services/compute/json/flavors_client.py
index dc05e3e..305a77b 100644
--- a/tempest/services/compute/json/flavors_client.py
+++ b/tempest/services/compute/json/flavors_client.py
@@ -109,6 +109,14 @@
         body = json.loads(body)
         return resp, body[key]
 
+    def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
+        """Gets specified extra Specs details of the mentioned flavor."""
+        resp, body = self.put('flavors/%s/os-extra_specs/%s' %
+                              (flavor_id, key),
+                              json.dumps(kwargs), self.headers)
+        body = json.loads(body)
+        return resp, body
+
     def unset_flavor_extra_spec(self, flavor_id, key):
         """Unsets extra Specs from the mentioned flavor."""
         return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
diff --git a/tempest/services/compute/xml/flavors_client.py b/tempest/services/compute/xml/flavors_client.py
index c7ed044..12e24d0 100644
--- a/tempest/services/compute/xml/flavors_client.py
+++ b/tempest/services/compute/xml/flavors_client.py
@@ -22,6 +22,7 @@
 from tempest.common.rest_client import RestClientXML
 from tempest.services.compute.xml.common import Document
 from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import Text
 from tempest.services.compute.xml.common import xml_to_json
 from tempest.services.compute.xml.common import XMLNS_11
 
@@ -29,7 +30,7 @@
 XMLNS_OS_FLV_EXT_DATA = \
     "http://docs.openstack.org/compute/ext/flavor_extra_data/api/v1.1"
 XMLNS_OS_FLV_ACCESS = \
-    "http://docs.openstack.org/compute/ext/flavor_access/api/v1.1"
+    "http://docs.openstack.org/compute/ext/flavor_access/api/v2"
 
 
 class FlavorsClientXML(RestClientXML):
@@ -49,6 +50,10 @@
             if k == '{%s}ephemeral' % XMLNS_OS_FLV_EXT_DATA:
                 k = 'OS-FLV-EXT-DATA:ephemeral'
 
+            if k == '{%s}is_public' % XMLNS_OS_FLV_ACCESS:
+                k = 'os-flavor-access:is_public'
+                v = True if v == 'True' else False
+
             if k == 'extra_specs':
                 k = 'OS-FLV-WITH-EXT-SPECS:extra_specs'
                 flavor[k] = dict(v)
@@ -155,6 +160,21 @@
         body = xml_to_json(etree.fromstring(body))
         return resp, body
 
+    def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
+        """Gets specified extra Specs details of the mentioned flavor."""
+        doc = Document()
+        for (k, v) in kwargs.items():
+            element = Element(k)
+            doc.append(element)
+            value = Text(v)
+            element.append(value)
+
+        resp, body = self.put('flavors/%s/os-extra_specs/%s' %
+                              (flavor_id, key),
+                              str(doc), self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        return resp, {key: body}
+
     def unset_flavor_extra_spec(self, flavor_id, key):
         """Unsets an extra spec based on the mentioned flavor and key."""
         return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index 92c1faf..e7cd33f 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -123,8 +123,12 @@
         resp, body = self.delete(uri, self.headers)
         return resp, body
 
-    def list_ports(self):
+    def list_ports(self, **filters):
         uri = '%s/ports' % (self.uri_prefix)
+        filter_items = ["%s=%s" % (k, v) for (k, v) in filters.iteritems()]
+        querystring = "&".join(filter_items)
+        if querystring:
+            uri = "%s?%s" % (uri, querystring)
         resp, body = self.get(uri, self.headers)
         body = json.loads(body)
         return resp, body
@@ -223,7 +227,7 @@
         body = json.loads(body)
         return resp, body
 
-    def update_router(self, router_id, **kwargs):
+    def _update_router(self, router_id, set_enable_snat, **kwargs):
         uri = '%s/routers/%s' % (self.uri_prefix, router_id)
         resp, body = self.get(uri, self.headers)
         body = json.loads(body)
@@ -231,15 +235,34 @@
         update_body['name'] = kwargs.get('name', body['router']['name'])
         update_body['admin_state_up'] = kwargs.get(
             'admin_state_up', body['router']['admin_state_up'])
-        # Must uncomment/modify these lines once LP question#233187 is solved
-        # update_body['external_gateway_info'] = kwargs.get(
-        # 'external_gateway_info', body['router']['external_gateway_info'])
+        cur_gw_info = body['router']['external_gateway_info']
+        if cur_gw_info and not set_enable_snat:
+            cur_gw_info.pop('enable_snat', None)
+        update_body['external_gateway_info'] = kwargs.get(
+            'external_gateway_info', body['router']['external_gateway_info'])
         update_body = dict(router=update_body)
         update_body = json.dumps(update_body)
         resp, body = self.put(uri, update_body, self.headers)
         body = json.loads(body)
         return resp, body
 
+    def update_router(self, router_id, **kwargs):
+        """Update a router leaving enable_snat to its default value."""
+        # If external_gateway_info contains enable_snat the request will fail
+        # with 404 unless executed with admin client, and therefore we instruct
+        # _update_router to not set this attribute
+        # NOTE(salv-orlando): The above applies as long as Neutron's default
+        # policy is to restrict enable_snat usage to admins only.
+        return self._update_router(router_id, set_enable_snat=False, **kwargs)
+
+    def update_router_with_snat_gw_info(self, router_id, **kwargs):
+        """Update a router passing also the enable_snat attribute.
+
+        This method must be execute with admin credentials, otherwise the API
+        call will return a 404 error.
+        """
+        return self._update_router(router_id, set_enable_snat=True, **kwargs)
+
     def add_router_interface_with_subnet_id(self, router_id, subnet_id):
         uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix,
               router_id)
diff --git a/tempest/tests/test_list_tests.py b/tempest/tests/test_list_tests.py
new file mode 100644
index 0000000..ab0d114
--- /dev/null
+++ b/tempest/tests/test_list_tests.py
@@ -0,0 +1,38 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import re
+import subprocess
+
+from tempest.tests import base
+
+
+class TestTestList(base.TestCase):
+
+    def test_no_import_errors(self):
+        import_failures = []
+        p = subprocess.Popen(['testr', 'list-tests'], stdout=subprocess.PIPE)
+        ids = p.stdout.read()
+        ids = ids.split('\n')
+        for test_id in ids:
+            if re.match('(\w+\.){3}\w+', test_id):
+                if not test_id.startswith('tempest.'):
+                    fail_id = test_id.split('unittest.loader.ModuleImport'
+                                            'Failure.')[1]
+                    import_failures.append(fail_id)
+        error_message = ("The following tests have import failures and aren't"
+                         " being run with test filters %s" % import_failures)
+        self.assertFalse(import_failures, error_message)
diff --git a/tox.ini b/tox.ini
index d93112c..a3c781b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -81,7 +81,7 @@
 # https://bugs.launchpad.net/tempest/+bug/1216076 so the neutron smoke
 # job would fail if we moved it to parallel.
 commands =
-   sh tools/pretty_tox_serial.sh 'smoke {posargs}'
+   sh tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
 
 [testenv:coverage]
 sitepackages = True