Merge "Test cases for Endpoints V3 API"
diff --git a/tempest/clients.py b/tempest/clients.py
index b3b5906..7d9a263 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -48,8 +48,11 @@
 from tempest.services.compute.xml.servers_client import ServersClientXML
 from tempest.services.compute.xml.volumes_extensions_client import \
     VolumesExtensionsClientXML
+from tempest.services.identity.v3.json.endpoints_client import \
+    EndPointClientJSON
 from tempest.services.identity.json.identity_client import IdentityClientJSON
 from tempest.services.identity.json.identity_client import TokenClientJSON
+from tempest.services.identity.v3.xml.endpoints_client import EndPointClientXML
 from tempest.services.identity.xml.identity_client import IdentityClientXML
 from tempest.services.identity.xml.identity_client import TokenClientXML
 from tempest.services.image.v1.json.image_client import ImageClientJSON
@@ -157,6 +160,11 @@
     "xml": InterfacesClientXML,
 }
 
+ENDPOINT_CLIENT = {
+    "json": EndPointClientJSON,
+    "xml": EndPointClientXML,
+}
+
 
 class Manager(object):
 
@@ -219,6 +227,7 @@
             self.security_groups_client = \
                 SECURITY_GROUPS_CLIENT[interface](*client_args)
             self.interfaces_client = INTERFACES_CLIENT[interface](*client_args)
+            self.endpoints_client = ENDPOINT_CLIENT[interface](*client_args)
         except KeyError:
             msg = "Unsupported interface type `%s'" % interface
             raise exceptions.InvalidConfiguration(msg)
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 366e467..fba3b0f 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -179,6 +179,9 @@
     def delete(self, url, headers=None):
         return self.request('DELETE', url, headers)
 
+    def patch(self, url, body, headers):
+        return self.request('PATCH', url, headers, body)
+
     def put(self, url, body, headers):
         return self.request('PUT', url, headers, body)
 
diff --git a/tempest/services/identity/v3/__init__.py b/tempest/services/identity/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/identity/v3/__init__.py
diff --git a/tempest/services/identity/v3/json/__init__.py b/tempest/services/identity/v3/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/identity/v3/json/__init__.py
diff --git a/tempest/services/identity/v3/json/endpoints_client.py b/tempest/services/identity/v3/json/endpoints_client.py
new file mode 100755
index 0000000..3cb8f90
--- /dev/null
+++ b/tempest/services/identity/v3/json/endpoints_client.py
@@ -0,0 +1,87 @@
+# 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.
+
+import json
+from urlparse import urlparse
+
+from tempest.common.rest_client import RestClient
+
+
+class EndPointClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(EndPointClientJSON, self).__init__(config,
+                                                 username, password,
+                                                 auth_url, tenant_name)
+        self.service = self.config.identity.catalog_type
+        self.endpoint_url = 'adminURL'
+
+    def request(self, method, url, headers=None, body=None, wait=None):
+        """Overriding the existing HTTP request in super class rest_client."""
+        self._set_auth()
+        self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+                                              "/v3")
+        return super(EndPointClientJSON, self).request(method, url,
+                                                       headers=headers,
+                                                       body=body)
+
+    def list_endpoints(self):
+        """GET endpoints."""
+        resp, body = self.get('endpoints')
+        body = json.loads(body)
+        return resp, body['endpoints']
+
+    def create_endpoint(self, service_id, interface, url, **kwargs):
+        """Create endpoint."""
+        region = kwargs.get('region', None)
+        enabled = kwargs.get('enabled', None)
+        post_body = {
+            'service_id': service_id,
+            'interface': interface,
+            'url': url,
+            'region': region,
+            'enabled': enabled
+        }
+        post_body = json.dumps({'endpoint': post_body})
+        resp, body = self.post('endpoints', post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['endpoint']
+
+    def update_endpoint(self, endpoint_id, service_id=None, interface=None,
+                        url=None, region=None, enabled=None):
+        """Updates an endpoint with given parameters."""
+        post_body = {}
+        if service_id is not None:
+            post_body['service_id'] = service_id
+        if interface is not None:
+            post_body['interface'] = interface
+        if url is not None:
+            post_body['url'] = url
+        if region is not None:
+            post_body['region'] = region
+        if enabled is not None:
+            post_body['enabled'] = enabled
+        post_body = json.dumps({'endpoint': post_body})
+        resp, body = self.patch('endpoints/%s' % endpoint_id, post_body,
+                                self.headers)
+        body = json.loads(body)
+        return resp, body['endpoint']
+
+    def delete_endpoint(self, endpoint_id):
+        """Delete endpoint."""
+        resp_header, resp_body = self.delete('endpoints/%s' % endpoint_id)
+        return resp_header, resp_body
diff --git a/tempest/services/identity/v3/xml/__init__.py b/tempest/services/identity/v3/xml/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/identity/v3/xml/__init__.py
diff --git a/tempest/services/identity/v3/xml/endpoints_client.py b/tempest/services/identity/v3/xml/endpoints_client.py
new file mode 100755
index 0000000..8400976
--- /dev/null
+++ b/tempest/services/identity/v3/xml/endpoints_client.py
@@ -0,0 +1,107 @@
+# 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 urlparse import urlparse
+
+import httplib2
+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 xml_to_json
+
+XMLNS = "http://docs.openstack.org/identity/api/v3"
+
+
+class EndPointClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(EndPointClientXML, self).__init__(config, username, password,
+                                                auth_url, tenant_name)
+        self.service = self.config.identity.catalog_type
+        self.endpoint_url = 'adminURL'
+
+    def _parse_array(self, node):
+        array = []
+        for child in node.getchildren():
+            tag_list = child.tag.split('}', 1)
+            if tag_list[1] == "endpoint":
+                array.append(xml_to_json(child))
+        return array
+
+    def _parse_body(self, body):
+        json = xml_to_json(body)
+        return json
+
+    def request(self, method, url, headers=None, body=None, wait=None):
+        """Overriding the existing HTTP request in super class RestClient."""
+        dscv = self.config.identity.disable_ssl_certificate_validation
+        self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv)
+        self._set_auth()
+        self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+                                              "/v3")
+        return super(EndPointClientXML, self).request(method, url,
+                                                      headers=headers,
+                                                      body=body)
+
+    def list_endpoints(self):
+        """Get the list of endpoints."""
+        resp, body = self.get("endpoints", self.headers)
+        body = self._parse_array(etree.fromstring(body))
+        return resp, body
+
+    def create_endpoint(self, service_id, interface, url, **kwargs):
+        """Create endpoint."""
+        region = kwargs.get('region', None)
+        enabled = kwargs.get('enabled', None)
+        create_endpoint = Element("endpoint",
+                                  xmlns=XMLNS,
+                                  service_id=service_id,
+                                  interface=interface,
+                                  url=url, region=region,
+                                  enabled=enabled)
+        resp, body = self.post('endpoints', str(Document(create_endpoint)),
+                               self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def update_endpoint(self, endpoint_id, service_id=None, interface=None,
+                        url=None, region=None, enabled=None):
+        """Updates an endpoint with given parameters."""
+        doc = Document()
+        endpoint = Element("endpoint")
+        doc.append(endpoint)
+
+        if service_id:
+            endpoint.add_attr("service_id", service_id)
+        if interface:
+            endpoint.add_attr("interface", interface)
+        if url:
+            endpoint.add_attr("url", url)
+        if region:
+            endpoint.add_attr("region", region)
+        if enabled is not None:
+            endpoint.add_attr("enabled", enabled)
+        resp, body = self.patch('endpoints/%s' % str(endpoint_id),
+                                str(doc), self.headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def delete_endpoint(self, endpoint_id):
+        """Delete endpoint."""
+        resp_header, resp_body = self.delete('endpoints/%s' % endpoint_id)
+        return resp_header, resp_body
diff --git a/tempest/tests/identity/admin/v3/__init__.py b/tempest/tests/identity/admin/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/identity/admin/v3/__init__.py
diff --git a/tempest/tests/identity/admin/v3/test_endpoints.py b/tempest/tests/identity/admin/v3/test_endpoints.py
new file mode 100755
index 0000000..98fab57
--- /dev/null
+++ b/tempest/tests/identity/admin/v3/test_endpoints.py
@@ -0,0 +1,149 @@
+# 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.common.utils.data_utils import rand_name
+from tempest.test import attr
+from tempest.tests.identity import base
+
+
+class EndPointsTestJSON(base.BaseIdentityAdminTest):
+    _interface = 'json'
+
+    @classmethod
+    def setUpClass(cls):
+        super(EndPointsTestJSON, cls).setUpClass()
+        cls.identity_client = cls.client
+        cls.client = cls.endpoints_client
+        cls.service_ids = list()
+        s_name = rand_name('service-')
+        s_type = rand_name('type--')
+        s_description = rand_name('description-')
+        resp, cls.service_data =\
+            cls.identity_client.create_service(s_name, s_type,
+                                               description=s_description)
+        cls.service_id = cls.service_data['id']
+        cls.service_ids.append(cls.service_id)
+        #Create endpoints so as to use for LIST and GET test cases
+        cls.setup_endpoints = list()
+        for i in range(2):
+            region = rand_name('region')
+            url = rand_name('url')
+            interface = 'public'
+            resp, endpoint = cls.client.create_endpoint(
+                cls.service_id, interface, url, region=region, enabled=True)
+            cls.setup_endpoints.append(endpoint)
+
+    @classmethod
+    def tearDownClass(cls):
+        for e in cls.setup_endpoints:
+            cls.client.delete_endpoint(e['id'])
+        for s in cls.service_ids:
+            cls.identity_client.delete_service(s)
+
+    @attr('positive')
+    def test_list_endpoints(self):
+        # Get a list of endpoints
+        resp, fetched_endpoints = self.client.list_endpoints()
+        #Asserting LIST Endpoint
+        self.assertEqual(resp['status'], '200')
+        missing_endpoints =\
+            [e for e in self.setup_endpoints if e not in fetched_endpoints]
+        self.assertEqual(0, len(missing_endpoints),
+                         "Failed to find endpoint %s in fetched list" %
+                         ', '.join(str(e) for e in missing_endpoints))
+
+    @attr('positive')
+    def test_create_delete_endpoint(self):
+        region = rand_name('region')
+        url = rand_name('url')
+        interface = 'public'
+        create_flag = False
+        matched = False
+        try:
+            resp, endpoint =\
+                self.client.create_endpoint(self.service_id, interface, url,
+                                            region=region, enabled=True)
+            create_flag = True
+            #Asserting Create Endpoint response body
+            self.assertEqual(resp['status'], '201')
+            self.assertEqual(region, endpoint['region'])
+            self.assertEqual(url, endpoint['url'])
+            #Checking if created endpoint is present in the list of endpoints
+            resp, fetched_endpoints = self.client.list_endpoints()
+            for e in fetched_endpoints:
+                if endpoint['id'] == e['id']:
+                    matched = True
+            if not matched:
+                self.fail("Created endpoint does not appear in the list"
+                          " of endpoints")
+        finally:
+            if create_flag:
+                matched = False
+                #Deleting the endpoint created in this method
+                resp_header, resp_body =\
+                    self.client.delete_endpoint(endpoint['id'])
+                self.assertEqual(resp_header['status'], '204')
+                self.assertEqual(resp_body, '')
+                #Checking whether endpoint is deleted successfully
+                resp, fetched_endpoints = self.client.list_endpoints()
+                for e in fetched_endpoints:
+                    if endpoint['id'] == e['id']:
+                        matched = True
+                if matched:
+                    self.fail("Delete endpoint is not successful")
+
+    @attr('smoke')
+    def test_update_endpoint(self):
+        #Creating an endpoint so as to check update endpoint
+        #with new values
+        region1 = rand_name('region')
+        url1 = rand_name('url')
+        interface1 = 'public'
+        resp, endpoint_for_update =\
+            self.client.create_endpoint(self.service_id, interface1,
+                                        url1, region=region1,
+                                        enabled=True)
+        #Creating service so as update endpoint with new service ID
+        s_name = rand_name('service-')
+        s_type = rand_name('type--')
+        s_description = rand_name('description-')
+        resp, self.service2 =\
+            self.identity_client.create_service(s_name, s_type,
+                                                description=s_description)
+        self.service_ids.append(self.service2['id'])
+        #Updating endpoint with new values
+        service_id = self.service2['id']
+        region2 = rand_name('region')
+        url2 = rand_name('url')
+        interface2 = 'internal'
+        resp, endpoint = \
+            self.client.update_endpoint(endpoint_for_update['id'],
+                                        service_id=self.service2['id'],
+                                        interface=interface2, url=url2,
+                                        region=region2, enabled=False)
+        self.assertEqual(resp['status'], '200')
+        #Asserting if the attributes of endpoint are updated
+        self.assertEqual(self.service2['id'], endpoint['service_id'])
+        self.assertEqual(interface2, endpoint['interface'])
+        self.assertEqual(url2, endpoint['url'])
+        self.assertEqual(region2, endpoint['region'])
+        self.assertEqual('False', str(endpoint['enabled']))
+        self.addCleanup(self.client.delete_endpoint, endpoint_for_update['id'])
+
+
+class EndPointsTestXML(EndPointsTestJSON):
+    _interface = 'xml'
diff --git a/tempest/tests/identity/base.py b/tempest/tests/identity/base.py
index 168b2ff..64b8993 100644
--- a/tempest/tests/identity/base.py
+++ b/tempest/tests/identity/base.py
@@ -28,6 +28,7 @@
         os = clients.AdminManager(interface=cls._interface)
         cls.client = os.identity_client
         cls.token_client = os.token_client
+        cls.endpoints_client = os.endpoints_client
 
         if not cls.client.has_admin_extensions():
             raise cls.skipException("Admin extensions disabled")