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.OpAddTags(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.OpDelTags(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 SubmitJob(op, cl=None):
174 """Generic wrapper for submit job, for better http compatibility. 175 176 @type op: list 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 180 @rtype: string 181 @return: the job ID 182 183 """ 184 try: 185 if cl is None: 186 cl = GetClient() 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))
200 201
202 -def HandleItemQueryErrors(fn, *args, **kwargs):
203 """Converts errors when querying a single item. 204 205 """ 206 try: 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() 211 212 raise
213 214
215 -def GetClient():
216 """Geric wrapper for luxi.Client(), for better http compatiblity. 217 218 """ 219 try: 220 return luxi.Client() 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")
226 227
228 -def FeedbackFn(msg):
229 """Feedback logging function for jobs. 230 231 We don't have a stdout for printing log messages, so log them to the 232 http log at least. 233 234 @param msg: the message 235 236 """ 237 (_, log_type, log_msg) = msg 238 logging.info("%s: %s", log_type, log_msg)
239 240
241 -def CheckType(value, exptype, descr):
242 """Abort request if value type doesn't match expected type. 243 244 @param value: Value 245 @type exptype: type 246 @param exptype: Expected type 247 @type descr: string 248 @param descr: Description of value 249 @return: Value (allows inline usage) 250 251 """ 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__)) 255 256 return value
257 258
259 -def CheckParameter(data, name, default=_DEFAULT, exptype=_DEFAULT):
260 """Check and return the value for a given parameter. 261 262 If no default value was given and the parameter doesn't exist in the input 263 data, an error is raise. 264 265 @type data: dict 266 @param data: Dictionary containing input data 267 @type name: string 268 @param name: Parameter name 269 @param default: Default value (can be None) 270 @param exptype: Expected type (can be None) 271 272 """ 273 try: 274 value = data[name] 275 except KeyError: 276 if default is not _DEFAULT: 277 return default 278 279 raise http.HttpBadRequest("Required parameter '%s' is missing" % 280 name) 281 282 if exptype is _DEFAULT: 283 return value 284 285 return CheckType(value, exptype, "'%s' parameter" % name)
286 287
288 -class R_Generic(object):
289 """Generic class for resources. 290 291 """ 292 # Default permission requirements 293 GET_ACCESS = [] 294 PUT_ACCESS = [rapi.RAPI_ACCESS_WRITE] 295 POST_ACCESS = [rapi.RAPI_ACCESS_WRITE] 296 DELETE_ACCESS = [rapi.RAPI_ACCESS_WRITE] 297
298 - def __init__(self, items, queryargs, req):
299 """Generic resource constructor. 300 301 @param items: a list with variables encoded in the URL 302 @param queryargs: a dictionary with additional options from URL 303 304 """ 305 self.items = items 306 self.queryargs = queryargs 307 self._req = req
308
309 - def _GetRequestBody(self):
310 """Returns the body data. 311 312 """ 313 return self._req.private.body_data
314 315 request_body = property(fget=_GetRequestBody) 316
317 - def _checkIntVariable(self, name, default=0):
318 """Return the parsed value of an int argument. 319 320 """ 321 val = self.queryargs.get(name, default) 322 if isinstance(val, list): 323 if val: 324 val = val[0] 325 else: 326 val = default 327 try: 328 val = int(val) 329 except (ValueError, TypeError): 330 raise http.HttpBadRequest("Invalid value for the" 331 " '%s' parameter" % (name,)) 332 return val
333
334 - def _checkStringVariable(self, name, default=None):
335 """Return the parsed value of an int argument. 336 337 """ 338 val = self.queryargs.get(name, default) 339 if isinstance(val, list): 340 if val: 341 val = val[0] 342 else: 343 val = default 344 return val
345
346 - def getBodyParameter(self, name, *args):
347 """Check and return the value for a given parameter. 348 349 If a second parameter is not given, an error will be returned, 350 otherwise this parameter specifies the default value. 351 352 @param name: the required parameter 353 354 """ 355 if args: 356 return CheckParameter(self.request_body, name, default=args[0]) 357 358 return CheckParameter(self.request_body, name)
359
360 - def useLocking(self):
361 """Check if the request specifies locking. 362 363 """ 364 return bool(self._checkIntVariable("lock"))
365
366 - def useBulk(self):
367 """Check if the request specifies bulk querying. 368 369 """ 370 return bool(self._checkIntVariable("bulk"))
371
372 - def useForce(self):
373 """Check if the request specifies a forced operation. 374 375 """ 376 return bool(self._checkIntVariable("force"))
377
378 - def dryRun(self):
379 """Check if the request specifies dry-run mode. 380 381 """ 382 return bool(self._checkIntVariable("dry-run"))
383