Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / baserlib.py @ 26d3fd2f

History | View | Annotate | Download (9.7 kB)

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