Skip to content

Commit 2bfc9e1

Browse files
author
Dean Troyer
committed
Add low level api
* api.api.BaseAPI holds the common operations * api.compute.APIv2, api.identity_v2, api.identity_v3 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 2bfc9e1

31 files changed

+1495
-1011
lines changed
File renamed without changes.

openstackclient/api/api.py

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
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+
# print("%s %s return: %s\n%s" % (method, url, ret.status_code, ret._content))
83+
return ret
84+
85+
# The basic action methods all take a Session and return dict/lists
86+
87+
def create(
88+
self,
89+
url,
90+
session=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+
"""
100+
101+
ret = self._request('PUT', url, session=session, **params)
102+
# Should this move into _requests()?
103+
try:
104+
return ret.json()
105+
except json.JSONDecodeError:
106+
pass
107+
return ret
108+
109+
def delete(
110+
self,
111+
url,
112+
session=None,
113+
**params
114+
):
115+
"""Delete a resource
116+
117+
:param string url:
118+
The API-specific portion of the URL path
119+
:param Session session:
120+
HTTP client session
121+
"""
122+
123+
return self._request('DELETE', url, **params)
124+
125+
def list(
126+
self,
127+
url,
128+
session=None,
129+
body=None,
130+
**params
131+
):
132+
"""Return a list of resources
133+
134+
:param string url:
135+
The API-specific portion of the URL path
136+
:param Session session:
137+
HTTP client session
138+
:param body: data that will be encoded as JSON and passed in POST
139+
request (GET will be sent by default)
140+
"""
141+
142+
if body:
143+
return self._request(
144+
'POST',
145+
url,
146+
# service=self.service_type,
147+
json=body,
148+
params=params,
149+
).json()
150+
else:
151+
return self._request(
152+
'GET',
153+
url,
154+
# service=self.service_type,
155+
params=params,
156+
).json()
157+
158+
# Layered actions built on top of the basic action methods do not
159+
# explicitly take a Session but may still be passed on kwargs
160+
161+
def find_bulk(
162+
self,
163+
url,
164+
**kwargs
165+
):
166+
"""Bulk load and filter locally
167+
168+
:param string url:
169+
The API-specific portion of the URL path
170+
:param kwargs: dict of AVPs to match - logical AND
171+
:returns: list of resource dicts
172+
"""
173+
174+
items = self.list(url)
175+
if type(items) == dict:
176+
# strip off the enclosing dict
177+
key = list(items.keys())[0]
178+
items = items[key]
179+
180+
ret = []
181+
for o in items:
182+
try:
183+
if all(o[attr] == kwargs[attr] for attr in kwargs.keys()):
184+
ret.append(o)
185+
except KeyError:
186+
continue
187+
188+
return ret
189+
190+
def find_one(
191+
self,
192+
url,
193+
**kwargs
194+
):
195+
"""Find a resource by name or ID
196+
197+
:param string url:
198+
The API-specific portion of the URL path
199+
:returns: list of resource dicts
200+
"""
201+
202+
bulk_list = self.find_bulk(url, **kwargs)
203+
num_bulk = len(bulk_list)
204+
if num_bulk == 0:
205+
msg = "none found"
206+
raise ksc_exceptions.NotFound(msg)
207+
elif num_bulk > 1:
208+
msg = "many found"
209+
raise RuntimeError(msg)
210+
return bulk_list[0]
211+
212+
def find(
213+
self,
214+
url,
215+
attr=None,
216+
search=None,
217+
):
218+
"""Find a single resource by name or ID
219+
220+
:param string url:
221+
The API-specific portion of the URL path
222+
:param attr: name of attribute for secondary search
223+
:param search: search expression
224+
"""
225+
226+
try:
227+
ret = self._request('GET', "/%s/%s" % (url, search)).json()
228+
except ksc_exceptions.NotFound:
229+
kwargs = {attr: search}
230+
try:
231+
ret = self.find_one("/%s/detail" % (url), **kwargs)
232+
except ksc_exceptions.NotFound:
233+
msg = "%s not found" % search
234+
raise ksc_exceptions.NotFound(msg)
235+
236+
return ret

openstackclient/api/compute.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
"""Compute v2 API Library"""
15+
16+
from openstackclient.api import api
17+
18+
19+
class APIv2(api.BaseAPI):
20+
"""Compute v2 API"""
21+
22+
def __init__(self, **kwargs):
23+
super(APIv2, self).__init__(**kwargs)
24+
25+
def flavor_list(
26+
self,
27+
detailed=True,
28+
is_public=True,
29+
):
30+
"""Get available flavors
31+
32+
:param detailed: retrieve detailed response from server if True
33+
:param is_public: return only public flavors if True
34+
"""
35+
36+
params = {}
37+
38+
if not is_public:
39+
params['is_public'] = is_public
40+
41+
url = "/flavors"
42+
if detailed:
43+
url += "/detail"
44+
45+
return self.list(url, **params)['flavors']
46+
47+
def flavor_show(
48+
self,
49+
flavor=None,
50+
):
51+
52+
return self.find("flavors", 'name', flavor)
53+
54+
def key_list(
55+
self,
56+
):
57+
"""Get available keys
58+
59+
This is an extension, look at loading it separately
60+
"""
61+
62+
# Each effin object has the 'keypair' wrapper...
63+
ret = []
64+
for k in self.list('os-keypairs')['keypairs']:
65+
ret.append(k['keypair'])
66+
67+
return ret

openstackclient/api/identity_v2.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
"""Identity v2 API Library"""
15+
16+
from openstackclient.api import api
17+
18+
19+
class APIv2(api.BaseAPI):
20+
"""Identity v2 API"""
21+
22+
def __init__(self, **kwargs):
23+
super(APIv2, self).__init__(**kwargs)
24+
25+
def project_list(
26+
self,
27+
):
28+
"""Get available projects
29+
30+
can add limit/marker
31+
"""
32+
33+
return self.list('tenants')['tenants']

openstackclient/api/identity_v3.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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+
"""Identity v3 API Library"""
15+
16+
from openstackclient.api import api
17+
18+
19+
class APIv3(api.BaseAPI):
20+
"""Identity v3 API"""
21+
22+
def __init__(self, **kwargs):
23+
super(APIv3, self).__init__(**kwargs)
24+
25+
def find_domain(self, domain):
26+
"""Find domain by name or id"""
27+
return self.find('domain', attr='name', search=domain)['domain']
28+
29+
def project_list(
30+
self,
31+
**params
32+
):
33+
"""Get available projects
34+
35+
can add limit/marker
36+
need to convert to v3...
37+
"""
38+
39+
return self.list('projects')['projects']

0 commit comments

Comments
 (0)