Merge "Add fixed ip client and tests"
diff --git a/tempest/clients.py b/tempest/clients.py
index 7d9a263..678b595 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -77,6 +77,8 @@
     InterfacesClientJSON
 from tempest.services.compute.xml.interfaces_client import \
     InterfacesClientXML
+from tempest.services.compute.json.fixed_ips_client import FixedIPsClientJSON
+from tempest.services.compute.xml.fixed_ips_client import FixedIPsClientXML
 
 LOG = logging.getLogger(__name__)
 
@@ -165,6 +167,11 @@
     "xml": EndPointClientXML,
 }
 
+FIXED_IPS_CLIENT = {
+    "json": FixedIPsClientJSON,
+    "xml": FixedIPsClientXML
+}
+
 
 class Manager(object):
 
@@ -228,6 +235,7 @@
                 SECURITY_GROUPS_CLIENT[interface](*client_args)
             self.interfaces_client = INTERFACES_CLIENT[interface](*client_args)
             self.endpoints_client = ENDPOINT_CLIENT[interface](*client_args)
+            self.fixed_ips_client = FIXED_IPS_CLIENT[interface](*client_args)
         except KeyError:
             msg = "Unsupported interface type `%s'" % interface
             raise exceptions.InvalidConfiguration(msg)
diff --git a/tempest/services/compute/json/fixed_ips_client.py b/tempest/services/compute/json/fixed_ips_client.py
new file mode 100644
index 0000000..4ef7c4c
--- /dev/null
+++ b/tempest/services/compute/json/fixed_ips_client.py
@@ -0,0 +1,40 @@
+# 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.
+
+import json
+
+from tempest.common.rest_client import RestClient
+
+
+class FixedIPsClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(FixedIPsClientJSON, self).__init__(config, username, password,
+                                                 auth_url, tenant_name)
+        self.service = self.config.compute.catalog_type
+
+    def get_fixed_ip_details(self, fixed_ip):
+        url = "os-fixed-ips/%s" % (fixed_ip)
+        resp, body = self.get(url)
+        body = json.loads(body)
+        return resp, body['fixed_ip']
+
+    def reserve_fixed_ip(self, ip, body):
+        """This reserves and unreserves fixed ips."""
+        url = "os-fixed-ips/%s/action" % (ip)
+        resp, body = self.post(url, json.dumps(body), self.headers)
+        return resp, body
diff --git a/tempest/services/compute/xml/fixed_ips_client.py b/tempest/services/compute/xml/fixed_ips_client.py
new file mode 100644
index 0000000..ef023f0
--- /dev/null
+++ b/tempest/services/compute/xml/fixed_ips_client.py
@@ -0,0 +1,54 @@
+# 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 lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import Text
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class FixedIPsClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(FixedIPsClientXML, self).__init__(config, username, password,
+                                                auth_url, tenant_name)
+        self.service = self.config.compute.catalog_type
+
+    def _parse_fixed_ip_details(self, body):
+        body = xml_to_json(etree.fromstring(body))
+        return body
+
+    def get_fixed_ip_details(self, fixed_ip):
+        url = "os-fixed-ips/%s" % (fixed_ip)
+        resp, body = self.get(url, self.headers)
+        body = self._parse_resp(body)
+        return resp, body
+
+    def reserve_fixed_ip(self, ip, body):
+        """This reserves and unreserves fixed ips."""
+        url = "os-fixed-ips/%s/action" % (ip)
+        # NOTE(maurosr): First converts the dict body to a json string then
+        # accept any action key value here to permit tests to cover cases with
+        # invalid actions raising badrequest.
+        key, value = body.popitem()
+        xml_body = Element(key)
+        xml_body.append(Text(value))
+        resp, body = self.post(url, str(Document(xml_body)), self.headers)
+        return resp, body
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index f5fd4a6..331d560 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -42,6 +42,13 @@
     version = ip.get('version')
     if version:
         ip['version'] = int(version)
+    # NOTE(maurosr): just a fast way to avoid the xml version with the
+    # expanded xml namespace.
+    type_ns_prefix = ('{http://docs.openstack.org/compute/ext/extended_ips/'
+                      'api/v1.1}type')
+    if type_ns_prefix in ip:
+        ip['OS-EXT-IPS:type'] = ip[type_ns_prefix]
+        ip.pop(type_ns_prefix)
     return ip
 
 
diff --git a/tempest/tests/compute/admin/test_fixed_ips.py b/tempest/tests/compute/admin/test_fixed_ips.py
new file mode 100644
index 0000000..d8b1359
--- /dev/null
+++ b/tempest/tests/compute/admin/test_fixed_ips.py
@@ -0,0 +1,108 @@
+# 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 import exceptions
+from tempest.test import attr
+from tempest.tests.compute import base
+
+
+class FixedIPsBase(base.BaseComputeAdminTest):
+    _interface = 'json'
+    ip = None
+
+    @classmethod
+    def setUpClass(cls):
+        super(FixedIPsBase, cls).setUpClass()
+        # NOTE(maurosr): The idea here is: the server creation is just an
+        # auxiliary element to the ip details or reservation, there was no way
+        # (at least none in my mind) to get an valid and existing ip except
+        # by creating a server and using its ip. So the intention is to create
+        # fewer server possible (one) and use it to both: json and xml tests.
+        # This decreased time to run both tests, in my test machine, from 53
+        # secs to 29 (agains 23 secs when running only json tests)
+        if cls.ip is None:
+            cls.client = cls.os_adm.fixed_ips_client
+            cls.non_admin_client = cls.fixed_ips_client
+            resp, server = cls.create_server(wait_until='ACTIVE')
+            resp, server = cls.servers_client.get_server(server['id'])
+            for ip_set in server['addresses']:
+                for ip in server['addresses'][ip_set]:
+                    if ip['OS-EXT-IPS:type'] == 'fixed':
+                        cls.ip = ip['addr']
+                        break
+                if cls.ip:
+                    break
+
+
+class FixedIPsTestJson(FixedIPsBase):
+    _interface = 'json'
+
+    @attr(type='positive')
+    def test_list_fixed_ip_details(self):
+        resp, fixed_ip = self.client.get_fixed_ip_details(self.ip)
+        self.assertEqual(fixed_ip['address'], self.ip)
+
+    @attr(type='negative')
+    def test_list_fixed_ip_details_with_non_admin_user(self):
+        self.assertRaises(exceptions.Unauthorized,
+                          self.non_admin_client.get_fixed_ip_details, self.ip)
+
+    @attr(type='positive')
+    def test_set_reserve(self):
+        body = {"reserve": "None"}
+        resp, body = self.client.reserve_fixed_ip(self.ip, body)
+        self.assertEqual(resp.status, 202)
+
+    @attr(type='positive')
+    def test_set_unreserve(self):
+        body = {"unreserve": "None"}
+        resp, body = self.client.reserve_fixed_ip(self.ip, body)
+        self.assertEqual(resp.status, 202)
+
+    @attr(type='negative')
+    def test_set_reserve_with_non_admin_user(self):
+        body = {"reserve": "None"}
+        self.assertRaises(exceptions.Unauthorized,
+                          self.non_admin_client.reserve_fixed_ip,
+                          self.ip, body)
+
+    @attr(type='negative')
+    def test_set_unreserve_with_non_admin_user(self):
+        body = {"unreserve": "None"}
+        self.assertRaises(exceptions.Unauthorized,
+                          self.non_admin_client.reserve_fixed_ip,
+                          self.ip, body)
+
+    @attr(type='negative')
+    def test_set_reserve_with_invalid_ip(self):
+        # NOTE(maurosr): since this exercises the same code snippet, we do it
+        # only for reserve action
+        body = {"reserve": "None"}
+        self.assertRaises(exceptions.NotFound,
+                          self.client.reserve_fixed_ip,
+                          "my.invalid.ip", body)
+
+    @attr(type='negative')
+    def test_fixed_ip_with_invalid_action(self):
+        body = {"invalid_action": "None"}
+        self.assertRaises(exceptions.BadRequest,
+                          self.client.reserve_fixed_ip,
+                          self.ip, body)
+
+
+class FixedIPsTestXml(FixedIPsTestJson):
+    _interface = 'xml'
diff --git a/tempest/tests/compute/base.py b/tempest/tests/compute/base.py
index 87aa889..7716922 100644
--- a/tempest/tests/compute/base.py
+++ b/tempest/tests/compute/base.py
@@ -60,6 +60,7 @@
         cls.volumes_extensions_client = os.volumes_extensions_client
         cls.volumes_client = os.volumes_client
         cls.interfaces_client = os.interfaces_client
+        cls.fixed_ips_client = os.fixed_ips_client
         cls.build_interval = cls.config.compute.build_interval
         cls.build_timeout = cls.config.compute.build_timeout
         cls.ssh_user = cls.config.compute.ssh_user