Statistics
| Branch: | Tag: | Revision:

root / lib / rapi / baserlib.py @ fe267188

History | View | Annotate | Download (7.4 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=""):
111
  """Helper function to set tags.
112

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

    
116

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

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

    
123

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

127
  @param itemslist: a list of items values
128
  @param fields: a list of items names
129

130
  @return: a list of mapped dictionaries
131

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

    
139

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

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

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

154
  """
155
  result = {}
156

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

    
164
  return result
165

    
166

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

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

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

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

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

    
201

    
202
def FeedbackFn(ts, log_type, log_msg):
203
  """Feedback logging function for http case.
204

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

208
  """
209
  logging.info("%s: %s", log_type, log_msg)
210

    
211

    
212
class R_Generic(object):
213
  """Generic class for resources.
214

215
  """
216
  # Default permission requirements
217
  GET_ACCESS = []
218
  PUT_ACCESS = [rapi.RAPI_ACCESS_WRITE]
219
  POST_ACCESS = [rapi.RAPI_ACCESS_WRITE]
220
  DELETE_ACCESS = [rapi.RAPI_ACCESS_WRITE]
221

    
222
  def __init__(self, items, queryargs, req):
223
    """Generic resource constructor.
224

225
    @param items: a list with variables encoded in the URL
226
    @param queryargs: a dictionary with additional options from URL
227

228
    """
229
    self.items = items
230
    self.queryargs = queryargs
231
    self.req = req
232
    self.sn = None
233

    
234
  def getSerialNumber(self):
235
    """Get Serial Number.
236

237
    """
238
    return self.sn
239

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

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

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

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

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

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

275
    @param name: the required parameter
276

277
    """
278
    if name in self.req.request_body:
279
      return self.req.request_body[name]
280
    elif args:
281
      return args[0]
282
    else:
283
      raise http.HttpBadRequest("Required parameter '%s' is missing" %
284
                                name)
285

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

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

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

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