Merge "actually turn on neutron cli tests"
diff --git a/.testr.conf b/.testr.conf
index 05b12c4..c25ebec 100644
--- a/.testr.conf
+++ b/.testr.conf
@@ -2,7 +2,8 @@
 test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
              OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
              OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-500} \
-             ${PYTHON:-python} -m subunit.run discover -t ./ ./tempest $LISTOPT $IDOPTION
+             OS_TEST_PATH=${OS_TEST_PATH:-./tempest} \
+             ${PYTHON:-python} -m subunit.run discover -t ./ $OS_TEST_PATH $LISTOPT $IDOPTION
 test_id_option=--load-list $IDFILE
 test_list_option=--list
 group_regex=([^\.]*\.)*
diff --git a/etc/whitelist.yaml b/etc/whitelist.yaml
index a822fae..a8c5276 100644
--- a/etc/whitelist.yaml
+++ b/etc/whitelist.yaml
@@ -218,3 +218,6 @@
     - module: ".*"
       message: ".*"
 
+s-proxy:
+    - module: "proxy-server"
+      message: "Timeout talking to memcached"
diff --git a/tempest/api/compute/__init__.py b/tempest/api/compute/__init__.py
index dd92ee9..d20068e 100644
--- a/tempest/api/compute/__init__.py
+++ b/tempest/api/compute/__init__.py
@@ -16,7 +16,6 @@
 #    under the License.
 
 from tempest import config
-from tempest.exceptions import InvalidConfiguration
 from tempest.openstack.common import log as logging
 
 LOG = logging.getLogger(__name__)
@@ -26,29 +25,3 @@
 RESIZE_AVAILABLE = CONFIG.compute_feature_enabled.resize
 CHANGE_PASSWORD_AVAILABLE = CONFIG.compute_feature_enabled.change_password
 DISK_CONFIG_ENABLED = CONFIG.compute_feature_enabled.disk_config
-MULTI_USER = True
-
-
-# All compute tests -- single setup function
-def generic_setup_package():
-    LOG.debug("Entering tempest.api.compute.setup_package")
-
-    global MULTI_USER
-
-    # Determine if there are two regular users that can be
-    # used in testing. If the test cases are allowed to create
-    # users (config.compute.allow_tenant_isolation is true,
-    # then we allow multi-user.
-    if not CONFIG.compute.allow_tenant_isolation:
-        user1 = CONFIG.identity.username
-        user2 = CONFIG.identity.alt_username
-        if not user2 or user1 == user2:
-            MULTI_USER = False
-        else:
-            user2_password = CONFIG.identity.alt_password
-            user2_tenant_name = CONFIG.identity.alt_tenant_name
-            if not user2_password or not user2_tenant_name:
-                msg = ("Alternate user specified but not alternate "
-                       "tenant or password: alt_tenant_name=%s alt_password=%s"
-                       % (user2_tenant_name, user2_password))
-                raise InvalidConfiguration(msg)
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 3c00851..d18b749 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -17,7 +17,6 @@
 
 import time
 
-from tempest.api import compute
 from tempest import clients
 from tempest.common.utils import data_utils
 from tempest import exceptions
@@ -31,7 +30,6 @@
 class BaseComputeTest(tempest.test.BaseTestCase):
     """Base test case class for all Compute API tests."""
 
-    conclusion = compute.generic_setup_package()
     force_tenant_isolation = False
 
     @classmethod
@@ -55,6 +53,30 @@
         cls.image_ssh_password = cls.config.compute.image_ssh_password
         cls.servers = []
         cls.images = []
+        cls.multi_user = cls.get_multi_user()
+
+    @classmethod
+    def get_multi_user(cls):
+        multi_user = True
+        # Determine if there are two regular users that can be
+        # used in testing. If the test cases are allowed to create
+        # users (config.compute.allow_tenant_isolation is true,
+        # then we allow multi-user.
+        if not cls.config.compute.allow_tenant_isolation:
+            user1 = cls.config.identity.username
+            user2 = cls.config.identity.alt_username
+            if not user2 or user1 == user2:
+                multi_user = False
+            else:
+                user2_password = cls.config.identity.alt_password
+                user2_tenant_name = cls.config.identity.alt_tenant_name
+                if not user2_password or not user2_tenant_name:
+                    msg = ("Alternate user specified but not alternate "
+                           "tenant or password: alt_tenant_name=%s "
+                           "alt_password=%s"
+                           % (user2_tenant_name, user2_password))
+                    raise exceptions.InvalidConfiguration(msg)
+        return multi_user
 
     @classmethod
     def clear_servers(cls):
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index 55260bf..f7db89b 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -14,7 +14,6 @@
 #    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 import clients
 from tempest.common.utils import data_utils
@@ -36,7 +35,7 @@
 
         cls.image_ids = []
 
-        if compute.MULTI_USER:
+        if cls.multi_user:
             if cls.config.compute.allow_tenant_isolation:
                 creds = cls.isolated_creds.get_alt_creds()
                 username, tenant_name, password = creds
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index 19a308a..b0ff7ab 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -68,7 +68,7 @@
 
         cls.image_ids = []
 
-        if compute.MULTI_USER:
+        if cls.multi_user:
             if cls.config.compute.allow_tenant_isolation:
                 creds = cls.isolated_creds.get_alt_creds()
                 username, tenant_name, password = creds
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index 6757e8a..2d27b81 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -16,7 +16,6 @@
 #    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 import clients
 from tempest.common.utils import data_utils
@@ -50,7 +49,10 @@
             LOG.exception(exc)
             # Rebuild server if cannot reach the ACTIVE state
             # Usually it means the server had a serius accident
-            self.__class__.server_id = self.rebuild_server(self.server_id)
+            self._reset_server()
+
+    def _reset_server(self):
+        self.__class__.server_id = self.rebuild_server(self.server_id)
 
     @classmethod
     def setUpClass(cls):
@@ -69,7 +71,7 @@
 
         cls.image_ids = []
 
-        if compute.MULTI_USER:
+        if cls.multi_user:
             if cls.config.compute.allow_tenant_isolation:
                 creds = cls.isolated_creds.get_alt_creds()
                 username, tenant_name, password = creds
@@ -117,12 +119,12 @@
         self.assertEqual(202, resp.status)
         image_id = data_utils.parse_image_id(resp['location'])
         self.image_ids.append(image_id)
+        self.addCleanup(self._reset_server)
 
         # Create second snapshot
         alt_snapshot_name = data_utils.rand_name('test-snap-')
         self.assertRaises(exceptions.Conflict, self.client.create_image,
                           self.server_id, alt_snapshot_name)
-        self.client.wait_for_image_status(image_id, 'ACTIVE')
 
     @attr(type=['negative', 'gate'])
     def test_create_image_specify_name_over_256_chars(self):
@@ -141,6 +143,7 @@
         self.assertEqual(202, resp.status)
         image_id = data_utils.parse_image_id(resp['location'])
         self.image_ids.append(image_id)
+        self.addCleanup(self._reset_server)
 
         # Do not wait, attempt to delete the image, ensure it's successful
         resp, body = self.client.delete_image(image_id)
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index f244155..5552d0b 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -247,6 +247,7 @@
         image1_id = data_utils.parse_image_id(resp['location'])
         self.addCleanup(_clean_oldest_backup, image1_id)
         self.assertEqual(202, resp.status)
+        self.os.image_client.wait_for_image_status(image1_id, 'active')
 
         backup2 = data_utils.rand_name('backup')
         self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
@@ -257,6 +258,7 @@
         image2_id = data_utils.parse_image_id(resp['location'])
         self.addCleanup(self.os.image_client.delete_image, image2_id)
         self.assertEqual(202, resp.status)
+        self.os.image_client.wait_for_image_status(image2_id, 'active')
 
         # verify they have been created
         properties = {
@@ -296,6 +298,14 @@
         self.assertEqual((backup2, backup3),
                          (image_list[0]['name'], image_list[1]['name']))
 
+    def _get_output(self):
+        resp, output = self.servers_client.get_console_output(
+            self.server_id, 10)
+        self.assertEqual(200, resp.status)
+        self.assertTrue(output, "Console output was empty.")
+        lines = len(output.split('\n'))
+        self.assertEqual(lines, 10)
+
     @attr(type='gate')
     def test_get_console_output(self):
         # Positive test:Should be able to GET the console output
@@ -310,29 +320,24 @@
         self.assertEqual(202, resp.status)
         self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
 
-        def get_output():
-            resp, output = self.servers_client.get_console_output(
-                self.server_id, 10)
-            self.assertEqual(200, resp.status)
-            self.assertTrue(output, "Console output was empty.")
-            lines = len(output.split('\n'))
-            self.assertEqual(lines, 10)
-        self.wait_for(get_output)
+        self.wait_for(self._get_output)
 
-    @skip_because(bug="1014683")
     @attr(type='gate')
-    def test_get_console_output_server_id_in_reboot_status(self):
+    def test_get_console_output_server_id_in_shutoff_status(self):
         # Positive test:Should be able to GET the console output
-        # for a given server_id in reboot status
-        resp, output = self.servers_client.reboot(self.server_id, 'SOFT')
-        self.servers_client.wait_for_server_status(self.server_id,
-                                                   'REBOOT')
-        resp, output = self.servers_client.get_console_output(self.server_id,
-                                                              10)
-        self.assertEqual(200, resp.status)
-        self.assertIsNotNone(output)
-        lines = len(output.split('\n'))
-        self.assertEqual(lines, 10)
+        # for a given server_id in SHUTOFF status
+
+        # NOTE: SHUTOFF is irregular status. To avoid test instability,
+        #       one server is created only for this test without using
+        #       the server that was created in setupClass.
+        resp, server = self.create_test_server(wait_until='ACTIVE')
+        temp_server_id = server['id']
+
+        resp, server = self.servers_client.stop(temp_server_id)
+        self.assertEqual(202, resp.status)
+        self.servers_client.wait_for_server_status(temp_server_id, 'SHUTOFF')
+
+        self.wait_for(self._get_output)
 
     @attr(type='gate')
     def test_pause_unpause_server(self):
diff --git a/tempest/api/compute/test_authorization.py b/tempest/api/compute/test_authorization.py
index 49c4f32..327c7d1 100644
--- a/tempest/api/compute/test_authorization.py
+++ b/tempest/api/compute/test_authorization.py
@@ -15,7 +15,6 @@
 #    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 import clients
 from tempest.common.utils import data_utils
@@ -31,12 +30,10 @@
 
     @classmethod
     def setUpClass(cls):
-        if not compute.MULTI_USER:
+        super(AuthorizationTestJSON, cls).setUpClass()
+        if not cls.multi_user:
             msg = "Need >1 user"
             raise cls.skipException(msg)
-
-        super(AuthorizationTestJSON, cls).setUpClass()
-
         cls.client = cls.os.servers_client
         cls.images_client = cls.os.images_client
         cls.keypairs_client = cls.os.keypairs_client
@@ -85,7 +82,7 @@
 
     @classmethod
     def tearDownClass(cls):
-        if compute.MULTI_USER:
+        if cls.multi_user:
             cls.images_client.delete_image(cls.image['id'])
             cls.keypairs_client.delete_keypair(cls.keypairname)
             cls.security_client.delete_security_group(cls.security_group['id'])
diff --git a/tempest/api/compute/v3/images/test_images.py b/tempest/api/compute/v3/images/test_images.py
index 3aacafb..a179d65 100644
--- a/tempest/api/compute/v3/images/test_images.py
+++ b/tempest/api/compute/v3/images/test_images.py
@@ -14,7 +14,6 @@
 #    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 import clients
 from tempest.common.utils import data_utils
@@ -34,7 +33,7 @@
         cls.client = cls.images_client
         cls.servers_client = cls.servers_client
 
-        if compute.MULTI_USER:
+        if cls.multi_user:
             if cls.config.compute.allow_tenant_isolation:
                 creds = cls.isolated_creds.get_alt_creds()
                 username, tenant_name, password = creds
diff --git a/tempest/api/compute/v3/servers/test_list_servers_negative.py b/tempest/api/compute/v3/servers/test_list_servers_negative.py
index 6225345..3f7f885 100644
--- a/tempest/api/compute/v3/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_list_servers_negative.py
@@ -17,7 +17,6 @@
 
 import datetime
 
-from tempest.api import compute
 from tempest.api.compute import base
 from tempest import clients
 from tempest import exceptions
@@ -53,7 +52,7 @@
         cls.client = cls.servers_client
         cls.servers = []
 
-        if compute.MULTI_USER:
+        if cls.multi_user:
             if cls.config.compute.allow_tenant_isolation:
                 creds = cls.isolated_creds.get_alt_creds()
                 username, tenant_name, password = creds
diff --git a/tempest/api/network/common.py b/tempest/api/network/common.py
index ab19fa8..528a204 100644
--- a/tempest/api/network/common.py
+++ b/tempest/api/network/common.py
@@ -47,6 +47,9 @@
     def delete(self):
         raise NotImplemented()
 
+    def __hash__(self):
+        return id(self)
+
 
 class DeletableNetwork(DeletableResource):
 
@@ -86,6 +89,23 @@
 
 class DeletableFloatingIp(DeletableResource):
 
+    def update(self, *args, **kwargs):
+        result = self.client.update_floatingip(floatingip=self.id,
+                                               body=dict(
+                                                   floatingip=dict(*args,
+                                                                   **kwargs)
+                                               ))
+        super(DeletableFloatingIp, self).update(**result['floatingip'])
+
+    def __repr__(self):
+        return '<%s addr="%s">' % (self.__class__.__name__,
+                                   self.floating_ip_address)
+
+    def __str__(self):
+        return '<"FloatingIP" addr="%s" id="%s">' % (self.__class__.__name__,
+                                                     self.floating_ip_address,
+                                                     self.id)
+
     def delete(self):
         self.client.delete_floatingip(self.id)
 
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 3cbe23f..06e07bb 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -15,6 +15,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.test import attr
@@ -229,3 +231,22 @@
             {'network_id': self.network_cfg.public_network_id,
              'enable_snat': False})
         self._verify_gateway_port(router['id'])
+
+    @attr(type='smoke')
+    def test_update_extra_route(self):
+        self.network = self.create_network()
+        self.name = self.network['name']
+        self.subnet = self.create_subnet(self.network)
+        # Add router interface with subnet id
+        self.router = self.create_router(data_utils.rand_name('router-'), True)
+        self.create_router_interface(self.router['id'], self.subnet['id'])
+        self.addCleanup(
+            self._delete_extra_routes,
+            self.router['id'])
+        # Update router extra route
+        cidr = netaddr.IPNetwork(self.subnet['cidr'])
+        resp, extra_route = self.client.update_extra_routes(
+            self.router['id'], str(cidr[0]), str(self.subnet['cidr']))
+
+    def _delete_extra_routes(self, router_id):
+        resp, _ = self.client.delete_extra_routes(router_id)
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 497a297..44198f0 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -75,10 +75,16 @@
         timed_out = int(time.time()) - start_time >= timeout
 
         if timed_out:
-            message = ('Server %s failed to reach %s status within the '
-                       'required time (%s s).' %
-                       (server_id, status, timeout))
+            expected_task_state = 'None' if ready_wait else 'n/a'
+            message = ('Server %(server_id)s failed to reach %(status)s '
+                       'status and task state "%(expected_task_state)s" '
+                       'within the required time (%(timeout)s s).' %
+                       {'server_id': server_id,
+                        'status': status,
+                        'expected_task_state': expected_task_state,
+                        'timeout': timeout})
             message += ' Current status: %s.' % server_status
+            message += ' Current task state: %s.' % task_state
             raise exceptions.TimeoutException(message)
         old_status = server_status
         old_task_state = task_state
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 06841e1..e839d20 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -343,7 +343,11 @@
                     raise
 
             new_status = thing.status
-            if new_status == error_status:
+
+            # Some components are reporting error status in lower case
+            # so case sensitive comparisons can really mess things
+            # up.
+            if new_status.lower() == error_status.lower():
                 message = "%s failed to get to expected status. \
                           In %s state." % (thing, new_status)
                 raise exceptions.BuildErrorException(message)
@@ -537,6 +541,27 @@
         routers = self.network_client.list_routers()
         return routers['routers']
 
+    def _list_ports(self):
+        ports = self.network_client.list_ports()
+        return ports['ports']
+
+    def _get_tenant_own_network_num(self, tenant_id):
+        nets = self._list_networks()
+        ownnets = [value for value in nets if tenant_id == value['tenant_id']]
+        return len(ownnets)
+
+    def _get_tenant_own_subnet_num(self, tenant_id):
+        subnets = self._list_subnets()
+        ownsubnets = ([value for value in subnets
+                      if tenant_id == value['tenant_id']])
+        return len(ownsubnets)
+
+    def _get_tenant_own_port_num(self, tenant_id):
+        ports = self._list_ports()
+        ownports = ([value for value in ports
+                    if tenant_id == value['tenant_id']])
+        return len(ownports)
+
     def _create_subnet(self, network, namestart='subnet-smoke-'):
         """
         Create a subnet for the given network within the cidr block
@@ -603,7 +628,15 @@
         self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
         return floating_ip
 
-    def _ping_ip_address(self, ip_address):
+    def _disassociate_floating_ip(self, floating_ip):
+        """
+        :param floating_ip: type DeletableFloatingIp
+        """
+        floating_ip.update(port_id=None)
+        self.assertEqual(None, floating_ip.port_id)
+        return floating_ip
+
+    def _ping_ip_address(self, ip_address, should_succeed=True):
         cmd = ['ping', '-c1', '-w1', ip_address]
 
         def ping():
@@ -611,8 +644,7 @@
                                     stdout=subprocess.PIPE,
                                     stderr=subprocess.PIPE)
             proc.wait()
-            if proc.returncode == 0:
-                return True
+            return (proc.returncode == 0) == should_succeed
 
         return tempest.test.call_until_true(
             ping, self.config.compute.ping_timeout, 1)
@@ -624,17 +656,37 @@
                                 timeout=timeout)
         return ssh_client.test_connection_auth()
 
-    def _check_vm_connectivity(self, ip_address, username, private_key):
-        self.assertTrue(self._ping_ip_address(ip_address),
-                        "Timed out waiting for %s to become "
-                        "reachable" % ip_address)
-        self.assertTrue(self._is_reachable_via_ssh(
-            ip_address,
-            username,
-            private_key,
-            timeout=self.config.compute.ssh_timeout),
-            'Auth failure in connecting to %s@%s via ssh' %
-            (username, ip_address))
+    def _check_vm_connectivity(self, ip_address,
+                               username=None,
+                               private_key=None,
+                               should_connect=True):
+        """
+        :param ip_address: server to test against
+        :param username: server's ssh username
+        :param private_key: server's ssh private key to be used
+        :param should_connect: True/False indicates positive/negative test
+            positive - attempt ping and ssh
+            negative - attempt ping and fail if succeed
+
+        :raises: AssertError if the result of the connectivity check does
+            not match the value of the should_connect param
+        """
+        if should_connect:
+            msg = "Timed out waiting for %s to become reachable" % ip_address
+        else:
+            msg = "ip address %s is reachable" % ip_address
+        self.assertTrue(self._ping_ip_address(ip_address,
+                                              should_succeed=should_connect),
+                        msg=msg)
+        if should_connect:
+            # no need to check ssh for negative connectivity
+            self.assertTrue(self._is_reachable_via_ssh(
+                ip_address,
+                username,
+                private_key,
+                timeout=self.config.compute.ssh_timeout),
+                'Auth failure in connecting to %s@%s via ssh' %
+                (username, ip_address))
 
     def _create_security_group_nova(self, client=None,
                                     namestart='secgroup-smoke-',
@@ -801,6 +853,18 @@
 
         return rules
 
+    def _show_quota_network(self, tenant_id):
+        quota = self.network_client.show_quota(tenant_id)
+        return quota['quota']['network']
+
+    def _show_quota_subnet(self, tenant_id):
+        quota = self.network_client.show_quota(tenant_id)
+        return quota['quota']['subnet']
+
+    def _show_quota_port(self, tenant_id):
+        quota = self.network_client.show_quota(tenant_id)
+        return quota['quota']['port']
+
 
 class OrchestrationScenarioTest(OfficialClientTest):
     """
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index bfded53..d605dff 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -20,14 +20,56 @@
 from tempest.common import debug
 from tempest.common.utils import data_utils
 from tempest import config
+from tempest.openstack.common import jsonutils
 from tempest.openstack.common import log as logging
 from tempest.scenario import manager
+
+import tempest.test
 from tempest.test import attr
 from tempest.test import services
 
 LOG = logging.getLogger(__name__)
 
 
+class FloatingIPCheckTracker(object):
+    """
+    Checking VM connectivity through floating IP addresses is bound to fail
+    if the floating IP has not actually been associated with the VM yet.
+    This helper class facilitates checking for floating IP assignments on
+    VMs. It only checks for a given IP address once.
+    """
+
+    def __init__(self, compute_client, floating_ip_map):
+        self.compute_client = compute_client
+        self.unchecked = floating_ip_map.copy()
+
+    def run_checks(self):
+        """Check for any remaining unverified floating IPs
+
+        Gets VM details from nova and checks for floating IPs
+        within the returned information. Returns true when all
+        checks are complete and is suitable for use with
+        tempest.test.call_until_true()
+        """
+        to_delete = []
+        loggable_map = {}
+        for check_addr, server in self.unchecked.iteritems():
+            serverdata = self.compute_client.servers.get(server.id)
+            ip_addr = [addr for sublist in serverdata.networks.values() for
+                       addr in sublist]
+            if check_addr.floating_ip_address in ip_addr:
+                to_delete.append(check_addr)
+            else:
+                loggable_map[server.id] = check_addr
+
+        for to_del in to_delete:
+            del self.unchecked[to_del]
+
+        LOG.debug('Unchecked floating IPs: %s',
+                  jsonutils.dumps(loggable_map))
+        return len(self.unchecked) == 0
+
+
 class TestNetworkBasicOps(manager.NetworkScenarioTest):
 
     """
@@ -46,6 +88,9 @@
          ssh server hosted at the IP address.  This check guarantees
          that the IP address is associated with the target VM.
 
+       - detach the floating-ip from the VM and verify that it becomes
+       unreachable
+
        # TODO(mnewby) - Need to implement the following:
        - the Tempest host can ssh into the VM via the IP address and
          successfully execute the following:
@@ -228,36 +273,56 @@
         # key-based authentication by cloud-init.
         ssh_login = self.config.compute.image_ssh_user
         private_key = self.keypairs[self.tenant_id].private_key
-        for server in self.servers:
-            for net_name, ip_addresses in server.networks.iteritems():
-                for ip_address in ip_addresses:
-                    self._check_vm_connectivity(ip_address, ssh_login,
-                                                private_key)
+        try:
+            for server in self.servers:
+                for net_name, ip_addresses in server.networks.iteritems():
+                    for ip_address in ip_addresses:
+                        self._check_vm_connectivity(ip_address, ssh_login,
+                                                    private_key)
+        except Exception as exc:
+            LOG.exception(exc)
+            debug.log_ip_ns()
+            raise exc
 
-    def _assign_floating_ips(self):
+    def _wait_for_floating_ip_association(self):
+        ip_tracker = FloatingIPCheckTracker(self.compute_client,
+                                            self.floating_ips)
+
+        self.assertTrue(
+            tempest.test.call_until_true(
+                ip_tracker.run_checks, self.config.compute.build_timeout,
+                self.config.compute.build_interval),
+            "Timed out while waiting for the floating IP assignments "
+            "to propagate")
+
+    def _create_and_associate_floating_ips(self):
         public_network_id = self.config.network.public_network_id
         for server in self.servers:
             floating_ip = self._create_floating_ip(server, public_network_id)
-            self.floating_ips.setdefault(server, [])
-            self.floating_ips[server].append(floating_ip)
+            self.floating_ips[floating_ip] = server
 
-    def _check_public_network_connectivity(self):
+    def _check_public_network_connectivity(self, should_connect=True):
         # The target login is assumed to have been configured for
         # key-based authentication by cloud-init.
         ssh_login = self.config.compute.image_ssh_user
         private_key = self.keypairs[self.tenant_id].private_key
         try:
-            for server, floating_ips in self.floating_ips.iteritems():
-                for floating_ip in floating_ips:
-                    ip_address = floating_ip.floating_ip_address
-                    self._check_vm_connectivity(ip_address,
-                                                ssh_login,
-                                                private_key)
+            for floating_ip, server in self.floating_ips.iteritems():
+                ip_address = floating_ip.floating_ip_address
+                self._check_vm_connectivity(ip_address,
+                                            ssh_login,
+                                            private_key,
+                                            should_connect=should_connect)
         except Exception as exc:
             LOG.exception(exc)
             debug.log_ip_ns()
             raise exc
 
+    def _disassociate_floating_ips(self):
+        for floating_ip, server in self.floating_ips.iteritems():
+            self._disassociate_floating_ip(floating_ip)
+            self.floating_ips[floating_ip] = None
+
     @attr(type='smoke')
     @services('compute', 'network')
     def test_network_basic_ops(self):
@@ -266,6 +331,9 @@
         self._create_networks()
         self._check_networks()
         self._create_servers()
-        self._assign_floating_ips()
-        self._check_public_network_connectivity()
+        self._create_and_associate_floating_ips()
+        self._wait_for_floating_ip_association()
         self._check_tenant_network_connectivity()
+        self._check_public_network_connectivity(should_connect=True)
+        self._disassociate_floating_ips()
+        self._check_public_network_connectivity(should_connect=False)
diff --git a/tempest/scenario/test_network_quotas.py b/tempest/scenario/test_network_quotas.py
index 3268066..cb7aa0b 100644
--- a/tempest/scenario/test_network_quotas.py
+++ b/tempest/scenario/test_network_quotas.py
@@ -20,8 +20,6 @@
 from tempest.scenario.manager import NetworkScenarioTest
 from tempest.test import services
 
-MAX_REASONABLE_ITERATIONS = 51  # more than enough. Default for port is 50.
-
 
 class TestNetworkQuotaBasic(NetworkScenarioTest):
     """
@@ -46,7 +44,9 @@
     @services('network')
     def test_create_network_until_quota_hit(self):
         hit_limit = False
-        for n in xrange(MAX_REASONABLE_ITERATIONS):
+        networknum = self._get_tenant_own_network_num(self.tenant_id)
+        max = self._show_quota_network(self.tenant_id) - networknum
+        for n in xrange(max):
             try:
                 self.networks.append(
                     self._create_network(self.tenant_id,
@@ -56,6 +56,16 @@
                     raise
                 hit_limit = True
                 break
+        self.assertFalse(hit_limit, "Failed: Hit quota limit !")
+
+        try:
+            self.networks.append(
+                self._create_network(self.tenant_id,
+                                     namestart='network-quotatest-'))
+        except exc.NeutronClientException as e:
+            if (e.status_code != 409):
+                raise
+            hit_limit = True
         self.assertTrue(hit_limit, "Failed: Did not hit quota limit !")
 
     @services('network')
@@ -65,7 +75,9 @@
                 self._create_network(self.tenant_id,
                                      namestart='network-quotatest-'))
         hit_limit = False
-        for n in xrange(MAX_REASONABLE_ITERATIONS):
+        subnetnum = self._get_tenant_own_subnet_num(self.tenant_id)
+        max = self._show_quota_subnet(self.tenant_id) - subnetnum
+        for n in xrange(max):
             try:
                 self.subnets.append(
                     self._create_subnet(self.networks[0],
@@ -75,6 +87,16 @@
                     raise
                 hit_limit = True
                 break
+        self.assertFalse(hit_limit, "Failed: Hit quota limit !")
+
+        try:
+            self.subnets.append(
+                self._create_subnet(self.networks[0],
+                                    namestart='subnet-quotatest-'))
+        except exc.NeutronClientException as e:
+            if (e.status_code != 409):
+                raise
+            hit_limit = True
         self.assertTrue(hit_limit, "Failed: Did not hit quota limit !")
 
     @services('network')
@@ -84,7 +106,9 @@
                 self._create_network(self.tenant_id,
                                      namestart='network-quotatest-'))
         hit_limit = False
-        for n in xrange(MAX_REASONABLE_ITERATIONS):
+        portnum = self._get_tenant_own_port_num(self.tenant_id)
+        max = self._show_quota_port(self.tenant_id) - portnum
+        for n in xrange(max):
             try:
                 self.ports.append(
                     self._create_port(self.networks[0],
@@ -94,4 +118,14 @@
                     raise
                 hit_limit = True
                 break
+        self.assertFalse(hit_limit, "Failed: Hit quota limit !")
+
+        try:
+            self.ports.append(
+                self._create_port(self.networks[0],
+                                  namestart='port-quotatest-'))
+        except exc.NeutronClientException as e:
+            if (e.status_code != 409):
+                raise
+            hit_limit = True
         self.assertTrue(hit_limit, "Failed: Did not hit quota limit !")
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index c6bd423..f5fb2bd 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -754,3 +754,29 @@
         resp, body = self.put(uri, body=body, headers=self.headers)
         body = json.loads(body)
         return resp, body
+
+    def update_extra_routes(self, router_id, nexthop, destination):
+        uri = '%s/routers/%s' % (self.uri_prefix, router_id)
+        put_body = {
+            'router': {
+                'routes': [{'nexthop': nexthop,
+                            "destination": destination}]
+            }
+        }
+        body = json.dumps(put_body)
+        resp, body = self.put(uri, body=body, headers=self.headers)
+        body = json.loads(body)
+        return resp, body
+
+    def delete_extra_routes(self, router_id):
+        uri = '%s/routers/%s' % (self.uri_prefix, router_id)
+        null_routes = None
+        put_body = {
+            'router': {
+                'routes': null_routes
+            }
+        }
+        body = json.dumps(put_body)
+        resp, body = self.put(uri, body=body, headers=self.headers)
+        body = json.loads(body)
+        return resp, body
diff --git a/tempest/tests/test_wrappers.py b/tempest/tests/test_wrappers.py
index dbf1809..88bef9b 100644
--- a/tempest/tests/test_wrappers.py
+++ b/tempest/tests/test_wrappers.py
@@ -56,7 +56,7 @@
         # version or an sdist to work. so make the test directory a git repo
         # too.
         subprocess.call(['git', 'init'])
-        exit_code = subprocess.call('sh pretty_tox.sh tests.passing',
+        exit_code = subprocess.call('bash pretty_tox.sh tests.passing',
                                     shell=True, stdout=DEVNULL, stderr=DEVNULL)
         self.assertEqual(exit_code, 0)
 
@@ -71,7 +71,7 @@
         # version or an sdist to work. so make the test directory a git repo
         # too.
         subprocess.call(['git', 'init'])
-        exit_code = subprocess.call('sh pretty_tox.sh', shell=True,
+        exit_code = subprocess.call('bash pretty_tox.sh', shell=True,
                                     stdout=DEVNULL, stderr=DEVNULL)
         self.assertEqual(exit_code, 1)
 
@@ -82,7 +82,7 @@
         # Change directory, run wrapper and check result
         self.addCleanup(os.chdir, os.path.abspath(os.curdir))
         os.chdir(self.directory)
-        exit_code = subprocess.call('sh pretty_tox_serial.sh tests.passing',
+        exit_code = subprocess.call('bash pretty_tox_serial.sh tests.passing',
                                     shell=True, stdout=DEVNULL, stderr=DEVNULL)
         self.assertEqual(exit_code, 0)
 
@@ -93,6 +93,6 @@
         # Change directory, run wrapper and check result
         self.addCleanup(os.chdir, os.path.abspath(os.curdir))
         os.chdir(self.directory)
-        exit_code = subprocess.call('sh pretty_tox_serial.sh', shell=True,
+        exit_code = subprocess.call('bash pretty_tox_serial.sh', shell=True,
                                     stdout=DEVNULL, stderr=DEVNULL)
         self.assertEqual(exit_code, 1)
diff --git a/tools/check_logs.py b/tools/check_logs.py
index 6d4436e..963709b 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -27,11 +27,13 @@
 
 
 is_neutron = os.environ.get('DEVSTACK_GATE_NEUTRON', "0") == "1"
+is_grenade = (os.environ.get('DEVSTACK_GATE_GRENADE', "0") == "1" or
+              os.environ.get('DEVSTACK_GATE_GRENADE_FORWARD', "0") == "1")
 dump_all_errors = is_neutron
 
 
 def process_files(file_specs, url_specs, whitelists):
-    regexp = re.compile(r"^.*(ERROR|CRITICAL).*\[.*\-.*\]")
+    regexp = re.compile(r"^.* (ERROR|CRITICAL) .*\[.*\-.*\]")
     had_errors = False
     for (name, filename) in file_specs:
         whitelist = whitelists.get(name, [])
@@ -125,6 +127,9 @@
         if is_neutron:
             print("Currently not failing neutron builds with errors")
             return 0
+        if is_grenade:
+            print("Currently not failing grenade runs with errors")
+            return 0
         print("FAILED")
         return 1
     else:
diff --git a/tools/pretty_tox.sh b/tools/pretty_tox.sh
index a5a6076..07c35a0 100755
--- a/tools/pretty_tox.sh
+++ b/tools/pretty_tox.sh
@@ -1,4 +1,6 @@
-#!/bin/sh
+#!/usr/bin/env bash
+
+set -o pipefail
 
 TESTRARGS=$1
 python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | subunit2pyunit
diff --git a/tools/pretty_tox_serial.sh b/tools/pretty_tox_serial.sh
index 45f05bd..42ce760 100755
--- a/tools/pretty_tox_serial.sh
+++ b/tools/pretty_tox_serial.sh
@@ -1,4 +1,6 @@
-#!/bin/sh
+#!/usr/bin/env bash
+
+set -o pipefail
 
 TESTRARGS=$@
 
diff --git a/tox.ini b/tox.ini
index 9389cf4..c7f92ae 100644
--- a/tox.ini
+++ b/tox.ini
@@ -12,12 +12,15 @@
 install_command = pip install -U {opts} {packages}
 
 [testenv:py26]
+setenv = OS_TEST_PATH=./tempest/tests
 commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
 
 [testenv:py33]
+setenv = OS_TEST_PATH=./tempest/tests
 commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
 
 [testenv:py27]
+setenv = OS_TEST_PATH=./tempest/tests
 commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
 
 [testenv:all]
@@ -31,19 +34,19 @@
 # The regex below is used to select which tests to run and exclude the slow tag:
 # See the testrepostiory bug: https://bugs.launchpad.net/testrepository/+bug/1208610
 commands =
-  sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
+  bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
 
 [testenv:testr-full]
 sitepackages = True
 commands =
-  sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
+  bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
 
 [testenv:heat-slow]
 sitepackages = True
 setenv = OS_TEST_TIMEOUT=1200
 # The regex below is used to select heat api/scenario tests tagged as slow.
 commands =
-  sh tools/pretty_tox_serial.sh '(?=.*\[.*\bslow\b.*\])(^tempest\.(api|scenario)\.orchestration) {posargs}'
+  bash tools/pretty_tox_serial.sh '(?=.*\[.*\bslow\b.*\])(^tempest\.(api|scenario)\.orchestration) {posargs}'
 
 [testenv:large-ops]
 sitepackages = True
@@ -79,7 +82,7 @@
 [testenv:smoke]
 sitepackages = True
 commands =
-   sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
+   bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
 
 [testenv:smoke-serial]
 sitepackages = True
@@ -87,7 +90,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 '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
+   bash tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
 
 [testenv:stress]
 sitepackages = True