Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / baserlib.py @ 627ad739

History | View | Annotate | Download (7.8 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
  request_body = property(fget=lambda self: self._req.private.body_data)
238

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

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

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

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

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

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

274
    @param name: the required parameter
275

276
    """
277
    try:
278
      return self.request_body[name]
279
    except KeyError:
280
      if args:
281
        return args[0]
282

    
283
    raise http.HttpBadRequest("Required parameter '%s' is missing" % name)
284

    
285
  def useLocking(self):
286
    """Check if the request specifies locking.
287

288
    """
289
    return self._checkIntVariable('lock')
290

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

294
    """
295
    return self._checkIntVariable('bulk')
296

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

300
    """
301
    return self._checkIntVariable('force')
302

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

306
    """
307
    return self._checkIntVariable('dry-run')