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 be unreachable: %s" % str(err))
194 except luxi.PermissionError:
195 raise http.HttpInternalServerError("Internal error: no permission to"
196 " connect to the master daemon")
197 except luxi.TimeoutError, err:
198 raise http.HttpGatewayTimeout("Timeout while talking to the master"
199 " daemon. Error: %s" % str(err))
202 def HandleItemQueryErrors(fn, *args, **kwargs):
203 """Converts errors when querying a single item.
207 return fn(*args, **kwargs)
208 except errors.OpPrereqError, err:
209 if len(err.args) == 2 and err.args[1] == errors.ECODE_NOENT:
210 raise http.HttpNotFound()
216 """Geric wrapper for luxi.Client(), for better http compatiblity.
221 except luxi.NoMasterError, err:
222 raise http.HttpBadGateway("Master seems to unreachable: %s" % str(err))
223 except luxi.PermissionError:
224 raise http.HttpInternalServerError("Internal error: no permission to"
225 " connect to the master daemon")
229 """Feedback logging function for jobs.
231 We don't have a stdout for printing log messages, so log them to the
234 @param msg: the message
237 (_, log_type, log_msg) = msg
238 logging.info("%s: %s", log_type, log_msg)
241 def CheckType(value, exptype, descr):
242 """Abort request if value type doesn't match expected type.
246 @param exptype: Expected type
248 @param descr: Description of value
249 @return: Value (allows inline usage)
252 if not isinstance(value, exptype):
253 raise http.HttpBadRequest("%s: Type is '%s', but '%s' is expected" %
254 (descr, type(value).__name__, exptype.__name__))
259 def CheckParameter(data, name, default=_DEFAULT, exptype=_DEFAULT):
260 """Check and return the value for a given parameter.
262 If no default value was given and the parameter doesn't exist in the input
263 data, an error is raise.
266 @param data: Dictionary containing input data
268 @param name: Parameter name
269 @param default: Default value (can be None)
270 @param exptype: Expected type (can be None)
276 if default is not _DEFAULT:
279 raise http.HttpBadRequest("Required parameter '%s' is missing" %
282 if exptype is _DEFAULT:
285 return CheckType(value, exptype, "'%s' parameter" % name)
288 class R_Generic(object):
289 """Generic class for resources.
292 # Default permission requirements
294 PUT_ACCESS = [rapi.RAPI_ACCESS_WRITE]
295 POST_ACCESS = [rapi.RAPI_ACCESS_WRITE]
296 DELETE_ACCESS = [rapi.RAPI_ACCESS_WRITE]
298 def __init__(self, items, queryargs, req):
299 """Generic resource constructor.
301 @param items: a list with variables encoded in the URL
302 @param queryargs: a dictionary with additional options from URL
306 self.queryargs = queryargs
309 def _GetRequestBody(self):
310 """Returns the body data.
313 return self._req.private.body_data
315 request_body = property(fget=_GetRequestBody)
317 def _checkIntVariable(self, name, default=0):
318 """Return the parsed value of an int argument.
321 val = self.queryargs.get(name, default)
322 if isinstance(val, list):
329 except (ValueError, TypeError):
330 raise http.HttpBadRequest("Invalid value for the"
331 " '%s' parameter" % (name,))
334 def _checkStringVariable(self, name, default=None):
335 """Return the parsed value of an int argument.
338 val = self.queryargs.get(name, default)
339 if isinstance(val, list):
346 def getBodyParameter(self, name, *args):
347 """Check and return the value for a given parameter.
349 If a second parameter is not given, an error will be returned,
350 otherwise this parameter specifies the default value.
352 @param name: the required parameter
356 return CheckParameter(self.request_body, name, default=args[0])
358 return CheckParameter(self.request_body, name)
360 def useLocking(self):
361 """Check if the request specifies locking.
364 return bool(self._checkIntVariable("lock"))
367 """Check if the request specifies bulk querying.
370 return bool(self._checkIntVariable("bulk"))
373 """Check if the request specifies a forced operation.
376 return bool(self._checkIntVariable("force"))
379 """Check if the request specifies dry-run mode.
382 return bool(self._checkIntVariable("dry-run"))