Merge "Fix colon in create volume logging output"
diff --git a/HACKING.rst b/HACKING.rst
index 1eb2d4f..03e7dc3 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -68,16 +68,34 @@
  the ``self.fail`` line as the origin of the error.
 
 Avoid constructing complex boolean expressions for assertion.
-The ``self.assertTrue`` or ``self.assertFalse`` will just tell you the
-single boolean, and you will not know anything about the values used in
-the formula. Most other assert method can include more information.
+The ``self.assertTrue`` or ``self.assertFalse`` without a ``msg`` argument,
+will just tell you the single boolean value, and you will not know anything
+about the values used in the formula, the ``msg`` argument might be good enough
+for providing more information.
+
+Most other assert method can include more information by default.
 For example ``self.assertIn`` can include the whole set.
 
+Recommended to use testtools matcher for more tricky assertion.
+`[doc] <http://testtools.readthedocs.org/en/latest/for-test-authors.html#matchers>`_
+
+You can implement your own specific matcher as well.
+`[doc] <http://testtools.readthedocs.org/en/latest/for-test-authors.html#writing-your-own-matchers>`_
+
 If the test case fails you can see the related logs and the information
 carried by the exception (exception class, backtrack and exception info).
 This and the service logs are your only guide to find the root cause of flaky
 issue.
 
+Test cases are independent
+--------------------------
+Every ``test_method`` must be callable individually and MUST NOT depends on,
+any other ``test_method`` or ``test_method`` ordering.
+
+Test cases MAY depend on commonly initialized resources/facilities, like
+credentials management, testresources and so on. These facilities, MUST be able
+to work even if just one ``test_method`` selected for execution.
+
 Guidelines
 ----------
 - Do not submit changesets with only testcases which are skipped as
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index d39ef70..8d96858 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -76,10 +76,18 @@
 flavor_ref = 1
 flavor_ref_alt = 2
 
-# User names used to authenticate to an instance for a given image.
+# User name used to authenticate to an instance
 image_ssh_user = root
+
+# Password used to authenticate to an instance
+image_ssh_password = password
+
+# User name used to authenticate to an instance using the alternate image
 image_alt_ssh_user = root
 
+# Password used to authenticate to an instance using the alternate image
+image_alt_ssh_password = password
+
 # Number of seconds to wait while looping to check the status of an
 # instance that is building.
 build_interval = 10
@@ -93,7 +101,7 @@
 #  executing the tests
 run_ssh = false
 
-# Name of a user used to authenticated to an instance
+# Name of a user used to authenticate to an instance.
 ssh_user = cirros
 
 # Visible fixed network name
@@ -150,6 +158,9 @@
 # When set to false, flavor extra data tests are forced to skip
 flavor_extra_enabled = true
 
+# Expected first device name when a volume is attached to an instance
+volume_device_name = vdb
+
 [whitebox]
 # Whitebox options for compute. Whitebox options enable the
 # whitebox test cases, which look at internal Nova database state,
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index 5f31084..7efd3c1 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -296,6 +296,24 @@
         _test_string_variations(['t', 'true', 'yes', '1'],
                                 flavor_name_public)
 
+    @attr(type='gate')
+    def test_create_flavor_using_string_ram(self):
+        flavor_name = rand_name(self.flavor_name_prefix)
+        new_flavor_id = rand_int_id(start=1000)
+
+        ram = " 1024 "
+        resp, flavor = self.client.create_flavor(flavor_name,
+                                                 ram, self.vcpus,
+                                                 self.disk,
+                                                 new_flavor_id)
+        self.addCleanup(self.flavor_clean_up, flavor['id'])
+        self.assertEqual(200, resp.status)
+        self.assertEqual(flavor['name'], flavor_name)
+        self.assertEqual(flavor['vcpus'], self.vcpus)
+        self.assertEqual(flavor['disk'], self.disk)
+        self.assertEqual(flavor['ram'], int(ram))
+        self.assertEqual(int(flavor['id']), new_flavor_id)
+
     @attr(type=['negative', 'gate'])
     def test_invalid_is_public_string(self):
         self.assertRaises(exceptions.BadRequest,
@@ -319,6 +337,26 @@
                           self.user_client.delete_flavor,
                           self.flavor_ref_alt)
 
+    @attr(type=['negative', 'gate'])
+    def test_create_flavor_using_invalid_ram(self):
+        flavor_name = rand_name(self.flavor_name_prefix)
+        new_flavor_id = rand_int_id(start=1000)
+
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.create_flavor,
+                          flavor_name, -1, self.vcpus,
+                          self.disk, new_flavor_id)
+
+    @attr(type=['negative', 'gate'])
+    def test_create_flavor_using_invalid_vcpus(self):
+        flavor_name = rand_name(self.flavor_name_prefix)
+        new_flavor_id = rand_int_id(start=1000)
+
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.create_flavor,
+                          flavor_name, self.ram, 0,
+                          self.disk, new_flavor_id)
+
 
 class FlavorsAdminTestXML(FlavorsAdminTestJSON):
     _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs.py b/tempest/api/compute/admin/test_flavors_extra_specs.py
index 7b79a12..f2f82b5 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -58,6 +58,7 @@
     @classmethod
     def tearDownClass(cls):
         resp, body = cls.client.delete_flavor(cls.flavor['id'])
+        cls.client.wait_for_resource_deletion(cls.flavor['id'])
         super(FlavorsExtraSpecsTestJSON, cls).tearDownClass()
 
     @attr(type='gate')
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index acf0275..09d9bc0 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -73,6 +73,8 @@
         cls.build_interval = cls.config.compute.build_interval
         cls.build_timeout = cls.config.compute.build_timeout
         cls.ssh_user = cls.config.compute.ssh_user
+        cls.image_ssh_user = cls.config.compute.image_ssh_user
+        cls.image_ssh_password = cls.config.compute.image_ssh_password
         cls.image_ref = cls.config.compute.image_ref
         cls.image_ref_alt = cls.config.compute.image_ref_alt
         cls.flavor_ref = cls.config.compute.flavor_ref
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 76ef461..5ea771b 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -225,7 +225,7 @@
             resp, output = self.servers_client.get_console_output(
                 self.server_id, 10)
             self.assertEqual(200, resp.status)
-            self.assertIsNotNone(output)
+            self.assertTrue(output, "Console output was empty.")
             lines = len(output.split('\n'))
             self.assertEqual(lines, 10)
         self.wait_for(get_output)
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index b67a5e0..5c1ad0d 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -26,6 +26,7 @@
 class AttachVolumeTestJSON(base.BaseComputeTest):
     _interface = 'json'
     run_ssh = tempest.config.TempestConfig().compute.run_ssh
+    device = tempest.config.TempestConfig().compute.volume_device_name
 
     def __init__(self, *args, **kwargs):
         super(AttachVolumeTestJSON, self).__init__(*args, **kwargs)
@@ -36,7 +37,7 @@
     @classmethod
     def setUpClass(cls):
         super(AttachVolumeTestJSON, cls).setUpClass()
-        cls.device = 'vdb'
+
         if not cls.config.service_available.cinder:
             skip_msg = ("%s skipped as Cinder is not available" % cls.__name__)
             raise cls.skipException(skip_msg)
@@ -54,7 +55,7 @@
     def _create_and_attach(self):
         # Start a server and wait for it to become ready
         resp, server = self.create_server(wait_until='ACTIVE',
-                                          adminPass='password')
+                                          adminPass=self.image_ssh_password)
         self.server = server
 
         # Record addresses so that we can ssh later
@@ -92,7 +93,7 @@
         self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
 
         linux_client = RemoteClient(server,
-                                    self.ssh_user, server['adminPass'])
+                                    self.image_ssh_user, server['adminPass'])
         partitions = linux_client.get_partitions()
         self.assertIn(self.device, partitions)
 
@@ -106,7 +107,7 @@
         self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
 
         linux_client = RemoteClient(server,
-                                    self.ssh_user, server['adminPass'])
+                                    self.image_ssh_user, server['adminPass'])
         partitions = linux_client.get_partitions()
         self.assertNotIn(self.device, partitions)
 
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
new file mode 100644
index 0000000..017864f
--- /dev/null
+++ b/tempest/api/network/test_floating_ips.py
@@ -0,0 +1,135 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from tempest.api.network import base
+from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
+
+
+class FloatingIPTest(base.BaseNetworkTest):
+    _interface = 'json'
+
+    """
+    Tests the following operations in the Quantum API using the REST client for
+    Quantum:
+
+        Create a Floating IP
+        Update a Floating IP
+        Delete a Floating IP
+        List all Floating IPs
+        Show Floating IP details
+
+    v2.0 of the Quantum API is assumed. It is also assumed that the following
+    options are defined in the [network] section of etc/tempest.conf:
+
+        public_network_id which is the id for the external network present
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        super(FloatingIPTest, cls).setUpClass()
+        cls.ext_net_id = cls.config.network.public_network_id
+
+        # Create network, subnet, router and add interface
+        cls.network = cls.create_network()
+        cls.subnet = cls.create_subnet(cls.network)
+        resp, router = cls.client.create_router(
+            rand_name('router-'),
+            external_gateway_info={"network_id":
+                                   cls.network_cfg.public_network_id})
+        cls.router = router['router']
+        resp, _ = cls.client.add_router_interface_with_subnet_id(
+            cls.router['id'], cls.subnet['id'])
+        cls.port = list()
+        # Create two ports one each for Creation and Updating of floatingIP
+        for i in range(2):
+            resp, port = cls.client.create_port(cls.network['id'])
+            cls.port.append(port['port'])
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.client.remove_router_interface_with_subnet_id(cls.router['id'],
+                                                          cls.subnet['id'])
+        for i in range(2):
+            cls.client.delete_port(cls.port[i]['id'])
+        cls.client.delete_router(cls.router['id'])
+        super(FloatingIPTest, cls).tearDownClass()
+
+    def _delete_floating_ip(self, floating_ip_id):
+        # Deletes a floating IP and verifies if it is deleted or not
+        resp, _ = self.client.delete_floating_ip(floating_ip_id)
+        self.assertEqual(204, resp.status)
+        # Asserting that the floating_ip is not found in list after deletion
+        resp, floating_ips = self.client.list_floating_ips()
+        floatingip_id_list = list()
+        for f in floating_ips['floatingips']:
+            floatingip_id_list.append(f['id'])
+        self.assertNotIn(floating_ip_id, floatingip_id_list)
+
+    @attr(type='smoke')
+    def test_create_list_show_update_delete_floating_ip(self):
+        # Creates a floating IP
+        resp, floating_ip = self.client.create_floating_ip(
+            self.ext_net_id, port_id=self.port[0]['id'])
+        self.assertEqual('201', resp['status'])
+        create_floating_ip = floating_ip['floatingip']
+        self.assertIsNotNone(create_floating_ip['id'])
+        self.assertIsNotNone(create_floating_ip['tenant_id'])
+        self.assertIsNotNone(create_floating_ip['floating_ip_address'])
+        self.assertEqual(create_floating_ip['port_id'], self.port[0]['id'])
+        self.assertEqual(create_floating_ip['floating_network_id'],
+                         self.ext_net_id)
+        self.addCleanup(self._delete_floating_ip, create_floating_ip['id'])
+        # Verifies the details of a floating_ip
+        resp, floating_ip = self.client.show_floating_ip(
+            create_floating_ip['id'])
+        self.assertEqual('200', resp['status'])
+        show_floating_ip = floating_ip['floatingip']
+        self.assertEqual(show_floating_ip['id'], create_floating_ip['id'])
+        self.assertEqual(show_floating_ip['floating_network_id'],
+                         self.ext_net_id)
+        self.assertEqual(show_floating_ip['tenant_id'],
+                         create_floating_ip['tenant_id'])
+        self.assertEqual(show_floating_ip['floating_ip_address'],
+                         create_floating_ip['floating_ip_address'])
+        self.assertEqual(show_floating_ip['port_id'], self.port[0]['id'])
+
+        # Verify the floating ip exists in the list of all floating_ips
+        resp, floating_ips = self.client.list_floating_ips()
+        self.assertEqual('200', resp['status'])
+        floatingip_id_list = list()
+        for f in floating_ips['floatingips']:
+            floatingip_id_list.append(f['id'])
+        self.assertIn(create_floating_ip['id'], floatingip_id_list)
+
+        # Associate floating IP to the other port
+        resp, floating_ip = self.client.update_floating_ip(
+            create_floating_ip['id'], port_id=self.port[1]['id'])
+        self.assertEqual('200', resp['status'])
+        update_floating_ip = floating_ip['floatingip']
+        self.assertEqual(update_floating_ip['port_id'], self.port[1]['id'])
+        self.assertIsNotNone(update_floating_ip['fixed_ip_address'])
+        self.assertEqual(update_floating_ip['router_id'], self.router['id'])
+
+        # Disassociate floating IP from the port
+        resp, floating_ip = self.client.update_floating_ip(
+            create_floating_ip['id'], port_id=None)
+        self.assertEqual('200', resp['status'])
+        update_floating_ip = floating_ip['floatingip']
+        self.assertIsNone(update_floating_ip['port_id'])
+        self.assertIsNone(update_floating_ip['fixed_ip_address'])
+        self.assertIsNone(update_floating_ip['router_id'])
diff --git a/tempest/api/object_storage/test_container_staticweb.py b/tempest/api/object_storage/test_container_staticweb.py
new file mode 100644
index 0000000..d07697a
--- /dev/null
+++ b/tempest/api/object_storage/test_container_staticweb.py
@@ -0,0 +1,94 @@
+# Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
+#
+# Author: Joe H. Rahme <joe.hakim.rahme@enovance.com>
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.object_storage import base
+from tempest.common.utils.data_utils import arbitrary_string
+from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
+
+
+class StaticWebTest(base.BaseObjectTest):
+
+    @classmethod
+    def setUpClass(cls):
+        super(StaticWebTest, cls).setUpClass()
+        cls.container_name = rand_name(name="TestContainer")
+
+        # This header should be posted on the container before every test
+        cls.headers_public_read_acl = {'Read': '.r:*'}
+
+        # Create test container and create one object in it
+        cls.container_client.create_container(cls.container_name)
+        cls.object_name = rand_name(name="TestObject")
+        cls.object_data = arbitrary_string()
+        cls.object_client.create_object(cls.container_name,
+                                        cls.object_name,
+                                        cls.object_data)
+
+        cls.container_client.update_container_metadata(
+            cls.container_name,
+            metadata=cls.headers_public_read_acl,
+            metadata_prefix="X-Container-")
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.delete_containers([cls.container_name])
+        cls.data.teardown_all()
+        super(StaticWebTest, cls).tearDownClass()
+
+    @attr('gate')
+    def test_web_index(self):
+        headers = {'web-index': self.object_name}
+
+        self.container_client.update_container_metadata(
+            self.container_name, metadata=headers)
+
+        # test GET on http://account_url/container_name
+        # we should retrieve the self.object_name file
+        resp, body = self.custom_account_client.request("GET",
+                                                        self.container_name)
+        self.assertEqual(resp['status'], '200')
+        self.assertEqual(body, self.object_data)
+
+        # clean up before exiting
+        self.container_client.update_container_metadata(self.container_name,
+                                                        {'web-index': ""})
+
+        _, body = self.container_client.list_container_metadata(
+            self.container_name)
+        self.assertNotIn('x-container-meta-web-index', body)
+
+    @attr('gate')
+    def test_web_listing(self):
+        headers = {'web-listings': 'true'}
+
+        self.container_client.update_container_metadata(
+            self.container_name, metadata=headers)
+
+        # test GET on http://account_url/container_name
+        # we should retrieve a listing of objects
+        resp, body = self.custom_account_client.request("GET",
+                                                        self.container_name)
+        self.assertEqual(resp['status'], '200')
+        self.assertIn(self.object_name, body)
+
+        # clean up before exiting
+        self.container_client.update_container_metadata(self.container_name,
+                                                        {'web-listings': ""})
+
+        _, body = self.container_client.list_container_metadata(
+            self.container_name)
+        self.assertNotIn('x-container-meta-web-listings', body)
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 822f691..b15f8dd 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -41,6 +41,7 @@
     def _delete_volume(self, volume_id):
         resp, _ = self.volumes_client.delete_volume(volume_id)
         self.assertEqual(202, resp.status)
+        self.volumes_client.wait_for_resource_deletion(volume_id)
 
     def _delete_volume_type(self, volume_type_id):
         resp, _ = self.client.delete_volume_type(volume_type_id)
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 9fa86b6..960785d 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -86,7 +86,7 @@
         resp, volume = self.client.get_volume(self.volume['id'])
         self.assertEqual(200, resp.status)
         self.assertIn('attachments', volume)
-        attachment = volume['attachments'][0]
+        attachment = self.client.get_attachment_from_volume(volume)
         self.assertEqual(mountpoint, attachment['device'])
         self.assertEqual(self.server['id'], attachment['server_id'])
         self.assertEqual(self.volume['id'], attachment['id'])
@@ -105,3 +105,7 @@
         self.assertEqual(202, resp.status)
         self.image_client.wait_for_image_status(image_id, 'active')
         self.client.wait_for_volume_status(self.volume['id'], 'available')
+
+
+class VolumesActionsTestXML(VolumesActionsTest):
+    _interface = "xml"
diff --git a/tempest/common/ssh.py b/tempest/common/ssh.py
index 2ed1057..2052705 100644
--- a/tempest/common/ssh.py
+++ b/tempest/common/ssh.py
@@ -56,7 +56,7 @@
             paramiko.AutoAddPolicy())
         _start_time = time.time()
 
-        while not self._is_timed_out(self.timeout, _start_time):
+        while not self._is_timed_out(_start_time):
             try:
                 ssh.connect(self.host, username=self.username,
                             password=self.password,
@@ -76,8 +76,8 @@
                                         password=self.password)
         return ssh
 
-    def _is_timed_out(self, timeout, start_time):
-        return (time.time() - timeout) > start_time
+    def _is_timed_out(self, start_time):
+        return (time.time() - self.timeout) > start_time
 
     def connect_until_closed(self):
         """Connect to the server and wait until connection is lost."""
@@ -85,10 +85,10 @@
             ssh = self._get_ssh_connection()
             _transport = ssh.get_transport()
             _start_time = time.time()
-            _timed_out = self._is_timed_out(self.timeout, _start_time)
+            _timed_out = self._is_timed_out(_start_time)
             while _transport.is_active() and not _timed_out:
                 time.sleep(5)
-                _timed_out = self._is_timed_out(self.timeout, _start_time)
+                _timed_out = self._is_timed_out(_start_time)
             ssh.close()
         except (EOFError, paramiko.AuthenticationException, socket.error):
             return
@@ -119,7 +119,7 @@
         while True:
             ready = poll.poll(self.channel_timeout)
             if not any(ready):
-                if not self._is_timed_out(self.timeout, start_time):
+                if not self._is_timed_out(start_time):
                     continue
                 raise exceptions.TimeoutException(
                     "Command: '{0}' executed on host '{1}'.".format(
diff --git a/tempest/config.py b/tempest/config.py
index 3b09b5e..7245b10 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -121,10 +121,17 @@
     cfg.StrOpt('image_ssh_user',
                default="root",
                help="User name used to authenticate to an instance."),
+    cfg.StrOpt('image_ssh_password',
+               default="password",
+               help="Password used to authenticate to an instance."),
     cfg.StrOpt('image_alt_ssh_user',
                default="root",
                help="User name used to authenticate to an instance using "
                     "the alternate image."),
+    cfg.StrOpt('image_alt_ssh_password',
+               default="password",
+               help="Password used to authenticate to an instance using "
+                    "the alternate image."),
     cfg.BoolOpt('resize_available',
                 default=False,
                 help="Does the test environment support resizing?"),
@@ -196,6 +203,10 @@
     cfg.BoolOpt('flavor_extra_enabled',
                 default=True,
                 help="If false, skip flavor extra data test"),
+    cfg.StrOpt('volume_device_name',
+               default='vdb',
+               help="Expected device name when a volume is attached to "
+                    "an instance")
 ]
 
 
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index 588dc8f..ef12a00 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -24,7 +24,7 @@
     V1 API has been removed from the code base.
 
     Implements create, delete, update, list and show for the basic Neutron
-    abstractions (networks, sub-networks, routers and ports):
+    abstractions (networks, sub-networks, routers, ports and floating IP):
 
     Implements add/remove interface to router using subnet ID / port ID
 
@@ -285,3 +285,39 @@
         resp, body = self.put(uri, update_body, self.headers)
         body = json.loads(body)
         return resp, body
+
+    def create_floating_ip(self, ext_network_id, **kwargs):
+        post_body = {
+            'floatingip': kwargs}
+        post_body['floatingip']['floating_network_id'] = ext_network_id
+        body = json.dumps(post_body)
+        uri = '%s/floatingips' % (self.uri_prefix)
+        resp, body = self.post(uri, headers=self.headers, body=body)
+        body = json.loads(body)
+        return resp, body
+
+    def show_floating_ip(self, floating_ip_id):
+        uri = '%s/floatingips/%s' % (self.uri_prefix, floating_ip_id)
+        resp, body = self.get(uri, self.headers)
+        body = json.loads(body)
+        return resp, body
+
+    def list_floating_ips(self):
+        uri = '%s/floatingips' % (self.uri_prefix)
+        resp, body = self.get(uri, self.headers)
+        body = json.loads(body)
+        return resp, body
+
+    def delete_floating_ip(self, floating_ip_id):
+        uri = '%s/floatingips/%s' % (self.uri_prefix, floating_ip_id)
+        resp, body = self.delete(uri, self.headers)
+        return resp, body
+
+    def update_floating_ip(self, floating_ip_id, **kwargs):
+        post_body = {
+            'floatingip': kwargs}
+        body = json.dumps(post_body)
+        uri = '%s/floatingips/%s' % (self.uri_prefix, floating_ip_id)
+        resp, body = self.put(uri, headers=self.headers, body=body)
+        body = json.loads(body)
+        return resp, body
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index c22b398..2ae73b1 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -36,6 +36,10 @@
         self.build_interval = self.config.volume.build_interval
         self.build_timeout = self.config.volume.build_timeout
 
+    def get_attachment_from_volume(self, volume):
+        """Return the element 'attachment' from input volumes."""
+        return volume['attachments'][0]
+
     def list_volumes(self, params=None):
         """List all the volumes created."""
         url = 'volumes'
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index eaa3ae0..936e036 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -56,6 +56,10 @@
                 vol[tag] = xml_to_json(child)
         return vol
 
+    def get_attachment_from_volume(self, volume):
+        """Return the element 'attachment' from input volumes."""
+        return volume['attachments']['attachment']
+
     def list_volumes(self, params=None):
         """List all the volumes created."""
         url = 'volumes'
@@ -157,3 +161,33 @@
         except exceptions.NotFound:
             return True
         return False
+
+    def attach_volume(self, volume_id, instance_uuid, mountpoint):
+        """Attaches a volume to a given instance on a given mountpoint."""
+        post_body = Element("os-attach",
+                            instance_uuid=instance_uuid,
+                            mountpoint=mountpoint
+                            )
+        url = 'volumes/%s/action' % str(volume_id)
+        resp, body = self.post(url, str(Document(post_body)), self.headers)
+        if body:
+            body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def detach_volume(self, volume_id):
+        """Detaches a volume from an instance."""
+        post_body = Element("os-detach")
+        url = 'volumes/%s/action' % str(volume_id)
+        resp, body = self.post(url, str(Document(post_body)), self.headers)
+        if body:
+            body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def upload_volume(self, volume_id, image_name):
+        """Uploads a volume in Glance."""
+        post_body = Element("os-volume_upload_image",
+                            image_name=image_name)
+        url = 'volumes/%s/action' % str(volume_id)
+        resp, body = self.post(url, str(Document(post_body)), self.headers)
+        volume = xml_to_json(etree.fromstring(body))
+        return resp, volume