Skip to content

Commit dbdbc4a

Browse files
author
Dean Troyer
committed
Add low level api
* api.api.BaseAPI holds the common operations * api.compute.APIv2, api.identity_v2APIv2, api.identity_v3.APIv3 and api.object_store.APIv1 represent the versioned REST API interfaces * Changes to OSC include adding the API object to the existing client objects, although in time these could replace the client objects. * The existing Object-Store commands in OSC have been moved to openstackclient.api * A couple of Compute commands have been added to demonstrate the CRUD operations * An Identity command was implemented for both v2 and v3 to work out versioning * Tests for the API-layer are included Change-Id: If0bf70adc18ceb5782237fc7f4f8d8cf1936a4c7
1 parent ae957b1 commit dbdbc4a

31 files changed

Lines changed: 1644 additions & 1031 deletions
File renamed without changes.

‎openstackclient/api/api.py‎

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
2+
# not use this file except in compliance with the License. You may obtain
3+
# a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10+
# License for the specific language governing permissions and limitations
11+
# under the License.
12+
#
13+
14+
"""Base API Library"""
15+
16+
import simplejson as json
17+
18+
from keystoneclient.openstack.common.apiclient \
19+
import exceptions as ksc_exceptions
20+
21+
22+
class BaseAPI(object):
23+
"""Base API"""
24+
25+
def __init__(
26+
self,
27+
session=None,
28+
service_type=None,
29+
endpoint=None,
30+
**kwargs
31+
):
32+
"""Base object that contains some common API objects and methods
33+
34+
:param Session session:
35+
The default session to be used for making the HTTP API calls.
36+
:param string service_type:
37+
API name, i.e. ``identity`` or ``compute``
38+
:param string endpoint:
39+
The URL from the Service Catalog to be used as the base for API
40+
requests on this API.
41+
"""
42+
43+
super(BaseAPI, self).__init__()
44+
45+
# a requests.Session-style interface
46+
self.session = session
47+
self.service_type = service_type
48+
self.endpoint = endpoint
49+
50+
if self.endpoint is None:
51+
# TODO(dtroyer): look it up via session
52+
pass
53+
54+
def _request(self, method, url, session=None, **kwargs):
55+
"""Perform call into session
56+
57+
All API calls are funneled through this method to provide a common
58+
place to finalize the passed URL and other things.
59+
60+
:param string method:
61+
The HTTP method name, i.e. ``GET``, ``PUT``, etc
62+
:param string url:
63+
The API-specific portion of the URL path
64+
:param Session session:
65+
HTTP client session
66+
:param kwargs:
67+
keyword arguments passed to requests.request().
68+
:return: the requests.Response object
69+
"""
70+
71+
if not session:
72+
session = self.session
73+
if not session:
74+
# TODO(dtroyer): sort this
75+
raise Exception
76+
77+
if self.endpoint:
78+
url = '/'.join([self.endpoint.rstrip('/'), url.lstrip('/')])
79+
80+
# Why is ksc session backwards???
81+
ret = session.request(url, method, **kwargs)
82+
return ret
83+
84+
# The basic action methods all take a Session and return dict/lists
85+
86+
def create(
87+
self,
88+
url,
89+
session=None,
90+
method=None,
91+
**params
92+
):
93+
"""Create a new resource
94+
95+
:param string url:
96+
The API-specific portion of the URL path
97+
:param Session session:
98+
HTTP client session
99+
:param string method:
100+
HTTP method (default POST)
101+
"""
102+
103+
if not method:
104+
method = 'POST'
105+
ret = self._request(method, url, session=session, **params)
106+
# Should this move into _requests()?
107+
try:
108+
return ret.json()
109+
except json.JSONDecodeError:
110+
pass
111+
return ret
112+
113+
def delete(
114+
self,
115+
url,
116+
session=None,
117+
**params
118+
):
119+
"""Delete a resource
120+
121+
:param string url:
122+
The API-specific portion of the URL path
123+
:param Session session:
124+
HTTP client session
125+
"""
126+
127+
return self._request('DELETE', url, **params)
128+
129+
def list(
130+
self,
131+
url,
132+
session=None,
133+
body=None,
134+
**params
135+
):
136+
"""Return a list of resources
137+
138+
:param string url:
139+
The API-specific portion of the URL path
140+
:param Session session:
141+
HTTP client session
142+
:param body: data that will be encoded as JSON and passed in POST
143+
request (GET will be sent by default)
144+
"""
145+
146+
if body:
147+
return self._request(
148+
'POST',
149+
url,
150+
# service=self.service_type,
151+
json=body,
152+
params=params,
153+
).json()
154+
else:
155+
return self._request(
156+
'GET',
157+
url,
158+
# service=self.service_type,
159+
params=params,
160+
).json()
161+
162+
# Layered actions built on top of the basic action methods do not
163+
# explicitly take a Session but may still be passed on kwargs
164+
165+
def find_bulk(
166+
self,
167+
url,
168+
**kwargs
169+
):
170+
"""Bulk load and filter locally
171+
172+
:param string url:
173+
The API-specific portion of the URL path
174+
:param kwargs: dict of AVPs to match - logical AND
175+
:returns: list of resource dicts
176+
"""
177+
178+
items = self.list(url)
179+
if type(items) == dict:
180+
# strip off the enclosing dict
181+
key = list(items.keys())[0]
182+
items = items[key]
183+
184+
ret = []
185+
for o in items:
186+
try:
187+
if all(o[attr] == kwargs[attr] for attr in kwargs.keys()):
188+
ret.append(o)
189+
except KeyError:
190+
continue
191+
192+
return ret
193+
194+
def find_one(
195+
self,
196+
url,
197+
**kwargs
198+
):
199+
"""Find a resource by name or ID
200+
201+
:param string url:
202+
The API-specific portion of the URL path
203+
:returns: list of resource dicts
204+
"""
205+
206+
bulk_list = self.find_bulk(url, **kwargs)
207+
num_bulk = len(bulk_list)
208+
if num_bulk == 0:
209+
msg = "none found"
210+
raise ksc_exceptions.NotFound(msg)
211+
elif num_bulk > 1:
212+
msg = "many found"
213+
raise RuntimeError(msg)
214+
return bulk_list[0]
215+
216+
def find(
217+
self,
218+
url,
219+
attr=None,
220+
search=None,
221+
):
222+
"""Find a single resource by name or ID
223+
224+
:param string url:
225+
The API-specific portion of the URL path
226+
:param attr: name of attribute for secondary search
227+
:param search: search expression
228+
"""
229+
230+
try:
231+
ret = self._request('GET', "/%s/%s" % (url, search)).json()
232+
except ksc_exceptions.NotFound:
233+
kwargs = {attr: search}
234+
try:
235+
ret = self.find_one("/%s/detail" % (url), **kwargs)
236+
except ksc_exceptions.NotFound:
237+
msg = "%s not found" % search
238+
raise ksc_exceptions.NotFound(msg)
239+
240+
return ret

0 commit comments

Comments
 (0)