ESCALATOR-42 Get available version list
[escalator.git] / client / escalatorclient / v1 / versions.py
1 # Copyright 2012 OpenStack Foundation
2 # All Rights Reserved.
3 #
4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
5 #    not use this file except in compliance with the License. You may obtain
6 #    a copy of the License at
7 #
8 #         http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #    Unless required by applicable law or agreed to in writing, software
11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 #    License for the specific language governing permissions and limitations
14 #    under the License.
15
16 import copy
17
18 from oslo_utils import encodeutils
19 from oslo_utils import strutils
20 import six
21 import six.moves.urllib.parse as urlparse
22
23 from escalatorclient.common import utils
24 from escalatorclient.openstack.common.apiclient import base
25 from escalatorclient.common.http import HTTPClient
26
27 CREATE_PARAMS = ('id', 'name', 'description', 'type', 'version', 'size',
28                  'checksum', 'status', 'os_status', 'version_patch')
29
30 DEFAULT_PAGE_SIZE = 200
31 VERSION_PARAMS = ('type')
32 SORT_DIR_VALUES = ('asc', 'desc')
33 SORT_KEY_VALUES = (
34     'name', 'id', 'cluster_id', 'created_at', 'updated_at', 'status')
35
36 OS_REQ_ID_HDR = 'x-openstack-request-id'
37
38
39 class Version(base.Resource):
40
41     def __repr__(self):
42         return "<Version %s>" % self._info
43
44     def update(self, **fields):
45         self.manager.update(self, **fields)
46
47     def delete(self, **kwargs):
48         return self.manager.delete(self)
49
50     def data(self, **kwargs):
51         return self.manager.data(self, **kwargs)
52
53
54 class VersionManager(base.ManagerWithFind):
55     resource_class = Version
56
57     def get_version_client(self):
58         endpoint = "http://127.0.0.1:19292"
59         client = HTTPClient(endpoint)
60         return client
61
62     def _list(self, url, response_key, obj_class=None, body=None):
63         version_client = self.get_version_client()
64         resp, body = version_client.get(url)
65
66         if obj_class is None:
67             obj_class = self.resource_class
68
69         data = body[response_key]
70         return ([obj_class(self, res, loaded=True) for res in data if res],
71                 resp)
72
73     def _version_meta_from_headers(self, headers):
74         meta = {'properties': {}}
75         safe_decode = encodeutils.safe_decode
76         for key, value in six.iteritems(headers):
77             value = safe_decode(value, incoming='utf-8')
78             if key.startswith('x-image-meta-property-'):
79                 _key = safe_decode(key[22:], incoming='utf-8')
80                 meta['properties'][_key] = value
81             elif key.startswith('x-image-meta-'):
82                 _key = safe_decode(key[13:], incoming='utf-8')
83                 meta[_key] = value
84
85         for key in ['is_public', 'protected', 'deleted']:
86             if key in meta:
87                 meta[key] = strutils.bool_from_string(meta[key])
88
89         return self._format_version_meta_for_user(meta)
90
91     def _version_meta_to_headers(self, fields):
92         headers = {}
93         fields_copy = copy.deepcopy(fields)
94         for key, value in six.iteritems(fields_copy):
95             headers['%s' % key] = utils.to_str(value)
96         return headers
97
98     @staticmethod
99     def _format_version_meta_for_user(meta):
100         for key in ['size', 'min_ram', 'min_disk']:
101             if key in meta:
102                 try:
103                     meta[key] = int(meta[key]) if meta[key] else 0
104                 except ValueError:
105                     pass
106         return meta
107
108     def get(self, version, **kwargs):
109         """Get the metadata for a specific version.
110
111         :param version: image object or id to look up
112         :rtype: :class:`version`
113         """
114         version_id = base.getid(version)
115         resp, body = self.client.get('/v1/versions/%s'
116                                      % urlparse.quote(str(version_id)))
117         # meta = self._version_meta_from_headers(resp.headers)
118         return_request_id = kwargs.get('return_req_id', None)
119         if return_request_id is not None:
120             return_request_id.append(resp.headers.get(OS_REQ_ID_HDR, None))
121         # return version(self, meta)
122         return Version(self, self._format_version_meta_for_user(
123             body['version']))
124
125     def _build_params(self, parameters):
126         params = {'limit': parameters.get('page_size', DEFAULT_PAGE_SIZE)}
127
128         if 'marker' in parameters:
129             params['marker'] = parameters['marker']
130
131         sort_key = parameters.get('sort_key')
132         if sort_key is not None:
133             if sort_key in SORT_KEY_VALUES:
134                 params['sort_key'] = sort_key
135             else:
136                 raise ValueError('sort_key must be one of the following: %s.'
137                                  % ', '.join(SORT_KEY_VALUES))
138
139         sort_dir = parameters.get('sort_dir')
140         if sort_dir is not None:
141             if sort_dir in SORT_DIR_VALUES:
142                 params['sort_dir'] = sort_dir
143             else:
144                 raise ValueError('sort_dir must be one of the following: %s.'
145                                  % ', '.join(SORT_DIR_VALUES))
146
147         filters = parameters.get('filters', {})
148         params.update(filters)
149
150         return params
151
152     def list(self, **kwargs):
153         """Get a list of versions.
154
155         :param page_size: number of items to request in each paginated request
156         :param limit: maximum number of versions to return
157         :param marker:begin returning versions that appear later in version
158                        list than that represented by this version id
159         :param filters: dict of direct comparison filters that mimics the
160                         structure of an version object
161         :param return_request_id: If an empty list is provided, populate this
162                               list with the request ID value from the header
163                               x-openstack-request-id
164         :rtype: list of :class:`version`
165         """
166         absolute_limit = kwargs.get('limit')
167         page_size = kwargs.get('page_size', DEFAULT_PAGE_SIZE)
168
169         def paginate(qp, return_request_id=None):
170             for param, value in six.iteritems(qp):
171                 if isinstance(value, six.string_types):
172                     # Note(flaper87) Url encoding should
173                     # be moved inside http utils, at least
174                     # shouldn't be here.
175                     #
176                     # Making sure all params are str before
177                     # trying to encode them
178                     qp[param] = encodeutils.safe_decode(value)
179
180             url = '/v1/versions?%s' % urlparse.urlencode(qp)
181             versions, resp = self._list(url, "versions")
182
183             if return_request_id is not None:
184                 return_request_id.append(resp.headers.get(OS_REQ_ID_HDR, None))
185
186             for version in versions:
187                 yield version
188
189         return_request_id = kwargs.get('return_req_id', None)
190
191         params = self._build_params(kwargs)
192
193         seen = 0
194         while True:
195             seen_last_page = 0
196             filtered = 0
197             for version in paginate(params, return_request_id):
198                 last_version = version.id
199
200                 if (absolute_limit is not None and
201                         seen + seen_last_page >= absolute_limit):
202                     # Note(kragniz): we've seen enough images
203                     return
204                 else:
205                     seen_last_page += 1
206                     yield version
207
208             seen += seen_last_page
209
210             if seen_last_page + filtered == 0:
211                 # Note(kragniz): we didn't get any versions in the last page
212                 return
213
214             if absolute_limit is not None and seen >= absolute_limit:
215                 # Note(kragniz): reached the limit of versions to return
216                 return
217
218             if page_size and seen_last_page + filtered < page_size:
219                 # Note(kragniz): we've reached the last page of the versions
220                 return
221
222             # Note(kragniz): there are more versions to come
223             params['marker'] = last_version
224             seen_last_page = 0
225
226     def add(self, **kwargs):
227         """Add a version
228
229         TODO(bcwaldon): document accepted params
230         """
231
232         fields = {}
233         for field in kwargs:
234             if field in CREATE_PARAMS:
235                 fields[field] = kwargs[field]
236             elif field == 'return_req_id':
237                 continue
238             else:
239                 msg = 'create() got an unexpected keyword argument \'%s\''
240                 raise TypeError(msg % field)
241
242         hdrs = self._version_meta_to_headers(fields)
243
244         resp, body = self.client.post('/v1/versions',
245                                       headers=None,
246                                       data=hdrs)
247         return_request_id = kwargs.get('return_req_id', None)
248         if return_request_id is not None:
249             return_request_id.append(resp.headers.get(OS_REQ_ID_HDR, None))
250
251         return Version(self, self._format_version_meta_for_user(
252             body['version']))
253
254     def delete(self, version, **kwargs):
255         """Delete an version."""
256         url = "/v1/versions/%s" % base.getid(version)
257         resp, body = self.client.delete(url)
258         return_request_id = kwargs.get('return_req_id', None)
259         if return_request_id is not None:
260             return_request_id.append(resp.headers.get(OS_REQ_ID_HDR, None))
261
262     def update(self, version, **kwargs):
263         """Update an version
264
265         TODO(bcwaldon): document accepted params
266         """
267         hdrs = {}
268         fields = {}
269         for field in kwargs:
270             if field in CREATE_PARAMS:
271                 fields[field] = kwargs[field]
272             elif field == 'return_req_id':
273                 continue
274         hdrs.update(self._version_meta_to_headers(fields))
275
276         url = '/v1/versions/%s' % base.getid(version)
277         resp, body = self.client.put(url, headers=None, data=hdrs)
278         return_request_id = kwargs.get('return_req_id', None)
279         if return_request_id is not None:
280             return_request_id.append(resp.headers.get(OS_REQ_ID_HDR, None))
281
282         return Version(self, self._format_version_meta_for_user(
283             body['version_meta']))
284
285     def version(self, **kwargs):
286         """Get internal or external version of escalator.
287
288         TODO(bcwaldon): document accepted params
289         """
290         fields = {}
291         for field in kwargs:
292             if field in VERSION_PARAMS:
293                 fields[field] = kwargs[field]
294             else:
295                 msg = 'install() got an unexpected keyword argument \'%s\''
296                 raise TypeError(msg % field)
297
298         url = '/v1/version'
299         hdrs = self._restore_meta_to_headers(fields)
300         resp, body = self.client.post(url, headers=None, data=hdrs)
301         return Version(self, body)