Remove mcpu's ReportLocks callback
[ganeti-local] / lib / rapi / baserlib.py
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"))