Merge "Extensions API"

This commit is contained in:
Jenkins
2013-07-23 18:43:43 +00:00
committed by Gerrit Code Review
13 changed files with 415 additions and 3 deletions

View File

@@ -13,4 +13,3 @@ 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.
"""

View File

@@ -32,3 +32,7 @@ class V2_0Constants(object):
XML_NS_RAX_KSGRP = \
'http://docs.rackspace.com/identity/api/ext/RAX-KSGRP/v1.0'
XML_NS_ATOM = 'http://www.w3.org/2005/Atom'
class AdminExtensions(object):
OS_KS_ADM = 'OS-KSADM'

View File

@@ -0,0 +1,15 @@
"""
Copyright 2013 Rackspace
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.
"""

View File

@@ -0,0 +1,103 @@
"""
Copyright 2013 Rackspace
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 cafe.engine.clients.rest import AutoMarshallingRestClient
from cloudcafe.identity.v2_0.common.models.constants import AdminExtensions
from cloudcafe.identity.v2_0.extensions_api.models.responses.extensions \
import Extensions
from cloudcafe.identity.v2_0.tenants_api.models.responses.role import \
Role, Roles
_version = 'v2.0'
_admin_extensions = AdminExtensions.OS_KS_ADM
class ExtensionsAPI_Client(AutoMarshallingRestClient):
def __init__(self, url=None, auth_token=None,
serialized_format=None, deserialized_format=None):
"""
@param url: Base URL for the compute service
@type url: String
@param auth_token: Auth token to be used for all requests
@type auth_token: String
@param serialized_format: Format for serializing requests
@type serialized_format: String
@param deserialized_format: Format for de-serializing responses
@type deserialized_format: String
"""
super(ExtensionsAPI_Client, self).__init__(
serialized_format, deserialized_format)
self.base_url = '{0}/{1}'.format(url, _version)
self.default_headers['Content-Type'] = 'application/{0}'.format(
serialized_format)
self.default_headers['Accept'] = 'application/{0}'.format(
serialized_format)
self.default_headers['X-Auth-Token'] = auth_token
def list_extensions(self, requestslib_kwargs=None):
"""
@summary: Lists all the extensions. Maps to /extensions
@return: response
@rtype: Response
"""
url = '{0}/extensions'.format(self.base_url)
response = self.request('GET', url,
response_entity_type=Extensions,
requestslib_kwargs=requestslib_kwargs)
return response
def list_roles(self, requestslib_kwargs=None):
"""
@summary: List all roles.
@return: response
@rtype: Response
"""
url = '{0}/{1}/roles'.format(self.base_url, _admin_extensions)
response = self.request('GET', url,
response_entity_type=Roles,
requestslib_kwargs=requestslib_kwargs)
return response
def create_role(self, name=None, requestslib_kwargs=None):
"""
@summary: Create a role.
@return: response
@rtype: Response
@param name: the role name
@type name: String
"""
url = '{0}/{1}/roles'.format(self.base_url, _admin_extensions)
role_request_object = Role(name=name)
response = self.request('POST', url,
response_entity_type=Role,
request_entity=role_request_object,
requestslib_kwargs=requestslib_kwargs)
return response
def delete_role(self, role_id, requestslib_kwargs=None):
"""
@summary: Delete a role.
@return: response
@rtype: Response
@param role_id: the role id
@type role_id: String
"""
url = '{0}/{1}/roles/{2}'.format(self.base_url,
_admin_extensions,
role_id)
response = self.request('DELETE', url,
requestslib_kwargs=requestslib_kwargs)
return response

View File

@@ -0,0 +1,15 @@
"""
Copyright 2013 Rackspace
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.
"""

View File

@@ -0,0 +1,15 @@
"""
Copyright 2013 Rackspace
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.
"""

View File

@@ -0,0 +1,122 @@
"""
Copyright 2013 Rackspace
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 cloudcafe.identity.v2_0.common.models.base import \
BaseIdentityModel, BaseIdentityListModel
class Extensions(BaseIdentityModel):
def __init__(self, values=None):
"""
Models a extensions object returned by keystone
"""
super(Extensions, self).__init__()
self.values = values
@classmethod
def _dict_to_obj(cls, json_dict):
extensions = Extensions()
extensions.values = Values._list_to_obj(
json_dict.get('extensions'))
return extensions
@classmethod
def _json_to_obj(cls, serialized_str):
json_dict = json.loads(serialized_str)
return cls._dict_to_obj(json_dict.get('extensions'))
class Values(BaseIdentityListModel):
def __init__(self, values=None):
"""
Models a list of values returned by keystone
"""
super(Values, self).__init__()
self.extend(values or [])
@classmethod
def _list_to_obj(self, value_dict_list):
values = Values()
for value_dict in value_dict_list:
value = Value._dict_to_obj(value_dict)
values.append(value)
return values
class Value(BaseIdentityModel):
def __init__(self, updated=None, name=None, links=None, namespace=None,
alias=None, description=None):
"""
Models a value object returned by keystone
"""
super(Value, self).__init__()
self.updated = updated
self.name = name
self.links = links
self.namespace = namespace
self.alias = alias
self.description = description
@classmethod
def _dict_to_obj(cls, json_dict):
value = Value(updated=json_dict.get('updated'),
name=json_dict.get('name'),
namespace=json_dict.get('namespace'),
alias=json_dict.get('alias'),
description=json_dict.get('description'),
links=(Links._list_to_obj(json_dict.get('links'))))
return value
class Links(BaseIdentityListModel):
def __init__(self, links=None):
"""
Models a list of links returned by keystone
"""
super(Links, self).__init__()
self.extend(links or [])
@classmethod
def _list_to_obj(self, link_dict_list):
links = Links()
for link_dict in link_dict_list:
link = Link._dict_to_obj(link_dict)
links.append(link)
return links
class Link(BaseIdentityModel):
def __init__(self, href=None, type_=None, rel=None):
"""
Models a link object returned by keystone
"""
super(Link, self).__init__()
self.href = href
self.type_ = type_
self.rel = rel
@classmethod
def _dict_to_obj(cls, json_dict):
link = Link(href=json_dict.get('href'),
type_=json_dict.get('type'),
rel=json_dict.get('rel'))
return link

View File

@@ -1,12 +1,12 @@
# ======================================================
# reference.json.config
# ------------------------------------------------------
# This configuration is specifically a reference
# This configuration is specifically a reference
# implementation for a configuration file.
# You must create a proper configuration file and supply
# the correct values for your Environment(s)
#
# For multiple environments it is suggested that you
# For multiple environments it is suggested that you
# generate specific configurations and name the files
# <ENVIRONMENT>.<FORMAT>.config
# ======================================================

View File

@@ -0,0 +1 @@
{"extensions": {"values": [{"updated": "2011-08-19T13:25:27-06:00", "name": "Openstack Keystone Admin", "links": [{"href": "https://github.com/openstack/identity-api", "type": "text/html", "rel": "describedby"}], "namespace": "http://docs.openstack.org/identity/api/ext/OS-KSADM/v1.0", "alias": "OS-KSADM", "description": "Openstack extensions to Keystone v2.0 API enabling Admin Operations."}]}}

View File

@@ -0,0 +1,80 @@
from unittest import TestCase
from httpretty import HTTPretty
from cloudcafe.identity.v2_0.extensions_api.client import ExtensionsAPI_Client
IDENTITY_ENDPOINT_URL = "http://localhost:35357"
class ExtensionsClientTest(TestCase):
def setUp(self):
self.url = IDENTITY_ENDPOINT_URL
self.serialized_format = "json"
self.deserialized_format = "json"
self.auth_token = "AUTH_TOKEN"
self.admin_extensions = "OS-KSADM"
self.extensions_api_client = ExtensionsAPI_Client(
url=self.url,
auth_token=self.auth_token,
serialized_format=self.serialized_format,
deserialized_format=self.deserialized_format)
self.role_id = "1"
HTTPretty.enable()
def test_list_extensions(self):
url = "{0}/v2.0/extensions".format(self.url)
HTTPretty.register_uri(HTTPretty.GET, url,
body=self._build_expected_body_response())
actual_response = self.extensions_api_client.list_extensions()
self._build_assertions(actual_response, url)
def test_list_roles(self):
url = "{0}/v2.0/{1}/roles".format(self.url, self.admin_extensions)
HTTPretty.register_uri(HTTPretty.GET, url,
body=self._build_create_role_response())
actual_response = self.extensions_api_client.list_roles()
self._build_assertions(actual_response, url)
def test_create_role(self):
url = "{0}/v2.0/{1}/roles".format(self.url, self.admin_extensions)
HTTPretty.register_uri(
HTTPretty.POST, url,
body=self._build_create_role_response())
actual_response = self.extensions_api_client.create_role()
self._build_assertions(actual_response, url)
def test_delete_role(self):
url = "{0}/v2.0/{1}/roles/{2}".format(self.url, self.admin_extensions,
self.role_id)
HTTPretty.register_uri(HTTPretty.DELETE, url)
actual_response = self.extensions_api_client.delete_role(
role_id=self.role_id)
self._build_assertions(actual_response, url)
def _build_assertions(self, actual_response, url):
assert HTTPretty.last_request.headers['Content-Type'] == \
'application/{0}'.format(self.serialized_format)
assert HTTPretty.last_request.headers['Accept'] == \
'application/{0}'.format(self.deserialized_format)
assert HTTPretty.last_request.headers[
'X-Auth-Token'] == self.auth_token
assert 200 == actual_response.status_code
assert url == actual_response.url
def _build_expected_body_response(self):
return {"extensions": [{"values": [
{"updated": "2011-08-19T13:25:27-06:00",
"name": "Openstack Keystone Admin",
"links": {"href": "https://github.com/openstack/identity-api",
"type": "text/html", "rel": "describedby"},
"namespace": "http://docs.openstack"
".org/identity/api/ext/OS-KSADM/v1.0",
"alias": "OS-KSADM",
"description": "Openstack extensions to Keystone "
"v2.0 API enabling Admin Operations."}]}]}
def _build_create_role_response(self):
return {"role": {"id": "25dfade062ca486ebdb4e00246c40441",
"name": "response-test-221460"}}

View File

@@ -0,0 +1,51 @@
import json
from unittest import TestCase
import os
from cloudcafe.identity.v2_0.extensions_api.models.responses.extensions \
import Extensions, Value, Values, Link, Links
class ExtensionsTest(TestCase):
def setUp(self):
self.extensions_json_dict = open(os.path.join(os.path.dirname(
__file__), "../../data/extensions.json")).read()
self.extensions_dict = json.loads(self.extensions_json_dict).get(
'extensions')
self.values = self.extensions_dict.get('values')
self.links = self.values[0].get('links')
self.dict_for_link = self.links[0]
self.href = self.dict_for_link.get('href')
self.type = self.dict_for_link.get('type')
self.rel = self.dict_for_link.get('rel')
self.updated_date = self.values[0].get('updated')
self.name = self.values[0].get('name')
self.name_space = self.values[0].get('namespace')
self.alias = self.values[0].get('alias')
self.description = self.values[0].get('description')
self.expected_link = Link(href=self.href, type_=self.type,
rel=self.rel)
self.expected_links = Links(links=[self.expected_link])
self.expected_value = Value(updated=self.updated_date,
name=self.name,
links=[self.expected_link],
namespace=self.name_space,
alias=self.alias,
description=self.description)
self.expected_values = Values(values=[self.expected_value])
self.expected_extensions = Extensions(values=self.expected_values)
self.dict_for_extensions = {'extensions': self.values}
def test_dict_to_obj(self):
assert self.expected_extensions == Extensions._dict_to_obj(
self.dict_for_extensions)
assert self.expected_link == Link._dict_to_obj(self.dict_for_link)
assert self.expected_value == Value._dict_to_obj(self.values[0])
def test_list_to_obj(self):
assert self.expected_values == Values._list_to_obj(self.values)
assert self.expected_links == Links._list_to_obj(self.links)

View File

@@ -6,3 +6,8 @@ os.environ["CCTNG_CONFIG_FILE"] = os.path.join(
"unittest.json.config"
)
os.environ["MOCK"] = 'True'
os.environ["OSTNG_CONFIG_FILE"] = os.path.join(
os.path.dirname(__file__),
"unittest.json.config"
)

2
unittest.json.config Normal file
View File

@@ -0,0 +1,2 @@
[CCTNG_ENGINE]
use_verbose_logging = false