Merge "Adds test for list/get volume attachments V2 APIs"
diff --git a/HACKING.rst b/HACKING.rst
index 83d67a9..fd63d64 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -260,15 +260,15 @@
 example of this would be::
 
     class TestVolumeBootPattern(manager.ScenarioTest):
-    """
-    This test case attempts to reproduce the following steps:
+        """
+        This test case attempts to reproduce the following steps:
 
-     * Create in Cinder some bootable volume importing a Glance image
-     * Boot an instance from the bootable volume
-     * Write content to the volume
-     * Delete an instance and Boot a new instance from the volume
-     * Check written content in the instance
-     * Create a volume snapshot while the instance is running
-     * Boot an additional instance from the new snapshot based volume
-     * Check written content in the instance booted from snapshot
-    """
+         * Create in Cinder some bootable volume importing a Glance image
+         * Boot an instance from the bootable volume
+         * Write content to the volume
+         * Delete an instance and Boot a new instance from the volume
+         * Check written content in the instance
+         * Create a volume snapshot while the instance is running
+         * Boot an additional instance from the new snapshot based volume
+         * Check written content in the instance booted from snapshot
+        """
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 1cccacc..9a9952d 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -802,6 +802,9 @@
 # attributes ipv6_ra_mode and ipv6_address_mode (boolean value)
 #ipv6_subnet_attributes = false
 
+# If false, skip all network api tests with xml (boolean value)
+#xml_api = false
+
 
 [object-storage]
 
diff --git a/requirements.txt b/requirements.txt
index 708ede3..ac72017 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -27,3 +27,4 @@
 iso8601>=0.1.9
 fixtures>=0.3.14
 testscenarios>=0.4
+tempest-lib
diff --git a/tempest/api/baremetal/admin/test_nodes.py b/tempest/api/baremetal/admin/test_nodes.py
index 8ccd36b..41c12c6 100644
--- a/tempest/api/baremetal/admin/test_nodes.py
+++ b/tempest/api/baremetal/admin/test_nodes.py
@@ -130,9 +130,7 @@
 
     @test.attr(type='smoke')
     def test_set_node_boot_device(self):
-        body = self.client.set_node_boot_device(self.node['uuid'], 'pxe')
-        # No content
-        self.assertEqual('', body)
+        self.client.set_node_boot_device(self.node['uuid'], 'pxe')
 
     @test.attr(type='smoke')
     def test_get_node_boot_device(self):
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index d365f3a..3307159 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -296,7 +296,7 @@
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
         new_flavor_id = data_utils.rand_int_id(start=1000)
 
-        ram = " 1024 "
+        ram = "1024"
         resp, flavor = self.client.create_flavor(flavor_name,
                                                  ram, self.vcpus,
                                                  self.disk,
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 6507ce1..2f53a0b 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -72,6 +72,8 @@
             cls.quotas_client = cls.os.quotas_client
             # NOTE(mriedem): os-quota-class-sets is v2 API only
             cls.quota_classes_client = cls.os.quota_classes_client
+            # NOTE(mriedem): os-networks is v2 API only
+            cls.networks_client = cls.os.networks_client
             cls.limits_client = cls.os.limits_client
             cls.volumes_extensions_client = cls.os.volumes_extensions_client
             cls.volumes_client = cls.os.volumes_client
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index bc452aa..d954c01 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -128,6 +128,9 @@
     @testtools.skipUnless(CONF.service_available.neutron,
                           'Neutron service must be available.')
     def test_verify_multiple_nics_order(self):
+        if getattr(self, '_interface',
+                   None) == 'xml' and not CONF.network_feature_enabled.xml_api:
+            raise self.skipException('Neutron XML API is not enabled')
         # Verify that the networks order given at the server creation is
         # preserved within the server.
         name_net1 = data_utils.rand_name(self.__class__.__name__)
diff --git a/tempest/api/compute/test_networks.py b/tempest/api/compute/test_networks.py
new file mode 100644
index 0000000..86779b3
--- /dev/null
+++ b/tempest/api/compute/test_networks.py
@@ -0,0 +1,33 @@
+# Copyright 2014 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.compute import base
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class NetworksTestJSON(base.BaseV2ComputeTest):
+    @classmethod
+    def resource_setup(cls):
+        if CONF.service_available.neutron:
+            raise cls.skipException('nova-network is not available.')
+        super(NetworksTestJSON, cls).resource_setup()
+        cls.client = cls.os.networks_client
+
+    @test.attr(type='gate')
+    def test_list_networks(self):
+        _, networks = self.client.list_networks()
+        self.assertNotEmpty(networks, "No networks found.")
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index f1f1eb6..676f101 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -81,8 +81,7 @@
         fetched_endpoints_id = [e['id'] for e in fetched_endpoints]
         self.assertIn(endpoint['id'], fetched_endpoints_id)
         # Deleting the endpoint created in this method
-        _, body = self.client.delete_endpoint(endpoint['id'])
-        self.assertEqual(body, '')
+        self.client.delete_endpoint(endpoint['id'])
         # Checking whether endpoint is deleted successfully
         resp, fetched_endpoints = self.client.list_endpoints()
         fetched_endpoints_id = [e['id'] for e in fetched_endpoints]
diff --git a/tempest/api/messaging/test_queues.py b/tempest/api/messaging/test_queues.py
index 8f9ac20..accbd17 100644
--- a/tempest/api/messaging/test_queues.py
+++ b/tempest/api/messaging/test_queues.py
@@ -20,6 +20,7 @@
 
 from tempest.api.messaging import base
 from tempest.common.utils import data_utils
+from tempest import exceptions
 from tempest import test
 
 
@@ -29,15 +30,22 @@
 class TestQueues(base.BaseMessagingTest):
 
     @test.attr(type='smoke')
-    def test_create_queue(self):
-        # Create Queue
+    def test_create_delete_queue(self):
+        # Create & Delete Queue
         queue_name = data_utils.rand_name('test-')
         _, body = self.create_queue(queue_name)
 
         self.addCleanup(self.client.delete_queue, queue_name)
-
+        # NOTE(gmann): create_queue returns response status code as 201
+        # so specifically checking the expected empty response body as
+        # this is not going to be checked in response_checker().
         self.assertEqual('', body)
 
+        self.delete_queue(queue_name)
+        self.assertRaises(exceptions.NotFound,
+                          self.client.get_queue,
+                          queue_name)
+
 
 class TestManageQueue(base.BaseMessagingTest):
     _interface = 'json'
@@ -53,25 +61,16 @@
             cls.client.create_queue(queue_name)
 
     @test.attr(type='smoke')
-    def test_delete_queue(self):
-        # Delete Queue
-        queue_name = self.queues.pop()
-        _, body = self.delete_queue(queue_name)
-        self.assertEqual('', body)
-
-    @test.attr(type='smoke')
     def test_check_queue_existence(self):
         # Checking Queue Existence
         for queue_name in self.queues:
-            _, body = self.check_queue_exists(queue_name)
-            self.assertEqual('', body)
+            self.check_queue_exists(queue_name)
 
     @test.attr(type='smoke')
     def test_check_queue_head(self):
         # Checking Queue Existence by calling HEAD
         for queue_name in self.queues:
-            _, body = self.check_queue_exists_head(queue_name)
-            self.assertEqual('', body)
+            self.check_queue_exists_head(queue_name)
 
     @test.attr(type='smoke')
     def test_list_queues(self):
@@ -111,8 +110,8 @@
         req_body = dict()
         req_body[data_utils.rand_name('key1')] = req_body1
         # Set Queue Metadata
-        _, body = self.set_queue_metadata(queue_name, req_body)
-        self.assertEqual('', body)
+        self.set_queue_metadata(queue_name, req_body)
+
         # Get Queue Metadata
         _, body = self.get_queue_metadata(queue_name)
         self.assertThat(body, matchers.Equals(req_body))
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index 91e3e14..7ba68f7 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -62,6 +62,9 @@
         super(BaseNetworkTest, cls).resource_setup()
         if not CONF.service_available.neutron:
             raise cls.skipException("Neutron support is required")
+        if getattr(cls, '_interface', None) == 'xml':
+            if not CONF.network_feature_enabled.xml_api:
+                raise cls.skipException('XML API is not enabled')
 
         os = cls.get_client_manager()
 
@@ -152,33 +155,35 @@
         return network
 
     @classmethod
-    def create_subnet(cls, network, gateway=None, cidr=None, mask_bits=None,
-                      **kwargs):
+    def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
+                      ip_version=None, **kwargs):
         """Wrapper utility that returns a test subnet."""
         # The cidr and mask_bits depend on the ip version.
-        if cls._ip_version == 4:
+        ip_version = ip_version if ip_version is not None else cls._ip_version
+        gateway_not_set = gateway == ''
+        if ip_version == 4:
             cidr = cidr or netaddr.IPNetwork(CONF.network.tenant_network_cidr)
             mask_bits = mask_bits or CONF.network.tenant_network_mask_bits
-        elif cls._ip_version == 6:
+        elif ip_version == 6:
             cidr = (
                 cidr or netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr))
             mask_bits = mask_bits or CONF.network.tenant_network_v6_mask_bits
         # Find a cidr that is not in use yet and create a subnet with it
         for subnet_cidr in cidr.subnet(mask_bits):
-            if not gateway:
-                gateway = str(netaddr.IPAddress(subnet_cidr) + 1)
+            if gateway_not_set:
+                gateway_ip = str(netaddr.IPAddress(subnet_cidr) + 1)
+            else:
+                gateway_ip = gateway
             try:
                 resp, body = cls.client.create_subnet(
                     network_id=network['id'],
                     cidr=str(subnet_cidr),
-                    ip_version=cls._ip_version,
-                    gateway_ip=gateway,
+                    ip_version=ip_version,
+                    gateway_ip=gateway_ip,
                     **kwargs)
                 break
             except exceptions.BadRequest as e:
                 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
-                # Unset gateway value if there is an overlapping subnet
-                gateway = None
                 if not is_overlapping_cidr:
                     raise
         else:
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 986a2c8..dd81a09 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -280,6 +280,10 @@
         self.subnets.pop()
 
     @test.attr(type='smoke')
+    def test_create_delete_subnet_without_gateway(self):
+        self._create_verify_delete_subnet()
+
+    @test.attr(type='smoke')
     def test_create_delete_subnet_with_gw(self):
         self._create_verify_delete_subnet(
             **self.subnet_dict(['gateway']))
@@ -492,7 +496,7 @@
         self.assertEqual(subnet['gateway_ip'], gateway)
 
     @test.attr(type='smoke')
-    def test_create_delete_subnet_without_gw(self):
+    def test_create_delete_subnet_with_default_gw(self):
         net = netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr)
         gateway_ip = str(netaddr.IPAddress(net.first + 1))
         name = data_utils.rand_name('network-')
@@ -501,16 +505,62 @@
         # Verifies Subnet GW in IPv6
         self.assertEqual(subnet['gateway_ip'], gateway_ip)
 
+    @test.attr(type='smoke')
+    def test_create_list_subnet_with_no_gw64_one_network(self):
+        name = data_utils.rand_name('network-')
+        network = self.create_network(name)
+        ipv6_gateway = self.subnet_dict(['gateway'])['gateway']
+        subnet1 = self.create_subnet(network,
+                                     ip_version=6,
+                                     gateway=ipv6_gateway)
+        self.assertEqual(netaddr.IPNetwork(subnet1['cidr']).version, 6,
+                         'The created subnet is not IPv6')
+        subnet2 = self.create_subnet(network,
+                                     gateway=None,
+                                     ip_version=4)
+        self.assertEqual(netaddr.IPNetwork(subnet2['cidr']).version, 4,
+                         'The created subnet is not IPv4')
+        # Verifies Subnet GW is set in IPv6
+        self.assertEqual(subnet1['gateway_ip'], ipv6_gateway)
+        # Verifies Subnet GW is None in IPv4
+        self.assertEqual(subnet2['gateway_ip'], None)
+        # Verifies all 2 subnets in the same network
+        _, body = self.client.list_subnets()
+        subnets = [sub['id'] for sub in body['subnets']
+                   if sub['network_id'] == network['id']]
+        test_subnet_ids = [sub['id'] for sub in (subnet1, subnet2)]
+        self.assertItemsEqual(subnets,
+                              test_subnet_ids,
+                              'Subnet are not in the same network')
+
     @testtools.skipUnless(CONF.network_feature_enabled.ipv6_subnet_attributes,
                           "IPv6 extended attributes for subnets not "
                           "available")
     @test.attr(type='smoke')
-    def test_create_delete_subnet_with_v6_attributes(self):
+    def test_create_delete_subnet_with_v6_attributes_stateful(self):
         self._create_verify_delete_subnet(
             gateway=self._subnet_data[self._ip_version]['gateway'],
+            ipv6_ra_mode='dhcpv6-stateful',
+            ipv6_address_mode='dhcpv6-stateful')
+
+    @testtools.skipUnless(CONF.network_feature_enabled.ipv6_subnet_attributes,
+                          "IPv6 extended attributes for subnets not "
+                          "available")
+    @test.attr(type='smoke')
+    def test_create_delete_subnet_with_v6_attributes_slaac(self):
+        self._create_verify_delete_subnet(
             ipv6_ra_mode='slaac',
             ipv6_address_mode='slaac')
 
+    @testtools.skipUnless(CONF.network_feature_enabled.ipv6_subnet_attributes,
+                          "IPv6 extended attributes for subnets not "
+                          "available")
+    @test.attr(type='smoke')
+    def test_create_delete_subnet_with_v6_attributes_stateless(self):
+        self._create_verify_delete_subnet(
+            ipv6_ra_mode='dhcpv6-stateless',
+            ipv6_address_mode='dhcpv6-stateless')
+
 
 class NetworksIpV6TestXML(NetworksIpV6TestJSON):
     _interface = 'xml'
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index ca6d7fe..8dd2df2 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -14,46 +14,20 @@
 #    under the License.
 
 import functools
-import os
-import shlex
-import subprocess
 
+from tempest_lib.cli import base
 import testtools
 
-import tempest.cli.output_parser
+from tempest.common import credentials
 from tempest import config
 from tempest import exceptions
-from tempest.openstack.common import log as logging
 from tempest.openstack.common import versionutils
-import tempest.test
+from tempest import test
 
 
-LOG = logging.getLogger(__name__)
-
 CONF = config.CONF
 
 
-def execute(cmd, action, flags='', params='', fail_ok=False,
-            merge_stderr=False):
-    """Executes specified command for the given action."""
-    cmd = ' '.join([os.path.join(CONF.cli.cli_dir, cmd),
-                    flags, action, params])
-    LOG.info("running: '%s'" % cmd)
-    cmd = shlex.split(cmd.encode('utf-8'))
-    result = ''
-    result_err = ''
-    stdout = subprocess.PIPE
-    stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
-    proc = subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
-    result, result_err = proc.communicate()
-    if not fail_ok and proc.returncode != 0:
-        raise exceptions.CommandFailed(proc.returncode,
-                                       cmd,
-                                       result,
-                                       result_err)
-    return result
-
-
 def check_client_version(client, version):
     """Checks if the client's version is compatible with the given version
 
@@ -62,8 +36,8 @@
     @return: True if the client version is compatible with the given version
              parameter, False otherwise.
     """
-    current_version = execute(client, '', params='--version',
-                              merge_stderr=True)
+    current_version = base.execute(client, '', params='--version',
+                                   merge_stderr=True, cli_dir=CONF.cli.cli_dir)
 
     if not current_version.strip():
         raise exceptions.TempestException('"%s --version" output was empty' %
@@ -92,100 +66,19 @@
     return decorator
 
 
-class ClientTestBase(tempest.test.BaseTestCase):
+class ClientTestBase(base.ClientTestBase, test.BaseTestCase):
     @classmethod
     def resource_setup(cls):
         if not CONF.cli.enabled:
             msg = "cli testing disabled"
             raise cls.skipException(msg)
         super(ClientTestBase, cls).resource_setup()
+        cls.cred_prov = credentials.get_isolated_credentials(cls.__name__)
+        cls.creds = cls.cred_prov.get_admin_creds()
 
-    def __init__(self, *args, **kwargs):
-        self.parser = tempest.cli.output_parser
-        super(ClientTestBase, self).__init__(*args, **kwargs)
-
-    def nova(self, action, flags='', params='', admin=True, fail_ok=False):
-        """Executes nova command for the given action."""
-        flags += ' --endpoint-type %s' % CONF.compute.endpoint_type
-        return self.cmd_with_auth(
-            'nova', action, flags, params, admin, fail_ok)
-
-    def nova_manage(self, action, flags='', params='', fail_ok=False,
-                    merge_stderr=False):
-        """Executes nova-manage command for the given action."""
-        return execute(
-            'nova-manage', action, flags, params, fail_ok, merge_stderr)
-
-    def keystone(self, action, flags='', params='', admin=True, fail_ok=False):
-        """Executes keystone command for the given action."""
-        return self.cmd_with_auth(
-            'keystone', action, flags, params, admin, fail_ok)
-
-    def glance(self, action, flags='', params='', admin=True, fail_ok=False):
-        """Executes glance command for the given action."""
-        flags += ' --os-endpoint-type %s' % CONF.image.endpoint_type
-        return self.cmd_with_auth(
-            'glance', action, flags, params, admin, fail_ok)
-
-    def ceilometer(self, action, flags='', params='', admin=True,
-                   fail_ok=False):
-        """Executes ceilometer command for the given action."""
-        flags += ' --os-endpoint-type %s' % CONF.telemetry.endpoint_type
-        return self.cmd_with_auth(
-            'ceilometer', action, flags, params, admin, fail_ok)
-
-    def heat(self, action, flags='', params='', admin=True,
-             fail_ok=False):
-        """Executes heat command for the given action."""
-        flags += ' --os-endpoint-type %s' % CONF.orchestration.endpoint_type
-        return self.cmd_with_auth(
-            'heat', action, flags, params, admin, fail_ok)
-
-    def cinder(self, action, flags='', params='', admin=True, fail_ok=False):
-        """Executes cinder command for the given action."""
-        flags += ' --endpoint-type %s' % CONF.volume.endpoint_type
-        return self.cmd_with_auth(
-            'cinder', action, flags, params, admin, fail_ok)
-
-    def swift(self, action, flags='', params='', admin=True, fail_ok=False):
-        """Executes swift command for the given action."""
-        flags += ' --os-endpoint-type %s' % CONF.object_storage.endpoint_type
-        return self.cmd_with_auth(
-            'swift', action, flags, params, admin, fail_ok)
-
-    def neutron(self, action, flags='', params='', admin=True, fail_ok=False):
-        """Executes neutron command for the given action."""
-        flags += ' --endpoint-type %s' % CONF.network.endpoint_type
-        return self.cmd_with_auth(
-            'neutron', action, flags, params, admin, fail_ok)
-
-    def sahara(self, action, flags='', params='', admin=True,
-               fail_ok=False, merge_stderr=True):
-        """Executes sahara command for the given action."""
-        flags += ' --endpoint-type %s' % CONF.data_processing.endpoint_type
-        return self.cmd_with_auth(
-            'sahara', action, flags, params, admin, fail_ok, merge_stderr)
-
-    def cmd_with_auth(self, cmd, action, flags='', params='',
-                      admin=True, fail_ok=False, merge_stderr=False):
-        """Executes given command with auth attributes appended."""
-        # TODO(jogo) make admin=False work
-        creds = ('--os-username %s --os-tenant-name %s --os-password %s '
-                 '--os-auth-url %s' %
-                 (CONF.identity.admin_username,
-                  CONF.identity.admin_tenant_name,
-                  CONF.identity.admin_password,
-                  CONF.identity.uri))
-        flags = creds + ' ' + flags
-        return execute(cmd, action, flags, params, fail_ok, merge_stderr)
-
-    def assertTableStruct(self, items, field_names):
-        """Verify that all items has keys listed in field_names."""
-        for item in items:
-            for field in field_names:
-                self.assertIn(field, item)
-
-    def assertFirstLineStartsWith(self, lines, beginning):
-        self.assertTrue(lines[0].startswith(beginning),
-                        msg=('Beginning of first line has invalid content: %s'
-                             % lines[:3]))
+    def _get_clients(self):
+        clients = base.CLIClient(self.creds.username,
+                                 self.creds.password,
+                                 self.creds.tenant_name,
+                                 CONF.identity.uri, CONF.cli.cli_dir)
+        return clients
diff --git a/tempest/cli/output_parser.py b/tempest/cli/output_parser.py
deleted file mode 100644
index 80234a3..0000000
--- a/tempest/cli/output_parser.py
+++ /dev/null
@@ -1,171 +0,0 @@
-# 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.
-
-"""Collection of utilities for parsing CLI clients output."""
-
-import re
-
-from tempest import exceptions
-from tempest.openstack.common import log as logging
-
-
-LOG = logging.getLogger(__name__)
-
-
-delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
-
-
-def details_multiple(output_lines, with_label=False):
-    """Return list of dicts with item details from cli output tables.
-
-    If with_label is True, key '__label' is added to each items dict.
-    For more about 'label' see OutputParser.tables().
-    """
-    items = []
-    tables_ = tables(output_lines)
-    for table_ in tables_:
-        if 'Property' not in table_['headers'] \
-           or 'Value' not in table_['headers']:
-            raise exceptions.InvalidStructure()
-        item = {}
-        for value in table_['values']:
-            item[value[0]] = value[1]
-        if with_label:
-            item['__label'] = table_['label']
-        items.append(item)
-    return items
-
-
-def details(output_lines, with_label=False):
-    """Return dict with details of first item (table) found in output."""
-    items = details_multiple(output_lines, with_label)
-    return items[0]
-
-
-def listing(output_lines):
-    """Return list of dicts with basic item info parsed from cli output.
-    """
-
-    items = []
-    table_ = table(output_lines)
-    for row in table_['values']:
-        item = {}
-        for col_idx, col_key in enumerate(table_['headers']):
-            item[col_key] = row[col_idx]
-        items.append(item)
-    return items
-
-
-def tables(output_lines):
-    """Find all ascii-tables in output and parse them.
-
-    Return list of tables parsed from cli output as dicts.
-    (see OutputParser.table())
-
-    And, if found, label key (separated line preceding the table)
-    is added to each tables dict.
-    """
-    tables_ = []
-
-    table_ = []
-    label = None
-
-    start = False
-    header = False
-
-    if not isinstance(output_lines, list):
-        output_lines = output_lines.split('\n')
-
-    for line in output_lines:
-        if delimiter_line.match(line):
-            if not start:
-                start = True
-            elif not header:
-                # we are after head area
-                header = True
-            else:
-                # table ends here
-                start = header = None
-                table_.append(line)
-
-                parsed = table(table_)
-                parsed['label'] = label
-                tables_.append(parsed)
-
-                table_ = []
-                label = None
-                continue
-        if start:
-            table_.append(line)
-        else:
-            if label is None:
-                label = line
-            else:
-                LOG.warn('Invalid line between tables: %s' % line)
-    if len(table_) > 0:
-        LOG.warn('Missing end of table')
-
-    return tables_
-
-
-def table(output_lines):
-    """Parse single table from cli output.
-
-    Return dict with list of column names in 'headers' key and
-    rows in 'values' key.
-    """
-    table_ = {'headers': [], 'values': []}
-    columns = None
-
-    if not isinstance(output_lines, list):
-        output_lines = output_lines.split('\n')
-
-    if not output_lines[-1]:
-        # skip last line if empty (just newline at the end)
-        output_lines = output_lines[:-1]
-
-    for line in output_lines:
-        if delimiter_line.match(line):
-            columns = _table_columns(line)
-            continue
-        if '|' not in line:
-            LOG.warn('skipping invalid table line: %s' % line)
-            continue
-        row = []
-        for col in columns:
-            row.append(line[col[0]:col[1]].strip())
-        if table_['headers']:
-            table_['values'].append(row)
-        else:
-            table_['headers'] = row
-
-    return table_
-
-
-def _table_columns(first_table_row):
-    """Find column ranges in output line.
-
-    Return list of tuples (start,end) for each column
-    detected by plus (+) characters in delimiter line.
-    """
-    positions = []
-    start = 1  # there is '+' at 0
-    while start < len(first_table_row):
-        end = first_table_row.find('+', start)
-        if end == -1:
-            break
-        positions.append((start, end))
-        start = end + 1
-    return positions
diff --git a/tempest/cli/simple_read_only/compute/test_nova.py b/tempest/cli/simple_read_only/compute/test_nova.py
index 6e5e077..4fe4982 100644
--- a/tempest/cli/simple_read_only/compute/test_nova.py
+++ b/tempest/cli/simple_read_only/compute/test_nova.py
@@ -13,11 +13,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest_lib import exceptions
 import testtools
 
 from tempest import cli
 from tempest import config
-from tempest import exceptions
 from tempest.openstack.common import log as logging
 import tempest.test
 
@@ -47,6 +47,11 @@
             raise cls.skipException(msg)
         super(SimpleReadOnlyNovaClientTest, cls).resource_setup()
 
+    def nova(self, *args, **kwargs):
+        return self.clients.nova(*args,
+                                 endpoint_type=CONF.compute.endpoint_type,
+                                 **kwargs)
+
     def test_admin_fake_action(self):
         self.assertRaises(exceptions.CommandFailed,
                           self.nova,
diff --git a/tempest/cli/simple_read_only/compute/test_nova_manage.py b/tempest/cli/simple_read_only/compute/test_nova_manage.py
index cff543f..34ec671 100644
--- a/tempest/cli/simple_read_only/compute/test_nova_manage.py
+++ b/tempest/cli/simple_read_only/compute/test_nova_manage.py
@@ -13,9 +13,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest_lib import exceptions
+
 from tempest import cli
 from tempest import config
-from tempest import exceptions
 from tempest.openstack.common import log as logging
 
 
@@ -46,6 +47,9 @@
             raise cls.skipException(msg)
         super(SimpleReadOnlyNovaManageTest, cls).resource_setup()
 
+    def nova_manage(self, *args, **kwargs):
+        return self.clients.nova_manage(*args, **kwargs)
+
     def test_admin_fake_action(self):
         self.assertRaises(exceptions.CommandFailed,
                           self.nova_manage,
diff --git a/tempest/cli/simple_read_only/data_processing/test_sahara.py b/tempest/cli/simple_read_only/data_processing/test_sahara.py
index 751a4ad..1f2403c 100644
--- a/tempest/cli/simple_read_only/data_processing/test_sahara.py
+++ b/tempest/cli/simple_read_only/data_processing/test_sahara.py
@@ -15,9 +15,10 @@
 import logging
 import re
 
+from tempest_lib import exceptions
+
 from tempest import cli
 from tempest import config
-from tempest import exceptions
 from tempest import test
 
 CONF = config.CONF
@@ -40,6 +41,10 @@
             raise cls.skipException(msg)
         super(SimpleReadOnlySaharaClientTest, cls).resource_setup()
 
+    def sahara(self, *args, **kwargs):
+        return self.clients.sahara(
+            *args, endpoint_type=CONF.data_processing.endpoint_type, **kwargs)
+
     @test.attr(type='negative')
     def test_sahara_fake_action(self):
         self.assertRaises(exceptions.CommandFailed,
diff --git a/tempest/cli/simple_read_only/identity/test_keystone.py b/tempest/cli/simple_read_only/identity/test_keystone.py
index 9218fcd..1fc7908 100644
--- a/tempest/cli/simple_read_only/identity/test_keystone.py
+++ b/tempest/cli/simple_read_only/identity/test_keystone.py
@@ -15,9 +15,10 @@
 
 import re
 
+from tempest_lib import exceptions
+
 from tempest import cli
 from tempest import config
-from tempest import exceptions
 from tempest.openstack.common import log as logging
 
 CONF = config.CONF
@@ -34,6 +35,9 @@
     their own. They only verify the structure of output if present.
     """
 
+    def keystone(self, *args, **kwargs):
+        return self.clients.keystone(*args, **kwargs)
+
     def test_admin_fake_action(self):
         self.assertRaises(exceptions.CommandFailed,
                           self.keystone,
diff --git a/tempest/cli/simple_read_only/image/test_glance.py b/tempest/cli/simple_read_only/image/test_glance.py
index a9cbadb..03e00d7 100644
--- a/tempest/cli/simple_read_only/image/test_glance.py
+++ b/tempest/cli/simple_read_only/image/test_glance.py
@@ -15,9 +15,10 @@
 
 import re
 
+from tempest_lib import exceptions
+
 from tempest import cli
 from tempest import config
-from tempest import exceptions
 from tempest.openstack.common import log as logging
 
 CONF = config.CONF
@@ -40,6 +41,11 @@
             raise cls.skipException(msg)
         super(SimpleReadOnlyGlanceClientTest, cls).resource_setup()
 
+    def glance(self, *args, **kwargs):
+        return self.clients.glance(*args,
+                                   endpoint_type=CONF.image.endpoint_type,
+                                   **kwargs)
+
     def test_glance_fake_action(self):
         self.assertRaises(exceptions.CommandFailed,
                           self.glance,
diff --git a/tempest/cli/simple_read_only/network/test_neutron.py b/tempest/cli/simple_read_only/network/test_neutron.py
index f9f8906..2b3920d 100644
--- a/tempest/cli/simple_read_only/network/test_neutron.py
+++ b/tempest/cli/simple_read_only/network/test_neutron.py
@@ -15,9 +15,10 @@
 
 import re
 
+from tempest_lib import exceptions
+
 from tempest import cli
 from tempest import config
-from tempest import exceptions
 from tempest.openstack.common import log as logging
 from tempest import test
 
@@ -41,6 +42,11 @@
             raise cls.skipException(msg)
         super(SimpleReadOnlyNeutronClientTest, cls).resource_setup()
 
+    def neutron(self, *args, **kwargs):
+        return self.clients.neutron(*args,
+                                    endpoint_type=CONF.network.endpoint_type,
+                                    **kwargs)
+
     @test.attr(type='smoke')
     def test_neutron_fake_action(self):
         self.assertRaises(exceptions.CommandFailed,
diff --git a/tempest/cli/simple_read_only/object_storage/test_swift.py b/tempest/cli/simple_read_only/object_storage/test_swift.py
index a162660..40c4c15 100644
--- a/tempest/cli/simple_read_only/object_storage/test_swift.py
+++ b/tempest/cli/simple_read_only/object_storage/test_swift.py
@@ -15,9 +15,10 @@
 
 import re
 
+from tempest_lib import exceptions
+
 from tempest import cli
 from tempest import config
-from tempest import exceptions
 
 CONF = config.CONF
 
@@ -37,6 +38,10 @@
             raise cls.skipException(msg)
         super(SimpleReadOnlySwiftClientTest, cls).resource_setup()
 
+    def swift(self, *args, **kwargs):
+        return self.clients.swift(
+            *args, endpoint_type=CONF.object_storage.endpoint_type, **kwargs)
+
     def test_swift_fake_action(self):
         self.assertRaises(exceptions.CommandFailed,
                           self.swift,
diff --git a/tempest/cli/simple_read_only/orchestration/test_heat.py b/tempest/cli/simple_read_only/orchestration/test_heat.py
index 7d7f8c9..d3a9b01 100644
--- a/tempest/cli/simple_read_only/orchestration/test_heat.py
+++ b/tempest/cli/simple_read_only/orchestration/test_heat.py
@@ -42,6 +42,10 @@
             os.path.dirname(os.path.realpath(__file__))),
             'heat_templates/heat_minimal.yaml')
 
+    def heat(self, *args, **kwargs):
+        return self.clients.heat(
+            *args, endpoint_type=CONF.orchestration.endpoint_type, **kwargs)
+
     def test_heat_stack_list(self):
         self.heat('stack-list')
 
diff --git a/tempest/cli/simple_read_only/telemetry/test_ceilometer.py b/tempest/cli/simple_read_only/telemetry/test_ceilometer.py
index 45b793b..f9bf8b2 100644
--- a/tempest/cli/simple_read_only/telemetry/test_ceilometer.py
+++ b/tempest/cli/simple_read_only/telemetry/test_ceilometer.py
@@ -39,6 +39,10 @@
             raise cls.skipException(msg)
         super(SimpleReadOnlyCeilometerClientTest, cls).resource_setup()
 
+    def ceilometer(self, *args, **kwargs):
+        return self.clients.ceilometer(
+            *args, endpoint_type=CONF.telemetry.endpoint_type, **kwargs)
+
     def test_ceilometer_meter_list(self):
         self.ceilometer('meter-list')
 
diff --git a/tempest/cli/simple_read_only/volume/test_cinder.py b/tempest/cli/simple_read_only/volume/test_cinder.py
index 45f6c41..6e1e7d3 100644
--- a/tempest/cli/simple_read_only/volume/test_cinder.py
+++ b/tempest/cli/simple_read_only/volume/test_cinder.py
@@ -16,11 +16,12 @@
 import logging
 import re
 
+from tempest_lib import exceptions
 import testtools
 
 from tempest import cli
 from tempest import config
-from tempest import exceptions
+
 
 CONF = config.CONF
 LOG = logging.getLogger(__name__)
@@ -41,6 +42,11 @@
             raise cls.skipException(msg)
         super(SimpleReadOnlyCinderClientTest, cls).resource_setup()
 
+    def cinder(self, *args, **kwargs):
+        return self.clients.cinder(*args,
+                                   endpoint_type=CONF.volume.endpoint_type,
+                                   **kwargs)
+
     def test_cinder_fake_action(self):
         self.assertRaises(exceptions.CommandFailed,
                           self.cinder,
diff --git a/tempest/clients.py b/tempest/clients.py
index cf04929..19b4e11 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -447,20 +447,6 @@
             self.auth_provider)
 
 
-class AltManager(Manager):
-
-    """
-    Manager object that uses the alt_XXX credentials for its
-    managed client objects
-    """
-
-    def __init__(self, interface='json', service=None):
-        super(AltManager, self).__init__(
-            credentials=auth.get_default_credentials('alt_user'),
-            interface=interface,
-            service=service)
-
-
 class AdminManager(Manager):
 
     """
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
old mode 100644
new mode 100755
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 0d3c6c6..8adfbef 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
 # Copyright 2014 Dell Inc.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index 0adc7e0..d6cafe2 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -26,6 +26,7 @@
 import sys
 import unittest
 
+import netaddr
 import yaml
 
 import tempest.auth
@@ -34,14 +35,17 @@
 from tempest.openstack.common import log as logging
 from tempest.openstack.common import timeutils
 from tempest.services.compute.json import flavors_client
+from tempest.services.compute.json import security_groups_client
 from tempest.services.compute.json import servers_client
 from tempest.services.identity.json import identity_client
 from tempest.services.image.v2.json import image_client
+from tempest.services.network.json import network_client
 from tempest.services.object_storage import container_client
 from tempest.services.object_storage import object_client
 from tempest.services.telemetry.json import telemetry_client
 from tempest.services.volume.json import volumes_client
 
+CONF = config.CONF
 OPTS = {}
 USERS = {}
 RES = collections.defaultdict(list)
@@ -69,7 +73,9 @@
         self.images = image_client.ImageClientV2JSON(_auth)
         self.flavors = flavors_client.FlavorsClientJSON(_auth)
         self.telemetry = telemetry_client.TelemetryClientJSON(_auth)
+        self.secgroups = security_groups_client.SecurityGroupsClientJSON(_auth)
         self.volumes = volumes_client.VolumesClientJSON(_auth)
+        self.networks = network_client.NetworkClientJSON(_auth)
 
 
 def load_resources(fname):
@@ -90,6 +96,10 @@
     else:
         LOG.error("%s not found in USERS: %s" % (name, USERS))
 
+
+def resp_ok(response):
+    return 200 >= int(response['status']) < 300
+
 ###################
 #
 # TENANTS
@@ -212,12 +222,36 @@
     def runTest(self, *args):
         pass
 
+    def _ping_ip(self, ip_addr, count, namespace=None):
+        if namespace is None:
+            ping_cmd = "ping -c1 " + ip_addr
+        else:
+            ping_cmd = "sudo ip netns exec %s ping -c1 %s" % (namespace,
+                                                              ip_addr)
+        for current in range(count):
+            return_code = os.system(ping_cmd)
+            if return_code is 0:
+                break
+        self.assertNotEqual(current, count - 1,
+                            "Server is not pingable at %s" % ip_addr)
+
     def check(self):
         self.check_users()
         self.check_objects()
         self.check_servers()
         self.check_volumes()
         self.check_telemetry()
+        self.check_secgroups()
+
+        # validate neutron is enabled and ironic disabled:
+        # Tenant network isolation is not supported when using ironic.
+        # "admin" has set up a neutron flat network environment within a shared
+        # fixed network for all tenants to use.
+        # In this case, network/subnet/router creation can be skipped and the
+        # server booted the same as nova network.
+        if (CONF.service_available.neutron and
+                not CONF.baremetal.driver_enabled):
+            self.check_networking()
 
     def check_users(self):
         """Check that the users we expect to exist, do.
@@ -264,15 +298,32 @@
                 "Couldn't find expected server %s" % server['name'])
 
             r, found = client.servers.get_server(found['id'])
-            # get the ipv4 address
-            addr = found['addresses']['private'][0]['addr']
-            for count in range(60):
-                return_code = os.system("ping -c1 " + addr)
-                if return_code is 0:
-                    break
-            self.assertNotEqual(count, 59,
-                                "Server %s is not pingable at %s" % (
-                                    server['name'], addr))
+            # validate neutron is enabled and ironic disabled:
+            if (CONF.service_available.neutron and
+                    not CONF.baremetal.driver_enabled):
+                for network_name, body in found['addresses'].items():
+                    for addr in body:
+                        ip = addr['addr']
+                        if addr.get('OS-EXT-IPS:type', 'fixed') == 'fixed':
+                            namespace = _get_router_namespace(client,
+                                                              network_name)
+                            self._ping_ip(ip, 60, namespace)
+                        else:
+                            self._ping_ip(ip, 60)
+            else:
+                addr = found['addresses']['private'][0]['addr']
+                self._ping_ip(addr, 60)
+
+    def check_secgroups(self):
+        """Check that the security groups are still existing."""
+        LOG.info("Checking security groups")
+        for secgroup in self.res['secgroups']:
+            client = client_for_user(secgroup['owner'])
+            found = _get_resource_by_name(client.secgroups, 'security_groups',
+                                          secgroup['name'])
+            self.assertIsNotNone(
+                found,
+                "Couldn't find expected secgroup %s" % secgroup['name'])
 
     def check_telemetry(self):
         """Check that ceilometer provides a sane sample.
@@ -334,6 +385,17 @@
                 'timestamp should come before start of second javelin run'
             )
 
+    def check_networking(self):
+        """Check that the networks are still there."""
+        for res_type in ('networks', 'subnets', 'routers'):
+            for res in self.res[res_type]:
+                client = client_for_user(res['owner'])
+                found = _get_resource_by_name(client.networks, res_type,
+                                              res['name'])
+                self.assertIsNotNone(
+                    found,
+                    "Couldn't find expected resource %s" % res['name'])
+
 
 #######################
 #
@@ -440,6 +502,115 @@
 
 #######################
 #
+# NETWORKS
+#
+#######################
+
+def _get_router_namespace(client, network):
+    network_id = _get_resource_by_name(client.networks,
+                                       'networks', network)['id']
+    resp, n_body = client.networks.list_routers()
+    if not resp_ok(resp):
+        raise ValueError("unable to routers list: [%s] %s" % (resp, n_body))
+    for router in n_body['routers']:
+        router_id = router['id']
+        resp, r_body = client.networks.list_router_interfaces(router_id)
+        if not resp_ok(resp):
+            raise ValueError("unable to router interfaces list: [%s] %s" %
+                             (resp, r_body))
+        for port in r_body['ports']:
+            if port['network_id'] == network_id:
+                return "qrouter-%s" % router_id
+
+
+def _get_resource_by_name(client, resource, name):
+    get_resources = getattr(client, 'list_%s' % resource)
+    if get_resources is None:
+        raise AttributeError("client doesn't have method list_%s" % resource)
+    r, body = get_resources()
+    if not resp_ok(r):
+        raise ValueError("unable to list %s: [%s] %s" % (resource, r, body))
+    if isinstance(body, dict):
+        body = body[resource]
+    for res in body:
+        if name == res['name']:
+            return res
+    raise ValueError('%s not found in %s resources' % (name, resource))
+
+
+def create_networks(networks):
+    LOG.info("Creating networks")
+    for network in networks:
+        client = client_for_user(network['owner'])
+
+        # only create a network if the name isn't here
+        r, body = client.networks.list_networks()
+        if any(item['name'] == network['name'] for item in body['networks']):
+            LOG.warning("Dupplicated network name: %s" % network['name'])
+            continue
+
+        client.networks.create_network(name=network['name'])
+
+
+def create_subnets(subnets):
+    LOG.info("Creating subnets")
+    for subnet in subnets:
+        client = client_for_user(subnet['owner'])
+
+        network = _get_resource_by_name(client.networks, 'networks',
+                                        subnet['network'])
+        ip_version = netaddr.IPNetwork(subnet['range']).version
+        # ensure we don't overlap with another subnet in the network
+        try:
+            client.networks.create_subnet(network_id=network['id'],
+                                          cidr=subnet['range'],
+                                          name=subnet['name'],
+                                          ip_version=ip_version)
+        except exceptions.BadRequest as e:
+            is_overlapping_cidr = 'overlaps with another subnet' in str(e)
+            if not is_overlapping_cidr:
+                raise
+
+
+def create_routers(routers):
+    LOG.info("Creating routers")
+    for router in routers:
+        client = client_for_user(router['owner'])
+
+        # only create a router if the name isn't here
+        r, body = client.networks.list_routers()
+        if any(item['name'] == router['name'] for item in body['routers']):
+            LOG.warning("Dupplicated router name: %s" % router['name'])
+            continue
+
+        client.networks.create_router(router['name'])
+
+
+def add_router_interface(routers):
+    for router in routers:
+        client = client_for_user(router['owner'])
+        router_id = _get_resource_by_name(client.networks,
+                                          'routers', router['name'])['id']
+
+        for subnet in router['subnet']:
+            subnet_id = _get_resource_by_name(client.networks,
+                                              'subnets', subnet)['id']
+            # connect routers to their subnets
+            client.networks.add_router_interface_with_subnet_id(router_id,
+                                                                subnet_id)
+        # connect routers to exteral network if set to "gateway"
+        if router['gateway']:
+            if CONF.network.public_network_id:
+                ext_net = CONF.network.public_network_id
+                client.networks._update_router(
+                    router_id, set_enable_snat=True,
+                    external_gateway_info={"network_id": ext_net})
+            else:
+                raise ValueError('public_network_id is not configured.')
+
+
+#######################
+#
 # SERVERS
 #
 #######################
@@ -473,10 +644,21 @@
 
         image_id = _get_image_by_name(client, server['image'])['id']
         flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
-        resp, body = client.servers.create_server(server['name'], image_id,
-                                                  flavor_id)
+        # validate neutron is enabled and ironic disabled
+        kwargs = dict()
+        if (CONF.service_available.neutron and
+                not CONF.baremetal.driver_enabled and server.get('networks')):
+            get_net_id = lambda x: (_get_resource_by_name(
+                client.networks, 'networks', x)['id'])
+            kwargs['networks'] = [{'uuid': get_net_id(network)}
+                                  for network in server['networks']]
+        resp, body = client.servers.create_server(
+            server['name'], image_id, flavor_id, **kwargs)
         server_id = body['id']
         client.servers.wait_for_server_status(server_id, 'ACTIVE')
+        # create to security group(s) after server spawning
+        for secgroup in server['secgroups']:
+            client.servers.add_security_group(server_id, secgroup)
 
 
 def destroy_servers(servers):
@@ -496,6 +678,33 @@
                                                    ignore_error=True)
 
 
+def create_secgroups(secgroups):
+    LOG.info("Creating security groups")
+    for secgroup in secgroups:
+        client = client_for_user(secgroup['owner'])
+
+        # only create a security group if the name isn't here
+        # i.e. a security group may be used by another server
+        # only create a router if the name isn't here
+        r, body = client.secgroups.list_security_groups()
+        if any(item['name'] == secgroup['name'] for item in body):
+            LOG.warning("Security group '%s' already exists" %
+                        secgroup['name'])
+            continue
+
+        resp, body = client.secgroups.create_security_group(
+            secgroup['name'], secgroup['description'])
+        if not resp_ok(resp):
+            raise ValueError("Failed to create security group: [%s] %s" %
+                             (resp, body))
+        secgroup_id = body['id']
+        # for each security group, create the rules
+        for rule in secgroup['rules']:
+            ip_proto, from_port, to_port, cidr = rule.split()
+            client.secgroups.create_security_group_rule(
+                secgroup_id, ip_proto, from_port, to_port, cidr=cidr)
+
+
 #######################
 #
 # VOLUMES
@@ -563,6 +772,15 @@
     # next create resources in a well known order
     create_objects(RES['objects'])
     create_images(RES['images'])
+
+    # validate neutron is enabled and ironic is disabled
+    if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
+        create_networks(RES['networks'])
+        create_subnets(RES['subnets'])
+        create_routers(RES['routers'])
+        add_router_interface(RES['routers'])
+
+    create_secgroups(RES['secgroups'])
     create_servers(RES['servers'])
     create_volumes(RES['volumes'])
     attach_volumes(RES['volumes'])
diff --git a/tempest/cmd/resources.yaml b/tempest/cmd/resources.yaml
index 2d5e686..2d6664c 100644
--- a/tempest/cmd/resources.yaml
+++ b/tempest/cmd/resources.yaml
@@ -17,11 +17,17 @@
     tenant: discuss
 
 secgroups:
-  - angon:
+  - name: angon
     owner: javelin
+    description: angon
     rules:
       - 'icmp -1 -1 0.0.0.0/0'
       - 'tcp 22 22 0.0.0.0/0'
+  - name: baobab
+    owner: javelin
+    description: baobab
+    rules:
+      - 'tcp 80 80 0.0.0.0/0'
 
 # resources that we want to create
 images:
@@ -43,15 +49,45 @@
     owner: javelin
     gb: 2
     device: /dev/vdb
+networks:
+  - name: world1
+    owner: javelin
+  - name: world2
+    owner: javelin
+subnets:
+  - name: subnet1
+    range: 10.1.0.0/24
+    network: world1
+    owner: javelin
+  - name: subnet2
+    range: 192.168.1.0/24
+    network: world2
+    owner: javelin
+routers:
+  - name: connector
+    owner: javelin
+    gateway: true
+    subnet:
+      - subnet1
+      - subnet2
 servers:
   - name: peltast
     owner: javelin
     flavor: m1.small
     image: javelin_cirros
+    networks:
+      - world1
+    secgroups:
+      - angon
+      - baobab
   - name: hoplite
     owner: javelin
     flavor: m1.medium
     image: javelin_cirros
+    networks:
+      - world2
+    secgroups:
+      - angon
 objects:
   - container: jc1
     name: javelin1
diff --git a/tempest/common/accounts.py b/tempest/common/accounts.py
index 88e8ced..66285e4 100644
--- a/tempest/common/accounts.py
+++ b/tempest/common/accounts.py
@@ -65,6 +65,9 @@
         else:
             return len(self.hash_dict) > 1
 
+    def is_multi_tenant(self):
+        return self.is_multi_user()
+
     def _create_hash_file(self, hash_string):
         path = os.path.join(os.path.join(self.accounts_dir, hash_string))
         if not os.path.isfile(path):
@@ -149,13 +152,13 @@
     to preserve the current behaviour of the serial tempest run.
     """
 
-    def is_multi_user(self):
+    def _unique_creds(self, cred_arg=None):
+        """Verify that the configured credentials are valid and distinct """
         if self.use_default_creds:
-            # Verify that the configured users are valid and distinct
             try:
                 user = self.get_primary_creds()
                 alt_user = self.get_alt_creds()
-                return user.username != alt_user.username
+                return getattr(user, cred_arg) != getattr(alt_user, cred_arg)
             except exceptions.InvalidCredentials as ic:
                 msg = "At least one of the configured credentials is " \
                       "not valid: %s" % ic.message
@@ -164,6 +167,12 @@
             # TODO(andreaf) Add a uniqueness check here
             return len(self.hash_dict) > 1
 
+    def is_multi_user(self):
+        return self._unique_creds('username')
+
+    def is_multi_tenant(self):
+        return self._unique_creds('tenant_id')
+
     def get_creds(self, id):
         try:
             # No need to sort the dict as within the same python process
diff --git a/tempest/common/cred_provider.py b/tempest/common/cred_provider.py
index b09c964..c5be0c0 100644
--- a/tempest/common/cred_provider.py
+++ b/tempest/common/cred_provider.py
@@ -48,3 +48,7 @@
     @abc.abstractmethod
     def is_multi_user(self):
         return
+
+    @abc.abstractmethod
+    def is_multi_tenant(self):
+        return
diff --git a/tempest/common/isolated_creds.py b/tempest/common/isolated_creds.py
index 2d16107..228e47c 100644
--- a/tempest/common/isolated_creds.py
+++ b/tempest/common/isolated_creds.py
@@ -354,3 +354,6 @@
 
     def is_multi_user(self):
         return True
+
+    def is_multi_tenant(self):
+        return True
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 42e4f56..c290dad 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -16,6 +16,7 @@
 
 import collections
 import json
+import logging as real_logging
 import re
 import time
 
@@ -36,8 +37,8 @@
 MAX_RECURSION_DEPTH = 2
 TOKEN_CHARS_RE = re.compile('^[-A-Za-z0-9+/=]*$')
 
-# All the successful HTTP status codes from RFC 2616
-HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206)
+# All the successful HTTP status codes from RFC 7231 & 4918
+HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206, 207)
 
 
 # convert a structure into a string safely
@@ -208,8 +209,9 @@
     @classmethod
     def expected_success(cls, expected_code, read_code):
         assert_msg = ("This function only allowed to use for HTTP status"
-                      "codes which explicitly defined in the RFC 2616. {0}"
-                      " is not a defined Success Code!").format(expected_code)
+                      "codes which explicitly defined in the RFC 7231 & 4918."
+                      "{0} is not a defined Success Code!"
+                      ).format(expected_code)
         if isinstance(expected_code, list):
             for code in expected_code:
                 assert code in HTTP_SUCCESS, assert_msg
@@ -310,14 +312,15 @@
         caller_name = misc_utils.find_test_caller()
         if secs:
             secs = " %.3fs" % secs
-        self.LOG.info(
-            'Request (%s): %s %s %s%s' % (
-                caller_name,
-                resp['status'],
-                method,
-                req_url,
-                secs),
-            extra=extra)
+        if not self.LOG.isEnabledFor(real_logging.DEBUG):
+            self.LOG.info(
+                'Request (%s): %s %s %s%s' % (
+                    caller_name,
+                    resp['status'],
+                    method,
+                    req_url,
+                    secs),
+                extra=extra)
 
         # Also look everything at DEBUG if you want to filter this
         # out, don't run at debug.
diff --git a/tempest/config.py b/tempest/config.py
index d8f22d4..6e8238a 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -481,7 +481,10 @@
                 help="Allow the execution of IPv6 subnet tests that use "
                      "the extended IPv6 attributes ipv6_ra_mode "
                      "and ipv6_address_mode"
-                )
+                ),
+    cfg.BoolOpt('xml_api',
+                default=False,
+                help='If false, skip all network api tests with xml')
 ]
 
 messaging_group = cfg.OptGroup(name='messaging',
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 9660a63..2ebfdd1 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -623,14 +623,20 @@
         return floating_ip
 
     def check_floating_ip_status(self, floating_ip, status):
-        """Verifies floatingip has reached given status. without waiting
+        """Verifies floatingip reaches the given status
 
         :param floating_ip: net_resources.DeletableFloatingIp floating IP to
         to check status
         :param status: target status
         :raises: AssertionError if status doesn't match
         """
-        floating_ip.refresh()
+        def refresh():
+            floating_ip.refresh()
+            return status == floating_ip.status
+
+        tempest.test.call_until_true(refresh,
+                                     CONF.network.build_timeout,
+                                     CONF.network.build_interval)
         self.assertEqual(status, floating_ip.status,
                          message="FloatingIP: {fp} is at status: {cst}. "
                                  "failed  to reach status: {st}"
diff --git a/tempest/scenario/test_baremetal_basic_ops.py b/tempest/scenario/test_baremetal_basic_ops.py
index 35571c6..ea10140 100644
--- a/tempest/scenario/test_baremetal_basic_ops.py
+++ b/tempest/scenario/test_baremetal_basic_ops.py
@@ -132,7 +132,6 @@
         # We expect the ephemeral partition to be mounted on /mnt and to have
         # the same size as our flavor definition.
         eph_size = self.get_flavor_ephemeral_size()
-        self.assertIsNotNone(eph_size)
         if eph_size > 0:
             preserve_ephemeral = True
 
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 55d66d1..ead021e 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -16,6 +16,7 @@
 from tempest.common import custom_matchers
 from tempest.common import debug
 from tempest import config
+from tempest import exceptions
 from tempest.openstack.common import log as logging
 from tempest.scenario import manager
 from tempest import test
@@ -129,6 +130,17 @@
         self.addCleanup(self.servers_client.remove_security_group,
                         self.server['id'], secgroup['name'])
 
+        def wait_for_secgroup_add():
+            _, body = self.servers_client.get_server(self.server['id'])
+            return {'name': secgroup['name']} in body['security_groups']
+
+        if not test.call_until_true(wait_for_secgroup_add,
+                                    CONF.compute.build_timeout,
+                                    CONF.compute.build_interval):
+            msg = ('Timed out waiting for adding security group %s to server '
+                   '%s' % (secgroup['id'], self.server['id']))
+            raise exceptions.TimeoutException(msg)
+
     @test.services('compute', 'volume', 'image', 'network')
     def test_minimum_basic_scenario(self):
         self.glance_image_create()
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 5d75b64..ac4f004 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -179,9 +179,6 @@
         """Verifies connectivty to a VM via public network and floating IP,
         and verifies floating IP has resource status is correct.
 
-        Floating IP status is verified after connectivity test in order to
-        not add extra waiting and mask racing conditions.
-
         :param should_connect: bool. determines if connectivity check is
         negative or positive.
         :param msg: Failure message to add to Error message. Should describe
diff --git a/tempest/tests/cli/test_cli.py b/tempest/tests/cli/test_cli.py
index 1fd5ccb..8f18dfc 100644
--- a/tempest/tests/cli/test_cli.py
+++ b/tempest/tests/cli/test_cli.py
@@ -13,17 +13,25 @@
 #    under the License.
 
 import mock
+from tempest_lib.cli import base as cli_base
 import testtools
 
 from tempest import cli
+from tempest import config
 from tempest import exceptions
 from tempest.tests import base
+from tempest.tests import fake_config
 
 
 class TestMinClientVersion(base.TestCase):
     """Tests for the min_client_version decorator.
     """
 
+    def setUp(self):
+        super(TestMinClientVersion, self).setUp()
+        self.useFixture(fake_config.ConfigFixture())
+        self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
+
     def _test_min_version(self, required, installed, expect_skip):
 
         @cli.min_client_version(client='nova', version=required)
@@ -33,7 +41,7 @@
                 # expected so we need to fail.
                 self.fail('Should not have gotten past the decorator.')
 
-        with mock.patch.object(cli, 'execute',
+        with mock.patch.object(cli_base, 'execute',
                                return_value=installed) as mock_cmd:
             if expect_skip:
                 self.assertRaises(testtools.TestCase.skipException, fake,
@@ -41,6 +49,7 @@
             else:
                 fake(self, expect_skip)
             mock_cmd.assert_called_once_with('nova', '', params='--version',
+                                             cli_dir='/usr/local/bin',
                                              merge_stderr=True)
 
     def test_min_client_version(self):
@@ -52,7 +61,7 @@
         for case in cases:
             self._test_min_version(*case)
 
-    @mock.patch.object(cli, 'execute', return_value=' ')
+    @mock.patch.object(cli_base, 'execute', return_value=' ')
     def test_check_client_version_empty_output(self, mock_execute):
         # Tests that an exception is raised if the command output is empty.
         self.assertRaises(exceptions.TempestException,
diff --git a/tempest/tests/cli/test_command_failed.py b/tempest/tests/cli/test_command_failed.py
deleted file mode 100644
index 36a4fc8..0000000
--- a/tempest/tests/cli/test_command_failed.py
+++ /dev/null
@@ -1,30 +0,0 @@
-#    Licensed under the Apache License, Version 2.0 (the "License"); you may
-#    not use this file except in compliance with the License. You may obtain
-#    a copy of the License at
-#
-#         http://www.apache.org/licenses/LICENSE-2.0
-#
-#    Unless required by applicable law or agreed to in writing, software
-#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-#    License for the specific language governing permissions and limitations
-#    under the License.
-
-from tempest import exceptions
-from tempest.tests import base
-
-
-class TestOutputParser(base.TestCase):
-
-    def test_command_failed_exception(self):
-        returncode = 1
-        cmd = "foo"
-        stdout = "output"
-        stderr = "error"
-        try:
-            raise exceptions.CommandFailed(returncode, cmd, stdout, stderr)
-        except exceptions.CommandFailed as e:
-            self.assertIn(str(returncode), str(e))
-            self.assertIn(cmd, str(e))
-            self.assertIn(stdout, str(e))
-            self.assertIn(stderr, str(e))
diff --git a/tempest/tests/cli/test_output_parser.py b/tempest/tests/cli/test_output_parser.py
deleted file mode 100644
index 7ad270c..0000000
--- a/tempest/tests/cli/test_output_parser.py
+++ /dev/null
@@ -1,177 +0,0 @@
-# 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.cli import output_parser
-from tempest import exceptions
-from tempest.tests import base
-
-
-class TestOutputParser(base.TestCase):
-    OUTPUT_LINES = """
-+----+------+---------+
-| ID | Name | Status  |
-+----+------+---------+
-| 11 | foo  | BUILD   |
-| 21 | bar  | ERROR   |
-| 31 | bee  | None    |
-+----+------+---------+
-"""
-    OUTPUT_LINES2 = """
-+----+-------+---------+
-| ID | Name2 | Status2 |
-+----+-------+---------+
-| 41 | aaa   | SSSSS   |
-| 51 | bbb   | TTTTT   |
-| 61 | ccc   | AAAAA   |
-+----+-------+---------+
-"""
-
-    EXPECTED_TABLE = {'headers': ['ID', 'Name', 'Status'],
-                      'values': [['11', 'foo', 'BUILD'],
-                                 ['21', 'bar', 'ERROR'],
-                                 ['31', 'bee', 'None']]}
-    EXPECTED_TABLE2 = {'headers': ['ID', 'Name2', 'Status2'],
-                       'values': [['41', 'aaa', 'SSSSS'],
-                                  ['51', 'bbb', 'TTTTT'],
-                                  ['61', 'ccc', 'AAAAA']]}
-
-    def test_table_with_normal_values(self):
-        actual = output_parser.table(self.OUTPUT_LINES)
-        self.assertIsInstance(actual, dict)
-        self.assertEqual(self.EXPECTED_TABLE, actual)
-
-    def test_table_with_list(self):
-        output_lines = self.OUTPUT_LINES.split('\n')
-        actual = output_parser.table(output_lines)
-        self.assertIsInstance(actual, dict)
-        self.assertEqual(self.EXPECTED_TABLE, actual)
-
-    def test_table_with_invalid_line(self):
-        output_lines = self.OUTPUT_LINES + "aaaa"
-        actual = output_parser.table(output_lines)
-        self.assertIsInstance(actual, dict)
-        self.assertEqual(self.EXPECTED_TABLE, actual)
-
-    def test_tables_with_normal_values(self):
-        output_lines = 'test' + self.OUTPUT_LINES +\
-                       'test2' + self.OUTPUT_LINES2
-        expected = [{'headers': self.EXPECTED_TABLE['headers'],
-                     'label': 'test',
-                     'values': self.EXPECTED_TABLE['values']},
-                    {'headers': self.EXPECTED_TABLE2['headers'],
-                     'label': 'test2',
-                     'values': self.EXPECTED_TABLE2['values']}]
-        actual = output_parser.tables(output_lines)
-        self.assertIsInstance(actual, list)
-        self.assertEqual(expected, actual)
-
-    def test_tables_with_invalid_values(self):
-        output_lines = 'test' + self.OUTPUT_LINES +\
-                       'test2' + self.OUTPUT_LINES2 + '\n'
-        expected = [{'headers': self.EXPECTED_TABLE['headers'],
-                     'label': 'test',
-                     'values': self.EXPECTED_TABLE['values']},
-                    {'headers': self.EXPECTED_TABLE2['headers'],
-                     'label': 'test2',
-                     'values': self.EXPECTED_TABLE2['values']}]
-        actual = output_parser.tables(output_lines)
-        self.assertIsInstance(actual, list)
-        self.assertEqual(expected, actual)
-
-    def test_tables_with_invalid_line(self):
-        output_lines = 'test' + self.OUTPUT_LINES +\
-                       'test2' + self.OUTPUT_LINES2 +\
-                       '+----+-------+---------+'
-        expected = [{'headers': self.EXPECTED_TABLE['headers'],
-                     'label': 'test',
-                     'values': self.EXPECTED_TABLE['values']},
-                    {'headers': self.EXPECTED_TABLE2['headers'],
-                     'label': 'test2',
-                     'values': self.EXPECTED_TABLE2['values']}]
-
-        actual = output_parser.tables(output_lines)
-        self.assertIsInstance(actual, list)
-        self.assertEqual(expected, actual)
-
-    LISTING_OUTPUT = """
-+----+
-| ID |
-+----+
-| 11 |
-| 21 |
-| 31 |
-+----+
-"""
-
-    def test_listing(self):
-        expected = [{'ID': '11'}, {'ID': '21'}, {'ID': '31'}]
-        actual = output_parser.listing(self.LISTING_OUTPUT)
-        self.assertIsInstance(actual, list)
-        self.assertEqual(expected, actual)
-
-    def test_details_multiple_with_invalid_line(self):
-        self.assertRaises(exceptions.InvalidStructure,
-                          output_parser.details_multiple,
-                          self.OUTPUT_LINES)
-
-    DETAILS_LINES1 = """First Table
-+----------+--------+
-| Property | Value  |
-+----------+--------+
-| foo      | BUILD  |
-| bar      | ERROR  |
-| bee      | None   |
-+----------+--------+
-"""
-    DETAILS_LINES2 = """Second Table
-+----------+--------+
-| Property | Value  |
-+----------+--------+
-| aaa      | VVVVV  |
-| bbb      | WWWWW  |
-| ccc      | XXXXX  |
-+----------+--------+
-"""
-
-    def test_details_with_normal_line_label_false(self):
-        expected = {'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'}
-        actual = output_parser.details(self.DETAILS_LINES1)
-        self.assertEqual(expected, actual)
-
-    def test_details_with_normal_line_label_true(self):
-        expected = {'__label': 'First Table',
-                    'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'}
-        actual = output_parser.details(self.DETAILS_LINES1, with_label=True)
-        self.assertEqual(expected, actual)
-
-    def test_details_multiple_with_normal_line_label_false(self):
-        expected = [{'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'},
-                    {'aaa': 'VVVVV', 'bbb': 'WWWWW', 'ccc': 'XXXXX'}]
-        actual = output_parser.details_multiple(self.DETAILS_LINES1 +
-                                                self.DETAILS_LINES2)
-        self.assertIsInstance(actual, list)
-        self.assertEqual(expected, actual)
-
-    def test_details_multiple_with_normal_line_label_true(self):
-        expected = [{'__label': 'First Table',
-                     'foo': 'BUILD', 'bar': 'ERROR', 'bee': 'None'},
-                    {'__label': 'Second Table',
-                     'aaa': 'VVVVV', 'bbb': 'WWWWW', 'ccc': 'XXXXX'}]
-        actual = output_parser.details_multiple(self.DETAILS_LINES1 +
-                                                self.DETAILS_LINES2,
-                                                with_label=True)
-        self.assertIsInstance(actual, list)
-        self.assertEqual(expected, actual)
diff --git a/tempest/tests/stress/test_stress.py b/tempest/tests/stress/test_stress.py
index 3dc2199..9c3533d 100644
--- a/tempest/tests/stress/test_stress.py
+++ b/tempest/tests/stress/test_stress.py
@@ -16,7 +16,8 @@
 import shlex
 import subprocess
 
-import tempest.cli as cli
+from tempest_lib import exceptions
+
 from tempest.openstack.common import log as logging
 from tempest.tests import base
 
@@ -43,9 +44,9 @@
             result, result_err = proc.communicate()
             if proc.returncode != 0:
                 LOG.debug('error of %s:\n%s' % (cmd_str, result_err))
-                raise cli.CommandFailed(proc.returncode,
-                                        cmd,
-                                        result)
+                raise exceptions.CommandFailed(proc.returncode,
+                                               cmd,
+                                               result)
         finally:
             LOG.debug('output of %s:\n%s' % (cmd_str, result))
         return proc.returncode
diff --git a/tempest/tests/test_wrappers.py b/tempest/tests/test_wrappers.py
index 0fd41f9..ae7860d 100644
--- a/tempest/tests/test_wrappers.py
+++ b/tempest/tests/test_wrappers.py
@@ -34,7 +34,6 @@
         # Setup Test files
         self.testr_conf_file = os.path.join(self.directory, '.testr.conf')
         self.setup_cfg_file = os.path.join(self.directory, 'setup.cfg')
-        self.subunit_trace = os.path.join(self.directory, 'subunit-trace.py')
         self.passing_file = os.path.join(self.test_dir, 'test_passing.py')
         self.failing_file = os.path.join(self.test_dir, 'test_failing.py')
         self.init_file = os.path.join(self.test_dir, '__init__.py')
@@ -45,7 +44,6 @@
         shutil.copy('setup.py', self.setup_py)
         shutil.copy('tempest/tests/files/setup.cfg', self.setup_cfg_file)
         shutil.copy('tempest/tests/files/__init__.py', self.init_file)
-        shutil.copy('tools/subunit-trace.py', self.subunit_trace)
         # copy over the pretty_tox scripts
         shutil.copy('tools/pretty_tox.sh',
                     os.path.join(self.directory, 'pretty_tox.sh'))
diff --git a/tempest/thirdparty/boto/test_ec2_network.py b/tempest/thirdparty/boto/test_ec2_network.py
index a75fb7b..132a5a8 100644
--- a/tempest/thirdparty/boto/test_ec2_network.py
+++ b/tempest/thirdparty/boto/test_ec2_network.py
@@ -13,7 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest import test
 from tempest.thirdparty.boto import test as boto_test
 
 
@@ -22,21 +21,22 @@
     @classmethod
     def resource_setup(cls):
         super(EC2NetworkTest, cls).resource_setup()
-        cls.client = cls.os.ec2api_client
+        cls.ec2_client = cls.os.ec2api_client
 
     # Note(afazekas): these tests for things duable without an instance
-    @test.skip_because(bug="1080406")
     def test_disassociate_not_associated_floating_ip(self):
         # EC2 disassociate not associated floating ip
         ec2_codes = self.ec2_error_code
-        address = self.client.allocate_address()
+        address = self.ec2_client.allocate_address()
         public_ip = address.public_ip
-        rcuk = self.addResourceCleanUp(self.client.release_address, public_ip)
-        addresses_get = self.client.get_all_addresses(addresses=(public_ip,))
+        rcuk = self.addResourceCleanUp(self.ec2_client.release_address,
+                                       public_ip)
+        addresses_get = self.ec2_client.get_all_addresses(
+            addresses=(public_ip,))
         self.assertEqual(len(addresses_get), 1)
         self.assertEqual(addresses_get[0].public_ip, public_ip)
         self.assertBotoError(ec2_codes.client.InvalidAssociationID.NotFound,
                              address.disassociate)
-        self.client.release_address(public_ip)
-        self.cancelResourceCleanUp(rcuk)
+        self.ec2_client.release_address(public_ip)
         self.assertAddressReleasedWait(address)
+        self.cancelResourceCleanUp(rcuk)
diff --git a/tools/check_logs.py b/tools/check_logs.py
index 7cf9d85..c8d3a1a 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -31,7 +31,7 @@
 is_grenade = os.environ.get('DEVSTACK_GATE_GRENADE') is not None
 dump_all_errors = True
 
-# As logs are made clean, add to this set
+# As logs are made clean, remove from this set
 allowed_dirty = set([
     'c-api',
     'ceilometer-acentral',
diff --git a/tools/pretty_tox.sh b/tools/pretty_tox.sh
index 0a04ce6..ff554c5 100755
--- a/tools/pretty_tox.sh
+++ b/tools/pretty_tox.sh
@@ -3,4 +3,4 @@
 set -o pipefail
 
 TESTRARGS=$1
-python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | $(dirname $0)/subunit-trace.py --no-failure-debug -f
+python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | subunit-trace --no-failure-debug -f
diff --git a/tools/pretty_tox_serial.sh b/tools/pretty_tox_serial.sh
index db70890..e0fca0f 100755
--- a/tools/pretty_tox_serial.sh
+++ b/tools/pretty_tox_serial.sh
@@ -7,7 +7,7 @@
 if [ ! -d .testrepository ]; then
     testr init
 fi
-testr run --subunit $TESTRARGS | $(dirname $0)/subunit-trace.py -f -n
+testr run --subunit $TESTRARGS | subunit-trace -f -n
 retval=$?
 testr slowest
 
diff --git a/tools/subunit-trace.py b/tools/subunit-trace.py
deleted file mode 100755
index 57e58f2..0000000
--- a/tools/subunit-trace.py
+++ /dev/null
@@ -1,247 +0,0 @@
-#!/usr/bin/env python
-
-# Copyright 2014 Hewlett-Packard Development Company, L.P.
-# Copyright 2014 Samsung Electronics
-# 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.
-
-"""Trace a subunit stream in reasonable detail and high accuracy."""
-
-import argparse
-import functools
-import re
-import sys
-
-import subunit
-import testtools
-
-DAY_SECONDS = 60 * 60 * 24
-FAILS = []
-RESULTS = {}
-
-
-def cleanup_test_name(name, strip_tags=True, strip_scenarios=False):
-    """Clean up the test name for display.
-
-    By default we strip out the tags in the test because they don't help us
-    in identifying the test that is run to it's result.
-
-    Make it possible to strip out the testscenarios information (not to
-    be confused with tempest scenarios) however that's often needed to
-    indentify generated negative tests.
-    """
-    if strip_tags:
-        tags_start = name.find('[')
-        tags_end = name.find(']')
-        if tags_start > 0 and tags_end > tags_start:
-            newname = name[:tags_start]
-            newname += name[tags_end + 1:]
-            name = newname
-
-    if strip_scenarios:
-        tags_start = name.find('(')
-        tags_end = name.find(')')
-        if tags_start > 0 and tags_end > tags_start:
-            newname = name[:tags_start]
-            newname += name[tags_end + 1:]
-            name = newname
-
-    return name
-
-
-def get_duration(timestamps):
-    start, end = timestamps
-    if not start or not end:
-        duration = ''
-    else:
-        delta = end - start
-        duration = '%d.%06ds' % (
-            delta.days * DAY_SECONDS + delta.seconds, delta.microseconds)
-    return duration
-
-
-def find_worker(test):
-    for tag in test['tags']:
-        if tag.startswith('worker-'):
-            return int(tag[7:])
-    return 'NaN'
-
-
-# Print out stdout/stderr if it exists, always
-def print_attachments(stream, test, all_channels=False):
-    """Print out subunit attachments.
-
-    Print out subunit attachments that contain content. This
-    runs in 2 modes, one for successes where we print out just stdout
-    and stderr, and an override that dumps all the attachments.
-    """
-    channels = ('stdout', 'stderr')
-    for name, detail in test['details'].items():
-        # NOTE(sdague): the subunit names are a little crazy, and actually
-        # are in the form pythonlogging:'' (with the colon and quotes)
-        name = name.split(':')[0]
-        if detail.content_type.type == 'test':
-            detail.content_type.type = 'text'
-        if (all_channels or name in channels) and detail.as_text():
-            title = "Captured %s:" % name
-            stream.write("\n%s\n%s\n" % (title, ('~' * len(title))))
-            # indent attachment lines 4 spaces to make them visually
-            # offset
-            for line in detail.as_text().split('\n'):
-                stream.write("    %s\n" % line)
-
-
-def show_outcome(stream, test, print_failures=False):
-    global RESULTS
-    status = test['status']
-    # TODO(sdague): ask lifeless why on this?
-    if status == 'exists':
-        return
-
-    worker = find_worker(test)
-    name = cleanup_test_name(test['id'])
-    duration = get_duration(test['timestamps'])
-
-    if worker not in RESULTS:
-        RESULTS[worker] = []
-    RESULTS[worker].append(test)
-
-    # don't count the end of the return code as a fail
-    if name == 'process-returncode':
-        return
-
-    if status == 'success':
-        stream.write('{%s} %s [%s] ... ok\n' % (
-            worker, name, duration))
-        print_attachments(stream, test)
-    elif status == 'fail':
-        FAILS.append(test)
-        stream.write('{%s} %s [%s] ... FAILED\n' % (
-            worker, name, duration))
-        if not print_failures:
-            print_attachments(stream, test, all_channels=True)
-    elif status == 'skip':
-        stream.write('{%s} %s ... SKIPPED: %s\n' % (
-            worker, name, test['details']['reason'].as_text()))
-    else:
-        stream.write('{%s} %s [%s] ... %s\n' % (
-            worker, name, duration, test['status']))
-        if not print_failures:
-            print_attachments(stream, test, all_channels=True)
-
-    stream.flush()
-
-
-def print_fails(stream):
-    """Print summary failure report.
-
-    Currently unused, however there remains debate on inline vs. at end
-    reporting, so leave the utility function for later use.
-    """
-    if not FAILS:
-        return
-    stream.write("\n==============================\n")
-    stream.write("Failed %s tests - output below:" % len(FAILS))
-    stream.write("\n==============================\n")
-    for f in FAILS:
-        stream.write("\n%s\n" % f['id'])
-        stream.write("%s\n" % ('-' * len(f['id'])))
-        print_attachments(stream, f, all_channels=True)
-    stream.write('\n')
-
-
-def count_tests(key, value):
-    count = 0
-    for k, v in RESULTS.items():
-        for item in v:
-            if key in item:
-                if re.search(value, item[key]):
-                    count += 1
-    return count
-
-
-def run_time():
-    runtime = 0.0
-    for k, v in RESULTS.items():
-        for test in v:
-            runtime += float(get_duration(test['timestamps']).strip('s'))
-    return runtime
-
-
-def worker_stats(worker):
-    tests = RESULTS[worker]
-    num_tests = len(tests)
-    delta = tests[-1]['timestamps'][1] - tests[0]['timestamps'][0]
-    return num_tests, delta
-
-
-def print_summary(stream):
-    stream.write("\n======\nTotals\n======\n")
-    stream.write("Run: %s in %s sec.\n" % (count_tests('status', '.*'),
-                                           run_time()))
-    stream.write(" - Passed: %s\n" % count_tests('status', 'success'))
-    stream.write(" - Skipped: %s\n" % count_tests('status', 'skip'))
-    stream.write(" - Failed: %s\n" % count_tests('status', 'fail'))
-
-    # we could have no results, especially as we filter out the process-codes
-    if RESULTS:
-        stream.write("\n==============\nWorker Balance\n==============\n")
-
-        for w in range(max(RESULTS.keys()) + 1):
-            if w not in RESULTS:
-                stream.write(
-                    " - WARNING: missing Worker %s! "
-                    "Race in testr accounting.\n" % w)
-            else:
-                num, time = worker_stats(w)
-                stream.write(" - Worker %s (%s tests) => %ss\n" %
-                             (w, num, time))
-
-
-def parse_args():
-    parser = argparse.ArgumentParser()
-    parser.add_argument('--no-failure-debug', '-n', action='store_true',
-                        dest='print_failures', help='Disable printing failure '
-                        'debug information in realtime')
-    parser.add_argument('--fails', '-f', action='store_true',
-                        dest='post_fails', help='Print failure debug '
-                        'information after the stream is proccesed')
-    return parser.parse_args()
-
-
-def main():
-    args = parse_args()
-    stream = subunit.ByteStreamToStreamResult(
-        sys.stdin, non_subunit_name='stdout')
-    outcomes = testtools.StreamToDict(
-        functools.partial(show_outcome, sys.stdout,
-                          print_failures=args.print_failures))
-    summary = testtools.StreamSummary()
-    result = testtools.CopyStreamResult([outcomes, summary])
-    result.startTestRun()
-    try:
-        stream.run(result)
-    finally:
-        result.stopTestRun()
-    if count_tests('status', '.*') == 0:
-        print("The test run didn't actually run any tests")
-        return 1
-    if args.post_fails:
-        print_fails(sys.stdout)
-    print_summary(sys.stdout)
-    return (0 if summary.wasSuccessful() else 1)
-
-
-if __name__ == '__main__':
-    sys.exit(main())
diff --git a/tox.ini b/tox.ini
index e575b4f..9f52f0d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -28,7 +28,9 @@
 
 [testenv:all]
 sitepackages = {[tempestenv]sitepackages}
+# 'all' includes slow tests
 setenv = {[tempestenv]setenv}
+         OS_TEST_TIMEOUT=1200
 deps = {[tempestenv]deps}
 commands =
   bash tools/pretty_tox.sh '{posargs}'