Merge "Move tempest runtime dependencies to the pip-requires"
diff --git a/HACKING.rst b/HACKING.rst
index a546f8c..eafa81b 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -153,6 +153,19 @@
                              kwarg2=dict_of_numbers)
 
 
+Test Skips
+----------
+If a test is broken because of a bug it is appropriate to skip the test until
+bug has been fixed. However, the skip message should be formatted so that
+Tempest's skip tracking tool can watch the bug status. The skip message should
+contain the string 'Bug' immediately followed by a space. Then the bug number
+should be included in the message '#' in front of the number.
+
+Example::
+
+  @testtools.skip("Skipped until the Bug #980688 is resolved")
+
+
 openstack-common
 ----------------
 
diff --git a/cli/__init__.py b/cli/__init__.py
index 6ffe229..5d986c0 100644
--- a/cli/__init__.py
+++ b/cli/__init__.py
@@ -60,10 +60,11 @@
         return self.cmd_with_auth(
             'nova', action, flags, params, admin, fail_ok)
 
-    def nova_manage(self, action, flags='', params='', fail_ok=False):
+    def nova_manage(self, action, flags='', params='', fail_ok=False,
+                    merge_stderr=False):
         """Executes nova-manage command for the given action."""
         return self.cmd(
-            'nova-manage', action, flags, params, fail_ok)
+            '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."""
@@ -81,14 +82,19 @@
         flags = creds + ' ' + flags
         return self.cmd(cmd, action, flags, params, fail_ok)
 
-    def cmd(self, cmd, action, flags='', params='', fail_ok=False):
+    def cmd(self, cmd, action, flags='', params='', fail_ok=False,
+            merge_stderr=False):
         """Executes specified command for the given action."""
         cmd = ' '.join([CONF.cli.cli_dir + cmd,
                         flags, action, params])
         LOG.info("running: '%s'" % cmd)
         cmd = shlex.split(cmd)
         try:
-            result = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+            if merge_stderr:
+                result = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
+            else:
+                devnull = open('/dev/null', 'w')
+                result = subprocess.check_output(cmd, stderr=devnull)
         except subprocess.CalledProcessError, e:
             LOG.error("command output:\n%s" % e.output)
             raise
diff --git a/cli/simple_read_only/test_compute.py b/cli/simple_read_only/test_compute.py
index c904882..d301d38 100644
--- a/cli/simple_read_only/test_compute.py
+++ b/cli/simple_read_only/test_compute.py
@@ -22,7 +22,6 @@
 import testtools
 
 import cli
-from tempest import config
 
 
 CONF = cfg.CONF
diff --git a/cli/simple_read_only/test_compute_manage.py b/cli/simple_read_only/test_compute_manage.py
index 5768c74..bbcc5b1 100644
--- a/cli/simple_read_only/test_compute_manage.py
+++ b/cli/simple_read_only/test_compute_manage.py
@@ -18,8 +18,6 @@
 import logging
 import subprocess
 
-import testtools
-
 import cli
 
 
@@ -50,9 +48,11 @@
         self.nova_manage('', '-h')
 
     def test_version_flag(self):
-        self.assertNotEqual("", self.nova_manage('', '--version'))
+        # Bug 1159957: nova-manage --version writes to stderr
+        self.assertNotEqual("", self.nova_manage('', '--version',
+                                                 merge_stderr=True))
         self.assertEqual(self.nova_manage('version'),
-                         self.nova_manage('', '--version'))
+                         self.nova_manage('', '--version', merge_stderr=True))
 
     def test_debug_flag(self):
         self.assertNotEqual("", self.nova_manage('instance_type list',
@@ -68,8 +68,8 @@
 
     def test_flavor_list(self):
         self.assertNotEqual("", self.nova_manage('flavor list'))
-        self.assertNotEqual(self.nova_manage('instance_type list'),
-                            self.nova_manage('flavor list'))
+        self.assertEqual(self.nova_manage('instance_type list'),
+                         self.nova_manage('flavor list'))
 
     def test_db_archive_deleted_rows(self):
         # make sure command doesn't error out
diff --git a/tempest/tests/boto/test_s3_objects.py b/tempest/tests/boto/test_s3_objects.py
index c735215..dcb7c86 100644
--- a/tempest/tests/boto/test_s3_objects.py
+++ b/tempest/tests/boto/test_s3_objects.py
@@ -18,7 +18,6 @@
 from contextlib import closing
 
 from boto.s3.key import Key
-import testtools
 
 from tempest import clients
 from tempest.common.utils.data_utils import rand_name
diff --git a/tempest/tests/compute/servers/test_list_server_filters.py b/tempest/tests/compute/servers/test_list_server_filters.py
index ff599fe..4d2b99f 100644
--- a/tempest/tests/compute/servers/test_list_server_filters.py
+++ b/tempest/tests/compute/servers/test_list_server_filters.py
@@ -126,11 +126,12 @@
         self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
 
     @attr(type='positive')
-    def test_list_servers_detailed_filter_by_limit(self):
+    def test_list_servers_filter_by_limit(self):
         # Verify only the expected number of servers are returned
         params = {'limit': 1}
-        resp, servers = self.client.list_servers_with_detail(params)
-        self.assertEqual(1, len(servers['servers']))
+        resp, servers = self.client.list_servers(params)
+        #when _interface='xml', one element for servers_links in servers
+        self.assertEqual(1, len([x for x in servers['servers'] if 'id' in x]))
 
     @utils.skip_unless_attr('multiple_images', 'Only one image found')
     @attr(type='positive')
diff --git a/tempest/tests/compute/servers/test_server_addresses.py b/tempest/tests/compute/servers/test_server_addresses.py
index cb8e85e..05fa320 100644
--- a/tempest/tests/compute/servers/test_server_addresses.py
+++ b/tempest/tests/compute/servers/test_server_addresses.py
@@ -15,7 +15,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
 from tempest.test import attr
 from tempest.tests.compute import base
diff --git a/tempest/tests/compute/servers/test_server_rescue.py b/tempest/tests/compute/servers/test_server_rescue.py
index 5fc730a..61ba384 100644
--- a/tempest/tests/compute/servers/test_server_rescue.py
+++ b/tempest/tests/compute/servers/test_server_rescue.py
@@ -15,17 +15,12 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import base64
-import time
-
 import testtools
 
 from tempest.common.utils.data_utils import rand_name
-from tempest.common.utils.linux.remote_client import RemoteClient
 import tempest.config
 from tempest import exceptions
 from tempest.test import attr
-from tempest.tests import compute
 from tempest.tests.compute import base
 
 
@@ -112,6 +107,11 @@
     def _delete(self, volume_id):
         self.volumes_extensions_client.delete_volume(volume_id)
 
+    def _unrescue(self, server_id):
+        resp, body = self.servers_client.unrescue_server(server_id)
+        self.assertEqual(202, resp.status)
+        self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
+
     @attr(type='smoke')
     def test_rescue_unrescue_instance(self):
         resp, body = self.servers_client.rescue_server(
@@ -123,9 +123,8 @@
         self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
 
     @attr(type='negative')
-    @testtools.skip("Skipped until Bug #1126163 is resolved")
     def test_rescued_vm_reboot(self):
-        self.assertRaises(exceptions.BadRequest, self.servers_client.reboot,
+        self.assertRaises(exceptions.Duplicate, self.servers_client.reboot,
                           self.rescue_id, 'HARD')
 
     @attr(type='negative')
@@ -135,37 +134,23 @@
                           self.rescue_id,
                           self.image_ref_alt)
 
-    @attr(type='positive')
-    @testtools.skip("Skipped due to Bug #1126187")
+    @attr(type='negative')
     def test_rescued_vm_attach_volume(self):
         client = self.volumes_extensions_client
 
         # Rescue the server
         self.servers_client.rescue_server(self.server_id, self.password)
         self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
+        self.addCleanup(self._unrescue, self.server_id)
 
         # Attach the volume to the server
-        resp, body = \
-        self.servers_client.attach_volume(self.server_id,
-                                          self.volume_to_attach['id'],
-                                          device='/dev/%s' % self.device)
-        self.assertEqual(200, resp.status)
-        client.wait_for_volume_status(self.volume_to_attach['id'], 'in-use')
+        self.assertRaises(exceptions.Duplicate,
+                          self.servers_client.attach_volume,
+                          self.server_id,
+                          self.volume_to_attach['id'],
+                          device='/dev/%s' % self.device)
 
-        # Detach the volume to the server
-        resp, body = \
-        self.servers_client.detach_volume(self.server_id,
-                                          self.volume_to_attach['id'])
-        self.assertEqual(202, resp.status)
-        client.wait_for_volume_status(self.volume_to_attach['id'], 'available')
-
-        # Unrescue the server
-        resp, body = self.servers_client.unrescue_server(self.server_id)
-        self.assertEqual(202, resp.status)
-        self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
-
-    @attr(type='positive')
-    @testtools.skip("Skipped until Bug #1126187 is resolved")
+    @attr(type='negative')
     def test_rescued_vm_detach_volume(self):
         # Attach the volume to the server
         self.servers_client.attach_volume(self.server_id,
@@ -177,19 +162,13 @@
         # Rescue the server
         self.servers_client.rescue_server(self.server_id, self.password)
         self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
+        self.addCleanup(self._unrescue, self.server_id)
 
-        # Detach the volume to the server
-        resp, body = \
-        self.servers_client.detach_volume(self.server_id,
-                                          self.volume_to_detach['id'])
-        self.assertEqual(202, resp.status)
-        client = self.volumes_extensions_client
-        client.wait_for_volume_status(self.volume_to_detach['id'], 'available')
-
-        # Unrescue the server
-        resp, body = self.servers_client.unrescue_server(self.server_id)
-        self.assertEqual(202, resp.status)
-        self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+        # Detach the volume from the server expecting failure
+        self.assertRaises(exceptions.Duplicate,
+                          self.servers_client.detach_volume,
+                          self.server_id,
+                          self.volume_to_detach['id'])
 
     @attr(type='positive')
     def test_rescued_vm_associate_dissociate_floating_ip(self):
diff --git a/tempest/tests/compute/test_authorization.py b/tempest/tests/compute/test_authorization.py
index 4ca197a..02ea4d4 100644
--- a/tempest/tests/compute/test_authorization.py
+++ b/tempest/tests/compute/test_authorization.py
@@ -15,8 +15,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testtools
-
 from tempest import clients
 from tempest.common.utils.data_utils import parse_image_id
 from tempest.common.utils.data_utils import rand_name
diff --git a/tempest/tests/compute/volumes/test_attach_volume.py b/tempest/tests/compute/volumes/test_attach_volume.py
index d9abe41..57f1e20 100644
--- a/tempest/tests/compute/volumes/test_attach_volume.py
+++ b/tempest/tests/compute/volumes/test_attach_volume.py
@@ -17,7 +17,6 @@
 
 import testtools
 
-from tempest.common.utils.data_utils import rand_name
 from tempest.common.utils.linux.remote_client import RemoteClient
 import tempest.config
 from tempest.test import attr
diff --git a/tempest/tests/image/base.py b/tempest/tests/image/base.py
index 65d81b6..f12e957 100644
--- a/tempest/tests/image/base.py
+++ b/tempest/tests/image/base.py
@@ -15,7 +15,6 @@
 #    under the License.
 
 import logging
-import time
 
 from tempest import clients
 from tempest.common.utils.data_utils import rand_name
diff --git a/tempest/tests/image/v1/test_images.py b/tempest/tests/image/v1/test_images.py
index af09b79..9304a33 100644
--- a/tempest/tests/image/v1/test_images.py
+++ b/tempest/tests/image/v1/test_images.py
@@ -17,7 +17,6 @@
 
 import cStringIO as StringIO
 
-from tempest import clients
 from tempest import exceptions
 from tempest.test import attr
 from tempest.tests.image import base
@@ -68,13 +67,18 @@
                                        container_format='bare',
                                        disk_format='raw', is_public=True,
                                        location='http://example.com'
-                                                '/someimage.iso')
+                                                '/someimage.iso',
+                                       properties={'key1': 'value1',
+                                                   'key2': 'value2'})
         self.assertTrue('id' in body)
         image_id = body.get('id')
         self.created_images.append(image_id)
         self.assertEqual('New Remote Image', body.get('name'))
         self.assertTrue(body.get('is_public'))
         self.assertEqual('active', body.get('status'))
+        properties = body.get('properties')
+        self.assertEqual(properties['key1'], 'value1')
+        self.assertEqual(properties['key2'], 'value2')
 
 
 class ListImagesTest(base.BaseV1ImageTest):
diff --git a/tempest/tests/image/v2/test_images.py b/tempest/tests/image/v2/test_images.py
index 19a7a95..eddeb78 100644
--- a/tempest/tests/image/v2/test_images.py
+++ b/tempest/tests/image/v2/test_images.py
@@ -19,7 +19,6 @@
 import cStringIO as StringIO
 import random
 
-from tempest import clients
 from tempest import exceptions
 from tempest.test import attr
 from tempest.tests.image import base
diff --git a/tempest/tests/network/common.py b/tempest/tests/network/common.py
new file mode 100644
index 0000000..0bb806f
--- /dev/null
+++ b/tempest/tests/network/common.py
@@ -0,0 +1,316 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import subprocess
+
+import netaddr
+
+from quantumclient.common import exceptions as exc
+from tempest.common.utils.data_utils import rand_name
+from tempest import smoke
+from tempest import test
+
+
+class AttributeDict(dict):
+
+    """
+    Provide attribute access (dict.key) to dictionary values.
+    """
+
+    def __getattr__(self, name):
+        """Allow attribute access for all keys in the dict."""
+        if name in self:
+            return self[name]
+        return super(AttributeDict, self).__getattribute__(name)
+
+
+class DeletableResource(AttributeDict):
+
+    """
+    Support deletion of quantum resources (networks, subnets) via a
+    delete() method, as is supported by keystone and nova resources.
+    """
+
+    def __init__(self, *args, **kwargs):
+        self.client = kwargs.pop('client', None)
+        super(DeletableResource, self).__init__(*args, **kwargs)
+
+    def __str__(self):
+        return '<%s id="%s" name="%s">' % (self.__class__.__name__,
+                                           self.id, self.name)
+
+    def delete(self):
+        raise NotImplemented()
+
+
+class DeletableNetwork(DeletableResource):
+
+    def delete(self):
+        self.client.delete_network(self.id)
+
+
+class DeletableSubnet(DeletableResource):
+
+    _router_ids = set()
+
+    def add_to_router(self, router_id):
+        self._router_ids.add(router_id)
+        body = dict(subnet_id=self.id)
+        self.client.add_interface_router(router_id, body=body)
+
+    def delete(self):
+        for router_id in self._router_ids.copy():
+            body = dict(subnet_id=self.id)
+            self.client.remove_interface_router(router_id, body=body)
+            self._router_ids.remove(router_id)
+        self.client.delete_subnet(self.id)
+
+
+class DeletableRouter(DeletableResource):
+
+    def add_gateway(self, network_id):
+        body = dict(network_id=network_id)
+        self.client.add_gateway_router(self.id, body=body)
+
+    def delete(self):
+        self.client.remove_gateway_router(self.id)
+        self.client.delete_router(self.id)
+
+
+class DeletableFloatingIp(DeletableResource):
+
+    def delete(self):
+        self.client.delete_floatingip(self.id)
+
+
+class DeletablePort(DeletableResource):
+
+    def delete(self):
+        self.client.delete_port(self.id)
+
+
+class TestNetworkSmokeCommon(smoke.DefaultClientSmokeTest):
+    """
+    Base class for network smoke tests
+    """
+
+    @classmethod
+    def check_preconditions(cls):
+        if (cls.config.network.quantum_available):
+            cls.enabled = True
+            #verify that quantum_available is telling the truth
+            try:
+                cls.network_client.list_networks()
+            except exc.EndpointNotFound:
+                cls.enabled = False
+                raise
+        else:
+            cls.enabled = False
+            msg = 'Quantum not available'
+            raise cls.skipException(msg)
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestNetworkSmokeCommon, cls).setUpClass()
+        cfg = cls.config.network
+        cls.tenant_id = cls.manager._get_identity_client(
+            cls.config.identity.username,
+            cls.config.identity.password,
+            cls.config.identity.tenant_name).tenant_id
+
+    def _create_keypair(self, client, namestart='keypair-smoke-'):
+        kp_name = rand_name(namestart)
+        keypair = client.keypairs.create(kp_name)
+        try:
+            self.assertEqual(keypair.id, kp_name)
+            self.set_resource(kp_name, keypair)
+        except AttributeError:
+            self.fail("Keypair object not successfully created.")
+        return keypair
+
+    def _create_security_group(self, client, namestart='secgroup-smoke-'):
+        # Create security group
+        sg_name = rand_name(namestart)
+        sg_desc = sg_name + " description"
+        secgroup = client.security_groups.create(sg_name, sg_desc)
+        try:
+            self.assertEqual(secgroup.name, sg_name)
+            self.assertEqual(secgroup.description, sg_desc)
+            self.set_resource(sg_name, secgroup)
+        except AttributeError:
+            self.fail("SecurityGroup object not successfully created.")
+
+        # Add rules to the security group
+        rulesets = [
+            {
+                # ssh
+                'ip_protocol': 'tcp',
+                'from_port': 22,
+                'to_port': 22,
+                'cidr': '0.0.0.0/0',
+                'group_id': secgroup.id
+            },
+            {
+                # ping
+                'ip_protocol': 'icmp',
+                'from_port': -1,
+                'to_port': -1,
+                'cidr': '0.0.0.0/0',
+                'group_id': secgroup.id
+            }
+        ]
+        for ruleset in rulesets:
+            try:
+                client.security_group_rules.create(secgroup.id, **ruleset)
+            except Exception:
+                self.fail("Failed to create rule in security group.")
+
+        return secgroup
+
+    def _create_network(self, tenant_id, namestart='network-smoke-'):
+        name = rand_name(namestart)
+        body = dict(
+            network=dict(
+                name=name,
+                tenant_id=tenant_id,
+            ),
+        )
+        result = self.network_client.create_network(body=body)
+        network = DeletableNetwork(client=self.network_client,
+                                   **result['network'])
+        self.assertEqual(network.name, name)
+        self.set_resource(name, network)
+        return network
+
+    def _list_networks(self):
+        nets = self.network_client.list_networks()
+        return nets['networks']
+
+    def _list_subnets(self):
+        subnets = self.network_client.list_subnets()
+        return subnets['subnets']
+
+    def _list_routers(self):
+        routers = self.network_client.list_routers()
+        return routers['routers']
+
+    def _create_subnet(self, network, namestart='subnet-smoke-'):
+        """
+        Create a subnet for the given network within the cidr block
+        configured for tenant networks.
+        """
+        cfg = self.config.network
+        tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
+        result = None
+        # Repeatedly attempt subnet creation with sequential cidr
+        # blocks until an unallocated block is found.
+        for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
+            body = dict(
+                subnet=dict(
+                    ip_version=4,
+                    network_id=network.id,
+                    tenant_id=network.tenant_id,
+                    cidr=str(subnet_cidr),
+                ),
+            )
+            try:
+                result = self.network_client.create_subnet(body=body)
+                break
+            except exc.QuantumClientException as e:
+                is_overlapping_cidr = 'overlaps with another subnet' in str(e)
+                if not is_overlapping_cidr:
+                    raise
+        self.assertIsNotNone(result, 'Unable to allocate tenant network')
+        subnet = DeletableSubnet(client=self.network_client,
+                                 **result['subnet'])
+        self.assertEqual(subnet.cidr, str(subnet_cidr))
+        self.set_resource(rand_name(namestart), subnet)
+        return subnet
+
+    def _create_port(self, network, namestart='port-quotatest-'):
+        name = rand_name(namestart)
+        body = dict(
+            port=dict(name=name,
+                      network_id=network.id,
+                      tenant_id=network.tenant_id))
+        try:
+            result = self.network_client.create_port(body=body)
+        except Exception as e:
+            raise
+        self.assertIsNotNone(result, 'Unable to allocate port')
+        port = DeletablePort(client=self.network_client,
+                             **result['port'])
+        self.set_resource(name, port)
+        return port
+
+    def _create_server(self, client, network, name, key_name, security_groups):
+        flavor_id = self.config.compute.flavor_ref
+        base_image_id = self.config.compute.image_ref
+        create_kwargs = {
+            'nics': [
+                {'net-id': network.id},
+            ],
+            'key_name': key_name,
+            'security_groups': security_groups,
+        }
+        server = client.servers.create(name, base_image_id, flavor_id,
+                                       **create_kwargs)
+        try:
+            self.assertEqual(server.name, name)
+            self.set_resource(name, server)
+        except AttributeError:
+            self.fail("Server not successfully created.")
+        self.status_timeout(client.servers, server.id, 'ACTIVE')
+        # The instance retrieved on creation is missing network
+        # details, necessitating retrieval after it becomes active to
+        # ensure correct details.
+        server = client.servers.get(server.id)
+        self.set_resource(name, server)
+        return server
+
+    def _create_floating_ip(self, server, external_network_id):
+        result = self.network_client.list_ports(device_id=server.id)
+        ports = result.get('ports', [])
+        self.assertEqual(len(ports), 1,
+                         "Unable to determine which port to target.")
+        port_id = ports[0]['id']
+        body = dict(
+            floatingip=dict(
+                floating_network_id=external_network_id,
+                port_id=port_id,
+                tenant_id=server.tenant_id,
+            )
+        )
+        result = self.network_client.create_floatingip(body=body)
+        floating_ip = DeletableFloatingIp(client=self.network_client,
+                                          **result['floatingip'])
+        self.set_resource(rand_name('floatingip-'), floating_ip)
+        return floating_ip
+
+    def _ping_ip_address(self, ip_address):
+        cmd = ['ping', '-c1', '-w1', ip_address]
+
+        def ping():
+            proc = subprocess.Popen(cmd,
+                                    stdout=subprocess.PIPE,
+                                    stderr=subprocess.PIPE)
+            proc.wait()
+            if proc.returncode == 0:
+                return True
+
+        # TODO(mnewby) Allow configuration of execution and sleep duration.
+        return test.call_until_true(ping, 20, 1)
diff --git a/tempest/tests/network/test_network_basic_ops.py b/tempest/tests/network/test_network_basic_ops.py
index aed368e..7d0ded3 100644
--- a/tempest/tests/network/test_network_basic_ops.py
+++ b/tempest/tests/network/test_network_basic_ops.py
@@ -1,6 +1,7 @@
 # vim: tabstop=4 shiftwidth=4 softtabstop=4
 
 # Copyright 2012 OpenStack, LLC
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
 # All Rights Reserved.
 #
 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -15,94 +16,14 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
-import subprocess
-
-import netaddr
-
 from quantumclient.common import exceptions as exc
-
 from tempest.common.utils.data_utils import rand_name
-from tempest import smoke
 from tempest import test
+from tempest.tests.network.common import DeletableRouter
+from tempest.tests.network.common import TestNetworkSmokeCommon
 
 
-LOG = logging.getLogger(__name__)
-
-
-class AttributeDict(dict):
-
-    """
-    Provide attribute access (dict.key) to dictionary values.
-    """
-
-    def __getattr__(self, name):
-        """Allow attribute access for all keys in the dict."""
-        if name in self:
-            return self[name]
-        return super(AttributeDict, self).__getattribute__(name)
-
-
-class DeletableResource(AttributeDict):
-
-    """
-    Support deletion of quantum resources (networks, subnets) via a
-    delete() method, as is supported by keystone and nova resources.
-    """
-
-    def __init__(self, *args, **kwargs):
-        self.client = kwargs.pop('client', None)
-        super(DeletableResource, self).__init__(*args, **kwargs)
-
-    def __str__(self):
-        return '<%s id="%s" name="%s">' % (self.__class__.__name__,
-                                           self.id, self.name)
-
-    def delete(self):
-        raise NotImplemented()
-
-
-class DeletableNetwork(DeletableResource):
-
-    def delete(self):
-        self.client.delete_network(self.id)
-
-
-class DeletableSubnet(DeletableResource):
-
-    _router_ids = set()
-
-    def add_to_router(self, router_id):
-        self._router_ids.add(router_id)
-        body = dict(subnet_id=self.id)
-        self.client.add_interface_router(router_id, body=body)
-
-    def delete(self):
-        for router_id in self._router_ids.copy():
-            body = dict(subnet_id=self.id)
-            self.client.remove_interface_router(router_id, body=body)
-            self._router_ids.remove(router_id)
-        self.client.delete_subnet(self.id)
-
-
-class DeletableRouter(DeletableResource):
-
-    def add_gateway(self, network_id):
-        body = dict(network_id=network_id)
-        self.client.add_gateway_router(self.id, body=body)
-
-    def delete(self):
-        self.client.remove_gateway_router(self.id)
-        self.client.delete_router(self.id)
-
-
-class DeletableFloatingIp(DeletableResource):
-
-    def delete(self):
-        self.client.delete_floatingip(self.id)
-
-
-class TestNetworkBasicOps(smoke.DefaultClientSmokeTest):
+class TestNetworkBasicOps(TestNetworkSmokeCommon):
 
     """
     This smoke test suite assumes that Nova has been configured to
@@ -165,19 +86,12 @@
 
     @classmethod
     def check_preconditions(cls):
+        super(TestNetworkBasicOps, cls).check_preconditions()
         cfg = cls.config.network
-        msg = None
         if not (cfg.tenant_networks_reachable or cfg.public_network_id):
             msg = ('Either tenant_networks_reachable must be "true", or '
                    'public_network_id must be defined.')
-        else:
-            try:
-                cls.network_client.list_networks()
-            except exc.QuantumClientException:
-                msg = 'Unable to connect to Quantum service.'
-
-        cls.enabled = not bool(msg)
-        if msg:
+            cls.enabled = False
             raise cls.skipException(msg)
 
     @classmethod
@@ -198,55 +112,6 @@
         cls.servers = []
         cls.floating_ips = {}
 
-    def _create_keypair(self, client):
-        kp_name = rand_name('keypair-smoke-')
-        keypair = client.keypairs.create(kp_name)
-        try:
-            self.assertEqual(keypair.id, kp_name)
-            self.set_resource(kp_name, keypair)
-        except AttributeError:
-            self.fail("Keypair object not successfully created.")
-        return keypair
-
-    def _create_security_group(self, client):
-        # Create security group
-        sg_name = rand_name('secgroup-smoke-')
-        sg_desc = sg_name + " description"
-        secgroup = client.security_groups.create(sg_name, sg_desc)
-        try:
-            self.assertEqual(secgroup.name, sg_name)
-            self.assertEqual(secgroup.description, sg_desc)
-            self.set_resource(sg_name, secgroup)
-        except AttributeError:
-            self.fail("SecurityGroup object not successfully created.")
-
-        # Add rules to the security group
-        rulesets = [
-            {
-                # ssh
-                'ip_protocol': 'tcp',
-                'from_port': 22,
-                'to_port': 22,
-                'cidr': '0.0.0.0/0',
-                'group_id': secgroup.id
-            },
-            {
-                # ping
-                'ip_protocol': 'icmp',
-                'from_port': -1,
-                'to_port': -1,
-                'cidr': '0.0.0.0/0',
-                'group_id': secgroup.id
-            }
-        ]
-        for ruleset in rulesets:
-            try:
-                client.security_group_rules.create(secgroup.id, **ruleset)
-            except Exception:
-                self.fail("Failed to create rule in security group.")
-
-        return secgroup
-
     def _get_router(self, tenant_id):
         """Retrieve a router for the given tenant id.
 
@@ -270,8 +135,8 @@
             raise Exception("Neither of 'public_router_id' or "
                             "'public_network_id' has been defined.")
 
-    def _create_router(self, tenant_id):
-        name = rand_name('router-smoke-')
+    def _create_router(self, tenant_id, namestart='router-smoke-'):
+        name = rand_name(namestart)
         body = dict(
             router=dict(
                 name=name,
@@ -286,124 +151,6 @@
         self.set_resource(name, router)
         return router
 
-    def _create_network(self, tenant_id):
-        name = rand_name('network-smoke-')
-        body = dict(
-            network=dict(
-                name=name,
-                tenant_id=tenant_id,
-            ),
-        )
-        result = self.network_client.create_network(body=body)
-        network = DeletableNetwork(client=self.network_client,
-                                   **result['network'])
-        self.assertEqual(network.name, name)
-        self.set_resource(name, network)
-        return network
-
-    def _list_networks(self):
-        nets = self.network_client.list_networks()
-        return nets['networks']
-
-    def _list_subnets(self):
-        subnets = self.network_client.list_subnets()
-        return subnets['subnets']
-
-    def _list_routers(self):
-        routers = self.network_client.list_routers()
-        return routers['routers']
-
-    def _create_subnet(self, network):
-        """
-        Create a subnet for the given network within the cidr block
-        configured for tenant networks.
-        """
-        cfg = self.config.network
-        tenant_cidr = netaddr.IPNetwork(cfg.tenant_network_cidr)
-        result = None
-        # Repeatedly attempt subnet creation with sequential cidr
-        # blocks until an unallocated block is found.
-        for subnet_cidr in tenant_cidr.subnet(cfg.tenant_network_mask_bits):
-            body = dict(
-                subnet=dict(
-                ip_version=4,
-                network_id=network.id,
-                tenant_id=network.tenant_id,
-                cidr=str(subnet_cidr),
-                ),
-            )
-            try:
-                result = self.network_client.create_subnet(body=body)
-                break
-            except exc.QuantumClientException as e:
-                is_overlapping_cidr = 'overlaps with another subnet' in str(e)
-                if not is_overlapping_cidr:
-                    raise
-        self.assertIsNotNone(result, 'Unable to allocate tenant network')
-        subnet = DeletableSubnet(client=self.network_client,
-                                 **result['subnet'])
-        self.assertEqual(subnet.cidr, str(subnet_cidr))
-        self.set_resource(rand_name('subnet-smoke-'), subnet)
-        return subnet
-
-    def _create_server(self, client, network, name, key_name, security_groups):
-        flavor_id = self.config.compute.flavor_ref
-        base_image_id = self.config.compute.image_ref
-        create_kwargs = {
-            'nics': [
-                {'net-id': network.id},
-            ],
-            'key_name': key_name,
-            'security_groups': security_groups,
-        }
-        server = client.servers.create(name, base_image_id, flavor_id,
-                                       **create_kwargs)
-        try:
-            self.assertEqual(server.name, name)
-            self.set_resource(name, server)
-        except AttributeError:
-            self.fail("Server not successfully created.")
-        self.status_timeout(client.servers, server.id, 'ACTIVE')
-        # The instance retrieved on creation is missing network
-        # details, necessitating retrieval after it becomes active to
-        # ensure correct details.
-        server = client.servers.get(server.id)
-        self.set_resource(name, server)
-        return server
-
-    def _create_floating_ip(self, server, external_network_id):
-        result = self.network_client.list_ports(device_id=server.id)
-        ports = result.get('ports', [])
-        self.assertEqual(len(ports), 1,
-                         "Unable to determine which port to target.")
-        port_id = ports[0]['id']
-        body = dict(
-            floatingip=dict(
-                floating_network_id=external_network_id,
-                port_id=port_id,
-                tenant_id=server.tenant_id,
-            )
-        )
-        result = self.network_client.create_floatingip(body=body)
-        floating_ip = DeletableFloatingIp(client=self.network_client,
-                                          **result['floatingip'])
-        self.set_resource(rand_name('floatingip-'), floating_ip)
-        return floating_ip
-
-    def _ping_ip_address(self, ip_address):
-        cmd = ['ping', '-c1', '-w1', ip_address]
-
-        def ping():
-            proc = subprocess.Popen(cmd,
-                                    stdout=subprocess.PIPE,
-                                    stderr=subprocess.PIPE)
-            proc.wait()
-            if proc.returncode == 0:
-                return True
-
-        # TODO(mnewby) Allow configuration of execution and sleep duration.
-        return test.call_until_true(ping, 20, 1)
-
     def test_001_create_keypairs(self):
         self.keypairs[self.tenant_id] = self._create_keypair(
             self.compute_client)
@@ -428,31 +175,21 @@
         seen_names = [n['name'] for n in seen_nets]
         seen_ids = [n['id'] for n in seen_nets]
         for mynet in self.networks:
-            assert mynet.name in seen_names, \
-            "Did not see expected network with name %s" % mynet.name
-            assert mynet.id in seen_ids, \
-            "Did not see expected network with id %s" % mynet.id
+            self.assertIn(mynet.name, seen_names)
+            self.assertIn(mynet.id, seen_ids)
         seen_subnets = self._list_subnets()
         seen_net_ids = [n['network_id'] for n in seen_subnets]
         seen_subnet_ids = [n['id'] for n in seen_subnets]
         for mynet in self.networks:
-            assert mynet.id in seen_net_ids, \
-            "Did not see subnet belonging to network %s/%s" % \
-            (mynet.name, mynet.id)
+            self.assertIn(mynet.id, seen_net_ids)
         for mysubnet in self.subnets:
-            assert mysubnet.id in seen_subnet_ids, \
-            "Did not see expected subnet with id %s" % \
-            mysubnet.id
+            self.assertIn(mysubnet.id, seen_subnet_ids)
         seen_routers = self._list_routers()
         seen_router_ids = [n['id'] for n in seen_routers]
         seen_router_names = [n['name'] for n in seen_routers]
         for myrouter in self.routers:
-            assert myrouter.name in seen_router_names, \
-            "Did not see expected router with name %s" % \
-            myrouter.name
-            assert myrouter.id in seen_router_ids, \
-            "Did not see expected router with id %s" % \
-            myrouter.id
+            self.assertIn(myrouter.name, seen_router_names)
+            self.assertIn(myrouter.id, seen_router_ids)
 
     def test_005_create_servers(self):
         if not (self.keypairs or self.security_groups or self.networks):
diff --git a/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py
index f528cec..13fcbbf 100644
--- a/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py
@@ -15,7 +15,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testtools
 import uuid
 
 from tempest.common.utils.data_utils import rand_name
diff --git a/tempest/tests/volume/admin/test_volume_types_negative.py b/tempest/tests/volume/admin/test_volume_types_negative.py
index 1b11d68..daf804d 100644
--- a/tempest/tests/volume/admin/test_volume_types_negative.py
+++ b/tempest/tests/volume/admin/test_volume_types_negative.py
@@ -15,7 +15,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testtools
 import uuid
 
 from tempest import exceptions
diff --git a/tools/hacking.py b/tools/hacking.py
index 617682d..7e46b74 100755
--- a/tools/hacking.py
+++ b/tools/hacking.py
@@ -21,7 +21,6 @@
 built on top of pep8.py
 """
 
-import fnmatch
 import inspect
 import logging
 import os
@@ -323,6 +322,30 @@
                 return (pos, "T404: test functions must "
                         "not have doc strings")
 
+SKIP_DECORATOR = '@testtools.skip('
+
+
+def tempest_skip_bugs(physical_line):
+    """Check skip lines for proper bug entries
+
+    T601: Bug not in skip line
+    T602: Bug in message formatted incorrectly
+    """
+
+    pos = physical_line.find(SKIP_DECORATOR)
+
+    skip_re = re.compile(r'^\s*@testtools.skip.*')
+
+    if pos != -1 and skip_re.match(physical_line):
+        bug = re.compile(r'^.*\bbug\b.*', re.IGNORECASE)
+        if bug.match(physical_line) is None:
+            return (pos, 'T601: skips must have an associated bug')
+
+        bug_re = re.compile(r'.*skip\(.*Bug\s\#\d+', re.IGNORECASE)
+
+        if bug_re.match(physical_line) is None:
+            return (pos, 'T602: Bug number formatted incorrectly')
+
 
 FORMAT_RE = re.compile("%(?:"
                        "%|"           # Ignore plain percents
diff --git a/tools/install_venv.py b/tools/install_venv.py
index 20dcefa..ef7b0a8 100644
--- a/tools/install_venv.py
+++ b/tools/install_venv.py
@@ -21,9 +21,7 @@
 
 """Installation script for Tempest's development virtualenv."""
 
-import optparse
 import os
-import subprocess
 import sys
 
 import install_venv_common as install_venv
diff --git a/tools/tempest_coverage.py b/tools/tempest_coverage.py
index a46d0fb..c385eae 100755
--- a/tools/tempest_coverage.py
+++ b/tools/tempest_coverage.py
@@ -16,7 +16,6 @@
 
 import json
 import os
-import re
 import shutil
 import sys
 
@@ -24,7 +23,6 @@
 
 from tempest.common.rest_client import RestClient
 from tempest import config
-from tempest.tests.compute import base
 
 CONF = config.TempestConfig()