Merge "Test to GET public-readable container's object"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index cc91716..534f3d9 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -185,6 +185,44 @@
 # Catalog type of the Quantum Service
 catalog_type = network
 
+# This should be the username of a user WITHOUT administrative privileges
+username = demo
+# The above non-administrative user's password
+password = pass
+# The above non-administrative user's tenant name
+tenant_name = demo
+
+# A large private cidr block from which to allocate smaller blocks for
+# tenant networks.
+tenant_network_cidr = 10.100.0.0/16
+
+# The mask bits used to partition the tenant block.
+tenant_network_mask_bits = 29
+
+# If tenant networks are reachable, connectivity checks will be
+# performed directly against addresses on those networks.
+tenant_networks_reachable = false
+
+# Id of the public network that provides external connectivity.
+public_network_id = {$PUBLIC_NETWORK_UUID}
+
+# Id of a shared public router that provides external connectivity.
+# A shared public router would commonly be used where IP namespaces
+# were disabled.  If namespaces are enabled, it would be preferable
+# for each tenant to have their own router.
+public_router_id =
+
+[network-admin]
+# This section contains configuration options for an administrative
+# user of the Network API.
+
+# This should be the username of a user WITH administrative privileges
+username = admin
+# The above administrative user's password
+password = pass
+# The above administrative user's tenant name
+tenant_name = admin
+
 [identity-admin]
 # This section contains configuration options for an administrative
 # user of the Compute API. These options are used in tests that stress
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 8924fd3..cb18a9c 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -141,13 +141,7 @@
 
             mgmt_url = None
             for ep in auth_data['serviceCatalog']:
-                if ep["type"] == service and service != 'volume':
-                    mgmt_url = ep['endpoints'][self.region][self.endpoint_url]
-                    tenant_id = auth_data['token']['tenant']['id']
-                    break
-
-                elif (ep["type"] == service and ep['name'] == 'cinder' and
-                      service == 'volume'):
+                if ep["type"] == service:
                     mgmt_url = ep['endpoints'][self.region][self.endpoint_url]
                     tenant_id = auth_data['token']['tenant']['id']
                     break
diff --git a/tempest/config.py b/tempest/config.py
index 25fbbb9..1d1aa49 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -378,6 +378,68 @@
         """Version of Quantum API"""
         return self.get("api_version", "v1.1")
 
+    @property
+    def username(self):
+        """Username to use for Quantum API requests."""
+        return self.get("username", "demo")
+
+    @property
+    def tenant_name(self):
+        """Tenant name to use for Quantum API requests."""
+        return self.get("tenant_name", "demo")
+
+    @property
+    def password(self):
+        """API key to use when authenticating as admin."""
+        return self.get("password", "pass")
+
+    @property
+    def tenant_network_cidr(self):
+        """The cidr block to allocate tenant networks from"""
+        return self.get("tenant_network_cidr", "10.100.0.0/16")
+
+    @property
+    def tenant_network_mask_bits(self):
+        """The mask bits for tenant networks"""
+        return int(self.get("tenant_network_mask_bits", "29"))
+
+    @property
+    def tenant_networks_reachable(self):
+        """Whether tenant network connectivity should be evaluated directly"""
+        return (
+            self.get("tenant_networks_reachable", 'false').lower() != 'false'
+        )
+
+    @property
+    def public_network_id(self):
+        """Id of the public network that provides external connectivity"""
+        return self.get("public_network_id", "")
+
+    @property
+    def public_router_id(self):
+        """Id of the public router that provides external connectivity"""
+        return self.get("public_router_id", "")
+
+
+class NetworkAdminConfig(BaseConfig):
+
+    SECTION_NAME = "network-admin"
+
+    @property
+    def username(self):
+        """Administrative Username to use for Quantum API requests."""
+        return self.get("username", "admin")
+
+    @property
+    def tenant_name(self):
+        """Administrative Tenant name to use for Quantum API requests."""
+        return self.get("tenant_name", "admin")
+
+    @property
+    def password(self):
+        """API key to use when authenticating as admin."""
+        return self.get("password", "pass")
+
 
 class VolumeConfig(BaseConfig):
     """Provides configuration information for connecting to an OpenStack Block
@@ -544,6 +606,7 @@
         self.identity_admin = IdentityAdminConfig(self._conf)
         self.images = ImagesConfig(self._conf)
         self.network = NetworkConfig(self._conf)
+        self.network_admin = NetworkAdminConfig(self._conf)
         self.volume = VolumeConfig(self._conf)
         self.object_storage = ObjectStorageConfig(self._conf)
         self.boto = BotoConfig(self._conf)
diff --git a/tempest/manager.py b/tempest/manager.py
index a17aa21..92caf57 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -145,12 +145,16 @@
                                                     endpoint_type='publicURL')
         return glanceclient.Client('1', endpoint=endpoint, token=token)
 
-    def _get_identity_client(self):
+    def _get_identity_client(self, username=None, password=None,
+                             tenant_name=None):
         # This identity client is not intended to check the security
-        # of the identity service, so use admin credentials.
-        username = self.config.identity_admin.username
-        password = self.config.identity_admin.password
-        tenant_name = self.config.identity_admin.tenant_name
+        # of the identity service, so use admin credentials by default.
+        if not username:
+            username = self.config.identity_admin.username
+        if not password:
+            password = self.config.identity_admin.password
+        if not tenant_name:
+            tenant_name = self.config.identity_admin.tenant_name
 
         if None in (username, password, tenant_name):
             msg = ("Missing required credentials for identity client. "
@@ -166,10 +170,15 @@
                                                  auth_url=auth_url)
 
     def _get_network_client(self):
-        # TODO(mnewby) add network-specific auth configuration
-        username = self.config.compute.username
-        password = self.config.compute.password
-        tenant_name = self.config.compute.tenant_name
+        # The intended configuration is for the network client to have
+        # admin privileges and indicate for whom resources are being
+        # created via a 'tenant_id' parameter.  This will often be
+        # preferable to authenticating as a specific user because
+        # working with certain resources (public routers and networks)
+        # often requires admin privileges anyway.
+        username = self.config.network_admin.username
+        password = self.config.network_admin.password
+        tenant_name = self.config.network_admin.tenant_name
 
         if None in (username, password, tenant_name):
             msg = ("Missing required credentials for network client. "
diff --git a/tempest/smoke.py b/tempest/smoke.py
index c929273..68d0927 100644
--- a/tempest/smoke.py
+++ b/tempest/smoke.py
@@ -51,15 +51,29 @@
         # order, and because test methods in smoke tests generally create
         # resources in a particular order, we destroy resources in the reverse
         # order in which resources are added to the smoke test class object
-        if not cls.resources:
-            return
-        thing = cls.resources.pop()
-        while True:
+        while cls.resources:
+            thing = cls.resources.pop()
             LOG.debug("Deleting %r from shared resources of %s" %
                       (thing, cls.__name__))
-            # Resources in novaclient all have a delete() method
-            # which destroys the resource...
+
+            # OpenStack resources are assumed to have a delete()
+            # method which destroys the resource...
             thing.delete()
-            if not cls.resources:
-                return
-            thing = cls.resources.pop()
+
+            def is_deletion_complete():
+                # Deletion testing is only required for objects whose
+                # existence cannot be checked via retrieval.
+                if isinstance(thing, dict):
+                    return True
+                try:
+                    thing.get()
+                except Exception as e:
+                    # Clients are expected to return an exception
+                    # called 'NotFound' if retrieval fails.
+                    if e.__class__.__name__ == 'NotFound':
+                        return True
+                    raise
+                return False
+
+            # Block until resource deletion has completed or timed-out
+            test.call_until_true(is_deletion_complete, 10, 1)
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index 5094b46..1195cca 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -193,6 +193,24 @@
         cls.servers.append(server)
         return server
 
+    @classmethod
+    def create_server_with_extras(cls, name, image_id=None,
+                                  flavor=None, **kwargs):
+        # TODO(sdague) transitional function because many
+        # server tests were using extra args and resp so can't
+        # easily be ported to create_server. Will be merged
+        # later
+        if not flavor:
+            flavor = cls.flavor_ref
+        if not image_id:
+            image_id = cls.image_ref
+
+        resp, server = cls.servers_client.create_server(name,
+                                                        image_id, flavor,
+                                                        **kwargs)
+        cls.servers.append(server)
+        return resp, server
+
     def wait_for(self, condition):
         """Repeatedly calls condition() until a timeout"""
         start_time = int(time.time())
diff --git a/tempest/tests/compute/servers/test_disk_config.py b/tempest/tests/compute/servers/test_disk_config.py
index 638e093..7ff666f 100644
--- a/tempest/tests/compute/servers/test_disk_config.py
+++ b/tempest/tests/compute/servers/test_disk_config.py
@@ -39,10 +39,10 @@
     def test_rebuild_server_with_manual_disk_config(self):
         """A server should be rebuilt using the manual disk config option"""
         name = rand_name('server')
-        resp, server = self.client.create_server(name,
-                                                 self.image_ref,
-                                                 self.flavor_ref,
-                                                 disk_config='AUTO')
+        resp, server = self.create_server_with_extras(name,
+                                                      self.image_ref,
+                                                      self.flavor_ref,
+                                                      disk_config='AUTO')
 
         #Wait for the server to become active
         self.client.wait_for_server_status(server['id'], 'ACTIVE')
@@ -69,10 +69,10 @@
     def test_rebuild_server_with_auto_disk_config(self):
         """A server should be rebuilt using the auto disk config option"""
         name = rand_name('server')
-        resp, server = self.client.create_server(name,
-                                                 self.image_ref,
-                                                 self.flavor_ref,
-                                                 disk_config='MANUAL')
+        resp, server = self.create_server_with_extras(name,
+                                                      self.image_ref,
+                                                      self.flavor_ref,
+                                                      disk_config='MANUAL')
 
         #Wait for the server to become active
         self.client.wait_for_server_status(server['id'], 'ACTIVE')
@@ -100,10 +100,10 @@
     def test_resize_server_from_manual_to_auto(self):
         """A server should be resized from manual to auto disk config"""
         name = rand_name('server')
-        resp, server = self.client.create_server(name,
-                                                 self.image_ref,
-                                                 self.flavor_ref,
-                                                 disk_config='MANUAL')
+        resp, server = self.create_server_with_extras(name,
+                                                      self.image_ref,
+                                                      self.flavor_ref,
+                                                      disk_config='MANUAL')
 
         #Wait for the server to become active
         self.client.wait_for_server_status(server['id'], 'ACTIVE')
@@ -126,10 +126,10 @@
     def test_resize_server_from_auto_to_manual(self):
         """A server should be resized from auto to manual disk config"""
         name = rand_name('server')
-        resp, server = self.client.create_server(name,
-                                                 self.image_ref,
-                                                 self.flavor_ref,
-                                                 disk_config='AUTO')
+        resp, server = self.create_server_with_extras(name,
+                                                      self.image_ref,
+                                                      self.flavor_ref,
+                                                      disk_config='AUTO')
 
         #Wait for the server to become active
         self.client.wait_for_server_status(server['id'], 'ACTIVE')
diff --git a/tempest/tests/compute/servers/test_server_actions.py b/tempest/tests/compute/servers/test_server_actions.py
index 835afb0..63308fc 100644
--- a/tempest/tests/compute/servers/test_server_actions.py
+++ b/tempest/tests/compute/servers/test_server_actions.py
@@ -36,9 +36,9 @@
 
     def setUp(self):
         self.name = rand_name('server')
-        resp, server = self.client.create_server(self.name,
-                                                 self.image_ref,
-                                                 self.flavor_ref)
+        resp, server = self.create_server_with_extras(self.name,
+                                                      self.image_ref,
+                                                      self.flavor_ref)
         self.server_id = server['id']
         self.password = server['adminPass']
         self.client.wait_for_server_status(self.server_id, 'ACTIVE')
diff --git a/tempest/tests/compute/servers/test_server_metadata.py b/tempest/tests/compute/servers/test_server_metadata.py
index 844e394..0198e4e 100644
--- a/tempest/tests/compute/servers/test_server_metadata.py
+++ b/tempest/tests/compute/servers/test_server_metadata.py
@@ -80,7 +80,8 @@
             meta = {key: 'data1'}
             name = rand_name('server')
             self.assertRaises(exceptions.OverLimit,
-                              self.client.create_server, name, self.image_ref,
+                              self.create_server_with_extras,
+                              name, self.image_ref,
                               self.flavor_ref, meta=meta)
 
         # no teardown - all creates should fail
diff --git a/tempest/tests/compute/servers/test_server_personality.py b/tempest/tests/compute/servers/test_server_personality.py
index 3003a52..320bac4 100644
--- a/tempest/tests/compute/servers/test_server_personality.py
+++ b/tempest/tests/compute/servers/test_server_personality.py
@@ -41,8 +41,9 @@
             personality.append({'path': path,
                                 'contents': base64.b64encode(file_contents)})
         try:
-            self.client.create_server(name, self.image_ref, self.flavor_ref,
-                                      personality=personality)
+            self.create_server_with_extras(name, self.image_ref,
+                                           self.flavor_ref,
+                                           personality=personality)
         except exceptions.OverLimit:
             pass
         else:
@@ -61,16 +62,16 @@
             max_file_limit = \
                 self.user_client.get_specific_absolute_limit("maxPersonality")
 
-            personality = []
+            person = []
             for i in range(0, int(max_file_limit)):
                 path = 'etc/test' + str(i) + '.txt'
-                personality.append({
+                person.append({
                     'path': path,
                     'contents': base64.b64encode(file_contents),
                 })
-            resp, server = self.client.create_server(name, self.image_ref,
-                                                     self.flavor_ref,
-                                                     personality=personality)
+            resp, server = self.create_server_with_extras(name, self.image_ref,
+                                                          self.flavor_ref,
+                                                          personality=person)
             self.assertEqual('202', resp['status'])
 
         except Exception:
diff --git a/tempest/tests/compute/servers/test_servers.py b/tempest/tests/compute/servers/test_servers.py
index c534829..fcf9975 100644
--- a/tempest/tests/compute/servers/test_servers.py
+++ b/tempest/tests/compute/servers/test_servers.py
@@ -33,9 +33,10 @@
         try:
             server = None
             name = rand_name('server')
-            resp, server = self.client.create_server(name, self.image_ref,
-                                                     self.flavor_ref,
-                                                     adminPass='testpassword')
+            resp, server = self.create_server_with_extras(name, self.image_ref,
+                                                          self.flavor_ref,
+                                                          adminPass='test'
+                                                          'password')
 
             #Verify the password is set correctly in the response
             self.assertEqual('testpassword', server['adminPass'])
@@ -52,14 +53,14 @@
             id1 = None
             id2 = None
             server_name = rand_name('server')
-            resp, server = self.client.create_server(server_name,
-                                                     self.image_ref,
-                                                     self.flavor_ref)
+            resp, server = self.create_server_with_extras(server_name,
+                                                          self.image_ref,
+                                                          self.flavor_ref)
             self.client.wait_for_server_status(server['id'], 'ACTIVE')
             id1 = server['id']
-            resp, server = self.client.create_server(server_name,
-                                                     self.image_ref,
-                                                     self.flavor_ref)
+            resp, server = self.create_server_with_extras(server_name,
+                                                          self.image_ref,
+                                                          self.flavor_ref)
             self.client.wait_for_server_status(server['id'], 'ACTIVE')
             id2 = server['id']
             self.assertNotEqual(id1, id2, "Did not create a new server")
@@ -83,10 +84,10 @@
             resp, keypair = self.keypairs_client.create_keypair(key_name)
             resp, body = self.keypairs_client.list_keypairs()
             server_name = rand_name('server')
-            resp, server = self.client.create_server(server_name,
-                                                     self.image_ref,
-                                                     self.flavor_ref,
-                                                     key_name=key_name)
+            resp, server = self.create_server_with_extras(server_name,
+                                                          self.image_ref,
+                                                          self.flavor_ref,
+                                                          key_name=key_name)
             self.assertEqual('202', resp['status'])
             self.client.wait_for_server_status(server['id'], 'ACTIVE')
             resp, server = self.client.get_server(server['id'])
@@ -101,8 +102,8 @@
         try:
             server = None
             name = rand_name('server')
-            resp, server = self.client.create_server(name, self.image_ref,
-                                                     self.flavor_ref)
+            resp, server = self.create_server_with_extras(name, self.image_ref,
+                                                          self.flavor_ref)
             self.client.wait_for_server_status(server['id'], 'ACTIVE')
 
             #Update the server with a new name
@@ -128,8 +129,8 @@
         try:
             server = None
             name = rand_name('server')
-            resp, server = self.client.create_server(name, self.image_ref,
-                                                     self.flavor_ref)
+            resp, server = self.create_server_with_extras(name, self.image_ref,
+                                                          self.flavor_ref)
             self.client.wait_for_server_status(server['id'], 'ACTIVE')
 
             #Update the IPv4 and IPv6 access addresses
@@ -152,8 +153,8 @@
     def test_delete_server_while_in_building_state(self):
         """Delete a server while it's VM state is Building"""
         name = rand_name('server')
-        resp, server = self.client.create_server(name, self.image_ref,
-                                                 self.flavor_ref)
+        resp, server = self.create_server_with_extras(name, self.image_ref,
+                                                      self.flavor_ref)
         self.client.wait_for_server_status(server['id'], 'BUILD')
         resp, _ = self.client.delete_server(server['id'])
         self.assertEqual('204', resp['status'])
@@ -165,9 +166,27 @@
         super(ServersTestJSON, cls).setUpClass()
         cls.client = cls.servers_client
 
+    def tearDown(self):
+        # clean up any remaining servers and wait for them to fully
+        # delete. This is done because delete calls are async, and if
+        # deletes are running slow we could very well overrun system
+        # memory
+        self.clear_servers()
+
+        super(ServersTestJSON, self).tearDown()
+
 
 class ServersTestXML(base.BaseComputeTestXML, ServersTestBase):
     @classmethod
     def setUpClass(cls):
         super(ServersTestXML, cls).setUpClass()
         cls.client = cls.servers_client
+
+    def tearDown(self):
+        # clean up any remaining servers and wait for them to fully
+        # delete. This is done because delete calls are async, and if
+        # deletes are running slow we could very well overrun system
+        # memory
+        self.clear_servers()
+
+        super(ServersTestXML, self).tearDown()
diff --git a/tempest/tests/compute/servers/test_servers_negative.py b/tempest/tests/compute/servers/test_servers_negative.py
index 60f3daf..c9ed5ed 100644
--- a/tempest/tests/compute/servers/test_servers_negative.py
+++ b/tempest/tests/compute/servers/test_servers_negative.py
@@ -42,8 +42,9 @@
     def test_server_name_blank(self):
         """Create a server with name parameter empty"""
         try:
-                resp, server = self.client.create_server('', self.image_ref,
-                                                         self.flavor_ref)
+                resp, server = self.create_server_with_extras('',
+                                                              self.image_ref,
+                                                              self.flavor_ref)
         except exceptions.BadRequest:
             pass
         else:
@@ -53,14 +54,14 @@
     def test_personality_file_contents_not_encoded(self):
         """Use an unencoded file when creating a server with personality"""
         file_contents = 'This is a test file.'
-        personality = [{'path': '/etc/testfile.txt',
-                        'contents': file_contents}]
+        person = [{'path': '/etc/testfile.txt',
+                   'contents': file_contents}]
 
         try:
-            resp, server = self.client.create_server('test',
-                                                     self.image_ref,
-                                                     self.flavor_ref,
-                                                     personality=personality)
+            resp, server = self.create_server_with_extras('test',
+                                                          self.image_ref,
+                                                          self.flavor_ref,
+                                                          personality=person)
         except exceptions.BadRequest:
             pass
         else:
@@ -70,8 +71,8 @@
     def test_create_with_invalid_image(self):
         """Create a server with an unknown image"""
         try:
-            resp, server = self.client.create_server('fail', -1,
-                                                     self.flavor_ref)
+            resp, server = self.create_server_with_extras('fail', -1,
+                                                          self.flavor_ref)
         except exceptions.BadRequest:
             pass
         else:
@@ -81,7 +82,7 @@
     def test_create_with_invalid_flavor(self):
         """Create a server with an unknown flavor"""
         try:
-            self.client.create_server('fail', self.image_ref, -1)
+            self.create_server_with_extras('fail', self.image_ref, -1)
         except exceptions.BadRequest:
             pass
         else:
@@ -90,13 +91,13 @@
     @attr(type='negative')
     def test_invalid_access_ip_v4_address(self):
         """An access IPv4 address must match a valid address pattern"""
-        accessIPv4 = '1.1.1.1.1.1'
+        IPv4 = '1.1.1.1.1.1'
         name = rand_name('server')
         try:
-            resp, server = self.client.create_server(name,
-                                                     self.image_ref,
-                                                     self.flavor_ref,
-                                                     accessIPv4=accessIPv4)
+            resp, server = self.create_server_with_extras(name,
+                                                          self.image_ref,
+                                                          self.flavor_ref,
+                                                          accessIPv4=IPv4)
         except exceptions.BadRequest:
             pass
         else:
@@ -105,13 +106,13 @@
     @attr(type='negative')
     def test_invalid_ip_v6_address(self):
         """An access IPv6 address must match a valid address pattern"""
-        accessIPv6 = 'notvalid'
+        IPv6 = 'notvalid'
         name = rand_name('server')
         try:
-            resp, server = self.client.create_server(name,
-                                                     self.image_ref,
-                                                     self.flavor_ref,
-                                                     accessIPv6=accessIPv6)
+            resp, server = self.create_server_with_extras(name,
+                                                          self.image_ref,
+                                                          self.flavor_ref,
+                                                          accessIPv6=IPv6)
         except exceptions.BadRequest:
             pass
         else:
@@ -121,9 +122,9 @@
     def test_reboot_deleted_server(self):
         """Reboot a deleted server"""
         self.name = rand_name('server')
-        resp, create_server = self.client.create_server(self.name,
-                                                        self.image_ref,
-                                                        self.flavor_ref)
+        resp, create_server = self.create_server_with_extras(self.name,
+                                                             self.image_ref,
+                                                             self.flavor_ref)
         self.server_id = create_server['id']
         self.client.delete_server(self.server_id)
         self.client.wait_for_server_termination(self.server_id)
@@ -138,9 +139,9 @@
     def test_rebuild_deleted_server(self):
         """Rebuild a deleted server"""
         self.name = rand_name('server')
-        resp, create_server = self.client.create_server(self.name,
-                                                        self.image_ref,
-                                                        self.flavor_ref)
+        resp, create_server = self.create_server_with_extras(self.name,
+                                                             self.image_ref,
+                                                             self.flavor_ref)
         self.server_id = create_server['id']
         self.client.delete_server(self.server_id)
         self.client.wait_for_server_termination(self.server_id)
@@ -157,7 +158,8 @@
         """Create a server with a numeric name"""
 
         server_name = 12345
-        self.assertRaises(exceptions.BadRequest, self.client.create_server,
+        self.assertRaises(exceptions.BadRequest,
+                          self.create_server_with_extras,
                           server_name, self.image_ref, self.flavor_ref)
 
     @attr(type='negative')
@@ -165,7 +167,8 @@
         """Create a server with name length exceeding 256 characters"""
 
         server_name = 'a' * 256
-        self.assertRaises(exceptions.BadRequest, self.client.create_server,
+        self.assertRaises(exceptions.BadRequest,
+                          self.create_server_with_extras,
                           server_name, self.image_ref, self.flavor_ref)
 
     @attr(type='negative')
@@ -175,7 +178,8 @@
         server_name = rand_name('server')
         networks = [{'fixed_ip': '10.0.1.1', 'uuid':'a-b-c-d-e-f-g-h-i-j'}]
 
-        self.assertRaises(exceptions.BadRequest, self.client.create_server,
+        self.assertRaises(exceptions.BadRequest,
+                          self.create_server_with_extras,
                           server_name, self.image_ref, self.flavor_ref,
                           networks=networks)
 
@@ -185,7 +189,8 @@
 
         key_name = rand_name('key')
         server_name = rand_name('server')
-        self.assertRaises(exceptions.BadRequest, self.client.create_server,
+        self.assertRaises(exceptions.BadRequest,
+                          self.create_server_with_extras,
                           server_name, self.image_ref, self.flavor_ref,
                           key_name=key_name)
 
@@ -196,7 +201,8 @@
 
         server_name = rand_name('server')
         metadata = {'a': 'b' * 260}
-        self.assertRaises(exceptions.OverLimit, self.client.create_server,
+        self.assertRaises(exceptions.OverLimit,
+                          self.create_server_with_extras,
                           server_name, self.image_ref, self.flavor_ref,
                           meta=metadata)
 
diff --git a/tempest/tests/network/test_network_basic_ops.py b/tempest/tests/network/test_network_basic_ops.py
new file mode 100644
index 0000000..1d88759
--- /dev/null
+++ b/tempest/tests/network/test_network_basic_ops.py
@@ -0,0 +1,454 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# 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 logging
+import subprocess
+
+import netaddr
+import nose
+
+from quantumclient.common import exceptions as exc
+
+from tempest.common.utils.data_utils import rand_name
+from tempest import smoke
+from tempest import test
+
+
+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):
+
+    """
+    This smoke test suite assumes that Nova has been configured to
+    boot VM's with Quantum-managed networking, and attempts to
+    verify network connectivity as follows:
+
+     * For a freshly-booted VM with an IP address ("port") on a given network:
+
+       - the Tempest host can ping the IP address.  This implies that
+         the VM has been assigned the correct IP address and has
+         connectivity to the Tempest host.
+
+       #TODO(mnewby) - Need to implement the following:
+       - the Tempest host can ssh into the VM via the IP address and
+         successfully execute the following:
+
+         - ping an external IP address, implying external connectivity.
+
+         - ping an external hostname, implying that dns is correctly
+           configured.
+
+         - ping an internal IP address, implying connectivity to another
+           VM on the same network.
+
+     There are presumed to be two types of networks: tenant and
+     public.  A tenant network may or may not be reachable from the
+     Tempest host.  A public network is assumed to be reachable from
+     the Tempest host, and it should be possible to associate a public
+     ('floating') IP address with a tenant ('fixed') IP address to
+     faciliate external connectivity to a potentially unroutable
+     tenant IP address.
+
+     This test suite can be configured to test network connectivity to
+     a VM via a tenant network, a public network, or both.  If both
+     networking types are to be evaluated, tests that need to be
+     executed remotely on the VM (via ssh) will only be run against
+     one of the networks (to minimize test execution time).
+
+     Determine which types of networks to test as follows:
+
+     * Configure tenant network checks (via the
+       'tenant_networks_reachable' key) if the Tempest host should
+       have direct connectivity to tenant networks.  This is likely to
+       be the case if Tempest is running on the same host as a
+       single-node devstack installation with IP namespaces disabled.
+
+     * Configure checks for a public network if a public network has
+       been configured prior to the test suite being run and if the
+       Tempest host should have connectivity to that public network.
+       Checking connectivity for a public network requires that a
+       value be provided for 'public_network_id'.  A value can
+       optionally be provided for 'public_router_id' if tenants will
+       use a shared router to access a public network (as is likely to
+       be the case when IP namespaces are not enabled).  If a value is
+       not provided for 'public_router_id', a router will be created
+       for each tenant and use the network identified by
+       'public_network_id' as its gateway.
+
+    """
+
+    @classmethod
+    def check_preconditions(cls):
+        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:
+            raise nose.SkipTest(msg)
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestNetworkBasicOps, cls).setUpClass()
+        cls.check_preconditions()
+        cfg = cls.config.network
+        cls.tenant_id = cls.manager._get_identity_client(
+            cfg.username,
+            cfg.password,
+            cfg.tenant_name).tenant_id
+        # TODO(mnewby) Consider looking up entities as needed instead
+        # of storing them as collections on the class.
+        cls.keypairs = {}
+        cls.security_groups = {}
+        cls.networks = []
+        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.
+
+        If a public router has been configured, it will be returned.
+
+        If a public router has not been configured, but a public
+        network has, a tenant router will be created and returned that
+        routes traffic to the public network.
+
+        """
+        router_id = self.config.network.public_router_id
+        network_id = self.config.network.public_network_id
+        if router_id:
+            result = self.network_client.show_router(router_id)
+            return AttributeDict(**result['router'])
+        elif network_id:
+            router = self._create_router(tenant_id)
+            router.add_gateway(network_id)
+            return router
+        else:
+            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-')
+        body = dict(
+            router=dict(
+                name=name,
+                admin_state_up=True,
+                tenant_id=tenant_id,
+            ),
+        )
+        result = self.network_client.create_router(body=body)
+        router = DeletableRouter(client=self.network_client,
+                                 **result['router'])
+        self.assertEqual(router.name, name)
+        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 _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)
+
+    def test_002_create_security_groups(self):
+        self.security_groups[self.tenant_id] = self._create_security_group(
+            self.compute_client)
+
+    def test_003_create_networks(self):
+        network = self._create_network(self.tenant_id)
+        router = self._get_router(self.tenant_id)
+        subnet = self._create_subnet(network)
+        subnet.add_to_router(router.id)
+        self.networks.append(network)
+
+    def test_004_create_servers(self):
+        if not (self.keypairs or self.security_groups or self.networks):
+            raise nose.SkipTest('Necessary resources have not been defined')
+        for i, network in enumerate(self.networks):
+            tenant_id = network.tenant_id
+            name = rand_name('server-smoke-%d-' % i)
+            keypair_name = self.keypairs[tenant_id].name
+            security_groups = [self.security_groups[tenant_id].name]
+            server = self._create_server(self.compute_client, network,
+                                         name, keypair_name, security_groups)
+            self.servers.append(server)
+
+    def test_005_check_tenant_network_connectivity(self):
+        if not self.config.network.tenant_networks_reachable:
+            msg = 'Tenant networks not configured to be reachable.'
+            raise nose.SkipTest(msg)
+        if not self.servers:
+            raise nose.SkipTest("No VM's have been created")
+        for server in self.servers:
+            for net_name, ip_addresses in server.networks.iteritems():
+                for ip_address in ip_addresses:
+                    self.assertTrue(self._ping_ip_address(ip_address),
+                                    "Timed out waiting for %s's ip to become "
+                                    "reachable" % server.name)
+
+    def test_006_assign_floating_ips(self):
+        public_network_id = self.config.network.public_network_id
+        if not public_network_id:
+            raise nose.SkipTest('Public network not configured')
+        if not self.servers:
+            raise nose.SkipTest("No VM's have been created")
+        for server in self.servers:
+            floating_ip = self._create_floating_ip(server, public_network_id)
+            self.floating_ips.setdefault(server, [])
+            self.floating_ips[server].append(floating_ip)
+
+    def test_007_check_public_network_connectivity(self):
+        if not self.floating_ips:
+            raise nose.SkipTest('No floating ips have been allocated.')
+        for server, floating_ips in self.floating_ips.iteritems():
+            for floating_ip in floating_ips:
+                ip_address = floating_ip.floating_ip_address
+                self.assertTrue(self._ping_ip_address(ip_address),
+                                "Timed out waiting for %s's ip to become "
+                                "reachable" % server.name)
diff --git a/tempest/tests/object_storage/test_object_expiry.py b/tempest/tests/object_storage/test_object_expiry.py
new file mode 100644
index 0000000..8437d55
--- /dev/null
+++ b/tempest/tests/object_storage/test_object_expiry.py
@@ -0,0 +1,93 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# 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 nose.plugins.attrib import attr
+from tempest.common.utils.data_utils import arbitrary_string
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.tests.object_storage import base
+from time import sleep
+import unittest2 as unittest
+
+
+class ObjectExpiryTest(base.BaseObjectTest):
+
+    @classmethod
+    def setUpClass(cls):
+        super(ObjectExpiryTest, cls).setUpClass()
+
+        #Create a container
+        cls.container_name = rand_name(name='TestContainer')
+        cls.container_client.create_container(cls.container_name)
+
+    @classmethod
+    def tearDownClass(cls):
+        """The test script fails in tear down class
+        as the container contains expired objects (LP bug 1069849).
+        But delete action for the expired object is raising
+        NotFound exception and also non empty container cannot be deleted."""
+
+        #Get list of all object in the container
+        objlist = \
+            cls.container_client.list_all_container_objects(cls.container_name)
+
+        #Attempt to delete every object in the container
+        if objlist:
+            for obj in objlist:
+                resp, _ = cls.object_client.delete_object(cls.container_name,
+                                                          obj['name'])
+
+        #Attempt to delete the container
+        resp, _ = cls.container_client.delete_container(cls.container_name)
+
+    @unittest.skip('Until bug 1069849 is resolved.')
+    @attr(type='regression')
+    def test_get_object_after_expiry_time(self):
+        """GET object after expiry time"""
+        #TODO(harika-vakadi): Similar test case has to be created for
+        # "X-Delete-At", after this test case works.
+
+        #Create Object
+        object_name = rand_name(name='TestObject')
+        data = arbitrary_string()
+        resp, _ = self.object_client.create_object(self.container_name,
+                                                   object_name, data)
+
+        #Update object metadata with expiry time of 3 seconds
+        metadata = {'X-Delete-After': '3'}
+        resp, _ = \
+            self.object_client.update_object_metadata(self.container_name,
+                                                      object_name, metadata,
+                                                      metadata_prefix='')
+
+        resp, _ = \
+            self.object_client.list_object_metadata(self.container_name,
+                                                    object_name)
+
+        self.assertEqual(resp['status'], '200')
+        self.assertIn('x-delete-at', resp)
+
+        resp, body = self.object_client.get_object(self.container_name,
+                                                   object_name)
+        self.assertEqual(resp['status'], '200')
+        # Check data
+        self.assertEqual(body, data)
+        # Sleep for over 5 seconds, so that object is expired
+        sleep(5)
+        # Verification of raised exception after object gets expired
+        self.assertRaises(exceptions.NotFound, self.object_client.get_object,
+                          self.container_name, object_name)
diff --git a/tools/pip-requires b/tools/pip-requires
index 3a2283f..7877906 100644
--- a/tools/pip-requires
+++ b/tools/pip-requires
@@ -5,3 +5,4 @@
 lxml
 boto>=2.2.1
 paramiko
+netaddr