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