Package ganeti :: Package rapi :: Module baserlib
[hide private]
[frames] | no frames]

Source Code for Module ganeti.rapi.baserlib

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2006, 2007, 2008 Google Inc. 
  5  # 
  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. 
 10  # 
 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. 
 15  # 
 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 
 19  # 02110-1301, USA. 
 20   
 21   
 22  """Remote API base resources library. 
 23   
 24  """ 
 25   
 26  # pylint: disable-msg=C0103 
 27   
 28  # C0103: Invalid name, since the R_* names are not conforming 
 29   
 30  import logging 
 31   
 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 
 39   
 40   
 41  # Dummy value to detect unchanged parameters 
 42  _DEFAULT = object() 
 43   
 44   
45 -def BuildUriList(ids, uri_format, uri_fields=("name", "uri")):
46 """Builds a URI list as used by index resources. 47 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 51 52 """ 53 (field_id, field_uri) = uri_fields 54 55 def _MapId(m_id): 56 return { field_id: m_id, field_uri: uri_format % m_id, }
57 58 # Make sure the result is sorted, makes it nicer to look at and simplifies 59 # unittests. 60 ids.sort() 61 62 return map(_MapId, ids) 63 64
65 -def ExtractField(sequence, index):
66 """Creates a list containing one column out of a list of lists. 67 68 @param sequence: sequence of lists 69 @param index: index of field 70 71 """ 72 return map(lambda item: item[index], sequence)
73 74
75 -def MapFields(names, data):
76 """Maps two lists into one dictionary. 77 78 Example:: 79 >>> MapFields(["a", "b"], ["foo", 123]) 80 {'a': 'foo', 'b': 123} 81 82 @param names: field names (list of strings) 83 @param data: field data (list) 84 85 """ 86 if len(names) != len(data): 87 raise AttributeError("Names and data must have the same length") 88 return dict(zip(names, data))
89 90
91 -def _Tags_GET(kind, name):
92 """Helper function to retrieve tags. 93 94 """ 95 if kind == constants.TAG_INSTANCE or kind == constants.TAG_NODE: 96 if not name: 97 raise http.HttpBadRequest("Missing name on tag request") 98 cl = GetClient() 99 if kind == constants.TAG_INSTANCE: 100 fn = cl.QueryInstances 101 else: 102 fn = cl.QueryNodes 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") 106 tags = result[0][0] 107 elif kind == constants.TAG_CLUSTER: 108 ssc = ssconf.SimpleStore() 109 tags = ssc.GetClusterTags() 110 111 return list(tags)
112 113
114 -def _Tags_PUT(kind, tags, name, dry_run):
115 """Helper function to set tags. 116 117 """ 118 return SubmitJob([opcodes.OpTagsSet(kind=kind, name=name, 119 tags=tags, dry_run=dry_run)])
120 121
122 -def _Tags_DELETE(kind, tags, name, dry_run):
123 """Helper function to delete tags. 124 125 """ 126 return SubmitJob([opcodes.OpTagsDel(kind=kind, name=name, 127 tags=tags, dry_run=dry_run)])
128 129
130 -def MapBulkFields(itemslist, fields):
131 """Map value to field name in to one dictionary. 132 133 @param itemslist: a list of items values 134 @param fields: a list of items names 135 136 @return: a list of mapped dictionaries 137 138 """ 139 items_details = [] 140 for item in itemslist: 141 mapped = MapFields(fields, item) 142 items_details.append(mapped) 143 return items_details
144 145
146 -def MakeParamsDict(opts, params):
147 """Makes params dictionary out of a option set. 148 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 151 from constants. 152 153 @type opts: dict 154 @param opts: selected options 155 @type params: frozenset 156 @param params: subset of options 157 @rtype: dict 158 @return: dictionary of options, filtered by given subset. 159 160 """ 161 result = {} 162 163 for p in params: 164 try: 165 value = opts[p] 166 except KeyError: 167 continue 168 result[p] = value 169 170 return result
171 172
173 -def FillOpcode(opcls, body, static):
174 """Fills an opcode with body parameters. 175 176 Parameter types are checked. 177 178 @type opcls: L{opcodes.OpCode} 179 @param opcls: Opcode class 180 @type body: dict 181 @param body: Body parameters as received from client 182 @type static: dict 183 @param static: Static parameters which can't be modified by client 184 @return: Opcode object 185 186 """ 187 CheckType(body, dict, "Body contents") 188 189 if static: 190 overwritten = set(body.keys()) & set(static.keys()) 191 if overwritten: 192 raise http.HttpBadRequest("Can't overwrite static parameters %r" % 193 overwritten) 194 195 # Combine parameters 196 params = body.copy() 197 198 if static: 199 params.update(static) 200 201 # Convert keys to strings (simplejson decodes them as unicode) 202 params = dict((str(key), value) for (key, value) in params.items()) 203 204 try: 205 op = opcls(**params) # pylint: disable-msg=W0142 206 op.Validate(False) 207 except (errors.OpPrereqError, TypeError), err: 208 raise http.HttpBadRequest("Invalid body parameters: %s" % err) 209 210 return op
211 212
213 -def SubmitJob(op, cl=None):
214 """Generic wrapper for submit job, for better http compatibility. 215 216 @type op: list 217 @param op: the list of opcodes for the job 218 @type cl: None or luxi.Client 219 @param cl: optional luxi client to use 220 @rtype: string 221 @return: the job ID 222 223 """ 224 try: 225 if cl is None: 226 cl = GetClient() 227 return cl.SubmitJob(op) 228 except errors.JobQueueFull: 229 raise http.HttpServiceUnavailable("Job queue is full, needs archiving") 230 except errors.JobQueueDrainError: 231 raise http.HttpServiceUnavailable("Job queue is drained, cannot submit") 232 except luxi.NoMasterError, err: 233 raise http.HttpBadGateway("Master seems to be unreachable: %s" % str(err)) 234 except luxi.PermissionError: 235 raise http.HttpInternalServerError("Internal error: no permission to" 236 " connect to the master daemon") 237 except luxi.TimeoutError, err: 238 raise http.HttpGatewayTimeout("Timeout while talking to the master" 239 " daemon. Error: %s" % str(err))
240 241
242 -def HandleItemQueryErrors(fn, *args, **kwargs):
243 """Converts errors when querying a single item. 244 245 """ 246 try: 247 return fn(*args, **kwargs) 248 except errors.OpPrereqError, err: 249 if len(err.args) == 2 and err.args[1] == errors.ECODE_NOENT: 250 raise http.HttpNotFound() 251 252 raise
253 254
255 -def GetClient():
256 """Geric wrapper for luxi.Client(), for better http compatiblity. 257 258 """ 259 try: 260 return luxi.Client() 261 except luxi.NoMasterError, err: 262 raise http.HttpBadGateway("Master seems to unreachable: %s" % str(err)) 263 except luxi.PermissionError: 264 raise http.HttpInternalServerError("Internal error: no permission to" 265 " connect to the master daemon")
266 267
268 -def FeedbackFn(msg):
269 """Feedback logging function for jobs. 270 271 We don't have a stdout for printing log messages, so log them to the 272 http log at least. 273 274 @param msg: the message 275 276 """ 277 (_, log_type, log_msg) = msg 278 logging.info("%s: %s", log_type, log_msg)
279 280
281 -def CheckType(value, exptype, descr):
282 """Abort request if value type doesn't match expected type. 283 284 @param value: Value 285 @type exptype: type 286 @param exptype: Expected type 287 @type descr: string 288 @param descr: Description of value 289 @return: Value (allows inline usage) 290 291 """ 292 if not isinstance(value, exptype): 293 raise http.HttpBadRequest("%s: Type is '%s', but '%s' is expected" % 294 (descr, type(value).__name__, exptype.__name__)) 295 296 return value
297 298
299 -def CheckParameter(data, name, default=_DEFAULT, exptype=_DEFAULT):
300 """Check and return the value for a given parameter. 301 302 If no default value was given and the parameter doesn't exist in the input 303 data, an error is raise. 304 305 @type data: dict 306 @param data: Dictionary containing input data 307 @type name: string 308 @param name: Parameter name 309 @param default: Default value (can be None) 310 @param exptype: Expected type (can be None) 311 312 """ 313 try: 314 value = data[name] 315 except KeyError: 316 if default is not _DEFAULT: 317 return default 318 319 raise http.HttpBadRequest("Required parameter '%s' is missing" % 320 name) 321 322 if exptype is _DEFAULT: 323 return value 324 325 return CheckType(value, exptype, "'%s' parameter" % name)
326 327
328 -class R_Generic(object):
329 """Generic class for resources. 330 331 """ 332 # Default permission requirements 333 GET_ACCESS = [] 334 PUT_ACCESS = [rapi.RAPI_ACCESS_WRITE] 335 POST_ACCESS = [rapi.RAPI_ACCESS_WRITE] 336 DELETE_ACCESS = [rapi.RAPI_ACCESS_WRITE] 337
338 - def __init__(self, items, queryargs, req):
339 """Generic resource constructor. 340 341 @param items: a list with variables encoded in the URL 342 @param queryargs: a dictionary with additional options from URL 343 344 """ 345 self.items = items 346 self.queryargs = queryargs 347 self._req = req
348
349 - def _GetRequestBody(self):
350 """Returns the body data. 351 352 """ 353 return self._req.private.body_data
354 355 request_body = property(fget=_GetRequestBody) 356
357 - def _checkIntVariable(self, name, default=0):
358 """Return the parsed value of an int argument. 359 360 """ 361 val = self.queryargs.get(name, default) 362 if isinstance(val, list): 363 if val: 364 val = val[0] 365 else: 366 val = default 367 try: 368 val = int(val) 369 except (ValueError, TypeError): 370 raise http.HttpBadRequest("Invalid value for the" 371 " '%s' parameter" % (name,)) 372 return val
373
374 - def _checkStringVariable(self, name, default=None):
375 """Return the parsed value of an int argument. 376 377 """ 378 val = self.queryargs.get(name, default) 379 if isinstance(val, list): 380 if val: 381 val = val[0] 382 else: 383 val = default 384 return val
385
386 - def getBodyParameter(self, name, *args):
387 """Check and return the value for a given parameter. 388 389 If a second parameter is not given, an error will be returned, 390 otherwise this parameter specifies the default value. 391 392 @param name: the required parameter 393 394 """ 395 if args: 396 return CheckParameter(self.request_body, name, default=args[0]) 397 398 return CheckParameter(self.request_body, name)
399
400 - def useLocking(self):
401 """Check if the request specifies locking. 402 403 """ 404 return bool(self._checkIntVariable("lock"))
405
406 - def useBulk(self):
407 """Check if the request specifies bulk querying. 408 409 """ 410 return bool(self._checkIntVariable("bulk"))
411
412 - def useForce(self):
413 """Check if the request specifies a forced operation. 414 415 """ 416 return bool(self._checkIntVariable("force"))
417
418 - def dryRun(self):
419 """Check if the request specifies dry-run mode. 420 421 """ 422 return bool(self._checkIntVariable("dry-run"))
423