"""
-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
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)
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):
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:
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"))