X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/441e7cfd9cadc50ff7df27a98e1d5b2db169e15d..9dd6889ba89836fe85de9ecee5c554909ce20aa5:/lib/rapi/baserlib.py diff --git a/lib/rapi/baserlib.py b/lib/rapi/baserlib.py index 0de15c9..dc7cd12 100644 --- a/lib/rapi/baserlib.py +++ b/lib/rapi/baserlib.py @@ -23,19 +23,31 @@ """ -import ganeti.cli -import ganeti.opcodes +# pylint: disable-msg=C0103 + +# C0103: Invalid name, since the R_* names are not conforming + +import logging from ganeti import luxi +from ganeti import rapi +from ganeti import http +from ganeti import ssconf +from ganeti import constants +from ganeti import opcodes +from ganeti import errors + + +# Dummy value to detect unchanged parameters +_DEFAULT = object() def BuildUriList(ids, uri_format, uri_fields=("name", "uri")): """Builds a URI list as used by index resources. - Args: - - ids: List of ids as strings - - uri_format: Format to be applied for URI - - uri_fields: Optional parameter for field ids + @param ids: list of ids as strings + @param uri_format: format to be applied for URI + @param uri_fields: optional parameter for field IDs """ (field_id, field_uri) = uri_fields @@ -53,9 +65,8 @@ def BuildUriList(ids, uri_format, uri_fields=("name", "uri")): def ExtractField(sequence, index): """Creates a list containing one column out of a list of lists. - Args: - - sequence: Sequence of lists - - index: Index of field + @param sequence: sequence of lists + @param index: index of field """ return map(lambda item: item[index], sequence) @@ -64,13 +75,12 @@ def ExtractField(sequence, index): def MapFields(names, data): """Maps two lists into one dictionary. - Args: - - names: Field names (list of strings) - - data: Field data (list) + Example:: + >>> MapFields(["a", "b"], ["foo", 123]) + {'a': 'foo', 'b': 123} - Example: - >>> MapFields(["a", "b"], ["foo", 123]) - {'a': 'foo', 'b': 123} + @param names: field names (list of strings) + @param data: field data (list) """ if len(names) != len(data): @@ -78,41 +88,53 @@ def MapFields(names, data): return dict(zip(names, data)) -def _Tags_GET(kind, name=None): +def _Tags_GET(kind, name): """Helper function to retrieve tags. """ - if name is None: - # Do not cause "missing parameter" error, which happens if a parameter - # is None. - name = "" - op = ganeti.opcodes.OpGetTags(kind=kind, name=name) - tags = ganeti.cli.SubmitOpCode(op) + if kind == constants.TAG_INSTANCE or kind == constants.TAG_NODE: + if not name: + raise http.HttpBadRequest("Missing name on tag request") + cl = GetClient() + if kind == constants.TAG_INSTANCE: + fn = cl.QueryInstances + else: + fn = cl.QueryNodes + result = fn(names=[name], fields=["tags"], use_locking=False) + if not result or not result[0]: + raise http.HttpBadGateway("Invalid response from tag query") + tags = result[0][0] + elif kind == constants.TAG_CLUSTER: + ssc = ssconf.SimpleStore() + tags = ssc.GetClusterTags() + return list(tags) -def _Tags_POST(kind, tags, name=None): +def _Tags_PUT(kind, tags, name, dry_run): """Helper function to set tags. """ - if name is None: - # Do not cause "missing parameter" error, which happens if a parameter - # is None. - name = "" - cl = luxi.Client() - return cl.SubmitJob([ganeti.opcodes.OpAddTags(kind=kind, name=name, - tags=tags)]) + return SubmitJob([opcodes.OpAddTags(kind=kind, name=name, + tags=tags, dry_run=dry_run)]) + + +def _Tags_DELETE(kind, tags, name, dry_run): + """Helper function to delete tags. + + """ + return SubmitJob([opcodes.OpDelTags(kind=kind, name=name, + tags=tags, dry_run=dry_run)]) def MapBulkFields(itemslist, fields): """Map value to field name in to one dictionary. - Args: - - itemslist: A list of items values - - instance: A list of items names + @param itemslist: a list of items values + @param fields: a list of items names + + @return: a list of mapped dictionaries - Returns: - A list of mapped dictionaries """ items_details = [] for item in itemslist: @@ -121,20 +143,234 @@ def MapBulkFields(itemslist, fields): return items_details +def MakeParamsDict(opts, params): + """Makes params dictionary out of a option set. + + This function returns a dictionary needed for hv or be parameters. But only + those fields which provided in the option set. Takes parameters frozensets + from constants. + + @type opts: dict + @param opts: selected options + @type params: frozenset + @param params: subset of options + @rtype: dict + @return: dictionary of options, filtered by given subset. + + """ + result = {} + + for p in params: + try: + value = opts[p] + except KeyError: + continue + result[p] = value + + return result + + +def SubmitJob(op, cl=None): + """Generic wrapper for submit job, for better http compatibility. + + @type op: list + @param op: the list of opcodes for the job + @type cl: None or luxi.Client + @param cl: optional luxi client to use + @rtype: string + @return: the job ID + + """ + try: + if cl is None: + cl = GetClient() + return cl.SubmitJob(op) + except errors.JobQueueFull: + raise http.HttpServiceUnavailable("Job queue is full, needs archiving") + except errors.JobQueueDrainError: + raise http.HttpServiceUnavailable("Job queue is drained, cannot submit") + except luxi.NoMasterError, err: + raise http.HttpBadGateway("Master seems to unreachable: %s" % str(err)) + except luxi.TimeoutError, err: + raise http.HttpGatewayTimeout("Timeout while talking to the master" + " daemon. Error: %s" % str(err)) + + +def HandleItemQueryErrors(fn, *args, **kwargs): + """Converts errors when querying a single item. + + """ + try: + return fn(*args, **kwargs) + except errors.OpPrereqError, err: + if len(err.args) == 2 and err.args[1] == errors.ECODE_NOENT: + raise http.HttpNotFound() + + raise + + +def GetClient(): + """Geric wrapper for luxi.Client(), for better http compatiblity. + + """ + try: + return luxi.Client() + except luxi.NoMasterError, err: + raise http.HttpBadGateway("Master seems to unreachable: %s" % str(err)) + + +def FeedbackFn(msg): + """Feedback logging function for jobs. + + We don't have a stdout for printing log messages, so log them to the + http log at least. + + @param msg: the message + + """ + (_, log_type, log_msg) = msg + logging.info("%s: %s", log_type, log_msg) + + +def CheckType(value, exptype, descr): + """Abort request if value type doesn't match expected type. + + @param value: Value + @type exptype: type + @param exptype: Expected type + @type descr: string + @param descr: Description of value + @return: Value (allows inline usage) + + """ + if not isinstance(value, exptype): + raise http.HttpBadRequest("%s: Type is '%s', but '%s' is expected" % + (descr, type(value).__name__, exptype.__name__)) + + return value + + +def CheckParameter(data, name, default=_DEFAULT, exptype=_DEFAULT): + """Check and return the value for a given parameter. + + If no default value was given and the parameter doesn't exist in the input + data, an error is raise. + + @type data: dict + @param data: Dictionary containing input data + @type name: string + @param name: Parameter name + @param default: Default value (can be None) + @param exptype: Expected type (can be None) + + """ + try: + value = data[name] + except KeyError: + if default is not _DEFAULT: + return default + + raise http.HttpBadRequest("Required parameter '%s' is missing" % + name) + + if exptype is _DEFAULT: + return value + + return CheckType(value, exptype, "'%s' parameter" % name) + + class R_Generic(object): """Generic class for resources. """ - def __init__(self, request, items, queryargs, post_data): + # Default permission requirements + GET_ACCESS = [] + PUT_ACCESS = [rapi.RAPI_ACCESS_WRITE] + POST_ACCESS = [rapi.RAPI_ACCESS_WRITE] + DELETE_ACCESS = [rapi.RAPI_ACCESS_WRITE] + + def __init__(self, items, queryargs, req): """Generic resource constructor. - Args: - request: HTTPRequestHandler object - items: a list with variables encoded in the URL - queryargs: a dictionary with additional options from URL + @param items: a list with variables encoded in the URL + @param queryargs: a dictionary with additional options from URL """ - self.request = request self.items = items self.queryargs = queryargs - self.post_data = post_data + self._req = req + + def _GetRequestBody(self): + """Returns the body data. + + """ + return self._req.private.body_data + + request_body = property(fget=_GetRequestBody) + + def _checkIntVariable(self, name, default=0): + """Return the parsed value of an int argument. + + """ + val = self.queryargs.get(name, default) + if isinstance(val, list): + if val: + val = val[0] + else: + val = default + try: + val = int(val) + except (ValueError, TypeError): + raise http.HttpBadRequest("Invalid value for the" + " '%s' parameter" % (name,)) + return val + + def _checkStringVariable(self, name, default=None): + """Return the parsed value of an int argument. + + """ + val = self.queryargs.get(name, default) + if isinstance(val, list): + if val: + val = val[0] + else: + val = default + return val + + def getBodyParameter(self, name, *args): + """Check and return the value for a given parameter. + + If a second parameter is not given, an error will be returned, + otherwise this parameter specifies the default value. + + @param name: the required parameter + + """ + if args: + return CheckParameter(self.request_body, name, default=args[0]) + + return CheckParameter(self.request_body, name) + + def useLocking(self): + """Check if the request specifies locking. + + """ + return bool(self._checkIntVariable("lock")) + + def useBulk(self): + """Check if the request specifies bulk querying. + + """ + return bool(self._checkIntVariable("bulk")) + + def useForce(self): + """Check if the request specifies a forced operation. + + """ + return bool(self._checkIntVariable("force")) + + def dryRun(self): + """Check if the request specifies dry-run mode. + + """ + return bool(self._checkIntVariable("dry-run"))