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