Merge "Fix OS_TEST_PATH in .testr.conf"
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index 3a41f4f..7ce8ca6 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -25,7 +25,7 @@
 
     """
     Tests the following operations in the Quantum API using the REST client for
-    Quantum:
+    Neutron:
 
         Create a Floating IP
         Update a Floating IP
@@ -33,7 +33,7 @@
         List all Floating IPs
         Show Floating IP details
 
-    v2.0 of the Quantum API is assumed. It is also assumed that the following
+    v2.0 of the Neutron 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
@@ -47,24 +47,13 @@
         # Create network, subnet, router and add interface
         cls.network = cls.create_network()
         cls.subnet = cls.create_subnet(cls.network)
-        cls.router = cls.create_router(
-            data_utils.rand_name('router-'),
-            external_network_id=cls.network_cfg.public_network_id)
-        resp, _ = cls.client.add_router_interface_with_subnet_id(
-            cls.router['id'], cls.subnet['id'])
+        cls.router = cls.create_router(data_utils.rand_name('router-'),
+                                       external_network_id=cls.ext_net_id)
+        cls.create_router_interface(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'])
-        super(FloatingIPTestJSON, cls).tearDownClass()
+            cls.create_port(cls.network)
 
     def _delete_floating_ip(self, floating_ip_id):
         # Deletes a floating IP and verifies if it is deleted or not
@@ -81,29 +70,29 @@
     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.ext_net_id, port_id=self.ports[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'],
+        created_floating_ip = floating_ip['floatingip']
+        self.assertIsNotNone(created_floating_ip['id'])
+        self.assertIsNotNone(created_floating_ip['tenant_id'])
+        self.assertIsNotNone(created_floating_ip['floating_ip_address'])
+        self.assertEqual(created_floating_ip['port_id'], self.ports[0]['id'])
+        self.assertEqual(created_floating_ip['floating_network_id'],
                          self.ext_net_id)
-        self.addCleanup(self._delete_floating_ip, create_floating_ip['id'])
+        self.addCleanup(self._delete_floating_ip, created_floating_ip['id'])
         # Verifies the details of a floating_ip
         resp, floating_ip = self.client.show_floating_ip(
-            create_floating_ip['id'])
+            created_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'],
+        shown_floating_ip = floating_ip['floatingip']
+        self.assertEqual(shown_floating_ip['id'], created_floating_ip['id'])
+        self.assertEqual(shown_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'])
+        self.assertEqual(shown_floating_ip['tenant_id'],
+                         created_floating_ip['tenant_id'])
+        self.assertEqual(shown_floating_ip['floating_ip_address'],
+                         created_floating_ip['floating_ip_address'])
+        self.assertEqual(shown_floating_ip['port_id'], self.ports[0]['id'])
 
         # Verify the floating ip exists in the list of all floating_ips
         resp, floating_ips = self.client.list_floatingips()
@@ -111,25 +100,25 @@
         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)
-
+        self.assertIn(created_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'])
+            created_floating_ip['id'], port_id=self.ports[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'])
+        updated_floating_ip = floating_ip['floatingip']
+        self.assertEqual(updated_floating_ip['port_id'], self.ports[1]['id'])
+        self.assertEqual(updated_floating_ip['fixed_ip_address'],
+                         self.ports[1]['fixed_ips'][0]['ip_address'])
+        self.assertEqual(updated_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)
+            created_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'])
+        updated_floating_ip = floating_ip['floatingip']
+        self.assertIsNone(updated_floating_ip['port_id'])
+        self.assertIsNone(updated_floating_ip['fixed_ip_address'])
+        self.assertIsNone(updated_floating_ip['router_id'])
 
 
 class FloatingIPTestXML(FloatingIPTestJSON):
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 2f28068..d3d34d0 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -24,12 +24,14 @@
 import cinderclient.client
 import glanceclient
 import heatclient.client
+import keystoneclient.apiclient.exceptions
 import keystoneclient.v2_0.client
 import netaddr
 from neutronclient.common import exceptions as exc
 import neutronclient.v2_0.client
 import novaclient.client
 from novaclient import exceptions as nova_exceptions
+import swiftclient
 
 from tempest.api.network import common as net_common
 from tempest.common import isolated_creds
@@ -74,6 +76,10 @@
         self.volume_client = self._get_volume_client(username,
                                                      password,
                                                      tenant_name)
+        self.object_storage_client = self._get_object_storage_client(
+            username,
+            password,
+            tenant_name)
         self.orchestration_client = self._get_orchestration_client(
             username,
             password,
@@ -122,6 +128,30 @@
                                           region_name=region,
                                           http_log_debug=True)
 
+    def _get_object_storage_client(self, username, password, tenant_name):
+        auth_url = self.config.identity.uri
+        # add current tenant to Member group.
+        keystone_admin = self._get_identity_client(
+            self.config.identity.admin_username,
+            self.config.identity.admin_password,
+            self.config.identity.admin_tenant_name)
+
+        # enable test user to operate swift by adding Member role to him.
+        roles = keystone_admin.roles.list()
+        member_role = [role for role in roles if role.name == 'Member'][0]
+        # NOTE(maurosr): This is surrounded in the try-except block cause
+        # neutron tests doesn't have tenant isolation.
+        try:
+            keystone_admin.roles.add_user_role(self.identity_client.user_id,
+                                               member_role.id,
+                                               self.identity_client.tenant_id)
+        except keystoneclient.apiclient.exceptions.Conflict:
+            pass
+
+        return swiftclient.Connection(auth_url, username, password,
+                                      tenant_name=tenant_name,
+                                      auth_version='2')
+
     def _get_orchestration_client(self, username=None, password=None,
                                   tenant_name=None):
         if not username:
@@ -215,6 +245,7 @@
         cls.identity_client = cls.manager.identity_client
         cls.network_client = cls.manager.network_client
         cls.volume_client = cls.manager.volume_client
+        cls.object_storage_client = cls.manager.object_storage_client
         cls.orchestration_client = cls.manager.orchestration_client
         cls.resource_keys = {}
         cls.os_resources = []
diff --git a/tempest/scenario/test_swift_basic_ops.py b/tempest/scenario/test_swift_basic_ops.py
new file mode 100644
index 0000000..6763503
--- /dev/null
+++ b/tempest/scenario/test_swift_basic_ops.py
@@ -0,0 +1,101 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+# 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.common.utils.data_utils import rand_name
+from tempest.openstack.common import log as logging
+from tempest.scenario import manager
+from tempest.test import services
+
+LOG = logging.getLogger(__name__)
+
+
+class TestSwiftBasicOps(manager.OfficialClientTest):
+    """
+    Test swift with the follow operations:
+     * get swift stat.
+     * create container.
+     * upload a file to the created container.
+     * list container's objects and assure that the uploaded file is present.
+     * delete object from container.
+     * list container's objects and assure that the deleted file is gone.
+     * delete a container.
+     * list containers and assure that the deleted container is gone.
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestSwiftBasicOps, cls).setUpClass()
+        if not cls.config.service_available.swift:
+            skip_msg = ("%s skipped as swift is not available" %
+                        cls.__name__)
+            raise cls.skipException(skip_msg)
+
+    def _get_swift_stat(self):
+        """get swift status for our user account."""
+        self.object_storage_client.get_account()
+        LOG.debug('Swift status information obtained successfully')
+
+    def _create_container(self, container_name=None):
+        name = container_name or rand_name('swift-scenario-container')
+        self.object_storage_client.put_container(name)
+        # look for the container to assure it is created
+        self._list_and_check_container_objects(name)
+        LOG.debug('Container %s created' % (name))
+        return name
+
+    def _delete_container(self, container_name):
+        self.object_storage_client.delete_container(container_name)
+        LOG.debug('Container %s deleted' % (container_name))
+
+    def _upload_object_to_container(self, container_name, obj_name=None):
+        obj_name = obj_name or rand_name('swift-scenario-object')
+        self.object_storage_client.put_object(container_name, obj_name,
+                                              rand_name('obj_data'),
+                                              content_type='text/plain')
+        return obj_name
+
+    def _delete_object(self, container_name, filename):
+        self.object_storage_client.delete_object(container_name, filename)
+        self._list_and_check_container_objects(container_name,
+                                               not_present_obj=[filename])
+
+    def _list_and_check_container_objects(self, container_name, present_obj=[],
+                                          not_present_obj=[]):
+        """
+        List objects for a given container and assert which are present and
+        which are not.
+        """
+        meta, response = self.object_storage_client.get_container(
+            container_name)
+        # create a list with file name only
+        object_list = [obj['name'] for obj in response]
+        if present_obj:
+            for obj in present_obj:
+                self.assertIn(obj, object_list)
+        if not_present_obj:
+            for obj in not_present_obj:
+                self.assertNotIn(obj, object_list)
+
+    @services('object')
+    def test_swift_basic_ops(self):
+        self._get_swift_stat()
+        container_name = self._create_container()
+        obj_name = self._upload_object_to_container(container_name)
+        self._list_and_check_container_objects(container_name, [obj_name])
+        self._delete_object(container_name, obj_name)
+        self._delete_container(container_name)
diff --git a/tempest/services/compute/xml/common.py b/tempest/services/compute/xml/common.py
index 860dd5b..d2a18a1 100644
--- a/tempest/services/compute/xml/common.py
+++ b/tempest/services/compute/xml/common.py
@@ -99,7 +99,15 @@
         return self.__content
 
 
-def xml_to_json(node):
+def parse_array(node, plurals=None):
+    array = []
+    for child in node.getchildren():
+        array.append(xml_to_json(child,
+                     plurals))
+    return array
+
+
+def xml_to_json(node, plurals=None):
     """This does a really braindead conversion of an XML tree to
     something that looks like a json dump. In cases where the XML
     and json structures are the same, then this "just works". In
@@ -115,7 +123,10 @@
         tag = child.tag
         if tag.startswith("{"):
             ns, tag = tag.split("}", 1)
-        json[tag] = xml_to_json(child)
+        if plurals is not None and tag in plurals:
+                json[tag] = parse_array(child)
+        else:
+            json[tag] = xml_to_json(child)
     return json
 
 
diff --git a/tempest/services/network/network_client_base.py b/tempest/services/network/network_client_base.py
index b48dd34..45ea2d6 100644
--- a/tempest/services/network/network_client_base.py
+++ b/tempest/services/network/network_client_base.py
@@ -14,7 +14,7 @@
 
 import urllib
 
-# the folliwing map is used to construct proper URI
+# the following map is used to construct proper URI
 # for the given neutron resource
 service_resource_prefix_map = {
     'networks': '',
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index a999e31..155fa35 100644
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -19,19 +19,24 @@
 from tempest.services.compute.xml.common import deep_dict_to_xml
 from tempest.services.compute.xml.common import Document
 from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import parse_array
 from tempest.services.compute.xml.common import xml_to_json
 from tempest.services.network import network_client_base as client_base
 
 
 class NetworkClientXML(client_base.NetworkClientBase):
 
+    # list of plurals used for xml serialization
+    PLURALS = ['dns_nameservers', 'host_routes', 'allocation_pools',
+               'fixed_ips', 'extensions']
+
     def get_rest_client(self, config, username, password,
                         auth_url, tenant_name=None):
         return RestClientXML(config, username, password,
                              auth_url, tenant_name)
 
     def deserialize_list(self, body):
-        return self._parse_array(etree.fromstring(body))
+        return parse_array(etree.fromstring(body), self.PLURALS)
 
     def deserialize_single(self, body):
         return _root_tag_fetcher_and_xml_to_json_parse(body)
@@ -54,7 +59,7 @@
                 p1.append(p2)
                 post_body.append(p1)
         resp, body = self.post(uri, str(Document(post_body)))
-        networks = self._parse_array(etree.fromstring(body))
+        networks = parse_array(etree.fromstring(body))
         networks = {"networks": networks}
         return resp, networks
 
@@ -83,12 +88,6 @@
         body = _root_tag_fetcher_and_xml_to_json_parse(body)
         return resp, body
 
-    def _parse_array(self, node):
-        array = []
-        for child in node.getchildren():
-            array.append(xml_to_json(child))
-        return array
-
     def update_port(self, port_id, name):
         uri = '%s/ports/%s' % (self.uri_prefix, str(port_id))
         port = Element("port")
@@ -151,7 +150,7 @@
                 p1.append(p2)
             post_body.append(p1)
         resp, body = self.post(uri, str(Document(post_body)))
-        subnets = self._parse_array(etree.fromstring(body))
+        subnets = parse_array(etree.fromstring(body))
         subnets = {"subnets": subnets}
         return resp, subnets
 
@@ -166,7 +165,7 @@
                 p1.append(p2)
             post_body.append(p1)
         resp, body = self.post(uri, str(Document(post_body)))
-        ports = self._parse_array(etree.fromstring(body))
+        ports = parse_array(etree.fromstring(body))
         ports = {"ports": ports}
         return resp, ports
 
@@ -366,7 +365,7 @@
     def list_router_interfaces(self, uuid):
         uri = '%s/ports?device_id=%s' % (self.uri_prefix, uuid)
         resp, body = self.get(uri)
-        ports = self._parse_array(etree.fromstring(body))
+        ports = parse_array(etree.fromstring(body), self.PLURALS)
         ports = {"ports": ports}
         return resp, ports
 
@@ -395,14 +394,14 @@
     def list_dhcp_agent_hosting_network(self, network_id):
         uri = '%s/networks/%s/dhcp-agents' % (self.uri_prefix, network_id)
         resp, body = self.get(uri)
-        agents = self._parse_array(etree.fromstring(body))
+        agents = parse_array(etree.fromstring(body))
         body = {'agents': agents}
         return resp, body
 
     def list_networks_hosted_by_one_dhcp_agent(self, agent_id):
         uri = '%s/agents/%s/dhcp-networks' % (self.uri_prefix, agent_id)
         resp, body = self.get(uri)
-        networks = self._parse_array(etree.fromstring(body))
+        networks = parse_array(etree.fromstring(body))
         body = {'networks': networks}
         return resp, body
 
@@ -418,7 +417,8 @@
     root_tag = body.tag
     if root_tag.startswith("{"):
         ns, root_tag = root_tag.split("}", 1)
-    body = xml_to_json(etree.fromstring(xml_returned_body))
+    body = xml_to_json(etree.fromstring(xml_returned_body),
+                       NetworkClientXML.PLURALS)
     nil = '{http://www.w3.org/2001/XMLSchema-instance}nil'
     for key, val in body.iteritems():
         if isinstance(val, dict):