Merge "Fix bug in validating Swift transaction ID"
diff --git a/requirements.txt b/requirements.txt
index e070549..0c4a659 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -14,6 +14,7 @@
 python-neutronclient>=2.3.0,<3
 python-cinderclient>=1.0.6
 python-heatclient>=0.2.3
+python-swiftclient>=1.5
 testresources>=0.2.4
 keyring>=1.6.1,<2.0
 testrepository>=0.0.17
diff --git a/tempest/api/volume/test_volume_metadata.py b/tempest/api/volume/test_volume_metadata.py
new file mode 100644
index 0000000..0909ade
--- /dev/null
+++ b/tempest/api/volume/test_volume_metadata.py
@@ -0,0 +1,124 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Huawei Technologies Co.,LTD
+# 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.volume.base import BaseVolumeTest
+from tempest import test
+
+
+class VolumeMetadataTest(BaseVolumeTest):
+    _interface = "json"
+
+    @classmethod
+    def setUpClass(cls):
+        super(VolumeMetadataTest, cls).setUpClass()
+        # Create a volume
+        cls.volume = cls.create_volume()
+        cls.volume_id = cls.volume['id']
+
+    @classmethod
+    def tearDownClass(cls):
+        super(VolumeMetadataTest, cls).tearDownClass()
+
+    def tearDown(self):
+        # Update the metadata to {}
+        self.volumes_client.update_volume_metadata(self.volume_id, {})
+        super(VolumeMetadataTest, self).tearDown()
+
+    @test.attr(type='gate')
+    def test_create_get_delete_volume_metadata(self):
+        # Create metadata for the volume
+        metadata = {"key1": "value1",
+                    "key2": "value2",
+                    "key3": "value3"}
+
+        rsp, body = self.volumes_client.create_volume_metadata(self.volume_id,
+                                                               metadata)
+        self.assertEqual(200, rsp.status)
+        # Get the metadata of the volume
+        resp, body = self.volumes_client.get_volume_metadata(self.volume_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(metadata, body)
+        # Delete one item metadata of the volume
+        rsp, body = self.volumes_client.delete_volume_metadata_item(
+            self.volume_id,
+            "key1")
+        self.assertEqual(200, rsp.status)
+        resp, body = self.volumes_client.get_volume_metadata(self.volume_id)
+        self.assertNotIn("key1", body)
+
+    @test.attr(type='gate')
+    def test_update_volume_metadata(self):
+        # Update metadata for the volume
+        metadata = {"key1": "value1",
+                    "key2": "value2",
+                    "key3": "value3"}
+
+        update = {"key4": "value4",
+                  "key1": "value1_update"}
+
+        # Create metadata for the volume
+        resp, body = self.volumes_client.create_volume_metadata(
+            self.volume_id,
+            metadata)
+        self.assertEqual(200, resp.status)
+        # Get the metadata of the volume
+        resp, body = self.volumes_client.get_volume_metadata(self.volume_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(metadata, body)
+        # Update metadata
+        resp, body = self.volumes_client.update_volume_metadata(
+            self.volume_id,
+            update)
+        self.assertEqual(200, resp.status)
+        # Get the metadata of the volume
+        resp, body = self.volumes_client.get_volume_metadata(self.volume_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(update, body)
+
+    @test.attr(type='gate')
+    def test_update_volume_metadata_item(self):
+        # Update metadata item for the volume
+        metadata = {"key1": "value1",
+                    "key2": "value2",
+                    "key3": "value3"}
+        create_expect = {"key1": "value1",
+                         "key2": "value2",
+                         "key3": "value3"}
+        update_item = {"key3": "value3_update"}
+        expect = {"key1": "value1",
+                  "key2": "value2",
+                  "key3": "value3_update"}
+        # Create metadata for the volume
+        resp, body = self.volumes_client.create_volume_metadata(
+            self.volume_id,
+            metadata)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(create_expect, body)
+        # Update metadata item
+        resp, body = self.volumes_client.update_volume_metadata_item(
+            self.volume_id,
+            "key3",
+            update_item)
+        self.assertEqual(200, resp.status)
+        # Get the metadata of the volume
+        resp, body = self.volumes_client.get_volume_metadata(self.volume_id)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(expect, body)
+
+
+class VolumeMetadataTestXML(VolumeMetadataTest):
+    _interface = "xml"
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index 967dc09..afba4b0 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -263,3 +263,40 @@
         resp, body = self.post('volumes/%s/action' % volume_id, post_body,
                                self.headers)
         return resp, body
+
+    def create_volume_metadata(self, volume_id, metadata):
+        """Create metadata for the volume."""
+        put_body = json.dumps({'metadata': metadata})
+        url = "volumes/%s/metadata" % str(volume_id)
+        resp, body = self.post(url, put_body, self.headers)
+        body = json.loads(body)
+        return resp, body['metadata']
+
+    def get_volume_metadata(self, volume_id):
+        """Get metadata of the volume."""
+        url = "volumes/%s/metadata" % str(volume_id)
+        resp, body = self.get(url, self.headers)
+        body = json.loads(body)
+        return resp, body['metadata']
+
+    def update_volume_metadata(self, volume_id, metadata):
+        """Update metadata for the volume."""
+        put_body = json.dumps({'metadata': metadata})
+        url = "volumes/%s/metadata" % str(volume_id)
+        resp, body = self.put(url, put_body, self.headers)
+        body = json.loads(body)
+        return resp, body['metadata']
+
+    def update_volume_metadata_item(self, volume_id, id, meta_item):
+        """Update metadata item for the volume."""
+        put_body = json.dumps({'meta': meta_item})
+        url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
+        resp, body = self.put(url, put_body, self.headers)
+        body = json.loads(body)
+        return resp, body['meta']
+
+    def delete_volume_metadata_item(self, volume_id, id):
+        """Delete metadata item for the volume."""
+        url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
+        resp, body = self.delete(url, self.headers)
+        return resp, body
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index 1fc63e9..f175138 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -356,3 +356,57 @@
         if body:
             body = xml_to_json(etree.fromstring(body))
         return resp, body
+
+    def _metadata_body(self, meta):
+        post_body = Element('metadata')
+        for k, v in meta.items():
+            data = Element('meta', key=k)
+            data.append(Text(v))
+            post_body.append(data)
+        return post_body
+
+    def _parse_key_value(self, node):
+        """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
+        data = {}
+        for node in node.getchildren():
+            data[node.get('key')] = node.text
+        return data
+
+    def create_volume_metadata(self, volume_id, metadata):
+        """Create metadata for the volume."""
+        post_body = self._metadata_body(metadata)
+        resp, body = self.post('volumes/%s/metadata' % volume_id,
+                               str(Document(post_body)),
+                               self.headers)
+        body = self._parse_key_value(etree.fromstring(body))
+        return resp, body
+
+    def get_volume_metadata(self, volume_id):
+        """Get metadata of the volume."""
+        url = "volumes/%s/metadata" % str(volume_id)
+        resp, body = self.get(url, self.headers)
+        body = self._parse_key_value(etree.fromstring(body))
+        return resp, body
+
+    def update_volume_metadata(self, volume_id, metadata):
+        """Update metadata for the volume."""
+        put_body = self._metadata_body(metadata)
+        url = "volumes/%s/metadata" % str(volume_id)
+        resp, body = self.put(url, str(Document(put_body)), self.headers)
+        body = self._parse_key_value(etree.fromstring(body))
+        return resp, body
+
+    def update_volume_metadata_item(self, volume_id, id, meta_item):
+        """Update metadata item for the volume."""
+        for k, v in meta_item.items():
+            put_body = Element('meta', key=k)
+            put_body.append(Text(v))
+        url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
+        resp, body = self.put(url, str(Document(put_body)), self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        return resp, body
+
+    def delete_volume_metadata_item(self, volume_id, id):
+        """Delete metadata item for the volume."""
+        url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
+        return self.delete(url)