4 # Copyright (C) 2006, 2007, 2008 Google Inc.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 """Remote API base resources library.
26 # pylint: disable-msg=C0103
28 # C0103: Invalid name, since the R_* names are not conforming
32 from ganeti import luxi
33 from ganeti import rapi
34 from ganeti import http
35 from ganeti import ssconf
36 from ganeti import constants
37 from ganeti import opcodes
38 from ganeti import errors
41 # Dummy value to detect unchanged parameters
45 def BuildUriList(ids, uri_format, uri_fields=("name", "uri")):
46 """Builds a URI list as used by index resources.
48 @param ids: list of ids as strings
49 @param uri_format: format to be applied for URI
50 @param uri_fields: optional parameter for field IDs
53 (field_id, field_uri) = uri_fields
56 return { field_id: m_id, field_uri: uri_format % m_id, }
58 # Make sure the result is sorted, makes it nicer to look at and simplifies
62 return map(_MapId, ids)
65 def ExtractField(sequence, index):
66 """Creates a list containing one column out of a list of lists.
68 @param sequence: sequence of lists
69 @param index: index of field
72 return map(lambda item: item[index], sequence)
75 def MapFields(names, data):
76 """Maps two lists into one dictionary.
79 >>> MapFields(["a", "b"], ["foo", 123])
80 {'a': 'foo', 'b': 123}
82 @param names: field names (list of strings)
83 @param data: field data (list)
86 if len(names) != len(data):
87 raise AttributeError("Names and data must have the same length")
88 return dict(zip(names, data))
91 def _Tags_GET(kind, name):
92 """Helper function to retrieve tags.
95 if kind == constants.TAG_INSTANCE or kind == constants.TAG_NODE:
97 raise http.HttpBadRequest("Missing name on tag request")
99 if kind == constants.TAG_INSTANCE:
100 fn = cl.QueryInstances
103 result = fn(names=[name], fields=["tags"], use_locking=False)
104 if not result or not result[0]:
105 raise http.HttpBadGateway("Invalid response from tag query")
107 elif kind == constants.TAG_CLUSTER:
108 ssc = ssconf.SimpleStore()
109 tags = ssc.GetClusterTags()
114 def _Tags_PUT(kind, tags, name, dry_run):
115 """Helper function to set tags.
118 return SubmitJob([opcodes.OpAddTags(kind=kind, name=name,
119 tags=tags, dry_run=dry_run)])
122 def _Tags_DELETE(kind, tags, name, dry_run):
123 """Helper function to delete tags.
126 return SubmitJob([opcodes.OpDelTags(kind=kind, name=name,
127 tags=tags, dry_run=dry_run)])
130 def MapBulkFields(itemslist, fields):
131 """Map value to field name in to one dictionary.
133 @param itemslist: a list of items values
134 @param fields: a list of items names
136 @return: a list of mapped dictionaries
140 for item in itemslist:
141 mapped = MapFields(fields, item)
142 items_details.append(mapped)
146 def MakeParamsDict(opts, params):
147 """Makes params dictionary out of a option set.
149 This function returns a dictionary needed for hv or be parameters. But only
150 those fields which provided in the option set. Takes parameters frozensets
154 @param opts: selected options
155 @type params: frozenset
156 @param params: subset of options
158 @return: dictionary of options, filtered by given subset.
173 def SubmitJob(op, cl=None):
174 """Generic wrapper for submit job, for better http compatibility.
177 @param op: the list of opcodes for the job
178 @type cl: None or luxi.Client
179 @param cl: optional luxi client to use
187 return cl.SubmitJob(op)
188 except errors.JobQueueFull:
189 raise http.HttpServiceUnavailable("Job queue is full, needs archiving")
190 except errors.JobQueueDrainError:
191 raise http.HttpServiceUnavailable("Job queue is drained, cannot submit")
192 except luxi.NoMasterError, err:
193 raise http.HttpBadGateway("Master seems to unreachable: %s" % str(err))
194 except luxi.TimeoutError, err:
195 raise http.HttpGatewayTimeout("Timeout while talking to the master"
196 " daemon. Error: %s" % str(err))
199 """Geric wrapper for luxi.Client(), for better http compatiblity.
204 except luxi.NoMasterError, err:
205 raise http.HttpBadGateway("Master seems to unreachable: %s" % str(err))
208 def FeedbackFn(ts, log_type, log_msg): # pylint: disable-msg=W0613
209 """Feedback logging function for http case.
211 We don't have a stdout for printing log messages, so log them to the
214 @param ts: the timestamp (unused)
217 logging.info("%s: %s", log_type, log_msg)
220 def CheckType(value, exptype, descr):
221 """Abort request if value type doesn't match expected type.
225 @param exptype: Expected type
227 @param descr: Description of value
228 @return: Value (allows inline usage)
231 if not isinstance(value, exptype):
232 raise http.HttpBadRequest("%s: Type is '%s', but '%s' is expected" %
233 (descr, type(value).__name__, exptype.__name__))
238 def CheckParameter(data, name, default=_DEFAULT, exptype=_DEFAULT):
239 """Check and return the value for a given parameter.
241 If no default value was given and the parameter doesn't exist in the input
242 data, an error is raise.
245 @param data: Dictionary containing input data
247 @param name: Parameter name
248 @param default: Default value (can be None)
249 @param exptype: Expected type (can be None)
255 if default is not _DEFAULT:
258 raise http.HttpBadRequest("Required parameter '%s' is missing" %
261 if exptype is _DEFAULT:
264 return CheckType(value, exptype, "'%s' parameter" % name)
267 class R_Generic(object):
268 """Generic class for resources.
271 # Default permission requirements
273 PUT_ACCESS = [rapi.RAPI_ACCESS_WRITE]
274 POST_ACCESS = [rapi.RAPI_ACCESS_WRITE]
275 DELETE_ACCESS = [rapi.RAPI_ACCESS_WRITE]
277 def __init__(self, items, queryargs, req):
278 """Generic resource constructor.
280 @param items: a list with variables encoded in the URL
281 @param queryargs: a dictionary with additional options from URL
285 self.queryargs = queryargs
288 def _GetRequestBody(self):
289 """Returns the body data.
292 return self._req.private.body_data
294 request_body = property(fget=_GetRequestBody)
296 def _checkIntVariable(self, name, default=0):
297 """Return the parsed value of an int argument.
300 val = self.queryargs.get(name, default)
301 if isinstance(val, list):
308 except (ValueError, TypeError):
309 raise http.HttpBadRequest("Invalid value for the"
310 " '%s' parameter" % (name,))
313 def _checkStringVariable(self, name, default=None):
314 """Return the parsed value of an int argument.
317 val = self.queryargs.get(name, default)
318 if isinstance(val, list):
325 def getBodyParameter(self, name, *args):
326 """Check and return the value for a given parameter.
328 If a second parameter is not given, an error will be returned,
329 otherwise this parameter specifies the default value.
331 @param name: the required parameter
335 return CheckParameter(self.request_body, name, default=args[0])
337 return CheckParameter(self.request_body, name)
339 def useLocking(self):
340 """Check if the request specifies locking.
343 return self._checkIntVariable('lock')
346 """Check if the request specifies bulk querying.
349 return self._checkIntVariable('bulk')
352 """Check if the request specifies a forced operation.
355 return self._checkIntVariable('force')
358 """Check if the request specifies dry-run mode.
361 return self._checkIntVariable('dry-run')