Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / baserlib.py @ ab221ddf

History | View | Annotate | Download (7.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
def BuildUriList(ids, uri_format, uri_fields=("name", "uri")):
42
  """Builds a URI list as used by index resources.
43

44
  @param ids: list of ids as strings
45
  @param uri_format: format to be applied for URI
46
  @param uri_fields: optional parameter for field IDs
47

48
  """
49
  (field_id, field_uri) = uri_fields
50

    
51
  def _MapId(m_id):
52
    return { field_id: m_id, field_uri: uri_format % m_id, }
53

    
54
  # Make sure the result is sorted, makes it nicer to look at and simplifies
55
  # unittests.
56
  ids.sort()
57

    
58
  return map(_MapId, ids)
59

    
60

    
61
def ExtractField(sequence, index):
62
  """Creates a list containing one column out of a list of lists.
63

64
  @param sequence: sequence of lists
65
  @param index: index of field
66

67
  """
68
  return map(lambda item: item[index], sequence)
69

    
70

    
71
def MapFields(names, data):
72
  """Maps two lists into one dictionary.
73

74
  Example::
75
      >>> MapFields(["a", "b"], ["foo", 123])
76
      {'a': 'foo', 'b': 123}
77

78
  @param names: field names (list of strings)
79
  @param data: field data (list)
80

81
  """
82
  if len(names) != len(data):
83
    raise AttributeError("Names and data must have the same length")
84
  return dict(zip(names, data))
85

    
86

    
87
def _Tags_GET(kind, name):
88
  """Helper function to retrieve tags.
89

90
  """
91
  if kind == constants.TAG_INSTANCE or kind == constants.TAG_NODE:
92
    if not name:
93
      raise http.HttpBadRequest("Missing name on tag request")
94
    cl = GetClient()
95
    if kind == constants.TAG_INSTANCE:
96
      fn = cl.QueryInstances
97
    else:
98
      fn = cl.QueryNodes
99
    result = fn(names=[name], fields=["tags"], use_locking=False)
100
    if not result or not result[0]:
101
      raise http.HttpBadGateway("Invalid response from tag query")
102
    tags = result[0][0]
103
  elif kind == constants.TAG_CLUSTER:
104
    ssc = ssconf.SimpleStore()
105
    tags = ssc.GetClusterTags()
106

    
107
  return list(tags)
108

    
109

    
110
def _Tags_PUT(kind, tags, name, dry_run):
111
  """Helper function to set tags.
112

113
  """
114
  return SubmitJob([opcodes.OpAddTags(kind=kind, name=name,
115
                                      tags=tags, dry_run=dry_run)])
116

    
117

    
118
def _Tags_DELETE(kind, tags, name, dry_run):
119
  """Helper function to delete tags.
120

121
  """
122
  return SubmitJob([opcodes.OpDelTags(kind=kind, name=name,
123
                                      tags=tags, dry_run=dry_run)])
124

    
125

    
126
def MapBulkFields(itemslist, fields):
127
  """Map value to field name in to one dictionary.
128

129
  @param itemslist: a list of items values
130
  @param fields: a list of items names
131

132
  @return: a list of mapped dictionaries
133

134
  """
135
  items_details = []
136
  for item in itemslist:
137
    mapped = MapFields(fields, item)
138
    items_details.append(mapped)
139
  return items_details
140

    
141

    
142
def MakeParamsDict(opts, params):
143
  """Makes params dictionary out of a option set.
144

145
  This function returns a dictionary needed for hv or be parameters. But only
146
  those fields which provided in the option set. Takes parameters frozensets
147
  from constants.
148

149
  @type opts: dict
150
  @param opts: selected options
151
  @type params: frozenset
152
  @param params: subset of options
153
  @rtype: dict
154
  @return: dictionary of options, filtered by given subset.
155

156
  """
157
  result = {}
158

    
159
  for p in params:
160
    try:
161
      value = opts[p]
162
    except KeyError:
163
      continue
164
    result[p] = value
165

    
166
  return result
167

    
168

    
169
def SubmitJob(op, cl=None):
170
  """Generic wrapper for submit job, for better http compatibility.
171

172
  @type op: list
173
  @param op: the list of opcodes for the job
174
  @type cl: None or luxi.Client
175
  @param cl: optional luxi client to use
176
  @rtype: string
177
  @return: the job ID
178

179
  """
180
  try:
181
    if cl is None:
182
      cl = GetClient()
183
    return cl.SubmitJob(op)
184
  except errors.JobQueueFull:
185
    raise http.HttpServiceUnavailable("Job queue is full, needs archiving")
186
  except errors.JobQueueDrainError:
187
    raise http.HttpServiceUnavailable("Job queue is drained, cannot submit")
188
  except luxi.NoMasterError, err:
189
    raise http.HttpBadGateway("Master seems to unreachable: %s" % str(err))
190
  except luxi.TimeoutError, err:
191
    raise http.HttpGatewayTimeout("Timeout while talking to the master"
192
                                  " daemon. Error: %s" % str(err))
193

    
194
def GetClient():
195
  """Geric wrapper for luxi.Client(), for better http compatiblity.
196

197
  """
198
  try:
199
    return luxi.Client()
200
  except luxi.NoMasterError, err:
201
    raise http.HttpBadGateway("Master seems to unreachable: %s" % str(err))
202

    
203

    
204
def FeedbackFn(ts, log_type, log_msg): # pylint: disable-msg=W0613
205
  """Feedback logging function for http case.
206

207
  We don't have a stdout for printing log messages, so log them to the
208
  http log at least.
209

210
  @param ts: the timestamp (unused)
211

212
  """
213
  logging.info("%s: %s", log_type, log_msg)
214

    
215

    
216
class R_Generic(object):
217
  """Generic class for resources.
218

219
  """
220
  # Default permission requirements
221
  GET_ACCESS = []
222
  PUT_ACCESS = [rapi.RAPI_ACCESS_WRITE]
223
  POST_ACCESS = [rapi.RAPI_ACCESS_WRITE]
224
  DELETE_ACCESS = [rapi.RAPI_ACCESS_WRITE]
225

    
226
  def __init__(self, items, queryargs, req):
227
    """Generic resource constructor.
228

229
    @param items: a list with variables encoded in the URL
230
    @param queryargs: a dictionary with additional options from URL
231

232
    """
233
    self.items = items
234
    self.queryargs = queryargs
235
    self.req = req
236

    
237
  def _checkIntVariable(self, name, default=0):
238
    """Return the parsed value of an int argument.
239

240
    """
241
    val = self.queryargs.get(name, default)
242
    if isinstance(val, list):
243
      if val:
244
        val = val[0]
245
      else:
246
        val = default
247
    try:
248
      val = int(val)
249
    except (ValueError, TypeError):
250
      raise http.HttpBadRequest("Invalid value for the"
251
                                " '%s' parameter" % (name,))
252
    return val
253

    
254
  def _checkStringVariable(self, name, default=None):
255
    """Return the parsed value of an int argument.
256

257
    """
258
    val = self.queryargs.get(name, default)
259
    if isinstance(val, list):
260
      if val:
261
        val = val[0]
262
      else:
263
        val = default
264
    return val
265

    
266
  def getBodyParameter(self, name, *args):
267
    """Check and return the value for a given parameter.
268

269
    If a second parameter is not given, an error will be returned,
270
    otherwise this parameter specifies the default value.
271

272
    @param name: the required parameter
273

274
    """
275
    try:
276
      return self.req.private.body_data[name]
277
    except KeyError:
278
      if args:
279
        return args[0]
280

    
281
    raise http.HttpBadRequest("Required parameter '%s' is missing" % name)
282

    
283
  def useLocking(self):
284
    """Check if the request specifies locking.
285

286
    """
287
    return self._checkIntVariable('lock')
288

    
289
  def useBulk(self):
290
    """Check if the request specifies bulk querying.
291

292
    """
293
    return self._checkIntVariable('bulk')
294

    
295
  def useForce(self):
296
    """Check if the request specifies a forced operation.
297

298
    """
299
    return self._checkIntVariable('force')
300

    
301
  def dryRun(self):
302
    """Check if the request specifies dry-run mode.
303

304
    """
305
    return self._checkIntVariable('dry-run')