Further pylint disables, mostly for Unused args
[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 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     self.sn = None
237
238   def getSerialNumber(self):
239     """Get Serial Number.
240
241     """
242     return self.sn
243
244   def _checkIntVariable(self, name, default=0):
245     """Return the parsed value of an int argument.
246
247     """
248     val = self.queryargs.get(name, default)
249     if isinstance(val, list):
250       if val:
251         val = val[0]
252       else:
253         val = default
254     try:
255       val = int(val)
256     except (ValueError, TypeError):
257       raise http.HttpBadRequest("Invalid value for the"
258                                 " '%s' parameter" % (name,))
259     return val
260
261   def _checkStringVariable(self, name, default=None):
262     """Return the parsed value of an int argument.
263
264     """
265     val = self.queryargs.get(name, default)
266     if isinstance(val, list):
267       if val:
268         val = val[0]
269       else:
270         val = default
271     return val
272
273   def getBodyParameter(self, name, *args):
274     """Check and return the value for a given parameter.
275
276     If a second parameter is not given, an error will be returned,
277     otherwise this parameter specifies the default value.
278
279     @param name: the required parameter
280
281     """
282     if name in self.req.request_body:
283       return self.req.request_body[name]
284     elif args:
285       return args[0]
286     else:
287       raise http.HttpBadRequest("Required parameter '%s' is missing" %
288                                 name)
289
290   def useLocking(self):
291     """Check if the request specifies locking.
292
293     """
294     return self._checkIntVariable('lock')
295
296   def useBulk(self):
297     """Check if the request specifies bulk querying.
298
299     """
300     return self._checkIntVariable('bulk')
301
302   def useForce(self):
303     """Check if the request specifies a forced operation.
304
305     """
306     return self._checkIntVariable('force')
307
308   def dryRun(self):
309     """Check if the request specifies dry-run mode.
310
311     """
312     return self._checkIntVariable('dry-run')